Skip to content

Commit c0fd980

Browse files
committed
api
1 parent 5de266e commit c0fd980

File tree

18 files changed

+152
-87
lines changed

18 files changed

+152
-87
lines changed

src/Servers/Connections.Abstractions/src/Features/IReconnectFeature.cs renamed to src/Servers/Connections.Abstractions/src/Features/IStatefulReconnectFeature.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ namespace Microsoft.AspNetCore.Connections.Abstractions;
1616
/// <summary>
1717
/// Provides access to connection reconnect operations.
1818
/// </summary>
19+
/// <remarks>This feature is experimental.</remarks>
1920
#if NET8_0_OR_GREATER
2021
[RequiresPreviewFeatures("IReconnectFeature is a preview interface")]
2122
#endif
22-
public interface IReconnectFeature
23+
public interface IStatefulReconnectFeature
2324
{
2425
/// <summary>
2526
/// Called when a connection reconnects. The new <see cref="PipeWriter"/> that application code should write to is passed in.
2627
/// </summary>
27-
public Action<PipeWriter> NotifyOnReconnect { get; set; }
28+
public void OnReconnected(Func<PipeWriter, Task> notifyOnReconnect);
2829

2930
/// <summary>
3031
/// Allows disabling the reconnect feature so a reconnecting connection will not be allowed anymore.

src/Servers/Connections.Abstractions/src/PublicAPI/net462/PublicAPI.Unshipped.txt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature
3-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.DisableReconnect() -> void
4-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.NotifyOnReconnect.get -> System.Action<System.IO.Pipelines.PipeWriter!>!
5-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.NotifyOnReconnect.set -> void
2+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature
3+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature.DisableReconnect() -> void
4+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature.OnReconnected(System.Func<System.IO.Pipelines.PipeWriter!, System.Threading.Tasks.Task!>! notifyOnReconnect) -> void
65
Microsoft.AspNetCore.Connections.Features.IConnectionMetricsTagsFeature
76
Microsoft.AspNetCore.Connections.Features.IConnectionMetricsTagsFeature.Tags.get -> System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string!, object?>>!
87
Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature

src/Servers/Connections.Abstractions/src/PublicAPI/net8.0/PublicAPI.Unshipped.txt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature
3-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.DisableReconnect() -> void
4-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.NotifyOnReconnect.get -> System.Action<System.IO.Pipelines.PipeWriter!>!
5-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.NotifyOnReconnect.set -> void
2+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature
3+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature.DisableReconnect() -> void
4+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature.OnReconnected(System.Func<System.IO.Pipelines.PipeWriter!, System.Threading.Tasks.Task!>! notifyOnReconnect) -> void
65
Microsoft.AspNetCore.Connections.Features.IConnectionMetricsTagsFeature
76
Microsoft.AspNetCore.Connections.Features.IConnectionMetricsTagsFeature.Tags.get -> System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string!, object?>>!
87
Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature

src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature
3-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.DisableReconnect() -> void
4-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.NotifyOnReconnect.get -> System.Action<System.IO.Pipelines.PipeWriter!>!
5-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.NotifyOnReconnect.set -> void
2+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature
3+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature.DisableReconnect() -> void
4+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature.OnReconnected(System.Func<System.IO.Pipelines.PipeWriter!, System.Threading.Tasks.Task!>! notifyOnReconnect) -> void
65
Microsoft.AspNetCore.Connections.Features.IConnectionMetricsTagsFeature
76
Microsoft.AspNetCore.Connections.Features.IConnectionMetricsTagsFeature.Tags.get -> System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string!, object?>>!
87
Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature

src/Servers/Connections.Abstractions/src/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#nullable enable
2-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature
3-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.DisableReconnect() -> void
4-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.NotifyOnReconnect.get -> System.Action<System.IO.Pipelines.PipeWriter!>!
5-
Microsoft.AspNetCore.Connections.Abstractions.IReconnectFeature.NotifyOnReconnect.set -> void
2+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature
3+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature.DisableReconnect() -> void
4+
Microsoft.AspNetCore.Connections.Abstractions.IStatefulReconnectFeature.OnReconnected(System.Func<System.IO.Pipelines.PipeWriter!, System.Threading.Tasks.Task!>! notifyOnReconnect) -> void
65
Microsoft.AspNetCore.Connections.Features.IConnectionMetricsTagsFeature
76
Microsoft.AspNetCore.Connections.Features.IConnectionMetricsTagsFeature.Tags.get -> System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<string!, object?>>!
87
Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature

src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ private async Task StopAsyncCore(bool disposing)
581581
}
582582

583583
#pragma warning disable CA2252 // This API requires opting into preview features
584-
if (connectionState.Connection.Features.Get<IReconnectFeature>() is IReconnectFeature feature)
584+
if (connectionState.Connection.Features.Get<IStatefulReconnectFeature>() is IStatefulReconnectFeature feature)
585585
{
586586
feature.DisableReconnect();
587587
}
@@ -1097,7 +1097,7 @@ private async Task SendWithLock(ConnectionState expectedConnectionState, HubMess
10971097
}
10981098

10991099
#pragma warning disable CA2252 // This API requires opting into preview features
1100-
if (connectionState.Connection.Features.Get<IReconnectFeature>() is IReconnectFeature feature)
1100+
if (connectionState.Connection.Features.Get<IStatefulReconnectFeature>() is IStatefulReconnectFeature feature)
11011101
{
11021102
feature.DisableReconnect();
11031103
}
@@ -1916,13 +1916,13 @@ public ConnectionState(ConnectionContext connection, HubConnection hubConnection
19161916
_hasInherentKeepAlive = connection.Features.Get<IConnectionInherentKeepAliveFeature>()?.HasInherentKeepAlive ?? false;
19171917

19181918
#pragma warning disable CA2252 // This API requires opting into preview features
1919-
if (Connection.Features.Get<IReconnectFeature>() is IReconnectFeature feature)
1919+
if (Connection.Features.Get<IStatefulReconnectFeature>() is IStatefulReconnectFeature feature)
19201920
{
19211921
_messageBuffer = new MessageBuffer(connection, hubConnection._protocol,
19221922
_hubConnection._serviceProvider.GetService<IOptions<HubConnectionOptions>>()?.Value.StatefulReconnectBufferSize
19231923
?? DefaultStatefulReconnectBufferSize);
19241924

1925-
feature.NotifyOnReconnect = _messageBuffer.Resend;
1925+
feature.OnReconnected(_messageBuffer.Resend);
19261926
}
19271927
#pragma warning restore CA2252 // This API requires opting into preview features
19281928
}

src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,7 @@ public async Task DisableReconnectCalledWhenCloseMessageReceived()
886886
var innerConnection = new TestConnection();
887887
var reconnectFeature = new TestReconnectFeature();
888888
#pragma warning disable CA2252 // This API requires opting into preview features
889-
innerConnection.Features.Set<IReconnectFeature>(reconnectFeature);
889+
innerConnection.Features.Set<IStatefulReconnectFeature>(reconnectFeature);
890890
#pragma warning restore CA2252 // This API requires opting into preview features
891891

892892
var delegateConnectionFactory = new DelegateConnectionFactory(
@@ -918,7 +918,7 @@ public async Task DisableReconnectCalledWhenSendingCloseMessage()
918918
var innerConnection = new TestConnection();
919919
var reconnectFeature = new TestReconnectFeature();
920920
#pragma warning disable CA2252 // This API requires opting into preview features
921-
innerConnection.Features.Set<IReconnectFeature>(reconnectFeature);
921+
innerConnection.Features.Set<IStatefulReconnectFeature>(reconnectFeature);
922922
#pragma warning restore CA2252 // This API requires opting into preview features
923923

924924
var delegateConnectionFactory = new DelegateConnectionFactory(
@@ -1029,15 +1029,15 @@ public ReadOnlyMemory<byte> GetMessageBytes(HubMessage message)
10291029
}
10301030

10311031
#pragma warning disable CA2252 // This API requires opting into preview features
1032-
private sealed class TestReconnectFeature : IReconnectFeature
1032+
private sealed class TestReconnectFeature : IStatefulReconnectFeature
10331033
#pragma warning restore CA2252 // This API requires opting into preview features
10341034
{
10351035
private TaskCompletionSource _disableReconnect = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
10361036

10371037
public Task DisableReconnectCalled => _disableReconnect.Task;
10381038

10391039
#pragma warning disable CA2252 // This API requires opting into preview features
1040-
public Action<PipeWriter> NotifyOnReconnect { get; set; }
1040+
public void OnReconnected(Func<PipeWriter, Task> notifyOnReconnected) { }
10411041
#pragma warning restore CA2252 // This API requires opting into preview features
10421042

10431043
#pragma warning disable CA2252 // This API requires opting into preview features

src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ private async Task StartTransport(Uri connectUrl, HttpTransportType transportTyp
531531
// We successfully started, set the transport properties (we don't want to set these until the transport is definitely running).
532532
_transport = transport;
533533

534-
if (useAck && _transport is IReconnectFeature reconnectFeature)
534+
if (useAck && _transport is IStatefulReconnectFeature reconnectFeature)
535535
{
536536
#pragma warning disable CA2252 // This API requires opting into preview features
537537
Features.Set(reconnectFeature);

src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/WebSocketsTransport.cs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
namespace Microsoft.AspNetCore.Http.Connections.Client.Internal;
2727

2828
#pragma warning disable CA2252 // This API requires opting into preview features
29-
internal sealed partial class WebSocketsTransport : ITransport, IReconnectFeature
29+
internal sealed partial class WebSocketsTransport : ITransport, IStatefulReconnectFeature
3030
#pragma warning restore CA2252 // This API requires opting into preview features
3131
{
3232
private WebSocket? _webSocket;
@@ -44,7 +44,7 @@ internal sealed partial class WebSocketsTransport : ITransport, IReconnectFeatur
4444
// Used for reconnect (when enabled) to determine if the close was ungraceful or not, reconnect only happens on ungraceful disconnect
4545
// The assumption is that a graceful close was triggered purposefully by either the client or server and a reconnect shouldn't occur
4646
private bool _gracefulClose;
47-
private Action<PipeWriter>? _notifyOnReconnect;
47+
private Func<PipeWriter, Task>? _notifyOnReconnect;
4848

4949
internal Task Running { get; private set; } = Task.CompletedTask;
5050

@@ -53,7 +53,23 @@ internal sealed partial class WebSocketsTransport : ITransport, IReconnectFeatur
5353
public PipeWriter Output => _transport!.Output;
5454

5555
#pragma warning disable CA2252 // This API requires opting into preview features
56-
public Action<PipeWriter> NotifyOnReconnect { get => _notifyOnReconnect is not null ? _notifyOnReconnect : (_) => { }; set => _notifyOnReconnect = value; }
56+
public void OnReconnected(Func<PipeWriter, Task> notifyOnReconnect)
57+
{
58+
if (_notifyOnReconnect is null)
59+
{
60+
_notifyOnReconnect = notifyOnReconnect;
61+
}
62+
else
63+
{
64+
var localNotifyOnReconnect = _notifyOnReconnect;
65+
_notifyOnReconnect = async writer =>
66+
{
67+
await localNotifyOnReconnect(writer).ConfigureAwait(false);
68+
await notifyOnReconnect(writer).ConfigureAwait(false);
69+
};
70+
}
71+
}
72+
//public Action<PipeWriter> NotifyOnReconnect { get => _notifyOnReconnect is not null ? _notifyOnReconnect : (_) => { }; set => _notifyOnReconnect = value; }
5773
#pragma warning restore CA2252 // This API requires opting into preview features
5874

5975
public WebSocketsTransport(HttpConnectionOptions httpConnectionOptions, ILoggerFactory loggerFactory, Func<Task<string?>> accessTokenProvider, HttpClient? httpClient,
@@ -299,7 +315,7 @@ public async Task StartAsync(Uri url, TransferFormat transferFormat, Cancellatio
299315

300316
_stopCts = new CancellationTokenSource();
301317

302-
var ignoreFirstCanceled = false;
318+
var isReconnect = false;
303319

304320
if (_transport is null)
305321
{
@@ -311,23 +327,29 @@ public async Task StartAsync(Uri url, TransferFormat transferFormat, Cancellatio
311327
}
312328
else
313329
{
314-
ignoreFirstCanceled = true;
330+
isReconnect = true;
315331
}
316332

317333
// TODO: Handle TCP connection errors
318334
// https://github.com/SignalR/SignalR/blob/1fba14fa3437e24c204dfaf8a18db3fce8acad3c/src/Microsoft.AspNet.SignalR.Core/Owin/WebSockets/WebSocketHandler.cs#L248-L251
319-
Running = ProcessSocketAsync(_webSocket, url, ignoreFirstCanceled);
335+
Running = ProcessSocketAsync(_webSocket, url, isReconnect);
320336
}
321337

322-
private async Task ProcessSocketAsync(WebSocket socket, Uri url, bool ignoreFirstCanceled)
338+
private async Task ProcessSocketAsync(WebSocket socket, Uri url, bool isReconnect)
323339
{
324340
Debug.Assert(_application != null);
325341

326342
using (socket)
327343
{
328344
// Begin sending and receiving.
329345
var receiving = StartReceiving(socket);
330-
var sending = StartSending(socket, ignoreFirstCanceled);
346+
var sending = StartSending(socket, ignoreFirstCanceled: isReconnect);
347+
348+
if (isReconnect)
349+
{
350+
Debug.Assert(_notifyOnReconnect is not null);
351+
await _notifyOnReconnect.Invoke(_transport!.Output).ConfigureAwait(false);
352+
}
331353

332354
// Wait for send or receive to complete
333355
var trigger = await Task.WhenAny(receiving, sending).ConfigureAwait(false);
@@ -375,7 +397,8 @@ private async Task ProcessSocketAsync(WebSocket socket, Uri url, bool ignoreFirs
375397
if (_useAck && !_gracefulClose)
376398
{
377399
UpdateConnectionPair();
378-
await StartAsync(url, _webSocketMessageType == WebSocketMessageType.Binary ? TransferFormat.Binary : TransferFormat.Text, default).ConfigureAwait(false);
400+
await StartAsync(url, _webSocketMessageType == WebSocketMessageType.Binary ? TransferFormat.Binary : TransferFormat.Text,
401+
cancellationToken: default).ConfigureAwait(false);
379402
}
380403
}
381404

@@ -642,7 +665,6 @@ public async Task StopAsync()
642665

643666
private void UpdateConnectionPair()
644667
{
645-
var prevPipe = _application!.Input;
646668
var input = new Pipe(_httpConnectionOptions.TransportPipeOptions);
647669

648670
// Add new pipe for reading from and writing to transport from app code
@@ -651,9 +673,6 @@ private void UpdateConnectionPair()
651673

652674
_application = applicationToTransport;
653675
_transport = transportToApplication;
654-
655-
Debug.Assert(_notifyOnReconnect is not null);
656-
_notifyOnReconnect.Invoke(input.Writer);
657676
}
658677

659678
#pragma warning disable CA2252 // This API requires opting into preview features

src/SignalR/common/Http.Connections.Common/src/NegotiationResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class NegotiationResponse
4949

5050
/// <summary>
5151
/// If set, the connection should attempt to reconnect with the same <see cref="BaseConnectionContext.ConnectionId"/> if it disconnects.
52-
/// It should also set <see cref="IReconnectFeature"/> on the <see cref="BaseConnectionContext.Features"/> collection so other layers of the
52+
/// It should also set <see cref="IStatefulReconnectFeature"/> on the <see cref="BaseConnectionContext.Features"/> collection so other layers of the
5353
/// application (like SignalR) can react.
5454
/// </summary>
5555
public bool UseAcking { get; set; }

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionContext.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ internal sealed partial class HttpConnectionContext : ConnectionContext,
3232
IConnectionLifetimeFeature,
3333
IConnectionLifetimeNotificationFeature,
3434
#pragma warning disable CA2252 // This API requires opting into preview features
35-
IReconnectFeature
35+
IStatefulReconnectFeature
3636
#pragma warning restore CA2252 // This API requires opting into preview features
3737
{
3838
private readonly HttpConnectionDispatcherOptions _options;
@@ -59,6 +59,8 @@ internal sealed partial class HttpConnectionContext : ConnectionContext,
5959
// on the same task
6060
private readonly TaskCompletionSource _disposeTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
6161

62+
internal Func<PipeWriter, Task>? NotifyOnReconnect { get; set; }
63+
6264
/// <summary>
6365
/// Creates the DefaultConnectionContext without Pipes to avoid upfront allocations.
6466
/// The caller is expected to set the <see cref="Transport"/> and <see cref="Application"/> pipes manually.
@@ -99,7 +101,7 @@ public HttpConnectionContext(string connectionId, string connectionToken, ILogge
99101
if (useAcks)
100102
{
101103
#pragma warning disable CA2252 // This API requires opting into preview features
102-
Features.Set<IReconnectFeature>(this);
104+
Features.Set<IStatefulReconnectFeature>(this);
103105
#pragma warning restore CA2252 // This API requires opting into preview features
104106
}
105107

@@ -209,7 +211,6 @@ public IDuplexPipe Application
209211
public CancellationToken ConnectionClosedRequested { get; set; }
210212

211213
#pragma warning disable CA2252 // This API requires opting into preview features
212-
public Action<PipeWriter> NotifyOnReconnect { get; set; } = (_) => { };
213214
#pragma warning restore CA2252 // This API requires opting into preview features
214215

215216
public override void Abort()
@@ -671,17 +672,32 @@ private void UpdateConnectionPair()
671672

672673
Application = applicationToTransport;
673674
Transport = transportToApplication;
675+
}
674676

675677
#pragma warning disable CA2252 // This API requires opting into preview features
676-
Features.GetRequiredFeature<IReconnectFeature>().NotifyOnReconnect.Invoke(input.Writer);
678+
public void DisableReconnect()
677679
#pragma warning restore CA2252 // This API requires opting into preview features
680+
{
681+
_useAcks = false;
678682
}
679683

680684
#pragma warning disable CA2252 // This API requires opting into preview features
681-
public void DisableReconnect()
685+
public void OnReconnected(Func<PipeWriter, Task> notifyOnReconnect)
682686
#pragma warning restore CA2252 // This API requires opting into preview features
683687
{
684-
_useAcks = false;
688+
if (NotifyOnReconnect is null)
689+
{
690+
NotifyOnReconnect = notifyOnReconnect;
691+
}
692+
else
693+
{
694+
var localOnReconnect = NotifyOnReconnect;
695+
NotifyOnReconnect = async (writer) =>
696+
{
697+
await localOnReconnect(writer);
698+
await notifyOnReconnect(writer);
699+
};
700+
}
685701
}
686702

687703
private static partial class Log

src/SignalR/common/Http.Connections/src/Internal/HttpConnectionDispatcher.Log.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,8 @@ public static void UserNameChanged(ILogger logger, string? previousUserName, str
6464
{
6565
UserNameChangedInternal(logger, previousUserName ?? "(null)", currentUserName ?? "(null)");
6666
}
67+
68+
[LoggerMessage(18, LogLevel.Debug, "Exception from IStatefulReconnectFeature.NotifyOnReconnect callback.", EventName = "NotifyOnReconnectError")]
69+
public static partial void NotifyOnReconnectError(ILogger logger, Exception ex);
6770
}
6871
}

0 commit comments

Comments
 (0)