Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review API from Revamp PR #107

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 264 additions & 0 deletions UiPath.Ipc.net6.0-windows.received.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
[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.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")]
[assembly: System.Runtime.Versioning.TargetPlatform("Windows7.0")]
namespace UiPath.Ipc
{
public readonly struct CallInfo
{
public CallInfo(bool newConnection, System.Reflection.MethodInfo method, object?[] arguments) { }
public object?[] Arguments { get; }
public System.Reflection.MethodInfo Method { get; }
public bool NewConnection { get; }
}
public sealed class ClientConfig : UiPath.Ipc.EndpointConfig, System.IEquatable<UiPath.Ipc.ClientConfig>
{
public ClientConfig() { }
public string DebugName { get; set; }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

not clear what DebugName does.

Copy link
Collaborator

@eduard-dumitru eduard-dumitru Nov 5, 2024

Choose a reason for hiding this comment

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

It's a CoreIpc-traditional tagging mechanism for reading logs and understanding who's who.
I didn't like how Name sounded (it seemed more functional than it actually was), so I prefixed it with Debug.

On support/v21.10 the public ones were ListenerSettings.Name and Connection.Name, both of which are public, and which spin off into internals (i.e. ServiceClient inherits the Name from it's associated Listener if it's of the callback sort) and all end up in logs, from hundreds of places, i.e: this

public UiPath.Ipc.ISerializer? Serializer { get; set; }
public System.Func<UiPath.Ipc.CallInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task>? BeforeCall { get; init; }
public System.Func<System.Threading.CancellationToken, System.Threading.Tasks.Task>? 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<UiPath.Ipc.ClientTransport>
vuplea marked this conversation as resolved.
Show resolved Hide resolved
{
protected ClientTransport() { }
public abstract UiPath.Ipc.IClientState CreateState();
public abstract void Validate();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

maybe we should think if we really need the Validate feature?

}
public class EndpointCollection : System.Collections.Generic.IEnumerable<UiPath.Ipc.EndpointSettings>, System.Collections.IEnumerable
vuplea marked this conversation as resolved.
Show resolved Hide resolved
{
public EndpointCollection() { }
public void Add(System.Type type) { }
public void Add(UiPath.Ipc.EndpointSettings endpointSettings) { }
public void Add(System.Type contractType, object? instance) { }
public System.Collections.Generic.IEnumerator<UiPath.Ipc.EndpointSettings> GetEnumerator() { }
vuplea marked this conversation as resolved.
Show resolved Hide resolved
}
public abstract class EndpointConfig : System.IEquatable<UiPath.Ipc.EndpointConfig>
{
protected EndpointConfig() { }
public System.TimeSpan RequestTimeout { get; init; }
}
[System.Serializable]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ISerializable is obsolete: dotnet/docs#34893
This is not a proper implementation anyway of ISerializable (you need special constructor and methods).
We had a particular situation where this attribute was a hack, but it's a good time to remove it. I think it will be fine without it and it's worth to support the consequences, if any.

public sealed class EndpointNotFoundException : System.ArgumentOutOfRangeException
Copy link
Collaborator Author

@vuplea vuplea Oct 23, 2024

Choose a reason for hiding this comment

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

not an argument out of range problem

argument out of range is if we could know statically that the argument is outside of the input domain

{
public EndpointNotFoundException(string paramName, string serverDebugName, string endpointName) { }
public string EndpointName { get; }
public string ServerDebugName { get; }
vuplea marked this conversation as resolved.
Show resolved Hide resolved
}
public class EndpointSettings : System.IEquatable<UiPath.Ipc.EndpointSettings>
{
public EndpointSettings(System.Type contractType, System.IServiceProvider serviceProvider) { }
public EndpointSettings(System.Type contractType, object? serviceInstance = null) { }
public System.Func<UiPath.Ipc.CallInfo, System.Threading.CancellationToken, System.Threading.Tasks.Task>? BeforeCall { get; set; }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we have to decide where we put the recurring configurations. We want a single place for them. we have to keep it as simple, this was one of our goals.
I'd say:

  • BeforeCall in EndpointSettings since more often than not is service-specific.
  • TaskScheduler and IServiceProvidder in EndpointConfiguration. It would be weird to want them differently for different services.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We can have a single constructor (Type, Func?) I think for advanced needs.

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() { }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see you've added ServiceProvider and scheduler to IpcBase - so we'd be good to remove them from here, right?

public virtual UiPath.Ipc.EndpointSettings WithServiceProvider(System.IServiceProvider? serviceProvider) { }
}
public sealed class EndpointSettings<TContract> : UiPath.Ipc.EndpointSettings, System.IEquatable<UiPath.Ipc.EndpointSettings<TContract>>
where TContract : class
{
public EndpointSettings(System.IServiceProvider serviceProvider) { }
public EndpointSettings(TContract? serviceInstance = null) { }
public override UiPath.Ipc.EndpointSettings WithServiceProvider(System.IServiceProvider? serviceProvider) { }
}
[System.Serializable]
public class Error : System.IEquatable<UiPath.Ipc.Error>
{
public Error(string Message, string StackTrace, string Type, UiPath.Ipc.Error? InnerError) { }
public UiPath.Ipc.Error? InnerError { get; init; }
public string Message { get; init; }
public string StackTrace { get; init; }
public string Type { get; init; }
public override string ToString() { }
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("exception")]
public static UiPath.Ipc.Error? FromException(System.Exception? exception) { }
}
public interface IClient
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

IClientConnection name would be better:

  • we have the usecase in which we identify the same connection in hashset
  • the callback is only valid for that client connection, not if recreated

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sounds good, but we'll have to discuss whether what you said still holds.

{
TCallbackInterface GetCallback<TCallbackInterface>()
where TCallbackInterface : class;
void Impersonate(System.Action action);
}
public interface IClientState : System.IDisposable
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

why not directly these in ClientTransport?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looking into it, maybe the tensions between parts disappeared, and I hadn't checked it. I hope so because, yeah, it's pretty complex.

{
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) { }
public static System.IO.Pipes.PipeSecurity Allow(this System.IO.Pipes.PipeSecurity pipeSecurity, System.Security.Principal.WellKnownSidType sid, System.IO.Pipes.PipeAccessRights pipeAccessRights) { }
public static System.IO.Pipes.PipeSecurity AllowCurrentUser(this System.IO.Pipes.PipeSecurity pipeSecurity, bool onlyNonAdmin = false) { }
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) { }
public static bool PipeExists(string pipeName, int timeout = 1) { }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

afaik we don't use PipeExists anymore, it came with a lot of problems - it showed false negatives. suggest removing it.

}
public interface ISerializer
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Realistically, experience (msgpack, System.Text.Json) showed us a generic abstraction over serialization is not that plausible.
I'd rather know that UiPath.Ipc is Json.Net specific. If anyone comes up with a usecase, we can make it public then.

{
object? Deserialize(string json, System.Type type);
System.Threading.Tasks.ValueTask<T?> DeserializeAsync<T>(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
{
[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 UiPath.Ipc.ClientTransport Transport { get; init; }
public TProxy GetProxy<TProxy>()
where TProxy : class { }
}
public class IpcProxy : System.Reflection.DispatchProxy, System.IDisposable
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Why would we have this public?
ConnectionClosed event might be useful but we can put it somewhere else.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Unfortunately, the number of usages is non-zero, some of which are scarier than others, i.e.:

private IPackagerIpcService _remoteProxy;

gets cast to IpcProxy to (un)subscribe to the underlying connection's death:
var ipcProxy = _remoteProxy as IpcProxy;

We also have more legit usages around logging categories/filtering.

Copy link
Collaborator Author

@vuplea vuplea Nov 27, 2024

Choose a reason for hiding this comment

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

for the event, we should be able to relocate the ConnectionClosed event on the IpcClient itself>
for the Dispose it might be tricker (because the IpcClient object might be lost when you call Dispose on another stack).

anyway, I'd like to try to remove this, but if it's costly, please tell me to evaluate options.

{
public IpcProxy() { }
public System.IO.Stream? Network { get; }
public event System.EventHandler ConnectionClosed;
public System.Threading.Tasks.ValueTask CloseConnection() { }
public void Dispose() { }
protected override object? Invoke(System.Reflection.MethodInfo? targetMethod, object?[]? args) { }
}
public sealed class IpcServer : 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<UiPath.Ipc.ListenerConfig> Listeners { get; init; }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think we can improve the entity model to have better matching between client and server, and generally to simplify it.
The only real relationship differences I can see:

  • A client has a single connection while a listener has many. I don't see any problem with the API regarding this.
  • A server can have multiple transport configurations "listeners". Of note, we could drop this feature of multiple listeners. A user could simply create more servers calling the same configuration code.

I'd propose this model:
Endpoints = { Client, Server }
Endpoint = (IServiceProvider, Scheduler, RequestTimeout)
Client : Endpoint + (ClientTransport)
Server : Endpoint + (ServerTransport)

public System.Threading.Tasks.TaskScheduler? Scheduler { get; init; }
public System.IServiceProvider ServiceProvider { get; init; }
public System.Threading.Tasks.ValueTask DisposeAsync() { }
public void Start() { }
public System.Threading.Tasks.Task WaitForStart() { }
public System.Threading.Tasks.Task WaitForStop() { }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

what;s the differentce between WaitForStop() and DisposeAsync()?
I'd like to avoid supporting the "restart" scenario.

}
public abstract class ListenerConfig : UiPath.Ipc.EndpointConfig, System.IEquatable<UiPath.Ipc.ListenerConfig>
{
protected ListenerConfig() { }
public System.Security.Cryptography.X509Certificates.X509Certificate? Certificate { get; init; }
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

was this in CoreIpc?
why do we expose this? is it for websocket only?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes it was, and it was agnostic than merely websockets;

I was wondering the same thing.

We use CoreIpc over Tcp/Ipc, but I can't find a precise usage for this certificate thing.
We have 0 websocket usage.

public int ConcurrentAccepts { get; init; }
public byte MaxReceivedMessageSizeInMegabytes { get; init; }
}
public class Message
{
public Message() { }
[Newtonsoft.Json.JsonIgnore]
public UiPath.Ipc.IClient Client { get; set; }
[Newtonsoft.Json.JsonIgnore]
public System.TimeSpan RequestTimeout { get; set; }
public TCallbackInterface GetCallback<TCallbackInterface>()
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

maybe it wouldn't be that bad to force the user to go through IClient(Connection) 😓 . it tells you more e.g. how long the callback is valid, but mostly for the reduced surface area.

where TCallbackInterface : class { }
public void ImpersonateClient(System.Action action) { }
}
public class Message<TPayload> : UiPath.Ipc.Message
{
public Message(TPayload payload) { }
public TPayload Payload { get; }
}
[System.Serializable]
public class RemoteException : System.Exception
{
public RemoteException(UiPath.Ipc.Error error) { }
public UiPath.Ipc.RemoteException? InnerException { get; }
public override string StackTrace { get; }
public string Type { get; }
public bool Is<TException>()
where TException : System.Exception { }
public override string ToString() { }
}
}
namespace UiPath.Ipc.Extensibility
{
public interface IListenerConfig<TSelf, TListenerState, TConnectionState>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

IListenerConfig vs ListenerConfig not obvious.
Why other namespace not obvious.
Maybe you'd want to move all transport stuff into UiPath.Ipc.Transport (client included), but I don't see the need.

Too many generics, one of the problems we wanted to avoid.

Copy link
Collaborator

@eduard-dumitru eduard-dumitru Nov 5, 2024

Choose a reason for hiding this comment

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

I agree, and we need to discuss it a bit, but this is exclusively for the select few who will do (maybe) a Blazor JSInterop transport.

where TSelf : UiPath.Ipc.ListenerConfig, UiPath.Ipc.Extensibility.IListenerConfig<TSelf, TListenerState, TConnectionState>
where TListenerState : System.IAsyncDisposable
{
System.Threading.Tasks.ValueTask<System.IO.Stream> 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<string> Validate();
}
}
namespace UiPath.Ipc.Transport.NamedPipe
{
public sealed class NamedPipeListener : UiPath.Ipc.ListenerConfig, System.IEquatable<UiPath.Ipc.Transport.NamedPipe.NamedPipeListener>, UiPath.Ipc.Extensibility.IListenerConfig<UiPath.Ipc.Transport.NamedPipe.NamedPipeListener, UiPath.Ipc.Transport.NamedPipe.NamedPipeListenerState, UiPath.Ipc.Transport.NamedPipe.NamedPipeServerConnectionState>
{
[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<System.IO.Pipes.PipeSecurity>? AccessControl { get; init; }
public string PipeName { get; init; }
public string ServerName { get; init; }
public override string ToString() { }
}
public sealed class NamedPipeTransport : UiPath.Ipc.ClientTransport, System.IEquatable<UiPath.Ipc.Transport.NamedPipe.NamedPipeTransport>
{
[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 string PipeName { get; init; }
public string ServerName { get; init; }
public override UiPath.Ipc.IClientState CreateState() { }
public override string ToString() { }
public override void Validate() { }
}
}
namespace UiPath.Ipc.Transport.Tcp
{
public sealed class TcpListener : UiPath.Ipc.ListenerConfig, System.IEquatable<UiPath.Ipc.Transport.Tcp.TcpListener>, UiPath.Ipc.Extensibility.IListenerConfig<UiPath.Ipc.Transport.Tcp.TcpListener, UiPath.Ipc.Transport.Tcp.TcpListenerState, UiPath.Ipc.Transport.Tcp.TcpServerConnectionState>
{
[System.Obsolete("Constructors of types with required members are not supported in this version of " +
"your compiler.", true)]
public TcpListener() { }
public System.Net.IPEndPoint EndPoint { get; init; }
public override string ToString() { }
}
public sealed class TcpTransport : UiPath.Ipc.ClientTransport, System.IEquatable<UiPath.Ipc.Transport.Tcp.TcpTransport>
{
[System.Obsolete("Constructors of types with required members are not supported in this version of " +
"your compiler.", true)]
public TcpTransport() { }
public System.Net.IPEndPoint EndPoint { get; init; }
public override UiPath.Ipc.IClientState CreateState() { }
public override string ToString() { }
public override void Validate() { }
}
}
namespace UiPath.Ipc.Transport.WebSocket
{
public sealed class WebSocketListener : UiPath.Ipc.ListenerConfig, System.IEquatable<UiPath.Ipc.Transport.WebSocket.WebSocketListener>, UiPath.Ipc.Extensibility.IListenerConfig<UiPath.Ipc.Transport.WebSocket.WebSocketListener, UiPath.Ipc.Transport.WebSocket.WebSocketListenerState, UiPath.Ipc.Transport.WebSocket.WebSocketServerConnectionState>
{
[System.Obsolete("Constructors of types with required members are not supported in this version of " +
"your compiler.", true)]
public WebSocketListener() { }
public System.Func<System.Threading.CancellationToken, System.Threading.Tasks.Task<System.Net.WebSockets.WebSocket>> Accept { get; init; }
public override string ToString() { }
}
public sealed class WebSocketTransport : UiPath.Ipc.ClientTransport, System.IEquatable<UiPath.Ipc.Transport.WebSocket.WebSocketTransport>
{
[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 override string ToString() { }
public override void Validate() { }
}
}