Skip to content

Commit b1dc8ab

Browse files
reintroduce ConnectionFactory as BeforeConnect
- simplify service client configuration - write tests - fix timeouts
1 parent 28c6d3c commit b1dc8ab

32 files changed

+496
-179
lines changed

NuGet.Config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
<configuration>
33
<packageSources>
44
<add key="Nuget" value="https://api.nuget.org/v3/index.json" />
5+
<add key="UiPath-Internal" value="https://uipath.pkgs.visualstudio.com/Public.Feeds/_packaging/UiPath-Internal/nuget/v3/index.json" />
56
</packageSources>
67
</configuration>

benchmarks/NuGet.Config

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/CoreIpc.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ EndProject
88
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{676A208A-2F08-4749-A833-F8D2BCB1B147}"
99
ProjectSection(SolutionItems) = preProject
1010
Directory.Build.targets = Directory.Build.targets
11-
NuGet.Config = NuGet.Config
11+
..\NuGet.Config = ..\NuGet.Config
1212
EndProjectSection
1313
EndProject
1414
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playground", "Playground\Playground.csproj", "{F0365E40-DA73-4583-A363-89CBEF68A4C6}"

src/UiPath.CoreIpc/Client/ServiceClient.cs

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,8 @@
22

33
internal abstract class ServiceClient : IDisposable
44
{
5-
#region " NonGeneric-Generic adapter cache "
6-
private static readonly MethodInfo GenericDefinition = ((Func<ServiceClient, MethodInfo, object?[], Task<int>>)Invoke<int>).Method.GetGenericMethodDefinition();
7-
private static readonly ConcurrentDictionary<Type, InvokeDelegate> ReturnTypeToInvokeDelegate = new();
8-
private static InvokeDelegate GetInvokeDelegate(Type returnType) => ReturnTypeToInvokeDelegate.GetOrAdd(returnType, CreateInvokeDelegate);
9-
private static InvokeDelegate CreateInvokeDelegate(Type returnType)
10-
=> GenericDefinition.MakeGenericDelegate<InvokeDelegate>(
11-
returnType.IsGenericType
12-
? returnType.GetGenericArguments()[0]
13-
: typeof(object));
14-
15-
private static Task<TResult> Invoke<TResult>(ServiceClient serviceClient, MethodInfo method, object?[] args) => serviceClient.Invoke<TResult>(method, args);
16-
#endregion
17-
18-
protected abstract TimeSpan RequestTimeout { get; }
19-
protected abstract BeforeCallHandler? BeforeCall { get; }
20-
protected abstract ILogger? Log { get; }
21-
protected abstract string DebugName { get; }
22-
protected abstract ISerializer? Serializer { get; }
5+
protected abstract IServiceClientConfig Config { get; }
236
public abstract Stream? Network { get; }
24-
257
public event EventHandler? ConnectionClosed;
268

279
private readonly Type _interfaceType;
@@ -33,7 +15,6 @@ protected ServiceClient(Type interfaceType)
3315
}
3416

3517
protected void RaiseConnectionClosed() => ConnectionClosed?.Invoke(this, EventArgs.Empty);
36-
3718
public virtual ValueTask CloseConnection() => throw new NotSupportedException();
3819
public object? Invoke(MethodInfo method, object?[] args) => GetInvokeDelegate(method.ReturnType)(this, method, args);
3920

@@ -64,7 +45,7 @@ async Task<TResult> Invoke()
6445
{
6546
CancellationToken cancellationToken = default;
6647
TimeSpan messageTimeout = default;
67-
TimeSpan clientTimeout = RequestTimeout;
48+
TimeSpan clientTimeout = Config.RequestTimeout;
6849
Stream? uploadStream = null;
6950
var methodName = method.Name;
7051

@@ -77,10 +58,10 @@ async Task<TResult> Invoke()
7758

7859
var (connection, newConnection) = await EnsureConnection(ct);
7960

80-
if (BeforeCall is not null)
61+
if (Config.BeforeCall is not null)
8162
{
8263
var callInfo = new CallInfo(newConnection, method, args);
83-
await BeforeCall(callInfo, ct);
64+
await Config.BeforeCall(callInfo, ct);
8465
}
8566

8667
var requestId = connection.NewRequestId();
@@ -89,11 +70,11 @@ async Task<TResult> Invoke()
8970
UploadStream = uploadStream
9071
};
9172

92-
Log?.ServiceClientCalling(methodName, requestId, DebugName);
73+
Config.Logger?.ServiceClientCalling(methodName, requestId, Config.DebugName);
9374
var response = await connection.RemoteCall(request, ct); // returns user errors instead of throwing them (could throw for system bugs)
94-
Log?.ServiceClientCalled(methodName, requestId, DebugName);
75+
Config.Logger?.ServiceClientCalled(methodName, requestId, Config.DebugName);
9576

96-
return response.Deserialize<TResult>(Serializer);
77+
return response.Deserialize<TResult>(Config.Serializer);
9778
}
9879
catch (Exception ex)
9980
{
@@ -127,7 +108,7 @@ string[] SerializeArguments()
127108
break;
128109
}
129110

130-
result[index] = Serializer.OrDefault().Serialize(args[index]);
111+
result[index] = Config.Serializer.OrDefault().Serialize(args[index]);
131112
}
132113

133114
return result;
@@ -142,9 +123,22 @@ public void Dispose()
142123
}
143124
private void Dispose(bool disposing)
144125
{
145-
Log?.ServiceClientDispose(DebugName);
126+
Config.Logger?.ServiceClientDispose(Config.DebugName);
146127
}
147-
public override string ToString() => DebugName;
128+
public override string ToString() => Config.DebugName;
129+
130+
#region Generic adapter cache
131+
private static readonly MethodInfo GenericDefinition = ((Func<ServiceClient, MethodInfo, object?[], Task<int>>)Invoke<int>).Method.GetGenericMethodDefinition();
132+
private static readonly ConcurrentDictionary<Type, InvokeDelegate> ReturnTypeToInvokeDelegate = new();
133+
private static InvokeDelegate GetInvokeDelegate(Type returnType) => ReturnTypeToInvokeDelegate.GetOrAdd(returnType, CreateInvokeDelegate);
134+
private static InvokeDelegate CreateInvokeDelegate(Type returnType)
135+
=> GenericDefinition.MakeGenericDelegate<InvokeDelegate>(
136+
returnType.IsGenericType
137+
? returnType.GetGenericArguments()[0]
138+
: typeof(object));
139+
140+
private static Task<TResult> Invoke<TResult>(ServiceClient serviceClient, MethodInfo method, object?[] args) => serviceClient.Invoke<TResult>(method, args);
141+
#endregion
148142
}
149143

150144
internal sealed class ServiceClientProper : ServiceClient
@@ -208,10 +202,16 @@ public override async ValueTask CloseConnection()
208202
return (LatestConnection, newlyConnected: false);
209203
}
210204

211-
LatestConnection = new Connection(await Connect(ct), Serializer, Log, DebugName);
205+
if (Config.BeforeConnect is not null)
206+
{
207+
await Config.BeforeConnect(ct);
208+
}
209+
210+
var network = await Connect(ct);
211+
LatestConnection = new Connection(network, Config.Serializer, Config.Logger, Config.DebugName);
212212
var router = new Router(_client.Config.CreateCallbackRouterConfig(), _client.Config.ServiceProvider);
213213
_latestServer = new Server(router, _client.Config.RequestTimeout, LatestConnection);
214-
LatestConnection.Listen().LogException(Log, DebugName);
214+
LatestConnection.Listen().LogException(Config.Logger, Config.DebugName);
215215
return (LatestConnection, newlyConnected: true);
216216
}
217217
}
@@ -228,11 +228,7 @@ private async Task<Network> Connect(CancellationToken ct)
228228
return network;
229229
}
230230

231-
protected override TimeSpan RequestTimeout => _client.Config.RequestTimeout;
232-
protected override BeforeCallHandler? BeforeCall => _client.Config.BeforeCall;
233-
protected override ILogger? Log => _client.Config.Logger;
234-
protected override string DebugName => _client.Transport.ToString();
235-
protected override ISerializer? Serializer => _client.Config.Serializer;
231+
protected override IServiceClientConfig Config => _client.Config;
236232
}
237233

238234
internal sealed class ServiceClientForCallback : ServiceClient
@@ -251,9 +247,5 @@ public ServiceClientForCallback(Connection connection, Listener listener, Type i
251247
protected override Task<(Connection connection, bool newlyConnected)> EnsureConnection(CancellationToken ct)
252248
=> Task.FromResult((_connection, newlyConnected: false));
253249

254-
protected override TimeSpan RequestTimeout => _listener.Config.RequestTimeout;
255-
protected override BeforeCallHandler? BeforeCall => null;
256-
protected override ILogger? Log => null;
257-
protected override string DebugName => $"ReverseClient for {_listener}";
258-
protected override ISerializer? Serializer => null;
250+
protected override IServiceClientConfig Config => _listener.Config;
259251
}

src/UiPath.CoreIpc/Config/ClientConfig.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
namespace UiPath.Ipc;
1+
using System.ComponentModel;
22

3-
public sealed record ClientConfig : EndpointConfig
3+
namespace UiPath.Ipc;
4+
5+
public sealed record ClientConfig : EndpointConfig, IServiceClientConfig
46
{
57
public EndpointCollection? Callbacks { get; init; }
68

79
public IServiceProvider? ServiceProvider { get; init; }
810
public ILogger? Logger { get; init; }
11+
public BeforeConnectHandler? BeforeConnect { get; init; }
912
public BeforeCallHandler? BeforeCall { get; init; }
1013
public TaskScheduler? Scheduler { get; init; }
1114
public ISerializer? Serializer { get; set; }
1215

16+
[EditorBrowsable(EditorBrowsableState.Never)]
17+
public string DebugName { get; set; } = null!;
18+
1319
internal void Validate()
1420
{
1521
var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace UiPath.Ipc;
2+
3+
internal interface IServiceClientConfig
4+
{
5+
TimeSpan RequestTimeout { get; }
6+
BeforeConnectHandler? BeforeConnect { get; }
7+
BeforeCallHandler? BeforeCall { get; }
8+
ILogger? Logger { get; }
9+
ISerializer? Serializer { get; }
10+
string DebugName { get; }
11+
}

src/UiPath.CoreIpc/Config/IpcClient.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@ internal void Validate()
2828

2929
Config.Validate();
3030
Transport.Validate();
31+
32+
Config.DebugName ??= Transport.ToString();
3133
}
3234
}

src/UiPath.CoreIpc/Config/ListenerConfig.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22

33
namespace UiPath.Ipc;
44

5-
public abstract record ListenerConfig : EndpointConfig
5+
public abstract record ListenerConfig : EndpointConfig, IServiceClientConfig
66
{
77
public int ConcurrentAccepts { get; init; } = 5;
88
public byte MaxReceivedMessageSizeInMegabytes { get; init; } = 2;
99
public X509Certificate? Certificate { get; init; }
10-
1110
internal int MaxMessageSize => MaxReceivedMessageSizeInMegabytes * 1024 * 1024;
1211

13-
internal string DebugName => GetType().Name;
1412
internal IEnumerable<string> Validate() => Enumerable.Empty<string>();
1513

1614
internal override RouterConfig CreateRouterConfig(IpcServer server)
@@ -20,4 +18,14 @@ internal override RouterConfig CreateRouterConfig(IpcServer server)
2018
{
2119
Scheduler = endpoint.Scheduler ?? server.Scheduler
2220
});
21+
22+
#region IServiceClientConfig
23+
/// Do not implement <see cref="IServiceClientConfig.RequestTimeout"/> explicitly, as it must be implicitly implemented by <see cref="EndpointConfig.RequestTimeout"/>.
24+
25+
BeforeConnectHandler? IServiceClientConfig.BeforeConnect => null;
26+
BeforeCallHandler? IServiceClientConfig.BeforeCall => null;
27+
ILogger? IServiceClientConfig.Logger => null;
28+
ISerializer? IServiceClientConfig.Serializer => null!;
29+
string IServiceClientConfig.DebugName => $"CallbackClient for {this}";
30+
#endregion
2331
}

src/UiPath.CoreIpc/GlobalUsings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
global using UiPath.Ipc.Extensibility;
2+
global using BeforeConnectHandler = System.Func<System.Threading.CancellationToken, System.Threading.Tasks.Task>;
23
global using BeforeCallHandler = System.Func<UiPath.Ipc.CallInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task>;
34
global using InvokeDelegate = System.Func<UiPath.Ipc.ServiceClient, System.Reflection.MethodInfo, object?[], object?>;
45
global using Accept = System.Func<System.Threading.CancellationToken, System.Threading.Tasks.Task<System.Net.WebSockets.WebSocket>>;

src/UiPath.CoreIpc/Server/Listener.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ protected Listener(IpcServer server, ListenerConfig config)
8282
{
8383
Config = config;
8484
Server = server;
85-
Logger = server.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(config.DebugName);
85+
Logger = server.ServiceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(categoryName: config.ToString());
8686
_disposeTask = new(DisposeCore);
8787
}
8888

@@ -131,27 +131,27 @@ public void LogError(Exception exception, string message)
131131

132132
protected override async Task DisposeCore()
133133
{
134-
Log($"Stopping listener {Config.DebugName}...");
134+
Log($"Stopping listener {Config}...");
135135
_cts.Cancel();
136136
try
137137
{
138138
await _listeningTask;
139139
}
140140
catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token)
141141
{
142-
Log($"Stopping listener {Config.DebugName} threw OCE.");
142+
Log($"Stopping listener {Config} threw OCE.");
143143
}
144144
catch (Exception ex)
145145
{
146-
LogError(ex, $"Stopping listener {Config.DebugName} failed.");
146+
LogError(ex, $"Stopping listener {Config} failed.");
147147
}
148148
await State.DisposeAsync();
149149
_cts.Dispose();
150150
}
151151

152152
private async Task Listen(CancellationToken ct)
153153
{
154-
Log($"Starting listener {Config.DebugName}...");
154+
Log($"Starting listener {Config}...");
155155

156156
await Task.WhenAll(Enumerable.Range(1, Config.ConcurrentAccepts).Select(async _ =>
157157
{
@@ -167,17 +167,15 @@ private async Task AcceptConnection(CancellationToken ct)
167167
try
168168
{
169169
var network = await serverConnection.AcceptClient(ct);
170-
serverConnection.Listen(network, ct).LogException(Logger, Config.DebugName);
170+
serverConnection.Listen(network, ct).LogException(Logger, Config);
171171
}
172172
catch (Exception ex)
173173
{
174174
serverConnection.Dispose();
175175
if (!ct.IsCancellationRequested)
176176
{
177-
Logger.LogException(ex, Config.DebugName);
177+
Logger.LogException(ex, Config);
178178
}
179179
}
180180
}
181-
182-
public override string ToString() => Config.ToString();
183181
}

0 commit comments

Comments
 (0)