Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
44ceb52
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Jan 20, 2026
17b23f0
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Jan 22, 2026
e36ce5f
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Jan 23, 2026
b903e50
Merge commit 'd25da5e31ba54ec5557ea37a8d88a3c5665bb653'
Feb 2, 2026
7a44dff
Resolve merge conflict
wtgodbe Feb 4, 2026
43f55d4
Merged PR 57542: [internal/release/9.0] Merge from public
wtgodbe Feb 4, 2026
1cb0db3
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 5, 2026
4105418
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 5, 2026
9c05c35
Merged PR 56918: [internal/release/9.0] Update dependencies from dnce…
Feb 5, 2026
e66df1b
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 5, 2026
dd09153
Merged PR 57604: [internal/release/9.0] Update dependencies from dnce…
Feb 5, 2026
5772ffd
Merge commit 'bf279cf8dddd0fceee1d0e6d824acee84fafec04'
Feb 9, 2026
a8ed809
Merge commit '12a6e1a9ce8798679c428e74766babfde7cdfa94'
Feb 9, 2026
ccb435d
Merged PR 57695: Fix cancellation with StatefulReconnect
BrennanConroy Feb 10, 2026
b69355e
Merge commit '06422501d36ae5449e562c7725f88f3c6f3ac634'
Feb 10, 2026
cbf21d5
Merged PR 57802: internal/release/9.0 - merge from public
vseanreesermsft Feb 11, 2026
5d3ebab
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 11, 2026
7e484d6
Merged PR 57804: [internal/release/9.0] Update dependencies from dnce…
Feb 11, 2026
bdfee04
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 11, 2026
06d9e25
Merged PR 57805: [internal/release/9.0] Update dependencies from dnce…
Feb 11, 2026
7713049
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 12, 2026
9177db0
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 12, 2026
07210d6
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 12, 2026
464963a
Merged PR 57824: [internal/release/9.0] Update dependencies from dnce…
Feb 12, 2026
d838d54
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 12, 2026
d1ce7df
Merged PR 57841: [internal/release/9.0] Update dependencies from dnce…
Feb 12, 2026
afb8213
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 12, 2026
3d77c0d
Merged PR 57862: [internal/release/9.0] Update dependencies from dnce…
Feb 12, 2026
e790ef0
Merge commit 'a73105063c677587a3c641987c32377bcf6e6049'
Feb 12, 2026
6ea9bf5
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 19, 2026
0e8f848
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Feb 20, 2026
baa6b29
Merged PR 58154: [internal/release/9.0] Update dependencies from dnce…
Feb 20, 2026
abf90c5
Merge commit 'baa6b294e728e6171378b4e8c52e42e7c4d4ed63' into internal…
vseanreesermsft Mar 10, 2026
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
4 changes: 4 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
<!-- Begin: Package sources from dotnet-extensions -->
<!-- End: Package sources from dotnet-extensions -->
<!-- Begin: Package sources from dotnet-runtime -->
<add key="darc-int-dotnet-runtime-19c0782" value="https://pkgs.dev.azure.com/dnceng/internal/_packaging/darc-int-dotnet-runtime-19c07820/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-runtime -->
<!-- Begin: Package sources from dotnet-efcore -->
<add key="darc-int-dotnet-efcore-1bea6ab" value="https://pkgs.dev.azure.com/dnceng/internal/_packaging/darc-int-dotnet-efcore-1bea6ab6/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-efcore -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
Expand All @@ -30,8 +32,10 @@
<clear />
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<!-- Begin: Package sources from dotnet-efcore -->
<add key="darc-int-dotnet-efcore-1bea6ab" value="true" />
<!-- End: Package sources from dotnet-efcore -->
<!-- Begin: Package sources from dotnet-runtime -->
<add key="darc-int-dotnet-runtime-19c0782" value="true" />
<!-- End: Package sources from dotnet-runtime -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
</disabledPackageSources>
Expand Down
320 changes: 160 additions & 160 deletions eng/Version.Details.xml

Large diffs are not rendered by default.

160 changes: 80 additions & 80 deletions eng/Versions.props

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2288,10 +2288,24 @@ public async Task TimerLoop(TimerAwaitable timer)
}
}

public ValueTask<FlushResult> WriteAsync(HubMessage message, CancellationToken cancellationToken)
public async ValueTask<FlushResult> WriteAsync(HubMessage message, CancellationToken cancellationToken)
{
Debug.Assert(_messageBuffer is not null);
return _messageBuffer.WriteAsync(message, cancellationToken);
CancellationTokenSource? cts = null;
var connectionToken = _hubConnection._state.StopCts.Token;
// StopAsync might have been called which would trigger the StopCts and thus prevent the CloseMessage from being sent.
// We'll use a short-lived token for CloseMessage specifically that isn't tied to the StopCts so we have a good chance of
// sending the CloseMessage to the server.
if (message is CloseMessage)
{
cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
connectionToken = cts.Token;
}

using var __ = cts;

using var _ = CancellationTokenUtils.CreateLinkedToken(cancellationToken, connectionToken, out var linkedToken);
return await _messageBuffer.WriteAsync(message, linkedToken).ConfigureAwait(false);
Comment on lines +2294 to +2308
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the server-side change: using var __ = cts; is a new pattern here and makes the disposal intent less clear. Consider a more descriptive name or explicit cts?.Dispose() in a finally block.

Suggested change
CancellationTokenSource? cts = null;
var connectionToken = _hubConnection._state.StopCts.Token;
// StopAsync might have been called which would trigger the StopCts and thus prevent the CloseMessage from being sent.
// We'll use a short-lived token for CloseMessage specifically that isn't tied to the StopCts so we have a good chance of
// sending the CloseMessage to the server.
if (message is CloseMessage)
{
cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
connectionToken = cts.Token;
}
using var __ = cts;
using var _ = CancellationTokenUtils.CreateLinkedToken(cancellationToken, connectionToken, out var linkedToken);
return await _messageBuffer.WriteAsync(message, linkedToken).ConfigureAwait(false);
CancellationTokenSource? timeoutCts = null;
var connectionToken = _hubConnection._state.StopCts.Token;
// StopAsync might have been called which would trigger the StopCts and thus prevent the CloseMessage from being sent.
// We'll use a short-lived token for CloseMessage specifically that isn't tied to the StopCts so we have a good chance of
// sending the CloseMessage to the server.
if (message is CloseMessage)
{
timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
connectionToken = timeoutCts.Token;
}
using var _ = CancellationTokenUtils.CreateLinkedToken(cancellationToken, connectionToken, out var linkedToken);
try
{
return await _messageBuffer.WriteAsync(message, linkedToken).ConfigureAwait(false);
}
finally
{
timeoutCts?.Dispose();
}

Copilot uses AI. Check for mistakes.
}

public bool ShouldProcessMessage(HubMessage message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,8 @@ public async Task DisableReconnectCalledWhenSendingCloseMessage()
Assert.Null(exception);

await reconnectFeature.DisableReconnectCalled.DefaultTimeout();

Assert.Equal("{\"type\":7}", await innerConnection.ReadSentTextMessageAsync().DefaultTimeout());
}

private class SampleObject
Expand Down
3 changes: 3 additions & 0 deletions src/SignalR/common/Shared/MessageBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ public ValueTask<FlushResult> WriteAsync(HubMessage hubMessage, CancellationToke

private async ValueTask<FlushResult> WriteAsyncCore(Type hubMessageType, ReadOnlyMemory<byte> messageBytes, CancellationToken cancellationToken)
{
// If backpressure is being observed a cancelable token is needed to make sure we can break out of waiting when the connection is closed
Debug.Assert(cancellationToken.CanBeCanceled);

// TODO: Add backpressure based on message count
if (_bufferedByteCount > _bufferLimit)
{
Expand Down
58 changes: 50 additions & 8 deletions src/SignalR/server/Core/src/HubConnectionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ internal ValueTask WriteAsync(HubMessage message, bool ignoreAbort, Cancellation
// The write didn't complete synchronously so await completion
if (!task.IsCompletedSuccessfully)
{
return new ValueTask(CompleteWriteAsync(task));
return new ValueTask(CompleteWriteAsync(task, cancellationToken));
}
else
{
Expand Down Expand Up @@ -250,7 +250,7 @@ public virtual ValueTask WriteAsync(SerializedHubMessage message, CancellationTo
// The write didn't complete synchronously so await completion
if (!task.IsCompletedSuccessfully)
{
return new ValueTask(CompleteWriteAsync(task));
return new ValueTask(CompleteWriteAsync(task, cancellationToken));
}
else
{
Expand All @@ -271,7 +271,30 @@ private ValueTask<FlushResult> WriteCore(HubMessage message, CancellationToken c
{
if (UsingStatefulReconnect())
{
return _messageBuffer.WriteAsync(message, cancellationToken);
return WriteAsync(_messageBuffer, this, message, cancellationToken);

static async ValueTask<FlushResult> WriteAsync(MessageBuffer messageBuffer, HubConnectionContext hubConnectionContext,
HubMessage message, CancellationToken cancellationToken)
{
CancellationTokenSource? cts = null;
var connectionToken = hubConnectionContext.ConnectionAborted;
if (message is CloseMessage)
{
// If it's a CloseMessage, we might already have triggered the ConnectionAborted token
// We would like to successfully send the CloseMessage for graceful close which means we can't use the ConnectionAborted token,
// but we need to make sure we don't get blocked by backpressure or anything, so we use a short-lived token.
cts = new CancellationTokenSource(TimeSpan.FromSeconds(5), hubConnectionContext._timeProvider);
connectionToken = cts.Token;
}

using var __ = cts;
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The using var __ = cts; pattern is new here and makes the disposal intent harder to read/grep. Consider using a descriptive name (e.g., ctsDisposable) or a try/finally that disposes cts when non-null, to keep this code more maintainable.

Suggested change
using var __ = cts;
using var ctsDisposable = cts;

Copilot uses AI. Check for mistakes.

// MessageBuffer can wait on things other than the PipeWriter (which is canceled by other means)
// So we need to make sure the cancellation token passed to it is also canceled when the connection is aborted
using var _ = CancellationTokenUtils.CreateLinkedToken(connectionToken, cancellationToken, out var linkedToken);
var result = await messageBuffer.WriteAsync(message, linkedToken);
return result;
}
}
else
{
Expand Down Expand Up @@ -300,7 +323,17 @@ private ValueTask<FlushResult> WriteCore(SerializedHubMessage message, Cancellat
if (UsingStatefulReconnect())
{
Debug.Assert(_messageBuffer is not null);
return _messageBuffer.WriteAsync(message, cancellationToken);
return WriteAsync(_messageBuffer, this, message, cancellationToken);

static async ValueTask<FlushResult> WriteAsync(MessageBuffer messageBuffer, HubConnectionContext hubConnectionContext,
SerializedHubMessage message, CancellationToken cancellationToken)
{
// MessageBuffer can wait on things other than the PipeWriter (which is canceled by other means)
// So we need to make sure the cancellation token passed to it is also canceled when the connection is aborted
using var _ = CancellationTokenUtils.CreateLinkedToken(hubConnectionContext.ConnectionAborted, cancellationToken, out var linkedToken);
var result = await messageBuffer.WriteAsync(message, linkedToken);
return result;
}
}
else
{
Expand All @@ -321,13 +354,16 @@ private ValueTask<FlushResult> WriteCore(SerializedHubMessage message, Cancellat
}
}

private async Task CompleteWriteAsync(ValueTask<FlushResult> task)
private async Task CompleteWriteAsync(ValueTask<FlushResult> task, CancellationToken cancellationToken)
{
try
{
await task;
}
catch (Exception ex)
// We care about errors while serializing to the PipeWriter as that will leave the Pipe
// in an invalid (for our scenario) state. OCE shouldn't occur while serializing bytes and
// writing to the Pipe. We assume that PipeWriter.WriteAsync(buffer) always writes the full message before calling FlushAsync
catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested)
Comment on lines +363 to +366
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CompleteWriteAsync filters out OperationCanceledException only when the caller-provided cancellationToken is canceled, but in the stateful reconnect path MessageBuffer.WriteAsync is awaited with a linked token (ConnectionAborted/timeout + caller token). If the linked token is canceled (e.g., connection abort/backpressure) while the caller token is not, the OCE will be treated as a write failure (CloseException set, FailedWritingMessage logged, AbortAllowReconnect called). Consider extending the filter to also ignore OCE when the connection is aborting (e.g., ConnectionAborted.IsCancellationRequested / _connectionAbortedTokenSource.IsCancellationRequested), or otherwise ensure the same token that can cancel MessageBuffer.WriteAsync is used for this decision.

Copilot uses AI. Check for mistakes.
{
CloseException = ex;
Log.FailedWritingMessage(_logger, ex);
Expand Down Expand Up @@ -355,7 +391,10 @@ private async Task WriteSlowAsync(HubMessage message, bool ignoreAbort, Cancella

await WriteCore(message, cancellationToken);
}
catch (Exception ex)
// We care about errors while serializing to the PipeWriter as that will leave the Pipe
// in an invalid (for our scenario) state. OCE shouldn't occur while serializing bytes and
// writing to the Pipe. We assume that PipeWriter.WriteAsync(buffer) always writes the full message before calling FlushAsync
catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same cancellation-token mismatch as in CompleteWriteAsync: WriteSlowAsync catches/logs OperationCanceledException unless the caller-provided cancellationToken is canceled, but stateful reconnect writes can be canceled via a linked token (e.g., ConnectionAborted) even when the caller token isn't. This can incorrectly set CloseException/log FailedWritingMessage during normal abort/timeout. Consider excluding OCE when the connection is aborting in addition to when cancellationToken.IsCancellationRequested.

Suggested change
catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested)
catch (Exception ex) when (ex is not OperationCanceledException || (!cancellationToken.IsCancellationRequested && !_connectionAborted))

Copilot uses AI. Check for mistakes.
{
CloseException = ex;
Log.FailedWritingMessage(_logger, ex);
Expand All @@ -381,7 +420,10 @@ private async Task WriteSlowAsync(SerializedHubMessage message, CancellationToke

await WriteCore(message, cancellationToken);
}
catch (Exception ex)
// We care about errors while serializing to the PipeWriter as that will leave the Pipe
// in an invalid (for our scenario) state. OCE shouldn't occur while serializing bytes and
// writing to the Pipe. We assume that PipeWriter.WriteAsync(buffer) always writes the full message before calling FlushAsync
catch (Exception ex) when (ex is not OperationCanceledException || !cancellationToken.IsCancellationRequested)
Comment on lines +423 to +426
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as the HubMessage WriteSlowAsync overload: this catch filter only treats OperationCanceledException as non-fatal when the caller-provided token is canceled, but stateful reconnect writes can be canceled by ConnectionAborted/linked tokens. Consider also excluding OCE when the connection is aborting to avoid incorrectly setting CloseException/logging FailedWritingMessage during normal shutdown.

Copilot uses AI. Check for mistakes.
{
CloseException = ex;
Log.FailedWritingMessage(_logger, ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,14 @@ public async Task SendAllAsyncWillCancelWithToken()
var sendTask = manager.SendAllAsync("Hello", new object[] { "World" }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());
var message = Assert.IsType<InvocationMessage>(client1.TryRead());
Assert.Equal("Hello", message.Target);
Assert.Single(message.Arguments);
Assert.Equal("World", (string)message.Arguments[0]);
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection2.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
Assert.False(connection2.ConnectionAborted.IsCancellationRequested);
}
}

Expand All @@ -65,14 +61,10 @@ public async Task SendAllExceptAsyncWillCancelWithToken()
var sendTask = manager.SendAllExceptAsync("Hello", new object[] { "World" }, new List<string> { connection1.ConnectionId }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection2.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
Assert.False(connection2.ConnectionAborted.IsCancellationRequested);
Assert.Null(client1.TryRead());
}
}
Expand All @@ -89,13 +81,9 @@ public async Task SendConnectionAsyncWillCancelWithToken()
var sendTask = manager.SendConnectionAsync(connection1.ConnectionId, "Hello", new object[] { "World" }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection1.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
}
}

Expand All @@ -111,13 +99,9 @@ public async Task SendConnectionsAsyncWillCancelWithToken()
var sendTask = manager.SendConnectionsAsync(new List<string> { connection1.ConnectionId }, "Hello", new object[] { "World" }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection1.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
}
}

Expand All @@ -134,13 +118,9 @@ public async Task SendGroupAsyncWillCancelWithToken()
var sendTask = manager.SendGroupAsync("group", "Hello", new object[] { "World" }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection1.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
}
}

Expand All @@ -161,14 +141,10 @@ public async Task SendGroupExceptAsyncWillCancelWithToken()
var sendTask = manager.SendGroupExceptAsync("group", "Hello", new object[] { "World" }, new List<string> { connection1.ConnectionId }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection2.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
Assert.False(connection2.ConnectionAborted.IsCancellationRequested);
Assert.Null(client1.TryRead());
}
}
Expand All @@ -186,13 +162,9 @@ public async Task SendGroupsAsyncWillCancelWithToken()
var sendTask = manager.SendGroupsAsync(new List<string> { "group" }, "Hello", new object[] { "World" }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection1.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
}
}

Expand All @@ -211,18 +183,14 @@ public async Task SendUserAsyncWillCancelWithToken()
var sendTask = manager.SendUserAsync("user", "Hello", new object[] { "World" }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());
var message = Assert.IsType<InvocationMessage>(client1.TryRead());
Assert.Equal("Hello", message.Target);
Assert.Single(message.Arguments);
Assert.Equal("World", (string)message.Arguments[0]);
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection2.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
Assert.False(connection2.ConnectionAborted.IsCancellationRequested);
}
}

Expand All @@ -241,18 +209,14 @@ public async Task SendUsersAsyncWillCancelWithToken()
var sendTask = manager.SendUsersAsync(new List<string> { "user1", "user2" }, "Hello", new object[] { "World" }, cts.Token).DefaultTimeout();
Assert.False(sendTask.IsCompleted);
cts.Cancel();
await sendTask.DefaultTimeout();
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await sendTask.DefaultTimeout());
var message = Assert.IsType<InvocationMessage>(client1.TryRead());
Assert.Equal("Hello", message.Target);
Assert.Single(message.Arguments);
Assert.Equal("World", (string)message.Arguments[0]);
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
connection2.ConnectionAborted.Register(t =>
{
((TaskCompletionSource)t).SetResult();
}, tcs);
await tcs.Task.DefaultTimeout();

Assert.False(connection1.ConnectionAborted.IsCancellationRequested);
Assert.False(connection2.ConnectionAborted.IsCancellationRequested);
}
}
}
Loading
Loading