Skip to content

Conversation

@rschmitt
Copy link
Contributor

I discovered a race condition while investigating a sporadic test failure in the client:

[ERROR]   HttpMinimalIntegrationTests$H2Tls>AbstractHttpAsyncFundamentalsTest.testSequentialGetRequests:86 » Execution org.apache.hc.core5.http2.H2StreamResetException: Cancelled

It turned out that two threads are somehow racing when calling ComplexCancellable.setDependency():

setDependency dep=org.apache.hc.client5.http.impl.async.MinimalH2AsyncClient$$Lambda$655/251536271@633478cb ct=ForkJoinPool-1-worker-2
setDependency dep=[id=1, reserved=false, removeClosed=false, localClosed=false, localReset=false] ct=httpclient-dispatch-1
setDependency dep=org.apache.hc.client5.http.impl.async.MinimalH2AsyncClient$$Lambda$655/251536271@2e66671a ct=ForkJoinPool-1-worker-2
setDependency dep=[id=3, reserved=false, removeClosed=false, localClosed=false, localReset=false] ct=httpclient-dispatch-1
setDependency dep=org.apache.hc.client5.http.impl.async.MinimalH2AsyncClient$$Lambda$655/251536271@32f4e96 ct=ForkJoinPool-1-worker-2
setDependency dep=[id=5, reserved=false, removeClosed=false, localClosed=false, localReset=false] ct=httpclient-dispatch-1

The implementation of setDependency was performing a single CAS and interpreting failure to mean that the mark bit had been set -- in other words, that another thread had called cancel(), rather than setDependency().

This change corrects the implementation of setDependency by having it run the CAS in a loop. On each update failure, it checks if the mark bit has been set (nothing ever unsets it). If so, it calls dependency.cancel() and returns; else, it just tries again.

I discovered a race condition while investigating a sporadic test
failure in the client:

```
[ERROR]   HttpMinimalIntegrationTests$H2Tls>AbstractHttpAsyncFundamentalsTest.testSequentialGetRequests:86 » Execution org.apache.hc.core5.http2.H2StreamResetException: Cancelled
```

I discovered that two threads are somehow racing when calling
`ComplexCancellable.setDependency()`:

```
setDependency dep=org.apache.hc.client5.http.impl.async.MinimalH2AsyncClient$$Lambda$655/251536271@633478cb ct=ForkJoinPool-1-worker-2
setDependency dep=[id=1, reserved=false, removeClosed=false, localClosed=false, localReset=false] ct=httpclient-dispatch-1
setDependency dep=org.apache.hc.client5.http.impl.async.MinimalH2AsyncClient$$Lambda$655/251536271@2e66671a ct=ForkJoinPool-1-worker-2
setDependency dep=[id=3, reserved=false, removeClosed=false, localClosed=false, localReset=false] ct=httpclient-dispatch-1
setDependency dep=org.apache.hc.client5.http.impl.async.MinimalH2AsyncClient$$Lambda$655/251536271@32f4e96 ct=ForkJoinPool-1-worker-2
setDependency dep=[id=5, reserved=false, removeClosed=false, localClosed=false, localReset=false] ct=httpclient-dispatch-1
```

The implementation of `setDependency` was performing a single CAS and
interpreting failure to mean that the mark bit had been set -- in other
words, that another thread had called `cancel()`, rather than
`setDependency()`.

This change corrects the implementation of `setDependency` by having it
run the CAS in a loop. On each update failure, it checks if the mark bit
has been set (nothing ever unsets it). If so, it calls
`dependency.cancel()` and returns; else, it just tries again.
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.

1 participant