Skip to content

Commit 27428b3

Browse files
Throw from OnConnected when using client results (#42447)
1 parent 363cdb1 commit 27428b3

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed

src/SignalR/server/Core/src/Internal/DefaultHubDispatcher.cs

+3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ public override async Task OnConnectedAsync(HubConnectionContext connection)
9090
{
9191
await hub.OnConnectedAsync();
9292
}
93+
94+
// OnConnectedAsync is finished, allow hub methods to use client results (ISingleClientProxy.InvokeAsync)
95+
connection.HubCallerClients.InvokeAllowed = true;
9396
}
9497
finally
9598
{

src/SignalR/server/Core/src/Internal/HubCallerClients.cs

+33
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ internal sealed class HubCallerClients : IHubCallerClients
1010
private readonly string[] _currentConnectionId;
1111
private readonly bool _parallelEnabled;
1212

13+
// Client results don't work in OnConnectedAsync
14+
// This property is set by the hub dispatcher when those methods are being called
15+
// so we can prevent users from making blocking client calls by returning a custom ISingleClientProxy instance
16+
internal bool InvokeAllowed { get; set; }
17+
1318
public HubCallerClients(IHubClients hubClients, string connectionId, bool parallelEnabled)
1419
{
1520
_connectionId = connectionId;
@@ -27,6 +32,10 @@ public ISingleClientProxy Caller
2732
{
2833
return new NotParallelSingleClientProxy(_hubClients.Client(_connectionId));
2934
}
35+
if (!InvokeAllowed)
36+
{
37+
return new NoInvokeSingleClientProxy(_hubClients.Client(_connectionId));
38+
}
3039
return _hubClients.Client(_connectionId);
3140
}
3241
}
@@ -47,6 +56,10 @@ public ISingleClientProxy Client(string connectionId)
4756
{
4857
return new NotParallelSingleClientProxy(_hubClients.Client(connectionId));
4958
}
59+
if (!InvokeAllowed)
60+
{
61+
return new NoInvokeSingleClientProxy(_hubClients.Client(_connectionId));
62+
}
5063
return _hubClients.Client(connectionId);
5164
}
5265

@@ -104,4 +117,24 @@ public Task SendCoreAsync(string method, object?[] args, CancellationToken cance
104117
return _proxy.SendCoreAsync(method, args, cancellationToken);
105118
}
106119
}
120+
121+
private sealed class NoInvokeSingleClientProxy : ISingleClientProxy
122+
{
123+
private readonly ISingleClientProxy _proxy;
124+
125+
public NoInvokeSingleClientProxy(ISingleClientProxy hubClients)
126+
{
127+
_proxy = hubClients;
128+
}
129+
130+
public Task<T> InvokeCoreAsync<T>(string method, object?[] args, CancellationToken cancellationToken = default)
131+
{
132+
throw new InvalidOperationException("Client results inside OnConnectedAsync Hub methods are not allowed.");
133+
}
134+
135+
public Task SendCoreAsync(string method, object?[] args, CancellationToken cancellationToken = default)
136+
{
137+
return _proxy.SendCoreAsync(method, args, cancellationToken);
138+
}
139+
}
107140
}

src/SignalR/server/SignalR/test/HubConnectionHandlerTestUtils/Hubs.cs

+16
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,22 @@ public class ConnectionLifetimeState
12541254
public Exception DisconnectedException { get; set; }
12551255
}
12561256

1257+
public class OnConnectedClientResultHub : Hub
1258+
{
1259+
public override async Task OnConnectedAsync()
1260+
{
1261+
await Clients.Caller.InvokeAsync<int>("Test");
1262+
}
1263+
}
1264+
1265+
public class OnDisconnectedClientResultHub : Hub
1266+
{
1267+
public override async Task OnDisconnectedAsync(Exception ex)
1268+
{
1269+
await Clients.Caller.InvokeAsync<int>("Test");
1270+
}
1271+
}
1272+
12571273
public class CallerServiceHub : Hub
12581274
{
12591275
private readonly CallerService _service;

src/SignalR/server/SignalR/test/HubConnectionHandlerTests.ClientResult.cs

+60
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,66 @@ public async Task ThrowsWhenParallelHubInvokesNotEnabled()
104104
}
105105
}
106106

107+
[Fact]
108+
public async Task ThrowsWhenUsedInOnConnectedAsync()
109+
{
110+
using (StartVerifiableLog(write => write.EventId.Name == "ErrorDispatchingHubEvent"))
111+
{
112+
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(builder =>
113+
{
114+
builder.AddSignalR(o =>
115+
{
116+
o.MaximumParallelInvocationsPerClient = 2;
117+
o.EnableDetailedErrors = true;
118+
});
119+
}, LoggerFactory);
120+
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<OnConnectedClientResultHub>>();
121+
122+
using (var client = new TestClient())
123+
{
124+
var connectionHandlerTask = await client.ConnectAsync(connectionHandler).DefaultTimeout();
125+
126+
// Hub asks client for a result, this is an invocation message with an ID
127+
var closeMessage = Assert.IsType<CloseMessage>(await client.ReadAsync().DefaultTimeout());
128+
Assert.Equal("Connection closed with an error. InvalidOperationException: Client results inside OnConnectedAsync Hub methods are not allowed.", closeMessage.Error);
129+
}
130+
}
131+
132+
Assert.Single(TestSink.Writes.Where(write => write.EventId.Name == "ErrorDispatchingHubEvent"));
133+
}
134+
135+
[Fact]
136+
public async Task ThrowsWhenUsedInOnDisconnectedAsync()
137+
{
138+
using (StartVerifiableLog(write => write.EventId.Name == "ErrorDispatchingHubEvent"))
139+
{
140+
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(builder =>
141+
{
142+
builder.AddSignalR(o =>
143+
{
144+
o.MaximumParallelInvocationsPerClient = 2;
145+
o.EnableDetailedErrors = true;
146+
});
147+
}, LoggerFactory);
148+
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<OnDisconnectedClientResultHub>>();
149+
150+
using (var client = new TestClient())
151+
{
152+
var connectionHandlerTask = await client.ConnectAsync(connectionHandler).DefaultTimeout();
153+
client.Connection.Abort();
154+
155+
// Hub asks client for a result, this is an invocation message with an ID
156+
var closeMessage = Assert.IsType<CloseMessage>(await client.ReadAsync().DefaultTimeout());
157+
Assert.Null(closeMessage.Error);
158+
159+
var ex = await Assert.ThrowsAsync<IOException>(() => connectionHandlerTask).DefaultTimeout();
160+
Assert.Equal($"Connection '{client.Connection.ConnectionId}' disconnected.", ex.Message);
161+
}
162+
}
163+
164+
Assert.Single(TestSink.Writes.Where(write => write.EventId.Name == "ErrorDispatchingHubEvent"));
165+
}
166+
107167
[Fact]
108168
public async Task CanUseClientResultsWithIHubContext()
109169
{

0 commit comments

Comments
 (0)