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

Publisher#flatMapConcatIterable may skip emitting items #3108

Merged
merged 1 commit into from
Nov 15, 2024

Conversation

Scottmitch
Copy link
Member

Motivation:
Publisher#flatMapConcatIterable may not emit some items due to race conditions and visibility issues. The iterator state is written to outside the scope of holding the lock. After a drain loop completes we may request 1 more iterator. However it is possible the thread emitting holds the lock while another thread invokes onNext(t). The emitting thread may not see the iterator, and instead see EmptyIterator.instance() and cause it to request 1 more item, but then the not-visible iterator contents won't be emitted.

Modifications:

  • Make FlatMapIterableSubscriber iterator state volatile and atomically update it. There is only ever 1 valid iterator because only 1 outstanding demand is issued only after the current iterator !hasNext(). The iterator state is re-read on each drain loop, and the terminal condition must atomically set to EmptyIterator.

Motivation:
Publisher#flatMapConcatIterable may not emit some items due to race
conditions and visibility issues. The iterator state is written to
outside the scope of holding the lock. After a drain loop completes
we may request 1 more iterator. However it is possible the thread
emitting holds the lock while another thread invokes onNext(t).
The emitting thread may not see the iterator, and instead see
`EmptyIterator.instance()` and cause it to request 1 more item, but
then the not-visible iterator contents won't be emitted.

Modifications:
- Make FlatMapIterableSubscriber iterator state volatile and atomically
update it. There is only ever 1 valid iterator because only 1 outstanding
demand is issued only after the current iterator `!hasNext()`. The iterator
state is re-read on each drain loop, and the terminal condition must atomically
set to EmptyIterator.
Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 Fast 2 Furious 😄

@Scottmitch Scottmitch merged commit f783e7d into apple:main Nov 15, 2024
11 checks passed
@Scottmitch Scottmitch deleted the pub_concat_map_iter branch November 15, 2024 16:14
@bryce-anderson
Copy link
Contributor

lgtm as well. Nice find.

@mgodave
Copy link
Contributor

mgodave commented Nov 15, 2024

How did we find this? Was it reported by a user?

@idelpivnitskiy
Copy link
Member

idelpivnitskiy commented Nov 15, 2024

@mgodave yes, and that user provided reproducer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants