/// <summary> /// Listens at the transport path for new connections. /// </summary> /// <param name="maxConnections">The maximum number of connections the server will support.</param> /// <param name="token">The token to monitor for cancellation requests.</param> /// <returns>A task that completes when the server is no longer listening at the transport path.</returns> private async Task ListenAsync(int maxConnections, CancellationToken token) { using var transport = IpcServerTransport.Create(_transportPath, maxConnections); while (!token.IsCancellationRequested) { Stream stream = null; IpcAdvertise advertise = null; try { stream = await transport.AcceptAsync(token).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception) { // The advertise data could be incomplete if the runtime shuts down before completely writing // the information. Catch the exception and continue waiting for a new connection. } if (null != stream) { // Cancel parsing of advertise data after timeout period to // mitigate runtimes that write partial data and do not close the stream (avoid waiting forever). using var parseCancellationSource = new CancellationTokenSource(); using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, parseCancellationSource.Token); try { parseCancellationSource.CancelAfter(ParseAdvertiseTimeout); advertise = await IpcAdvertise.ParseAsync(stream, linkedSource.Token).ConfigureAwait(false); } catch (Exception) { } } if (null != advertise) { Guid runtimeCookie = advertise.RuntimeInstanceCookie; int pid = unchecked ((int)advertise.ProcessId); lock (_streamLock) { ProvideStream(runtimeCookie, stream); if (!_cachedEndpoints.ContainsKey(runtimeCookie)) { ServerIpcEndpoint endpoint = new ServerIpcEndpoint(this, runtimeCookie); _cachedEndpoints.Add(runtimeCookie, endpoint); ProvideEndpointInfo(new IpcEndpointInfo(endpoint, pid, runtimeCookie)); } } } } }
/// <summary> /// Listens at the address for new connections. /// </summary> /// <param name="maxConnections">The maximum number of connections the server will support.</param> /// <param name="token">The token to monitor for cancellation requests.</param> /// <returns>A task that completes when the server is no longer listening at the address.</returns> private async Task ListenAsync(int maxConnections, CancellationToken token) { // This disposal shuts down the transport in case an exception is thrown. using var transport = IpcServerTransport.Create(_address, maxConnections, _enableTcpIpProtocol, TransportCallback); // This disposal shuts down the transport in case of cancellation; causes the transport // to not recreate the server stream before the AcceptAsync call observes the cancellation. using var _ = token.Register(() => transport.Dispose()); while (!token.IsCancellationRequested) { Stream stream = null; IpcAdvertise advertise = null; try { stream = await transport.AcceptAsync(token).ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception) { // The advertise data could be incomplete if the runtime shuts down before completely writing // the information. Catch the exception and continue waiting for a new connection. } if (null != stream) { // Cancel parsing of advertise data after timeout period to // mitigate runtimes that write partial data and do not close the stream (avoid waiting forever). using var parseCancellationSource = new CancellationTokenSource(); using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, parseCancellationSource.Token); try { parseCancellationSource.CancelAfter(ParseAdvertiseTimeout); advertise = await IpcAdvertise.ParseAsync(stream, linkedSource.Token).ConfigureAwait(false); } catch (Exception) { stream.Dispose(); } } if (null != advertise) { Guid runtimeCookie = advertise.RuntimeInstanceCookie; int pid = unchecked ((int)advertise.ProcessId); // The valueFactory parameter of the GetOrAdd overload that uses Func<TKey, TValue> valueFactory // does not execute the factory under a lock thus it is not thread-safe. Create the collection and // use a thread-safe version of GetOrAdd; use equality comparison on the result to determine if // the new collection was added to the dictionary or if an existing one was returned. var newStreamCollection = new HandleableCollection <Stream>(); var streamCollection = _streamCollections.GetOrAdd(runtimeCookie, newStreamCollection); try { streamCollection.ClearItems(); streamCollection.Add(stream); if (newStreamCollection == streamCollection) { ServerIpcEndpoint endpoint = new ServerIpcEndpoint(this, runtimeCookie); _endpointInfos.Add(new IpcEndpointInfo(endpoint, pid, runtimeCookie)); } else { newStreamCollection.Dispose(); } } catch (ObjectDisposedException) { // The stream collection could be disposed by RemoveConnection which would cause an // ObjectDisposedException to be thrown if trying to clear/add the stream. stream.Dispose(); } } } }
public override async Task <Router> CreateRouterAsync(CancellationToken token) { Stream tcpServerStream = null; Stream ipcClientStream = null; _logger?.LogDebug("Trying to create a new router instance."); try { using CancellationTokenSource cancelRouter = CancellationTokenSource.CreateLinkedTokenSource(token); // Get new server endpoint. tcpServerStream = await _tcpServerRouterFactory.AcceptTcpStreamAsync(cancelRouter.Token).ConfigureAwait(false); // Get new client endpoint. using var ipcClientStreamTask = _ipcClientRouterFactory.ConnectIpcStreamAsync(cancelRouter.Token); // We have a valid tcp stream and a pending ipc stream. Wait for completion // or disconnect of tcp stream. using var checkTcpStreamTask = IsStreamConnectedAsync(tcpServerStream, cancelRouter.Token); // Wait for at least completion of one task. await Task.WhenAny(ipcClientStreamTask, checkTcpStreamTask).ConfigureAwait(false); // Cancel out any pending tasks not yet completed. cancelRouter.Cancel(); try { await Task.WhenAll(ipcClientStreamTask, checkTcpStreamTask).ConfigureAwait(false); } catch (Exception) { // Check if we have an accepted ipc stream. if (IsCompletedSuccessfully(ipcClientStreamTask)) { ipcClientStreamTask.Result?.Dispose(); } if (checkTcpStreamTask.IsFaulted) { _logger?.LogInformation("Broken tcp connection detected, aborting ipc connection."); checkTcpStreamTask.GetAwaiter().GetResult(); } throw; } ipcClientStream = ipcClientStreamTask.Result; try { // TcpServer consumes advertise message, needs to be replayed back to ipc client stream. Use router process ID as representation. await IpcAdvertise.SerializeAsync(ipcClientStream, _tcpServerRouterFactory.RuntimeInstanceId, (ulong)Process.GetCurrentProcess().Id, token).ConfigureAwait(false); } catch (Exception) { _logger?.LogDebug("Failed sending advertise message."); throw; } } catch (Exception) { _logger?.LogDebug("Failed creating new router instance."); // Cleanup and rethrow. tcpServerStream?.Dispose(); ipcClientStream?.Dispose(); throw; } // Create new router. _logger?.LogDebug("New router instance successfully created."); return(new Router(ipcClientStream, tcpServerStream, _logger, (ulong)IpcAdvertise.V1SizeInBytes)); }
/// <summary> /// Provides endpoint information when a new runtime instance connects to the server. /// </summary> /// <param name="token">The token to monitor for cancellation requests.</param> /// <returns>A <see cref="IpcEndpointInfo"/> that contains information about the new runtime instance connection.</returns> /// <remarks> /// This will only provide endpoint information on the first time a runtime connects to the server. /// If a connection is removed using <see cref="RemoveConnection(Guid)"/> and the same runtime instance, /// reconnects after this call, then a new <see cref="IpcEndpointInfo"/> will be produced. /// </remarks> public async Task <IpcEndpointInfo> AcceptAsync(CancellationToken token) { VerifyNotDisposed(); while (true) { Stream stream = null; IpcAdvertise advertise = null; try { stream = await _transport.AcceptAsync(token).ConfigureAwait(false); } catch (Exception ex) when(!(ex is OperationCanceledException)) { // The advertise data could be incomplete if the runtime shuts down before completely writing // the information. Catch the exception and continue waiting for a new connection. } token.ThrowIfCancellationRequested(); if (null != stream) { // Cancel parsing of advertise data after timeout period to // mitigate runtimes that write partial data and do not close the stream (avoid waiting forever). using var parseCancellationSource = new CancellationTokenSource(); using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(token, parseCancellationSource.Token); try { parseCancellationSource.CancelAfter(ParseAdvertiseTimeout); advertise = await IpcAdvertise.ParseAsync(stream, linkedSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) when(parseCancellationSource.IsCancellationRequested) { // Only handle cancellation if it was due to the parse timeout. } catch (Exception ex) when(!(ex is OperationCanceledException)) { // Catch all other exceptions and continue waiting for a new connection. } } token.ThrowIfCancellationRequested(); if (null != advertise) { Guid runtimeCookie = advertise.RuntimeInstanceCookie; int pid = unchecked ((int)advertise.ProcessId); lock (_lock) { ProvideStream(runtimeCookie, stream); // Consumers should hold onto the endpoint info and use it for diagnostic communication, // regardless of the number of times the same runtime instance connects. This requires consumers // to continuously invoke the AcceptAsync method in order to handle runtime instance reconnects, // even if the consumer only wants to handle a single endpoint. if (!_cachedEndpoints.ContainsKey(runtimeCookie)) { ServerIpcEndpoint endpoint = new ServerIpcEndpoint(this, runtimeCookie); _cachedEndpoints.Add(runtimeCookie, endpoint); return(new IpcEndpointInfo(endpoint, pid, runtimeCookie)); } } } token.ThrowIfCancellationRequested(); } }
protected async Task <Stream> ConnectIpcStreamAsync(CancellationToken token) { Stream ipcClientStream = null; Logger.LogDebug($"Connecting new ipc endpoint \"{_ipcClientPath}\"."); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var namedPipe = new NamedPipeClientStream( ".", _ipcClientPath, PipeDirection.InOut, PipeOptions.Asynchronous, TokenImpersonationLevel.Impersonation); try { await namedPipe.ConnectAsync(IpcClientTimeoutMs, token).ConfigureAwait(false); } catch (Exception ex) { namedPipe?.Dispose(); if (ex is TimeoutException) { Logger.LogDebug("No ipc stream connected, timing out."); } throw; } ipcClientStream = namedPipe; } else { bool retry = false; IpcUnixDomainSocket unixDomainSocket; do { unixDomainSocket = new IpcUnixDomainSocket(); using var connectTimeoutTokenSource = new CancellationTokenSource(); using var connectTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, connectTimeoutTokenSource.Token); try { connectTimeoutTokenSource.CancelAfter(IpcClientTimeoutMs); await unixDomainSocket.ConnectAsync(new IpcUnixDomainSocketEndPoint(_ipcClientPath), token).ConfigureAwait(false); retry = false; } catch (Exception) { unixDomainSocket?.Dispose(); if (connectTimeoutTokenSource.IsCancellationRequested) { Logger.LogDebug("No ipc stream connected, timing out."); throw new TimeoutException(); } Logger.LogTrace($"Failed connecting {_ipcClientPath}, wait {IpcClientRetryTimeoutMs} ms before retrying."); // If we get an error (without hitting timeout above), most likely due to unavailable listener. // Delay execution to prevent to rapid retry attempts. await Task.Delay(IpcClientRetryTimeoutMs, token).ConfigureAwait(false); if (IpcClientTimeoutMs != Timeout.Infinite) { throw; } retry = true; } }while (retry); ipcClientStream = new ExposedSocketNetworkStream(unixDomainSocket, ownsSocket: true); } try { // ReversedDiagnosticsServer consumes advertise message, needs to be replayed back to ipc client stream. Use router process ID as representation. await IpcAdvertise.SerializeAsync(ipcClientStream, RuntimeInstanceId, (ulong)Process.GetCurrentProcess().Id, token).ConfigureAwait(false); } catch (Exception) { Logger.LogDebug("Failed sending advertise message."); ipcClientStream?.Dispose(); throw; } if (ipcClientStream != null) { Logger.LogDebug("Successfully connected ipc stream."); } return(ipcClientStream); }