Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions src/Magick.NET/Helpers/AsyncStreamWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,18 @@ public async Task ReadAsync(Action action, CancellationToken cancellationToken)
TaskScheduler.Default);
var readTask = ReadAsync(cancellationToken);

await Task.WhenAll(actionTask, readTask).ConfigureAwait(false);
try
{
await Task.WhenAll(actionTask, readTask).ConfigureAwait(false);
}
catch when (_exceptionThrown)
{
// The native operation failed because a stream operation failed. When that failure was
// caused by a cancellation the exception of the native operation (e.g. a corrupt image
// exception from the coder) should be replaced with an OperationCanceledException.
cancellationToken.ThrowIfCancellationRequested();
throw;
}

if (_exceptionThrown)
cancellationToken.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -88,7 +99,19 @@ public async Task WriteAsync(Action action, CancellationToken cancellationToken)
var readTask = ReadAsync(cancellationToken);
var writeTask = WriteAsync(cancellationToken);

await Task.WhenAll(actionTask, readTask, writeTask).ConfigureAwait(false);
try
{
await Task.WhenAll(actionTask, readTask, writeTask).ConfigureAwait(false);
}
catch when (_exceptionThrown)
{
// The native operation failed because a stream operation failed. When that failure was
// caused by a cancellation the exception of the native operation (e.g. "Output file
// write error --- out of disk space?" from the jpeg coder) should be replaced with
// an OperationCanceledException.
cancellationToken.ThrowIfCancellationRequested();
throw;
}

if (_exceptionThrown)
cancellationToken.ThrowIfCancellationRequested();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,28 @@ unsafe void ReadSync()

await Assert.ThrowsAsync<OperationCanceledException>(() => wrapper.ReadAsync(ReadSync, cancellationTokenSource.Token));
}

[Fact]
public async Task ShouldThrowExceptionWhenOperationIsCancelledAndActionThrowsException()
{
using var stream = new MemoryStream();
using var wrapper = AsyncStreamWrapper.CreateForReading(stream);

using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();

unsafe void ReadSync()
{
var buffer = new byte[10];
fixed (byte* p = buffer)
{
var count = wrapper.Read((IntPtr)p, (UIntPtr)10, IntPtr.Zero);
if (count == -1)
throw new InvalidOperationException("Simulates the exception the native code throws when a read fails (e.g. a corrupt image exception).");
}
}

await Assert.ThrowsAsync<OperationCanceledException>(() => wrapper.ReadAsync(ReadSync, cancellationTokenSource.Token));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,28 @@ unsafe void WriteSync()

await Assert.ThrowsAsync<OperationCanceledException>(() => wrapper.WriteAsync(WriteSync, cancellationTokenSource.Token));
}

[Fact]
public async Task ShouldThrowExceptionWhenOperationIsCancelledAndActionThrowsException()
{
using var stream = new MemoryStream();
using var wrapper = AsyncStreamWrapper.CreateForWriting(stream);

using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();

unsafe void WriteSync()
{
var buffer = new byte[5];
fixed (byte* p = buffer)
{
var count = wrapper.Write((IntPtr)p, (UIntPtr)5, IntPtr.Zero);
if (count == -1)
throw new InvalidOperationException("Simulates the exception the native code throws when a write fails (e.g. the jpeg coder).");
}
}

await Assert.ThrowsAsync<OperationCanceledException>(() => wrapper.WriteAsync(WriteSync, cancellationTokenSource.Token));
}
}
}
12 changes: 12 additions & 0 deletions tests/Magick.NET.Tests/MagickImageTests/TheReadAsyncMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using Xunit;
Expand Down Expand Up @@ -198,6 +199,17 @@ public async Task ShouldThrowExceptionWhenStreamIsNull()
await Assert.ThrowsAsync<ArgumentNullException>("stream", () => image.ReadAsync((Stream)null!, TestContext.Current.CancellationToken));
}

[Fact]
public async Task ShouldThrowOperationCanceledExceptionWhenOperationIsCancelled()
{
using var image = new MagickImage();
using var stream = File.OpenRead(Files.ImageMagickJPG);
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();

await Assert.ThrowsAsync<OperationCanceledException>(() => image.ReadAsync(stream, cancellationTokenSource.Token));
}

[Fact]
public async Task ShouldThrowExceptionWhenStreamIsEmpty()
{
Expand Down
14 changes: 14 additions & 0 deletions tests/Magick.NET.Tests/MagickImageTests/TheWriteAsyncMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ImageMagick;
using ImageMagick.Formats;
Expand Down Expand Up @@ -225,6 +226,19 @@ public async Task ShouldThrowExceptionWhenStreamIsNull()

await Assert.ThrowsAsync<ArgumentNullException>("stream", () => image.WriteAsync((Stream)null!, TestContext.Current.CancellationToken));
}

[Fact]
public async Task ShouldThrowOperationCanceledExceptionWhenOperationIsCancelled()
{
using var image = new MagickImage(MagickColors.Red, 100, 100);
image.Format = MagickFormat.Jpeg;

using var stream = new MemoryStream();
using var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();

await Assert.ThrowsAsync<OperationCanceledException>(() => image.WriteAsync(stream, cancellationTokenSource.Token));
}
}

public class WithStreamAndMagickFormat
Expand Down