Fix a race condition that may make OperatorMaterialize emit too many terminal notifications#5850
Conversation
Let's assume the source generates N item, completes and the consumer requests N notifications. Since the source completion is another Notification, it can't be emitted unless the consumer request yet another notification. Since this can happen after the original |
|
Thanks! I get it. You're right, somehow I missed the fact that BTW: This is not the real cause of the bug I'm hunting for, because I've just found another occurrence that was not invoking iterators / lists at all. So I dug more into it, and I found another race in OperatorMerge. I submitted a PR. |
davidmoten
left a comment
There was a problem hiding this comment.
Great find @pkolaczk, thanks!
|
Unit test confirms that without the patch |
|
May I rebase and fix the commit message of the fix? It says "miss the item" while in fact it may emit too many items. |
terminal notification more than once The guards in `OperatorMaterialize.ParentSubscriber#drain` were never working, because `busy` was actually never set to true. Therefore it was possible that the `drain` loop was executed by more than one thread concurrently, which could led to undefined behavior. This fix sets `busy` to true at the entry of `drain`.
|
If you really want to. |
3e754d9 to
ad5b16d
Compare
|
Misleading commit messages are bad. |

We're using RxJava 1.3.5 and one of our tests occasionally fails (very hard to reproduce). The test asserts that
anObservable.toBlocking.toList(RxScala) expression throws a particular exception, because we expect the observable to be in error state by emitting a singleonError. However, in rare cases, we observe that there is no exception thrown, and an empty list is returned instead. We verified that the problem is not the observable we use. The observable properly fires thedoOnErrorhandler prior to the test assertion failure. Therefore we suspected something wrong happening in thetoBlocking.toListconversion. I debugged that in fact this conversion usesOperatorMaterializeas a part ofBlockingOperatorToIterator.I took a look at the
OperatorMaterializeand found an obvious bug. Thebusyvariable is never set to true anywhere, despite the comments showing the author of the code intended it to be true when thedrainloop was running.The PR fixes this problem by setting
busyto true at the entry of thedrainmethod.However I still have some doubts about the original code:
drainhave to be called fromrequestMore? Why isn't it enough to call it fromonCompletedandonError? It looks like thedrainlogic does anything only if theterminalNotificationis set, and it can be set only byonCompletedandonError.drainlogic supposed to do? Looks rather complex and the intent of it is not obvious. If we don't call it fromrequestMorethen maybe we don't need thedrainlogic at all? Why not emit the terminal notifications directly fromonErrorandonCompleted?Thanks,
Piotr Kołaczkowski