Skip to content

WriteAsync(Stream) with a cancelled CancellationToken throws MagickCorruptImageErrorException instead of OperationCanceledException (regression in 14.15.0, still present in 14.16.0) #2066

Description

@adutton

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions