Skip to content
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

Merged
merged 42 commits into from
Oct 24, 2022
Merged

Add main logic for Client Invocation #1687

merged 42 commits into from
Oct 24, 2022

Conversation

xingsy97
Copy link
Contributor

Summary of the changes

  • Add main logic for Client Invocation

The implementation for ClientInvocationManager is #1684

@xingsy97
Copy link
Contributor Author

All comments related to CientInvocationManager, CallerClientResultsManager and RoutedClientResulsManager were resolved in #1684

@JialinXin
Copy link
Contributor

Remove file Visual Studio 2022/Visualizers/attribcache140.bin?

@@ -267,6 +288,86 @@ public override Task RemoveFromGroupAsync(string connectionId, string groupName,
return WriteAckableMessageAsync(message, cancellationToken);
}

#if NET7_0_OR_GREATER
public override async Task<T> InvokeConnectionAsync<T>(string connectionId, string methodName, object[] args, CancellationToken cancellationToken = default)
Copy link
Member

Choose a reason for hiding this comment

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

since it is in base, does it mean Management API also supports ClientInvocation? @JialinXin ? Shall we throw for Management API?

}
catch (Exception)
{
throw;
Copy link
Member

Choose a reason for hiding this comment

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

if it is catch and throw, why catch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated exception handling logic here

}
else
{
throw new ArgumentException(NullOrEmptyStringErrorMessage, nameof(connectionId));
Copy link
Member

Choose a reason for hiding this comment

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

ArgumentException does not sound correct here
Again, what is the user experience when it throws?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will replace it with ArgumentNullException
Throwing exception here follows AspNetCore.SignalR

To know the user experience when it throws, I started a back-track from InvokeConnectionAsync. Finally the back-track broke at this method. Found no reference to this method

if (clientResultsManager != null)
{
var protocol = clientConnectionContext.Protocol;
var message = AppendMessageTracingId(new ClientCompletionMessage(result.InvocationId, connectionId, _callerId, protocol, SerializeCompletionMessage(result, protocol)));
Copy link
Member

Choose a reason for hiding this comment

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

What is ClientCompletionMessage for when it is from Caller? There is an additional result seralization, is it neccessory when Caller case?

Copy link
Contributor Author

@xingsy97 xingsy97 Oct 21, 2022

Choose a reason for hiding this comment

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

  1. Caller need to send back ClientCompletionMessage to service as well. Because service will rely on ClientCompeletionMessage to cleanup.
    See here
  2. Thus this seralization is also necessary when caller case. Not necessary for caller case. I'll update

Copy link
Member

@vicancy vicancy Oct 21, 2022

Choose a reason for hiding this comment

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

Probably we could simplify the case for Caller: make payload empty instead of calling SerializeCompletionMessage?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I did this.
And the other simplification will not be adopted.
Because we need to distinguish between caller and router to decide whether to use empty payload.
Just knowing the server is caller or router is not enough.

}

// Block unknown `results` which belongs to neither Caller nor Router
if (clientResultsManager != null)
Copy link
Member

Choose a reason for hiding this comment

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

L338-349 can be simplified to:

if (!ClientInvocationManager.Caller.TryCompleteResult(connectionId, result) || !ClientInvocationManager.Router.TryCompleteResult(connectionId, result) { return; }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The simplification should use && rather than || ?

if (!ClientInvocationManager.Caller.TryCompleteResult(connectionId, result) 
&& 
!ClientInvocationManager.Router.TryCompleteResult(connectionId, result) { return; }

do return ; when the invocation belongs to neither caller nor router. So &&

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's better not to simplify here. Explaination

}

// when condition `_clientConnectionManager.ClientConnections.TryGetValue` is false
throw new InvalidOperationException($"ConnectionId {connectionId} is invalid.");
Copy link
Contributor

Choose a reason for hiding this comment

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

Why throw here? If target client is not routed to this invoke server, false is expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

}

// when condition `_clientConnectionManager.ClientConnections.TryGetValue` is false
throw new InvalidOperationException($"ConnectionId {connectionId} is invalid.");
Copy link
Member

Choose a reason for hiding this comment

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

it is possible that connection is closed during this period

Copy link
Contributor Author

@xingsy97 xingsy97 Oct 21, 2022

Choose a reason for hiding this comment

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

This line is a bug and was deleted.
If target connection is closed during method InvokeConnectionAsync, the triggered exception will be handled by try-catch

if (clientResultsManager != null)
{
var protocol = clientConnectionContext.Protocol;
var message = AppendMessageTracingId(new ClientCompletionMessage(result.InvocationId, connectionId, _callerId, protocol, SerializeCompletionMessage(result, protocol)));
Copy link
Member

@vicancy vicancy Oct 21, 2022

Choose a reason for hiding this comment

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

Probably we could simplify the case for Caller: make payload empty instead of calling SerializeCompletionMessage?

// If the invocation was not added by caller, it will be ignored.
// The content of error message is useless and will not be exposed to client.
_clientInvocationManager.Caller.TryCompleteResult(connectionId, CompletionMessage.WithError(invocationId, "Invocation Failed"));
_clientInvocationManager.Caller.TryCompleteResult(connectionId, CompletionMessage.WithError(invocationId, ""));
Copy link
Member

Choose a reason for hiding this comment

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

Error message in CompletionMessage.WithError is not exposed to the customer? Then what is it for?

{
instanceId = clientConnectionContext.InstanceId;
}
var task = _clientInvocationManager.Caller.AddInvocation<T>(connectionId, invocationId, instanceId, cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

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

why move out? if still add invocation when the connectionId is removed from _clientConnectionManager, who will clean the Invocation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the target connection may not exist in current server

if (clientResultsManager != null)
{
var protocol = clientConnectionContext.Protocol;
var payload = clientResultsManager == _clientInvocationManager.Router
Copy link
Member

Choose a reason for hiding this comment

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

add some comments here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added

#if NET7_0_OR_GREATER
if (RuntimeServicePingMessage.TryGetOffline(pingMessage, out var instanceId))
{
_clientInvocationManager.Caller.CleanupInvocationsByInstance(instanceId);
Copy link
Member

Choose a reason for hiding this comment

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

no need to clean Router here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added comments

@@ -53,7 +53,7 @@ public Task<T> AddInvocation<T>(string connectionId, string invocationId, string
{
tcs.TrySetException(new Exception(completionMessage.Error));
}
}) { RouterInstanceId = instanceId }
}) { RouterInstanceId = null }
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
}) { RouterInstanceId = null }
})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -174,6 +166,8 @@ public bool TryGetInvocationReturnType(string invocationId, out Type type)
return false;
}

public bool RemoveInvocation(string invocationId) => _pendingInvocations.TryRemove(invocationId, out _);
Copy link
Member

Choose a reason for hiding this comment

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

when it returns bool use TryRemoveInvocation, or change bool to void

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 57 to 61
if (nameProvider == null)
{
throw new ArgumentNullException(nameof(nameProvider));
}
_callerId = nameProvider.GetName();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (nameProvider == null)
{
throw new ArgumentNullException(nameof(nameProvider));
}
_callerId = nameProvider.GetName();
_callerId = nameProvider?.GetName() ?? throw new ArgumentNullException(nameof(nameProvider));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 138 to 139
var message = AppendMessageTracingId(new ClientInvocationMessage(invocationId, connectionId, _callerId, SerializeAllProtocols(methodName, args, invocationId)));
await WriteAsync(message);
Copy link
Member

Choose a reason for hiding this comment

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

can move to before L133

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved


public override bool TryGetReturnType(string invocationId, [NotNullWhen(true)] out Type type)
{
if (_clientInvocationManager.Router.ContainsInvocation(invocationId))
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this check. Also remove method Router.ContainsInvocation

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved

@@ -70,14 +70,6 @@ public void AddServiceMapping(ServiceMappingMessage serviceMappingMessage)
{
Copy link
Contributor

@JialinXin JialinXin Oct 24, 2022

Choose a reason for hiding this comment

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

Initial null and service message only send once, assign directly is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@xingsy97 xingsy97 merged commit c959735 into Azure:dev Oct 24, 2022
@xingsy97 xingsy97 deleted the ci-main branch October 24, 2022 12:09
@@ -434,7 +464,34 @@ private ClientConnectionContext RemoveClientConnection(string connectionId)
{
_connectionIds.TryRemove(connectionId, out _);
_clientConnectionManager.TryRemoveClientConnection(connectionId, out var connection);
#if NET7_0_OR_GREATER
_clientInvocationManager.Router.CleanupInvocationsByConnection(connectionId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Shall we also do this for Caller? In the case server is both router and caller.

JialinXin added a commit that referenced this pull request Nov 8, 2022
* Update Actions to use dotnet build (#1663)

* Update windows.yml

* Add configurable options (#1654)

* Update CI to enable net7.0 preview builds (#1677)

* Support net7.0

* update osx/ubuntu.

* revert common package upgrade.

* update build-source guide and revert net7.0 in src.

* Add OrTimeout()

* Fix build warnings and add net6 samples (#1676)

* Fix build warning

* Update docs

* Fix build failure

* Fix test failure

* Fix vlunerability issue (#1678)

* Fix vlunerability issue

* fix filters

* fix filters

* Update use-signalr-service.md (#1683)

* Change emulator to net6.0 (#1682)

* Change emulator to net6.0

* Fix test

* Add filter property...... (#1686)

* Add filter

* Add multiPayload case

* Adding comments

* Update management-sdk-guide.md (#1689)

Improve a confusing statement. The original statement can also be interpreted as this SDK only supports SignalR clients based on ASP.NET Core C#, which confuses users. #1688

* Expose InstanceId in ClientConnectionContext (#1692)

* Fix `HubConnectionContext.UserIdentifier` is null when negotiation with Management SDK (#1691)

When clients negotiatie with Management SDK and connect to SignalR server, IUserIdProvider might not work as the user ID is set directly in the Management SDK.

To make HubConnectionContext.UserIdentifier have the valid value in this case, we should set it before the server accesses it. HubLifetimeManager{THub}.OnConnectedAsync(HubConnectionContext) is the only chance we can set the value. However, we cannot access the Constants.ClaimType.UserId as ASRS system claims are trimmed there. HubConnectionContext.Features is the place where we can store the user Id.

The following code is the injection point.
https://github.com/dotnet/aspnetcore/blob/v6.0.9/src/SignalR/server/Core/src/HubConnectionHandler.cs#L132-L141

Fixes #1679

* Add ClientInvocationManager (#1684)

* add ClientInvocationManager

* update CallerClientResultsManager (#1697)

add `TryCompleteResult` for `ErrorCompletionMessage`

* Implement close on client authentication expiration (#1699)

* Add spec for client-invocation. (#1701)

* Add spec for client-invocation.

* minor update direction for ErrorCompletionMessage

* Add client Invocation implementation and UTs (#1687)

* Add client Invocation implementation and UTs

* [ClientInvocation] Fix cleanup in caller case. (#1702)

* Fix cleanup in caller case.

* fix UT.

* Update src/Microsoft.Azure.SignalR.Common/ClientInvocation/IClientInvocationManager.cs

Co-authored-by: Liangying.Wei <lianwei@microsoft.com>

Co-authored-by: Liangying.Wei <lianwei@microsoft.com>

* Add ClientResultSample (#1703)

* Add ClientResultSample

* Move file

* minor update.

* minor update README.

* show server/service ex.message

* Add Broadcast method and update README

* Fix proxy not applied to serverless transient mode (#1708)

Fix #1700

* silent IDE0090 check (#1709)

* reduce retry interval when auth failed (#1451)

* Clean up dependencies. (#1711)

* Update dep to GA version.

* clean up dependencies

* clean management.

* fix test dep.

Co-authored-by: Liangying.Wei <lianwei@microsoft.com>
Co-authored-by: Kevin Guo <105208143+kevinguo-ed@users.noreply.github.com>
Co-authored-by: yzt <zityang@microsoft.com>
Co-authored-by: Eric Xing <87063252+xingsy97@users.noreply.github.com>
Co-authored-by: Terence Fan <stdrickforce@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants