Ejemplo n.º 1
0
 private void SetState(MessageHandlerState startingOperation)
 {
     lock (this.syncObject)
     {
         Verify.NotDisposed(this);
         MessageHandlerState state = this.state;
         Assumes.False(state.HasFlag(startingOperation));
         this.state |= startingOperation;
     }
 }
Ejemplo n.º 2
0
        /// <summary>
        /// Writes a message to the transport and flushes.
        /// </summary>
        /// <param name="content">The message to write.</param>
        /// <param name="cancellationToken">A token to cancel the write request.</param>
        /// <returns>A task that represents the asynchronous operation.</returns>
        /// <exception cref="InvalidOperationException">Thrown when <see cref="CanWrite"/> returns <c>false</c>.</exception>
        /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is canceled before message transmission begins.</exception>
        /// <exception cref="ObjectDisposedException">Thrown if this instance is disposed before or during transmission.</exception>
        /// <remarks>
        /// Implementations should expect this method to be invoked concurrently
        /// and use a queue to preserve message order as they are transmitted one at a time.
        /// </remarks>
        public async ValueTask WriteAsync(JsonRpcMessage content, CancellationToken cancellationToken)
        {
            Requires.NotNull(content, nameof(content));
            Verify.Operation(this.CanWrite, "No sending stream.");
            cancellationToken.ThrowIfCancellationRequested();

            try
            {
                using (await this.sendingSemaphore.EnterAsync(cancellationToken).ConfigureAwait(false))
                {
                    this.SetState(MessageHandlerState.Writing);
                    try
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                        await this.WriteCoreAsync(content, cancellationToken).ConfigureAwait(false);

                        // When flushing, do NOT honor the caller's CancellationToken since the writing is done
                        // and we must not throw OperationCanceledException back at them as if we hadn't transmitted it.
                        // But *do* cancel flushing if we're being disposed.
                        try
                        {
                            await this.FlushAsync(this.DisposalToken).ConfigureAwait(false);
                        }
                        catch (OperationCanceledException ex) when(this.DisposalToken.IsCancellationRequested)
                        {
                            throw new ObjectDisposedException(this.GetType().FullName, ex);
                        }
                    }
                    finally
                    {
                        lock (this.syncObject)
                        {
                            this.state &= ~MessageHandlerState.Writing;
                            if (this.DisposalToken.IsCancellationRequested)
                            {
                                this.writingCompleted.Set();
                            }
                        }
                    }
                }
            }
            catch (ObjectDisposedException)
            {
                // If already canceled, throw that instead of ObjectDisposedException.
                cancellationToken.ThrowIfCancellationRequested();
                throw;
            }
        }
Ejemplo n.º 3
0
        private bool CheckIfDisposalAppropriate(MessageHandlerState completedOperation = MessageHandlerState.None)
        {
            lock (this.syncObject)
            {
                // Revert the flag of our caller to indicate that writing or reading has finished, if applicable.
                this.state &= ~completedOperation;

                if (!this.DisposalToken.IsCancellationRequested)
                {
                    // Disposal hasn't been requested.
                    return(false);
                }

                // We should dispose ourselves if we have not done so and if reading and writing are not active.
                bool shouldDispose = (this.state & (MessageHandlerState.Reading | MessageHandlerState.Writing | MessageHandlerState.DisposeVirtualMethodInvoked)) == MessageHandlerState.None;
                if (shouldDispose)
                {
                    this.state |= MessageHandlerState.DisposeVirtualMethodInvoked;
                }

                return(shouldDispose);
            }
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Reads a distinct and complete message from the transport, waiting for one if necessary.
        /// </summary>
        /// <param name="cancellationToken">A token to cancel the read request.</param>
        /// <returns>The received message, or <c>null</c> if the underlying transport ends before beginning another message.</returns>
        /// <exception cref="InvalidOperationException">Thrown when <see cref="CanRead"/> returns <c>false</c>.</exception>
        /// <exception cref="System.IO.EndOfStreamException">Thrown if the transport ends while reading a message.</exception>
        /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is canceled before a new message is received.</exception>
        /// <remarks>
        /// Implementations may assume this method is never called before any async result
        /// from a prior call to this method has completed.
        /// </remarks>
        public async ValueTask <JsonRpcMessage?> ReadAsync(CancellationToken cancellationToken)
        {
            Verify.Operation(this.CanRead, "No receiving stream.");
            cancellationToken.ThrowIfCancellationRequested();
            Verify.NotDisposed(this);
            this.SetState(MessageHandlerState.Reading);

            try
            {
                JsonRpcMessage?result = await this.ReadCoreAsync(cancellationToken).ConfigureAwait(false);

                return(result);
            }
            catch (InvalidOperationException ex) when(cancellationToken.IsCancellationRequested)
            {
                // PipeReader.ReadAsync can throw InvalidOperationException in a race where PipeReader.Complete() has been
                // called but we haven't noticed the CancellationToken was canceled yet.
                throw new OperationCanceledException("Reading failed during cancellation.", ex, cancellationToken);
            }
            catch (ObjectDisposedException)
            {
                // If already canceled, throw that instead of ObjectDisposedException.
                cancellationToken.ThrowIfCancellationRequested();
                throw;
            }
            finally
            {
                lock (this.syncObject)
                {
                    this.state &= ~MessageHandlerState.Reading;
                    if (this.DisposalToken.IsCancellationRequested)
                    {
                        this.readingCompleted.Set();
                    }
                }
            }
        }
        /// <summary>
        /// Writes a message to the transport and flushes.
        /// </summary>
        /// <param name="content">The message to write.</param>
        /// <param name="cancellationToken">A token to cancel the write request.</param>
        /// <returns>A task that represents the asynchronous operation.</returns>
        /// <exception cref="InvalidOperationException">Thrown when <see cref="CanWrite"/> returns <c>false</c>.</exception>
        /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken"/> is canceled before message transmission begins.</exception>
        /// <remarks>
        /// Implementations should expect this method to be invoked concurrently
        /// and use a queue to preserve message order as they are transmitted one at a time.
        /// </remarks>
        public async ValueTask WriteAsync(JsonRpcMessage content, CancellationToken cancellationToken)
        {
            Requires.NotNull(content, nameof(content));
            Verify.Operation(this.CanWrite, "No sending stream.");
            cancellationToken.ThrowIfCancellationRequested();

            try
            {
                using (await this.sendingSemaphore.EnterAsync(cancellationToken).ConfigureAwait(false))
                {
                    this.SetState(MessageHandlerState.Writing);
                    try
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                        await this.WriteCoreAsync(content, cancellationToken).ConfigureAwait(false);
                        await this.FlushAsync(cancellationToken).ConfigureAwait(false);
                    }
                    finally
                    {
                        lock (this.syncObject)
                        {
                            this.state &= ~MessageHandlerState.Writing;
                            if (this.DisposalToken.IsCancellationRequested)
                            {
                                this.writingCompleted.Set();
                            }
                        }
                    }
                }
            }
            catch (ObjectDisposedException)
            {
                // If already canceled, throw that instead of ObjectDisposedException.
                cancellationToken.ThrowIfCancellationRequested();
                throw;
            }
        }