From 37599c5a6088cc02422d0a80513b0a3481efbbc4 Mon Sep 17 00:00:00 2001 From: Kamil Sobol <61715331+kasobol-msft@users.noreply.github.com> Date: Wed, 20 May 2020 14:08:17 -0700 Subject: [PATCH 1/2] Backport deadlock fix targetting .NET Framework. --- Lib/ClassLibraryCommon/Core/TimeoutStream.cs | 38 ++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/Lib/ClassLibraryCommon/Core/TimeoutStream.cs b/Lib/ClassLibraryCommon/Core/TimeoutStream.cs index b818802ef..e8c554071 100644 --- a/Lib/ClassLibraryCommon/Core/TimeoutStream.cs +++ b/Lib/ClassLibraryCommon/Core/TimeoutStream.cs @@ -32,7 +32,7 @@ internal class TimeoutStream : Stream private readonly Stream wrappedStream; private TimeSpan readTimeout; private TimeSpan writeTimeout; - private CancellationTokenSource cancellationTokenSource; + private CancellationTokenSource cancellationTokenSource = null!; public TimeoutStream(Stream wrappedStream, TimeSpan timeout) : this(wrappedStream, timeout, timeout) { } @@ -47,7 +47,7 @@ public TimeoutStream(Stream wrappedStream, TimeSpan readTimeout, TimeSpan writeT this.writeTimeout = writeTimeout; this.UpdateReadTimeout(); this.UpdateWriteTimeout(); - this.cancellationTokenSource = new CancellationTokenSource(); + this.InitializeTokenSource(); } public override long Position @@ -140,7 +140,12 @@ public override async Task FlushAsync(CancellationToken cancellationToken) var source = StartTimeout(cancellationToken, out bool dispose); try { - await this.wrappedStream.FlushAsync(source.Token); + await this.wrappedStream.FlushAsync(source.Token).ConfigureAwait(false); + } + // We dispose stream on timeout so catch and check if cancellation token was cancelled + catch (ObjectDisposedException) when (source.IsCancellationRequested) + { + throw new OperationCanceledException(source.Token); } finally { @@ -158,7 +163,12 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, var source = StartTimeout(cancellationToken, out bool dispose); try { - return await this.wrappedStream.ReadAsync(buffer, offset, count, source.Token); + return await this.wrappedStream.ReadAsync(buffer, offset, count, source.Token).ConfigureAwait(false); + } + // We dispose stream on timeout so catch and check if cancellation token was cancelled + catch (ObjectDisposedException) when (source.IsCancellationRequested) + { + throw new OperationCanceledException(source.Token); } finally { @@ -191,7 +201,12 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc var source = StartTimeout(cancellationToken, out bool dispose); try { - await this.wrappedStream.WriteAsync(buffer, offset, count, source.Token); + await this.wrappedStream.WriteAsync(buffer, offset, count, source.Token).ConfigureAwait(false); + } + // We dispose stream on timeout so catch and check if cancellation token was cancelled + catch (ObjectDisposedException) when (source.IsCancellationRequested) + { + throw new OperationCanceledException(source.Token); } finally { @@ -204,11 +219,22 @@ public override void WriteByte(byte value) this.wrappedStream.WriteByte(value); } + private void InitializeTokenSource() + { + this.cancellationTokenSource = new CancellationTokenSource(); + this.cancellationTokenSource.Token.Register(() => DisposeStream()); + } + + private void DisposeStream() + { + this.wrappedStream.Dispose(); + } + private CancellationTokenSource StartTimeout(CancellationToken additionalToken, out bool dispose) { if (this.cancellationTokenSource.IsCancellationRequested) { - this.cancellationTokenSource = new CancellationTokenSource(); + this.InitializeTokenSource(); } CancellationTokenSource source; From 5efcb63aa9412dcdbbba1a387979d1472bbde48f Mon Sep 17 00:00:00 2001 From: Kamil Sobol <61715331+kasobol-msft@users.noreply.github.com> Date: Wed, 20 May 2020 14:15:16 -0700 Subject: [PATCH 2/2] wip --- Lib/ClassLibraryCommon/Core/TimeoutStream.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Lib/ClassLibraryCommon/Core/TimeoutStream.cs b/Lib/ClassLibraryCommon/Core/TimeoutStream.cs index e8c554071..821bf2710 100644 --- a/Lib/ClassLibraryCommon/Core/TimeoutStream.cs +++ b/Lib/ClassLibraryCommon/Core/TimeoutStream.cs @@ -143,9 +143,10 @@ public override async Task FlushAsync(CancellationToken cancellationToken) await this.wrappedStream.FlushAsync(source.Token).ConfigureAwait(false); } // We dispose stream on timeout so catch and check if cancellation token was cancelled - catch (ObjectDisposedException) when (source.IsCancellationRequested) + catch (ObjectDisposedException) { - throw new OperationCanceledException(source.Token); + source.Token.ThrowIfCancellationRequested(); + throw; } finally { @@ -166,9 +167,10 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, return await this.wrappedStream.ReadAsync(buffer, offset, count, source.Token).ConfigureAwait(false); } // We dispose stream on timeout so catch and check if cancellation token was cancelled - catch (ObjectDisposedException) when (source.IsCancellationRequested) + catch (ObjectDisposedException) { - throw new OperationCanceledException(source.Token); + source.Token.ThrowIfCancellationRequested(); + throw; } finally { @@ -204,9 +206,10 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc await this.wrappedStream.WriteAsync(buffer, offset, count, source.Token).ConfigureAwait(false); } // We dispose stream on timeout so catch and check if cancellation token was cancelled - catch (ObjectDisposedException) when (source.IsCancellationRequested) + catch (ObjectDisposedException) { - throw new OperationCanceledException(source.Token); + source.Token.ThrowIfCancellationRequested(); + throw; } finally {