-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add main logic for Client Invocation #1687
Changes from 36 commits
79a0567
f3281af
790b6c0
f594136
9dccae7
a7c8c8a
f1b96c1
b129204
f4e143c
ee60530
d2a3178
8299c2d
d18743a
ecf28b4
789427d
a64dec1
9a57478
c8198d5
1ad6573
9265187
b93a8c1
ec252ad
886b985
0c40d03
c337cd3
3ba27a2
4b1dc1e
fcb849e
995cadb
d0a36d0
fb5a38e
2d98f5a
2c58f3b
e6dcc32
2168535
8a5356f
85f4b9f
67ff88c
51b1271
0e6631f
a80e79f
a1a38e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,7 +31,7 @@ public string GenerateInvocationId(string connectionId) | |
return $"{connectionId}-{_clientResultManagerId}-{Interlocked.Increment(ref _lastInvocationId)}"; | ||
} | ||
|
||
public Task<T> AddInvocation<T>(string connectionId, string invocationId, string instanceId, CancellationToken cancellationToken) | ||
public Task<T> AddInvocation<T>(string connectionId, string invocationId, CancellationToken cancellationToken) | ||
{ | ||
var tcs = new TaskCompletionSourceWithCancellation<T>( | ||
cancellationToken, | ||
|
@@ -53,7 +53,7 @@ public Task<T> AddInvocation<T>(string connectionId, string invocationId, string | |
{ | ||
tcs.TrySetException(new Exception(completionMessage.Error)); | ||
} | ||
}) { RouterInstanceId = instanceId } | ||
}) { RouterInstanceId = null } | ||
); | ||
Debug.Assert(result); | ||
|
||
|
@@ -70,14 +70,6 @@ public void AddServiceMapping(ServiceMappingMessage serviceMappingMessage) | |
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initial null and service message only send once, assign directly is enough. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
invocation.RouterInstanceId = serviceMappingMessage.InstanceId; | ||
} | ||
else | ||
{ | ||
// do nothing | ||
} | ||
} | ||
else | ||
{ | ||
// do nothing | ||
} | ||
} | ||
|
||
|
@@ -174,6 +166,8 @@ public bool TryGetInvocationReturnType(string invocationId, out Type type) | |
return false; | ||
} | ||
|
||
public bool RemoveInvocation(string invocationId) => _pendingInvocations.TryRemove(invocationId, out _); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when it returns There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
// Unused, here to honor the IInvocationBinder interface but should never be called | ||
public IReadOnlyList<Type> GetParameterTypes(string methodName) => throw new NotImplementedException(); | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,10 +3,12 @@ | |||||||||||||
|
||||||||||||||
using System; | ||||||||||||||
using System.Collections.Generic; | ||||||||||||||
using System.Diagnostics.CodeAnalysis; | ||||||||||||||
using System.Linq; | ||||||||||||||
using System.Threading; | ||||||||||||||
using System.Threading.Tasks; | ||||||||||||||
using Microsoft.AspNetCore.SignalR; | ||||||||||||||
using Microsoft.AspNetCore.SignalR.Protocol; | ||||||||||||||
using Microsoft.Azure.SignalR.Common; | ||||||||||||||
using Microsoft.Azure.SignalR.Protocol; | ||||||||||||||
using Microsoft.Extensions.Logging; | ||||||||||||||
|
@@ -18,8 +20,9 @@ internal class ServiceLifetimeManager<THub> : ServiceLifetimeManagerBase<THub> w | |||||||||||||
{ | ||||||||||||||
private const string MarkerNotConfiguredError = | ||||||||||||||
"'AddAzureSignalR(...)' was called without a matching call to 'IApplicationBuilder.UseAzureSignalR(...)'."; | ||||||||||||||
|
||||||||||||||
private readonly IClientInvocationManager _clientInvocationManager; | ||||||||||||||
private readonly IClientConnectionManager _clientConnectionManager; | ||||||||||||||
private readonly string _callerId; | ||||||||||||||
|
||||||||||||||
public ServiceLifetimeManager( | ||||||||||||||
IServiceConnectionManager<THub> serviceConnectionManager, | ||||||||||||||
|
@@ -29,12 +32,15 @@ public ServiceLifetimeManager( | |||||||||||||
AzureSignalRMarkerService marker, | ||||||||||||||
IOptions<HubOptions> globalHubOptions, | ||||||||||||||
IOptions<HubOptions<THub>> hubOptions, | ||||||||||||||
IBlazorDetector blazorDetector) | ||||||||||||||
IBlazorDetector blazorDetector, | ||||||||||||||
IServerNameProvider nameProvider, | ||||||||||||||
IClientInvocationManager clientInvocationManager) | ||||||||||||||
: base( | ||||||||||||||
serviceConnectionManager, | ||||||||||||||
protocolResolver, | ||||||||||||||
globalHubOptions, | ||||||||||||||
hubOptions, logger) | ||||||||||||||
hubOptions, | ||||||||||||||
logger) | ||||||||||||||
{ | ||||||||||||||
// after core 3.0 UseAzureSignalR() is not required. | ||||||||||||||
#if NETSTANDARD2_0 | ||||||||||||||
|
@@ -43,12 +49,19 @@ public ServiceLifetimeManager( | |||||||||||||
throw new InvalidOperationException(MarkerNotConfiguredError); | ||||||||||||||
} | ||||||||||||||
#endif | ||||||||||||||
_clientConnectionManager = clientConnectionManager; | ||||||||||||||
|
||||||||||||||
if (hubOptions.Value.SupportedProtocols != null && hubOptions.Value.SupportedProtocols.Any(x => x.Equals(Constants.Protocol.BlazorPack, StringComparison.OrdinalIgnoreCase))) | ||||||||||||||
{ | ||||||||||||||
blazorDetector?.TrySetBlazor(typeof(THub).Name, true); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if (nameProvider == null) | ||||||||||||||
{ | ||||||||||||||
throw new ArgumentNullException(nameof(nameProvider)); | ||||||||||||||
} | ||||||||||||||
_callerId = nameProvider.GetName(); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||||||||||||||
|
||||||||||||||
_clientInvocationManager = clientInvocationManager ?? throw new ArgumentNullException(nameof(clientInvocationManager)); | ||||||||||||||
_clientConnectionManager = clientConnectionManager ?? throw new ArgumentNullException(nameof(clientConnectionManager)); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
public override Task OnConnectedAsync(HubConnectionContext connection) | ||||||||||||||
|
@@ -103,6 +116,85 @@ public override async Task SendConnectionAsync(string connectionId, string metho | |||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
#if NET7_0_OR_GREATER | ||||||||||||||
vicancy marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
public override async Task<T> InvokeConnectionAsync<T>(string connectionId, string methodName, object[] args, CancellationToken cancellationToken = default) | ||||||||||||||
{ | ||||||||||||||
if (IsInvalidArgument(connectionId)) | ||||||||||||||
{ | ||||||||||||||
throw new ArgumentNullException(nameof(connectionId)); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if (IsInvalidArgument(methodName)) | ||||||||||||||
{ | ||||||||||||||
throw new ArgumentNullException(nameof(methodName)); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
var invocationId = _clientInvocationManager.Caller.GenerateInvocationId(connectionId); | ||||||||||||||
var task = _clientInvocationManager.Caller.AddInvocation<T>(connectionId, invocationId, cancellationToken); | ||||||||||||||
|
||||||||||||||
// Exception handling follows https://source.dot.net/#Microsoft.AspNetCore.SignalR.Core/DefaultHubLifetimeManager.cs,349 | ||||||||||||||
try | ||||||||||||||
{ | ||||||||||||||
var message = AppendMessageTracingId(new ClientInvocationMessage(invocationId, connectionId, _callerId, SerializeAllProtocols(methodName, args, invocationId))); | ||||||||||||||
await WriteAsync(message); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can move to before L133 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved |
||||||||||||||
return await task; | ||||||||||||||
} | ||||||||||||||
catch | ||||||||||||||
{ | ||||||||||||||
_clientInvocationManager.Caller.RemoveInvocation(invocationId); | ||||||||||||||
throw; | ||||||||||||||
vicancy marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
public override async Task SetConnectionResultAsync(string connectionId, CompletionMessage result) | ||||||||||||||
{ | ||||||||||||||
if (IsInvalidArgument(connectionId)) | ||||||||||||||
{ | ||||||||||||||
throw new ArgumentException(NullOrEmptyStringErrorMessage, nameof(connectionId)); | ||||||||||||||
} | ||||||||||||||
if (_clientConnectionManager.ClientConnections.TryGetValue(connectionId, out var clientConnectionContext)) | ||||||||||||||
{ | ||||||||||||||
// Determine which manager (Caller / Router) the `result` belongs to. | ||||||||||||||
// `TryCompletionResult` returns false when the corresponding invocation is not existing. | ||||||||||||||
IClientResultsManager clientResultsManager = null; | ||||||||||||||
if (_clientInvocationManager.Caller.TryCompleteResult(connectionId, result)) | ||||||||||||||
{ | ||||||||||||||
clientResultsManager = _clientInvocationManager.Caller; | ||||||||||||||
} | ||||||||||||||
if (_clientInvocationManager.Router.TryCompleteResult(connectionId, result)) | ||||||||||||||
{ | ||||||||||||||
clientResultsManager = _clientInvocationManager.Router; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Block unknown `results` which belongs to neither Caller nor Router | ||||||||||||||
if (clientResultsManager != null) | ||||||||||||||
{ | ||||||||||||||
var protocol = clientConnectionContext.Protocol; | ||||||||||||||
// For router server, it should send a ClientCompletionMessage with accurate payload content, which is necessary for the caller server. | ||||||||||||||
// For caller server, the only purpose of sending ClientCompletionMessage is to inform service to cleanup the invocation, which means only InvocationId and ConnectionId make sense. To avoid serialization for useless payload, we assign payload with empty bytes. | ||||||||||||||
var payload = clientResultsManager == _clientInvocationManager.Router | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add some comments here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about move payload assignment along with the check during L156~L162. So you don't have to do another manager type check here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved |
||||||||||||||
? SerializeCompletionMessage(result, protocol) | ||||||||||||||
: Array.Empty<byte>(); | ||||||||||||||
|
||||||||||||||
var message = AppendMessageTracingId(new ClientCompletionMessage(result.InvocationId, connectionId, _callerId, protocol, payload)); | ||||||||||||||
await WriteAsync(message); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
public override bool TryGetReturnType(string invocationId, [NotNullWhen(true)] out Type type) | ||||||||||||||
{ | ||||||||||||||
if (_clientInvocationManager.Router.ContainsInvocation(invocationId)) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this check necessary? Simply try get from caller then try get from router? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed this check. Also remove method |
||||||||||||||
{ | ||||||||||||||
return _clientInvocationManager.Router.TryGetInvocationReturnType(invocationId, out type); | ||||||||||||||
} | ||||||||||||||
else | ||||||||||||||
{ | ||||||||||||||
return _clientInvocationManager.Caller.TryGetInvocationReturnType(invocationId, out type); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
#endif | ||||||||||||||
|
||||||||||||||
private MultiConnectionDataMessage CreateMessage(string connectionId, string methodName, object[] args, ClientConnectionContext serviceConnectionContext) | ||||||||||||||
{ | ||||||||||||||
IDictionary<string, ReadOnlyMemory<byte>> payloads; | ||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done