Premature thread interruption when using observeOn operator
See original GitHub issueWhen using RxJava 2.1.3 to 2.1.6, we observed a possible racing condition which caused the observeOn thread to be interrupted prematurely.
A sample code snippet could be the following (not a complete example):
Observable.fromPublisher(publisher)
.observeOn(Schedulers.computation())
.firstElement()
.map(operation)
.blockingGet();
In the above example, observeOn operator would create a ObservableObserveOn instance. When there is the first item coming (ObservableObserveOn.ObserveOnObserver.onNext() is called), a Runnable is scheduled via the NewThreadWorker.scheduleActual(). Inside NewThreadWorker.scheduleActual(), a ScheduledRunnable is created wrapping the given Runnable, and submit the ScheduledRunnable to the executor for execution. Then the future returned by the executor is set into ScheduledRunnable via setFuture method.
The problem lies when the ScheduledRunnable.dispose() method is called between submitting to the executor and ScheduledRunnable.setFuture method by the underlying Runnable wrapped in ScheduledRunnable (which is executed in parallel by the executor). In this case, the Runnable contains the firstElement() operator would create a ObservableElementAtMaybe instance. ObservableElementAtMaybe.onNext() method would call the dispose() method before calling actual.onSuccess(). So combining these, if ScheduledRunnable.setFuture is called after ScheduledRunnable.dispose() in ObservableElementAtMaybe.onNext(), the actual.onSuccess() would be interrupted, which is not what we want.
tl;dr In the sample code, if the execution order is the following, it would create a thread interruption that interfere with the processing of the chained operators.
Thread 1: future = executor.submit(ScheduledRunnable)
Thread 2 (executor thread): ScheduledRunnable.dispose()
Thread 1: ScheduledRunnable.setFuture(future)
Thread 2: actual callback (e.g. actual.onSuccess()) <-- Thread interrupted. Throws exception when we call some synchronization methods in a non-blocking way (e.g. Semaphore.tryAcquire(0, TimeUnit.SECONDS)).
A possible solution is to change the order, i.e. call actual.onSuccess() before dispose(). I searched the code base and found out that in general, dispose() is called before the actual callback. I am not sure if there are other concerns with this specific ordering (i.e. dispose before actual callback), but it certainly sounds odd to me.
Issue Analytics
- State:
- Created 6 years ago
- Comments:13 (6 by maintainers)
I would argue that the behavior of interrupting a downstream processing thread for observeOn when the observeOn is disposed is questionable. I think it makes perfect sense to interrupt the upstream threads when an operator is disposed, but definitely not downstream threads.
If your argument stands, i.e. we should not have interrupt-sensitive code in RxJava callbacks, why cancel the future with interrupt in the first place?
I’m not making any judgement at all on your personal contribution. Just trying making the point that this issue perhaps should not be closed without a fix, since it creates surprising and difficult to debug breakages downstream.