-
Notifications
You must be signed in to change notification settings - Fork 7.6k
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
Observable.delay(delayErrors=true) drops onNext signals at random when disposed of concurrently #7851
Comments
Thanks for the report. I'll look into it in the coming days. Until then, try using |
Here is a better repro: for (int attempt = 1; attempt <= 1000; attempt++) {
ConcurrentLinkedDeque<Integer> sink = new ConcurrentLinkedDeque<>();
SequentialDisposable disposable = new SequentialDisposable();
disposable.replace(Observable.range(0, 10)
.delay(1, TimeUnit.MICROSECONDS, Schedulers.computation(), true)
.doOnNext(value -> {
if (!disposable.isDisposed()) {
Schedulers.computation().scheduleDirect(disposable::dispose);
}
sink.add(value);
})
.subscribe());
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
List<Integer> values = List.copyOf(sink);
List<String> errors = new ArrayList<>();
int expected = 0;
for (int i = 0; i < values.size(); i++) {
int value = values.get(i);
if (value != expected) {
errors.add((errors.size() + 1) + ". Wrong value at index " + i + " (expected " + expected +
", was " + value + ")");
}
expected = value + 1;
}
if (!errors.isEmpty()) {
throw new AssertionError(new StringBuilder()
.append("Attempt ")
.append(attempt)
.append(" failed.\n\t\tError(s):\n\t\t\t")
.append(String.join("\n\t\t\t", errors))
.append("\n\t\tValues: ")
.append(values)
.toString());
}
} On my Macbook M2 Max, this seems to reliably reproduce the problematic behavior within the first couple attempts. |
The problem is caused by the race between the individual event emissions and the code that cancels each of the associated tasks, resulting in some of them still running. As a quick workaround, you can use disposable.replace(Observable.range(0, 10)
.delay(1, TimeUnit.MICROSECONDS, Schedulers.computation(), true)
.onTerminateDetach() // <--------------------------------------------------- I'll work out a more direct fix in a few days. |
When a subscription to an
Observable.delay(delayErrors=true)
operator is disposed of concurrently to the subscription's emissions, theonNext
signals it issues to its downstream can be dropped at random.See the following repro:
Here is an example failure:
I understand that a subscription that is actively producing signals may continue to propagate those signals to the downstream when disposed of concurrently (i.e. disposal is "eventually consistent"). But I would expect that any signals emitted during or after the race would preserve their original ordering. In other words, once
delay()
has caught up to its newly-disposed state and does not emit itsN
thonNext
signal, it seems wrong for it to go on to emit itsN+1
th signal.The text was updated successfully, but these errors were encountered: