|
13 | 13 | using Microsoft.AspNetCore.Connections; |
14 | 14 | using Microsoft.AspNetCore.Http; |
15 | 15 | using Microsoft.AspNetCore.Http.Connections.Features; |
| 16 | +using Microsoft.AspNetCore.SignalR.Internal; |
16 | 17 | using Microsoft.AspNetCore.SignalR.Protocol; |
17 | 18 | using Microsoft.Extensions.DependencyInjection; |
18 | 19 | using Microsoft.Extensions.Options; |
@@ -1982,6 +1983,153 @@ public async Task ErrorInHubOnConnectSendsCloseMessageWithError(bool detailedErr |
1982 | 1983 | } |
1983 | 1984 | } |
1984 | 1985 |
|
| 1986 | + [Fact] |
| 1987 | + public async Task StreamingInvocationsDoNotBlockOtherInvocations() |
| 1988 | + { |
| 1989 | + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(); |
| 1990 | + var connectionHandler = serviceProvider.GetService<HubConnectionHandler<StreamingHub>>(); |
| 1991 | + |
| 1992 | + using (var client = new TestClient(new JsonHubProtocol())) |
| 1993 | + { |
| 1994 | + var connectionHandlerTask = await client.ConnectAsync(connectionHandler).OrTimeout(); |
| 1995 | + |
| 1996 | + // Blocking streaming invocation to test that other invocations can still run |
| 1997 | + await client.SendHubMessageAsync(new StreamInvocationMessage("1", nameof(StreamingHub.BlockingStream), Array.Empty<object>())).OrTimeout(); |
| 1998 | + |
| 1999 | + var completion = await client.InvokeAsync(nameof(StreamingHub.NonStream)).OrTimeout(); |
| 2000 | + Assert.Equal(42L, completion.Result); |
| 2001 | + |
| 2002 | + // Shut down |
| 2003 | + client.Dispose(); |
| 2004 | + |
| 2005 | + await connectionHandlerTask.OrTimeout(); |
| 2006 | + } |
| 2007 | + } |
| 2008 | + |
| 2009 | + [Fact] |
| 2010 | + public async Task InvocationsRunInOrder() |
| 2011 | + { |
| 2012 | + var tcsService = new TcsService(); |
| 2013 | + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(builder => |
| 2014 | + { |
| 2015 | + builder.AddSingleton(tcsService); |
| 2016 | + }); |
| 2017 | + var connectionHandler = serviceProvider.GetService<HubConnectionHandler<LongRunningHub>>(); |
| 2018 | + |
| 2019 | + // Because we use PipeScheduler.Inline the hub invocations will run inline until they wait, which happens inside the LongRunningMethod call |
| 2020 | + using (var client = new TestClient()) |
| 2021 | + { |
| 2022 | + var connectionHandlerTask = await client.ConnectAsync(connectionHandler).OrTimeout(); |
| 2023 | + |
| 2024 | + // Long running hub invocation to test that other invocations will not run until it is completed |
| 2025 | + await client.SendInvocationAsync(nameof(LongRunningHub.LongRunningMethod), nonBlocking: false).OrTimeout(); |
| 2026 | + // Wait for the long running method to start |
| 2027 | + await tcsService.StartedMethod.Task.OrTimeout(); |
| 2028 | + |
| 2029 | + // Invoke another hub method which will wait for the first method to finish |
| 2030 | + await client.SendInvocationAsync(nameof(LongRunningHub.SimpleMethod), nonBlocking: false).OrTimeout(); |
| 2031 | + // Both invocations should be waiting now |
| 2032 | + Assert.Null(client.TryRead()); |
| 2033 | + |
| 2034 | + // Release the long running hub method |
| 2035 | + tcsService.EndMethod.TrySetResult(null); |
| 2036 | + |
| 2037 | + // Long running hub method result |
| 2038 | + var firstResult = await client.ReadAsync().OrTimeout(); |
| 2039 | + |
| 2040 | + var longRunningCompletion = Assert.IsType<CompletionMessage>(firstResult); |
| 2041 | + Assert.Equal(12L, longRunningCompletion.Result); |
| 2042 | + |
| 2043 | + // simple hub method result |
| 2044 | + var secondResult = await client.ReadAsync().OrTimeout(); |
| 2045 | + |
| 2046 | + var simpleCompletion = Assert.IsType<CompletionMessage>(secondResult); |
| 2047 | + Assert.Equal(21L, simpleCompletion.Result); |
| 2048 | + |
| 2049 | + // Shut down |
| 2050 | + client.Dispose(); |
| 2051 | + |
| 2052 | + await connectionHandlerTask.OrTimeout(); |
| 2053 | + } |
| 2054 | + } |
| 2055 | + |
| 2056 | + [Fact] |
| 2057 | + public async Task StreamInvocationsBlockOtherInvocationsUntilTheyStartStreaming() |
| 2058 | + { |
| 2059 | + var tcsService = new TcsService(); |
| 2060 | + var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(builder => |
| 2061 | + { |
| 2062 | + builder.AddSingleton(tcsService); |
| 2063 | + builder.AddSingleton(typeof(IHubActivator<>), typeof(CustomHubActivator<>)); |
| 2064 | + }); |
| 2065 | + var connectionHandler = serviceProvider.GetService<HubConnectionHandler<LongRunningHub>>(); |
| 2066 | + |
| 2067 | + // Because we use PipeScheduler.Inline the hub invocations will run inline until they wait, which happens inside the LongRunningMethod call |
| 2068 | + using (var client = new TestClient()) |
| 2069 | + { |
| 2070 | + var connectionHandlerTask = await client.ConnectAsync(connectionHandler).OrTimeout(); |
| 2071 | + |
| 2072 | + // Long running hub invocation to test that other invocations will not run until it is completed |
| 2073 | + var streamInvocationId = await client.SendStreamInvocationAsync(nameof(LongRunningHub.LongRunningStream)).OrTimeout(); |
| 2074 | + // Wait for the long running method to start |
| 2075 | + await tcsService.StartedMethod.Task.OrTimeout(); |
| 2076 | + |
| 2077 | + // Invoke another hub method which will wait for the first method to finish |
| 2078 | + await client.SendInvocationAsync(nameof(LongRunningHub.SimpleMethod), nonBlocking: false).OrTimeout(); |
| 2079 | + // Both invocations should be waiting now |
| 2080 | + Assert.Null(client.TryRead()); |
| 2081 | + |
| 2082 | + // Release the long running hub method |
| 2083 | + tcsService.EndMethod.TrySetResult(null); |
| 2084 | + |
| 2085 | + // simple hub method result |
| 2086 | + var result = await client.ReadAsync().OrTimeout(); |
| 2087 | + |
| 2088 | + var simpleCompletion = Assert.IsType<CompletionMessage>(result); |
| 2089 | + Assert.Equal(21L, simpleCompletion.Result); |
| 2090 | + |
| 2091 | + var hubActivator = serviceProvider.GetService<IHubActivator<LongRunningHub>>() as CustomHubActivator<LongRunningHub>; |
| 2092 | + |
| 2093 | + // OnConnectedAsync and SimpleMethod hubs have been disposed at this point |
| 2094 | + Assert.Equal(2, hubActivator.ReleaseCount); |
| 2095 | + |
| 2096 | + await client.SendHubMessageAsync(new CancelInvocationMessage(streamInvocationId)).OrTimeout(); |
| 2097 | + |
| 2098 | + // Completion message for canceled Stream |
| 2099 | + await client.ReadAsync().OrTimeout(); |
| 2100 | + |
| 2101 | + // Stream method is now disposed |
| 2102 | + Assert.Equal(3, hubActivator.ReleaseCount); |
| 2103 | + |
| 2104 | + // Shut down |
| 2105 | + client.Dispose(); |
| 2106 | + |
| 2107 | + await connectionHandlerTask.OrTimeout(); |
| 2108 | + } |
| 2109 | + } |
| 2110 | + |
| 2111 | + private class CustomHubActivator<THub> : IHubActivator<THub> where THub : Hub |
| 2112 | + { |
| 2113 | + public int ReleaseCount; |
| 2114 | + private IServiceProvider _serviceProvider; |
| 2115 | + |
| 2116 | + public CustomHubActivator(IServiceProvider serviceProvider) |
| 2117 | + { |
| 2118 | + _serviceProvider = serviceProvider; |
| 2119 | + } |
| 2120 | + |
| 2121 | + public THub Create() |
| 2122 | + { |
| 2123 | + return new DefaultHubActivator<THub>(_serviceProvider).Create(); |
| 2124 | + } |
| 2125 | + |
| 2126 | + public void Release(THub hub) |
| 2127 | + { |
| 2128 | + ReleaseCount++; |
| 2129 | + hub.Dispose(); |
| 2130 | + } |
| 2131 | + } |
| 2132 | + |
1985 | 2133 | public static IEnumerable<object[]> HubTypes() |
1986 | 2134 | { |
1987 | 2135 | yield return new[] { typeof(DynamicTestHub) }; |
|
0 commit comments