/// <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));
        }
Example #4
0
        /// <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);
        }