private void SetState(MessageHandlerState startingOperation) { lock (this.syncObject) { Verify.NotDisposed(this); MessageHandlerState state = this.state; Assumes.False(state.HasFlag(startingOperation)); this.state |= startingOperation; } }
/// <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; } }
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); } }
/// <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; } }