From 9ecc7b4b18fd638d7a201a11bc684ac99382f46f Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Tue, 19 Nov 2024 11:29:44 +0100 Subject: [PATCH 01/13] taking shape --- src/Playground/Program.cs | 2 +- src/UiPath.CoreIpc.Http/BidiHttpListener.cs | 2 +- src/UiPath.CoreIpc/Config/ClientConfig.cs | 2 +- src/UiPath.CoreIpc/Config/IListenerConfig.cs | 4 +- src/UiPath.CoreIpc/Config/IpcClient.cs | 4 +- src/UiPath.CoreIpc/Config/IpcServer.cs | 58 ++------ .../Config/{EndpointConfig.cs => Peer.cs} | 6 +- .../{ListenerConfig.cs => ServerTransport.cs} | 4 +- src/UiPath.CoreIpc/Helpers/Result.cs | 21 +++ src/UiPath.CoreIpc/Server/Listener.cs | 127 ++++++++---------- src/UiPath.CoreIpc/Server/ServerConnection.cs | 2 +- .../Transport/NamedPipe/NamedPipeListener.cs | 2 +- .../Transport/Tcp/TcpListener.cs | 2 +- .../Transport/WebSocket/WebSocketListener.cs | 2 +- src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs | 2 +- src/UiPath.Ipc.Tests/ComputingTests.cs | 8 +- .../ComputingTestsOverNamedPipes.cs | 4 +- .../Config/OverrideConfigAttribute.cs | 2 +- src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs | 2 +- src/UiPath.Ipc.Tests/Program.cs | 2 +- src/UiPath.Ipc.Tests/RobotTests.cs | 2 +- .../RobotTestsOverNamedPipes.cs | 2 +- src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs | 2 +- src/UiPath.Ipc.Tests/SystemTests.cs | 10 +- .../SystemTestsOverNamedPipes.cs | 2 +- src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs | 2 +- .../SystemTestsOverWebSockets.cs | 2 +- src/UiPath.Ipc.Tests/TestBase.cs | 8 +- 28 files changed, 135 insertions(+), 153 deletions(-) rename src/UiPath.CoreIpc/Config/{EndpointConfig.cs => Peer.cs} (60%) rename src/UiPath.CoreIpc/Config/{ListenerConfig.cs => ServerTransport.cs} (90%) create mode 100644 src/UiPath.CoreIpc/Helpers/Result.cs diff --git a/src/Playground/Program.cs b/src/Playground/Program.cs index 86bb8470..df10be22 100644 --- a/src/Playground/Program.cs +++ b/src/Playground/Program.cs @@ -50,7 +50,7 @@ private static async Task Main(string[] args) }, typeof(Contracts.IClientOperations2) }, - Listeners = [ + Transport = [ new NamedPipeListener() { PipeName = Contracts.PipeName, diff --git a/src/UiPath.CoreIpc.Http/BidiHttpListener.cs b/src/UiPath.CoreIpc.Http/BidiHttpListener.cs index e5c299df..06076a21 100644 --- a/src/UiPath.CoreIpc.Http/BidiHttpListener.cs +++ b/src/UiPath.CoreIpc.Http/BidiHttpListener.cs @@ -12,7 +12,7 @@ namespace UiPath.Ipc.Http; using static Constants; using IBidiHttpListenerConfig = IListenerConfig; -public sealed partial record BidiHttpListener : ListenerConfig, IBidiHttpListenerConfig +public sealed partial record BidiHttpListener : ServerTransport, IBidiHttpListenerConfig { public required Uri Uri { get; init; } diff --git a/src/UiPath.CoreIpc/Config/ClientConfig.cs b/src/UiPath.CoreIpc/Config/ClientConfig.cs index 6b0191ac..7d77abfa 100644 --- a/src/UiPath.CoreIpc/Config/ClientConfig.cs +++ b/src/UiPath.CoreIpc/Config/ClientConfig.cs @@ -2,7 +2,7 @@ namespace UiPath.Ipc; -public sealed record ClientConfig : EndpointConfig, IServiceClientConfig +public sealed record ClientConfig : Peer, IServiceClientConfig { public EndpointCollection? Callbacks { get; init; } diff --git a/src/UiPath.CoreIpc/Config/IListenerConfig.cs b/src/UiPath.CoreIpc/Config/IListenerConfig.cs index a9513957..dd8976cc 100644 --- a/src/UiPath.CoreIpc/Config/IListenerConfig.cs +++ b/src/UiPath.CoreIpc/Config/IListenerConfig.cs @@ -1,7 +1,7 @@ namespace UiPath.Ipc.Extensibility; -public interface IListenerConfig - where TSelf : ListenerConfig, IListenerConfig +internal interface IListenerConfig + where TSelf : ServerTransport, IListenerConfig where TListenerState : IAsyncDisposable { TListenerState CreateListenerState(IpcServer server); diff --git a/src/UiPath.CoreIpc/Config/IpcClient.cs b/src/UiPath.CoreIpc/Config/IpcClient.cs index d09c7e08..24104b16 100644 --- a/src/UiPath.CoreIpc/Config/IpcClient.cs +++ b/src/UiPath.CoreIpc/Config/IpcClient.cs @@ -1,9 +1,7 @@ namespace UiPath.Ipc; -public sealed class IpcClient +public sealed class IpcClient : Peer { - private ClientTransport _transport = null!; - public required ClientConfig Config { get; init; } public required ClientTransport Transport { get; init; } diff --git a/src/UiPath.CoreIpc/Config/IpcServer.cs b/src/UiPath.CoreIpc/Config/IpcServer.cs index d4347d96..a9485a3b 100644 --- a/src/UiPath.CoreIpc/Config/IpcServer.cs +++ b/src/UiPath.CoreIpc/Config/IpcServer.cs @@ -1,16 +1,17 @@ namespace UiPath.Ipc; -public sealed class IpcServer : IAsyncDisposable +public sealed class IpcServer : Peer, IAsyncDisposable { - public required IServiceProvider ServiceProvider { get; init; } public required EndpointCollection Endpoints { get; init; } - public required IReadOnlyList Listeners { get; init; } - public TaskScheduler? Scheduler { get; init; } + public required ServerTransport Transport { get; init; } - private readonly Lazy> _started; + private readonly Lazy _listener; private readonly TaskCompletionSource _tcsStopped = new(); - public IpcServer() => _started = new(StartCore); + public IpcServer() + { + _listener = new(() => Listener.Create(server: this)); + } public void Start() { @@ -24,25 +25,21 @@ public void Start() public Task WaitForStart() => _started.Value; public Task WaitForStop() => _tcsStopped.Task; - private async Task StartCore() + private Listener? StartCore() { if (!IsValid(out _)) { return null; } - var disposables = new StopAdapter(this); try { - foreach (var listenerConfig in Listeners) - { - disposables.Add(Listener.Create(this, listenerConfig)); - } - return disposables; + return Listener.Create(this, Transport); } catch (Exception ex) { Trace.TraceError($"Failed to start server. Ex: {ex}"); + _tcsStopped.TrySetException(ex); disposables.SetException(ex); await disposables.DisposeAsync(); throw; @@ -51,11 +48,11 @@ public void Start() private bool IsValid(out IReadOnlyList errors) { - errors = Listeners.SelectMany(PrefixErrors).ToArray(); + errors = PrefixErrors(Transport).ToArray(); return errors is { Count: 0 }; - static IEnumerable PrefixErrors(ListenerConfig config) - => config.Validate().Select(error => $"{config.GetType().Name}: {error}"); + static IEnumerable PrefixErrors(ServerTransport transport) + => transport.Validate().Select(error => $"{transport.GetType().Name}: {error}"); } public async ValueTask DisposeAsync() @@ -64,33 +61,4 @@ public async ValueTask DisposeAsync() await ((await _started.Value)?.DisposeAsync() ?? default); } - - private sealed class StopAdapter : IAsyncDisposable - { - private readonly List _items = new(); - private readonly IpcServer _server; - private Exception? _exception; - - public StopAdapter(IpcServer server) => _server = server; - - public void Add(IAsyncDisposable item) => _items.Add(item); - - public void SetException(Exception ex) => _exception = ex; - - public async ValueTask DisposeAsync() - { - foreach (var item in _items) - { - await item.DisposeAsync(); - } - - if (_exception is not null) - { - _server._tcsStopped.TrySetException(_exception); - return; - } - - _server._tcsStopped.TrySetResult(null); - } - } } diff --git a/src/UiPath.CoreIpc/Config/EndpointConfig.cs b/src/UiPath.CoreIpc/Config/Peer.cs similarity index 60% rename from src/UiPath.CoreIpc/Config/EndpointConfig.cs rename to src/UiPath.CoreIpc/Config/Peer.cs index 4a06504a..432e3a86 100644 --- a/src/UiPath.CoreIpc/Config/EndpointConfig.cs +++ b/src/UiPath.CoreIpc/Config/Peer.cs @@ -1,8 +1,12 @@ namespace UiPath.Ipc; -public abstract record EndpointConfig +public abstract class Peer { public TimeSpan RequestTimeout { get; init; } = Timeout.InfiniteTimeSpan; + public IServiceProvider? ServiceProvider { get; init; } + public TaskScheduler? Scheduler { get; init; } + + internal ISerializer? Serializer => IpcJsonSerializer.Instance; internal virtual RouterConfig CreateRouterConfig(IpcServer server) => throw new NotSupportedException(); diff --git a/src/UiPath.CoreIpc/Config/ListenerConfig.cs b/src/UiPath.CoreIpc/Config/ServerTransport.cs similarity index 90% rename from src/UiPath.CoreIpc/Config/ListenerConfig.cs rename to src/UiPath.CoreIpc/Config/ServerTransport.cs index 2c41dcc6..4c15ec53 100644 --- a/src/UiPath.CoreIpc/Config/ListenerConfig.cs +++ b/src/UiPath.CoreIpc/Config/ServerTransport.cs @@ -2,7 +2,7 @@ namespace UiPath.Ipc; -public abstract record ListenerConfig : EndpointConfig, IServiceClientConfig +public abstract class ServerTransport : Peer, IServiceClientConfig { public int ConcurrentAccepts { get; init; } = 5; public byte MaxReceivedMessageSizeInMegabytes { get; init; } = 2; @@ -20,7 +20,7 @@ internal override RouterConfig CreateRouterConfig(IpcServer server) }); #region IServiceClientConfig - /// Do not implement explicitly, as it must be implicitly implemented by . + /// Do not implement explicitly, as it must be implicitly implemented by . BeforeConnectHandler? IServiceClientConfig.BeforeConnect => null; BeforeCallHandler? IServiceClientConfig.BeforeCall => null; diff --git a/src/UiPath.CoreIpc/Helpers/Result.cs b/src/UiPath.CoreIpc/Helpers/Result.cs new file mode 100644 index 00000000..7f47b3de --- /dev/null +++ b/src/UiPath.CoreIpc/Helpers/Result.cs @@ -0,0 +1,21 @@ +namespace UiPath.Ipc; + +internal readonly struct Result +{ + private readonly T _value; + private readonly Exception? _exception; + + public T Value => _exception is null ? _value : throw _exception; + + public Result(T value) + { + _value = value; + _exception = null; + } + + public Result(Exception exception) + { + _value = default!; + _exception = exception; + } +} diff --git a/src/UiPath.CoreIpc/Server/Listener.cs b/src/UiPath.CoreIpc/Server/Listener.cs index 9f999d69..df472011 100644 --- a/src/UiPath.CoreIpc/Server/Listener.cs +++ b/src/UiPath.CoreIpc/Server/Listener.cs @@ -2,87 +2,29 @@ namespace UiPath.Ipc; -using ListenerFactory = Func; +using ListenerFactory = Func; internal abstract class Listener : IAsyncDisposable { - public static Listener Create(IpcServer server, ListenerConfig config) - => GetFactory(config.GetType())( - server ?? throw new ArgumentNullException(nameof(server)), - config ?? throw new ArgumentNullException(nameof(config))); + private static readonly GenericListenerFactoryCache Cache = new(); - private readonly struct Result + public static Listener Create(IpcServer server) { - private readonly T _value; - private readonly Exception? _exception; - - public T Value => _exception is null ? _value : throw _exception; - - public Result(T value) - { - _value = value; - _exception = null; - } - - public Result(Exception exception) - { - _value = default!; - _exception = exception; - } - } - private static readonly ConcurrentDictionary> Factories = new(); - private static ListenerFactory GetFactory(Type configType) => Factories.GetOrAdd(configType, CreateFactory).Value; - private static Result CreateFactory(Type configType) - { - try - { - return new(Pal(configType)); - } - catch (Exception ex) - { - return new(ex); - } - - static ListenerFactory Pal(Type configType) - { - if (configType.GetInterfaces().SingleOrDefault(IsIListenerConfig) is not { } iface - || iface.GetGenericArguments() is not [_, var listenerStateType, var connectionStateType]) - { - throw new ArgumentOutOfRangeException(nameof(iface), $"The ListenerConfig type must implement IListenerConfig<,>. ListenerConfig type was: {configType.FullName}"); - } - - var listenerType = typeof(Listener<,,>).MakeGenericType(configType, listenerStateType, connectionStateType); - var listenerCtor = listenerType.GetConstructor( - bindingAttr: BindingFlags.Public | BindingFlags.Instance, - binder: Type.DefaultBinder, - types: [typeof(IpcServer), configType], - modifiers: null)!; - - var paramofServer = Expression.Parameter(typeof(IpcServer)); - var paramofConfig = Expression.Parameter(typeof(ListenerConfig)); - var lambda = Expression.Lambda( - delegateType: typeof(ListenerFactory), - body: Expression.New(listenerCtor, paramofServer, Expression.Convert(paramofConfig, configType)), - paramofServer, - paramofConfig); - - var @delegate = (lambda.Compile() as ListenerFactory)!; - return @delegate; - } - - static bool IsIListenerConfig(Type candidateIface) - => candidateIface.IsGenericType && candidateIface.GetGenericTypeDefinition() == typeof(IListenerConfig<,,>); + var transportType = server.Transport.GetType(); + var listenerFactory = Cache.Get(transportType); + var listener = listenerFactory(server); + return listener; } private readonly Lazy _disposeTask = null!; - public readonly ListenerConfig Config; + public readonly ServerTransport Config; public readonly IpcServer Server; private readonly Lazy _loggerCategory; private readonly Lazy _lazyLogger; public ILogger Logger => _lazyLogger.Value; - protected Listener(IpcServer server, ListenerConfig config) + protected Listener(IpcServer server, ServerTransport config) { _loggerCategory = new(ComputeLoggerCategory); Config = config; @@ -104,7 +46,7 @@ private string ComputeLoggerCategory() } internal sealed class Listener : Listener, IAsyncDisposable - where TConfig : ListenerConfig, IListenerConfig + where TConfig : ServerTransport, IListenerConfig where TListenerState : IAsyncDisposable where TConnectionState : IDisposable { @@ -224,3 +166,52 @@ async Task TryToListen() protected override Type ConfigType => typeof(TConfig); } + +internal sealed class GenericListenerFactoryCache +{ + private readonly ConcurrentDictionary> _cache = new(); + + public ListenerFactory Get(Type configType) => _cache.GetOrAdd(configType, Create).Value; + + private Result Create(Type configType) + { + try + { + return new(Emit(configType)); + } + catch (Exception ex) + { + return new(ex); + } + + static ListenerFactory Emit(Type configType) + { + if (configType.GetInterfaces().SingleOrDefault(IsIListenerConfig) is not { } iface + || iface.GetGenericArguments() is not [_, var listenerStateType, var connectionStateType]) + { + throw new ArgumentOutOfRangeException(nameof(iface), $"The ListenerConfig type must implement IListenerConfig<,>. ListenerConfig type was: {configType.FullName}"); + } + + var listenerType = typeof(Listener<,,>).MakeGenericType(configType, listenerStateType, connectionStateType); + var listenerCtor = listenerType.GetConstructor( + bindingAttr: BindingFlags.Public | BindingFlags.Instance, + binder: Type.DefaultBinder, + types: [typeof(IpcServer), configType], + modifiers: null)!; + + var paramofServer = Expression.Parameter(typeof(IpcServer)); + var paramofConfig = Expression.Parameter(typeof(ServerTransport)); + var lambda = Expression.Lambda( + delegateType: typeof(ListenerFactory), + body: Expression.New(listenerCtor, paramofServer, Expression.Convert(paramofConfig, configType)), + paramofServer, + paramofConfig); + + var @delegate = (lambda.Compile() as ListenerFactory)!; + return @delegate; + } + + static bool IsIListenerConfig(Type candidateIface) + => candidateIface.IsGenericType && candidateIface.GetGenericTypeDefinition() == typeof(IListenerConfig<,,>); + } +} diff --git a/src/UiPath.CoreIpc/Server/ServerConnection.cs b/src/UiPath.CoreIpc/Server/ServerConnection.cs index 84aafe9e..be2c90c8 100644 --- a/src/UiPath.CoreIpc/Server/ServerConnection.cs +++ b/src/UiPath.CoreIpc/Server/ServerConnection.cs @@ -9,7 +9,7 @@ public interface IClient } internal sealed class ServerConnection : ServerConnection - where TConfig : ListenerConfig, IListenerConfig + where TConfig : ServerTransport, IListenerConfig where TListenerState : IAsyncDisposable where TConnectionState : IDisposable { diff --git a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeListener.cs b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeListener.cs index 4e687558..219fc7a6 100644 --- a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeListener.cs +++ b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeListener.cs @@ -6,7 +6,7 @@ namespace UiPath.Ipc.Transport.NamedPipe; using INamedPipeListenerConfig = IListenerConfig; -public sealed record NamedPipeListener : ListenerConfig, INamedPipeListenerConfig +public sealed record NamedPipeListener : ServerTransport, INamedPipeListenerConfig { public required string PipeName { get; init; } public string ServerName { get; init; } = "."; diff --git a/src/UiPath.CoreIpc/Transport/Tcp/TcpListener.cs b/src/UiPath.CoreIpc/Transport/Tcp/TcpListener.cs index d9de0eaf..fc146f5a 100644 --- a/src/UiPath.CoreIpc/Transport/Tcp/TcpListener.cs +++ b/src/UiPath.CoreIpc/Transport/Tcp/TcpListener.cs @@ -5,7 +5,7 @@ namespace UiPath.Ipc.Transport.Tcp; using ITcpListenerConfig = IListenerConfig; -public sealed record TcpListener : ListenerConfig, ITcpListenerConfig +public sealed record TcpListener : ServerTransport, ITcpListenerConfig { public required IPEndPoint EndPoint { get; init; } diff --git a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketListener.cs b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketListener.cs index 95911151..0e8b1552 100644 --- a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketListener.cs +++ b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketListener.cs @@ -2,7 +2,7 @@ using IWebSocketListenerConfig = IListenerConfig; -public sealed record WebSocketListener : ListenerConfig, IWebSocketListenerConfig +public sealed record WebSocketListener : ServerTransport, IWebSocketListenerConfig { public required Accept Accept { get; init; } diff --git a/src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs b/src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs index ff78f64a..a9e6d712 100644 --- a/src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs +++ b/src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs @@ -5,7 +5,7 @@ namespace UiPath.Ipc; -public interface ISerializer +internal interface ISerializer { ValueTask DeserializeAsync(Stream json, ILogger? logger); void Serialize(object? obj, Stream stream); diff --git a/src/UiPath.Ipc.Tests/ComputingTests.cs b/src/UiPath.Ipc.Tests/ComputingTests.cs index a58e6172..24fdacb1 100644 --- a/src/UiPath.Ipc.Tests/ComputingTests.cs +++ b/src/UiPath.Ipc.Tests/ComputingTests.cs @@ -44,7 +44,7 @@ protected override void ConfigureSpecificServices(IServiceCollection services) .AddSingletonAlias() ; - protected override ListenerConfig ConfigTransportAgnostic(ListenerConfig listener) + protected override ServerTransport ConfigTransportAgnostic(ServerTransport listener) => listener with { ConcurrentAccepts = 10, @@ -279,12 +279,12 @@ await Enumerable.Range(1, CParallelism) .WhenAll(); } - public abstract IAsyncDisposable? RandomTransportPair(out ListenerConfig listener, out ClientTransport transport); + public abstract IAsyncDisposable? RandomTransportPair(out ServerTransport listener, out ClientTransport transport); public abstract ExternalServerParams RandomServerParams(); public readonly record struct ExternalServerParams(ServerKind Kind, string? PipeName = null, int Port = 0) { - public IAsyncDisposable? CreateListenerConfig(out ListenerConfig listenerConfig) + public IAsyncDisposable? CreateListenerConfig(out ServerTransport listenerConfig) { switch (Kind) { @@ -321,7 +321,7 @@ public enum ServerKind { NamedPipes, Tcp, WebSockets } private sealed class DisableInProcClientServer : OverrideConfig { - public override async Task Override(Func> listener) => null; + public override async Task Override(Func> listener) => null; public override IpcClient? Override(Func client) => null; } } diff --git a/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs index 68235bae..0a6c67af 100644 --- a/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs @@ -9,7 +9,7 @@ public sealed class ComputingTestsOverNamedPipes : ComputingTests public ComputingTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected override async Task CreateListener() => new NamedPipeListener + protected override async Task CreateListener() => new NamedPipeListener { PipeName = PipeName }; @@ -19,7 +19,7 @@ public ComputingTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outpu AllowImpersonation = true, }; - public override IAsyncDisposable? RandomTransportPair(out ListenerConfig listener, out ClientTransport transport) + public override IAsyncDisposable? RandomTransportPair(out ServerTransport listener, out ClientTransport transport) { var pipeName = $"{Guid.NewGuid():N}"; listener = new NamedPipeListener { PipeName = pipeName }; diff --git a/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs b/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs index a49f2aa9..237c1e40 100644 --- a/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs +++ b/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs @@ -34,6 +34,6 @@ public OverrideConfigAttribute(Type overrideConfigType) public abstract class OverrideConfig { - public virtual Task Override(Func> listener) => listener()!; + public virtual Task Override(Func> listener) => listener()!; public virtual IpcClient? Override(Func client) => client(); } \ No newline at end of file diff --git a/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs b/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs index 87f90be6..665dbab2 100644 --- a/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs +++ b/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs @@ -26,7 +26,7 @@ public async Task NamedPipesShoulNotLeak() private static IpcServer CreateServer(string pipeName) => new IpcServer { - Listeners = [ + Transport = [ new NamedPipeListener { PipeName = pipeName, diff --git a/src/UiPath.Ipc.Tests/Program.cs b/src/UiPath.Ipc.Tests/Program.cs index 6e917ad3..084096ce 100644 --- a/src/UiPath.Ipc.Tests/Program.cs +++ b/src/UiPath.Ipc.Tests/Program.cs @@ -25,7 +25,7 @@ { { typeof(IComputingService) }, }, - Listeners = [listener], + Transport = [listener], }; ipcServer.Start(); await ipcServer.WaitForStop(); diff --git a/src/UiPath.Ipc.Tests/RobotTests.cs b/src/UiPath.Ipc.Tests/RobotTests.cs index f632f26c..c07902d8 100644 --- a/src/UiPath.Ipc.Tests/RobotTests.cs +++ b/src/UiPath.Ipc.Tests/RobotTests.cs @@ -31,7 +31,7 @@ protected override void ConfigureSpecificServices(IServiceCollection services) .AddSingleton() .AddSingletonAlias(); - protected override ListenerConfig ConfigTransportAgnostic(ListenerConfig listener) + protected override ServerTransport ConfigTransportAgnostic(ServerTransport listener) => listener with { ConcurrentAccepts = 10, diff --git a/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs index 78d607a0..22ef42cb 100644 --- a/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs @@ -12,7 +12,7 @@ public sealed class RobotTestsOverNamedPipes : RobotTests public RobotTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected override async Task CreateListener() => new NamedPipeListener + protected override async Task CreateListener() => new NamedPipeListener { PipeName = PipeName }; diff --git a/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs b/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs index 416f2fb5..afc79a1d 100644 --- a/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs +++ b/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs @@ -47,7 +47,7 @@ public async Task RemoteCallingSyncOverAsync_IpcShouldBeResilient(ScenarioId sce private static IpcServer CreateServer(string pipeName) => new IpcServer { - Listeners = [ + Transport = [ new NamedPipeListener { PipeName = pipeName, diff --git a/src/UiPath.Ipc.Tests/SystemTests.cs b/src/UiPath.Ipc.Tests/SystemTests.cs index a00f97a6..73dc59a3 100644 --- a/src/UiPath.Ipc.Tests/SystemTests.cs +++ b/src/UiPath.Ipc.Tests/SystemTests.cs @@ -30,7 +30,7 @@ protected override void ConfigureSpecificServices(IServiceCollection services) .AddSingleton() .AddSingletonAlias(); - protected override ListenerConfig ConfigTransportAgnostic(ListenerConfig listener) + protected override ServerTransport ConfigTransportAgnostic(ServerTransport listener) => listener with { ConcurrentAccepts = 10, @@ -94,20 +94,20 @@ public async Task ClientWaitingForTooLongACall_ShouldThrowTimeout() private sealed class ServerExecutingTooLongACall_ShouldThrowTimeout_Config : OverrideConfig { - public override async Task Override(Func> listener) => await listener() with { RequestTimeout = Timeouts.Short }; + public override async Task Override(Func> listener) => await listener() with { RequestTimeout = Timeouts.Short }; public override IpcClient? Override(Func client) => client().WithRequestTimeout(Timeout.InfiniteTimeSpan); } private sealed class ClientWaitingForTooLongACall_ShouldThrowTimeout_Config : OverrideConfig { - public override async Task Override(Func> listener) => await listener() with { RequestTimeout = Timeout.InfiniteTimeSpan }; + public override async Task Override(Func> listener) => await listener() with { RequestTimeout = Timeout.InfiniteTimeSpan }; public override IpcClient? Override(Func client) => client().WithRequestTimeout(Timeouts.IpcRoundtrip); } - private ListenerConfig ShortClientTimeout(ListenerConfig listener) => listener with { RequestTimeout = TimeSpan.FromMilliseconds(100) }; - private ListenerConfig InfiniteServerTimeout(ListenerConfig listener) => listener with { RequestTimeout = Timeout.InfiniteTimeSpan }; + private ServerTransport ShortClientTimeout(ServerTransport listener) => listener with { RequestTimeout = TimeSpan.FromMilliseconds(100) }; + private ServerTransport InfiniteServerTimeout(ServerTransport listener) => listener with { RequestTimeout = Timeout.InfiniteTimeSpan }; [Fact] public async Task FireAndForget_ShouldWork() diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs index ebcdac6c..5d173821 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs @@ -9,7 +9,7 @@ public sealed class SystemTestsOverNamedPipes : SystemTests public SystemTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected sealed override async Task CreateListener() => new NamedPipeListener + protected sealed override async Task CreateListener() => new NamedPipeListener { PipeName = PipeName }; diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs b/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs index d1fefa1f..97432053 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs @@ -10,7 +10,7 @@ public sealed class SystemTestsOverTcp : SystemTests public SystemTestsOverTcp(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected sealed override async Task CreateListener() + protected sealed override async Task CreateListener() => new TcpListener { EndPoint = _endPoint, diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs b/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs index 09598cf6..235f0518 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs @@ -15,7 +15,7 @@ protected override async Task DisposeAsync() await base.DisposeAsync(); } - protected override async Task CreateListener() + protected override async Task CreateListener() { var listener = new WebSocketListener { diff --git a/src/UiPath.Ipc.Tests/TestBase.cs b/src/UiPath.Ipc.Tests/TestBase.cs index 783bf6fa..3086c9b3 100644 --- a/src/UiPath.Ipc.Tests/TestBase.cs +++ b/src/UiPath.Ipc.Tests/TestBase.cs @@ -67,7 +67,7 @@ public TestBase(ITestOutputHelper outputHelper) protected abstract void ConfigureSpecificServices(IServiceCollection services); - private Task CreateListenerAndConfigure() + private Task CreateListenerAndConfigure() { var factory = async () => { @@ -101,7 +101,7 @@ public TestBase(ITestOutputHelper outputHelper) } } }, - Listeners = [listener], + Transport = [listener], ServiceProvider = _serviceProvider, Scheduler = GuiScheduler }; @@ -133,12 +133,12 @@ public TestBase(ITestOutputHelper outputHelper) protected void CreateLazyProxy(out Lazy lazy) where TContract : class => lazy = new(GetProxy); - protected abstract Task CreateListener(); + protected abstract Task CreateListener(); protected abstract ClientConfig CreateClientConfig(EndpointCollection? callbacks = null); protected abstract ClientTransport CreateClientTransport(); - protected abstract ListenerConfig ConfigTransportAgnostic(ListenerConfig listener); + protected abstract ServerTransport ConfigTransportAgnostic(ServerTransport listener); protected virtual async Task DisposeAsync() { From 380c48aa48d06c53915f2bf16fa01b54f97a3675 Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Mon, 25 Nov 2024 09:36:32 +0100 Subject: [PATCH 02/13] taking shape --- src/Playground/Playground.csproj | 1 + src/Playground/Program.cs | 31 +-- src/UiPath.CoreIpc/Client/ServiceClient.cs | 18 +- src/UiPath.CoreIpc/Config/ClientConfig.cs | 5 +- src/UiPath.CoreIpc/Config/IListenerConfig.cs | 1 - .../Config/IServiceClientConfig.cs | 2 +- src/UiPath.CoreIpc/Config/IpcServer.cs | 172 +++++++++++--- src/UiPath.CoreIpc/Config/Peer.cs | 2 - src/UiPath.CoreIpc/Config/ServerTransport.cs | 65 ++++-- src/UiPath.CoreIpc/Connection.cs | 24 +- .../Helpers/DefaultsExtensions.cs | 4 +- src/UiPath.CoreIpc/Helpers/Router.cs | 8 +- .../Polyfills/MemberNotNullAttribute.cs | 71 ++++++ .../Polyfills/MemberNotNullWhenAttribute.cs | 85 +++---- src/UiPath.CoreIpc/Server/IClient.cs | 7 + src/UiPath.CoreIpc/Server/Listener.cs | 217 ------------------ src/UiPath.CoreIpc/Server/Server.cs | 5 +- src/UiPath.CoreIpc/Server/ServerConnection.cs | 169 ++++---------- .../Server/ServerTransportRunner.cs | 10 + ...ansport.cs => NamedPipeClientTransport.cs} | 4 +- .../Transport/NamedPipe/NamedPipeListener.cs | 72 ------ .../NamedPipe/NamedPipeServerTransport.cs | 80 +++++++ .../Transport/Tcp/TcpListener.cs | 61 ----- .../Transport/Tcp/TcpServerTransport.cs | 56 +++++ .../Transport/WebSocket/WebSocketListener.cs | 38 --- .../WebSocket/WebSocketServerTransport.cs | 25 ++ src/UiPath.CoreIpc/Wire/Dtos.cs | 4 +- src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs | 10 +- src/UiPath.Ipc.Tests/ComputingTests.cs | 12 +- .../ComputingTestsOverNamedPipes.cs | 8 +- src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs | 4 +- .../RobotTestsOverNamedPipes.cs | 10 +- src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs | 4 +- .../SystemTestsOverNamedPipes.cs | 4 +- src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs | 2 +- .../SystemTestsOverWebSockets.cs | 2 +- 36 files changed, 582 insertions(+), 711 deletions(-) create mode 100644 src/UiPath.CoreIpc/Polyfills/MemberNotNullAttribute.cs create mode 100644 src/UiPath.CoreIpc/Server/IClient.cs delete mode 100644 src/UiPath.CoreIpc/Server/Listener.cs create mode 100644 src/UiPath.CoreIpc/Server/ServerTransportRunner.cs rename src/UiPath.CoreIpc/Transport/NamedPipe/{NamedPipeTransport.cs => NamedPipeClientTransport.cs} (89%) delete mode 100644 src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeListener.cs create mode 100644 src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeServerTransport.cs delete mode 100644 src/UiPath.CoreIpc/Transport/Tcp/TcpListener.cs create mode 100644 src/UiPath.CoreIpc/Transport/Tcp/TcpServerTransport.cs delete mode 100644 src/UiPath.CoreIpc/Transport/WebSocket/WebSocketListener.cs create mode 100644 src/UiPath.CoreIpc/Transport/WebSocket/WebSocketServerTransport.cs diff --git a/src/Playground/Playground.csproj b/src/Playground/Playground.csproj index 760afbc7..334c100b 100644 --- a/src/Playground/Playground.csproj +++ b/src/Playground/Playground.csproj @@ -5,6 +5,7 @@ net8.0 enable enable + 1998 diff --git a/src/Playground/Program.cs b/src/Playground/Program.cs index df10be22..613e8d5a 100644 --- a/src/Playground/Program.cs +++ b/src/Playground/Program.cs @@ -38,6 +38,7 @@ private static async Task Main(string[] args) { Scheduler = serverScheduler, ServiceProvider = serverSP, + RequestTimeout = TimeSpan.FromHours(10), Endpoints = new() { typeof(Contracts.IServerOperations), // DEVINE @@ -50,23 +51,15 @@ private static async Task Main(string[] args) }, typeof(Contracts.IClientOperations2) }, - Transport = [ - new NamedPipeListener() + Transport = new NamedPipeServerTransport() + { + PipeName = Contracts.PipeName, + ServerName = ".", + AccessControl = ps => { - PipeName = Contracts.PipeName, - ServerName = ".", - AccessControl = ps => - { - }, - MaxReceivedMessageSizeInMegabytes = 100, - RequestTimeout = TimeSpan.FromHours(10) }, - //new BidirectionalHttp.ListenerConfig() - //{ - // Uri = serverUri, - // RequestTimeout = TimeSpan.FromHours(1) - //} - ] + MaxReceivedMessageSizeInMegabytes = 100, + } }; try @@ -91,9 +84,9 @@ private static async Task Main(string[] args) { typeof(Contracts.IClientOperations2), new Impl.Client2() }, }, ServiceProvider = clientSP, - Scheduler = clientScheduler, + Scheduler = clientScheduler, }, - Transport = new NamedPipeTransport() + Transport = new NamedPipeClientTransport() { PipeName = Contracts.PipeName, ServerName = ".", @@ -113,7 +106,7 @@ private static async Task Main(string[] args) }, Scheduler = clientScheduler, }, - Transport = new NamedPipeTransport() + Transport = new NamedPipeClientTransport() { PipeName = Contracts.PipeName, ServerName = ".", @@ -133,7 +126,7 @@ private static async Task Main(string[] args) }, Scheduler = clientScheduler, }, - Transport = new NamedPipeTransport() + Transport = new NamedPipeClientTransport() { PipeName = Contracts.PipeName, ServerName = ".", diff --git a/src/UiPath.CoreIpc/Client/ServiceClient.cs b/src/UiPath.CoreIpc/Client/ServiceClient.cs index dfe12f2b..4c0edd2f 100644 --- a/src/UiPath.CoreIpc/Client/ServiceClient.cs +++ b/src/UiPath.CoreIpc/Client/ServiceClient.cs @@ -93,7 +93,7 @@ async Task Invoke() throw; } - return response.Deserialize(Config.Serializer); + return response.Deserialize(); } catch (Exception ex) { @@ -123,7 +123,7 @@ string[] SerializeArguments() break; } - result[index] = Config.Serializer.OrDefault().Serialize(args[index]); + result[index] = IpcJsonSerializer.Instance.Serialize(args[index]); } return result; @@ -227,7 +227,7 @@ public override async ValueTask CloseConnection() var network = await Connect(ct); - LatestConnection = new Connection(network, Config.Serializer, Config.Logger, Config.DebugName); + LatestConnection = new Connection(network, Config.DebugName, Config.Logger); var router = new Router(_client.Config.CreateCallbackRouterConfig(), _client.Config.ServiceProvider); _latestServer = new Server(router, _client.Config.RequestTimeout, LatestConnection); @@ -263,19 +263,21 @@ private async Task Connect(CancellationToken ct) protected override IServiceClientConfig Config => _client.Config; } -internal sealed class ServiceClientForCallback : ServiceClient +internal sealed class ServiceClientForCallback : ServiceClient where TInterface : class { private readonly Connection _connection; - private readonly Listener _listener; + private readonly IServiceClientConfig _config; public override Stream? Network => _connection.Network; - public ServiceClientForCallback(Connection connection, Listener listener, Type interfaceType) : base(interfaceType) + public ServiceClientForCallback(Connection connection, IServiceClientConfig config) : base(typeof(TInterface)) { _connection = connection; - _listener = listener; + _config = config; } + public TInterface GetProxy() => GetProxy(); + public override void Dispose() { // do nothing @@ -284,5 +286,5 @@ public override void Dispose() protected override Task<(Connection connection, bool newlyConnected)> EnsureConnection(CancellationToken ct) => Task.FromResult((_connection, newlyConnected: false)); - protected override IServiceClientConfig Config => _listener.Config; + protected override IServiceClientConfig Config => _config; } diff --git a/src/UiPath.CoreIpc/Config/ClientConfig.cs b/src/UiPath.CoreIpc/Config/ClientConfig.cs index 7d77abfa..c6ac4bdb 100644 --- a/src/UiPath.CoreIpc/Config/ClientConfig.cs +++ b/src/UiPath.CoreIpc/Config/ClientConfig.cs @@ -2,16 +2,13 @@ namespace UiPath.Ipc; -public sealed record ClientConfig : Peer, IServiceClientConfig +public sealed class ClientConfig : Peer, IServiceClientConfig { public EndpointCollection? Callbacks { get; init; } - public IServiceProvider? ServiceProvider { get; init; } public ILogger? Logger { get; init; } public BeforeConnectHandler? BeforeConnect { get; init; } public BeforeCallHandler? BeforeCall { get; init; } - public TaskScheduler? Scheduler { get; init; } - public ISerializer? Serializer { get; set; } [EditorBrowsable(EditorBrowsableState.Never)] public string DebugName { get; set; } = null!; diff --git a/src/UiPath.CoreIpc/Config/IListenerConfig.cs b/src/UiPath.CoreIpc/Config/IListenerConfig.cs index dd8976cc..da4732fc 100644 --- a/src/UiPath.CoreIpc/Config/IListenerConfig.cs +++ b/src/UiPath.CoreIpc/Config/IListenerConfig.cs @@ -9,4 +9,3 @@ internal interface IListenerConfig ValueTask AwaitConnection(TListenerState listenerState, TConnectionState connectionState, CancellationToken ct); IEnumerable Validate(); } - diff --git a/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs b/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs index 95d3a29e..19ed49fd 100644 --- a/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs +++ b/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs @@ -1,11 +1,11 @@ namespace UiPath.Ipc; +// Maybe decommission internal interface IServiceClientConfig { TimeSpan RequestTimeout { get; } BeforeConnectHandler? BeforeConnect { get; } BeforeCallHandler? BeforeCall { get; } ILogger? Logger { get; } - ISerializer? Serializer { get; } string DebugName { get; } } diff --git a/src/UiPath.CoreIpc/Config/IpcServer.cs b/src/UiPath.CoreIpc/Config/IpcServer.cs index a9485a3b..d0f58020 100644 --- a/src/UiPath.CoreIpc/Config/IpcServer.cs +++ b/src/UiPath.CoreIpc/Config/IpcServer.cs @@ -1,64 +1,174 @@ -namespace UiPath.Ipc; +using System.Diagnostics.CodeAnalysis; + +namespace UiPath.Ipc; public sealed class IpcServer : Peer, IAsyncDisposable { public required EndpointCollection Endpoints { get; init; } public required ServerTransport Transport { get; init; } - private readonly Lazy _listener; - private readonly TaskCompletionSource _tcsStopped = new(); + private readonly object _lock = new(); + private readonly TaskCompletionSource _listening = new(); + private readonly TaskCompletionSource _stopped = new(); + private readonly CancellationTokenSource _ctsActiveConnections = new(); + + private bool _disposeStarted; + private Accepter? _accepter; - public IpcServer() + public async ValueTask DisposeAsync() { - _listener = new(() => Listener.Create(server: this)); + Accepter? accepter = null; + lock (_lock) + { + _disposeStarted = true; + accepter = _accepter; + } + + await (accepter?.DisposeAsync() ?? default); + _ctsActiveConnections.Cancel(); + _ctsActiveConnections.Dispose(); } + [MemberNotNull(nameof(Transport), nameof(_accepter))] public void Start() { - if (!IsValid(out var errors)) + lock (_lock) { - throw new InvalidOperationException($"ValidationErrors:\r\n{string.Join("\r\n", errors)}"); + if (_disposeStarted) + { + throw new ObjectDisposedException(nameof(IpcServer)); + } + + if (!IsValid(out var errors)) + { + throw new InvalidOperationException($"ValidationErrors:\r\n{string.Join("\r\n", errors)}"); + } + + if (_accepter is not null) + { + return; + } + + _accepter = new(Transport, new ObserverAdapter() + { + OnNext = OnNewConnection, + OnError = OnNewConnectionError, + }); } + } + + public Task WaitForStart() + { + Start(); + return _accepter.StartedAccepting; + } + public Task WaitForStop() => _stopped.Task; + + internal ILogger? CreateLogger(string category) => ServiceProvider.MaybeCreateLogger(category); + + private void OnNewConnection(Stream network) + { + ServerConnection.CreateAndListen(server: this, network, ct: _ctsActiveConnections.Token); + } + + private void OnNewConnectionError(Exception ex) + { + Trace.TraceError($"Failed to accept new connection. Ex: {ex}"); + _stopped.TrySetException(ex); + } + + private sealed class ObserverAdapter : IObserver + { + public required Action OnNext { get; init; } + public Action? OnError { get; init; } + public Action? OnCompleted { get; init; } - _ = _started.Value; + void IObserver.OnNext(T value) => OnNext(value); + void IObserver.OnError(Exception error) => OnError?.Invoke(error); + void IObserver.OnCompleted() => OnCompleted?.Invoke(); } - public Task WaitForStart() => _started.Value; - public Task WaitForStop() => _tcsStopped.Task; - private Listener? StartCore() + private sealed class Accepter : IAsyncDisposable { - if (!IsValid(out _)) + private readonly CancellationTokenSource _cts = new(); + private readonly ServerTransport.IServerState _serverState; + private readonly Task _running; + private readonly IObserver _newConnection; + private readonly TaskCompletionSource _tcsStartedAccepting = new(); + + public Task StartedAccepting => _tcsStartedAccepting.Task; + + public Accepter(ServerTransport transport, IObserver connected) { - return null; + _serverState = transport.CreateServerState(); + _newConnection = connected; + _running = RunOnThreadPool(LoopAccept, parallelCount: transport.ConcurrentAccepts, _cts.Token); } - try + public async ValueTask DisposeAsync() { - return Listener.Create(this, Transport); + _cts.Cancel(); + await _running; + _cts.Dispose(); } - catch (Exception ex) + + private async Task LoopAccept(CancellationToken ct) { - Trace.TraceError($"Failed to start server. Ex: {ex}"); - _tcsStopped.TrySetException(ex); - disposables.SetException(ex); - await disposables.DisposeAsync(); - throw; + try + { + while (!ct.IsCancellationRequested) + { + await Accept(ct); + } + } + catch (OperationCanceledException ex) when (ex.CancellationToken == ct) + { + // Ignore + } + + _newConnection.OnCompleted(); } - } - private bool IsValid(out IReadOnlyList errors) - { - errors = PrefixErrors(Transport).ToArray(); - return errors is { Count: 0 }; + private async Task Accept(CancellationToken ct) + { + var slot = _serverState.CreateConnectionSlot(); - static IEnumerable PrefixErrors(ServerTransport transport) - => transport.Validate().Select(error => $"{transport.GetType().Name}: {error}"); + try + { + var taskNewConnection = slot.AwaitConnection(ct); + _tcsStartedAccepting.TrySetResult(null); + + var newConnection = await taskNewConnection; + _newConnection.OnNext(newConnection); + } + catch (Exception ex) + { + slot.Dispose(); + _newConnection.OnError(ex); + return; + } + } + + private static Task RunOnThreadPool(Func action, int parallelCount, CancellationToken ct) + => Task.WhenAll(Enumerable.Range(start: 0, parallelCount).Select(_ => Task.Run(() => action(ct)))); } - public async ValueTask DisposeAsync() + [MemberNotNullWhen(returnValue: true, member: nameof(Transport))] + private bool IsValid([NotNullWhen(returnValue: false)] out string? errorMessage) { - var maybeLogger = ServiceProvider.GetService()?.CreateLogger(typeof(IpcServer)); + if (Transport is null) + { + errorMessage = $"{nameof(Transport)} is not set."; + return false; + } + + if (string.Join("\r\n", Transport.Validate()) is { Length: > 0 } concatenation) + { + errorMessage = concatenation; + return false; + } - await ((await _started.Value)?.DisposeAsync() ?? default); + errorMessage = null; + return true; } } diff --git a/src/UiPath.CoreIpc/Config/Peer.cs b/src/UiPath.CoreIpc/Config/Peer.cs index 432e3a86..c7e9ba8a 100644 --- a/src/UiPath.CoreIpc/Config/Peer.cs +++ b/src/UiPath.CoreIpc/Config/Peer.cs @@ -6,8 +6,6 @@ public abstract class Peer public IServiceProvider? ServiceProvider { get; init; } public TaskScheduler? Scheduler { get; init; } - internal ISerializer? Serializer => IpcJsonSerializer.Instance; - internal virtual RouterConfig CreateRouterConfig(IpcServer server) => throw new NotSupportedException(); internal virtual RouterConfig CreateCallbackRouterConfig() => throw new NotSupportedException(); diff --git a/src/UiPath.CoreIpc/Config/ServerTransport.cs b/src/UiPath.CoreIpc/Config/ServerTransport.cs index 4c15ec53..ed49685e 100644 --- a/src/UiPath.CoreIpc/Config/ServerTransport.cs +++ b/src/UiPath.CoreIpc/Config/ServerTransport.cs @@ -1,31 +1,58 @@ -using System.Security.Cryptography.X509Certificates; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; namespace UiPath.Ipc; -public abstract class ServerTransport : Peer, IServiceClientConfig +public abstract class ServerTransport { public int ConcurrentAccepts { get; init; } = 5; public byte MaxReceivedMessageSizeInMegabytes { get; init; } = 2; public X509Certificate? Certificate { get; init; } internal int MaxMessageSize => MaxReceivedMessageSizeInMegabytes * 1024 * 1024; - internal IEnumerable Validate() => Enumerable.Empty(); + // TODO: Maybe decommission. + internal async Task MaybeAuthenticate(Stream network) + { + if (Certificate is null) + { + return network; + } - internal override RouterConfig CreateRouterConfig(IpcServer server) - => RouterConfig.From( - server.Endpoints, - endpoint => endpoint with + var sslStream = new SslStream(network, leaveInnerStreamOpen: false); + try + { + await sslStream.AuthenticateAsServerAsync(Certificate); + } + catch { - Scheduler = endpoint.Scheduler ?? server.Scheduler - }); - - #region IServiceClientConfig - /// Do not implement explicitly, as it must be implicitly implemented by . - - BeforeConnectHandler? IServiceClientConfig.BeforeConnect => null; - BeforeCallHandler? IServiceClientConfig.BeforeCall => null; - ILogger? IServiceClientConfig.Logger => null; - ISerializer? IServiceClientConfig.Serializer => null!; - string IServiceClientConfig.DebugName => $"CallbackClient for {this}"; - #endregion + sslStream.Dispose(); + throw; + } + + Debug.Assert(sslStream.IsEncrypted && sslStream.IsSigned); + return sslStream; + } + + protected internal abstract IServerState CreateServerState(); + + internal IEnumerable Validate() + => ValidateCore().Where(x => x is not null).Select(x => $"{GetType().Name}.{x}"); + protected abstract IEnumerable ValidateCore(); + protected static string? IsNotNull(T? propertyValue, [CallerArgumentExpression(nameof(propertyValue))] string? propertyName = null) + { + if (propertyValue is null) + { + return $"{propertyName} is required."; + } + return null; + } + + protected internal interface IServerState : IAsyncDisposable + { + IServerConnectionSlot CreateConnectionSlot(); + } + protected internal interface IServerConnectionSlot : IDisposable + { + ValueTask AwaitConnection(CancellationToken ct); + } } diff --git a/src/UiPath.CoreIpc/Connection.cs b/src/UiPath.CoreIpc/Connection.cs index a8c3aa30..be2b3bde 100644 --- a/src/UiPath.CoreIpc/Connection.cs +++ b/src/UiPath.CoreIpc/Connection.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.IO.Pipes; namespace UiPath.Ipc; @@ -21,22 +20,21 @@ internal sealed class Connection : IDisposable private readonly Action _cancelRequest; private readonly byte[] _buffer = new byte[sizeof(long)]; private readonly NestedStream _nestedStream; + + public string DebugName { get; } + public ILogger? Logger { get; } + public Stream Network { get; } - public ILogger? Logger { get; internal set; } [MemberNotNullWhen(returnValue: true, nameof(Logger))] public bool LogEnabled => Logger.Enabled(); - public string DebugName { get; } - public ISerializer? Serializer { get; } - - public Connection(Stream network, ISerializer? serializer, ILogger? logger, string debugName, int maxMessageSize = int.MaxValue) + public Connection(Stream network, string debugName, ILogger? logger, int maxMessageSize = int.MaxValue) { Network = network; _nestedStream = new NestedStream(network, 0); - Serializer = serializer; + DebugName = debugName; Logger = logger; - DebugName = $"{debugName} {GetHashCode()}"; _maxMessageSize = maxMessageSize; _onResponse = response => OnResponseReceived((Response)response!); _onRequest = request => OnRequestReceived((Request)request!); @@ -49,8 +47,8 @@ public Connection(Stream network, ISerializer? serializer, ILogger? logger, stri public string NewRequestId() => Interlocked.Increment(ref _requestCounter).ToString(); internal Task Listen() => _receiveLoop.Value; - internal event Func RequestReceived; - internal event Action CancellationReceived; + internal event Func? RequestReceived; + internal event Action? CancellationReceived; public event EventHandler? Closed; #if !NET461 [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))] @@ -351,7 +349,7 @@ private MemoryStream SerializeToStream(object value) try { stream.Position = HeaderLength; - Serializer.OrDefault().Serialize(value, stream); + IpcJsonSerializer.Instance.Serialize(value, stream); return stream; } catch @@ -360,7 +358,7 @@ private MemoryStream SerializeToStream(object value) throw; } } - private ValueTask Deserialize() => Serializer.OrDefault().DeserializeAsync(_nestedStream, Logger); + private ValueTask Deserialize() => IpcJsonSerializer.Instance.DeserializeAsync(_nestedStream, Logger); private void OnCancellationReceived(CancellationRequest cancellationRequest) { diff --git a/src/UiPath.CoreIpc/Helpers/DefaultsExtensions.cs b/src/UiPath.CoreIpc/Helpers/DefaultsExtensions.cs index 9752811d..4b6d9ca1 100644 --- a/src/UiPath.CoreIpc/Helpers/DefaultsExtensions.cs +++ b/src/UiPath.CoreIpc/Helpers/DefaultsExtensions.cs @@ -4,8 +4,8 @@ namespace UiPath.Ipc; internal static class DefaultsExtensions { - public static ISerializer OrDefault(this ISerializer? serializer) => serializer ?? IpcJsonSerializer.Instance; - public static ILoggerFactory OrDefault(this ILoggerFactory? loggerFactory) => loggerFactory ?? NullLoggerFactory.Instance; + public static ILogger? MaybeCreateLogger(this IServiceProvider? serviceProvider, string category) => serviceProvider?.GetService()?.CreateLogger(category); + public static ILogger OrDefault(this ILogger? logger) => logger ?? NullLogger.Instance; public static BeforeCallHandler OrDefault(this BeforeCallHandler? beforeCallHandler) => beforeCallHandler ?? DefaultBeforeCallHandler; public static TaskScheduler OrDefault(this TaskScheduler? scheduler) => scheduler ?? TaskScheduler.Default; diff --git a/src/UiPath.CoreIpc/Helpers/Router.cs b/src/UiPath.CoreIpc/Helpers/Router.cs index ff3d25a6..45252243 100644 --- a/src/UiPath.CoreIpc/Helpers/Router.cs +++ b/src/UiPath.CoreIpc/Helpers/Router.cs @@ -24,6 +24,12 @@ internal readonly struct Router private readonly RouterConfig? _config; // nullable for the case when the constructor is bypassed private readonly IServiceProvider? _serviceProvider; + public Router(IpcServer ipcServer) + { + _config = ipcServer.CreateRouterConfig(ipcServer); + _serviceProvider = ipcServer.ServiceProvider; + } + public Router(RouterConfig config, IServiceProvider? serviceProvider) { _config = config; @@ -128,7 +134,6 @@ public static Route From(IServiceProvider? serviceProvider, EndpointSettings end BeforeCall = endpointSettings.BeforeCall, Scheduler = endpointSettings.Scheduler.OrDefault(), LoggerFactory = serviceProvider.MaybeCreateServiceFactory(), - Serializer = serviceProvider.MaybeCreateServiceFactory() }; public required ServiceFactory Service { get; init; } @@ -136,5 +141,4 @@ public static Route From(IServiceProvider? serviceProvider, EndpointSettings end public TaskScheduler Scheduler { get; init; } public BeforeCallHandler? BeforeCall { get; init; } public Func? LoggerFactory { get; init; } - public Func? Serializer { get; init; } } diff --git a/src/UiPath.CoreIpc/Polyfills/MemberNotNullAttribute.cs b/src/UiPath.CoreIpc/Polyfills/MemberNotNullAttribute.cs new file mode 100644 index 00000000..bf9ad270 --- /dev/null +++ b/src/UiPath.CoreIpc/Polyfills/MemberNotNullAttribute.cs @@ -0,0 +1,71 @@ +// +#pragma warning disable + +#if NETSTANDARD || NETFRAMEWORK || NETCOREAPPX + +namespace System.Diagnostics.CodeAnalysis; + +using Targets = AttributeTargets; + +/// +/// Specifies that the method or property will ensure that the listed field and property members have +/// non- values when returning with the specified return value condition. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + validOn: Targets.Method | + Targets.Property, + Inherited = false, + AllowMultiple = true)] +#if PolyPublic +public +#endif +sealed class MemberNotNullWhenAttribute : + Attribute +{ + /// + /// Gets the return value condition. + /// + public bool ReturnValue { get; } + + /// + /// Gets field or property member names. + /// + public string[] Members { get; } + + /// + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// + /// The return value condition. If the method returns this value, + /// the associated parameter will not be . + /// + /// + /// The field or property member that is promised to be not-. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = [member]; + } + + /// + /// Initializes the attribute with the specified return value condition and list + /// of field and property members. + /// + /// + /// The return value condition. If the method returns this value, + /// the associated parameter will not be . + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } +} + +#endif \ No newline at end of file diff --git a/src/UiPath.CoreIpc/Polyfills/MemberNotNullWhenAttribute.cs b/src/UiPath.CoreIpc/Polyfills/MemberNotNullWhenAttribute.cs index bf9ad270..b49daaab 100644 --- a/src/UiPath.CoreIpc/Polyfills/MemberNotNullWhenAttribute.cs +++ b/src/UiPath.CoreIpc/Polyfills/MemberNotNullWhenAttribute.cs @@ -5,67 +5,34 @@ namespace System.Diagnostics.CodeAnalysis; -using Targets = AttributeTargets; - -/// -/// Specifies that the method or property will ensure that the listed field and property members have -/// non- values when returning with the specified return value condition. -/// -[ExcludeFromCodeCoverage] -[DebuggerNonUserCode] -[AttributeUsage( - validOn: Targets.Method | - Targets.Property, - Inherited = false, - AllowMultiple = true)] -#if PolyPublic -public -#endif -sealed class MemberNotNullWhenAttribute : - Attribute +// +// Summary: +// Specifies that the method or property will ensure that the listed field and property +// members have values that aren't null. +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +internal sealed class MemberNotNullAttribute : Attribute { - /// - /// Gets the return value condition. - /// - public bool ReturnValue { get; } - - /// - /// Gets field or property member names. - /// + // + // Summary: + // Initializes the attribute with a field or property member. + // + // Parameters: + // member: + // The field or property member that is promised to be non-null. + public MemberNotNullAttribute(string member) : this([member]) { } + // + // Summary: + // Initializes the attribute with the list of field and property members. + // + // Parameters: + // members: + // The list of field and property members that are promised to be non-null. + public MemberNotNullAttribute(params string[] members) => Members = members; + + // + // Summary: + // Gets field or property member names. public string[] Members { get; } - - /// - /// Initializes the attribute with the specified return value condition and a field or property member. - /// - /// - /// The return value condition. If the method returns this value, - /// the associated parameter will not be . - /// - /// - /// The field or property member that is promised to be not-. - /// - public MemberNotNullWhenAttribute(bool returnValue, string member) - { - ReturnValue = returnValue; - Members = [member]; - } - - /// - /// Initializes the attribute with the specified return value condition and list - /// of field and property members. - /// - /// - /// The return value condition. If the method returns this value, - /// the associated parameter will not be . - /// - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, params string[] members) - { - ReturnValue = returnValue; - Members = members; - } } #endif \ No newline at end of file diff --git a/src/UiPath.CoreIpc/Server/IClient.cs b/src/UiPath.CoreIpc/Server/IClient.cs new file mode 100644 index 00000000..1fa7c645 --- /dev/null +++ b/src/UiPath.CoreIpc/Server/IClient.cs @@ -0,0 +1,7 @@ +namespace UiPath.Ipc; + +public interface IClient +{ + TCallbackInterface GetCallback() where TCallbackInterface : class; + void Impersonate(Action action); +} diff --git a/src/UiPath.CoreIpc/Server/Listener.cs b/src/UiPath.CoreIpc/Server/Listener.cs deleted file mode 100644 index df472011..00000000 --- a/src/UiPath.CoreIpc/Server/Listener.cs +++ /dev/null @@ -1,217 +0,0 @@ -using System.Linq.Expressions; - -namespace UiPath.Ipc; - -using ListenerFactory = Func; - -internal abstract class Listener : IAsyncDisposable -{ - private static readonly GenericListenerFactoryCache Cache = new(); - - public static Listener Create(IpcServer server) - { - var transportType = server.Transport.GetType(); - var listenerFactory = Cache.Get(transportType); - var listener = listenerFactory(server); - return listener; - } - - private readonly Lazy _disposeTask = null!; - public readonly ServerTransport Config; - public readonly IpcServer Server; - private readonly Lazy _loggerCategory; - - private readonly Lazy _lazyLogger; - public ILogger Logger => _lazyLogger.Value; - - protected Listener(IpcServer server, ServerTransport config) - { - _loggerCategory = new(ComputeLoggerCategory); - Config = config; - Server = server; - _lazyLogger = new(() => server.ServiceProvider.GetService().OrDefault().CreateLogger(LoggerCategory)); - _disposeTask = new(DisposeCore); - } - - ValueTask IAsyncDisposable.DisposeAsync() => new(_disposeTask.Value); - - protected abstract Task DisposeCore(); - - private string LoggerCategory => _loggerCategory.Value; - - private string ComputeLoggerCategory() - => $"{GetType().Namespace}.{nameof(Listener)}<{ConfigType.Name}[{Config}],..>"; - - protected abstract Type ConfigType { get; } -} - -internal sealed class Listener : Listener, IAsyncDisposable - where TConfig : ServerTransport, IListenerConfig - where TListenerState : IAsyncDisposable - where TConnectionState : IDisposable -{ - private readonly CancellationTokenSource _cts = new(); - private readonly Task _listeningTask = null!; - - public new readonly TConfig Config; - - public TListenerState State { get; } - - public Listener(IpcServer server, TConfig config) : base(server, config) - { - Config = config; - State = Config.CreateListenerState(server); - - _listeningTask = Task.Run(() => Listen(_cts.Token)); - } - - public void Log(string message) - { - if (!Logger.Enabled()) - { - return; - } - - Logger.LogInformation(message); - } - public void LogError(Exception exception, string message) - { - if (!Logger.Enabled(LogLevel.Error)) - { - return; - } - - Logger.LogError(exception, message); - } - - protected override async Task DisposeCore() - { - Log($"Stopping listener {Config}..."); - try - { - _cts.Cancel(); - } - catch (Exception ex) - { - LogError(ex, $"Canceling {Config} failed."); - } - try - { - await _listeningTask; - } - catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token) - { - Log($"Stopping listener {Config} threw OCE."); - } - catch (Exception ex) - { - LogError(ex, $"Stopping listener {Config} failed."); - } - await State.DisposeAsync(); - _cts.Dispose(); - } - - private async Task Listen(CancellationToken ct) - { - Log($"Starting listener {Config}..."); - - await Task.WhenAll(Enumerable.Range(1, Config.ConcurrentAccepts).Select(async _ => - { - while (!ct.IsCancellationRequested) - { - await AcceptConnection(ct); - } - })); - } - private async Task AcceptConnection(CancellationToken ct) - { - var serverConnection = new ServerConnection(this); - - Stream? network = null; - try - { - network = await serverConnection.AcceptClient(ct); - } - catch - { - serverConnection.Dispose(); - return; - } - - try - { - _ = Task.Run(TryToListen); - } - catch (Exception ex) - { - serverConnection.Dispose(); - if (!ct.IsCancellationRequested) - { - Logger.LogException(ex, Config); - } - } - - async Task TryToListen() - { - try - { - await serverConnection.Listen(network, ct); - } - catch (Exception ex) - { - Logger.LogException(ex, $"Listen loop failed for {Config}"); - } - } - } - - protected override Type ConfigType => typeof(TConfig); -} - -internal sealed class GenericListenerFactoryCache -{ - private readonly ConcurrentDictionary> _cache = new(); - - public ListenerFactory Get(Type configType) => _cache.GetOrAdd(configType, Create).Value; - - private Result Create(Type configType) - { - try - { - return new(Emit(configType)); - } - catch (Exception ex) - { - return new(ex); - } - - static ListenerFactory Emit(Type configType) - { - if (configType.GetInterfaces().SingleOrDefault(IsIListenerConfig) is not { } iface - || iface.GetGenericArguments() is not [_, var listenerStateType, var connectionStateType]) - { - throw new ArgumentOutOfRangeException(nameof(iface), $"The ListenerConfig type must implement IListenerConfig<,>. ListenerConfig type was: {configType.FullName}"); - } - - var listenerType = typeof(Listener<,,>).MakeGenericType(configType, listenerStateType, connectionStateType); - var listenerCtor = listenerType.GetConstructor( - bindingAttr: BindingFlags.Public | BindingFlags.Instance, - binder: Type.DefaultBinder, - types: [typeof(IpcServer), configType], - modifiers: null)!; - - var paramofServer = Expression.Parameter(typeof(IpcServer)); - var paramofConfig = Expression.Parameter(typeof(ServerTransport)); - var lambda = Expression.Lambda( - delegateType: typeof(ListenerFactory), - body: Expression.New(listenerCtor, paramofServer, Expression.Convert(paramofConfig, configType)), - paramofServer, - paramofConfig); - - var @delegate = (lambda.Compile() as ListenerFactory)!; - return @delegate; - } - - static bool IsIListenerConfig(Type candidateIface) - => candidateIface.IsGenericType && candidateIface.GetGenericTypeDefinition() == typeof(IListenerConfig<,,>); - } -} diff --git a/src/UiPath.CoreIpc/Server/Server.cs b/src/UiPath.CoreIpc/Server/Server.cs index e54c6c96..16b1e691 100644 --- a/src/UiPath.CoreIpc/Server/Server.cs +++ b/src/UiPath.CoreIpc/Server/Server.cs @@ -28,7 +28,6 @@ static Server() private ILogger? Logger => _connection.Logger; private bool LogEnabled => Logger.Enabled(); - public ISerializer Serializer => _connection.Serializer.OrDefault(); public string DebugName => _connection.DebugName; public Server(Router router, TimeSpan requestTimeout, Connection connection, IClient? client = null) @@ -154,7 +153,7 @@ async ValueTask InvokeMethod() return result switch { Stream downloadStream => Response.Success(request, downloadStream), - var x => Response.Success(request, Serializer.Serialize(x)) + var x => Response.Success(request, IpcJsonSerializer.Instance.Serialize(x)) }; } @@ -213,7 +212,7 @@ void Deserialize() } else { - argument = Serializer.Deserialize(request.Parameters[index], parameterType); + argument = IpcJsonSerializer.Instance.Deserialize(request.Parameters[index], parameterType); argument = CheckMessage(argument, parameterType); } allArguments[index] = argument; diff --git a/src/UiPath.CoreIpc/Server/ServerConnection.cs b/src/UiPath.CoreIpc/Server/ServerConnection.cs index be2c90c8..cde20546 100644 --- a/src/UiPath.CoreIpc/Server/ServerConnection.cs +++ b/src/UiPath.CoreIpc/Server/ServerConnection.cs @@ -1,155 +1,80 @@ using System.IO.Pipes; -using System.Net.Security; -namespace UiPath.Ipc; -public interface IClient -{ - TCallbackInterface GetCallback() where TCallbackInterface : class; - void Impersonate(Action action); -} +namespace UiPath.Ipc; -internal sealed class ServerConnection : ServerConnection - where TConfig : ServerTransport, IListenerConfig - where TListenerState : IAsyncDisposable - where TConnectionState : IDisposable +internal sealed class ServerConnection : IClient, IDisposable, IServiceClientConfig { - public new readonly Listener Listener; - - private readonly object _lock = new(); - private bool _acceptClientCalled = false; - private bool _disposed = false; - private TConnectionState? _connectionState; - - public ServerConnection(Listener listener) : base(listener) => Listener = listener; - - public override ValueTask AcceptClient(CancellationToken ct) + public static void CreateAndListen(IpcServer server, Stream network, CancellationToken ct) { - lock (_lock) + _ = Task.Run(async () => { - if (_disposed) - { - throw new ObjectDisposedException(nameof(ServerConnection)); - } - if (_acceptClientCalled) - { - throw new InvalidOperationException("AcceptClient can only be called once."); - } - _acceptClientCalled = true; - _connectionState = Listener.Config.CreateConnectionState(Listener.Server, Listener.State); - } - - return Listener.Config.AwaitConnection(Listener.State, _connectionState, ct); + _ = new ServerConnection(server, await server.Transport.MaybeAuthenticate(network), ct); + }); } - public override void Dispose() - { - base.Dispose(); + private readonly string _debugName; + private readonly ILogger? _logger; + private readonly ConcurrentDictionary _callbacks = new(); + private readonly IpcServer _ipcServer; - lock (_lock) - { - if (_disposed) - { - return; - } - _disposed = true; - - if (_connectionState is not null) - { - _connectionState.Dispose(); - } - } - } -} + private readonly Stream _network; + private readonly Connection _connection; + private readonly Server _server; -internal abstract class ServerConnection : IClient, IDisposable -{ - public readonly Listener Listener; + private readonly Task _listening; - private readonly ConcurrentDictionary _callbacks = new(); - internal Connection? Connection; - private Task? _connectionAsTask; - private Server? Server; + private ServerConnection(IpcServer server, Stream network, CancellationToken ct) + { + _ipcServer = server; - protected ServerConnection(Listener listener) => Listener = listener; + _debugName = $"{nameof(ServerConnection)} {RuntimeHelpers.GetHashCode(this)}"; + _logger = server.CreateLogger(_debugName); - protected internal virtual void Initialize() { } + _network = network; - public abstract ValueTask AcceptClient(CancellationToken cancellationToken); + _connection = new Connection(network, _debugName, _logger, maxMessageSize: _ipcServer.Transport.MaxMessageSize); + _server = new Server(new Router(_ipcServer), _ipcServer.RequestTimeout, _connection, client: this); - public void Impersonate(Action action) - { - if (Connection is null) - { - throw new InvalidOperationException("The server connection is not listening yet."); - } + _listening = Listen(ct); + } - if (Connection.Network is not NamedPipeServerStream pipeStream) + private async Task Listen(CancellationToken ct) + { + // close the connection when the service host closes + using (ct.UnsafeRegister(_ => _connection.Dispose(), state: null)) { - action(); - return; + await _connection.Listen(); } - - pipeStream.RunAsClient(() => action()); } - TCallbackInterface IClient.GetCallback() where TCallbackInterface : class + void IDisposable.Dispose() => _network.Dispose(); + + TCallbackInterface IClient.GetCallback() { return (TCallbackInterface)_callbacks.GetOrAdd(typeof(TCallbackInterface), CreateCallback); TCallbackInterface CreateCallback(Type callbackContract) { - Listener.Logger.LogInformation($"Create callback {callbackContract} {Listener.Config}"); - - _connectionAsTask ??= Task.FromResult(Connection!); - - // TODO: rethink this double specification of TCallbackInterface - return new ServiceClientForCallback(Connection!, Listener, typeof(TCallbackInterface)).GetProxy(); + _logger.LogInformation($"Create callback {callbackContract}."); + return new ServiceClientForCallback(_connection, config: this).GetProxy(); } } - public async Task Listen(Stream network, CancellationToken cancellationToken) + void IClient.Impersonate(Action action) { - var stream = await AuthenticateAsServer(); // TODO: should we decommission this? - var serializer = Listener.Server.ServiceProvider.GetService(); - - Connection = new Connection(stream, serializer, Listener.Logger, debugName: Listener.ToString()!, maxMessageSize: Listener.Config.MaxMessageSize); - - Server = new Server( - new Router( - Listener.Config.CreateRouterConfig(Listener.Server), - Listener.Server.ServiceProvider), - Listener.Config.RequestTimeout, - Connection, - client: this); - - // close the connection when the service host closes - using (cancellationToken.UnsafeRegister(_ => Connection.Dispose(), state: null)) + if (_connection.Network is not NamedPipeServerStream pipeStream) { - await Connection.Listen(); + action(); + return; } - return; - // TODO: should we decommission this? - async Task AuthenticateAsServer() - { - if (Listener.Config.Certificate is null) - { - return network; - } - - var sslStream = new SslStream(network); - try - { - await sslStream.AuthenticateAsServerAsync(Listener.Config.Certificate); - } - catch - { - sslStream.Dispose(); - throw; - } - - Debug.Assert(sslStream.IsEncrypted && sslStream.IsSigned); - return sslStream; - } + pipeStream.RunAsClient(() => action()); } - public virtual void Dispose() { } + + #region IServiceClientConfig + TimeSpan IServiceClientConfig.RequestTimeout => _ipcServer.RequestTimeout; + BeforeConnectHandler? IServiceClientConfig.BeforeConnect => null; + BeforeCallHandler? IServiceClientConfig.BeforeCall => null; + ILogger? IServiceClientConfig.Logger => _logger; + string IServiceClientConfig.DebugName => _debugName; + #endregion } diff --git a/src/UiPath.CoreIpc/Server/ServerTransportRunner.cs b/src/UiPath.CoreIpc/Server/ServerTransportRunner.cs new file mode 100644 index 00000000..bb68f927 --- /dev/null +++ b/src/UiPath.CoreIpc/Server/ServerTransportRunner.cs @@ -0,0 +1,10 @@ +namespace UiPath.Ipc; + +internal static class ServerTransportRunner +{ + public static async Task Start(ServerTransport transport) + { + var serverState = transport.CreateServerState(); + return serverState; + } +} diff --git a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeTransport.cs b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeClientTransport.cs similarity index 89% rename from src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeTransport.cs rename to src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeClientTransport.cs index ba8fe11e..d6bd0aff 100644 --- a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeTransport.cs +++ b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeClientTransport.cs @@ -3,7 +3,7 @@ namespace UiPath.Ipc.Transport.NamedPipe; -public sealed record NamedPipeTransport : ClientTransport +public sealed record NamedPipeClientTransport : ClientTransport { public required string PipeName { get; init; } public string ServerName { get; init; } = "."; @@ -35,7 +35,7 @@ internal sealed class NamedPipeClientState : IClientState public async ValueTask Connect(IpcClient client, CancellationToken ct) { - var transport = client.Transport as NamedPipeTransport ?? throw new InvalidOperationException(); + var transport = client.Transport as NamedPipeClientTransport ?? throw new InvalidOperationException(); _pipe = new NamedPipeClientStream( transport.ServerName, diff --git a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeListener.cs b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeListener.cs deleted file mode 100644 index 219fc7a6..00000000 --- a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeListener.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Newtonsoft.Json; -using System.IO.Pipes; -using System.Security.Principal; - -namespace UiPath.Ipc.Transport.NamedPipe; - -using INamedPipeListenerConfig = IListenerConfig; - -public sealed record NamedPipeListener : ServerTransport, INamedPipeListenerConfig -{ - public required string PipeName { get; init; } - public string ServerName { get; init; } = "."; - [JsonIgnore] - public AccessControlDelegate? AccessControl { get; init; } - - private PipeSecurity? GetPipeSecurity() - { - var setAccessControl = AccessControl; - if (setAccessControl is null) - { - return null; - } - - var pipeSecurity = new PipeSecurity(); - FullControlFor(WellKnownSidType.BuiltinAdministratorsSid); - FullControlFor(WellKnownSidType.LocalSystemSid); - pipeSecurity.AllowCurrentUser(onlyNonAdmin: true); - setAccessControl(pipeSecurity); - return pipeSecurity; - void FullControlFor(WellKnownSidType sid) => pipeSecurity.Allow(sid, PipeAccessRights.FullControl); - } - - NamedPipeListenerState INamedPipeListenerConfig.CreateListenerState(IpcServer server) - => new(); - - NamedPipeServerConnectionState INamedPipeListenerConfig.CreateConnectionState(IpcServer server, NamedPipeListenerState listenerState) - => new() - { - Stream = IOHelpers.NewNamedPipeServerStream( - PipeName, - PipeDirection.InOut, - NamedPipeServerStream.MaxAllowedServerInstances, - PipeTransmissionMode.Byte, - PipeOptions.Asynchronous, - GetPipeSecurity) - }; - - async ValueTask INamedPipeListenerConfig.AwaitConnection(NamedPipeListenerState listenerState, NamedPipeServerConnectionState connectionState, CancellationToken ct) - { - await connectionState.Stream.WaitForConnectionAsync(ct); - return connectionState.Stream; - } - - IEnumerable INamedPipeListenerConfig.Validate() - { - if (PipeName is null or "") { yield return "PipeName is required"; } - } - - public override string ToString() => $"ServerPipe={PipeName}"; -} - -internal sealed class NamedPipeServerConnectionState : IDisposable -{ - public required NamedPipeServerStream Stream { get; init; } - - public void Dispose() => Stream.Dispose(); -} - -internal sealed class NamedPipeListenerState : IAsyncDisposable -{ - public ValueTask DisposeAsync() => default; -} diff --git a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeServerTransport.cs b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeServerTransport.cs new file mode 100644 index 00000000..aff79351 --- /dev/null +++ b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeServerTransport.cs @@ -0,0 +1,80 @@ +using Newtonsoft.Json; +using System.IO.Pipes; +using System.Security.Principal; + +namespace UiPath.Ipc.Transport.NamedPipe; + +public sealed class NamedPipeServerTransport : ServerTransport +{ + public required string PipeName { get; init; } + public string ServerName { get; init; } = "."; + [JsonIgnore] + public AccessControlDelegate? AccessControl { get; init; } + + protected internal override IServerState CreateServerState() + => new ServerState { Transport = this }; + + protected override IEnumerable ValidateCore() + { + yield return IsNotNull(PipeName); + yield return IsNotNull(ServerName); + } + + public override string ToString() => $"ServerPipe={PipeName}"; + + private sealed class ServerState : IServerState + { + public required NamedPipeServerTransport Transport { get; init; } + + IServerConnectionSlot IServerState.CreateConnectionSlot() => ServerConnectionState.Create(serverState: this); + + ValueTask IAsyncDisposable.DisposeAsync() => default; + } + + private sealed class ServerConnectionState : IServerConnectionSlot + { + public static ServerConnectionState Create(ServerState serverState) + { + return new() + { + Stream = CreateStream() + }; + + NamedPipeServerStream CreateStream() + => IOHelpers.NewNamedPipeServerStream( + serverState.Transport.PipeName, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Byte, + PipeOptions.Asynchronous, + GetPipeSecurity); + + PipeSecurity? GetPipeSecurity() + { + if (serverState.Transport.AccessControl is not { } setAccessControl) + { + return null; + } + + var pipeSecurity = new PipeSecurity(); + FullControlFor(WellKnownSidType.BuiltinAdministratorsSid); + FullControlFor(WellKnownSidType.LocalSystemSid); + pipeSecurity.AllowCurrentUser(onlyNonAdmin: true); + setAccessControl(pipeSecurity); + return pipeSecurity; + + void FullControlFor(WellKnownSidType sid) => pipeSecurity.Allow(sid, PipeAccessRights.FullControl); + } + } + + public required NamedPipeServerStream Stream { get; init; } + + void IDisposable.Dispose() => Stream.Dispose(); + + async ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken ct) + { + await Stream.WaitForConnectionAsync(ct); + return Stream; + } + } +} diff --git a/src/UiPath.CoreIpc/Transport/Tcp/TcpListener.cs b/src/UiPath.CoreIpc/Transport/Tcp/TcpListener.cs deleted file mode 100644 index fc146f5a..00000000 --- a/src/UiPath.CoreIpc/Transport/Tcp/TcpListener.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Net; -using System.Net.Sockets; - -namespace UiPath.Ipc.Transport.Tcp; - -using ITcpListenerConfig = IListenerConfig; - -public sealed record TcpListener : ServerTransport, ITcpListenerConfig -{ - public required IPEndPoint EndPoint { get; init; } - - TcpListenerState ITcpListenerConfig.CreateListenerState(IpcServer server) - { - var listener = new System.Net.Sockets.TcpListener(EndPoint); - listener.Start(backlog: ConcurrentAccepts); - - return new() { Listener = listener }; - } - - TcpServerConnectionState ITcpListenerConfig.CreateConnectionState(IpcServer server, TcpListenerState listenerState) - => new(); - - async ValueTask ITcpListenerConfig.AwaitConnection(TcpListenerState listenerState, TcpServerConnectionState connectionState, CancellationToken ct) - { - System.Net.Sockets.TcpClient tcpClient; -#if NET461 - using var ctreg = ct.Register(listenerState.Listener.Stop); - tcpClient = await listenerState.Listener.AcceptTcpClientAsync(); -#else - tcpClient = await listenerState.Listener.AcceptTcpClientAsync(ct); -#endif - return tcpClient.GetStream(); - } - - IEnumerable ITcpListenerConfig.Validate() - { - if (EndPoint is null) - { - yield return "EndPoint is required"; - } - } - - public override string ToString() => $"TcpServer={EndPoint}"; -} - -internal sealed class TcpListenerState : IAsyncDisposable -{ - public required System.Net.Sockets.TcpListener Listener { get; init; } - - public ValueTask DisposeAsync() - { - Listener.Stop(); - return default; - } -} - -internal sealed class TcpServerConnectionState : IDisposable -{ - public void Dispose() { } -} - diff --git a/src/UiPath.CoreIpc/Transport/Tcp/TcpServerTransport.cs b/src/UiPath.CoreIpc/Transport/Tcp/TcpServerTransport.cs new file mode 100644 index 00000000..18bb0a5f --- /dev/null +++ b/src/UiPath.CoreIpc/Transport/Tcp/TcpServerTransport.cs @@ -0,0 +1,56 @@ +using System.Net; +using System.Net.Sockets; + +namespace UiPath.Ipc.Transport.Tcp; + +public sealed class TcpServerTransport : ServerTransport +{ + public required IPEndPoint EndPoint { get; init; } + + protected internal override IServerState CreateServerState() + { + var listener = new TcpListener(EndPoint); + listener.Start(backlog: ConcurrentAccepts); + return new ServerState() { TcpListener = listener }; + } + + protected override IEnumerable ValidateCore() + { + yield return IsNotNull(EndPoint); + } + + public override string ToString() => $"TcpServer={EndPoint}"; + + private sealed class ServerState : IServerState + { + public required TcpListener TcpListener { get; init; } + + ValueTask IAsyncDisposable.DisposeAsync() + { + TcpListener.Stop(); + return default; + } + + IServerConnectionSlot IServerState.CreateConnectionSlot() + => new ServerConnectionState { ServerState = this }; + } + + private sealed class ServerConnectionState : IServerConnectionSlot + { + public required ServerState ServerState { get; init; } + + async ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken ct) + { + TcpClient tcpClient; +#if NET461 + using var ctreg = ct.Register(ServerState.TcpListener.Stop); + tcpClient = await ServerState.TcpListener.AcceptTcpClientAsync(); +#else + tcpClient = await ServerState.TcpListener.AcceptTcpClientAsync(ct); +#endif + return tcpClient.GetStream(); + } + + void IDisposable.Dispose() { } + } +} diff --git a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketListener.cs b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketListener.cs deleted file mode 100644 index 0e8b1552..00000000 --- a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketListener.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace UiPath.Ipc.Transport.WebSocket; - -using IWebSocketListenerConfig = IListenerConfig; - -public sealed record WebSocketListener : ServerTransport, IWebSocketListenerConfig -{ - public required Accept Accept { get; init; } - - WebSocketListenerState IWebSocketListenerConfig.CreateListenerState(IpcServer server) - => new(); - - WebSocketServerConnectionState IWebSocketListenerConfig.CreateConnectionState(IpcServer server, WebSocketListenerState listenerState) - => new(); - - async ValueTask IWebSocketListenerConfig.AwaitConnection(WebSocketListenerState listenerState, WebSocketServerConnectionState connectionState, CancellationToken ct) - { - var webSocket = await Accept(ct); - - return new WebSocketStream(webSocket); - } - - IEnumerable IWebSocketListenerConfig.Validate() - { - if (Accept is null) { yield return "Accept is required"; } - } - - public override string ToString() => "WebSocketServer"; -} - -internal sealed class WebSocketListenerState : IAsyncDisposable -{ - public ValueTask DisposeAsync() => default; -} - -internal sealed class WebSocketServerConnectionState : IDisposable -{ - public void Dispose() { } -} diff --git a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketServerTransport.cs b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketServerTransport.cs new file mode 100644 index 00000000..14110555 --- /dev/null +++ b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketServerTransport.cs @@ -0,0 +1,25 @@ +namespace UiPath.Ipc.Transport.WebSocket; + +public sealed class WebSocketServerTransport : ServerTransport, ServerTransport.IServerState, ServerTransport.IServerConnectionSlot +{ + public required Accept Accept { get; init; } + + protected internal override IServerState CreateServerState() => this; + + IServerConnectionSlot IServerState.CreateConnectionSlot() => this; + + async ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken ct) + { + var webSocket = await Accept(ct); + return new WebSocketStream(webSocket); + } + ValueTask IAsyncDisposable.DisposeAsync() => default; + void IDisposable.Dispose() { } + + protected override IEnumerable ValidateCore() + { + yield return IsNotNull(Accept); + } + + public override string ToString() => nameof(WebSocketServerTransport); +} diff --git a/src/UiPath.CoreIpc/Wire/Dtos.cs b/src/UiPath.CoreIpc/Wire/Dtos.cs index a5cb95e9..7dfcba58 100644 --- a/src/UiPath.CoreIpc/Wire/Dtos.cs +++ b/src/UiPath.CoreIpc/Wire/Dtos.cs @@ -38,14 +38,14 @@ internal record Response(string RequestId, string? Data = null, Error? Error = n public static Response Fail(Request request, Exception ex) => new(request.Id, Error: ex.ToError()); public static Response Success(Request request, string data) => new(request.Id, data); public static Response Success(Request request, Stream downloadStream) => new(request.Id) { DownloadStream = downloadStream }; - public TResult Deserialize(ISerializer? serializer) + public TResult Deserialize() { if (Error != null) { throw new RemoteException(Error); } - return (TResult)(DownloadStream ?? serializer.OrDefault().Deserialize(Data ?? "", typeof(TResult)))!; + return (TResult)(DownloadStream ?? IpcJsonSerializer.Instance.Deserialize(Data ?? "", typeof(TResult)))!; } } [Serializable] diff --git a/src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs b/src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs index a9e6d712..feb7a455 100644 --- a/src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs +++ b/src/UiPath.CoreIpc/Wire/IpcJsonSerializer.cs @@ -5,15 +5,7 @@ namespace UiPath.Ipc; -internal interface ISerializer -{ - ValueTask DeserializeAsync(Stream json, ILogger? logger); - void Serialize(object? obj, Stream stream); - string Serialize(object? obj); - object? Deserialize(string json, Type type); -} - -internal class IpcJsonSerializer : ISerializer, IArrayPool +internal class IpcJsonSerializer : IArrayPool { public static readonly IpcJsonSerializer Instance = new(); diff --git a/src/UiPath.Ipc.Tests/ComputingTests.cs b/src/UiPath.Ipc.Tests/ComputingTests.cs index 24fdacb1..e6858e6b 100644 --- a/src/UiPath.Ipc.Tests/ComputingTests.cs +++ b/src/UiPath.Ipc.Tests/ComputingTests.cs @@ -1,5 +1,4 @@ -using AutoFixture; -using Newtonsoft.Json; +using Newtonsoft.Json; using Nito.AsyncEx; using Nito.Disposables; using NSubstitute; @@ -9,7 +8,6 @@ using UiPath.Ipc.Transport.NamedPipe; using UiPath.Ipc.Transport.Tcp; using UiPath.Ipc.Transport.WebSocket; -using Xunit; using Xunit.Abstractions; namespace UiPath.Ipc.Tests; @@ -290,18 +288,18 @@ public readonly record struct ExternalServerParams(ServerKind Kind, string? Pipe { case ServerKind.NamedPipes: { - listenerConfig = new NamedPipeListener() { PipeName = PipeName! }; + listenerConfig = new NamedPipeServerTransport() { PipeName = PipeName! }; return null; } case ServerKind.Tcp: { - listenerConfig = new TcpListener() { EndPoint = new(System.Net.IPAddress.Loopback, Port) }; + listenerConfig = new TcpServerTransport() { EndPoint = new(System.Net.IPAddress.Loopback, Port) }; return null; } case ServerKind.WebSockets: { var context = new WebSocketContext(Port); - listenerConfig = new WebSocketListener { Accept = context.Accept }; + listenerConfig = new WebSocketServerTransport { Accept = context.Accept }; return context; } default: @@ -311,7 +309,7 @@ public readonly record struct ExternalServerParams(ServerKind Kind, string? Pipe public ClientTransport CreateClientTransport() => Kind switch { - ServerKind.NamedPipes => new NamedPipeTransport() { PipeName = PipeName! }, + ServerKind.NamedPipes => new NamedPipeClientTransport() { PipeName = PipeName! }, ServerKind.Tcp => new TcpTransport() { EndPoint = new(System.Net.IPAddress.Loopback, Port) }, ServerKind.WebSockets => new WebSocketTransport() { Uri = new($"ws://localhost:{Port}") }, _ => throw new NotSupportedException($"Kind not supported. Kind was {Kind}") diff --git a/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs index 0a6c67af..067b5ba7 100644 --- a/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs @@ -9,11 +9,11 @@ public sealed class ComputingTestsOverNamedPipes : ComputingTests public ComputingTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected override async Task CreateListener() => new NamedPipeListener + protected override async Task CreateListener() => new NamedPipeServerTransport { PipeName = PipeName }; - protected override ClientTransport CreateClientTransport() => new NamedPipeTransport + protected override ClientTransport CreateClientTransport() => new NamedPipeClientTransport { PipeName = PipeName, AllowImpersonation = true, @@ -22,8 +22,8 @@ public ComputingTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outpu public override IAsyncDisposable? RandomTransportPair(out ServerTransport listener, out ClientTransport transport) { var pipeName = $"{Guid.NewGuid():N}"; - listener = new NamedPipeListener { PipeName = pipeName }; - transport = new NamedPipeTransport { PipeName = pipeName }; + listener = new NamedPipeServerTransport { PipeName = pipeName }; + transport = new NamedPipeClientTransport { PipeName = pipeName }; return null; } diff --git a/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs b/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs index 665dbab2..71fa5d83 100644 --- a/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs +++ b/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs @@ -27,7 +27,7 @@ private static IpcServer CreateServer(string pipeName) => new IpcServer { Transport = [ - new NamedPipeListener + new NamedPipeServerTransport { PipeName = pipeName, } @@ -45,7 +45,7 @@ private static IpcServer CreateServer(string pipeName) private static IpcClient CreateClient(string pipeName) => new() { - Transport = new NamedPipeTransport { PipeName = pipeName }, + Transport = new NamedPipeClientTransport { PipeName = pipeName }, Config = new() }; diff --git a/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs index 22ef42cb..fed8d637 100644 --- a/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs @@ -12,11 +12,11 @@ public sealed class RobotTestsOverNamedPipes : RobotTests public RobotTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected override async Task CreateListener() => new NamedPipeListener + protected override async Task CreateListener() => new NamedPipeServerTransport { PipeName = PipeName }; - protected override ClientTransport CreateClientTransport() => new NamedPipeTransport + protected override ClientTransport CreateClientTransport() => new NamedPipeClientTransport { PipeName = PipeName, AllowImpersonation = true, @@ -62,7 +62,7 @@ public static void OnConnectingToUserService() public TContract CreateUserServiceProxy(string pipeName) where TContract : class => RobotIpcHelpers.CreateProxy( - listener: new NamedPipeListener() + listener: new NamedPipeServerTransport() { PipeName = pipeName, AccessControl = security => security.AllowCurrentUser(), @@ -82,7 +82,7 @@ internal static partial class RobotIpcHelpers private static readonly ConcurrentDictionary PipeClients = new(); public static TContract CreateProxy( - NamedPipeListener listener, + NamedPipeServerTransport listener, EndpointCollection? callbacks = null, IServiceProvider? provider = null, Action? beforeConnect = null, @@ -153,7 +153,7 @@ private static ClientAndParams CreateClient(CreateProxyRequest request) BeforeCall = request.Params.BeforeCall, Scheduler = request.Params.Scheduler, }, - Transport = new NamedPipeTransport + Transport = new NamedPipeClientTransport { PipeName = request.ActualKey.Name, AllowImpersonation = request.ActualKey.AllowImpersonation, diff --git a/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs b/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs index afc79a1d..01d4ef79 100644 --- a/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs +++ b/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs @@ -48,7 +48,7 @@ private static IpcServer CreateServer(string pipeName) => new IpcServer { Transport = [ - new NamedPipeListener + new NamedPipeServerTransport { PipeName = pipeName, } @@ -66,7 +66,7 @@ private static IpcServer CreateServer(string pipeName) private static IpcClient CreateClient(string pipeName) => new() { - Transport = new NamedPipeTransport { PipeName = pipeName }, + Transport = new NamedPipeClientTransport { PipeName = pipeName }, Config = new() }; diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs index 5d173821..d0430161 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs @@ -9,11 +9,11 @@ public sealed class SystemTestsOverNamedPipes : SystemTests public SystemTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected sealed override async Task CreateListener() => new NamedPipeListener + protected sealed override async Task CreateListener() => new NamedPipeServerTransport { PipeName = PipeName }; - protected sealed override ClientTransport CreateClientTransport() => new NamedPipeTransport() + protected sealed override ClientTransport CreateClientTransport() => new NamedPipeClientTransport() { PipeName = PipeName, AllowImpersonation = true, diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs b/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs index 97432053..265a9134 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs @@ -11,7 +11,7 @@ public sealed class SystemTestsOverTcp : SystemTests public SystemTestsOverTcp(ITestOutputHelper outputHelper) : base(outputHelper) { } protected sealed override async Task CreateListener() - => new TcpListener + => new TcpServerTransport { EndPoint = _endPoint, }; diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs b/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs index 235f0518..b8795dd2 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs @@ -17,7 +17,7 @@ protected override async Task DisposeAsync() protected override async Task CreateListener() { - var listener = new WebSocketListener + var listener = new WebSocketServerTransport { Accept = _webSocketContext.Accept, ConcurrentAccepts = 1, From 6da3415d5a5a51972a7b133c886ac9fa437bd5e3 Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Mon, 25 Nov 2024 13:28:04 +0100 Subject: [PATCH 03/13] taking shape --- src/CoreIpc.sln | 8 +- src/Playground/Program.cs | 43 ++- ...ath.CoreIpc.Extensions.Abstractions.csproj | 17 ++ src/UiPath.CoreIpc.Http/BidiHttpListener.cs | 285 ------------------ .../BidiHttpServerTransport.cs | 278 +++++++++++++++++ .../UiPath.CoreIpc.Http.csproj | 2 +- src/UiPath.CoreIpc/Client/ServiceClient.cs | 32 +- src/UiPath.CoreIpc/Config/ClientConfig.cs | 43 +-- ...erviceClientConfig.cs => IClientConfig.cs} | 6 +- src/UiPath.CoreIpc/Config/IClientState.cs | 9 + src/UiPath.CoreIpc/Config/IpcClient.cs | 50 ++- src/UiPath.CoreIpc/Config/IpcServer.cs | 26 +- src/UiPath.CoreIpc/Config/Peer.cs | 10 +- src/UiPath.CoreIpc/Config/ServerTransport.cs | 5 +- src/UiPath.CoreIpc/Helpers/Router.cs | 2 +- src/UiPath.CoreIpc/Server/EndpointSettings.cs | 2 +- src/UiPath.CoreIpc/Server/ServerConnection.cs | 12 +- ...{TcpTransport.cs => TcpClientTransport.cs} | 4 +- ...ansport.cs => WebSocketClientTransport.cs} | 4 +- src/UiPath.CoreIpc/UiPath.CoreIpc.csproj | 8 + src/UiPath.Ipc.Tests/ComputingTests.cs | 51 +--- .../ComputingTestsOverNamedPipes.cs | 2 +- .../Config/OverrideConfigAttribute.cs | 4 +- src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs | 28 +- src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs | 13 +- src/UiPath.Ipc.Tests/Program.cs | 4 +- src/UiPath.Ipc.Tests/RobotTests.cs | 23 +- .../RobotTestsOverNamedPipes.cs | 43 +-- src/UiPath.Ipc.Tests/SpyTestBase.cs | 20 ++ src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs | 10 +- src/UiPath.Ipc.Tests/SystemTests.cs | 38 +-- .../SystemTestsOverNamedPipes.cs | 2 +- src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs | 4 +- .../SystemTestsOverWebSockets.cs | 4 +- src/UiPath.Ipc.Tests/TestBase.cs | 104 ++++--- 35 files changed, 594 insertions(+), 602 deletions(-) create mode 100644 src/UiPath.CoreIpc.Extensions.Abstractions/UiPath.CoreIpc.Extensions.Abstractions.csproj delete mode 100644 src/UiPath.CoreIpc.Http/BidiHttpListener.cs create mode 100644 src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs rename src/UiPath.CoreIpc/Config/{IServiceClientConfig.cs => IClientConfig.cs} (58%) create mode 100644 src/UiPath.CoreIpc/Config/IClientState.cs rename src/UiPath.CoreIpc/Transport/Tcp/{TcpTransport.cs => TcpClientTransport.cs} (89%) rename src/UiPath.CoreIpc/Transport/WebSocket/{WebSocketTransport.cs => WebSocketClientTransport.cs} (84%) create mode 100644 src/UiPath.Ipc.Tests/SpyTestBase.cs diff --git a/src/CoreIpc.sln b/src/CoreIpc.sln index 12d4a07d..74779ed7 100644 --- a/src/CoreIpc.sln +++ b/src/CoreIpc.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# 17 +# Visual Studio Version 17 VisualStudioVersion = 17.0.31919.166 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiPath.CoreIpc", "UiPath.CoreIpc\UiPath.CoreIpc.csproj", "{58200319-1F71-4E22-894D-7E69E0CD0B57}" @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiPath.CoreIpc.Http", "UiPa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiPath.Ipc.Tests", "UiPath.Ipc.Tests\UiPath.Ipc.Tests.csproj", "{E238E183-92CF-48A6-890F-C422853D6656}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UiPath.CoreIpc.Extensions.Abstractions", "UiPath.CoreIpc.Extensions.Abstractions\UiPath.CoreIpc.Extensions.Abstractions.csproj", "{F519AE2B-88A6-482E-A6E2-B525F71F566D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {E238E183-92CF-48A6-890F-C422853D6656}.Debug|Any CPU.Build.0 = Debug|Any CPU {E238E183-92CF-48A6-890F-C422853D6656}.Release|Any CPU.ActiveCfg = Release|Any CPU {E238E183-92CF-48A6-890F-C422853D6656}.Release|Any CPU.Build.0 = Release|Any CPU + {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Playground/Program.cs b/src/Playground/Program.cs index 613e8d5a..e373bd18 100644 --- a/src/Playground/Program.cs +++ b/src/Playground/Program.cs @@ -44,7 +44,7 @@ private static async Task Main(string[] args) typeof(Contracts.IServerOperations), // DEVINE new EndpointSettings(typeof(Contracts.IServerOperations)) // ASTALALT { - BeforeCall = async (callInfo, _) => + BeforeIncommingCall = async (callInfo, _) => { Console.WriteLine($"Server: {callInfo.Method.Name}"); } @@ -76,36 +76,30 @@ private static async Task Main(string[] args) var c1 = new IpcClient() { - Config = new() + Callbacks = new() { - Callbacks = new() - { - typeof(Contracts.IClientOperations), - { typeof(Contracts.IClientOperations2), new Impl.Client2() }, - }, - ServiceProvider = clientSP, - Scheduler = clientScheduler, + typeof(Contracts.IClientOperations), + { typeof(Contracts.IClientOperations2), new Impl.Client2() }, }, + ServiceProvider = clientSP, + Scheduler = clientScheduler, Transport = new NamedPipeClientTransport() { PipeName = Contracts.PipeName, ServerName = ".", AllowImpersonation = false, - }, + } }; var c2 = new IpcClient() { - Config = new() + ServiceProvider = clientSP, + Callbacks = new() { - ServiceProvider = clientSP, - Callbacks = new() - { - typeof(Contracts.IClientOperations), - { typeof(Contracts.IClientOperations2), new Impl.Client2() }, - }, - Scheduler = clientScheduler, + typeof(Contracts.IClientOperations), + { typeof(Contracts.IClientOperations2), new Impl.Client2() }, }, + Scheduler = clientScheduler, Transport = new NamedPipeClientTransport() { PipeName = Contracts.PipeName, @@ -116,16 +110,13 @@ private static async Task Main(string[] args) var proxy1 = new IpcClient() { - Config = new() + ServiceProvider = clientSP, + Callbacks = new() { - ServiceProvider = clientSP, - Callbacks = new() - { - typeof(Contracts.IClientOperations), - { typeof(Contracts.IClientOperations2), new Impl.Client2() }, - }, - Scheduler = clientScheduler, + typeof(Contracts.IClientOperations), + { typeof(Contracts.IClientOperations2), new Impl.Client2() }, }, + Scheduler = clientScheduler, Transport = new NamedPipeClientTransport() { PipeName = Contracts.PipeName, diff --git a/src/UiPath.CoreIpc.Extensions.Abstractions/UiPath.CoreIpc.Extensions.Abstractions.csproj b/src/UiPath.CoreIpc.Extensions.Abstractions/UiPath.CoreIpc.Extensions.Abstractions.csproj new file mode 100644 index 00000000..2186c679 --- /dev/null +++ b/src/UiPath.CoreIpc.Extensions.Abstractions/UiPath.CoreIpc.Extensions.Abstractions.csproj @@ -0,0 +1,17 @@ + + + + net6.0;net461;net6.0-windows + enable + enable + preview + true + enable + true + + + + + + + diff --git a/src/UiPath.CoreIpc.Http/BidiHttpListener.cs b/src/UiPath.CoreIpc.Http/BidiHttpListener.cs deleted file mode 100644 index 06076a21..00000000 --- a/src/UiPath.CoreIpc.Http/BidiHttpListener.cs +++ /dev/null @@ -1,285 +0,0 @@ -using Nito.AsyncEx; -using System.Buffers; -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using System.IO.Pipelines; -using System.Net; -using System.Net.Http; -using System.Threading.Channels; - -namespace UiPath.Ipc.Http; - -using static Constants; -using IBidiHttpListenerConfig = IListenerConfig; - -public sealed partial record BidiHttpListener : ServerTransport, IBidiHttpListenerConfig -{ - public required Uri Uri { get; init; } - - BidiHttpListenerState IBidiHttpListenerConfig.CreateListenerState(IpcServer server) - => new(server, this); - - BidiHttpServerConnectionState IBidiHttpListenerConfig.CreateConnectionState(IpcServer server, BidiHttpListenerState listenerState) - => new(server, listenerState); - - async ValueTask IBidiHttpListenerConfig.AwaitConnection(BidiHttpListenerState listenerState, BidiHttpServerConnectionState connectionState, CancellationToken ct) - { - await connectionState.WaitForConnection(ct); - return connectionState; - } - - public IEnumerable Validate() - { - throw new NotImplementedException(); - } -} - -internal sealed class BidiHttpListenerState : IAsyncDisposable -{ - private readonly IpcServer _ipcServer; - private readonly CancellationTokenSource _cts = new(); - private readonly HttpListener _httpListener; - private readonly Task _processing; - private readonly Lazy _disposing; - - private readonly ConcurrentDictionary> _connections = new(); - private readonly Channel<(Guid connectionId, Uri reverseUri)> _newConnections = Channel.CreateUnbounded<(Guid connectionId, Uri reverseUri)>(); - - public ChannelReader<(Guid connectionId, Uri reverseUri)> NewConnections => _newConnections.Reader; - public ChannelReader GetConnectionChannel(Guid connectionId) => _connections[connectionId]; - - public BidiHttpListenerState(IpcServer ipcServer, BidiHttpListener listener) - { - _ipcServer = ipcServer; - _httpListener = new HttpListener() - { - Prefixes = - { - listener.Uri.ToString() - } - }; - _processing = ProcessContexts(); - _disposing = new(DisposeCore); - } - - public ValueTask DisposeAsync() => new(_disposing.Value); - - private async Task DisposeCore() - { - _cts.Cancel(); - try - { - await _processing; - } - catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token) - { - } - - foreach (var pair in _connections) - { - pair.Value.Writer.Complete(); - } - _cts.Dispose(); - } - - private async Task ProcessContexts() - { - await foreach (var (context, connectionId, reverseUri) in AwaitContexts()) - { - var connectionChannel = _connections.GetOrAdd(connectionId, _ => - { - _newConnections.Writer.TryWrite((connectionId, reverseUri)); - return Channel.CreateUnbounded(); - }); - - await connectionChannel.Writer.WriteAsync(context, _cts.Token); - } - - async IAsyncEnumerable<(HttpListenerContext context, Guid connectionId, Uri reverseUri)> AwaitContexts() - { - while (!_cts.Token.IsCancellationRequested) - { - var context = await _httpListener.GetContextAsync(); - - if (!TryAcceptContext(context, out var connectionId, out var reverseUri)) - { - context.Response.StatusCode = 400; - context.Response.Close(); - continue; - } - - yield return (context, connectionId, reverseUri); - } - } - - bool TryAcceptContext(HttpListenerContext context, out Guid connectionId, [NotNullWhen(returnValue: true)] out Uri? reverseUri) - { - if (!Guid.TryParse(context.Request.Headers[ConnectionIdHeader], out connectionId) || - !Uri.TryCreate(context.Request.Headers[ReverseUriHeader], UriKind.Absolute, out reverseUri)) - { - connectionId = Guid.Empty; - reverseUri = null; - return false; - } - - return true; - } - } -} - -internal sealed class BidiHttpServerConnectionState : Stream, IAsyncDisposable -{ - private readonly Pipe _pipe = new(); - - private readonly IpcServer _server; - private readonly BidiHttpListenerState _listenerState; - - private readonly CancellationTokenSource _cts = new(); - private readonly AsyncLock _lock = new(); - private (Guid connectionId, Uri reverseUri)? _connection = null; - private HttpClient? _client; - private Task? _processing = null; - private readonly Lazy _disposing; - - public BidiHttpServerConnectionState(IpcServer server, BidiHttpListenerState listenerState) - { - _server = server; - _listenerState = listenerState; - _disposing = new(DisposeCore); - } - - public -#if !NET461 - override -#endif - ValueTask DisposeAsync() => new(_disposing.Value); - - private async Task DisposeCore() - { - _cts.Cancel(); - - _client?.Dispose(); - - try - { - await (_processing ?? Task.CompletedTask); - } - catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token) - { - // ignored - } - - _cts.Dispose(); - } - - public async Task WaitForConnection(CancellationToken ct) - { - using (await _lock.LockAsync(ct)) - { - if (_connection is not null) - { - throw new InvalidOperationException(); - } - - _connection = await _listenerState.NewConnections.ReadAsync(ct); - - _client = new() - { - BaseAddress = _connection.Value.reverseUri, - DefaultRequestHeaders = - { - { ConnectionIdHeader, _connection.Value.connectionId.ToString() } - } - }; - - _processing = ProcessContexts(_cts.Token); - } - } - - private async Task ProcessContexts(CancellationToken ct) - { - var reader = _listenerState.GetConnectionChannel(_connection!.Value.connectionId); - - while (await reader.WaitToReadAsync(ct)) - { - if (!reader.TryRead(out var context)) - { - continue; - } - await ProcessContext(context); - } - - async Task ProcessContext(HttpListenerContext context) - { - try - { - while (true) - { - var memory = _pipe.Writer.GetMemory(); - var cbRead = await context.Request.InputStream.ReadAsync(memory, ct); - if (cbRead is 0) - { - break; - } - _pipe.Writer.Advance(cbRead); - var flushResult = await _pipe.Writer.FlushAsync(ct); - if (flushResult.IsCompleted) - { - break; - } - } - } - finally - { - context.Response.StatusCode = 200; - context.Response.Close(); - } - } - } - - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => true; - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) - { - var memory = new Memory(buffer, offset, count); - var readResult = await _pipe.Reader.ReadAsync(ct); - - var take = (int)Math.Min(readResult.Buffer.Length, memory.Length); - - readResult.Buffer.Slice(start: 0, length: take).CopyTo(memory.Span); - _pipe.Reader.AdvanceTo(readResult.Buffer.GetPosition(take)); - - return take; - } - - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct) - { - var memory = new ReadOnlyMemory(buffer, offset, count); - if (_client is null) - { - throw new InvalidOperationException(); - } - - HttpContent content = -#if NET461 - new ByteArrayContent(memory.ToArray()); -#else - new ReadOnlyMemoryContent(memory); -#endif - - await _client.PostAsync(requestUri: "", content, ct); - } - - public override Task FlushAsync(CancellationToken cancellationToken) - => Task.CompletedTask; - - public override void Flush() => throw new NotImplementedException(); - public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); - public override void SetLength(long value) => throw new NotImplementedException(); - public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); - public override long Length => throw new NotImplementedException(); - public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } -} diff --git a/src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs b/src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs new file mode 100644 index 00000000..4e43ce58 --- /dev/null +++ b/src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs @@ -0,0 +1,278 @@ +using Nito.AsyncEx; +using System.Buffers; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.IO.Pipelines; +using System.Net; +using System.Net.Http; +using System.Threading.Channels; + +namespace UiPath.Ipc.Http; + +using static Constants; + +public sealed partial class BidiHttpServerTransport : ServerTransport +{ + public required Uri Uri { get; init; } + + protected override IServerState CreateServerState() + => new BidiHttpServerState(this); + + protected override IEnumerable ValidateCore() => []; + + private sealed class BidiHttpServerState : IServerState + { + private readonly CancellationTokenSource _cts = new(); + private readonly HttpListener _httpListener; + private readonly Task _processing; + private readonly Lazy _disposing; + + private readonly ConcurrentDictionary> _connections = new(); + private readonly Channel<(Guid connectionId, Uri reverseUri)> _newConnections = Channel.CreateUnbounded<(Guid connectionId, Uri reverseUri)>(); + + public ChannelReader<(Guid connectionId, Uri reverseUri)> NewConnections => _newConnections.Reader; + public ChannelReader GetConnectionChannel(Guid connectionId) => _connections[connectionId]; + + public BidiHttpServerState(BidiHttpServerTransport transport) + { + _httpListener = new HttpListener() + { + Prefixes = + { + transport.Uri.ToString() + } + }; + _processing = ProcessContexts(); + _disposing = new(DisposeCore); + } + + public ValueTask DisposeAsync() => new(_disposing.Value); + + private async Task DisposeCore() + { + _cts.Cancel(); + try + { + await _processing; + } + catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token) + { + } + + foreach (var pair in _connections) + { + pair.Value.Writer.Complete(); + } + _cts.Dispose(); + } + + private async Task ProcessContexts() + { + await foreach (var (context, connectionId, reverseUri) in AwaitContexts()) + { + var connectionChannel = _connections.GetOrAdd(connectionId, _ => + { + _newConnections.Writer.TryWrite((connectionId, reverseUri)); + return Channel.CreateUnbounded(); + }); + + await connectionChannel.Writer.WriteAsync(context, _cts.Token); + } + + async IAsyncEnumerable<(HttpListenerContext context, Guid connectionId, Uri reverseUri)> AwaitContexts() + { + while (!_cts.Token.IsCancellationRequested) + { + var context = await _httpListener.GetContextAsync(); + + if (!TryAcceptContext(context, out var connectionId, out var reverseUri)) + { + context.Response.StatusCode = 400; + context.Response.Close(); + continue; + } + + yield return (context, connectionId, reverseUri); + } + } + + bool TryAcceptContext(HttpListenerContext context, out Guid connectionId, [NotNullWhen(returnValue: true)] out Uri? reverseUri) + { + if (!Guid.TryParse(context.Request.Headers[ConnectionIdHeader], out connectionId) || + !Uri.TryCreate(context.Request.Headers[ReverseUriHeader], UriKind.Absolute, out reverseUri)) + { + connectionId = Guid.Empty; + reverseUri = null; + return false; + } + + return true; + } + } + + IServerConnectionSlot IServerState.CreateConnectionSlot() => new BidiHttpServerConnectionSlot(this); + } + + private sealed class BidiHttpServerConnectionSlot : Stream, IServerConnectionSlot, IAsyncDisposable + { + private readonly Pipe _pipe = new(); + + private readonly BidiHttpServerState _listenerState; + + private readonly CancellationTokenSource _cts = new(); + private readonly AsyncLock _lock = new(); + private (Guid connectionId, Uri reverseUri)? _connection = null; + private HttpClient? _client; + private Task? _processing = null; + private readonly Lazy _disposing; + + public BidiHttpServerConnectionSlot(BidiHttpServerState serverState) + { + _listenerState = serverState; + _disposing = new(DisposeCore); + } + + public +#if !NET461 + override +#endif + ValueTask DisposeAsync() => new(_disposing.Value); + + private async Task DisposeCore() + { + _cts.Cancel(); + + _client?.Dispose(); + + try + { + await (_processing ?? Task.CompletedTask); + } + catch (OperationCanceledException ex) when (ex.CancellationToken == _cts.Token) + { + // ignored + } + + _cts.Dispose(); + } + + public async Task WaitForConnection(CancellationToken ct) + { + using (await _lock.LockAsync(ct)) + { + if (_connection is not null) + { + throw new InvalidOperationException(); + } + + _connection = await _listenerState.NewConnections.ReadAsync(ct); + + _client = new() + { + BaseAddress = _connection.Value.reverseUri, + DefaultRequestHeaders = + { + { ConnectionIdHeader, _connection.Value.connectionId.ToString() } + } + }; + + _processing = ProcessContexts(_cts.Token); + } + } + + private async Task ProcessContexts(CancellationToken ct) + { + var reader = _listenerState.GetConnectionChannel(_connection!.Value.connectionId); + + while (await reader.WaitToReadAsync(ct)) + { + if (!reader.TryRead(out var context)) + { + continue; + } + await ProcessContext(context); + } + + async Task ProcessContext(HttpListenerContext context) + { + try + { + while (true) + { + var memory = _pipe.Writer.GetMemory(); + var cbRead = await context.Request.InputStream.ReadAsync(memory, ct); + if (cbRead is 0) + { + break; + } + _pipe.Writer.Advance(cbRead); + var flushResult = await _pipe.Writer.FlushAsync(ct); + if (flushResult.IsCompleted) + { + break; + } + } + } + finally + { + context.Response.StatusCode = 200; + context.Response.Close(); + } + } + } + + ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken ct) + { + throw new NotImplementedException(); + } + + + + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => true; + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) + { + var memory = new Memory(buffer, offset, count); + var readResult = await _pipe.Reader.ReadAsync(ct); + + var take = (int)Math.Min(readResult.Buffer.Length, memory.Length); + + readResult.Buffer.Slice(start: 0, length: take).CopyTo(memory.Span); + _pipe.Reader.AdvanceTo(readResult.Buffer.GetPosition(take)); + + return take; + } + + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct) + { + var memory = new ReadOnlyMemory(buffer, offset, count); + if (_client is null) + { + throw new InvalidOperationException(); + } + + HttpContent content = +#if NET461 + new ByteArrayContent(memory.ToArray()); +#else + new ReadOnlyMemoryContent(memory); +#endif + + await _client.PostAsync(requestUri: "", content, ct); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + => Task.CompletedTask; + + public override void Flush() => throw new NotImplementedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); + public override void SetLength(long value) => throw new NotImplementedException(); + public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); + + public override long Length => throw new NotImplementedException(); + public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + } +} diff --git a/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj b/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj index 524ffbc1..83f0f21e 100644 --- a/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj +++ b/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/UiPath.CoreIpc/Client/ServiceClient.cs b/src/UiPath.CoreIpc/Client/ServiceClient.cs index 4c0edd2f..95ef34c3 100644 --- a/src/UiPath.CoreIpc/Client/ServiceClient.cs +++ b/src/UiPath.CoreIpc/Client/ServiceClient.cs @@ -11,7 +11,7 @@ private static IpcProxy CreateProxy(ServiceClient serviceClient) where T : cl return proxy; } - protected abstract IServiceClientConfig Config { get; } + protected abstract IClientConfig Config { get; } public abstract Stream? Network { get; } public event EventHandler? ConnectionClosed; @@ -66,11 +66,7 @@ async Task Invoke() var (connection, newConnection) = await EnsureConnection(ct); - if (Config.BeforeCall is not null) - { - var callInfo = new CallInfo(newConnection, method, args); - await Config.BeforeCall(callInfo, ct); - } + await (Config.BeforeOutgoingCall?.Invoke(new CallInfo(newConnection, method, args), ct) ?? Task.CompletedTask); var requestId = connection.NewRequestId(); var request = new Request(_interfaceType.Name, requestId, methodName, serializedArguments, messageTimeout.TotalSeconds) @@ -78,18 +74,18 @@ async Task Invoke() UploadStream = uploadStream }; - Config.Logger?.ServiceClient_Calling(methodName, requestId, Config.DebugName); + Config.Logger?.ServiceClient_Calling(methodName, requestId, Config.GetComputedDebugName()); Response response; try { response = await connection.RemoteCall(request, ct); // returns user errors instead of throwing them (could throw for system bugs) - Config.Logger?.ServiceClient_CalledSuccessfully(request.MethodName, requestId, Config.DebugName); + Config.Logger?.ServiceClient_CalledSuccessfully(request.MethodName, requestId, Config.GetComputedDebugName()); } catch (Exception ex) { - Config.Logger?.ServiceClient_FailedToCall(request.MethodName, requestId, Config.DebugName, ex); + Config.Logger?.ServiceClient_FailedToCall(request.MethodName, requestId, Config.GetComputedDebugName(), ex); throw; } @@ -133,7 +129,7 @@ string[] SerializeArguments() public abstract void Dispose(); - public override string ToString() => Config.DebugName; + public override string ToString() => Config.GetComputedDebugName(); #region Generic adapter cache private static readonly MethodInfo GenericDefOf_Invoke = ((Func>)Invoke).Method.GetGenericMethodDefinition(); @@ -227,9 +223,9 @@ public override async ValueTask CloseConnection() var network = await Connect(ct); - LatestConnection = new Connection(network, Config.DebugName, Config.Logger); - var router = new Router(_client.Config.CreateCallbackRouterConfig(), _client.Config.ServiceProvider); - _latestServer = new Server(router, _client.Config.RequestTimeout, LatestConnection); + LatestConnection = new Connection(network, Config.GetComputedDebugName(), Config.Logger); + var router = new Router(_client.CreateCallbackRouterConfig(), _client.ServiceProvider); + _latestServer = new Server(router, _client.RequestTimeout, LatestConnection); _ = Pal(); return (LatestConnection, newlyConnected: true); @@ -242,7 +238,7 @@ async Task Pal() } catch (Exception ex) { - Config.Logger.LogException(ex, Config.DebugName); + Config.Logger.LogException(ex, Config.GetComputedDebugName()); } } } @@ -260,17 +256,17 @@ private async Task Connect(CancellationToken ct) return network; } - protected override IServiceClientConfig Config => _client.Config; + protected override IClientConfig Config => _client; } internal sealed class ServiceClientForCallback : ServiceClient where TInterface : class { private readonly Connection _connection; - private readonly IServiceClientConfig _config; + private readonly IClientConfig _config; public override Stream? Network => _connection.Network; - public ServiceClientForCallback(Connection connection, IServiceClientConfig config) : base(typeof(TInterface)) + public ServiceClientForCallback(Connection connection, IClientConfig config) : base(typeof(TInterface)) { _connection = connection; _config = config; @@ -286,5 +282,5 @@ public override void Dispose() protected override Task<(Connection connection, bool newlyConnected)> EnsureConnection(CancellationToken ct) => Task.FromResult((_connection, newlyConnected: false)); - protected override IServiceClientConfig Config => _config; + protected override IClientConfig Config => _config; } diff --git a/src/UiPath.CoreIpc/Config/ClientConfig.cs b/src/UiPath.CoreIpc/Config/ClientConfig.cs index c6ac4bdb..6c05c192 100644 --- a/src/UiPath.CoreIpc/Config/ClientConfig.cs +++ b/src/UiPath.CoreIpc/Config/ClientConfig.cs @@ -1,18 +1,9 @@ -using System.ComponentModel; - -namespace UiPath.Ipc; +namespace UiPath.Ipc; public sealed class ClientConfig : Peer, IServiceClientConfig { - public EndpointCollection? Callbacks { get; init; } - - public ILogger? Logger { get; init; } - public BeforeConnectHandler? BeforeConnect { get; init; } public BeforeCallHandler? BeforeCall { get; init; } - [EditorBrowsable(EditorBrowsableState.Never)] - public string DebugName { get; set; } = null!; - internal void Validate() { var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false; @@ -22,36 +13,4 @@ internal void Validate() throw new InvalidOperationException("ServiceProvider is required when you register injectable callbacks. Consider registering a callback instance."); } } - - internal ILogger? GetLogger(string name) - { - if (Logger is not null) - { - return Logger; - } - - if (ServiceProvider?.GetService() is not { } loggerFactory) - { - return null; - } - - return loggerFactory.CreateLogger(name); - } - - internal override RouterConfig CreateCallbackRouterConfig() - => RouterConfig.From( - Callbacks.OrDefault(), - endpoint => endpoint with - { - BeforeCall = null, // callbacks don't support BeforeCall - Scheduler = endpoint.Scheduler ?? Scheduler - }); } - -public interface IClientState : IDisposable -{ - Stream? Network { get; } - - bool IsConnected(); - ValueTask Connect(IpcClient client, CancellationToken ct); -} \ No newline at end of file diff --git a/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs b/src/UiPath.CoreIpc/Config/IClientConfig.cs similarity index 58% rename from src/UiPath.CoreIpc/Config/IServiceClientConfig.cs rename to src/UiPath.CoreIpc/Config/IClientConfig.cs index 19ed49fd..9e4741ab 100644 --- a/src/UiPath.CoreIpc/Config/IServiceClientConfig.cs +++ b/src/UiPath.CoreIpc/Config/IClientConfig.cs @@ -1,11 +1,11 @@ namespace UiPath.Ipc; // Maybe decommission -internal interface IServiceClientConfig +internal interface IClientConfig { TimeSpan RequestTimeout { get; } BeforeConnectHandler? BeforeConnect { get; } - BeforeCallHandler? BeforeCall { get; } + BeforeCallHandler? BeforeOutgoingCall { get; } ILogger? Logger { get; } - string DebugName { get; } + string GetComputedDebugName(); } diff --git a/src/UiPath.CoreIpc/Config/IClientState.cs b/src/UiPath.CoreIpc/Config/IClientState.cs new file mode 100644 index 00000000..74ff640c --- /dev/null +++ b/src/UiPath.CoreIpc/Config/IClientState.cs @@ -0,0 +1,9 @@ +namespace UiPath.Ipc; + +public interface IClientState : IDisposable +{ + Stream? Network { get; } + + bool IsConnected(); + ValueTask Connect(IpcClient client, CancellationToken ct); +} diff --git a/src/UiPath.CoreIpc/Config/IpcClient.cs b/src/UiPath.CoreIpc/Config/IpcClient.cs index 24104b16..7bed1c05 100644 --- a/src/UiPath.CoreIpc/Config/IpcClient.cs +++ b/src/UiPath.CoreIpc/Config/IpcClient.cs @@ -1,10 +1,22 @@ -namespace UiPath.Ipc; +using System.ComponentModel; -public sealed class IpcClient : Peer +namespace UiPath.Ipc; + +public sealed class IpcClient : Peer, IClientConfig { - public required ClientConfig Config { get; init; } + public EndpointCollection? Callbacks { get; set; } + + public ILogger? Logger { get; init; } + public BeforeConnectHandler? BeforeConnect { get; set; } + public BeforeCallHandler? BeforeOutgoingCall { get; set; } + + [EditorBrowsable(EditorBrowsableState.Never)] + public string DebugName { get; set; } = null!; + public required ClientTransport Transport { get; init; } + string IClientConfig.GetComputedDebugName() => DebugName ?? Transport.ToString(); + private readonly ConcurrentDictionary _clients = new(); private ServiceClient GetServiceClient(Type proxyType) { @@ -16,18 +28,42 @@ private ServiceClient GetServiceClient(Type proxyType) internal void Validate() { - if (Config is null) + var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false; + + if (haveDeferredInjectedCallbacks && ServiceProvider is null) { - throw new InvalidOperationException($"{Config} is required."); + throw new InvalidOperationException("ServiceProvider is required when you register injectable callbacks. Consider registering a callback instance."); } + if (Transport is null) { throw new InvalidOperationException($"{Transport} is required."); } - Config.Validate(); Transport.Validate(); + } - Config.DebugName ??= Transport.ToString(); + internal ILogger? GetLogger(string name) + { + if (Logger is not null) + { + return Logger; + } + + if (ServiceProvider?.GetService() is not { } loggerFactory) + { + return null; + } + + return loggerFactory.CreateLogger(name); } + + internal RouterConfig CreateCallbackRouterConfig() + => RouterConfig.From( + Callbacks.OrDefault(), + endpoint => endpoint with + { + BeforeIncommingCall = null, // callbacks don't support BeforeCall + Scheduler = endpoint.Scheduler ?? Scheduler + }); } diff --git a/src/UiPath.CoreIpc/Config/IpcServer.cs b/src/UiPath.CoreIpc/Config/IpcServer.cs index d0f58020..e9c3d14d 100644 --- a/src/UiPath.CoreIpc/Config/IpcServer.cs +++ b/src/UiPath.CoreIpc/Config/IpcServer.cs @@ -14,8 +14,16 @@ public sealed class IpcServer : Peer, IAsyncDisposable private bool _disposeStarted; private Accepter? _accepter; + private Lazy _dispose; - public async ValueTask DisposeAsync() + public IpcServer() + { + _dispose = new(DisposeCore); + } + + public ValueTask DisposeAsync() => new(_dispose.Value); + + private async Task DisposeCore() { Accepter? accepter = null; lock (_lock) @@ -77,6 +85,13 @@ private void OnNewConnectionError(Exception ex) _stopped.TrySetException(ex); } + internal RouterConfig CreateRouterConfig(IpcServer server) => RouterConfig.From( + server.Endpoints, + endpoint => endpoint with + { + Scheduler = endpoint.Scheduler ?? server.Scheduler + }); + private sealed class ObserverAdapter : IObserver { public required Action OnNext { get; init; } @@ -95,6 +110,7 @@ private sealed class Accepter : IAsyncDisposable private readonly Task _running; private readonly IObserver _newConnection; private readonly TaskCompletionSource _tcsStartedAccepting = new(); + private readonly Lazy _dispose; public Task StartedAccepting => _tcsStartedAccepting.Task; @@ -103,15 +119,19 @@ public Accepter(ServerTransport transport, IObserver connected) _serverState = transport.CreateServerState(); _newConnection = connected; _running = RunOnThreadPool(LoopAccept, parallelCount: transport.ConcurrentAccepts, _cts.Token); + _dispose = new(DisposeCore); } - public async ValueTask DisposeAsync() - { + public ValueTask DisposeAsync() => new(_dispose.Value); + + private async Task DisposeCore() + { _cts.Cancel(); await _running; _cts.Dispose(); } + private async Task LoopAccept(CancellationToken ct) { try diff --git a/src/UiPath.CoreIpc/Config/Peer.cs b/src/UiPath.CoreIpc/Config/Peer.cs index c7e9ba8a..4d4f7121 100644 --- a/src/UiPath.CoreIpc/Config/Peer.cs +++ b/src/UiPath.CoreIpc/Config/Peer.cs @@ -2,11 +2,7 @@ public abstract class Peer { - public TimeSpan RequestTimeout { get; init; } = Timeout.InfiniteTimeSpan; - public IServiceProvider? ServiceProvider { get; init; } - public TaskScheduler? Scheduler { get; init; } - - internal virtual RouterConfig CreateRouterConfig(IpcServer server) => throw new NotSupportedException(); - - internal virtual RouterConfig CreateCallbackRouterConfig() => throw new NotSupportedException(); + public TimeSpan RequestTimeout { get; set; } = Timeout.InfiniteTimeSpan; + public IServiceProvider? ServiceProvider { get; set; } + public TaskScheduler? Scheduler { get; set; } } diff --git a/src/UiPath.CoreIpc/Config/ServerTransport.cs b/src/UiPath.CoreIpc/Config/ServerTransport.cs index ed49685e..c63bc155 100644 --- a/src/UiPath.CoreIpc/Config/ServerTransport.cs +++ b/src/UiPath.CoreIpc/Config/ServerTransport.cs @@ -5,8 +5,9 @@ namespace UiPath.Ipc; public abstract class ServerTransport { - public int ConcurrentAccepts { get; init; } = 5; - public byte MaxReceivedMessageSizeInMegabytes { get; init; } = 2; + public int ConcurrentAccepts { get; set; } = 5; + public byte MaxReceivedMessageSizeInMegabytes { get; set; } = 2; + public X509Certificate? Certificate { get; init; } internal int MaxMessageSize => MaxReceivedMessageSizeInMegabytes * 1024 * 1024; diff --git a/src/UiPath.CoreIpc/Helpers/Router.cs b/src/UiPath.CoreIpc/Helpers/Router.cs index 45252243..26684819 100644 --- a/src/UiPath.CoreIpc/Helpers/Router.cs +++ b/src/UiPath.CoreIpc/Helpers/Router.cs @@ -131,7 +131,7 @@ public static Route From(IServiceProvider? serviceProvider, EndpointSettings end => new Route() { Service = endpointSettings.Service.WithProvider(serviceProvider), - BeforeCall = endpointSettings.BeforeCall, + BeforeCall = endpointSettings.BeforeIncommingCall, Scheduler = endpointSettings.Scheduler.OrDefault(), LoggerFactory = serviceProvider.MaybeCreateServiceFactory(), }; diff --git a/src/UiPath.CoreIpc/Server/EndpointSettings.cs b/src/UiPath.CoreIpc/Server/EndpointSettings.cs index 26a39d6b..3b00df73 100644 --- a/src/UiPath.CoreIpc/Server/EndpointSettings.cs +++ b/src/UiPath.CoreIpc/Server/EndpointSettings.cs @@ -5,7 +5,7 @@ public record EndpointSettings { public TaskScheduler? Scheduler { get; set; } - public BeforeCallHandler? BeforeCall { get; set; } + public BeforeCallHandler? BeforeIncommingCall { get; set; } public Type ContractType => Service.Type; public object? ServiceInstance => Service.MaybeGetInstance(); public IServiceProvider? ServiceProvider => Service.MaybeGetServiceProvider(); diff --git a/src/UiPath.CoreIpc/Server/ServerConnection.cs b/src/UiPath.CoreIpc/Server/ServerConnection.cs index cde20546..276985b1 100644 --- a/src/UiPath.CoreIpc/Server/ServerConnection.cs +++ b/src/UiPath.CoreIpc/Server/ServerConnection.cs @@ -2,7 +2,7 @@ namespace UiPath.Ipc; -internal sealed class ServerConnection : IClient, IDisposable, IServiceClientConfig +internal sealed class ServerConnection : IClient, IDisposable, IClientConfig { public static void CreateAndListen(IpcServer server, Stream network, CancellationToken ct) { @@ -71,10 +71,10 @@ void IClient.Impersonate(Action action) } #region IServiceClientConfig - TimeSpan IServiceClientConfig.RequestTimeout => _ipcServer.RequestTimeout; - BeforeConnectHandler? IServiceClientConfig.BeforeConnect => null; - BeforeCallHandler? IServiceClientConfig.BeforeCall => null; - ILogger? IServiceClientConfig.Logger => _logger; - string IServiceClientConfig.DebugName => _debugName; + TimeSpan IClientConfig.RequestTimeout => _ipcServer.RequestTimeout; + BeforeConnectHandler? IClientConfig.BeforeConnect => null; + BeforeCallHandler? IClientConfig.BeforeOutgoingCall => null; + ILogger? IClientConfig.Logger => _logger; + string IClientConfig.GetComputedDebugName() => _debugName; #endregion } diff --git a/src/UiPath.CoreIpc/Transport/Tcp/TcpTransport.cs b/src/UiPath.CoreIpc/Transport/Tcp/TcpClientTransport.cs similarity index 89% rename from src/UiPath.CoreIpc/Transport/Tcp/TcpTransport.cs rename to src/UiPath.CoreIpc/Transport/Tcp/TcpClientTransport.cs index c114f4f6..dd7cf5a3 100644 --- a/src/UiPath.CoreIpc/Transport/Tcp/TcpTransport.cs +++ b/src/UiPath.CoreIpc/Transport/Tcp/TcpClientTransport.cs @@ -2,7 +2,7 @@ namespace UiPath.Ipc.Transport.Tcp; -public sealed record TcpTransport : ClientTransport +public sealed record TcpClientTransport : ClientTransport { public required IPEndPoint EndPoint { get; init; } @@ -32,7 +32,7 @@ public bool IsConnected() public async ValueTask Connect(IpcClient client, CancellationToken ct) { - var transport = client.Transport as TcpTransport ?? throw new InvalidOperationException(); + var transport = client.Transport as TcpClientTransport ?? throw new InvalidOperationException(); _tcpClient = new System.Net.Sockets.TcpClient(); #if NET461 diff --git a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketTransport.cs b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketClientTransport.cs similarity index 84% rename from src/UiPath.CoreIpc/Transport/WebSocket/WebSocketTransport.cs rename to src/UiPath.CoreIpc/Transport/WebSocket/WebSocketClientTransport.cs index 6558b2c7..e5b36472 100644 --- a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketTransport.cs +++ b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketClientTransport.cs @@ -2,7 +2,7 @@ namespace UiPath.Ipc.Transport.WebSocket; -public sealed record WebSocketTransport : ClientTransport +public sealed record WebSocketClientTransport : ClientTransport { public required Uri Uri { get; init; } public override string ToString() => $"WebSocketClient={Uri}"; @@ -28,7 +28,7 @@ internal sealed class WebSocketClientState : IClientState public async ValueTask Connect(IpcClient client, CancellationToken ct) { - var transport = client.Transport as WebSocketTransport ?? throw new InvalidOperationException(); + var transport = client.Transport as WebSocketClientTransport ?? throw new InvalidOperationException(); _clientWebSocket = new(); await _clientWebSocket.ConnectAsync(transport.Uri, ct); diff --git a/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj b/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj index af37d5f6..d2bb74a6 100644 --- a/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj +++ b/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj @@ -27,6 +27,13 @@ + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -35,6 +42,7 @@ + diff --git a/src/UiPath.Ipc.Tests/ComputingTests.cs b/src/UiPath.Ipc.Tests/ComputingTests.cs index e6858e6b..aeecd313 100644 --- a/src/UiPath.Ipc.Tests/ComputingTests.cs +++ b/src/UiPath.Ipc.Tests/ComputingTests.cs @@ -2,7 +2,6 @@ using Nito.AsyncEx; using Nito.Disposables; using NSubstitute; -using System.Collections.Concurrent; using System.Runtime.InteropServices; using System.Text; using UiPath.Ipc.Transport.NamedPipe; @@ -12,7 +11,7 @@ namespace UiPath.Ipc.Tests; -public abstract class ComputingTests : TestBase +public abstract class ComputingTests : SpyTestBase { #region " Setup " protected readonly ComputingCallback _computingCallback = new(); @@ -25,8 +24,10 @@ public abstract class ComputingTests : TestBase protected sealed override IpcProxy? IpcProxy => Proxy as IpcProxy; protected sealed override Type ContractType => typeof(IComputingService); - - protected readonly ConcurrentBag _clientBeforeCalls = new(); + protected override EndpointCollection? Callbacks => new() + { + { typeof(IComputingCallback), _computingCallback } + }; protected ComputingTests(ITestOutputHelper outputHelper) : base(outputHelper) { @@ -42,24 +43,7 @@ protected override void ConfigureSpecificServices(IServiceCollection services) .AddSingletonAlias() ; - protected override ServerTransport ConfigTransportAgnostic(ServerTransport listener) - => listener with - { - ConcurrentAccepts = 10, - RequestTimeout = Timeouts.DefaultRequest, - MaxReceivedMessageSizeInMegabytes = 1, - }; - protected override ClientConfig CreateClientConfig(EndpointCollection? callbacks = null) - => new() - { - RequestTimeout = Timeouts.DefaultRequest, - Scheduler = GuiScheduler, - Callbacks = callbacks ?? new() - { - { typeof(IComputingCallback), _computingCallback } - }, - BeforeCall = async (callInfo, _) => _clientBeforeCalls.Add(callInfo), - }; + protected override TimeSpan ServerRequestTimeout => Timeouts.DefaultRequest; #endregion [Theory, IpcAutoData] @@ -231,16 +215,13 @@ public async Task BeforeConnect_ShouldStartExternalServerJIT() }); var proxy = new IpcClient { - Config = new() + Scheduler = GuiScheduler, + BeforeConnect = async (_) => { - Scheduler = GuiScheduler, - BeforeConnect = async (_) => - { - serverProcess.Start(); - var time = TimeSpan.FromSeconds(1); - _outputHelper.WriteLine($"Server started. Waiting {time}. PID={serverProcess.Id}"); - await Task.Delay(time); - }, + serverProcess.Start(); + var time = TimeSpan.FromSeconds(1); + _outputHelper.WriteLine($"Server started. Waiting {time}. PID={serverProcess.Id}"); + await Task.Delay(time); }, Transport = externalServerParams.CreateClientTransport() }.GetProxy(); @@ -260,7 +241,7 @@ await Enumerable.Range(1, CParallelism) var mockCallback = Substitute.For(); mockCallback.AddInts(0, 1).Returns(1); - var proxy = CreateClient(callbacks: new() + var proxy = CreateIpcClient(callbacks: new() { { typeof(IComputingCallback), mockCallback } })!.GetProxy(); @@ -310,8 +291,8 @@ public readonly record struct ExternalServerParams(ServerKind Kind, string? Pipe public ClientTransport CreateClientTransport() => Kind switch { ServerKind.NamedPipes => new NamedPipeClientTransport() { PipeName = PipeName! }, - ServerKind.Tcp => new TcpTransport() { EndPoint = new(System.Net.IPAddress.Loopback, Port) }, - ServerKind.WebSockets => new WebSocketTransport() { Uri = new($"ws://localhost:{Port}") }, + ServerKind.Tcp => new TcpClientTransport() { EndPoint = new(System.Net.IPAddress.Loopback, Port) }, + ServerKind.WebSockets => new WebSocketClientTransport() { Uri = new($"ws://localhost:{Port}") }, _ => throw new NotSupportedException($"Kind not supported. Kind was {Kind}") }; } @@ -319,7 +300,7 @@ public enum ServerKind { NamedPipes, Tcp, WebSockets } private sealed class DisableInProcClientServer : OverrideConfig { - public override async Task Override(Func> listener) => null; + public override async Task Override(Func> ipcServerFactory) => null; public override IpcClient? Override(Func client) => null; } } diff --git a/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs index 067b5ba7..88ef854f 100644 --- a/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/ComputingTestsOverNamedPipes.cs @@ -9,7 +9,7 @@ public sealed class ComputingTestsOverNamedPipes : ComputingTests public ComputingTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected override async Task CreateListener() => new NamedPipeServerTransport + protected override async Task CreateServerTransport() => new NamedPipeServerTransport { PipeName = PipeName }; diff --git a/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs b/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs index 237c1e40..cecdedde 100644 --- a/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs +++ b/src/UiPath.Ipc.Tests/Config/OverrideConfigAttribute.cs @@ -34,6 +34,6 @@ public OverrideConfigAttribute(Type overrideConfigType) public abstract class OverrideConfig { - public virtual Task Override(Func> listener) => listener()!; - public virtual IpcClient? Override(Func client) => client(); + public virtual Task Override(Func> ipcServer) => ipcServer()!; + public virtual IpcClient? Override(Func ipcClientFactory) => ipcClientFactory(); } \ No newline at end of file diff --git a/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs b/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs index 0296354b..3ccc33c9 100644 --- a/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs +++ b/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs @@ -35,23 +35,27 @@ public static IServiceProvider GetRequired(this IServiceProvider serviceProvi internal static class IpcClientExtensions { public static IpcClient WithRequestTimeout(this IpcClient ipcClient, TimeSpan requestTimeout) - => new() { - Config = ipcClient.Config with { RequestTimeout = requestTimeout }, - Transport = ipcClient.Transport, - }; + ipcClient.RequestTimeout = requestTimeout; + return ipcClient; + } + public static IpcServer WithRequestTimeout(this IpcServer ipcServer, TimeSpan requestTimeout) + { + ipcServer.RequestTimeout = requestTimeout; + return ipcServer; + } + public static async Task WithRequestTimeout(this Task ipcServerTask, TimeSpan requestTimeout) + => (await ipcServerTask).WithRequestTimeout(requestTimeout); public static IpcClient WithCallbacks(this IpcClient ipcClient, EndpointCollection callbacks) - => new() { - Config = ipcClient.Config with { Callbacks = callbacks }, - Transport = ipcClient.Transport, - }; + ipcClient.Callbacks = callbacks; + return ipcClient; + } public static IpcClient WithBeforeConnect(this IpcClient ipcClient, BeforeConnectHandler beforeConnect) - => new() { - Config = ipcClient.Config with { BeforeConnect = beforeConnect }, - Transport = ipcClient.Transport, - }; + ipcClient.BeforeConnect = beforeConnect; + return ipcClient; + } } \ No newline at end of file diff --git a/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs b/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs index 71fa5d83..e43eee21 100644 --- a/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs +++ b/src/UiPath.Ipc.Tests/NamedPipeSmokeTests.cs @@ -26,12 +26,10 @@ public async Task NamedPipesShoulNotLeak() private static IpcServer CreateServer(string pipeName) => new IpcServer { - Transport = [ - new NamedPipeServerTransport - { - PipeName = pipeName, - } - ], + Transport = new NamedPipeServerTransport + { + PipeName = pipeName, + }, Endpoints = new() { typeof(IComputingService) @@ -45,8 +43,7 @@ private static IpcServer CreateServer(string pipeName) private static IpcClient CreateClient(string pipeName) => new() { - Transport = new NamedPipeClientTransport { PipeName = pipeName }, - Config = new() + Transport = new NamedPipeClientTransport { PipeName = pipeName } }; private static Task ListPipes(string pattern) diff --git a/src/UiPath.Ipc.Tests/Program.cs b/src/UiPath.Ipc.Tests/Program.cs index 084096ce..feb7a67e 100644 --- a/src/UiPath.Ipc.Tests/Program.cs +++ b/src/UiPath.Ipc.Tests/Program.cs @@ -10,7 +10,7 @@ return 1; } var externalServerParams = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Convert.FromBase64String(base64))); -await using var asyncDisposable = externalServerParams.CreateListenerConfig(out var listener); +await using var asyncDisposable = externalServerParams.CreateListenerConfig(out var serverTransport); await using var serviceProvider = new ServiceCollection() .AddLogging(builder => builder.AddConsole()) @@ -25,7 +25,7 @@ { { typeof(IComputingService) }, }, - Transport = [listener], + Transport = serverTransport, }; ipcServer.Start(); await ipcServer.WaitForStop(); diff --git a/src/UiPath.Ipc.Tests/RobotTests.cs b/src/UiPath.Ipc.Tests/RobotTests.cs index c07902d8..627bb566 100644 --- a/src/UiPath.Ipc.Tests/RobotTests.cs +++ b/src/UiPath.Ipc.Tests/RobotTests.cs @@ -17,6 +17,10 @@ public abstract class RobotTests : TestBase protected sealed override IpcProxy? IpcProxy => Proxy as IpcProxy; protected sealed override Type ContractType => typeof(IStudioOperations); + protected override EndpointCollection? Callbacks => new() + { + { typeof(IStudioEvents), _studioEvents } + }; protected readonly ConcurrentBag _clientBeforeCalls = new(); @@ -31,24 +35,7 @@ protected override void ConfigureSpecificServices(IServiceCollection services) .AddSingleton() .AddSingletonAlias(); - protected override ServerTransport ConfigTransportAgnostic(ServerTransport listener) - => listener with - { - ConcurrentAccepts = 10, - RequestTimeout = Timeouts.DefaultRequest, - MaxReceivedMessageSizeInMegabytes = 1, - }; - protected override ClientConfig CreateClientConfig(EndpointCollection? callbacks = null) - => new() - { - RequestTimeout = Timeouts.DefaultRequest, - Scheduler = GuiScheduler, - Callbacks = callbacks ?? new() - { - { typeof(IStudioEvents), _studioEvents } - }, - BeforeCall = async (callInfo, _) => _clientBeforeCalls.Add(callInfo), - }; + protected override TimeSpan ServerRequestTimeout => Timeouts.DefaultRequest; #endregion [Fact] diff --git a/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs index fed8d637..5e9b11eb 100644 --- a/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs @@ -12,7 +12,7 @@ public sealed class RobotTestsOverNamedPipes : RobotTests public RobotTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected override async Task CreateListener() => new NamedPipeServerTransport + protected override async Task CreateServerTransport() => new NamedPipeServerTransport { PipeName = PipeName }; @@ -62,13 +62,8 @@ public static void OnConnectingToUserService() public TContract CreateUserServiceProxy(string pipeName) where TContract : class => RobotIpcHelpers.CreateProxy( - listener: new NamedPipeServerTransport() - { - PipeName = pipeName, - AccessControl = security => security.AllowCurrentUser(), - MaxReceivedMessageSizeInMegabytes = 10, - RequestTimeout = TimeSpan.FromSeconds(40), - }, + pipeName, + requestTimeout: TimeSpan.FromSeconds(40), callbacks: new EndpointCollection() { { typeof(TCallback), Instance } @@ -81,16 +76,6 @@ internal static partial class RobotIpcHelpers { private static readonly ConcurrentDictionary PipeClients = new(); - public static TContract CreateProxy( - NamedPipeServerTransport listener, - EndpointCollection? callbacks = null, - IServiceProvider? provider = null, - Action? beforeConnect = null, - BeforeCallHandler? beforeCall = null, - bool allowImpersonation = false, - TaskScheduler? scheduler = null) where TContract : class - => CreateProxy(listener.PipeName, listener.RequestTimeout, callbacks, provider, beforeConnect, beforeCall, allowImpersonation, scheduler); - public static TContract CreateProxy( string pipeName, TimeSpan? requestTimeout = null, @@ -101,6 +86,7 @@ public static TContract CreateProxy( bool allowImpersonation = false, TaskScheduler? scheduler = null) where TContract : class { + // TODO: Fix this // Dirty hack (temporary): different callback sets will result in different connections // Hopefully, the different sets are also disjunctive. @@ -139,20 +125,17 @@ private static ClientAndParams CreateClient(CreateProxyRequest request) => new( Client: new() { - Config = new() + RequestTimeout = request.Params.RequestTimeout ?? Timeout.InfiniteTimeSpan, + ServiceProvider = request.Params.Provider, + Logger = request.Params.Provider?.GetService>(), + Callbacks = request.Callbacks, + BeforeConnect = request.Params.BeforeConnect is null ? null : _ => { - RequestTimeout = request.Params.RequestTimeout ?? Timeout.InfiniteTimeSpan, - ServiceProvider = request.Params.Provider, - Logger = request.Params.Provider?.GetService>(), - Callbacks = request.Callbacks, - BeforeConnect = request.Params.BeforeConnect is null ? null : _ => - { - request.Params.BeforeConnect(); - return Task.CompletedTask; - }, - BeforeCall = request.Params.BeforeCall, - Scheduler = request.Params.Scheduler, + request.Params.BeforeConnect(); + return Task.CompletedTask; }, + BeforeOutgoingCall = request.Params.BeforeCall, + Scheduler = request.Params.Scheduler, Transport = new NamedPipeClientTransport { PipeName = request.ActualKey.Name, diff --git a/src/UiPath.Ipc.Tests/SpyTestBase.cs b/src/UiPath.Ipc.Tests/SpyTestBase.cs new file mode 100644 index 00000000..2fc866f3 --- /dev/null +++ b/src/UiPath.Ipc.Tests/SpyTestBase.cs @@ -0,0 +1,20 @@ +using System.Collections.Concurrent; +using Xunit.Abstractions; + +namespace UiPath.Ipc.Tests; + +public abstract class SpyTestBase : TestBase +{ + protected readonly ConcurrentBag _clientBeforeCalls = new(); + + protected SpyTestBase(ITestOutputHelper outputHelper) : base(outputHelper) + { + } + + protected override void ConfigureClient(IpcClient ipcClient) + { + base.ConfigureClient(ipcClient); + + ipcClient.BeforeOutgoingCall = async (callInfo, _) => _clientBeforeCalls.Add(callInfo); + } +} diff --git a/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs b/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs index 01d4ef79..6992eeeb 100644 --- a/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs +++ b/src/UiPath.Ipc.Tests/SyncOverAsyncTests.cs @@ -47,12 +47,7 @@ public async Task RemoteCallingSyncOverAsync_IpcShouldBeResilient(ScenarioId sce private static IpcServer CreateServer(string pipeName) => new IpcServer { - Transport = [ - new NamedPipeServerTransport - { - PipeName = pipeName, - } - ], + Transport = new NamedPipeServerTransport { PipeName = pipeName }, Endpoints = new() { typeof(IComputingService) @@ -66,8 +61,7 @@ private static IpcServer CreateServer(string pipeName) private static IpcClient CreateClient(string pipeName) => new() { - Transport = new NamedPipeClientTransport { PipeName = pipeName }, - Config = new() + Transport = new NamedPipeClientTransport { PipeName = pipeName } }; diff --git a/src/UiPath.Ipc.Tests/SystemTests.cs b/src/UiPath.Ipc.Tests/SystemTests.cs index 73dc59a3..70db74df 100644 --- a/src/UiPath.Ipc.Tests/SystemTests.cs +++ b/src/UiPath.Ipc.Tests/SystemTests.cs @@ -30,20 +30,7 @@ protected override void ConfigureSpecificServices(IServiceCollection services) .AddSingleton() .AddSingletonAlias(); - protected override ServerTransport ConfigTransportAgnostic(ServerTransport listener) - => listener with - { - ConcurrentAccepts = 10, - RequestTimeout = Timeouts.DefaultRequest, - MaxReceivedMessageSizeInMegabytes = 1, - }; - protected override ClientConfig CreateClientConfig(EndpointCollection? callbacks = null) - => new() - { - RequestTimeout = Timeouts.DefaultRequest, - ServiceProvider = ServiceProvider, - Callbacks = callbacks - }; + protected override TimeSpan ServerRequestTimeout => Timeouts.DefaultRequest; #endregion [Theory, IpcAutoData] @@ -76,7 +63,7 @@ public async Task NotPassingAnOptionalMessage_ShouldWork() .ShouldBeAsync(true); [Fact] - [OverrideConfig(typeof(ServerExecutingTooLongACall_ShouldThrowTimeout_Config))] + [OverrideConfig(typeof(ShortServerLongClientTimeout))] public async Task ServerExecutingTooLongACall_ShouldThrowTimeout() => await Proxy.EchoGuidAfter(Guid.Empty, Timeout.InfiniteTimeSpan) // method takes forever but we have a server side RequestTimeout configured .ShouldThrowAsync() @@ -92,23 +79,24 @@ public async Task ClientWaitingForTooLongACall_ShouldThrowTimeout() => await Proxy.EchoGuidAfter(Guid.Empty, Timeout.InfiniteTimeSpan) // method takes forever but we have a server side RequestTimeout configured .ShouldThrowAsync(); - private sealed class ServerExecutingTooLongACall_ShouldThrowTimeout_Config : OverrideConfig + private sealed class ShortServerLongClientTimeout : OverrideConfig { - public override async Task Override(Func> listener) => await listener() with { RequestTimeout = Timeouts.Short }; - public override IpcClient? Override(Func client) - => client().WithRequestTimeout(Timeout.InfiniteTimeSpan); + public override async Task Override(Func> ipcServerFactory) + { + var ipcServer = await ipcServerFactory(); + ipcServer.RequestTimeout = Timeouts.Short; + return ipcServer; + } + + public override IpcClient? Override(Func client) => client().WithRequestTimeout(Timeout.InfiniteTimeSpan); } private sealed class ClientWaitingForTooLongACall_ShouldThrowTimeout_Config : OverrideConfig { - public override async Task Override(Func> listener) => await listener() with { RequestTimeout = Timeout.InfiniteTimeSpan }; - public override IpcClient? Override(Func client) - => client().WithRequestTimeout(Timeouts.IpcRoundtrip); + public override Task Override(Func> ipcServerFactory) => ipcServerFactory().WithRequestTimeout(Timeout.InfiniteTimeSpan)!; + public override IpcClient? Override(Func client) => client().WithRequestTimeout(Timeouts.IpcRoundtrip); } - private ServerTransport ShortClientTimeout(ServerTransport listener) => listener with { RequestTimeout = TimeSpan.FromMilliseconds(100) }; - private ServerTransport InfiniteServerTimeout(ServerTransport listener) => listener with { RequestTimeout = Timeout.InfiniteTimeSpan }; - [Fact] public async Task FireAndForget_ShouldWork() { diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs index d0430161..27f83f70 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverNamedPipes.cs @@ -9,7 +9,7 @@ public sealed class SystemTestsOverNamedPipes : SystemTests public SystemTestsOverNamedPipes(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected sealed override async Task CreateListener() => new NamedPipeServerTransport + protected sealed override async Task CreateServerTransport() => new NamedPipeServerTransport { PipeName = PipeName }; diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs b/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs index 265a9134..d9878b51 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverTcp.cs @@ -10,12 +10,12 @@ public sealed class SystemTestsOverTcp : SystemTests public SystemTestsOverTcp(ITestOutputHelper outputHelper) : base(outputHelper) { } - protected sealed override async Task CreateListener() + protected sealed override async Task CreateServerTransport() => new TcpServerTransport { EndPoint = _endPoint, }; protected override ClientTransport CreateClientTransport() - => new TcpTransport() { EndPoint = _endPoint }; + => new TcpClientTransport() { EndPoint = _endPoint }; } diff --git a/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs b/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs index b8795dd2..bc17f6f6 100644 --- a/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs +++ b/src/UiPath.Ipc.Tests/SystemTestsOverWebSockets.cs @@ -15,7 +15,7 @@ protected override async Task DisposeAsync() await base.DisposeAsync(); } - protected override async Task CreateListener() + protected override async Task CreateServerTransport() { var listener = new WebSocketServerTransport { @@ -27,5 +27,5 @@ protected override async Task CreateListener() } protected override ClientTransport CreateClientTransport() - => new WebSocketTransport() { Uri = _webSocketContext.ClientUri }; + => new WebSocketClientTransport() { Uri = _webSocketContext.ClientUri }; } diff --git a/src/UiPath.Ipc.Tests/TestBase.cs b/src/UiPath.Ipc.Tests/TestBase.cs index 3086c9b3..5652726f 100644 --- a/src/UiPath.Ipc.Tests/TestBase.cs +++ b/src/UiPath.Ipc.Tests/TestBase.cs @@ -45,8 +45,8 @@ public TestBase(ITestOutputHelper outputHelper) _guiThread.SynchronizationContext.Send(() => Thread.CurrentThread.Name = Names.GuiThreadName); _serviceProvider = IpcHelpers.ConfigureServices(_outputHelper, ConfigureSpecificServices); - _ipcServer = new(CreateServer); - _ipcClient = new(() => CreateClient()); + _ipcServer = new(CreateIpcServer); + _ipcClient = new(() => CreateIpcClient()); OverrideConfig? GetOverrideConfig() { @@ -67,78 +67,84 @@ public TestBase(ITestOutputHelper outputHelper) protected abstract void ConfigureSpecificServices(IServiceCollection services); - private Task CreateListenerAndConfigure() - { - var factory = async () => - { - _outputHelper.WriteLine("Creating listener..."); - var listener = await CreateListener(); - listener = ConfigTransportAgnostic(listener); - return listener; - }; + protected virtual EndpointCollection? Callbacks => []; - if (_overrideConfig is null) + private Task CreateIpcServer() + { + if (_overrideConfig is not null) { - return factory(); + return _overrideConfig.Override(Core); } - return _overrideConfig.Override(factory); - } - - protected async Task CreateServer() - { - if (await CreateListenerAndConfigure() is not { } listener) return null; + return Core()!; - return new() + async Task Core() { - Endpoints = new() { - new EndpointSettings(ContractType) + _outputHelper.WriteLine($"Creating {nameof(ServerTransport)}..."); + + var serverTransport = await CreateServerTransport(); + ConfigTransportBase(serverTransport); + + var endpointSettings = new EndpointSettings(ContractType) + { + BeforeIncommingCall = (callInfo, ct) => { - BeforeCall = (callInfo, ct) => - { - _serverBeforeCalls.Add(callInfo); - return _tailBeforeCall?.Invoke(callInfo, ct) ?? Task.CompletedTask; - } + _serverBeforeCalls.Add(callInfo); + return _tailBeforeCall?.Invoke(callInfo, ct) ?? Task.CompletedTask; } - }, - Transport = [listener], - ServiceProvider = _serviceProvider, - Scheduler = GuiScheduler - }; - } + }; - protected IpcClient? CreateClient(EndpointCollection? callbacks = null) + return new() + { + Endpoints = new() { endpointSettings }, + Transport = serverTransport, + ServiceProvider = _serviceProvider, + Scheduler = GuiScheduler, + RequestTimeout = ServerRequestTimeout + }; + } + } + protected IpcClient? CreateIpcClient(EndpointCollection? callbacks = null) { - var factory = () => + if (_overrideConfig is null) + { + return CreateDefaultClient(); + } + + return _overrideConfig.Override(CreateDefaultClient); + + IpcClient CreateDefaultClient() { - var config = CreateClientConfig(callbacks); - var transport = CreateClientTransport(); var client = new IpcClient { - Config = config, - Transport = transport + Callbacks = callbacks ?? Callbacks, + Transport = CreateClientTransport() }; + ConfigureClient(client); return client; - }; - - if (_overrideConfig is null) - { - return factory(); } - - return _overrideConfig.Override(factory); } + protected TContract? GetProxy() where TContract : class => _ipcClient.Value?.GetProxy(); protected void CreateLazyProxy(out Lazy lazy) where TContract : class => lazy = new(GetProxy); - protected abstract Task CreateListener(); + protected abstract Task CreateServerTransport(); + protected abstract TimeSpan ServerRequestTimeout { get; } - protected abstract ClientConfig CreateClientConfig(EndpointCollection? callbacks = null); + protected virtual void ConfigureClient(IpcClient ipcClient) + { + ipcClient.RequestTimeout = Timeouts.DefaultRequest; + ipcClient.Scheduler = GuiScheduler; + } protected abstract ClientTransport CreateClientTransport(); - protected abstract ServerTransport ConfigTransportAgnostic(ServerTransport listener); + protected virtual void ConfigTransportBase(ServerTransport serverTransport) + { + serverTransport.ConcurrentAccepts = 10; + serverTransport.MaxReceivedMessageSizeInMegabytes = 1; + } protected virtual async Task DisposeAsync() { From d50e6ab5bb19f046ebb06b9c9a3055eb8420770b Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Tue, 26 Nov 2024 10:20:17 +0100 Subject: [PATCH 04/13] taking shape --- src/UiPath.CoreIpc/Connection.cs | 4 +- src/UiPath.CoreIpc/Helpers/NestedStream.cs | 4 +- src/UiPath.CoreIpc/PublicAPI.Shipped.txt | 177 ++++++++++++++++++ src/UiPath.CoreIpc/PublicAPI.Unshipped.txt | 1 + src/UiPath.CoreIpc/Server/Server.cs | 2 +- .../UiPath.Ipc.net6.0-windows.received.txt | 135 +++++++------ 6 files changed, 247 insertions(+), 76 deletions(-) create mode 100644 src/UiPath.CoreIpc/PublicAPI.Shipped.txt create mode 100644 src/UiPath.CoreIpc/PublicAPI.Unshipped.txt diff --git a/src/UiPath.CoreIpc/Connection.cs b/src/UiPath.CoreIpc/Connection.cs index be2b3bde..3959f01e 100644 --- a/src/UiPath.CoreIpc/Connection.cs +++ b/src/UiPath.CoreIpc/Connection.cs @@ -364,7 +364,7 @@ private void OnCancellationReceived(CancellationRequest cancellationRequest) { try { - CancellationReceived(cancellationRequest.RequestId); + CancellationReceived?.Invoke(cancellationRequest.RequestId); } catch (Exception ex) { @@ -380,7 +380,7 @@ private async Task OnRequestReceivedAsyncSafe(Request request) { try { - await RequestReceived(request); + await (RequestReceived?.Invoke(request) ?? default); } catch (Exception ex) { diff --git a/src/UiPath.CoreIpc/Helpers/NestedStream.cs b/src/UiPath.CoreIpc/Helpers/NestedStream.cs index 2e69166e..a726678d 100644 --- a/src/UiPath.CoreIpc/Helpers/NestedStream.cs +++ b/src/UiPath.CoreIpc/Helpers/NestedStream.cs @@ -35,7 +35,7 @@ public void Reset(long length) _length = length; } - public event EventHandler Disposed; + public event EventHandler? Disposed; /// public bool IsDisposed => _underlyingStream == null; /// @@ -148,7 +148,7 @@ protected override void Dispose(bool disposing) if (_remainingBytes != 0) { _underlyingStream?.Dispose(); - _underlyingStream = null; + _underlyingStream = null!; } Disposed?.Invoke(this, EventArgs.Empty); base.Dispose(disposing); diff --git a/src/UiPath.CoreIpc/PublicAPI.Shipped.txt b/src/UiPath.CoreIpc/PublicAPI.Shipped.txt new file mode 100644 index 00000000..3a9b8f82 --- /dev/null +++ b/src/UiPath.CoreIpc/PublicAPI.Shipped.txt @@ -0,0 +1,177 @@ +#nullable enable +abstract UiPath.Ipc.ClientTransport.CreateState() -> UiPath.Ipc.IClientState! +abstract UiPath.Ipc.ClientTransport.Validate() -> void +abstract UiPath.Ipc.ServerTransport.CreateServerState() -> UiPath.Ipc.ServerTransport.IServerState! +abstract UiPath.Ipc.ServerTransport.ValidateCore() -> System.Collections.Generic.IEnumerable! +override UiPath.Ipc.EndpointSettings.WithServiceProvider(System.IServiceProvider? serviceProvider) -> UiPath.Ipc.EndpointSettings! +override UiPath.Ipc.Error.ToString() -> string! +override UiPath.Ipc.IpcProxy.Invoke(System.Reflection.MethodInfo? targetMethod, object?[]? args) -> object? +override UiPath.Ipc.RemoteException.StackTrace.get -> string! +override UiPath.Ipc.RemoteException.ToString() -> string! +override UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.CreateState() -> UiPath.Ipc.IClientState! +override UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.ToString() -> string! +override UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.Validate() -> void +override UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.ToString() -> string! +override UiPath.Ipc.Transport.Tcp.TcpClientTransport.CreateState() -> UiPath.Ipc.IClientState! +override UiPath.Ipc.Transport.Tcp.TcpClientTransport.ToString() -> string! +override UiPath.Ipc.Transport.Tcp.TcpClientTransport.Validate() -> void +override UiPath.Ipc.Transport.Tcp.TcpServerTransport.ToString() -> string! +override UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.CreateState() -> UiPath.Ipc.IClientState! +override UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.ToString() -> string! +override UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.Validate() -> void +override UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport.ToString() -> string! +static UiPath.Ipc.Error.FromException(System.Exception? exception) -> UiPath.Ipc.Error? +static UiPath.Ipc.IOHelpers.Allow(this System.IO.Pipes.PipeSecurity! pipeSecurity, System.Security.Principal.IdentityReference! sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) -> System.IO.Pipes.PipeSecurity! +static UiPath.Ipc.IOHelpers.Allow(this System.IO.Pipes.PipeSecurity! pipeSecurity, System.Security.Principal.WellKnownSidType sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) -> System.IO.Pipes.PipeSecurity! +static UiPath.Ipc.IOHelpers.AllowCurrentUser(this System.IO.Pipes.PipeSecurity! pipeSecurity, bool onlyNonAdmin = false) -> System.IO.Pipes.PipeSecurity! +static UiPath.Ipc.IOHelpers.Deny(this System.IO.Pipes.PipeSecurity! pipeSecurity, System.Security.Principal.IdentityReference! sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) -> System.IO.Pipes.PipeSecurity! +static UiPath.Ipc.IOHelpers.Deny(this System.IO.Pipes.PipeSecurity! pipeSecurity, System.Security.Principal.WellKnownSidType sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) -> System.IO.Pipes.PipeSecurity! +static UiPath.Ipc.IOHelpers.LocalOnly(this System.IO.Pipes.PipeSecurity! pipeSecurity) -> System.IO.Pipes.PipeSecurity! +static UiPath.Ipc.IOHelpers.PipeExists(string! pipeName, int timeout = 1) -> bool +static UiPath.Ipc.ServerTransport.IsNotNull(T? propertyValue, string? propertyName = null) -> string? +UiPath.Ipc.CallInfo +UiPath.Ipc.CallInfo.Arguments.get -> object?[]! +UiPath.Ipc.CallInfo.CallInfo() -> void +UiPath.Ipc.CallInfo.CallInfo(bool newConnection, System.Reflection.MethodInfo! method, object?[]! arguments) -> void +UiPath.Ipc.CallInfo.Method.get -> System.Reflection.MethodInfo! +UiPath.Ipc.CallInfo.NewConnection.get -> bool +UiPath.Ipc.ClientTransport +UiPath.Ipc.EndpointCollection +UiPath.Ipc.EndpointCollection.Add(System.Type! contractType, object? instance) -> void +UiPath.Ipc.EndpointCollection.Add(System.Type! type) -> void +UiPath.Ipc.EndpointCollection.Add(UiPath.Ipc.EndpointSettings! endpointSettings) -> void +UiPath.Ipc.EndpointCollection.EndpointCollection() -> void +UiPath.Ipc.EndpointCollection.GetEnumerator() -> System.Collections.Generic.IEnumerator! +UiPath.Ipc.EndpointNotFoundException +UiPath.Ipc.EndpointNotFoundException.EndpointName.get -> string! +UiPath.Ipc.EndpointNotFoundException.EndpointNotFoundException(string! paramName, string! serverDebugName, string! endpointName) -> void +UiPath.Ipc.EndpointNotFoundException.ServerDebugName.get -> string! +UiPath.Ipc.EndpointSettings +UiPath.Ipc.EndpointSettings.BeforeIncommingCall.get -> System.Func? +UiPath.Ipc.EndpointSettings.BeforeIncommingCall.set -> void +UiPath.Ipc.EndpointSettings.ContractType.get -> System.Type! +UiPath.Ipc.EndpointSettings.EndpointSettings(System.Type! contractType, object? serviceInstance = null) -> void +UiPath.Ipc.EndpointSettings.EndpointSettings(System.Type! contractType, System.IServiceProvider! serviceProvider) -> void +UiPath.Ipc.EndpointSettings.Scheduler.get -> System.Threading.Tasks.TaskScheduler? +UiPath.Ipc.EndpointSettings.Scheduler.set -> void +UiPath.Ipc.EndpointSettings.ServiceInstance.get -> object? +UiPath.Ipc.EndpointSettings.ServiceProvider.get -> System.IServiceProvider? +UiPath.Ipc.EndpointSettings.Validate() -> void +UiPath.Ipc.EndpointSettings +UiPath.Ipc.EndpointSettings.EndpointSettings(System.IServiceProvider! serviceProvider) -> void +UiPath.Ipc.EndpointSettings.EndpointSettings(TContract? serviceInstance = null) -> void +UiPath.Ipc.Error +UiPath.Ipc.Error.Error(string! Message, string! StackTrace, string! Type, UiPath.Ipc.Error? InnerError) -> void +UiPath.Ipc.Error.InnerError.get -> UiPath.Ipc.Error? +UiPath.Ipc.Error.InnerError.init -> void +UiPath.Ipc.Error.Message.get -> string! +UiPath.Ipc.Error.Message.init -> void +UiPath.Ipc.Error.StackTrace.get -> string! +UiPath.Ipc.Error.StackTrace.init -> void +UiPath.Ipc.Error.Type.get -> string! +UiPath.Ipc.Error.Type.init -> void +UiPath.Ipc.IClient +UiPath.Ipc.IClient.GetCallback() -> TCallbackInterface! +UiPath.Ipc.IClient.Impersonate(System.Action! action) -> void +UiPath.Ipc.IClientState +UiPath.Ipc.IClientState.Connect(UiPath.Ipc.IpcClient! client, System.Threading.CancellationToken ct) -> System.Threading.Tasks.ValueTask +UiPath.Ipc.IClientState.IsConnected() -> bool +UiPath.Ipc.IClientState.Network.get -> System.IO.Stream? +UiPath.Ipc.IOHelpers +UiPath.Ipc.IpcClient +UiPath.Ipc.IpcClient.BeforeConnect.get -> System.Func? +UiPath.Ipc.IpcClient.BeforeConnect.set -> void +UiPath.Ipc.IpcClient.BeforeOutgoingCall.get -> System.Func? +UiPath.Ipc.IpcClient.BeforeOutgoingCall.set -> void +UiPath.Ipc.IpcClient.Callbacks.get -> UiPath.Ipc.EndpointCollection? +UiPath.Ipc.IpcClient.Callbacks.set -> void +UiPath.Ipc.IpcClient.DebugName.get -> string! +UiPath.Ipc.IpcClient.DebugName.set -> void +UiPath.Ipc.IpcClient.GetProxy() -> TProxy! +UiPath.Ipc.IpcClient.IpcClient() -> void +UiPath.Ipc.IpcClient.Logger.get -> Microsoft.Extensions.Logging.ILogger? +UiPath.Ipc.IpcClient.Logger.init -> void +UiPath.Ipc.IpcClient.Transport.get -> UiPath.Ipc.ClientTransport! +UiPath.Ipc.IpcClient.Transport.init -> void +UiPath.Ipc.IpcProxy +UiPath.Ipc.IpcProxy.CloseConnection() -> System.Threading.Tasks.ValueTask +UiPath.Ipc.IpcProxy.ConnectionClosed -> System.EventHandler! +UiPath.Ipc.IpcProxy.Dispose() -> void +UiPath.Ipc.IpcProxy.IpcProxy() -> void +UiPath.Ipc.IpcProxy.Network.get -> System.IO.Stream? +UiPath.Ipc.IpcServer +UiPath.Ipc.IpcServer.DisposeAsync() -> System.Threading.Tasks.ValueTask +UiPath.Ipc.IpcServer.Endpoints.get -> UiPath.Ipc.EndpointCollection! +UiPath.Ipc.IpcServer.Endpoints.init -> void +UiPath.Ipc.IpcServer.IpcServer() -> void +UiPath.Ipc.IpcServer.Start() -> void +UiPath.Ipc.IpcServer.Transport.get -> UiPath.Ipc.ServerTransport! +UiPath.Ipc.IpcServer.Transport.init -> void +UiPath.Ipc.IpcServer.WaitForStart() -> System.Threading.Tasks.Task! +UiPath.Ipc.IpcServer.WaitForStop() -> System.Threading.Tasks.Task! +UiPath.Ipc.Message +UiPath.Ipc.Message.Client.get -> UiPath.Ipc.IClient! +UiPath.Ipc.Message.Client.set -> void +UiPath.Ipc.Message.GetCallback() -> TCallbackInterface! +UiPath.Ipc.Message.ImpersonateClient(System.Action! action) -> void +UiPath.Ipc.Message.Message() -> void +UiPath.Ipc.Message.RequestTimeout.get -> System.TimeSpan +UiPath.Ipc.Message.RequestTimeout.set -> void +UiPath.Ipc.Message +UiPath.Ipc.Message.Message(TPayload payload) -> void +UiPath.Ipc.Message.Payload.get -> TPayload +UiPath.Ipc.Peer +UiPath.Ipc.Peer.Peer() -> void +UiPath.Ipc.Peer.RequestTimeout.get -> System.TimeSpan +UiPath.Ipc.Peer.RequestTimeout.set -> void +UiPath.Ipc.Peer.Scheduler.get -> System.Threading.Tasks.TaskScheduler? +UiPath.Ipc.Peer.Scheduler.set -> void +UiPath.Ipc.Peer.ServiceProvider.get -> System.IServiceProvider? +UiPath.Ipc.Peer.ServiceProvider.set -> void +UiPath.Ipc.RemoteException +UiPath.Ipc.RemoteException.InnerException.get -> UiPath.Ipc.RemoteException? +UiPath.Ipc.RemoteException.Is() -> bool +UiPath.Ipc.RemoteException.RemoteException(UiPath.Ipc.Error! error) -> void +UiPath.Ipc.RemoteException.Type.get -> string! +UiPath.Ipc.ServerTransport +UiPath.Ipc.ServerTransport.Certificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate? +UiPath.Ipc.ServerTransport.Certificate.init -> void +UiPath.Ipc.ServerTransport.ConcurrentAccepts.get -> int +UiPath.Ipc.ServerTransport.ConcurrentAccepts.set -> void +UiPath.Ipc.ServerTransport.IServerConnectionSlot +UiPath.Ipc.ServerTransport.IServerConnectionSlot.AwaitConnection(System.Threading.CancellationToken ct) -> System.Threading.Tasks.ValueTask +UiPath.Ipc.ServerTransport.IServerState +UiPath.Ipc.ServerTransport.IServerState.CreateConnectionSlot() -> UiPath.Ipc.ServerTransport.IServerConnectionSlot! +UiPath.Ipc.ServerTransport.MaxReceivedMessageSizeInMegabytes.get -> byte +UiPath.Ipc.ServerTransport.MaxReceivedMessageSizeInMegabytes.set -> void +UiPath.Ipc.ServerTransport.ServerTransport() -> void +UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport +UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.AllowImpersonation.get -> bool +UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.AllowImpersonation.init -> void +UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.PipeName.get -> string! +UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.PipeName.init -> void +UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.ServerName.get -> string! +UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.ServerName.init -> void +UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport +UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.AccessControl.get -> System.Action? +UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.AccessControl.init -> void +UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.NamedPipeServerTransport() -> void +UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.PipeName.get -> string! +UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.PipeName.init -> void +UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.ServerName.get -> string! +UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.ServerName.init -> void +UiPath.Ipc.Transport.Tcp.TcpClientTransport +UiPath.Ipc.Transport.Tcp.TcpClientTransport.EndPoint.get -> System.Net.IPEndPoint! +UiPath.Ipc.Transport.Tcp.TcpClientTransport.EndPoint.init -> void +UiPath.Ipc.Transport.Tcp.TcpServerTransport +UiPath.Ipc.Transport.Tcp.TcpServerTransport.EndPoint.get -> System.Net.IPEndPoint! +UiPath.Ipc.Transport.Tcp.TcpServerTransport.EndPoint.init -> void +UiPath.Ipc.Transport.Tcp.TcpServerTransport.TcpServerTransport() -> void +UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport +UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.Uri.get -> System.Uri! +UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.Uri.init -> void +UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport +UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport.Accept.get -> System.Func!>! +UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport.Accept.init -> void +UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport.WebSocketServerTransport() -> void +virtual UiPath.Ipc.EndpointSettings.WithServiceProvider(System.IServiceProvider? serviceProvider) -> UiPath.Ipc.EndpointSettings! \ No newline at end of file diff --git a/src/UiPath.CoreIpc/PublicAPI.Unshipped.txt b/src/UiPath.CoreIpc/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/UiPath.CoreIpc/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/UiPath.CoreIpc/Server/Server.cs b/src/UiPath.CoreIpc/Server/Server.cs index 16b1e691..e49f8164 100644 --- a/src/UiPath.CoreIpc/Server/Server.cs +++ b/src/UiPath.CoreIpc/Server/Server.cs @@ -226,7 +226,7 @@ void Deserialize() } if (argument is Message message) { - message.Client = _client; + message.Client = _client!; } return argument; } diff --git a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt index c99c698a..a866328c 100644 --- a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt +++ b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt @@ -16,18 +16,6 @@ namespace UiPath.Ipc public System.Reflection.MethodInfo Method { get; } public bool NewConnection { get; } } - public sealed class ClientConfig : UiPath.Ipc.EndpointConfig, System.IEquatable - { - public ClientConfig() { } - public string DebugName { get; set; } - public UiPath.Ipc.ISerializer? Serializer { get; set; } - public System.Func? BeforeCall { get; init; } - public System.Func? BeforeConnect { get; init; } - public UiPath.Ipc.EndpointCollection? Callbacks { get; init; } - public Microsoft.Extensions.Logging.ILogger? Logger { get; init; } - public System.Threading.Tasks.TaskScheduler? Scheduler { get; init; } - public System.IServiceProvider? ServiceProvider { get; init; } - } public abstract class ClientTransport : System.IEquatable { protected ClientTransport() { } @@ -42,11 +30,6 @@ namespace UiPath.Ipc public void Add(System.Type contractType, object? instance) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } } - public abstract class EndpointConfig : System.IEquatable - { - protected EndpointConfig() { } - public System.TimeSpan RequestTimeout { get; init; } - } [System.Serializable] public sealed class EndpointNotFoundException : System.ArgumentOutOfRangeException { @@ -58,7 +41,7 @@ namespace UiPath.Ipc { public EndpointSettings(System.Type contractType, System.IServiceProvider serviceProvider) { } public EndpointSettings(System.Type contractType, object? serviceInstance = null) { } - public System.Func? BeforeCall { get; set; } + public System.Func? BeforeIncommingCall { get; set; } public System.Type ContractType { get; } public System.Threading.Tasks.TaskScheduler? Scheduler { get; set; } public object? ServiceInstance { get; } @@ -107,19 +90,16 @@ namespace UiPath.Ipc public static System.IO.Pipes.PipeSecurity LocalOnly(this System.IO.Pipes.PipeSecurity pipeSecurity) { } public static bool PipeExists(string pipeName, int timeout = 1) { } } - public interface ISerializer - { - object? Deserialize(string json, System.Type type); - System.Threading.Tasks.ValueTask DeserializeAsync(System.IO.Stream json, Microsoft.Extensions.Logging.ILogger? logger); - string Serialize(object? obj); - void Serialize(object? obj, System.IO.Stream stream); - } - public sealed class IpcClient + public sealed class IpcClient : UiPath.Ipc.Peer { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] public IpcClient() { } - public UiPath.Ipc.ClientConfig Config { get; init; } + public System.Func? BeforeConnect { get; set; } + public System.Func? BeforeOutgoingCall { get; set; } + public UiPath.Ipc.EndpointCollection? Callbacks { get; set; } + public string DebugName { get; set; } + public Microsoft.Extensions.Logging.ILogger? Logger { get; init; } public UiPath.Ipc.ClientTransport Transport { get; init; } public TProxy GetProxy() where TProxy : class { } @@ -133,27 +113,21 @@ namespace UiPath.Ipc public void Dispose() { } protected override object? Invoke(System.Reflection.MethodInfo? targetMethod, object?[]? args) { } } - public sealed class IpcServer : System.IAsyncDisposable + public sealed class IpcServer : UiPath.Ipc.Peer, System.IAsyncDisposable { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] public IpcServer() { } public UiPath.Ipc.EndpointCollection Endpoints { get; init; } - public System.Collections.Generic.IReadOnlyList Listeners { get; init; } - public System.Threading.Tasks.TaskScheduler? Scheduler { get; init; } - public System.IServiceProvider ServiceProvider { get; init; } + public UiPath.Ipc.ServerTransport Transport { get; init; } public System.Threading.Tasks.ValueTask DisposeAsync() { } + [System.Diagnostics.CodeAnalysis.MemberNotNull(new string[] { + "Transport", + "_accepter"})] public void Start() { } public System.Threading.Tasks.Task WaitForStart() { } public System.Threading.Tasks.Task WaitForStop() { } } - public abstract class ListenerConfig : UiPath.Ipc.EndpointConfig, System.IEquatable - { - protected ListenerConfig() { } - public System.Security.Cryptography.X509Certificates.X509Certificate? Certificate { get; init; } - public int ConcurrentAccepts { get; init; } - public byte MaxReceivedMessageSizeInMegabytes { get; init; } - } public class Message { public Message() { } @@ -170,6 +144,13 @@ namespace UiPath.Ipc public Message(TPayload payload) { } public TPayload Payload { get; } } + public abstract class Peer + { + protected Peer() { } + public System.TimeSpan RequestTimeout { get; set; } + public System.Threading.Tasks.TaskScheduler? Scheduler { get; set; } + public System.IServiceProvider? ServiceProvider { get; set; } + } [System.Serializable] public class RemoteException : System.Exception { @@ -181,84 +162,96 @@ namespace UiPath.Ipc where TException : System.Exception { } public override string ToString() { } } -} -namespace UiPath.Ipc.Extensibility -{ - public interface IListenerConfig - where TSelf : UiPath.Ipc.ListenerConfig, UiPath.Ipc.Extensibility.IListenerConfig - where TListenerState : System.IAsyncDisposable + public abstract class ServerTransport { - System.Threading.Tasks.ValueTask AwaitConnection(TListenerState listenerState, TConnectionState connectionState, System.Threading.CancellationToken ct); - TConnectionState CreateConnectionState(UiPath.Ipc.IpcServer server, TListenerState listenerState); - TListenerState CreateListenerState(UiPath.Ipc.IpcServer server); - System.Collections.Generic.IEnumerable Validate(); + protected ServerTransport() { } + public int ConcurrentAccepts { get; set; } + public byte MaxReceivedMessageSizeInMegabytes { get; set; } + public System.Security.Cryptography.X509Certificates.X509Certificate? Certificate { get; init; } + protected abstract UiPath.Ipc.ServerTransport.IServerState CreateServerState(); + protected abstract System.Collections.Generic.IEnumerable ValidateCore(); + protected static string? IsNotNull(T? propertyValue, [System.Runtime.CompilerServices.CallerArgumentExpression("propertyValue")] string? propertyName = null) { } + protected interface IServerConnectionSlot : System.IDisposable + { + System.Threading.Tasks.ValueTask AwaitConnection(System.Threading.CancellationToken ct); + } + protected interface IServerState : System.IAsyncDisposable + { + UiPath.Ipc.ServerTransport.IServerConnectionSlot CreateConnectionSlot(); + } } } namespace UiPath.Ipc.Transport.NamedPipe { - public sealed class NamedPipeListener : UiPath.Ipc.ListenerConfig, System.IEquatable, UiPath.Ipc.Extensibility.IListenerConfig + public sealed class NamedPipeClientTransport : UiPath.Ipc.ClientTransport, System.IEquatable { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] - public NamedPipeListener() { } - [Newtonsoft.Json.JsonIgnore] - public System.Action? AccessControl { get; init; } + public NamedPipeClientTransport() { } + public bool AllowImpersonation { get; init; } public string PipeName { get; init; } public string ServerName { get; init; } + public override UiPath.Ipc.IClientState CreateState() { } public override string ToString() { } + public override void Validate() { } } - public sealed class NamedPipeTransport : UiPath.Ipc.ClientTransport, System.IEquatable + public sealed class NamedPipeServerTransport : UiPath.Ipc.ServerTransport { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] - public NamedPipeTransport() { } - public bool AllowImpersonation { get; init; } + public NamedPipeServerTransport() { } + [Newtonsoft.Json.JsonIgnore] + public System.Action? AccessControl { get; init; } public string PipeName { get; init; } public string ServerName { get; init; } - public override UiPath.Ipc.IClientState CreateState() { } + protected override UiPath.Ipc.ServerTransport.IServerState CreateServerState() { } public override string ToString() { } - public override void Validate() { } + protected override System.Collections.Generic.IEnumerable ValidateCore() { } } } namespace UiPath.Ipc.Transport.Tcp { - public sealed class TcpListener : UiPath.Ipc.ListenerConfig, System.IEquatable, UiPath.Ipc.Extensibility.IListenerConfig + public sealed class TcpClientTransport : UiPath.Ipc.ClientTransport, System.IEquatable { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] - public TcpListener() { } + public TcpClientTransport() { } public System.Net.IPEndPoint EndPoint { get; init; } + public override UiPath.Ipc.IClientState CreateState() { } public override string ToString() { } + public override void Validate() { } } - public sealed class TcpTransport : UiPath.Ipc.ClientTransport, System.IEquatable + public sealed class TcpServerTransport : UiPath.Ipc.ServerTransport { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] - public TcpTransport() { } + public TcpServerTransport() { } public System.Net.IPEndPoint EndPoint { get; init; } - public override UiPath.Ipc.IClientState CreateState() { } + protected override UiPath.Ipc.ServerTransport.IServerState CreateServerState() { } public override string ToString() { } - public override void Validate() { } + protected override System.Collections.Generic.IEnumerable ValidateCore() { } } } namespace UiPath.Ipc.Transport.WebSocket { - public sealed class WebSocketListener : UiPath.Ipc.ListenerConfig, System.IEquatable, UiPath.Ipc.Extensibility.IListenerConfig + public sealed class WebSocketClientTransport : UiPath.Ipc.ClientTransport, System.IEquatable { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] - public WebSocketListener() { } - public System.Func> Accept { get; init; } + public WebSocketClientTransport() { } + public System.Uri Uri { get; init; } + public override UiPath.Ipc.IClientState CreateState() { } public override string ToString() { } + public override void Validate() { } } - public sealed class WebSocketTransport : UiPath.Ipc.ClientTransport, System.IEquatable + public sealed class WebSocketServerTransport : UiPath.Ipc.ServerTransport, System.IAsyncDisposable, System.IDisposable, UiPath.Ipc.ServerTransport.IServerConnectionSlot, UiPath.Ipc.ServerTransport.IServerState { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] - public WebSocketTransport() { } - public System.Uri Uri { get; init; } - public override UiPath.Ipc.IClientState CreateState() { } + public WebSocketServerTransport() { } + public System.Func> Accept { get; init; } + protected override UiPath.Ipc.ServerTransport.IServerState CreateServerState() { } public override string ToString() { } - public override void Validate() { } + protected override System.Collections.Generic.IEnumerable ValidateCore() { } } } \ No newline at end of file From 5e6bf605a533711bf06bfce1a1f66220a23cc45f Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Tue, 26 Nov 2024 14:45:22 +0100 Subject: [PATCH 05/13] taking shape --- src/CoreIpc.sln | 12 +- src/Playground/Playground.csproj | 2 +- src/Playground/Program.cs | 2 +- .../ServerTransportBase.cs | 26 ++++ .../BidiHttpServerTransport.cs | 124 +++++++++--------- .../Constants.cs | 2 +- .../GlobalUsings.cs | 1 + ...reIpc.Extensions.BidirectionalHttp.csproj} | 0 src/UiPath.CoreIpc.Http/GlobalUsings.cs | 1 - src/UiPath.CoreIpc/Config/ClientTransport.cs | 4 +- src/UiPath.CoreIpc/Config/IClientState.cs | 2 +- src/UiPath.CoreIpc/Config/IListenerConfig.cs | 11 -- .../Config/{Peer.cs => IpcBase.cs} | 2 +- src/UiPath.CoreIpc/Config/IpcClient.cs | 4 +- src/UiPath.CoreIpc/Config/IpcServer.cs | 4 +- src/UiPath.CoreIpc/Config/ServerTransport.cs | 14 +- src/UiPath.CoreIpc/GlobalUsings.cs | 3 +- src/UiPath.CoreIpc/Helpers/Router.cs | 2 +- src/UiPath.CoreIpc/Server/EndpointSettings.cs | 2 +- .../Server/ServerTransportRunner.cs | 10 -- .../NamedPipe/NamedPipeClientTransport.cs | 4 +- .../NamedPipe/NamedPipeServerTransport.cs | 14 +- .../Transport/Tcp/TcpClientTransport.cs | 4 +- .../Transport/Tcp/TcpServerTransport.cs | 6 +- .../WebSocket/WebSocketClientTransport.cs | 4 +- .../WebSocket/WebSocketServerTransport.cs | 32 +++-- src/UiPath.CoreIpc/UiPath.CoreIpc.csproj | 5 +- .../UiPath.Ipc.net6.0-windows.received.txt | 56 ++------ src/UiPath.Ipc.Tests/TestBase.cs | 2 +- src/UiPath.Ipc.Tests/UiPath.Ipc.Tests.csproj | 2 +- 30 files changed, 175 insertions(+), 182 deletions(-) create mode 100644 src/UiPath.CoreIpc.Extensions.Abstractions/ServerTransportBase.cs rename src/{UiPath.CoreIpc.Http => UiPath.CoreIpc.Extensions.BidirectionalHttp}/BidiHttpServerTransport.cs (66%) rename src/{UiPath.CoreIpc.Http => UiPath.CoreIpc.Extensions.BidirectionalHttp}/Constants.cs (76%) create mode 100644 src/UiPath.CoreIpc.Extensions.BidirectionalHttp/GlobalUsings.cs rename src/{UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj => UiPath.CoreIpc.Extensions.BidirectionalHttp/UiPath.CoreIpc.Extensions.BidirectionalHttp.csproj} (100%) delete mode 100644 src/UiPath.CoreIpc.Http/GlobalUsings.cs delete mode 100644 src/UiPath.CoreIpc/Config/IListenerConfig.cs rename src/UiPath.CoreIpc/Config/{Peer.cs => IpcBase.cs} (87%) delete mode 100644 src/UiPath.CoreIpc/Server/ServerTransportRunner.cs diff --git a/src/CoreIpc.sln b/src/CoreIpc.sln index 74779ed7..2d716dc9 100644 --- a/src/CoreIpc.sln +++ b/src/CoreIpc.sln @@ -13,12 +13,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playground", "Playground\Playground.csproj", "{F0365E40-DA73-4583-A363-89CBEF68A4C6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiPath.CoreIpc.Http", "UiPath.CoreIpc.Http\UiPath.CoreIpc.Http.csproj", "{8776E55A-D4EB-4C3A-8FA2-29E9A1CAE469}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UiPath.Ipc.Tests", "UiPath.Ipc.Tests\UiPath.Ipc.Tests.csproj", "{E238E183-92CF-48A6-890F-C422853D6656}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UiPath.CoreIpc.Extensions.Abstractions", "UiPath.CoreIpc.Extensions.Abstractions\UiPath.CoreIpc.Extensions.Abstractions.csproj", "{F519AE2B-88A6-482E-A6E2-B525F71F566D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UiPath.CoreIpc.Extensions.BidirectionalHttp", "UiPath.CoreIpc.Extensions.BidirectionalHttp\UiPath.CoreIpc.Extensions.BidirectionalHttp.csproj", "{CE41844B-0F4F-4A5D-9AF9-77B58BE6ECD1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,10 +33,6 @@ Global {F0365E40-DA73-4583-A363-89CBEF68A4C6}.Debug|Any CPU.Build.0 = Debug|Any CPU {F0365E40-DA73-4583-A363-89CBEF68A4C6}.Release|Any CPU.ActiveCfg = Release|Any CPU {F0365E40-DA73-4583-A363-89CBEF68A4C6}.Release|Any CPU.Build.0 = Release|Any CPU - {8776E55A-D4EB-4C3A-8FA2-29E9A1CAE469}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8776E55A-D4EB-4C3A-8FA2-29E9A1CAE469}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8776E55A-D4EB-4C3A-8FA2-29E9A1CAE469}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8776E55A-D4EB-4C3A-8FA2-29E9A1CAE469}.Release|Any CPU.Build.0 = Release|Any CPU {E238E183-92CF-48A6-890F-C422853D6656}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E238E183-92CF-48A6-890F-C422853D6656}.Debug|Any CPU.Build.0 = Debug|Any CPU {E238E183-92CF-48A6-890F-C422853D6656}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -45,6 +41,10 @@ Global {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Debug|Any CPU.Build.0 = Debug|Any CPU {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Release|Any CPU.ActiveCfg = Release|Any CPU {F519AE2B-88A6-482E-A6E2-B525F71F566D}.Release|Any CPU.Build.0 = Release|Any CPU + {CE41844B-0F4F-4A5D-9AF9-77B58BE6ECD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE41844B-0F4F-4A5D-9AF9-77B58BE6ECD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE41844B-0F4F-4A5D-9AF9-77B58BE6ECD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE41844B-0F4F-4A5D-9AF9-77B58BE6ECD1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Playground/Playground.csproj b/src/Playground/Playground.csproj index 334c100b..e6a9a7fb 100644 --- a/src/Playground/Playground.csproj +++ b/src/Playground/Playground.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Playground/Program.cs b/src/Playground/Program.cs index e373bd18..fbfeed13 100644 --- a/src/Playground/Program.cs +++ b/src/Playground/Program.cs @@ -44,7 +44,7 @@ private static async Task Main(string[] args) typeof(Contracts.IServerOperations), // DEVINE new EndpointSettings(typeof(Contracts.IServerOperations)) // ASTALALT { - BeforeIncommingCall = async (callInfo, _) => + BeforeIncomingCall = async (callInfo, _) => { Console.WriteLine($"Server: {callInfo.Method.Name}"); } diff --git a/src/UiPath.CoreIpc.Extensions.Abstractions/ServerTransportBase.cs b/src/UiPath.CoreIpc.Extensions.Abstractions/ServerTransportBase.cs new file mode 100644 index 00000000..6fd88769 --- /dev/null +++ b/src/UiPath.CoreIpc.Extensions.Abstractions/ServerTransportBase.cs @@ -0,0 +1,26 @@ +using UiPath.Ipc; + +namespace UiPath.CoreIpc.Extensions.Abstractions; + +public abstract class ServerTransportBase : ServerTransport +{ + protected abstract ServerState CreateState(); + protected new abstract IEnumerable Validate(); + + internal override IServerState CreateServerState() => CreateState(); + internal override IEnumerable ValidateCore() => Validate(); +} +public abstract class ServerState : ServerTransport.IServerState +{ + public abstract ValueTask DisposeAsync(); + public abstract ServerConnectionSlot CreateServerConnectionSlot(); + + ServerTransport.IServerConnectionSlot ServerTransport.IServerState.CreateConnectionSlot() => CreateServerConnectionSlot(); +} + +public abstract class ServerConnectionSlot : ServerTransport.IServerConnectionSlot +{ + public abstract ValueTask AwaitConnection(CancellationToken ct); + + public abstract ValueTask DisposeAsync(); +} diff --git a/src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs b/src/UiPath.CoreIpc.Extensions.BidirectionalHttp/BidiHttpServerTransport.cs similarity index 66% rename from src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs rename to src/UiPath.CoreIpc.Extensions.BidirectionalHttp/BidiHttpServerTransport.cs index 4e43ce58..7b536b1d 100644 --- a/src/UiPath.CoreIpc.Http/BidiHttpServerTransport.cs +++ b/src/UiPath.CoreIpc.Extensions.BidirectionalHttp/BidiHttpServerTransport.cs @@ -7,20 +7,20 @@ using System.Net.Http; using System.Threading.Channels; -namespace UiPath.Ipc.Http; +namespace UiPath.CoreIpc.Extensions.BidirectionalHttp; using static Constants; -public sealed partial class BidiHttpServerTransport : ServerTransport +public sealed partial class BidiHttpServerTransport : ServerTransportBase { public required Uri Uri { get; init; } - protected override IServerState CreateServerState() + protected override ServerState CreateState() => new BidiHttpServerState(this); - protected override IEnumerable ValidateCore() => []; + protected override IEnumerable Validate() => []; - private sealed class BidiHttpServerState : IServerState + private sealed class BidiHttpServerState : ServerState { private readonly CancellationTokenSource _cts = new(); private readonly HttpListener _httpListener; @@ -46,7 +46,7 @@ public BidiHttpServerState(BidiHttpServerTransport transport) _disposing = new(DisposeCore); } - public ValueTask DisposeAsync() => new(_disposing.Value); + public override ValueTask DisposeAsync() => new(_disposing.Value); private async Task DisposeCore() { @@ -110,34 +110,27 @@ bool TryAcceptContext(HttpListenerContext context, out Guid connectionId, [NotNu } } - IServerConnectionSlot IServerState.CreateConnectionSlot() => new BidiHttpServerConnectionSlot(this); + public override ServerConnectionSlot CreateServerConnectionSlot() => new BidiHttpServerConnectionSlot(this); + } - private sealed class BidiHttpServerConnectionSlot : Stream, IServerConnectionSlot, IAsyncDisposable + private sealed class BidiHttpServerConnectionSlot : ServerConnectionSlot { private readonly Pipe _pipe = new(); - - private readonly BidiHttpServerState _listenerState; - + private readonly BidiHttpServerState _serverState; + private readonly Lazy _disposing; private readonly CancellationTokenSource _cts = new(); private readonly AsyncLock _lock = new(); private (Guid connectionId, Uri reverseUri)? _connection = null; private HttpClient? _client; private Task? _processing = null; - private readonly Lazy _disposing; public BidiHttpServerConnectionSlot(BidiHttpServerState serverState) { - _listenerState = serverState; + _serverState = serverState; _disposing = new(DisposeCore); } - public -#if !NET461 - override -#endif - ValueTask DisposeAsync() => new(_disposing.Value); - private async Task DisposeCore() { _cts.Cancel(); @@ -156,7 +149,7 @@ private async Task DisposeCore() _cts.Dispose(); } - public async Task WaitForConnection(CancellationToken ct) + public override async ValueTask AwaitConnection(CancellationToken ct) { using (await _lock.LockAsync(ct)) { @@ -165,24 +158,28 @@ public async Task WaitForConnection(CancellationToken ct) throw new InvalidOperationException(); } - _connection = await _listenerState.NewConnections.ReadAsync(ct); + _connection = await _serverState.NewConnections.ReadAsync(ct); _client = new() { BaseAddress = _connection.Value.reverseUri, DefaultRequestHeaders = - { - { ConnectionIdHeader, _connection.Value.connectionId.ToString() } - } + { + { ConnectionIdHeader, _connection.Value.connectionId.ToString() } + } }; _processing = ProcessContexts(_cts.Token); + + return new Adapter(this); } } + public override ValueTask DisposeAsync() => new(_disposing.Value); + private async Task ProcessContexts(CancellationToken ct) { - var reader = _listenerState.GetConnectionChannel(_connection!.Value.connectionId); + var reader = _serverState.GetConnectionChannel(_connection!.Value.connectionId); while (await reader.WaitToReadAsync(ct)) { @@ -221,58 +218,67 @@ async Task ProcessContext(HttpListenerContext context) } } - ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken ct) + private sealed class Adapter : Stream { - throw new NotImplementedException(); - } + private readonly BidiHttpServerConnectionSlot _slot; + public Adapter(BidiHttpServerConnectionSlot slot) + { + _slot = slot; + } + public +#if !NET461 + override +#endif + ValueTask DisposeAsync() => _slot.DisposeAsync(); - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => true; + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => true; - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) - { - var memory = new Memory(buffer, offset, count); - var readResult = await _pipe.Reader.ReadAsync(ct); + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken ct) + { + var memory = new Memory(buffer, offset, count); + var readResult = await _slot._pipe.Reader.ReadAsync(ct); - var take = (int)Math.Min(readResult.Buffer.Length, memory.Length); + var take = (int)Math.Min(readResult.Buffer.Length, memory.Length); - readResult.Buffer.Slice(start: 0, length: take).CopyTo(memory.Span); - _pipe.Reader.AdvanceTo(readResult.Buffer.GetPosition(take)); + readResult.Buffer.Slice(start: 0, length: take).CopyTo(memory.Span); + _slot._pipe.Reader.AdvanceTo(readResult.Buffer.GetPosition(take)); - return take; - } + return take; + } - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct) - { - var memory = new ReadOnlyMemory(buffer, offset, count); - if (_client is null) + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken ct) { - throw new InvalidOperationException(); - } + var memory = new ReadOnlyMemory(buffer, offset, count); + if (_slot._client is null) + { + throw new InvalidOperationException(); + } - HttpContent content = + HttpContent content = #if NET461 new ByteArrayContent(memory.ToArray()); #else - new ReadOnlyMemoryContent(memory); + new ReadOnlyMemoryContent(memory); #endif - await _client.PostAsync(requestUri: "", content, ct); - } + await _slot._client.PostAsync(requestUri: "", content, ct); + } - public override Task FlushAsync(CancellationToken cancellationToken) - => Task.CompletedTask; + public override Task FlushAsync(CancellationToken cancellationToken) + => Task.CompletedTask; - public override void Flush() => throw new NotImplementedException(); - public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); - public override void SetLength(long value) => throw new NotImplementedException(); - public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); + public override void Flush() => throw new NotImplementedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); + public override void SetLength(long value) => throw new NotImplementedException(); + public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); - public override long Length => throw new NotImplementedException(); - public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public override long Length => throw new NotImplementedException(); + public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + } } } diff --git a/src/UiPath.CoreIpc.Http/Constants.cs b/src/UiPath.CoreIpc.Extensions.BidirectionalHttp/Constants.cs similarity index 76% rename from src/UiPath.CoreIpc.Http/Constants.cs rename to src/UiPath.CoreIpc.Extensions.BidirectionalHttp/Constants.cs index c04e37b7..43abe56d 100644 --- a/src/UiPath.CoreIpc.Http/Constants.cs +++ b/src/UiPath.CoreIpc.Extensions.BidirectionalHttp/Constants.cs @@ -1,4 +1,4 @@ -namespace UiPath.Ipc.Http; +namespace UiPath.CoreIpc.Extensions.BidirectionalHttp; internal static class Constants { diff --git a/src/UiPath.CoreIpc.Extensions.BidirectionalHttp/GlobalUsings.cs b/src/UiPath.CoreIpc.Extensions.BidirectionalHttp/GlobalUsings.cs new file mode 100644 index 00000000..d502d37a --- /dev/null +++ b/src/UiPath.CoreIpc.Extensions.BidirectionalHttp/GlobalUsings.cs @@ -0,0 +1 @@ +global using UiPath.CoreIpc.Extensions.Abstractions; diff --git a/src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj b/src/UiPath.CoreIpc.Extensions.BidirectionalHttp/UiPath.CoreIpc.Extensions.BidirectionalHttp.csproj similarity index 100% rename from src/UiPath.CoreIpc.Http/UiPath.CoreIpc.Http.csproj rename to src/UiPath.CoreIpc.Extensions.BidirectionalHttp/UiPath.CoreIpc.Extensions.BidirectionalHttp.csproj diff --git a/src/UiPath.CoreIpc.Http/GlobalUsings.cs b/src/UiPath.CoreIpc.Http/GlobalUsings.cs deleted file mode 100644 index a7770972..00000000 --- a/src/UiPath.CoreIpc.Http/GlobalUsings.cs +++ /dev/null @@ -1 +0,0 @@ -global using UiPath.Ipc.Extensibility; diff --git a/src/UiPath.CoreIpc/Config/ClientTransport.cs b/src/UiPath.CoreIpc/Config/ClientTransport.cs index 2e21728a..221c16e3 100644 --- a/src/UiPath.CoreIpc/Config/ClientTransport.cs +++ b/src/UiPath.CoreIpc/Config/ClientTransport.cs @@ -2,6 +2,6 @@ public abstract record ClientTransport { - public abstract IClientState CreateState(); - public abstract void Validate(); + internal abstract IClientState CreateState(); + internal abstract void Validate(); } diff --git a/src/UiPath.CoreIpc/Config/IClientState.cs b/src/UiPath.CoreIpc/Config/IClientState.cs index 74ff640c..a09230cf 100644 --- a/src/UiPath.CoreIpc/Config/IClientState.cs +++ b/src/UiPath.CoreIpc/Config/IClientState.cs @@ -1,6 +1,6 @@ namespace UiPath.Ipc; -public interface IClientState : IDisposable +internal interface IClientState : IDisposable { Stream? Network { get; } diff --git a/src/UiPath.CoreIpc/Config/IListenerConfig.cs b/src/UiPath.CoreIpc/Config/IListenerConfig.cs deleted file mode 100644 index da4732fc..00000000 --- a/src/UiPath.CoreIpc/Config/IListenerConfig.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace UiPath.Ipc.Extensibility; - -internal interface IListenerConfig - where TSelf : ServerTransport, IListenerConfig - where TListenerState : IAsyncDisposable -{ - TListenerState CreateListenerState(IpcServer server); - TConnectionState CreateConnectionState(IpcServer server, TListenerState listenerState); - ValueTask AwaitConnection(TListenerState listenerState, TConnectionState connectionState, CancellationToken ct); - IEnumerable Validate(); -} diff --git a/src/UiPath.CoreIpc/Config/Peer.cs b/src/UiPath.CoreIpc/Config/IpcBase.cs similarity index 87% rename from src/UiPath.CoreIpc/Config/Peer.cs rename to src/UiPath.CoreIpc/Config/IpcBase.cs index 4d4f7121..1c03b561 100644 --- a/src/UiPath.CoreIpc/Config/Peer.cs +++ b/src/UiPath.CoreIpc/Config/IpcBase.cs @@ -1,6 +1,6 @@ namespace UiPath.Ipc; -public abstract class Peer +public abstract class IpcBase { public TimeSpan RequestTimeout { get; set; } = Timeout.InfiniteTimeSpan; public IServiceProvider? ServiceProvider { get; set; } diff --git a/src/UiPath.CoreIpc/Config/IpcClient.cs b/src/UiPath.CoreIpc/Config/IpcClient.cs index 7bed1c05..1b95fd3a 100644 --- a/src/UiPath.CoreIpc/Config/IpcClient.cs +++ b/src/UiPath.CoreIpc/Config/IpcClient.cs @@ -2,7 +2,7 @@ namespace UiPath.Ipc; -public sealed class IpcClient : Peer, IClientConfig +public sealed class IpcClient : IpcBase, IClientConfig { public EndpointCollection? Callbacks { get; set; } @@ -63,7 +63,7 @@ internal RouterConfig CreateCallbackRouterConfig() Callbacks.OrDefault(), endpoint => endpoint with { - BeforeIncommingCall = null, // callbacks don't support BeforeCall + BeforeIncomingCall = null, // callbacks don't support BeforeCall Scheduler = endpoint.Scheduler ?? Scheduler }); } diff --git a/src/UiPath.CoreIpc/Config/IpcServer.cs b/src/UiPath.CoreIpc/Config/IpcServer.cs index e9c3d14d..da5083db 100644 --- a/src/UiPath.CoreIpc/Config/IpcServer.cs +++ b/src/UiPath.CoreIpc/Config/IpcServer.cs @@ -2,7 +2,7 @@ namespace UiPath.Ipc; -public sealed class IpcServer : Peer, IAsyncDisposable +public sealed class IpcServer : IpcBase, IAsyncDisposable { public required EndpointCollection Endpoints { get; init; } public required ServerTransport Transport { get; init; } @@ -163,7 +163,7 @@ private async Task Accept(CancellationToken ct) } catch (Exception ex) { - slot.Dispose(); + await slot.DisposeAsync(); _newConnection.OnError(ex); return; } diff --git a/src/UiPath.CoreIpc/Config/ServerTransport.cs b/src/UiPath.CoreIpc/Config/ServerTransport.cs index c63bc155..1d0c27c8 100644 --- a/src/UiPath.CoreIpc/Config/ServerTransport.cs +++ b/src/UiPath.CoreIpc/Config/ServerTransport.cs @@ -8,7 +8,9 @@ public abstract class ServerTransport public int ConcurrentAccepts { get; set; } = 5; public byte MaxReceivedMessageSizeInMegabytes { get; set; } = 2; - public X509Certificate? Certificate { get; init; } + // TODO: Will be decommissioned altogether. + internal X509Certificate? Certificate { get; init; } + internal int MaxMessageSize => MaxReceivedMessageSizeInMegabytes * 1024 * 1024; // TODO: Maybe decommission. @@ -34,12 +36,12 @@ internal async Task MaybeAuthenticate(Stream network) return sslStream; } - protected internal abstract IServerState CreateServerState(); + internal abstract IServerState CreateServerState(); internal IEnumerable Validate() => ValidateCore().Where(x => x is not null).Select(x => $"{GetType().Name}.{x}"); - protected abstract IEnumerable ValidateCore(); - protected static string? IsNotNull(T? propertyValue, [CallerArgumentExpression(nameof(propertyValue))] string? propertyName = null) + internal abstract IEnumerable ValidateCore(); + internal static string? IsNotNull(T? propertyValue, [CallerArgumentExpression(nameof(propertyValue))] string? propertyName = null) { if (propertyValue is null) { @@ -48,11 +50,11 @@ internal IEnumerable Validate() return null; } - protected internal interface IServerState : IAsyncDisposable + internal interface IServerState : IAsyncDisposable { IServerConnectionSlot CreateConnectionSlot(); } - protected internal interface IServerConnectionSlot : IDisposable + internal interface IServerConnectionSlot : IAsyncDisposable { ValueTask AwaitConnection(CancellationToken ct); } diff --git a/src/UiPath.CoreIpc/GlobalUsings.cs b/src/UiPath.CoreIpc/GlobalUsings.cs index ab295693..00369edd 100644 --- a/src/UiPath.CoreIpc/GlobalUsings.cs +++ b/src/UiPath.CoreIpc/GlobalUsings.cs @@ -1,5 +1,4 @@ -global using UiPath.Ipc.Extensibility; -global using BeforeConnectHandler = System.Func; +global using BeforeConnectHandler = System.Func; global using BeforeCallHandler = System.Func; global using InvokeDelegate = System.Func; global using Accept = System.Func>; diff --git a/src/UiPath.CoreIpc/Helpers/Router.cs b/src/UiPath.CoreIpc/Helpers/Router.cs index 26684819..7906f013 100644 --- a/src/UiPath.CoreIpc/Helpers/Router.cs +++ b/src/UiPath.CoreIpc/Helpers/Router.cs @@ -131,7 +131,7 @@ public static Route From(IServiceProvider? serviceProvider, EndpointSettings end => new Route() { Service = endpointSettings.Service.WithProvider(serviceProvider), - BeforeCall = endpointSettings.BeforeIncommingCall, + BeforeCall = endpointSettings.BeforeIncomingCall, Scheduler = endpointSettings.Scheduler.OrDefault(), LoggerFactory = serviceProvider.MaybeCreateServiceFactory(), }; diff --git a/src/UiPath.CoreIpc/Server/EndpointSettings.cs b/src/UiPath.CoreIpc/Server/EndpointSettings.cs index 3b00df73..65023197 100644 --- a/src/UiPath.CoreIpc/Server/EndpointSettings.cs +++ b/src/UiPath.CoreIpc/Server/EndpointSettings.cs @@ -5,7 +5,7 @@ public record EndpointSettings { public TaskScheduler? Scheduler { get; set; } - public BeforeCallHandler? BeforeIncommingCall { get; set; } + public BeforeCallHandler? BeforeIncomingCall { get; set; } public Type ContractType => Service.Type; public object? ServiceInstance => Service.MaybeGetInstance(); public IServiceProvider? ServiceProvider => Service.MaybeGetServiceProvider(); diff --git a/src/UiPath.CoreIpc/Server/ServerTransportRunner.cs b/src/UiPath.CoreIpc/Server/ServerTransportRunner.cs deleted file mode 100644 index bb68f927..00000000 --- a/src/UiPath.CoreIpc/Server/ServerTransportRunner.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace UiPath.Ipc; - -internal static class ServerTransportRunner -{ - public static async Task Start(ServerTransport transport) - { - var serverState = transport.CreateServerState(); - return serverState; - } -} diff --git a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeClientTransport.cs b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeClientTransport.cs index d6bd0aff..0ccfc08e 100644 --- a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeClientTransport.cs +++ b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeClientTransport.cs @@ -11,9 +11,9 @@ public sealed record NamedPipeClientTransport : ClientTransport public override string ToString() => $"ClientPipe={PipeName}"; - public override IClientState CreateState() => new NamedPipeClientState(); + internal override IClientState CreateState() => new NamedPipeClientState(); - public override void Validate() + internal override void Validate() { if (PipeName is null or "") { diff --git a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeServerTransport.cs b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeServerTransport.cs index aff79351..2319ae4c 100644 --- a/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeServerTransport.cs +++ b/src/UiPath.CoreIpc/Transport/NamedPipe/NamedPipeServerTransport.cs @@ -11,10 +11,10 @@ public sealed class NamedPipeServerTransport : ServerTransport [JsonIgnore] public AccessControlDelegate? AccessControl { get; init; } - protected internal override IServerState CreateServerState() + internal override IServerState CreateServerState() => new ServerState { Transport = this }; - protected override IEnumerable ValidateCore() + internal override IEnumerable ValidateCore() { yield return IsNotNull(PipeName); yield return IsNotNull(ServerName); @@ -69,7 +69,15 @@ NamedPipeServerStream CreateStream() public required NamedPipeServerStream Stream { get; init; } - void IDisposable.Dispose() => Stream.Dispose(); + ValueTask IAsyncDisposable.DisposeAsync() + { +#if NET461 + Stream.Dispose(); + return default; +#else + return Stream.DisposeAsync(); +#endif + } async ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken ct) { diff --git a/src/UiPath.CoreIpc/Transport/Tcp/TcpClientTransport.cs b/src/UiPath.CoreIpc/Transport/Tcp/TcpClientTransport.cs index dd7cf5a3..f82dc7f0 100644 --- a/src/UiPath.CoreIpc/Transport/Tcp/TcpClientTransport.cs +++ b/src/UiPath.CoreIpc/Transport/Tcp/TcpClientTransport.cs @@ -8,9 +8,9 @@ public sealed record TcpClientTransport : ClientTransport public override string ToString() => $"TcpClient={EndPoint}"; - public override IClientState CreateState() => new TcpClientState(); + internal override IClientState CreateState() => new TcpClientState(); - public override void Validate() + internal override void Validate() { if (EndPoint is null) { diff --git a/src/UiPath.CoreIpc/Transport/Tcp/TcpServerTransport.cs b/src/UiPath.CoreIpc/Transport/Tcp/TcpServerTransport.cs index 18bb0a5f..33c74f3d 100644 --- a/src/UiPath.CoreIpc/Transport/Tcp/TcpServerTransport.cs +++ b/src/UiPath.CoreIpc/Transport/Tcp/TcpServerTransport.cs @@ -7,14 +7,14 @@ public sealed class TcpServerTransport : ServerTransport { public required IPEndPoint EndPoint { get; init; } - protected internal override IServerState CreateServerState() + internal override IServerState CreateServerState() { var listener = new TcpListener(EndPoint); listener.Start(backlog: ConcurrentAccepts); return new ServerState() { TcpListener = listener }; } - protected override IEnumerable ValidateCore() + internal override IEnumerable ValidateCore() { yield return IsNotNull(EndPoint); } @@ -51,6 +51,6 @@ async ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken return tcpClient.GetStream(); } - void IDisposable.Dispose() { } + ValueTask IAsyncDisposable.DisposeAsync() => default; } } diff --git a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketClientTransport.cs b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketClientTransport.cs index e5b36472..6aa333f1 100644 --- a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketClientTransport.cs +++ b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketClientTransport.cs @@ -7,9 +7,9 @@ public sealed record WebSocketClientTransport : ClientTransport public required Uri Uri { get; init; } public override string ToString() => $"WebSocketClient={Uri}"; - public override IClientState CreateState() => new WebSocketClientState(); + internal override IClientState CreateState() => new WebSocketClientState(); - public override void Validate() + internal override void Validate() { if (Uri is null) { diff --git a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketServerTransport.cs b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketServerTransport.cs index 14110555..e81536c1 100644 --- a/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketServerTransport.cs +++ b/src/UiPath.CoreIpc/Transport/WebSocket/WebSocketServerTransport.cs @@ -1,25 +1,31 @@ namespace UiPath.Ipc.Transport.WebSocket; -public sealed class WebSocketServerTransport : ServerTransport, ServerTransport.IServerState, ServerTransport.IServerConnectionSlot +public sealed class WebSocketServerTransport : ServerTransport { public required Accept Accept { get; init; } - protected internal override IServerState CreateServerState() => this; + internal override IServerState CreateServerState() => new State { Transport = this }; - IServerConnectionSlot IServerState.CreateConnectionSlot() => this; - - async ValueTask IServerConnectionSlot.AwaitConnection(CancellationToken ct) - { - var webSocket = await Accept(ct); - return new WebSocketStream(webSocket); - } - ValueTask IAsyncDisposable.DisposeAsync() => default; - void IDisposable.Dispose() { } - - protected override IEnumerable ValidateCore() + internal override IEnumerable ValidateCore() { yield return IsNotNull(Accept); } public override string ToString() => nameof(WebSocketServerTransport); + + private sealed class State : IServerState, IServerConnectionSlot + { + public required WebSocketServerTransport Transport { get; init; } + + public async ValueTask AwaitConnection(CancellationToken ct) + { + var webSocket = await Transport.Accept(ct); + return new WebSocketStream(webSocket); + } + + public IServerConnectionSlot CreateConnectionSlot() => this; + + public void Dispose() { } + public ValueTask DisposeAsync() => default; + } } diff --git a/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj b/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj index d2bb74a6..781bb27e 100644 --- a/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj +++ b/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj @@ -54,7 +54,7 @@ - + @@ -65,7 +65,6 @@ - - + \ No newline at end of file diff --git a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt index a866328c..83beb956 100644 --- a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt +++ b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt @@ -1,8 +1,8 @@ [assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/UiPath/coreipc.git")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Playground")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UiPath.CoreIpc.BackCompat")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UiPath.CoreIpc.Extensions.Abstractions")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UiPath.CoreIpc.Tests")] -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UiPath.Ipc.TV")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UiPath.Ipc.Tests")] [assembly: System.Runtime.Versioning.SupportedOSPlatform("Windows7.0")] [assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName=".NET 6.0")] @@ -19,8 +19,6 @@ namespace UiPath.Ipc public abstract class ClientTransport : System.IEquatable { protected ClientTransport() { } - public abstract UiPath.Ipc.IClientState CreateState(); - public abstract void Validate(); } public class EndpointCollection : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { @@ -41,7 +39,7 @@ namespace UiPath.Ipc { public EndpointSettings(System.Type contractType, System.IServiceProvider serviceProvider) { } public EndpointSettings(System.Type contractType, object? serviceInstance = null) { } - public System.Func? BeforeIncommingCall { get; set; } + public System.Func? BeforeIncomingCall { get; set; } public System.Type ContractType { get; } public System.Threading.Tasks.TaskScheduler? Scheduler { get; set; } public object? ServiceInstance { get; } @@ -74,12 +72,6 @@ namespace UiPath.Ipc where TCallbackInterface : class; void Impersonate(System.Action action); } - public interface IClientState : System.IDisposable - { - System.IO.Stream? Network { get; } - System.Threading.Tasks.ValueTask Connect(UiPath.Ipc.IpcClient client, System.Threading.CancellationToken ct); - bool IsConnected(); - } public static class IOHelpers { public static System.IO.Pipes.PipeSecurity Allow(this System.IO.Pipes.PipeSecurity pipeSecurity, System.Security.Principal.IdentityReference sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) { } @@ -90,7 +82,14 @@ namespace UiPath.Ipc public static System.IO.Pipes.PipeSecurity LocalOnly(this System.IO.Pipes.PipeSecurity pipeSecurity) { } public static bool PipeExists(string pipeName, int timeout = 1) { } } - public sealed class IpcClient : UiPath.Ipc.Peer + public abstract class IpcBase + { + protected IpcBase() { } + public System.TimeSpan RequestTimeout { get; set; } + public System.Threading.Tasks.TaskScheduler? Scheduler { get; set; } + public System.IServiceProvider? ServiceProvider { get; set; } + } + public sealed class IpcClient : UiPath.Ipc.IpcBase { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] @@ -113,7 +112,7 @@ namespace UiPath.Ipc public void Dispose() { } protected override object? Invoke(System.Reflection.MethodInfo? targetMethod, object?[]? args) { } } - public sealed class IpcServer : UiPath.Ipc.Peer, System.IAsyncDisposable + public sealed class IpcServer : UiPath.Ipc.IpcBase, System.IAsyncDisposable { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] @@ -144,13 +143,6 @@ namespace UiPath.Ipc public Message(TPayload payload) { } public TPayload Payload { get; } } - public abstract class Peer - { - protected Peer() { } - public System.TimeSpan RequestTimeout { get; set; } - public System.Threading.Tasks.TaskScheduler? Scheduler { get; set; } - public System.IServiceProvider? ServiceProvider { get; set; } - } [System.Serializable] public class RemoteException : System.Exception { @@ -167,18 +159,6 @@ namespace UiPath.Ipc protected ServerTransport() { } public int ConcurrentAccepts { get; set; } public byte MaxReceivedMessageSizeInMegabytes { get; set; } - public System.Security.Cryptography.X509Certificates.X509Certificate? Certificate { get; init; } - protected abstract UiPath.Ipc.ServerTransport.IServerState CreateServerState(); - protected abstract System.Collections.Generic.IEnumerable ValidateCore(); - protected static string? IsNotNull(T? propertyValue, [System.Runtime.CompilerServices.CallerArgumentExpression("propertyValue")] string? propertyName = null) { } - protected interface IServerConnectionSlot : System.IDisposable - { - System.Threading.Tasks.ValueTask AwaitConnection(System.Threading.CancellationToken ct); - } - protected interface IServerState : System.IAsyncDisposable - { - UiPath.Ipc.ServerTransport.IServerConnectionSlot CreateConnectionSlot(); - } } } namespace UiPath.Ipc.Transport.NamedPipe @@ -191,9 +171,7 @@ namespace UiPath.Ipc.Transport.NamedPipe public bool AllowImpersonation { get; init; } public string PipeName { get; init; } public string ServerName { get; init; } - public override UiPath.Ipc.IClientState CreateState() { } public override string ToString() { } - public override void Validate() { } } public sealed class NamedPipeServerTransport : UiPath.Ipc.ServerTransport { @@ -204,9 +182,7 @@ namespace UiPath.Ipc.Transport.NamedPipe public System.Action? AccessControl { get; init; } public string PipeName { get; init; } public string ServerName { get; init; } - protected override UiPath.Ipc.ServerTransport.IServerState CreateServerState() { } public override string ToString() { } - protected override System.Collections.Generic.IEnumerable ValidateCore() { } } } namespace UiPath.Ipc.Transport.Tcp @@ -217,9 +193,7 @@ namespace UiPath.Ipc.Transport.Tcp "your compiler.", true)] public TcpClientTransport() { } public System.Net.IPEndPoint EndPoint { get; init; } - public override UiPath.Ipc.IClientState CreateState() { } public override string ToString() { } - public override void Validate() { } } public sealed class TcpServerTransport : UiPath.Ipc.ServerTransport { @@ -227,9 +201,7 @@ namespace UiPath.Ipc.Transport.Tcp "your compiler.", true)] public TcpServerTransport() { } public System.Net.IPEndPoint EndPoint { get; init; } - protected override UiPath.Ipc.ServerTransport.IServerState CreateServerState() { } public override string ToString() { } - protected override System.Collections.Generic.IEnumerable ValidateCore() { } } } namespace UiPath.Ipc.Transport.WebSocket @@ -240,18 +212,14 @@ namespace UiPath.Ipc.Transport.WebSocket "your compiler.", true)] public WebSocketClientTransport() { } public System.Uri Uri { get; init; } - public override UiPath.Ipc.IClientState CreateState() { } public override string ToString() { } - public override void Validate() { } } - public sealed class WebSocketServerTransport : UiPath.Ipc.ServerTransport, System.IAsyncDisposable, System.IDisposable, UiPath.Ipc.ServerTransport.IServerConnectionSlot, UiPath.Ipc.ServerTransport.IServerState + public sealed class WebSocketServerTransport : UiPath.Ipc.ServerTransport { [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] public WebSocketServerTransport() { } public System.Func> Accept { get; init; } - protected override UiPath.Ipc.ServerTransport.IServerState CreateServerState() { } public override string ToString() { } - protected override System.Collections.Generic.IEnumerable ValidateCore() { } } } \ No newline at end of file diff --git a/src/UiPath.Ipc.Tests/TestBase.cs b/src/UiPath.Ipc.Tests/TestBase.cs index 5652726f..cd15246a 100644 --- a/src/UiPath.Ipc.Tests/TestBase.cs +++ b/src/UiPath.Ipc.Tests/TestBase.cs @@ -87,7 +87,7 @@ async Task Core() var endpointSettings = new EndpointSettings(ContractType) { - BeforeIncommingCall = (callInfo, ct) => + BeforeIncomingCall = (callInfo, ct) => { _serverBeforeCalls.Add(callInfo); return _tailBeforeCall?.Invoke(callInfo, ct) ?? Task.CompletedTask; diff --git a/src/UiPath.Ipc.Tests/UiPath.Ipc.Tests.csproj b/src/UiPath.Ipc.Tests/UiPath.Ipc.Tests.csproj index 3f326d6d..6b62feb9 100644 --- a/src/UiPath.Ipc.Tests/UiPath.Ipc.Tests.csproj +++ b/src/UiPath.Ipc.Tests/UiPath.Ipc.Tests.csproj @@ -24,7 +24,7 @@ - + From 458cb319ef3e3943a55c87cfbff46a34b2cd1fd5 Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Tue, 26 Nov 2024 15:37:31 +0100 Subject: [PATCH 06/13] taking shape --- src/Playground/Program.cs | 3 ++- src/UiPath.CoreIpc/Config/IpcClient.cs | 8 +++----- src/UiPath.CoreIpc/Wire/Dtos.cs | 4 ++-- src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs | 1 - 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Playground/Program.cs b/src/Playground/Program.cs index fbfeed13..26577244 100644 --- a/src/Playground/Program.cs +++ b/src/Playground/Program.cs @@ -88,7 +88,8 @@ private static async Task Main(string[] args) PipeName = Contracts.PipeName, ServerName = ".", AllowImpersonation = false, - } + }, + DebugName = "Client1", }; var c2 = new IpcClient() diff --git a/src/UiPath.CoreIpc/Config/IpcClient.cs b/src/UiPath.CoreIpc/Config/IpcClient.cs index 1b95fd3a..942c5739 100644 --- a/src/UiPath.CoreIpc/Config/IpcClient.cs +++ b/src/UiPath.CoreIpc/Config/IpcClient.cs @@ -1,6 +1,4 @@ -using System.ComponentModel; - -namespace UiPath.Ipc; +namespace UiPath.Ipc; public sealed class IpcClient : IpcBase, IClientConfig { @@ -10,8 +8,7 @@ public sealed class IpcClient : IpcBase, IClientConfig public BeforeConnectHandler? BeforeConnect { get; set; } public BeforeCallHandler? BeforeOutgoingCall { get; set; } - [EditorBrowsable(EditorBrowsableState.Never)] - public string DebugName { get; set; } = null!; + internal string DebugName { get; set; } = null!; public required ClientTransport Transport { get; init; } @@ -26,6 +23,7 @@ private ServiceClient GetServiceClient(Type proxyType) } public TProxy GetProxy() where TProxy : class => GetServiceClient(typeof(TProxy)).GetProxy(); + // TODO: should decommission? internal void Validate() { var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false; diff --git a/src/UiPath.CoreIpc/Wire/Dtos.cs b/src/UiPath.CoreIpc/Wire/Dtos.cs index 7dfcba58..6f796e0c 100644 --- a/src/UiPath.CoreIpc/Wire/Dtos.cs +++ b/src/UiPath.CoreIpc/Wire/Dtos.cs @@ -48,7 +48,7 @@ public TResult Deserialize() return (TResult)(DownloadStream ?? IpcJsonSerializer.Instance.Deserialize(Data ?? "", typeof(TResult)))!; } } -[Serializable] + public record Error(string Message, string StackTrace, string Type, Error? InnerError) { [return: NotNullIfNotNull("exception")] @@ -64,7 +64,7 @@ public record Error(string Message, string StackTrace, string Type, Error? Inner private static string GetExceptionType(Exception exception) => (exception as RemoteException)?.Type ?? exception.GetType().FullName!; } -[Serializable] + public class RemoteException : Exception { public RemoteException(Error error) : base(error.Message, error.InnerError == null ? null : new RemoteException(error.InnerError)) diff --git a/src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs b/src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs index 037e9fc1..92604922 100644 --- a/src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs +++ b/src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs @@ -1,6 +1,5 @@ namespace UiPath.Ipc; -[Serializable] public sealed class EndpointNotFoundException : ArgumentOutOfRangeException { public string ServerDebugName { get; } From 426238271f839077a9c4a898c16c8b19f42fb8be Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Tue, 26 Nov 2024 15:44:56 +0100 Subject: [PATCH 07/13] taking shape --- src/UiPath.CoreIpc/Helpers/Helpers.cs | 3 +++ src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/UiPath.CoreIpc/Helpers/Helpers.cs b/src/UiPath.CoreIpc/Helpers/Helpers.cs index abbfedf9..926cdb00 100644 --- a/src/UiPath.CoreIpc/Helpers/Helpers.cs +++ b/src/UiPath.CoreIpc/Helpers/Helpers.cs @@ -1,5 +1,6 @@ using Microsoft.IO; using System.Collections.ObjectModel; +using System.ComponentModel; using System.IO.Pipes; using System.Runtime.InteropServices; using System.Security.AccessControl; @@ -136,6 +137,8 @@ public static PipeSecurity AllowCurrentUser(this PipeSecurity pipeSecurity, bool return pipeSecurity; } + [Browsable(false)] + [EditorBrowsable( EditorBrowsableState.Never)] public static bool PipeExists(string pipeName, int timeout = 1) { try diff --git a/src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs b/src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs index 92604922..de6ba869 100644 --- a/src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs +++ b/src/UiPath.CoreIpc/Wire/EndpointNotFoundException.cs @@ -1,11 +1,11 @@ namespace UiPath.Ipc; -public sealed class EndpointNotFoundException : ArgumentOutOfRangeException +public sealed class EndpointNotFoundException : ArgumentException { public string ServerDebugName { get; } public string EndpointName { get; } - public EndpointNotFoundException(string paramName, string serverDebugName, string endpointName) + internal EndpointNotFoundException(string paramName, string serverDebugName, string endpointName) : base(paramName, FormatMessage(serverDebugName, endpointName)) { ServerDebugName = serverDebugName; From 1298356cbdd8a1f8e718548aa69c8f069f72bd4e Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Tue, 26 Nov 2024 16:00:44 +0100 Subject: [PATCH 08/13] taking shape --- .../report/UiPath.Ipc.net6.0-windows.received.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt index 83beb956..8cfda51b 100644 --- a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt +++ b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt @@ -28,10 +28,8 @@ namespace UiPath.Ipc public void Add(System.Type contractType, object? instance) { } public System.Collections.Generic.IEnumerator GetEnumerator() { } } - [System.Serializable] - public sealed class EndpointNotFoundException : System.ArgumentOutOfRangeException + public sealed class EndpointNotFoundException : System.ArgumentException { - public EndpointNotFoundException(string paramName, string serverDebugName, string endpointName) { } public string EndpointName { get; } public string ServerDebugName { get; } } @@ -54,7 +52,6 @@ namespace UiPath.Ipc public EndpointSettings(TContract? serviceInstance = null) { } public override UiPath.Ipc.EndpointSettings WithServiceProvider(System.IServiceProvider? serviceProvider) { } } - [System.Serializable] public class Error : System.IEquatable { public Error(string Message, string StackTrace, string Type, UiPath.Ipc.Error? InnerError) { } @@ -80,6 +77,7 @@ namespace UiPath.Ipc public static System.IO.Pipes.PipeSecurity Deny(this System.IO.Pipes.PipeSecurity pipeSecurity, System.Security.Principal.IdentityReference sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) { } public static System.IO.Pipes.PipeSecurity Deny(this System.IO.Pipes.PipeSecurity pipeSecurity, System.Security.Principal.WellKnownSidType sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) { } public static System.IO.Pipes.PipeSecurity LocalOnly(this System.IO.Pipes.PipeSecurity pipeSecurity) { } + [System.ComponentModel.Browsable(false)] public static bool PipeExists(string pipeName, int timeout = 1) { } } public abstract class IpcBase @@ -97,7 +95,6 @@ namespace UiPath.Ipc public System.Func? BeforeConnect { get; set; } public System.Func? BeforeOutgoingCall { get; set; } public UiPath.Ipc.EndpointCollection? Callbacks { get; set; } - public string DebugName { get; set; } public Microsoft.Extensions.Logging.ILogger? Logger { get; init; } public UiPath.Ipc.ClientTransport Transport { get; init; } public TProxy GetProxy() @@ -143,7 +140,6 @@ namespace UiPath.Ipc public Message(TPayload payload) { } public TPayload Payload { get; } } - [System.Serializable] public class RemoteException : System.Exception { public RemoteException(UiPath.Ipc.Error error) { } From 780752d8f04e3c9c61cada69748948b9475f12f1 Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Wed, 27 Nov 2024 12:40:21 +0100 Subject: [PATCH 09/13] taking shape --- src/UiPath.CoreIpc/Config/EndpointCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UiPath.CoreIpc/Config/EndpointCollection.cs b/src/UiPath.CoreIpc/Config/EndpointCollection.cs index 8174056f..ac13caa0 100644 --- a/src/UiPath.CoreIpc/Config/EndpointCollection.cs +++ b/src/UiPath.CoreIpc/Config/EndpointCollection.cs @@ -2,7 +2,7 @@ namespace UiPath.Ipc; -public class EndpointCollection : IEnumerable, IEnumerable +public class EndpointCollection : IEnumerable { internal readonly Dictionary Endpoints = new(); From 99b8c7ca229f43534bcd32813d7fa381d492c42e Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Wed, 27 Nov 2024 22:46:01 +0100 Subject: [PATCH 10/13] rename Endpoint.. to Contract.. - simplifications --- src/Playground/Program.cs | 2 +- src/UiPath.CoreIpc/Client/ServiceClient.cs | 1 + src/UiPath.CoreIpc/Config/ClientConfig.cs | 16 -- src/UiPath.CoreIpc/Config/ClientTransport.cs | 3 + ...intCollection.cs => ContractCollection.cs} | 13 +- src/UiPath.CoreIpc/Config/IpcClient.cs | 28 +-- src/UiPath.CoreIpc/Config/IpcServer.cs | 8 +- src/UiPath.CoreIpc/Config/ServerTransport.cs | 2 + src/UiPath.CoreIpc/GlobalUsings.cs | 2 +- .../Helpers/DefaultsExtensions.cs | 2 +- src/UiPath.CoreIpc/Helpers/Router.cs | 6 +- src/UiPath.CoreIpc/PublicAPI.Shipped.txt | 177 ------------------ src/UiPath.CoreIpc/PublicAPI.Unshipped.txt | 1 - src/UiPath.CoreIpc/Server/ContractSettings.cs | 44 +++++ src/UiPath.CoreIpc/Server/EndpointSettings.cs | 58 ------ src/UiPath.CoreIpc/UiPath.CoreIpc.csproj | 8 - .../UiPath.Ipc.net6.0-windows.received.txt | 44 ++--- src/UiPath.Ipc.Tests/ComputingTests.cs | 2 +- src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs | 2 +- src/UiPath.Ipc.Tests/RobotTests.cs | 2 +- .../RobotTestsOverNamedPipes.cs | 16 +- src/UiPath.Ipc.Tests/TestBase.cs | 6 +- 22 files changed, 101 insertions(+), 342 deletions(-) delete mode 100644 src/UiPath.CoreIpc/Config/ClientConfig.cs rename src/UiPath.CoreIpc/Config/{EndpointCollection.cs => ContractCollection.cs} (50%) delete mode 100644 src/UiPath.CoreIpc/PublicAPI.Shipped.txt delete mode 100644 src/UiPath.CoreIpc/PublicAPI.Unshipped.txt create mode 100644 src/UiPath.CoreIpc/Server/ContractSettings.cs delete mode 100644 src/UiPath.CoreIpc/Server/EndpointSettings.cs diff --git a/src/Playground/Program.cs b/src/Playground/Program.cs index 26577244..45d6453a 100644 --- a/src/Playground/Program.cs +++ b/src/Playground/Program.cs @@ -42,7 +42,7 @@ private static async Task Main(string[] args) Endpoints = new() { typeof(Contracts.IServerOperations), // DEVINE - new EndpointSettings(typeof(Contracts.IServerOperations)) // ASTALALT + new ContractSettings(typeof(Contracts.IServerOperations)) // ASTALALT { BeforeIncomingCall = async (callInfo, _) => { diff --git a/src/UiPath.CoreIpc/Client/ServiceClient.cs b/src/UiPath.CoreIpc/Client/ServiceClient.cs index 95ef34c3..bdde0ae7 100644 --- a/src/UiPath.CoreIpc/Client/ServiceClient.cs +++ b/src/UiPath.CoreIpc/Client/ServiceClient.cs @@ -183,6 +183,7 @@ private Connection? LatestConnection public ServiceClientProper(IpcClient client, Type interfaceType) : base(interfaceType) { _client = client; + client.Transport.Validate(); _clientState = client.Transport.CreateState(); } diff --git a/src/UiPath.CoreIpc/Config/ClientConfig.cs b/src/UiPath.CoreIpc/Config/ClientConfig.cs deleted file mode 100644 index 6c05c192..00000000 --- a/src/UiPath.CoreIpc/Config/ClientConfig.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace UiPath.Ipc; - -public sealed class ClientConfig : Peer, IServiceClientConfig -{ - public BeforeCallHandler? BeforeCall { get; init; } - - internal void Validate() - { - var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false; - - if (haveDeferredInjectedCallbacks && ServiceProvider is null) - { - throw new InvalidOperationException("ServiceProvider is required when you register injectable callbacks. Consider registering a callback instance."); - } - } -} diff --git a/src/UiPath.CoreIpc/Config/ClientTransport.cs b/src/UiPath.CoreIpc/Config/ClientTransport.cs index 221c16e3..1ef3942d 100644 --- a/src/UiPath.CoreIpc/Config/ClientTransport.cs +++ b/src/UiPath.CoreIpc/Config/ClientTransport.cs @@ -2,6 +2,9 @@ public abstract record ClientTransport { + private protected ClientTransport() { } + internal abstract IClientState CreateState(); + internal abstract void Validate(); } diff --git a/src/UiPath.CoreIpc/Config/EndpointCollection.cs b/src/UiPath.CoreIpc/Config/ContractCollection.cs similarity index 50% rename from src/UiPath.CoreIpc/Config/EndpointCollection.cs rename to src/UiPath.CoreIpc/Config/ContractCollection.cs index ac13caa0..0e1b3252 100644 --- a/src/UiPath.CoreIpc/Config/EndpointCollection.cs +++ b/src/UiPath.CoreIpc/Config/ContractCollection.cs @@ -2,20 +2,19 @@ namespace UiPath.Ipc; -public class EndpointCollection : IEnumerable +public class ContractCollection : IEnumerable { - internal readonly Dictionary Endpoints = new(); + internal readonly Dictionary Endpoints = new(); - public void Add(Type type) => Add(type, instance: null); - public void Add(Type contractType, object? instance) => Add(new EndpointSettings(contractType, instance)); - public void Add(EndpointSettings endpointSettings) + public void Add(Type contractType) => Add(contractType, instance: null); + public void Add(Type contractType, object? instance) => Add(new ContractSettings(contractType, instance)); + public void Add(ContractSettings endpointSettings) { if (endpointSettings is null) throw new ArgumentNullException(nameof(endpointSettings)); - // endpointSettings.Validate(); Endpoints[endpointSettings.Service.Type] = endpointSettings; } - public IEnumerator GetEnumerator() => Endpoints.Values.GetEnumerator(); + public IEnumerator GetEnumerator() => Endpoints.Values.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/src/UiPath.CoreIpc/Config/IpcClient.cs b/src/UiPath.CoreIpc/Config/IpcClient.cs index 942c5739..79ae4220 100644 --- a/src/UiPath.CoreIpc/Config/IpcClient.cs +++ b/src/UiPath.CoreIpc/Config/IpcClient.cs @@ -2,7 +2,7 @@ public sealed class IpcClient : IpcBase, IClientConfig { - public EndpointCollection? Callbacks { get; set; } + public ContractCollection? Callbacks { get; set; } public ILogger? Logger { get; init; } public BeforeConnectHandler? BeforeConnect { get; set; } @@ -23,24 +23,6 @@ private ServiceClient GetServiceClient(Type proxyType) } public TProxy GetProxy() where TProxy : class => GetServiceClient(typeof(TProxy)).GetProxy(); - // TODO: should decommission? - internal void Validate() - { - var haveDeferredInjectedCallbacks = Callbacks?.Any(x => x.Service.MaybeGetServiceProvider() is null && x.Service.MaybeGetInstance() is null) ?? false; - - if (haveDeferredInjectedCallbacks && ServiceProvider is null) - { - throw new InvalidOperationException("ServiceProvider is required when you register injectable callbacks. Consider registering a callback instance."); - } - - if (Transport is null) - { - throw new InvalidOperationException($"{Transport} is required."); - } - - Transport.Validate(); - } - internal ILogger? GetLogger(string name) { if (Logger is not null) @@ -59,9 +41,11 @@ internal void Validate() internal RouterConfig CreateCallbackRouterConfig() => RouterConfig.From( Callbacks.OrDefault(), - endpoint => endpoint with + endpoint => { - BeforeIncomingCall = null, // callbacks don't support BeforeCall - Scheduler = endpoint.Scheduler ?? Scheduler + var clone = new ContractSettings(endpoint); + clone.BeforeIncomingCall = null; // callbacks don't support BeforeIncomingCall + clone.Scheduler ??= Scheduler; + return clone; }); } diff --git a/src/UiPath.CoreIpc/Config/IpcServer.cs b/src/UiPath.CoreIpc/Config/IpcServer.cs index da5083db..0fbc3d40 100644 --- a/src/UiPath.CoreIpc/Config/IpcServer.cs +++ b/src/UiPath.CoreIpc/Config/IpcServer.cs @@ -4,7 +4,7 @@ namespace UiPath.Ipc; public sealed class IpcServer : IpcBase, IAsyncDisposable { - public required EndpointCollection Endpoints { get; init; } + public required ContractCollection Endpoints { get; init; } public required ServerTransport Transport { get; init; } private readonly object _lock = new(); @@ -87,9 +87,11 @@ private void OnNewConnectionError(Exception ex) internal RouterConfig CreateRouterConfig(IpcServer server) => RouterConfig.From( server.Endpoints, - endpoint => endpoint with + endpoint => { - Scheduler = endpoint.Scheduler ?? server.Scheduler + var clone = new ContractSettings(endpoint); + clone.Scheduler ??= server.Scheduler; + return clone; }); private sealed class ObserverAdapter : IObserver diff --git a/src/UiPath.CoreIpc/Config/ServerTransport.cs b/src/UiPath.CoreIpc/Config/ServerTransport.cs index 1d0c27c8..8c66a7e7 100644 --- a/src/UiPath.CoreIpc/Config/ServerTransport.cs +++ b/src/UiPath.CoreIpc/Config/ServerTransport.cs @@ -5,6 +5,8 @@ namespace UiPath.Ipc; public abstract class ServerTransport { + private protected ServerTransport() { } + public int ConcurrentAccepts { get; set; } = 5; public byte MaxReceivedMessageSizeInMegabytes { get; set; } = 2; diff --git a/src/UiPath.CoreIpc/GlobalUsings.cs b/src/UiPath.CoreIpc/GlobalUsings.cs index 00369edd..4fd94c5b 100644 --- a/src/UiPath.CoreIpc/GlobalUsings.cs +++ b/src/UiPath.CoreIpc/GlobalUsings.cs @@ -2,5 +2,5 @@ global using BeforeCallHandler = System.Func; global using InvokeDelegate = System.Func; global using Accept = System.Func>; -global using ContractToSettingsMap = System.Collections.Generic.Dictionary; +global using ContractToSettingsMap = System.Collections.Generic.Dictionary; global using AccessControlDelegate = System.Action; diff --git a/src/UiPath.CoreIpc/Helpers/DefaultsExtensions.cs b/src/UiPath.CoreIpc/Helpers/DefaultsExtensions.cs index 4b6d9ca1..82ebf122 100644 --- a/src/UiPath.CoreIpc/Helpers/DefaultsExtensions.cs +++ b/src/UiPath.CoreIpc/Helpers/DefaultsExtensions.cs @@ -10,7 +10,7 @@ internal static class DefaultsExtensions public static BeforeCallHandler OrDefault(this BeforeCallHandler? beforeCallHandler) => beforeCallHandler ?? DefaultBeforeCallHandler; public static TaskScheduler OrDefault(this TaskScheduler? scheduler) => scheduler ?? TaskScheduler.Default; public static ContractToSettingsMap OrDefault(this ContractToSettingsMap? map) => map ?? EmptyContractToSettingsMap; - public static EndpointCollection OrDefault(this EndpointCollection? endpoints) => endpoints ?? new(); + public static ContractCollection OrDefault(this ContractCollection? endpoints) => endpoints ?? new(); public static Func? MaybeCreateServiceFactory(this IServiceProvider? serviceProvider) where T : class { diff --git a/src/UiPath.CoreIpc/Helpers/Router.cs b/src/UiPath.CoreIpc/Helpers/Router.cs index 7906f013..9a023c46 100644 --- a/src/UiPath.CoreIpc/Helpers/Router.cs +++ b/src/UiPath.CoreIpc/Helpers/Router.cs @@ -1,8 +1,8 @@ namespace UiPath.Ipc; -internal readonly record struct RouterConfig(IReadOnlyDictionary Endpoints) +internal readonly record struct RouterConfig(IReadOnlyDictionary Endpoints) { - public static RouterConfig From(EndpointCollection endpoints, Func transform) + public static RouterConfig From(ContractCollection endpoints, Func transform) { ContractToSettingsMap nameToEndpoint = []; @@ -127,7 +127,7 @@ public override ServiceFactory WithProvider(IServiceProvider? serviceProvider) internal readonly struct Route { - public static Route From(IServiceProvider? serviceProvider, EndpointSettings endpointSettings) + public static Route From(IServiceProvider? serviceProvider, ContractSettings endpointSettings) => new Route() { Service = endpointSettings.Service.WithProvider(serviceProvider), diff --git a/src/UiPath.CoreIpc/PublicAPI.Shipped.txt b/src/UiPath.CoreIpc/PublicAPI.Shipped.txt deleted file mode 100644 index 3a9b8f82..00000000 --- a/src/UiPath.CoreIpc/PublicAPI.Shipped.txt +++ /dev/null @@ -1,177 +0,0 @@ -#nullable enable -abstract UiPath.Ipc.ClientTransport.CreateState() -> UiPath.Ipc.IClientState! -abstract UiPath.Ipc.ClientTransport.Validate() -> void -abstract UiPath.Ipc.ServerTransport.CreateServerState() -> UiPath.Ipc.ServerTransport.IServerState! -abstract UiPath.Ipc.ServerTransport.ValidateCore() -> System.Collections.Generic.IEnumerable! -override UiPath.Ipc.EndpointSettings.WithServiceProvider(System.IServiceProvider? serviceProvider) -> UiPath.Ipc.EndpointSettings! -override UiPath.Ipc.Error.ToString() -> string! -override UiPath.Ipc.IpcProxy.Invoke(System.Reflection.MethodInfo? targetMethod, object?[]? args) -> object? -override UiPath.Ipc.RemoteException.StackTrace.get -> string! -override UiPath.Ipc.RemoteException.ToString() -> string! -override UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.CreateState() -> UiPath.Ipc.IClientState! -override UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.ToString() -> string! -override UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.Validate() -> void -override UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.ToString() -> string! -override UiPath.Ipc.Transport.Tcp.TcpClientTransport.CreateState() -> UiPath.Ipc.IClientState! -override UiPath.Ipc.Transport.Tcp.TcpClientTransport.ToString() -> string! -override UiPath.Ipc.Transport.Tcp.TcpClientTransport.Validate() -> void -override UiPath.Ipc.Transport.Tcp.TcpServerTransport.ToString() -> string! -override UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.CreateState() -> UiPath.Ipc.IClientState! -override UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.ToString() -> string! -override UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.Validate() -> void -override UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport.ToString() -> string! -static UiPath.Ipc.Error.FromException(System.Exception? exception) -> UiPath.Ipc.Error? -static UiPath.Ipc.IOHelpers.Allow(this System.IO.Pipes.PipeSecurity! pipeSecurity, System.Security.Principal.IdentityReference! sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) -> System.IO.Pipes.PipeSecurity! -static UiPath.Ipc.IOHelpers.Allow(this System.IO.Pipes.PipeSecurity! pipeSecurity, System.Security.Principal.WellKnownSidType sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) -> System.IO.Pipes.PipeSecurity! -static UiPath.Ipc.IOHelpers.AllowCurrentUser(this System.IO.Pipes.PipeSecurity! pipeSecurity, bool onlyNonAdmin = false) -> System.IO.Pipes.PipeSecurity! -static UiPath.Ipc.IOHelpers.Deny(this System.IO.Pipes.PipeSecurity! pipeSecurity, System.Security.Principal.IdentityReference! sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) -> System.IO.Pipes.PipeSecurity! -static UiPath.Ipc.IOHelpers.Deny(this System.IO.Pipes.PipeSecurity! pipeSecurity, System.Security.Principal.WellKnownSidType sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) -> System.IO.Pipes.PipeSecurity! -static UiPath.Ipc.IOHelpers.LocalOnly(this System.IO.Pipes.PipeSecurity! pipeSecurity) -> System.IO.Pipes.PipeSecurity! -static UiPath.Ipc.IOHelpers.PipeExists(string! pipeName, int timeout = 1) -> bool -static UiPath.Ipc.ServerTransport.IsNotNull(T? propertyValue, string? propertyName = null) -> string? -UiPath.Ipc.CallInfo -UiPath.Ipc.CallInfo.Arguments.get -> object?[]! -UiPath.Ipc.CallInfo.CallInfo() -> void -UiPath.Ipc.CallInfo.CallInfo(bool newConnection, System.Reflection.MethodInfo! method, object?[]! arguments) -> void -UiPath.Ipc.CallInfo.Method.get -> System.Reflection.MethodInfo! -UiPath.Ipc.CallInfo.NewConnection.get -> bool -UiPath.Ipc.ClientTransport -UiPath.Ipc.EndpointCollection -UiPath.Ipc.EndpointCollection.Add(System.Type! contractType, object? instance) -> void -UiPath.Ipc.EndpointCollection.Add(System.Type! type) -> void -UiPath.Ipc.EndpointCollection.Add(UiPath.Ipc.EndpointSettings! endpointSettings) -> void -UiPath.Ipc.EndpointCollection.EndpointCollection() -> void -UiPath.Ipc.EndpointCollection.GetEnumerator() -> System.Collections.Generic.IEnumerator! -UiPath.Ipc.EndpointNotFoundException -UiPath.Ipc.EndpointNotFoundException.EndpointName.get -> string! -UiPath.Ipc.EndpointNotFoundException.EndpointNotFoundException(string! paramName, string! serverDebugName, string! endpointName) -> void -UiPath.Ipc.EndpointNotFoundException.ServerDebugName.get -> string! -UiPath.Ipc.EndpointSettings -UiPath.Ipc.EndpointSettings.BeforeIncommingCall.get -> System.Func? -UiPath.Ipc.EndpointSettings.BeforeIncommingCall.set -> void -UiPath.Ipc.EndpointSettings.ContractType.get -> System.Type! -UiPath.Ipc.EndpointSettings.EndpointSettings(System.Type! contractType, object? serviceInstance = null) -> void -UiPath.Ipc.EndpointSettings.EndpointSettings(System.Type! contractType, System.IServiceProvider! serviceProvider) -> void -UiPath.Ipc.EndpointSettings.Scheduler.get -> System.Threading.Tasks.TaskScheduler? -UiPath.Ipc.EndpointSettings.Scheduler.set -> void -UiPath.Ipc.EndpointSettings.ServiceInstance.get -> object? -UiPath.Ipc.EndpointSettings.ServiceProvider.get -> System.IServiceProvider? -UiPath.Ipc.EndpointSettings.Validate() -> void -UiPath.Ipc.EndpointSettings -UiPath.Ipc.EndpointSettings.EndpointSettings(System.IServiceProvider! serviceProvider) -> void -UiPath.Ipc.EndpointSettings.EndpointSettings(TContract? serviceInstance = null) -> void -UiPath.Ipc.Error -UiPath.Ipc.Error.Error(string! Message, string! StackTrace, string! Type, UiPath.Ipc.Error? InnerError) -> void -UiPath.Ipc.Error.InnerError.get -> UiPath.Ipc.Error? -UiPath.Ipc.Error.InnerError.init -> void -UiPath.Ipc.Error.Message.get -> string! -UiPath.Ipc.Error.Message.init -> void -UiPath.Ipc.Error.StackTrace.get -> string! -UiPath.Ipc.Error.StackTrace.init -> void -UiPath.Ipc.Error.Type.get -> string! -UiPath.Ipc.Error.Type.init -> void -UiPath.Ipc.IClient -UiPath.Ipc.IClient.GetCallback() -> TCallbackInterface! -UiPath.Ipc.IClient.Impersonate(System.Action! action) -> void -UiPath.Ipc.IClientState -UiPath.Ipc.IClientState.Connect(UiPath.Ipc.IpcClient! client, System.Threading.CancellationToken ct) -> System.Threading.Tasks.ValueTask -UiPath.Ipc.IClientState.IsConnected() -> bool -UiPath.Ipc.IClientState.Network.get -> System.IO.Stream? -UiPath.Ipc.IOHelpers -UiPath.Ipc.IpcClient -UiPath.Ipc.IpcClient.BeforeConnect.get -> System.Func? -UiPath.Ipc.IpcClient.BeforeConnect.set -> void -UiPath.Ipc.IpcClient.BeforeOutgoingCall.get -> System.Func? -UiPath.Ipc.IpcClient.BeforeOutgoingCall.set -> void -UiPath.Ipc.IpcClient.Callbacks.get -> UiPath.Ipc.EndpointCollection? -UiPath.Ipc.IpcClient.Callbacks.set -> void -UiPath.Ipc.IpcClient.DebugName.get -> string! -UiPath.Ipc.IpcClient.DebugName.set -> void -UiPath.Ipc.IpcClient.GetProxy() -> TProxy! -UiPath.Ipc.IpcClient.IpcClient() -> void -UiPath.Ipc.IpcClient.Logger.get -> Microsoft.Extensions.Logging.ILogger? -UiPath.Ipc.IpcClient.Logger.init -> void -UiPath.Ipc.IpcClient.Transport.get -> UiPath.Ipc.ClientTransport! -UiPath.Ipc.IpcClient.Transport.init -> void -UiPath.Ipc.IpcProxy -UiPath.Ipc.IpcProxy.CloseConnection() -> System.Threading.Tasks.ValueTask -UiPath.Ipc.IpcProxy.ConnectionClosed -> System.EventHandler! -UiPath.Ipc.IpcProxy.Dispose() -> void -UiPath.Ipc.IpcProxy.IpcProxy() -> void -UiPath.Ipc.IpcProxy.Network.get -> System.IO.Stream? -UiPath.Ipc.IpcServer -UiPath.Ipc.IpcServer.DisposeAsync() -> System.Threading.Tasks.ValueTask -UiPath.Ipc.IpcServer.Endpoints.get -> UiPath.Ipc.EndpointCollection! -UiPath.Ipc.IpcServer.Endpoints.init -> void -UiPath.Ipc.IpcServer.IpcServer() -> void -UiPath.Ipc.IpcServer.Start() -> void -UiPath.Ipc.IpcServer.Transport.get -> UiPath.Ipc.ServerTransport! -UiPath.Ipc.IpcServer.Transport.init -> void -UiPath.Ipc.IpcServer.WaitForStart() -> System.Threading.Tasks.Task! -UiPath.Ipc.IpcServer.WaitForStop() -> System.Threading.Tasks.Task! -UiPath.Ipc.Message -UiPath.Ipc.Message.Client.get -> UiPath.Ipc.IClient! -UiPath.Ipc.Message.Client.set -> void -UiPath.Ipc.Message.GetCallback() -> TCallbackInterface! -UiPath.Ipc.Message.ImpersonateClient(System.Action! action) -> void -UiPath.Ipc.Message.Message() -> void -UiPath.Ipc.Message.RequestTimeout.get -> System.TimeSpan -UiPath.Ipc.Message.RequestTimeout.set -> void -UiPath.Ipc.Message -UiPath.Ipc.Message.Message(TPayload payload) -> void -UiPath.Ipc.Message.Payload.get -> TPayload -UiPath.Ipc.Peer -UiPath.Ipc.Peer.Peer() -> void -UiPath.Ipc.Peer.RequestTimeout.get -> System.TimeSpan -UiPath.Ipc.Peer.RequestTimeout.set -> void -UiPath.Ipc.Peer.Scheduler.get -> System.Threading.Tasks.TaskScheduler? -UiPath.Ipc.Peer.Scheduler.set -> void -UiPath.Ipc.Peer.ServiceProvider.get -> System.IServiceProvider? -UiPath.Ipc.Peer.ServiceProvider.set -> void -UiPath.Ipc.RemoteException -UiPath.Ipc.RemoteException.InnerException.get -> UiPath.Ipc.RemoteException? -UiPath.Ipc.RemoteException.Is() -> bool -UiPath.Ipc.RemoteException.RemoteException(UiPath.Ipc.Error! error) -> void -UiPath.Ipc.RemoteException.Type.get -> string! -UiPath.Ipc.ServerTransport -UiPath.Ipc.ServerTransport.Certificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate? -UiPath.Ipc.ServerTransport.Certificate.init -> void -UiPath.Ipc.ServerTransport.ConcurrentAccepts.get -> int -UiPath.Ipc.ServerTransport.ConcurrentAccepts.set -> void -UiPath.Ipc.ServerTransport.IServerConnectionSlot -UiPath.Ipc.ServerTransport.IServerConnectionSlot.AwaitConnection(System.Threading.CancellationToken ct) -> System.Threading.Tasks.ValueTask -UiPath.Ipc.ServerTransport.IServerState -UiPath.Ipc.ServerTransport.IServerState.CreateConnectionSlot() -> UiPath.Ipc.ServerTransport.IServerConnectionSlot! -UiPath.Ipc.ServerTransport.MaxReceivedMessageSizeInMegabytes.get -> byte -UiPath.Ipc.ServerTransport.MaxReceivedMessageSizeInMegabytes.set -> void -UiPath.Ipc.ServerTransport.ServerTransport() -> void -UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport -UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.AllowImpersonation.get -> bool -UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.AllowImpersonation.init -> void -UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.PipeName.get -> string! -UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.PipeName.init -> void -UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.ServerName.get -> string! -UiPath.Ipc.Transport.NamedPipe.NamedPipeClientTransport.ServerName.init -> void -UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport -UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.AccessControl.get -> System.Action? -UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.AccessControl.init -> void -UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.NamedPipeServerTransport() -> void -UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.PipeName.get -> string! -UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.PipeName.init -> void -UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.ServerName.get -> string! -UiPath.Ipc.Transport.NamedPipe.NamedPipeServerTransport.ServerName.init -> void -UiPath.Ipc.Transport.Tcp.TcpClientTransport -UiPath.Ipc.Transport.Tcp.TcpClientTransport.EndPoint.get -> System.Net.IPEndPoint! -UiPath.Ipc.Transport.Tcp.TcpClientTransport.EndPoint.init -> void -UiPath.Ipc.Transport.Tcp.TcpServerTransport -UiPath.Ipc.Transport.Tcp.TcpServerTransport.EndPoint.get -> System.Net.IPEndPoint! -UiPath.Ipc.Transport.Tcp.TcpServerTransport.EndPoint.init -> void -UiPath.Ipc.Transport.Tcp.TcpServerTransport.TcpServerTransport() -> void -UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport -UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.Uri.get -> System.Uri! -UiPath.Ipc.Transport.WebSocket.WebSocketClientTransport.Uri.init -> void -UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport -UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport.Accept.get -> System.Func!>! -UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport.Accept.init -> void -UiPath.Ipc.Transport.WebSocket.WebSocketServerTransport.WebSocketServerTransport() -> void -virtual UiPath.Ipc.EndpointSettings.WithServiceProvider(System.IServiceProvider? serviceProvider) -> UiPath.Ipc.EndpointSettings! \ No newline at end of file diff --git a/src/UiPath.CoreIpc/PublicAPI.Unshipped.txt b/src/UiPath.CoreIpc/PublicAPI.Unshipped.txt deleted file mode 100644 index 5f282702..00000000 --- a/src/UiPath.CoreIpc/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/UiPath.CoreIpc/Server/ContractSettings.cs b/src/UiPath.CoreIpc/Server/ContractSettings.cs new file mode 100644 index 00000000..45ba6eda --- /dev/null +++ b/src/UiPath.CoreIpc/Server/ContractSettings.cs @@ -0,0 +1,44 @@ +namespace UiPath.Ipc; + +using System; + +public sealed class ContractSettings +{ + public TaskScheduler? Scheduler { get; set; } + public BeforeCallHandler? BeforeIncomingCall { get; set; } + internal ServiceFactory Service { get; } + + internal Type ContractType => Service.Type; + internal object? ServiceInstance => Service.MaybeGetInstance(); + internal IServiceProvider? ServiceProvider => Service.MaybeGetServiceProvider(); + + public ContractSettings(Type contractType, object? serviceInstance = null) : this( + serviceInstance is not null + ? new ServiceFactory.Instance() + { + Type = contractType ?? throw new ArgumentNullException(nameof(contractType)), + ServiceInstance = serviceInstance + } + : new ServiceFactory.Deferred() + { + Type = contractType ?? throw new ArgumentNullException(nameof(contractType)), + }) + { } + + public ContractSettings(Type contractType, IServiceProvider serviceProvider) : this( + new ServiceFactory.Injected() + { + Type = contractType ?? throw new ArgumentNullException(nameof(contractType)), + ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)) + }) + { } + + private ContractSettings(ServiceFactory service) => Service = service; + + internal ContractSettings(ContractSettings other) + { + Scheduler = other.Scheduler; + BeforeIncomingCall = other.BeforeIncomingCall; + Service = other.Service; + } +} diff --git a/src/UiPath.CoreIpc/Server/EndpointSettings.cs b/src/UiPath.CoreIpc/Server/EndpointSettings.cs deleted file mode 100644 index 65023197..00000000 --- a/src/UiPath.CoreIpc/Server/EndpointSettings.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace UiPath.Ipc; - -using System; - -public record EndpointSettings -{ - public TaskScheduler? Scheduler { get; set; } - public BeforeCallHandler? BeforeIncomingCall { get; set; } - public Type ContractType => Service.Type; - public object? ServiceInstance => Service.MaybeGetInstance(); - public IServiceProvider? ServiceProvider => Service.MaybeGetServiceProvider(); - internal ServiceFactory Service { get; } - - public EndpointSettings(Type contractType, object? serviceInstance = null) : this( - serviceInstance is not null - ? new ServiceFactory.Instance() - { - Type = contractType ?? throw new ArgumentNullException(nameof(contractType)), - ServiceInstance = serviceInstance - } - : new ServiceFactory.Deferred() - { - Type = contractType ?? throw new ArgumentNullException(nameof(contractType)), - }) - { } - - public EndpointSettings(Type contractType, IServiceProvider serviceProvider) : this( - new ServiceFactory.Injected() - { - Type = contractType ?? throw new ArgumentNullException(nameof(contractType)), - ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)) - }) - { } - - private protected EndpointSettings(ServiceFactory service) => Service = service; - - public virtual EndpointSettings WithServiceProvider(IServiceProvider? serviceProvider) - => new(Service.WithProvider(serviceProvider)); - - public void Validate() - { - Validator.Validate(Service.Type); - if (Service.MaybeGetInstance() is { } instance && !Service.Type.IsAssignableFrom(instance.GetType())) - { - throw new ArgumentOutOfRangeException(nameof(instance)); - } - } -} - -public sealed record EndpointSettings : EndpointSettings where TContract : class -{ - public EndpointSettings(TContract? serviceInstance = null) : base(typeof(TContract), serviceInstance) { } - public EndpointSettings(IServiceProvider serviceProvider) : base(typeof(TContract), serviceProvider) { } - private EndpointSettings(ServiceFactory service) : base(service) { } - - public override EndpointSettings WithServiceProvider(IServiceProvider? serviceProvider) - => new EndpointSettings(Service.WithProvider(serviceProvider)); -} diff --git a/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj b/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj index 781bb27e..85014d33 100644 --- a/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj +++ b/src/UiPath.CoreIpc/UiPath.CoreIpc.csproj @@ -27,13 +27,6 @@ - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -42,7 +35,6 @@ - diff --git a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt index 8cfda51b..95fd1bdb 100644 --- a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt +++ b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt @@ -16,41 +16,26 @@ namespace UiPath.Ipc public System.Reflection.MethodInfo Method { get; } public bool NewConnection { get; } } - public abstract class ClientTransport : System.IEquatable + public abstract class ClientTransport : System.IEquatable { } + public class ContractCollection : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable { - protected ClientTransport() { } - } - public class EndpointCollection : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EndpointCollection() { } - public void Add(System.Type type) { } - public void Add(UiPath.Ipc.EndpointSettings endpointSettings) { } + public ContractCollection() { } + public void Add(System.Type contractType) { } + public void Add(UiPath.Ipc.ContractSettings endpointSettings) { } public void Add(System.Type contractType, object? instance) { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - } - public sealed class EndpointNotFoundException : System.ArgumentException - { - public string EndpointName { get; } - public string ServerDebugName { get; } + public System.Collections.Generic.IEnumerator GetEnumerator() { } } - public class EndpointSettings : System.IEquatable + public sealed class ContractSettings { - public EndpointSettings(System.Type contractType, System.IServiceProvider serviceProvider) { } - public EndpointSettings(System.Type contractType, object? serviceInstance = null) { } + public ContractSettings(System.Type contractType, System.IServiceProvider serviceProvider) { } + public ContractSettings(System.Type contractType, object? serviceInstance = null) { } public System.Func? BeforeIncomingCall { get; set; } - public System.Type ContractType { get; } public System.Threading.Tasks.TaskScheduler? Scheduler { get; set; } - public object? ServiceInstance { get; } - public System.IServiceProvider? ServiceProvider { get; } - public void Validate() { } - public virtual UiPath.Ipc.EndpointSettings WithServiceProvider(System.IServiceProvider? serviceProvider) { } } - public sealed class EndpointSettings : UiPath.Ipc.EndpointSettings, System.IEquatable> - where TContract : class + public sealed class EndpointNotFoundException : System.ArgumentException { - public EndpointSettings(System.IServiceProvider serviceProvider) { } - public EndpointSettings(TContract? serviceInstance = null) { } - public override UiPath.Ipc.EndpointSettings WithServiceProvider(System.IServiceProvider? serviceProvider) { } + public string EndpointName { get; } + public string ServerDebugName { get; } } public class Error : System.IEquatable { @@ -94,7 +79,7 @@ namespace UiPath.Ipc public IpcClient() { } public System.Func? BeforeConnect { get; set; } public System.Func? BeforeOutgoingCall { get; set; } - public UiPath.Ipc.EndpointCollection? Callbacks { get; set; } + public UiPath.Ipc.ContractCollection? Callbacks { get; set; } public Microsoft.Extensions.Logging.ILogger? Logger { get; init; } public UiPath.Ipc.ClientTransport Transport { get; init; } public TProxy GetProxy() @@ -114,7 +99,7 @@ namespace UiPath.Ipc [System.Obsolete("Constructors of types with required members are not supported in this version of " + "your compiler.", true)] public IpcServer() { } - public UiPath.Ipc.EndpointCollection Endpoints { get; init; } + public UiPath.Ipc.ContractCollection Endpoints { get; init; } public UiPath.Ipc.ServerTransport Transport { get; init; } public System.Threading.Tasks.ValueTask DisposeAsync() { } [System.Diagnostics.CodeAnalysis.MemberNotNull(new string[] { @@ -152,7 +137,6 @@ namespace UiPath.Ipc } public abstract class ServerTransport { - protected ServerTransport() { } public int ConcurrentAccepts { get; set; } public byte MaxReceivedMessageSizeInMegabytes { get; set; } } diff --git a/src/UiPath.Ipc.Tests/ComputingTests.cs b/src/UiPath.Ipc.Tests/ComputingTests.cs index aeecd313..2d7b1d86 100644 --- a/src/UiPath.Ipc.Tests/ComputingTests.cs +++ b/src/UiPath.Ipc.Tests/ComputingTests.cs @@ -24,7 +24,7 @@ public abstract class ComputingTests : SpyTestBase protected sealed override IpcProxy? IpcProxy => Proxy as IpcProxy; protected sealed override Type ContractType => typeof(IComputingService); - protected override EndpointCollection? Callbacks => new() + protected override ContractCollection? Callbacks => new() { { typeof(IComputingCallback), _computingCallback } }; diff --git a/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs b/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs index 3ccc33c9..1f0c1217 100644 --- a/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs +++ b/src/UiPath.Ipc.Tests/Helpers/IpcHelpers.cs @@ -47,7 +47,7 @@ public static IpcServer WithRequestTimeout(this IpcServer ipcServer, TimeSpan re public static async Task WithRequestTimeout(this Task ipcServerTask, TimeSpan requestTimeout) => (await ipcServerTask).WithRequestTimeout(requestTimeout); - public static IpcClient WithCallbacks(this IpcClient ipcClient, EndpointCollection callbacks) + public static IpcClient WithCallbacks(this IpcClient ipcClient, ContractCollection callbacks) { ipcClient.Callbacks = callbacks; return ipcClient; diff --git a/src/UiPath.Ipc.Tests/RobotTests.cs b/src/UiPath.Ipc.Tests/RobotTests.cs index 627bb566..bc173e51 100644 --- a/src/UiPath.Ipc.Tests/RobotTests.cs +++ b/src/UiPath.Ipc.Tests/RobotTests.cs @@ -17,7 +17,7 @@ public abstract class RobotTests : TestBase protected sealed override IpcProxy? IpcProxy => Proxy as IpcProxy; protected sealed override Type ContractType => typeof(IStudioOperations); - protected override EndpointCollection? Callbacks => new() + protected override ContractCollection? Callbacks => new() { { typeof(IStudioEvents), _studioEvents } }; diff --git a/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs b/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs index 5e9b11eb..2473766d 100644 --- a/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs +++ b/src/UiPath.Ipc.Tests/RobotTestsOverNamedPipes.cs @@ -64,7 +64,7 @@ public TContract CreateUserServiceProxy(string pipeName) => RobotIpcHelpers.CreateProxy( pipeName, requestTimeout: TimeSpan.FromSeconds(40), - callbacks: new EndpointCollection() + callbacks: new ContractCollection() { { typeof(TCallback), Instance } }, @@ -79,7 +79,7 @@ internal static partial class RobotIpcHelpers public static TContract CreateProxy( string pipeName, TimeSpan? requestTimeout = null, - EndpointCollection? callbacks = null, + ContractCollection? callbacks = null, IServiceProvider? provider = null, Action? beforeConnect = null, BeforeCallHandler? beforeCall = null, @@ -199,7 +199,7 @@ static string Compose(string name, T @new, T old) => $"New {name} is {@new?.ToString() ?? "null"} but was originally {old?.ToString() ?? "null"}."; } } - internal readonly record struct CreateProxyRequest(Key ActualKey, Params Params, EndpointCollection? Callbacks) + internal readonly record struct CreateProxyRequest(Key ActualKey, Params Params, ContractCollection? Callbacks) { public bool Equals(CreateProxyRequest other) => ActualKey.Equals(other.ActualKey); public override int GetHashCode() => ActualKey.GetHashCode(); @@ -208,11 +208,11 @@ internal readonly record struct CreateProxyRequest(Key ActualKey, Params Params, internal readonly struct EquatableEndpointSet : IEquatable { - public static EquatableEndpointSet From(EndpointCollection? endpoints, bool haveProvider) + public static EquatableEndpointSet From(ContractCollection? endpoints, bool haveProvider) { return Pal(endpoints?.AsEnumerable(), haveProvider); - static EquatableEndpointSet Pal(IEnumerable? endpoints, bool haveProvider) + static EquatableEndpointSet Pal(IEnumerable? endpoints, bool haveProvider) { var items = endpoints?.AsEnumerable(); @@ -237,9 +237,9 @@ static EquatableEndpointSet Pal(IEnumerable? endpoints, bool h } public static readonly EquatableEndpointSet Empty = new([]); - private readonly HashSet _set; + private readonly HashSet _set; - private EquatableEndpointSet(HashSet set) => _set = set; + private EquatableEndpointSet(HashSet set) => _set = set; public bool Equals(EquatableEndpointSet other) => _set.SetEquals(other._set); public override bool Equals(object? obj) => obj is EquatableEndpointSet other && Equals(other); @@ -248,7 +248,7 @@ static EquatableEndpointSet Pal(IEnumerable? endpoints, bool h public override string ToString() { return $"[{string.Join(", ", _set.Select(Pal))}]"; - static string Pal(EndpointSettings endpointSettings) + static string Pal(ContractSettings endpointSettings) => $"{endpointSettings.ContractType.Name},sp:{RuntimeHelpers.GetHashCode(endpointSettings.ServiceProvider)},instance:{RuntimeHelpers.GetHashCode(endpointSettings.ServiceInstance)}"; } } diff --git a/src/UiPath.Ipc.Tests/TestBase.cs b/src/UiPath.Ipc.Tests/TestBase.cs index cd15246a..1318aeef 100644 --- a/src/UiPath.Ipc.Tests/TestBase.cs +++ b/src/UiPath.Ipc.Tests/TestBase.cs @@ -67,7 +67,7 @@ public TestBase(ITestOutputHelper outputHelper) protected abstract void ConfigureSpecificServices(IServiceCollection services); - protected virtual EndpointCollection? Callbacks => []; + protected virtual ContractCollection? Callbacks => []; private Task CreateIpcServer() { @@ -85,7 +85,7 @@ async Task Core() var serverTransport = await CreateServerTransport(); ConfigTransportBase(serverTransport); - var endpointSettings = new EndpointSettings(ContractType) + var endpointSettings = new ContractSettings(ContractType) { BeforeIncomingCall = (callInfo, ct) => { @@ -104,7 +104,7 @@ async Task Core() }; } } - protected IpcClient? CreateIpcClient(EndpointCollection? callbacks = null) + protected IpcClient? CreateIpcClient(ContractCollection? callbacks = null) { if (_overrideConfig is null) { From ce5c4a29ae1640a168012a9eab4b3a603d6d4941 Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Thu, 28 Nov 2024 10:01:07 +0100 Subject: [PATCH 11/13] update NodeInterop to the new API --- .../UiPath.CoreIpc.NodeInterop/Program.cs | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/Program.cs b/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/Program.cs index bb5ed827..b4476882 100644 --- a/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/Program.cs +++ b/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/Program.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Net; using System.Net.WebSockets; using System.Threading; @@ -88,23 +89,9 @@ static async Task MainCore(string? pipeName, string? webSocketUrl, int? maybeSec thread.Context.SynchronizationContext.Send(_ => Thread.CurrentThread.Name = "GuiThread", null); var scheduler = thread.Context.Scheduler; - var ipcServer = new IpcServer() - { - Endpoints = new() - { - typeof(IAlgebra), - typeof(ICalculus), - typeof(IBrittleService), - typeof(IEnvironmentVariableGetter), - typeof(IDtoService) - }, - Listeners = [ - ..EnumerateListeners(pipeName, webSocketUrl) - ], - ServiceProvider = sp, - Scheduler = scheduler - }; - ipcServer.Start(); + var ipcServers = EnumerateServerTransports(pipeName, webSocketUrl) + .Select(CreateAndStartIpcServer) + .ToArray(); _ = Task.Run(async () => { @@ -122,16 +109,13 @@ IEnumerable EnumeratePings() { yield return new IpcClient { - Config = new() - { - ServiceProvider = sp, - RequestTimeout = TimeSpan.FromHours(5), - Callbacks = new() + ServiceProvider = sp, + RequestTimeout = TimeSpan.FromHours(5), + Callbacks = new() { { typeof(IArithmetic), callback } }, - }, - Transport = new WebSocketTransport + Transport = new WebSocketClientTransport { Uri = new(webSocketUrl), } @@ -144,16 +128,13 @@ IEnumerable EnumeratePings() { yield return new IpcClient { - Config = new() + ServiceProvider = sp, + RequestTimeout = TimeSpan.FromHours(5), + Callbacks = new() { - ServiceProvider = sp, - RequestTimeout = TimeSpan.FromHours(5), - Callbacks = new() - { - { typeof(IArithmetic), callback } - } + { typeof(IArithmetic), callback } }, - Transport = new NamedPipeTransport() + Transport = new NamedPipeClientTransport() { PipeName = pipeName, } @@ -174,20 +155,41 @@ IEnumerable EnumeratePings() } }); - await ipcServer.WaitForStop(); + await Task.WhenAll(ipcServers.Select(ipcServer => ipcServer.WaitForStop())); + + IpcServer CreateAndStartIpcServer(ServerTransport transport) + { + var ipcServer = new IpcServer() + { + Endpoints = new() + { + typeof(IAlgebra), + typeof(ICalculus), + typeof(IBrittleService), + typeof(IEnvironmentVariableGetter), + typeof(IDtoService) + }, + Transport = transport, + ServiceProvider = sp, + Scheduler = scheduler + }; + ipcServer.Start(); + return ipcServer; + } + - IEnumerable EnumerateListeners(string? pipeName, string? webSocketUrl) + IEnumerable EnumerateServerTransports(string? pipeName, string? webSocketUrl) { if (pipeName is not null) { - yield return new NamedPipeListener() { PipeName = pipeName }; + yield return new NamedPipeServerTransport() { PipeName = pipeName }; } if (webSocketUrl is not null) { string url = CurateWebSocketUrl(webSocketUrl); var accept = new HttpSysWebSocketsListener(url).Accept; - yield return new WebSocketListener() { Accept = accept }; + yield return new WebSocketServerTransport() { Accept = accept }; } static string CurateWebSocketUrl(string raw) From 59fe06519c93bf589df3544b78d5cf7e16dcd657 Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Thu, 28 Nov 2024 12:18:34 +0100 Subject: [PATCH 12/13] simplifications after review - decommission WaitForStop --- .../UiPath.CoreIpc.NodeInterop/Program.cs | 2 +- src/UiPath.CoreIpc/Config/IpcServer.cs | 3 - .../UiPath.Ipc.net6.0-windows.received.txt | 1 - src/UiPath.Ipc.Tests/Program.cs | 58 +++++++++++++------ 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/Program.cs b/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/Program.cs index b4476882..cba5d22e 100644 --- a/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/Program.cs +++ b/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/Program.cs @@ -155,7 +155,7 @@ IEnumerable EnumeratePings() } }); - await Task.WhenAll(ipcServers.Select(ipcServer => ipcServer.WaitForStop())); + await Task.Delay(Timeout.InfiniteTimeSpan); IpcServer CreateAndStartIpcServer(ServerTransport transport) { diff --git a/src/UiPath.CoreIpc/Config/IpcServer.cs b/src/UiPath.CoreIpc/Config/IpcServer.cs index 0fbc3d40..138d0ec3 100644 --- a/src/UiPath.CoreIpc/Config/IpcServer.cs +++ b/src/UiPath.CoreIpc/Config/IpcServer.cs @@ -9,7 +9,6 @@ public sealed class IpcServer : IpcBase, IAsyncDisposable private readonly object _lock = new(); private readonly TaskCompletionSource _listening = new(); - private readonly TaskCompletionSource _stopped = new(); private readonly CancellationTokenSource _ctsActiveConnections = new(); private bool _disposeStarted; @@ -70,7 +69,6 @@ public Task WaitForStart() Start(); return _accepter.StartedAccepting; } - public Task WaitForStop() => _stopped.Task; internal ILogger? CreateLogger(string category) => ServiceProvider.MaybeCreateLogger(category); @@ -82,7 +80,6 @@ private void OnNewConnection(Stream network) private void OnNewConnectionError(Exception ex) { Trace.TraceError($"Failed to accept new connection. Ex: {ex}"); - _stopped.TrySetException(ex); } internal RouterConfig CreateRouterConfig(IpcServer server) => RouterConfig.From( diff --git a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt index 95fd1bdb..1290f0c3 100644 --- a/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt +++ b/src/UiPath.CoreIpc/report/UiPath.Ipc.net6.0-windows.received.txt @@ -107,7 +107,6 @@ namespace UiPath.Ipc "_accepter"})] public void Start() { } public System.Threading.Tasks.Task WaitForStart() { } - public System.Threading.Tasks.Task WaitForStop() { } } public class Message { diff --git a/src/UiPath.Ipc.Tests/Program.cs b/src/UiPath.Ipc.Tests/Program.cs index feb7a67e..4f21b49b 100644 --- a/src/UiPath.Ipc.Tests/Program.cs +++ b/src/UiPath.Ipc.Tests/Program.cs @@ -4,30 +4,50 @@ using UiPath.Ipc; using UiPath.Ipc.Tests; -if (args is not [var base64]) +using (ConsoleCancellation(out var ct)) { - Console.Error.WriteLine($"Usage: dotnet {Path.GetFileName(Assembly.GetEntryAssembly()!.Location)} "); - return 1; + return await Entry(ct); } -var externalServerParams = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Convert.FromBase64String(base64))); -await using var asyncDisposable = externalServerParams.CreateListenerConfig(out var serverTransport); -await using var serviceProvider = new ServiceCollection() - .AddLogging(builder => builder.AddConsole()) - .AddSingleton() - .BuildServiceProvider(); - -await using var ipcServer = new IpcServer() +async Task Entry(CancellationToken ct) { - ServiceProvider = serviceProvider, - Scheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler, - Endpoints = new() + if (args is not [var base64]) + { + Console.Error.WriteLine($"Usage: dotnet {Path.GetFileName(Assembly.GetEntryAssembly()!.Location)} "); + return 1; + } + var externalServerParams = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Convert.FromBase64String(base64))); + await using var asyncDisposable = externalServerParams.CreateListenerConfig(out var serverTransport); + + await using var serviceProvider = new ServiceCollection() + .AddLogging(builder => builder.AddConsole()) + .AddSingleton() + .BuildServiceProvider(); + + await using var ipcServer = new IpcServer() + { + ServiceProvider = serviceProvider, + Scheduler = new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler, + Endpoints = new() { { typeof(IComputingService) }, }, - Transport = serverTransport, -}; -ipcServer.Start(); -await ipcServer.WaitForStop(); + Transport = serverTransport, + }; + ipcServer.Start(); + await Task.Delay(Timeout.InfiniteTimeSpan, ct); -return 0; \ No newline at end of file + return 0; +} + +static IDisposable ConsoleCancellation(out CancellationToken ct) +{ + var cts = new CancellationTokenSource(); + ct = cts.Token; + Console.CancelKeyPress += (sender, e) => + { + e.Cancel = true; + cts.Cancel(); + }; + return cts; +} \ No newline at end of file From d911fb6d6228a79bf38f46e7e62b273785b1ce06 Mon Sep 17 00:00:00 2001 From: Eduard Dumitru Date: Tue, 3 Dec 2024 09:34:29 +0100 Subject: [PATCH 13/13] reduce surface area - decommission the duplication of IClient methods in Message --- .../js/dotnet/UiPath.CoreIpc.NodeInterop/ServiceImpls.cs | 4 ++-- src/Playground/Impl.cs | 4 ++-- src/UiPath.CoreIpc/Wire/Dtos.cs | 5 +---- src/UiPath.Ipc.Tests/Services/ComputingService.cs | 4 ++-- src/UiPath.Ipc.Tests/Services/Robot/Pals.cs | 2 +- src/UiPath.Ipc.Tests/Services/SystemService.cs | 6 +++--- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/ServiceImpls.cs b/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/ServiceImpls.cs index 3b9706f1..e6de1f24 100644 --- a/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/ServiceImpls.cs +++ b/src/Clients/js/dotnet/UiPath.CoreIpc.NodeInterop/ServiceImpls.cs @@ -20,7 +20,7 @@ public async Task MultiplySimple(int x, int y) public async Task Multiply(int x, int y, Message message = default!) { - var arithmetic = message.GetCallback(); + var arithmetic = message.Client.GetCallback(); int result = 0; for (int i = 0; i < x; i++) @@ -32,7 +32,7 @@ public async Task Multiply(int x, int y, Message message = default!) } public async Task TestMessage(Message message) { - var arithmetic = message.GetCallback(); + var arithmetic = message.Client.GetCallback(); return await arithmetic.SendMessage(message); } diff --git a/src/Playground/Impl.cs b/src/Playground/Impl.cs index 4d020920..66bc292d 100644 --- a/src/Playground/Impl.cs +++ b/src/Playground/Impl.cs @@ -20,8 +20,8 @@ public sealed class Server(ClientRegistry clients) : Contracts.IServerOperations { public async Task Register(Message? m = null) { - var clientOps = m!.GetCallback(); - var clientOps2 = m.GetCallback(); + var clientOps = m!.Client.GetCallback(); + var clientOps2 = m.Client.GetCallback(); var added = clients.Add(new(clientOps, clientOps2)); diff --git a/src/UiPath.CoreIpc/Wire/Dtos.cs b/src/UiPath.CoreIpc/Wire/Dtos.cs index 6f796e0c..bbb3e5da 100644 --- a/src/UiPath.CoreIpc/Wire/Dtos.cs +++ b/src/UiPath.CoreIpc/Wire/Dtos.cs @@ -7,12 +7,9 @@ namespace UiPath.Ipc; public class Message { [JsonIgnore] - public IClient Client { get; set; } + public IClient Client { get; set; } = null!; [JsonIgnore] public TimeSpan RequestTimeout { get; set; } - public TCallbackInterface GetCallback() where TCallbackInterface : class => - Client.GetCallback(); - public void ImpersonateClient(Action action) => Client.Impersonate(action); } public class Message : Message { diff --git a/src/UiPath.Ipc.Tests/Services/ComputingService.cs b/src/UiPath.Ipc.Tests/Services/ComputingService.cs index 40f1863a..4b382d52 100644 --- a/src/UiPath.Ipc.Tests/Services/ComputingService.cs +++ b/src/UiPath.Ipc.Tests/Services/ComputingService.cs @@ -32,7 +32,7 @@ public async Task Wait(TimeSpan duration, CancellationToken ct = default) public async Task GetCallbackThreadName(TimeSpan waitOnServer, Message message = null!, CancellationToken cancellationToken = default) { await Task.Delay(waitOnServer); - return await message.GetCallback().GetThreadName(); + return await message.Client.GetCallback().GetThreadName(); } public async Task AddComplexNumberList(IReadOnlyList numbers) @@ -47,7 +47,7 @@ public async Task AddComplexNumberList(IReadOnlyList MultiplyInts(int x, int y, Message message = null!) { - var callback = message.GetCallback(); + var callback = message.Client.GetCallback(); var result = 0; for (int i = 0; i < y; i++) diff --git a/src/UiPath.Ipc.Tests/Services/Robot/Pals.cs b/src/UiPath.Ipc.Tests/Services/Robot/Pals.cs index 5f2daea2..cdea5968 100644 --- a/src/UiPath.Ipc.Tests/Services/Robot/Pals.cs +++ b/src/UiPath.Ipc.Tests/Services/Robot/Pals.cs @@ -49,7 +49,7 @@ internal sealed class Callback where T : class public Callback(Message message) { Client = message.Client; - _callback = message.GetCallback(); + _callback = message.Client.GetCallback(); } public void Invoke(Func call) => InvokeAsync(call).TraceError(); diff --git a/src/UiPath.Ipc.Tests/Services/SystemService.cs b/src/UiPath.Ipc.Tests/Services/SystemService.cs index 9f1d9394..d7cd22ca 100644 --- a/src/UiPath.Ipc.Tests/Services/SystemService.cs +++ b/src/UiPath.Ipc.Tests/Services/SystemService.cs @@ -34,7 +34,7 @@ public async Task FireAndForget(TimeSpan wait) { try { - _ = await message.GetCallback().SomeMethod(); + _ = await message.Client.GetCallback().SomeMethod(); return null; } catch (Exception ex) @@ -75,8 +75,8 @@ public async Task Download(string s, CancellationToken ct = default) public async Task AddIncrement(int x, int y, Message message = null!) { - var sum = await message.GetCallback().AddInts(x, y); - var result = await message.GetCallback().Increment(sum); + var sum = await message.Client.GetCallback().AddInts(x, y); + var result = await message.Client.GetCallback().Increment(sum); return result; } }