Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

使用问题:SheduledExecutorService设置releaseTtlValueReferenceAfterCall时,似乎不太正常 #379

Closed
lylylyly opened this issue May 20, 2022 · 4 comments
Assignees

Comments

@lylylyly
Copy link

lylylyly commented May 20, 2022

注意到TtlCallable上有一个参数releaseTtlValueReferenceAfterCall,请问这个参数的设计意图是要解决:

某些意料之外情况下复用线程时,旧的TransmittableThreadLocal有可能未被正常清除,导致引用到错误的上下文吗?
我在issue里似乎没找到有关于ScheduledExecutorService的提问。

我这里有一个使用场景遇到一个问题,不知道用法对不对,或者有是否有其他最佳实践可用:

public static void main(String[] arg) throws Exception {

    ScheduledExecutorService testScheduled =  new ScheduledExecutorService() {

        private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("testSchedule"));

        protected  Callable mdc(Callable callable) {
            return TtlCallable.get(() -> {
                Object var1;
                try {
                    var1 = callable.call();
                } finally {
                }

                return var1;
            }, true);
        }

        protected Runnable mdc(Runnable callable) {
            return TtlRunnable.get(() -> {
                try {
                    callable.run();
                } finally {
                }

            }, true);
        }

        public void shutdown() {
            this.scheduledExecutorService.shutdown();
        }

        public List<Runnable> shutdownNow() {
            return this.scheduledExecutorService.shutdownNow();
        }

        public boolean isShutdown() {
            return this.scheduledExecutorService.isShutdown();
        }

        public boolean isTerminated() {
            return this.scheduledExecutorService.isTerminated();
        }

        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
            return this.scheduledExecutorService.awaitTermination(timeout, unit);
        }

        public Future submit(Callable task) {
            return this.scheduledExecutorService.submit(this.mdc(task));
        }

        public  Future submit(Runnable task, Object result) {
            return this.scheduledExecutorService.submit(this.mdc(task), result);
        }

        public Future<?> submit(Runnable task) {
            return this.scheduledExecutorService.submit(this.mdc(task));
        }

        @Override
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
            return this.scheduledExecutorService.invokeAll((Collection) tasks.stream().map(this::mdc).collect(Collectors.toList()));
        }

        @Override
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
            return this.scheduledExecutorService.invokeAll((Collection) tasks.stream().map(this::mdc).collect(Collectors.toList()), timeout, unit);
        }

        @Override
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
            return null;
        }

        @Override
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
            return null;
        }

        public void execute(Runnable command) {
            this.scheduledExecutorService.execute(this.mdc(command));
        }

        public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
            return this.scheduledExecutorService.schedule(this.mdc(command), delay, unit);
        }

        public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
            return this.scheduledExecutorService.schedule(this.mdc(callable), delay, unit);
        }

        public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
            return this.scheduledExecutorService.scheduleAtFixedRate(this.mdc(command), initialDelay, period, unit);
        }

        public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
            return this.scheduledExecutorService.scheduleWithFixedDelay(this.mdc(command), initialDelay, delay, unit);
        }
    };

    Runnable run = () -> {
        System.out.println("run一下");
    };
    testScheduled.scheduleWithFixedDelay(run, 5, 5, TimeUnit.SECONDS);

    LockSupport.parkNanos(TimeUnit.HOURS.toNanos(1));
}

这段定时调度的代码只会执行一次,第二次执行会在TtlCallable处抛异常终止。

解释下releaseTtlValueReferenceAfterCall我设置为true的原因:

线上大规模使用TransmittableThreadLocal时确实时不时遇到没清理上下文,引用到错误的上下文的问题,设置true后,这个问题的频率大幅降低几乎不见。

然鹅对于ScheduledExecutorRunnableCallable的重用似乎是无法避免的,这与TtlCallable的设计似乎有冲突,请问对于我的应用场景,本项目有合理的支持方式或者考虑吗?叩谢

@oldratlee oldratlee added the ❓question Further information is requested label May 20, 2022
@oldratlee
Copy link
Member

oldratlee commented May 20, 2022

某些意料之外情况下复用线程时,旧的TransmittableThreadLocal有可能未被正常清除,导致引用到错误的上下文吗?

简单回答:正确使用TransmittableThreadLocal的前提下,不会出现:未被正常清除,导致引用到错误的上下文;否则是TransmittableThreadLocalBug@lylylyly

当然上面的说明,没什么用,不能解决问题。😁

有用的是,一个极简可运行的复现问题的Demo代码,

  • 方便大家一起确认&排查问题。(无论是TTL的问题还是业务的)
  • 可以具体说明:
    • 什么是『意料之外情况下复用线程』?
      • 这个意料之外,是合理的(需求)?还是错误的使用?
    • 什么是『未被正常清除』?
    • ……

这段定时调度的代码只会执行一次,第二次执行会在TtlCallable处抛异常终止。

设置releaseTtlValueReferenceAfterCalltrue,即任务不能多次运行,否则会抛异常。(通过这个变量名 或是 TtlRunnable的代码实现 可以确认到。)

在你的业务中,任务应该是 只会一次执行吗? @lylylyly

  • 如果不是:
    • 即是业务要执行这个任务多次。
    • 不能 设置releaseTtlValueReferenceAfterCalltrue,否则业务功能不能正常运行了。
    • 在上面你写的demo中,定时调度任务(testScheduled.scheduleWithFixedDelay)是这种情况。
  • 如果是:
    • TtlCallable处抛异常终止,说明逻辑有Bug了。
    • 排查一下解决逻辑的Bug吧。

线上大规模使用TransmittableThreadLocal时,确实时不时遇到没清理上下文,引用到错误的上下文的问题,…

『确实时不时遇到没清理上下文,引用到错误的上下文的问题』,这是Bug
需要确认是在业务逻辑的实现或使用TTL的哪儿引起的;要挖一下,fix bug。 @lylylyly

关于 Bug vs Failure

有Bug,正面fix bug;对于 系统可靠性稳定性 与 后期可维护性,应该是最有效经济的做法。一个Bug绕路了,可能就变成神坑/Feature了;可能需要梳理一下业务逻辑实现,然后做好填坑。😄 ❤️
关于系统可靠性,个人的一个资料 软件可靠性设计的实践 / v0.9.2.pptx
bug vs feature

关于 上下文 与 TransmittableThreadLocal

总的来说,

  • 上下文 是 由业务来设置的;需要 正确设计 处理上下文的业务逻辑
  • 上下文的处理 是 复杂的(尤其在多线程/并发场景下)

TransmittableThreadLocal因为规范化了上下文的传递过程与使用方式,可以简化 上下文处理的复杂性。更多资料:


注意到TtlCallable上有一个参数releaseTtlValueReferenceAfterCall,请问这个参数的设计意图是要解决

@lylylyly 对于业务确定任务只会运行一次的情况,
设置releaseTtlValueReferenceAfterCalltrue 有的好处 是可以

  • 安全地尽早释放掉,GC友好
  • 倒过来发现上层业务的Bug(业务实际执行了任务多次,不预期),防御式编程

@oldratlee oldratlee self-assigned this May 20, 2022
@lylylyly
Copy link
Author

好吧也就是说这个场景不能这么用了……

@oldratlee
Copy link
Member

oldratlee commented May 20, 2022

好吧也就是说这个场景不能这么用了……

是的。


我在releaseTtlValueReferenceAfterCall的 JavaDoc 上,
看看加一些向业务场景的具体使用式的说明,以方便理解使用。

@oldratlee
Copy link
Member

oldratlee commented May 22, 2022

@lylylyly 在上面我第一条回复中,补充了一些说明与资料,可以再看一下 ❤️

@oldratlee oldratlee changed the title SheduledExecutorService遭遇releaseTtlValueReferenceAfterCall时,似乎不太正常 SheduledExecutorService设置releaseTtlValueReferenceAfterCall时,似乎不太正常 May 22, 2022
@oldratlee oldratlee changed the title SheduledExecutorService设置releaseTtlValueReferenceAfterCall时,似乎不太正常 使用问题:SheduledExecutorService设置releaseTtlValueReferenceAfterCall时,似乎不太正常 May 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants