Skip to content

Make Abort() interrupt the libcurl and NSURLSession transports#34

Draft
bkaradzic-microsoft wants to merge 1 commit into
BabylonJS:mainfrom
bkaradzic-microsoft:urllib-abort-backends
Draft

Make Abort() interrupt the libcurl and NSURLSession transports#34
bkaradzic-microsoft wants to merge 1 commit into
BabylonJS:mainfrom
bkaradzic-microsoft:urllib-abort-backends

Conversation

@bkaradzic-microsoft

Copy link
Copy Markdown
Member

Makes UrlRequest::Abort() actually interrupt the libcurl and NSURLSession transports. Today Abort() cancels m_cancellationSource, but only the Windows backend observes it (its WinRT continuations are guarded by .then(m_cancellationSource)); the curl and NSURLSession backends ignore it, so an in-flight request blocks until the transport's own timeout and a consumer's cancellation can't actually stop the work. This was flagged as a follow-up in #31 ("UrlRequest::Abort() only has an effect on the Windows backend today").

It's the transport-side prerequisite for fetch AbortSignal support in JsRuntimeHost (BabylonJS/JsRuntimeHost#196): the polyfill there wires init.signal to UrlRequest::Abort(), which currently no-ops on Linux/Apple.

Changes

  • libcurl: curl_easy_perform runs synchronously on a worker thread and doesn't watch the cancellation source. Install a CURLOPT_XFERINFOFUNCTION progress callback (with CURLOPT_NOPROGRESS=0) that returns non-zero once m_cancellationSource is cancelled — libcurl invokes it periodically during the transfer (including while connecting), so curl_easy_perform returns CURLE_ABORTED_BY_CALLBACK, which PerformAsync already records as the transport error. cancelled() is safe to poll from the worker thread while Abort() is called from another. CURLE_ABORTED_BY_CALLBACK is added to the stable-symbol map.
  • NSURLSession: register a cancellation listener that [task cancel]s the in-flight data task; its completion handler then fires with NSURLErrorCancelled. The listener ticket is a derived member, released before m_cancellationSource (a base member).

Tests

Adds AbortInterruptsInFlightRequest, backed by a new offline-deterministic HangingServer fixture (a loopback listener that accepts but never responds, so the request hangs until aborted). It asserts the request is interrupted promptly and reports CURLE_ABORTED_BY_CALLBACK(42) / NSURLErrorCancelled(-999). Skipped on the Windows backend, which observes Abort() but doesn't populate the transport-error accessors.

⚠️ Validation status (why this is a draft)

I could only build/run on Win32, where the abort test skips (the Windows backend already aborts) and the success case passes — so the test file compiles and the harness is sound, but the curl and NSURLSession code paths are not yet exercised. UrlLib main has no CI yet (the workflows are in #32, still open), so this PR doesn't get automated Linux/macOS runs.

Before merging, this needs the curl/NSURLSession paths validated — easiest once #32 lands (its Linux + macOS jobs run UrlLibTests), or via the fork-mirror approach used for #31, or a maintainer build on Linux/macOS. Happy to set up whichever you prefer.

Related: #31, #32, BabylonJS/JsRuntimeHost#196.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

UrlRequest::Abort() cancels m_cancellationSource, but only the Windows backend
observed it (its WinRT continuations are guarded by .then(m_cancellationSource)).
The libcurl and NSURLSession backends ignored it, so an in-flight request blocked
until the transport's own timeout -- and a consumer's cancellation (e.g. fetch's
AbortSignal in JsRuntimeHost) could not actually stop the work. Noted as a
follow-up in BabylonJS#31.

- libcurl: curl_easy_perform runs synchronously on a worker thread and does not
  watch the cancellation source. Install a CURLOPT_XFERINFOFUNCTION progress
  callback (with CURLOPT_NOPROGRESS=0) that returns non-zero once
  m_cancellationSource is cancelled; curl_easy_perform then returns
  CURLE_ABORTED_BY_CALLBACK, which PerformAsync records as the transport error
  (cancelled() is safe to poll cross-thread). CURLE_ABORTED_BY_CALLBACK is added
  to the stable-symbol map.
- NSURLSession: register a cancellation listener that [task cancel]s the in-flight
  data task; its completion handler then fires with NSURLErrorCancelled. The
  listener ticket is a derived member, released before m_cancellationSource.

Adds an offline-deterministic abort test backed by a loopback server that accepts
but never responds (so the request hangs until aborted), asserting the request is
interrupted promptly with CURLE_ABORTED_BY_CALLBACK / NSURLErrorCancelled. Skipped
on the Windows backend, which observes Abort() but does not populate the
transport-error accessors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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