public async Task <Stream> AcceptIpcStreamAsync(CancellationToken token) { Stream ipcServerStream = null; _logger?.LogDebug($"Waiting for new ipc connection at endpoint \"{_ipcServerPath}\"."); using var connectTimeoutTokenSource = new CancellationTokenSource(); using var connectTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, connectTimeoutTokenSource.Token); try { connectTimeoutTokenSource.CancelAfter(IpcServerTimeoutMs); ipcServerStream = await _ipcServer.AcceptAsync(connectTokenSource.Token).ConfigureAwait(false); } catch (Exception) { ipcServerStream?.Dispose(); if (connectTimeoutTokenSource.IsCancellationRequested) { _logger?.LogDebug("No ipc stream connected, timing out."); throw new TimeoutException(); } throw; } if (ipcServerStream != null) { _logger?.LogDebug("Successfully connected ipc stream."); } return(ipcServerStream); }
/// <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(); } }