Description
Since 14.15.0, cancelling MagickImage.WriteAsync(Stream, MagickFormat, CancellationToken) no longer surfaces as an OperationCanceledException. Instead, the aborted stream write propagates through the native coder as a write failure, producing a misleading coder-specific exception:
- JPEG:
MagickCorruptImageErrorException: Output file write error --- out of disk space? ' @ error/jpeg.c/JPEGErrorHandler/614`
- PNG:
MagickBlobErrorException: unable to write blob ' @ error/png.c/MagickPNGError/1308`
The PNG variant shows this is not specific to the JPEG coder; the cancellation appears to be observed by the stream write callback layer (AsyncStreamWrapper?) and reported to ImageMagick as a failed write rather than short-circuiting as a cancellation.
On 14.14.0 the same code throws TaskCanceledException (derives from OperationCanceledException), as expected.
Steps to Reproduce
using ImageMagick;
using var image = new MagickImage(MagickColors.SteelBlue, 500, 500);
using var cts = new CancellationTokenSource();
cts.Cancel();
using var output = new MemoryStream();
await image.WriteAsync(output, MagickFormat.Jpeg, cts.Token);
A pre-cancelled token reproduces it deterministically (100% of calls). A token cancelled mid-write (e.g. new CancellationTokenSource(TimeSpan.FromMilliseconds(5)) with a larger image) produces the same exception whenever the cancellation lands during the write.
Expected Behavior
OperationCanceledException (or TaskCanceledException), as in 14.14.0 and earlier.
Actual Behavior
ImageMagick.MagickCorruptImageErrorException: Output file write error --- out of disk space? `' @ error/jpeg.c/JPEGErrorHandler/614
at ImageMagick.MagickImage.<>c__DisplayClass719_0.<WriteAsync>b__0() in /_/src/Magick.NET/MagickImage.cs:line 7576
at ImageMagick.AsyncStreamWrapper.<>c__DisplayClass12_0.<WriteAsync>b__0() in /_/src/Magick.NET/Helpers/AsyncStreamWrapper.cs:line 73
...
at ImageMagick.AsyncStreamWrapper.WriteAsync(Action action, CancellationToken cancellationToken) in /_/src/Magick.NET/Helpers/AsyncStreamWrapper.cs:line 87
at ImageMagick.MagickImage.WriteAsync(Stream stream, CancellationToken cancellationToken) in /_/src/Magick.NET/MagickImage.cs:line 7559
at ImageMagick.MagickImage.WriteAsync(Stream stream, MagickFormat format, CancellationToken cancellationToken) in /_/src/Magick.NET/MagickImage.cs:line 7626
Version Matrix
| Package |
Result |
| Magick.NET-Q8-AnyCPU 14.14.0 |
TaskCanceledException (correct) |
| Magick.NET-Q8-AnyCPU 14.15.0 |
MagickCorruptImageErrorException |
| Magick.NET-Q8-AnyCPU 14.16.0 |
MagickCorruptImageErrorException (the #2056 thread pool starvation fix did not change this) |
Reproduced on Windows 11 (net10.0 console app, Q8 AnyCPU) and on Linux x64 containers (Q8 OpenMP) in production.
Real-World Impact
In an ASP.NET Core image-serving endpoint we pass HttpContext.RequestAborted into the resize pipeline. Every client that disconnects mid-download (routine at scale) now produces an unhandled MagickCorruptImageErrorException claiming "out of disk space", instead of the OperationCanceledException our pipeline treats as routine. After upgrading to 14.15.0 this fired several thousand times per hour and was initially misdiagnosed as node disk exhaustion. We have pinned to 14.14.0 for now, which keeps us on a version with known security advisories, so we would love to see this fixed.
Description
Since 14.15.0, cancelling
MagickImage.WriteAsync(Stream, MagickFormat, CancellationToken)no longer surfaces as anOperationCanceledException. Instead, the aborted stream write propagates through the native coder as a write failure, producing a misleading coder-specific exception:MagickCorruptImageErrorException: Output file write error --- out of disk space?' @ error/jpeg.c/JPEGErrorHandler/614`MagickBlobErrorException: unable to write blob' @ error/png.c/MagickPNGError/1308`The PNG variant shows this is not specific to the JPEG coder; the cancellation appears to be observed by the stream write callback layer (
AsyncStreamWrapper?) and reported to ImageMagick as a failed write rather than short-circuiting as a cancellation.On 14.14.0 the same code throws
TaskCanceledException(derives fromOperationCanceledException), as expected.Steps to Reproduce
A pre-cancelled token reproduces it deterministically (100% of calls). A token cancelled mid-write (e.g.
new CancellationTokenSource(TimeSpan.FromMilliseconds(5))with a larger image) produces the same exception whenever the cancellation lands during the write.Expected Behavior
OperationCanceledException(orTaskCanceledException), as in 14.14.0 and earlier.Actual Behavior
Version Matrix
TaskCanceledException(correct)MagickCorruptImageErrorExceptionMagickCorruptImageErrorException(the #2056 thread pool starvation fix did not change this)Reproduced on Windows 11 (net10.0 console app, Q8 AnyCPU) and on Linux x64 containers (Q8 OpenMP) in production.
Real-World Impact
In an ASP.NET Core image-serving endpoint we pass
HttpContext.RequestAbortedinto the resize pipeline. Every client that disconnects mid-download (routine at scale) now produces an unhandledMagickCorruptImageErrorExceptionclaiming "out of disk space", instead of theOperationCanceledExceptionour pipeline treats as routine. After upgrading to 14.15.0 this fired several thousand times per hour and was initially misdiagnosed as node disk exhaustion. We have pinned to 14.14.0 for now, which keeps us on a version with known security advisories, so we would love to see this fixed.