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

Fix DemandBuffer Data Races #3514

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jpsim
Copy link

@jpsim jpsim commented Nov 29, 2024

We're still seeing crashes due to what look like data races in DemandBuffer. Here, I'm making speculative fixes based on what appears to have helped in #3447:

In buffer(value:):

  • Check if the demand is unlimited and, if so, unlock before calling subscriber.receive(value).
  • If the demand is not unlimited, append the value to the buffer and call flush() after unlocking.

In flush(adding:):

  • Process values in a loop, acquiring and releasing the lock around shared state access.
  • Unlock before calling subscriber.receive(value) to avoid holding the lock during subscriber calls.
  • After sending a value, lock again to update demandState.requested with any additional demand returned by the subscriber.
  • Ensure that subscriber.receive(completion:) is called outside the lock.

Also add a new DemandBufferTests test file. I added some tests that I was hoping would fail without the above changes, but they all pass both before and after, even with ThreadSanitizer enabled.

We're still seeing crashes due to what look like data races in
`DemandBuffer`. Here, I'm making fixes based on what appears to have
helped in
pointfreeco#3447:

In `buffer(value:)`:

* Check if the demand is unlimited and, if so, unlock before calling
  `subscriber.receive(value)`.
* If the demand is not unlimited, append the value to the buffer and
  call `flush()` after unlocking.

In `flush(adding:)`:

* Process values in a loop, acquiring and releasing the lock around
  shared state access.
* Unlock before calling `subscriber.receive(value)` to avoid holding
  the lock during subscriber calls.
* After sending a value, lock again to update `demandState.requested`
  with any additional demand returned by the subscriber.
* Ensure that `subscriber.receive(completion:)` is called outside the
  lock.

Also add a new `DemandBufferTests` test file. I added some tests that
I was hoping would fail without the above changes, but they all pass
both before and after, even with ThreadSanitizer enabled.
@stephencelis
Copy link
Member

@jpsim Create is more or less vendored from https://github.com/CombineCommunity/CombineExt/blob/main/Sources/Operators/Create.swift

Maybe there are some fixes upstream that we could bring in? And/or maybe you should share these fixes back with CombineExt?

/cc @freak4pc

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.

2 participants