Add AbortSignal support for image processing cancellation - #4580
Conversation
|
Thanks for the PR, it looks like this is mostly LLM-generated. At first glance:
|
|
Yes, i'm using LLM for my work, but all of this is revewed by me in any case. I'm not very familiar with sharp codebase, so any feedback is more than welcome. We are starting to use sharp on penpot and cancellation was one thing i have missed on the API, this is the reason of this PR. |
I'm not in position on take this decision from the beginning, i mean, this is something that only you can know, if you provide me a guidance i will try to adapt the PR to the changes. Do you want me to do this also? (in my opinion this can be addressed in other PR)
ACK, i will try to address this |
14c93f6 to
675e71e
Compare
Prepare for upcoming deprecation of resolveWithObject option by enforcing separation between signal and resolveWithObject options in toBuffer(). Changes: - Update TypeScript definitions to remove resolveWithObject from signal overloads - Add validation in toBuffer() to reject resolveWithObject when signal is present - Add tests to verify the new behavior This protects users from future breaking changes when resolveWithObject is deprecated and always set to true. Users can now use either signal (for cancellation) OR resolveWithObject (for structured output), but not both. All 1825 tests passing with 100% coverage.
675e71e to
552e503
Compare
|
I think i have addressed the second point of the feedback. |
Summary
This PR adds support for the standard
AbortSignalAPI to allow cancellation of in-flight image processing operations. The signal can be provided via thesharp()constructor options or output method options (toBuffer(),toFile()).Closes #4579
Motivation
Image processing operations can be long-running, especially for:
Currently, once processing starts, there's no way to cancel it. This leads to:
The existing
timeout()option provides time-based cancellation, but doesn't support event-driven or user-initiated cancellation patterns that are standard in modern Node.js APIs likefetch(),fs.promises, and streams.Implementation Approach
Cross-thread communication
The core challenge is that image processing happens on a libuv worker thread (via the C++ addon), while the abort signal is triggered on the main JavaScript thread. We need a way to communicate the cancellation across this boundary.
Solution: Use a
SharedArrayBuffer(1)as a lock-free flag shared between threads.Why SharedArrayBuffer?
SharedArrayBufferprovides genuine cross-thread visibility without synchronization primitivesnapi_threadsafe_functionfor this simple signalIntegration with existing timeout mechanism
The abort mechanism reuses the same libvips progress callback infrastructure as the existing
timeout()feature:VipsImagewhen processing beginsAtomics.store()from the abort listener), it callsvips_image_set_kill()to stop processingWhy reuse the timeout mechanism?
Error handling
When an operation is aborted, the error is converted to a standard
AbortError:This matches the standard
DOMExceptionpattern used byfetch()and other async APIs, making it easy for developers to handle cancellation consistently across their application.Already-aborted signals
If a signal is already aborted when passed to sharp, the operation rejects immediately without invoking the native pipeline. This avoids unnecessary work and provides fast feedback.
API Design
The signal can be provided in two places:
1. Constructor options
Applies to all subsequent operations on this sharp instance. Convenient for simple cases where the entire pipeline should be cancellable.
2. Output method options
Applies to that specific output operation. Useful when you have multiple outputs from the same input and want different cancellation logic for each.
Why both?
Testing
Comprehensive tests cover:
toFile()All existing tests continue to pass with 100% code coverage maintained.
Backward compatibility
This change is fully backward compatible:
signaloption is optionalUse cases
1. HTTP request cancellation
2. User-initiated cancellation
3. Batch processing with early termination
4. Timeout with custom logic
Notes
This implementation provides a foundation for cancellation support that aligns with modern Node.js patterns while maintaining full backward compatibility.