Skip to content

[Bug] HystrixContextScheduler silently swallows exceptions by ignoring Future.get() after submit() #2116

@QiuYucheng2003

Description

@QiuYucheng2003

Description
The HystrixContextScheduler (specifically its internal ThreadPoolWorker) uses ThreadPoolExecutor.submit() to schedule tasks. While it captures the returned Future object, it wraps it in a FutureCompleterWithConfigurableInterrupt which only uses the Future for cancellation.

Crucially, future.get() is never called anywhere in the implementation.

According to the ExecutorService contract, exceptions thrown by tasks submitted via submit() are captured and stored within the Future. If get() is never called, these exceptions are never thrown to the caller or the uncaught exception handler. This results in a Silent Failure (Exception Not Caught - ENC), where initialization errors or runtime exceptions within the scheduler logic simply disappear without any logs.

Affected Component

  • Class: com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler
  • Inner Class: ThreadPoolWorker
  • Method: schedule(Action0 action)

Root Cause Analysis
In HystrixContextScheduler.java:

// Inside ThreadPoolWorker.schedule(Action0 action)
ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPool.getExecutor();

// 1. Task is submitted, returning a Future. Exceptions are now encapsulated in 'f'.
FutureTask<?> f = (FutureTask<?>) executor.submit(sa);

// 2. 'f' is passed to a helper, but ONLY for cancellation purposes.
sa.add(new FutureCompleterWithConfigurableInterrupt(f, shouldInterruptThread, executor));

return sa;


And in the helper class:
private static class FutureCompleterWithConfigurableInterrupt implements Subscription {
    // ...
    @Override
    public void unsubscribe() {
        executor.remove(f);
        // 3. The Future is used here only to cancel the task.
        // MISSING: No logic to check f.get() for exceptions if the task completed abnormally.
        if (shouldInterruptThread.call()) {
            f.cancel(true);
        } else {
            f.cancel(false);
        }
    }
}

Expected Behavior When a scheduled action fails, the exception should not be swallowed. Since submit() is used, the implementation should ensure that exceptions are either:
1. Propagated to a registered error handler (e.g., via HystrixPlugins or RxJava plugins).
2. Or, execute() should be used instead of submit() if the Future is only needed for cancellation (though execute does not return a Future, making standard cancellation harder, implying submit + get handling is the correct path).

Actual Behavior Any unchecked exception thrown during the execution of the ScheduledAction (that isn't handled internally by RxJava's SafeSubscriber logic) is caught by the FutureTask, stored, and effectively ignored because get() is never invoked.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions