WebInputStream: cancel() during connect() on macOS is lost when it lands before the task exists, and connect() waits out the timeout

#1747 · closed · 1 comments

View on GitHub ↗

jdf

### Detailed steps on how to reproduce the bug 1. On macOS, in a process that has made no web request yet, construct a `WebInputStream` toward an address that does not answer (a blackhole such as `http://10.255.255.1/`), with `withConnectionTimeout (10000)`. 2. On a thread, call `connect (nullptr)`. 3. A few tens of milliseconds later, from another thread, call `cancel()` on the stream, then join the first thread. 4. Time how long the join takes. A test that does exactly this lives in our tree and runs on every landing, inside a larger suite; there it passes. Run on its own as the first web request after the machine boots, it takes the full connection timeout. The interval it depends on is wider then because the first task in a process also constructs the shared `NSURLSession`, and the first request after boot pays for the system's network stack as well. ### What is the expected behaviour? `cancel()` interrupts `connect()`, which returns `false` within milliseconds, whenever it is called. ### What is the actual behaviour? When `cancel()` runs after `connect()` has created its connection state but before `URLConnectionState::start` has assigned the task token, `connect()` waits out the whole connection timeout. In `juce_Network_mac.mm`, `WebInputStream::Pimpl::cancel` takes `createConnectionLock`, and if the connection exists calls `URLConnectionState::cancel`, which cancels whatever task `token` holds. `URLConnectionState::start` creates the `TaskToken` (which creates and resumes the `NSURLSessionTask`, constructing the shared session the first time) and only then, under `mutex`, assigns it to `token` and starts waiting for the state to leave `beforeStart`. A cancel that lands between `createConnection` and that assignment cancels an empty token and sets `hasBeenCancelled`, which nothing reads again; `start` then resumes the task and waits for the response or the timeout. Observed on JUCE 9.0.2: the first run of the test after a boot of a macOS 26 guest took 10.19 s and failed its 5 s bound; nine further processes in the same guest took 55 ms each. With the connection state recording the cancel under `mutex` and `start` honouring it right after the assignment, two boots with the test run first and nine more runs each stayed under 0.35 s. The Windows implementation already re-checks `hasBeenCancelled` under the lock at each handle assignment. Pull request to follow. ### Operating systems macOS ### What versions of the operating systems? macOS 26.6 ### Architectures Arm64/aarch64 ### Stacktrace _No response_ ### Plug-in formats (if applicable) _No response_ ### Plug-in host applications (DAWs) (if applicable) _No response_ ### Testing on the `develop` branch I have not tested against the `develop` branch ### Code of Conduct - [X] I agree to follow the Code of Conduct

Comments

reuk

Fixed here: https://github.com/juce-framework/JUCE/commit/1c66556aedb2a801854ec972f2a26ebd1db0ccbd