From 0be4706a2ca72544b7f176bdd41f27f2d447d11f Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Thu, 22 Feb 2018 12:33:45 -0800 Subject: [PATCH 01/29] custom alloc free custom awaitable --- examples/Telnet.Server/TelnetServerHandler.cs | 7 +- src/DotNetty.Codecs/MessageToByteEncoder.cs | 9 +- .../MessageToMessageEncoder.cs | 13 +- .../Concurrency/AbstractChannelPromise.cs | 174 ++++++++++++++++++ .../AbstractRecyclableChannelPromise.cs | 95 ++++++++++ .../Concurrency/AggregatingPromise.cs | 66 +++++++ .../Concurrency/ChannelFuture.cs | 80 ++++++++ .../Concurrency/CompletedFuture.cs | 37 ++++ .../Concurrency/IChannelFuture.cs | 17 ++ .../Concurrency/IChannelPromise.cs | 16 ++ .../Concurrency/IRecyclable.cs | 12 ++ src/DotNetty.Common/DotNetty.Common.csproj | 5 + src/DotNetty.Common/Utilities/TaskEx.cs | 21 +++ .../Logging/LoggingHandler.cs | 4 +- .../Timeout/IdleStateHandler.cs | 11 +- .../Timeout/WriteTimeoutHandler.cs | 19 +- src/DotNetty.Handlers/Tls/TlsHandler.cs | 22 ++- .../Channels/AbstractChannel.cs | 16 +- .../Channels/AbstractChannelHandlerContext.cs | 84 +++++---- .../Channels/BatchingPendingWriteQueue.cs | 65 +++---- .../Channels/ChannelHandlerAdapter.cs | 3 +- .../Channels/ChannelOutboundBuffer.cs | 49 ++--- .../Channels/DefaultChannelPipeline.cs | 8 +- .../Channels/Embedded/EmbeddedChannel.cs | 18 +- .../Channels/Groups/DefaultChannelGroup.cs | 16 +- .../Groups/DefaultChannelGroupPromise.cs | 68 +++++++ .../Channels/Groups/IChannelGroup.cs | 9 +- src/DotNetty.Transport/Channels/IChannel.cs | 5 +- .../Channels/IChannelHandler.cs | 3 +- .../Channels/IChannelHandlerContext.cs | 4 +- .../Channels/IChannelPipeline.cs | 4 +- .../Channels/IChannelUnsafe.cs | 3 +- .../Channels/PendingWriteQueue.cs | 64 +++---- src/DotNetty.Transport/Channels/Util.cs | 22 +++ .../Frame/LengthFieldPrependerTest.cs | 3 +- .../ChannelExtensions.cs | 2 +- .../ChannelFutureExtensions.cs | 22 +++ test/DotNetty.Tests.End2End/End2EndTests.cs | 2 +- .../AutoReadTests.cs | 4 +- .../BufReleaseTests.cs | 8 +- .../CompositeBufferGatheringWriteTests.cs | 3 +- .../DetectPeerCloseWithoutReadTests.cs | 4 +- .../ExceptionHandlingTests.cs | 3 +- .../ReadPendingTests.cs | 3 +- .../WriteBeforeRegisteredTests.cs | 4 +- .../Sockets/SocketDatagramChannelPerfSpecs.cs | 3 +- .../Utilities/CounterHandlerOutbound.cs | 3 +- .../SocketDatagramChannelMulticastTest.cs | 12 +- .../SocketDatagramChannelUnicastTest.cs | 12 +- 49 files changed, 898 insertions(+), 239 deletions(-) create mode 100644 src/DotNetty.Common/Concurrency/AbstractChannelPromise.cs create mode 100644 src/DotNetty.Common/Concurrency/AbstractRecyclableChannelPromise.cs create mode 100644 src/DotNetty.Common/Concurrency/AggregatingPromise.cs create mode 100644 src/DotNetty.Common/Concurrency/ChannelFuture.cs create mode 100644 src/DotNetty.Common/Concurrency/CompletedFuture.cs create mode 100644 src/DotNetty.Common/Concurrency/IChannelFuture.cs create mode 100644 src/DotNetty.Common/Concurrency/IChannelPromise.cs create mode 100644 src/DotNetty.Common/Concurrency/IRecyclable.cs create mode 100644 src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs create mode 100644 test/DotNetty.Tests.Common/ChannelFutureExtensions.cs diff --git a/examples/Telnet.Server/TelnetServerHandler.cs b/examples/Telnet.Server/TelnetServerHandler.cs index 563384761..7361447f6 100644 --- a/examples/Telnet.Server/TelnetServerHandler.cs +++ b/examples/Telnet.Server/TelnetServerHandler.cs @@ -6,6 +6,7 @@ namespace Telnet.Server using System; using System.Net; using System.Threading.Tasks; + using DotNetty.Common.Concurrency; using DotNetty.Transport.Channels; public class TelnetServerHandler : SimpleChannelInboundHandler @@ -16,7 +17,7 @@ public override void ChannelActive(IChannelHandlerContext contex) contex.WriteAndFlushAsync(string.Format("It is {0} now !\r\n", DateTime.Now)); } - protected override void ChannelRead0(IChannelHandlerContext contex, string msg) + protected override async void ChannelRead0(IChannelHandlerContext contex, string msg) { // Generate and write a response. string response; @@ -35,10 +36,10 @@ protected override void ChannelRead0(IChannelHandlerContext contex, string msg) response = "Did you say '" + msg + "'?\r\n"; } - Task wait_close = contex.WriteAndFlushAsync(response); + ChannelFuture wait_close = contex.WriteAndFlushAsync(response); if (close) { - Task.WaitAll(wait_close); + await wait_close; contex.CloseAsync(); } } diff --git a/src/DotNetty.Codecs/MessageToByteEncoder.cs b/src/DotNetty.Codecs/MessageToByteEncoder.cs index cff1dee2e..21231eb4a 100644 --- a/src/DotNetty.Codecs/MessageToByteEncoder.cs +++ b/src/DotNetty.Codecs/MessageToByteEncoder.cs @@ -7,6 +7,7 @@ namespace DotNetty.Codecs using System.Diagnostics.Contracts; using System.Threading.Tasks; using DotNetty.Buffers; + using DotNetty.Common.Concurrency; using DotNetty.Common.Utilities; using DotNetty.Transport.Channels; @@ -14,12 +15,12 @@ public abstract class MessageToByteEncoder : ChannelHandlerAdapter { public virtual bool AcceptOutboundMessage(object message) => message is T; - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) { Contract.Requires(context != null); IByteBuffer buffer = null; - Task result; + ChannelFuture result; try { if (this.AcceptOutboundMessage(message)) @@ -54,11 +55,11 @@ public override Task WriteAsync(IChannelHandlerContext context, object message) } catch (EncoderException e) { - return TaskEx.FromException(e); + throw;//return TaskEx.FromException(e); } catch (Exception ex) { - return TaskEx.FromException(new EncoderException(ex)); + throw new EncoderException(ex);//return TaskEx.FromException(new EncoderException(ex)); } finally { diff --git a/src/DotNetty.Codecs/MessageToMessageEncoder.cs b/src/DotNetty.Codecs/MessageToMessageEncoder.cs index cecc8ebde..6633d9132 100644 --- a/src/DotNetty.Codecs/MessageToMessageEncoder.cs +++ b/src/DotNetty.Codecs/MessageToMessageEncoder.cs @@ -7,6 +7,7 @@ namespace DotNetty.Codecs using System.Collections.Generic; using System.Threading.Tasks; using DotNetty.Common; + using DotNetty.Common.Concurrency; using DotNetty.Common.Utilities; using DotNetty.Transport.Channels; @@ -18,9 +19,9 @@ public abstract class MessageToMessageEncoder : ChannelHandlerAdapter /// public virtual bool AcceptOutboundMessage(object msg) => msg is T; - public override Task WriteAsync(IChannelHandlerContext ctx, object msg) + public override ChannelFuture WriteAsync(IChannelHandlerContext ctx, object msg) { - Task result; + ChannelFuture result; ThreadLocalObjectList output = null; try { @@ -52,11 +53,11 @@ public override Task WriteAsync(IChannelHandlerContext ctx, object msg) } catch (EncoderException e) { - return TaskEx.FromException(e); + throw;//return TaskEx.FromException(e); } catch (Exception ex) { - return TaskEx.FromException(new EncoderException(ex)); // todo: we don't have a stack on EncoderException but it's present on inner exception. + throw new EncoderException(ex);//return TaskEx.FromException(new EncoderException(ex)); // todo: we don't have a stack on EncoderException but it's present on inner exception. } finally { @@ -79,14 +80,14 @@ public override Task WriteAsync(IChannelHandlerContext ctx, object msg) else { // 0 items in output - must never get here - result = null; + result = default(ChannelFuture); } output.Return(); } else { // output was reset during exception handling - must never get here - result = null; + result = default(ChannelFuture); } } return result; diff --git a/src/DotNetty.Common/Concurrency/AbstractChannelPromise.cs b/src/DotNetty.Common/Concurrency/AbstractChannelPromise.cs new file mode 100644 index 000000000..1e28af054 --- /dev/null +++ b/src/DotNetty.Common/Concurrency/AbstractChannelPromise.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Common.Concurrency +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using System.Runtime.ExceptionServices; + + public abstract class AbstractChannelPromise : IChannelFuture, IChannelPromise + { + protected static readonly Exception CompletedNoException = new Exception(); + + protected Exception exception; + + protected int callbackCount; + protected (Delegate, object)[] callbacks; + + public virtual bool IsCompleted => this.exception != null; + + public virtual bool TryComplete(Exception exception = null) + { + if (this.exception == null) + { + // Set the exception object to the exception passed in or a sentinel value + this.exception = exception ?? CompletedNoException; + this.ExecuteCallbacks(); + return true; + } + + return false; + } + + public IChannelFuture Future => this; + + public bool SetUncancellable() => true; + + public virtual void GetResult() + { + if (!this.IsCompleted) + { + throw new InvalidOperationException("Attempt to get result on not yet completed promise"); + //ThrowHelper.ThrowInvalidOperationException_GetResultNotCompleted(); + } + + this.IsCompletedOrThrow(); + /* + // Change the state from to be canceled -> observed + if (_writerAwaitable.ObserveCancelation()) + { + result._resultFlags |= ResultFlags.Canceled; + } + if (_readerCompletion.IsCompletedOrThrow()) + { + result._resultFlags |= ResultFlags.Completed; + } + */ + } + + public void UnsafeOnCompleted(Action continuation) => this.OnCompleted(continuation); + + public void OnCompleted(Action callback) => this.OnCompleted0(callback, null); + + public void OnCompleted(Action continuation, object state) => this.OnCompleted0(continuation, null); + + protected virtual void OnCompleted0(Delegate callback, object state) + { + if (this.callbacks == null) + { + this.callbacks = new (Delegate, object)[1]; + //this.callbacks = s_completionCallbackPool.Rent(InitialCallbacksSize); + } + + int newIndex = this.callbackCount; + this.callbackCount++; + + if (newIndex == this.callbacks.Length) + { + var newArray = new (Delegate, object)[this.callbacks.Length * 2]; + Array.Copy(this.callbacks, newArray, this.callbacks.Length); + this.callbacks = newArray; + } + + this.callbacks[newIndex] = (callback, state); + + if (this.IsCompleted) + { + this.ExecuteCallbacks(); + } + } + + public static implicit operator ChannelFuture(AbstractChannelPromise promise) => new ChannelFuture(promise); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + bool IsCompletedOrThrow() + { + if (this.exception == null) + { + return false; + } + + if (this.exception != CompletedNoException) + { + this.ThrowLatchedException(); + } + + return true; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void ThrowLatchedException() => ExceptionDispatchInfo.Capture(this.exception).Throw(); + + void ExecuteCallbacks() + { + if (this.callbacks == null || this.callbackCount == 0) + { + return; + } + + try + { + List exceptions = null; + + for (int i = 0; i < this.callbackCount; i++) + { + try + { + (Delegate callback, object state) = this.callbacks[i]; + switch (callback) + { + case Action action: + action(); + break; + case Action action: + action(state); + break; + default: + throw new ArgumentException("action"); + } + } + catch (Exception ex) + { + if (exceptions == null) + { + exceptions = new List(); + } + + exceptions.Add(ex); + } + } + + if (exceptions != null) + { + throw new AggregateException(exceptions); + } + } + finally + { + this.ClearCallbacks(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void ClearCallbacks() + { + if (this.callbackCount > 0) + { + this.callbackCount = 0; + Array.Clear(this.callbacks, 0, this.callbacks.Length); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/AbstractRecyclableChannelPromise.cs b/src/DotNetty.Common/Concurrency/AbstractRecyclableChannelPromise.cs new file mode 100644 index 000000000..01b6bb8ab --- /dev/null +++ b/src/DotNetty.Common/Concurrency/AbstractRecyclableChannelPromise.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Common.Concurrency +{ + using System; + using System.Runtime.CompilerServices; + + public abstract class AbstractRecyclableChannelPromise : AbstractChannelPromise + { + protected IEventExecutor executor; + protected bool recycled; + + protected readonly ThreadLocalPool.Handle handle; + + protected AbstractRecyclableChannelPromise(ThreadLocalPool.Handle handle) + { + this.handle = handle; + } + + public override bool IsCompleted + { + get + { + this.ThrowIfRecycled(); + return base.IsCompleted; + } + } + + public override void GetResult() + { + this.ThrowIfRecycled(); + base.GetResult(); + } + + public override bool TryComplete(Exception exception = null) + { + this.ThrowIfRecycled(); + + bool completed; + try + { + completed = base.TryComplete(exception); + } + catch + { + this.executor.Execute(this.Recycle); + throw; + } + + if (completed) + { + this.executor.Execute(this.Recycle); + } + + return completed; + } + + protected void Init(IEventExecutor executor) + { + this.executor = executor; + this.recycled = false; + } + + protected virtual void Recycle() + { + this.executor = null; + this.exception = null; + this.ClearCallbacks(); + this.recycled = true; + + this.handle.Release(this); + } + + protected override void OnCompleted0(Delegate callback, object state) + { + this.ThrowIfRecycled(); + base.OnCompleted0(callback, state); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void ThrowIfRecycled() + { + if (this.recycled) + { + throw new InvalidOperationException("Attempt to use recycled channel promise"); + } + + if (this.executor == null) + { + throw new InvalidOperationException("Attempt to use recyclable channel promise without executor"); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/AggregatingPromise.cs b/src/DotNetty.Common/Concurrency/AggregatingPromise.cs new file mode 100644 index 000000000..0cbf0ed97 --- /dev/null +++ b/src/DotNetty.Common/Concurrency/AggregatingPromise.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Common.Concurrency +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + + public sealed class AggregatingPromise : AbstractChannelPromise + { + int successCount; + int failureCount; + + IList failures; + + public AggregatingPromise(IList futures) + { + Contract.Requires(futures != null); + + foreach (ChannelFuture future in futures) + { + future.OnCompleted( + () => + { + try + { + future.GetResult(); + this.successCount++; + } + catch(Exception ex) + { + this.failureCount++; + + if (this.failures == null) + { + this.failures = new List(); + } + this.failures.Add(ex); + } + + bool callSetDone = this.successCount + this.failureCount == futures.Count; + Contract.Assert(this.successCount + this.failureCount <= futures.Count); + + if (callSetDone) + { + if (this.failureCount > 0) + { + this.TryComplete(new AggregateException(this.failures)); + } + else + { + this.TryComplete(); + } + } + }); + } + + // Done on arrival? + if (futures.Count == 0) + { + this.TryComplete(); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/ChannelFuture.cs b/src/DotNetty.Common/Concurrency/ChannelFuture.cs new file mode 100644 index 000000000..030cbfe8b --- /dev/null +++ b/src/DotNetty.Common/Concurrency/ChannelFuture.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Common.Concurrency +{ + using System; + using System.Runtime.CompilerServices; + using System.Runtime.ExceptionServices; + + public struct ChannelFuture : ICriticalNotifyCompletion + { + public static readonly ChannelFuture Completed = new ChannelFuture(); + + public static ChannelFuture FromException(Exception ex) => new ChannelFuture(ex); + + readonly object state; + + public ChannelFuture(IChannelFuture future) : this((object)future) + { + this.state = future; + } + + ChannelFuture(Exception ex) : this(ExceptionDispatchInfo.Capture(ex)) + { + + } + + ChannelFuture(object state) + { + this.state = state; + } + + public ChannelFuture GetAwaiter() => this; + + public bool IsCompleted => this.state is IChannelFuture future ? future.IsCompleted : true; + + public void GetResult() + { + switch (this.state) + { + case null: + break; + case ExceptionDispatchInfo edi: + edi.Throw(); + break; + case IChannelFuture future: + future.GetResult(); + break; + default: + throw new InvalidOperationException("should not come here"); + } + } + + public void OnCompleted(Action continuation) + { + if (this.state is IChannelFuture future) + { + future.OnCompleted(continuation); + } + else + { + continuation(); + } + } + + public void OnCompleted(Action continuation, object state) + { + if (this.state is IChannelFuture future) + { + future.OnCompleted(continuation, state); + } + else + { + continuation(state); + } + } + + public void UnsafeOnCompleted(Action continuation) => this.OnCompleted(continuation); + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/CompletedFuture.cs b/src/DotNetty.Common/Concurrency/CompletedFuture.cs new file mode 100644 index 000000000..d5947b3ac --- /dev/null +++ b/src/DotNetty.Common/Concurrency/CompletedFuture.cs @@ -0,0 +1,37 @@ +namespace DotNetty.Common.Concurrency +{ + using System; + + public class CompletedFuture : IChannelFuture + { + public static readonly IChannelFuture Instance = new CompletedFuture(); + + CompletedFuture() + { + + } + + public void OnCompleted(Action continuation) => continuation(); + + public void UnsafeOnCompleted(Action continuation) => this.OnCompleted(continuation); + + public bool IsCompleted => true; + + public void GetResult() + { + + } + + public void OnCompleted(Action continuation, object state) => continuation(state); + + public void Init(IEventExecutor executor) + { + + } + + public void Recycle() + { + + } + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/IChannelFuture.cs b/src/DotNetty.Common/Concurrency/IChannelFuture.cs new file mode 100644 index 000000000..73bf77065 --- /dev/null +++ b/src/DotNetty.Common/Concurrency/IChannelFuture.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Common.Concurrency +{ + using System; + using System.Runtime.CompilerServices; + + public interface IChannelFuture : ICriticalNotifyCompletion + { + bool IsCompleted { get; } + + void GetResult(); + + void OnCompleted(Action continuation, object state); + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/IChannelPromise.cs b/src/DotNetty.Common/Concurrency/IChannelPromise.cs new file mode 100644 index 000000000..b79ae8d97 --- /dev/null +++ b/src/DotNetty.Common/Concurrency/IChannelPromise.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Common.Concurrency +{ + using System; + + public interface IChannelPromise + { + bool TryComplete(Exception exception = null); + + IChannelFuture Future { get; } + + bool SetUncancellable(); + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/IRecyclable.cs b/src/DotNetty.Common/Concurrency/IRecyclable.cs new file mode 100644 index 000000000..3777dbca1 --- /dev/null +++ b/src/DotNetty.Common/Concurrency/IRecyclable.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Common.Concurrency +{ + public interface IRecyclable + { + void Init(IEventExecutor executor); + + void Recycle(); + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/DotNetty.Common.csproj b/src/DotNetty.Common/DotNetty.Common.csproj index c187ae363..e08763cda 100644 --- a/src/DotNetty.Common/DotNetty.Common.csproj +++ b/src/DotNetty.Common/DotNetty.Common.csproj @@ -42,4 +42,9 @@ + + + C:\Users\mkim\.nuget\packages\system.valuetuple\4.3.0\lib\netstandard1.0\System.ValueTuple.dll + + \ No newline at end of file diff --git a/src/DotNetty.Common/Utilities/TaskEx.cs b/src/DotNetty.Common/Utilities/TaskEx.cs index d0093a97d..2f01243d4 100644 --- a/src/DotNetty.Common/Utilities/TaskEx.cs +++ b/src/DotNetty.Common/Utilities/TaskEx.cs @@ -80,6 +80,27 @@ public static void LinkOutcome(this Task task, TaskCompletionSource taskCompleti } } + public static void LinkOutcome(this ChannelFuture future, IChannelPromise promise) + { + if (future.IsCompleted) + { + try + { + future.GetResult(); + promise.TryComplete(); + } + catch (Exception ex) + { + promise.TryComplete(ex); + } + } + else + { + future.OnCompleted(() => LinkOutcome(future, promise)); + } + } + + static class LinkOutcomeActionHost { public static readonly Action, object> Action = diff --git a/src/DotNetty.Handlers/Logging/LoggingHandler.cs b/src/DotNetty.Handlers/Logging/LoggingHandler.cs index 1115d1f41..f6e24557c 100644 --- a/src/DotNetty.Handlers/Logging/LoggingHandler.cs +++ b/src/DotNetty.Handlers/Logging/LoggingHandler.cs @@ -8,6 +8,7 @@ namespace DotNetty.Handlers.Logging using System.Text; using System.Threading.Tasks; using DotNetty.Buffers; + using DotNetty.Common.Concurrency; using DotNetty.Common.Internal.Logging; using DotNetty.Transport.Channels; @@ -209,6 +210,7 @@ public override void ChannelRead(IChannelHandlerContext ctx, object message) ctx.FireChannelRead(message); } + public override Task WriteAsync(IChannelHandlerContext ctx, object msg) public override void ChannelReadComplete(IChannelHandlerContext ctx) { if (this.Logger.IsEnabled(this.InternalLevel)) @@ -251,7 +253,7 @@ public override void Read(IChannelHandlerContext ctx) ctx.Read(); } - public override Task WriteAsync(IChannelHandlerContext ctx, object msg) + public override ChannelFuture WriteAsync(IChannelHandlerContext ctx, object msg) { if (this.Logger.IsEnabled(this.InternalLevel)) { diff --git a/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs b/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs index f20f52fab..62348081d 100644 --- a/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs +++ b/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs @@ -96,7 +96,7 @@ public class IdleStateHandler : ChannelDuplexHandler { static readonly TimeSpan MinTimeout = TimeSpan.FromMilliseconds(1); - readonly Action writeListener; + readonly Action writeListener; readonly bool observeOutput; readonly TimeSpan readerIdleTime; @@ -201,7 +201,7 @@ public IdleStateHandler(bool observeOutput, ? TimeUtil.Max(allIdleTime, IdleStateHandler.MinTimeout) : TimeSpan.Zero; - this.writeListener = new Action(antecedent => + this.writeListener = new Action(() => { this.lastWriteTime = this.Ticks(); this.firstWriterIdleEvent = this.firstAllIdleEvent = true; @@ -303,12 +303,13 @@ public override void ChannelReadComplete(IChannelHandlerContext context) context.FireChannelReadComplete(); } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) { if (this.writerIdleTime.Ticks > 0 || this.allIdleTime.Ticks > 0) { - Task task = context.WriteAsync(message); - task.ContinueWith(this.writeListener, TaskContinuationOptions.ExecuteSynchronously); + ChannelFuture task = context.WriteAsync(message); + //task.ContinueWith(this.writeListener, TaskContinuationOptions.ExecuteSynchronously); + task.OnCompleted(this.writeListener); return task; } diff --git a/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs b/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs index 25fa214fb..78d914762 100644 --- a/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs +++ b/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs @@ -83,9 +83,9 @@ public WriteTimeoutHandler(TimeSpan timeout) : TimeSpan.Zero; } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) { - Task task = context.WriteAsync(message); + ChannelFuture task = context.WriteAsync(message); if (this.timeout.Ticks > 0) { @@ -106,7 +106,7 @@ public override void HandlerRemoved(IChannelHandlerContext context) } } - void ScheduleTimeout(IChannelHandlerContext context, Task future) + void ScheduleTimeout(IChannelHandlerContext context, ChannelFuture future) { // Schedule a timeout. var task = new WriteTimeoutTask(context, future, this); @@ -118,7 +118,8 @@ void ScheduleTimeout(IChannelHandlerContext context, Task future) this.AddWriteTimeoutTask(task); // Cancel the scheduled timeout if the flush promise is complete. - future.ContinueWith(WriteTimeoutTask.OperationCompleteAction, task, TaskContinuationOptions.ExecuteSynchronously); + future.OnCompleted(WriteTimeoutTask.OperationCompleteAction, task); + // future.ContinueWith(WriteTimeoutTask.OperationCompleteAction, task, TaskContinuationOptions.ExecuteSynchronously); } } @@ -146,22 +147,22 @@ protected virtual void WriteTimedOut(IChannelHandlerContext context) } } - sealed class WriteTimeoutTask : IRunnable + sealed class WriteTimeoutTask : AbstractChannelPromise, IRunnable { readonly WriteTimeoutHandler handler; readonly IChannelHandlerContext context; - readonly Task future; + readonly ChannelFuture future; - public static readonly Action OperationCompleteAction = HandleOperationComplete; + public static readonly Action OperationCompleteAction = HandleOperationComplete; - public WriteTimeoutTask(IChannelHandlerContext context, Task future, WriteTimeoutHandler handler) + public WriteTimeoutTask(IChannelHandlerContext context, ChannelFuture future, WriteTimeoutHandler handler) { this.context = context; this.future = future; this.handler = handler; } - static void HandleOperationComplete(Task future, object state) + static void HandleOperationComplete(object state) { var writeTimeoutTask = (WriteTimeoutTask) state; diff --git a/src/DotNetty.Handlers/Tls/TlsHandler.cs b/src/DotNetty.Handlers/Tls/TlsHandler.cs index 063aa2db9..39189d90e 100644 --- a/src/DotNetty.Handlers/Tls/TlsHandler.cs +++ b/src/DotNetty.Handlers/Tls/TlsHandler.cs @@ -36,7 +36,7 @@ public sealed class TlsHandler : ByteToMessageDecoder int packetLength; volatile IChannelHandlerContext capturedContext; BatchingPendingWriteQueue pendingUnencryptedWrites; - Task lastContextWriteTask; + ChannelFuture lastContextWriteTask; bool firedChannelRead; IByteBuffer pendingSslStreamReadBuffer; Task pendingSslStreamReadFuture; @@ -508,11 +508,11 @@ bool EnsureAuthenticated() return oldState.Has(TlsHandlerState.Authenticated); } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) { if (!(message is IByteBuffer)) { - return TaskEx.FromException(new UnsupportedMessageTypeException(message, typeof(IByteBuffer))); + throw new UnsupportedMessageTypeException(message, typeof(IByteBuffer)); } return this.pendingUnencryptedWrites.Add(message); } @@ -572,12 +572,12 @@ void Wrap(IChannelHandlerContext context) buf.ReadBytes(this.sslStream, buf.ReadableBytes); // this leads to FinishWrap being called 0+ times buf.Release(); - TaskCompletionSource promise = this.pendingUnencryptedWrites.Remove(); - Task task = this.lastContextWriteTask; - if (task != null) + IChannelPromise promise = this.pendingUnencryptedWrites.Remove(); + ChannelFuture task = this.lastContextWriteTask; + if (!task.IsCompleted) { task.LinkOutcome(promise); - this.lastContextWriteTask = null; + this.lastContextWriteTask = ChannelFuture.Completed; } else { @@ -609,7 +609,7 @@ void FinishWrap(byte[] buffer, int offset, int count) this.lastContextWriteTask = this.capturedContext.WriteAsync(output); } - Task FinishWrapNonAppDataAsync(byte[] buffer, int offset, int count) + ChannelFuture FinishWrapNonAppDataAsync(byte[] buffer, int offset, int count) { var future = this.capturedContext.WriteAndFlushAsync(Unpooled.WrappedBuffer(buffer, offset, count)); this.ReadIfNeeded(this.capturedContext); @@ -814,8 +814,10 @@ IAsyncResult PrepareSyncReadResult(int readBytes, object state) public override void Write(byte[] buffer, int offset, int count) => this.owner.FinishWrap(buffer, offset, count); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => this.owner.FinishWrapNonAppDataAsync(buffer, offset, count); + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + await this.owner.FinishWrapNonAppDataAsync(buffer, offset, count); + } #if !NETSTANDARD1_3 static readonly Action WriteCompleteCallback = HandleChannelWriteComplete; diff --git a/src/DotNetty.Transport/Channels/AbstractChannel.cs b/src/DotNetty.Transport/Channels/AbstractChannel.cs index 10f06f7a5..61a873abe 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannel.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannel.cs @@ -189,9 +189,9 @@ public IChannel Read() return this; } - public Task WriteAsync(object msg) => this.pipeline.WriteAsync(msg); + public ChannelFuture WriteAsync(object msg) => this.pipeline.WriteAsync(msg); - public Task WriteAndFlushAsync(object message) => this.pipeline.WriteAndFlushAsync(message); + public ChannelFuture WriteAndFlushAsync(object message) => this.pipeline.WriteAndFlushAsync(message); public Task CloseCompletion => this.closeFuture.Task; @@ -670,7 +670,7 @@ public void BeginRead() } } - public Task WriteAsync(object msg) + public ChannelFuture WriteAsync(object msg) { this.AssertEventLoop(); @@ -684,7 +684,8 @@ public Task WriteAsync(object msg) // release message now to prevent resource-leak ReferenceCountUtil.Release(msg); - return TaskEx.FromException(new ClosedChannelException()); + throw new ClosedChannelException(); + //return TaskEx.FromException(new ClosedChannelException()); } int size; @@ -700,13 +701,10 @@ public Task WriteAsync(object msg) catch (Exception t) { ReferenceCountUtil.Release(msg); - - return TaskEx.FromException(t); + throw; //return TaskEx.FromException(t); } - var promise = new TaskCompletionSource(); - outboundBuffer.AddMessage(msg, size, promise); - return promise.Task; + return outboundBuffer.AddMessage(msg, size); } public void Flush() diff --git a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs index 6401d5c1f..0265599d3 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs @@ -793,16 +793,16 @@ void InvokeRead() } } - public Task WriteAsync(object msg) + public ChannelFuture WriteAsync(object msg) { Contract.Requires(msg != null); // todo: check for cancellation return this.WriteAsync(msg, false); } - Task InvokeWriteAsync(object msg) => this.Added ? this.InvokeWriteAsync0(msg) : this.WriteAsync(msg); + ChannelFuture InvokeWriteAsync(object msg) => this.Added ? this.InvokeWriteAsync0(msg) : this.WriteAsync(msg); - Task InvokeWriteAsync0(object msg) + ChannelFuture InvokeWriteAsync0(object msg) { try { @@ -810,7 +810,7 @@ Task InvokeWriteAsync0(object msg) } catch (Exception ex) { - return ComposeExceptionTask(ex); + return ChannelFuture.FromException(ex); } } @@ -853,7 +853,7 @@ void InvokeFlush0() } } - public Task WriteAndFlushAsync(object message) + public ChannelFuture WriteAndFlushAsync(object message) { Contract.Requires(message != null); // todo: check for cancellation @@ -861,18 +861,18 @@ public Task WriteAndFlushAsync(object message) return this.WriteAsync(message, true); } - Task InvokeWriteAndFlushAsync(object msg) + ChannelFuture InvokeWriteAndFlushAsync(object msg) { if (this.Added) { - Task task = this.InvokeWriteAsync0(msg); + ChannelFuture task = this.InvokeWriteAsync0(msg); this.InvokeFlush0(); return task; } return this.WriteAndFlushAsync(msg); } - Task WriteAsync(object msg, bool flush) + ChannelFuture WriteAsync(object msg, bool flush) { AbstractChannelHandlerContext next = this.FindContextOutbound(); object m = this.pipeline.Touch(msg, next); @@ -885,12 +885,11 @@ Task WriteAsync(object msg, bool flush) } else { - var promise = new TaskCompletionSource(); AbstractWriteTask task = flush - ? WriteAndFlushTask.NewInstance(next, m, promise) - : (AbstractWriteTask)WriteTask.NewInstance(next, m, promise); - SafeExecuteOutbound(nextExecutor, task, promise, msg); - return promise.Task; + ? WriteAndFlushTask.NewInstance(next, m) + : (AbstractWriteTask)WriteTask.NewInstance(next, m); + SafeExecuteOutbound(nextExecutor, task, msg); + return task; } } @@ -952,7 +951,7 @@ static Task SafeExecuteOutboundAsync(IEventExecutor executor, Func functio return promise.Task; } - static void SafeExecuteOutbound(IEventExecutor executor, IRunnable task, TaskCompletionSource promise, object msg) + static void SafeExecuteOutbound(IEventExecutor executor, AbstractWriteTask task, object msg) { try { @@ -962,7 +961,7 @@ static void SafeExecuteOutbound(IEventExecutor executor, IRunnable task, TaskCom { try { - promise.TrySetException(cause); + task.TryComplete(cause); } finally { @@ -976,7 +975,7 @@ static void SafeExecuteOutbound(IEventExecutor executor, IRunnable task, TaskCom public override string ToString() => $"{typeof(IChannelHandlerContext).Name} ({this.Name}, {this.Channel})"; - abstract class AbstractWriteTask : IRunnable + abstract class AbstractWriteTask : AbstractRecyclableChannelPromise, IRunnable { static readonly bool EstimateTaskSizeOnSubmit = SystemPropertyUtil.GetBoolean("io.netty.transport.estimateSizeOnSubmit", true); @@ -985,17 +984,15 @@ abstract class AbstractWriteTask : IRunnable static readonly int WriteTaskOverhead = SystemPropertyUtil.GetInt("io.netty.transport.writeTaskSizeOverhead", 56); - ThreadLocalPool.Handle handle; AbstractChannelHandlerContext ctx; object msg; - TaskCompletionSource promise; int size; - protected static void Init(AbstractWriteTask task, AbstractChannelHandlerContext ctx, object msg, TaskCompletionSource promise) + protected static void Init(AbstractWriteTask task, AbstractChannelHandlerContext ctx, object msg) { + task.Init(ctx.Executor); task.ctx = ctx; task.msg = msg; - task.promise = promise; if (EstimateTaskSizeOnSubmit) { @@ -1018,9 +1015,9 @@ protected static void Init(AbstractWriteTask task, AbstractChannelHandlerContext } } - protected AbstractWriteTask(ThreadLocalPool.Handle handle) + protected AbstractWriteTask(ThreadLocalPool.Handle handle) : base(handle) { - this.handle = handle; + } public void Run() @@ -1033,28 +1030,42 @@ public void Run() { buffer?.DecrementPendingOutboundBytes(this.size); } - this.WriteAsync(this.ctx, this.msg).LinkOutcome(this.promise); + + this.WriteAsync(this.ctx, this.msg).LinkOutcome(this); + } + catch (Exception ex) + { + this.TryComplete(ex); } finally { // Set to null so the GC can collect them directly this.ctx = null; this.msg = null; - this.promise = null; - this.handle.Release(this); + + //this.Recycle(); + //this.handle.Release(this); } } - protected virtual Task WriteAsync(AbstractChannelHandlerContext ctx, object msg) => ctx.InvokeWriteAsync(msg); - } - sealed class WriteTask : AbstractWriteTask { + protected virtual ChannelFuture WriteAsync(AbstractChannelHandlerContext ctx, object msg) => ctx.InvokeWriteAsync(msg); + /*public override void Recycle() + { + base.Recycle(); + this.handle.Release(this); + }*/ + } + + + sealed class WriteTask : AbstractWriteTask + { static readonly ThreadLocalPool Recycler = new ThreadLocalPool(handle => new WriteTask(handle)); - public static WriteTask NewInstance(AbstractChannelHandlerContext ctx, object msg, TaskCompletionSource promise) + public static WriteTask NewInstance(AbstractChannelHandlerContext ctx, object msg) { WriteTask task = Recycler.Take(); - Init(task, ctx, msg, promise); + Init(task, ctx, msg); return task; } @@ -1065,14 +1076,13 @@ public static WriteTask NewInstance(AbstractChannelHandlerContext ctx, object ms } sealed class WriteAndFlushTask : AbstractWriteTask - { - + { static readonly ThreadLocalPool Recycler = new ThreadLocalPool(handle => new WriteAndFlushTask(handle)); - public static WriteAndFlushTask NewInstance( - AbstractChannelHandlerContext ctx, object msg, TaskCompletionSource promise) { + public static WriteAndFlushTask NewInstance(AbstractChannelHandlerContext ctx, object msg) + { WriteAndFlushTask task = Recycler.Take(); - Init(task, ctx, msg, promise); + Init(task, ctx, msg); return task; } @@ -1081,9 +1091,9 @@ public static WriteAndFlushTask NewInstance( { } - protected override Task WriteAsync(AbstractChannelHandlerContext ctx, object msg) + protected override ChannelFuture WriteAsync(AbstractChannelHandlerContext ctx, object msg) { - Task result = base.WriteAsync(ctx, msg); + ChannelFuture result = base.WriteAsync(ctx, msg); ctx.InvokeFlush(); return result; } diff --git a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs index 8d2fc2278..d727567a9 100644 --- a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs @@ -64,7 +64,7 @@ public int Size } /// Add the given msg and returns for completion of processing msg. - public Task Add(object msg) + public ChannelFuture Add(object msg) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(msg != null); @@ -82,12 +82,11 @@ public Task Add(object msg) if (canBundle) { currentTail.Add(msg, messageSize); - return currentTail.Promise.Task; + return currentTail; } } - var promise = new TaskCompletionSource(); - PendingWrite write = PendingWrite.NewInstance(msg, messageSize, promise); + PendingWrite write = PendingWrite.NewInstance(this.ctx.Executor, msg, messageSize); if (currentTail == null) { this.tail = this.head = write; @@ -102,7 +101,7 @@ public Task Add(object msg) // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 this.buffer?.IncrementPendingOutboundBytes(messageSize); - return promise.Task; + return write; } /// @@ -124,9 +123,9 @@ public void RemoveAndFailAll(Exception cause) { PendingWrite next = write.Next; ReleaseMessages(write.Messages); - TaskCompletionSource promise = write.Promise; + Util.SafeSetFailure(write, cause, Logger); this.Recycle(write, false); - Util.SafeSetFailure(promise, cause, Logger); + write = next; } this.AssertEmpty(); @@ -149,8 +148,7 @@ public void RemoveAndFail(Exception cause) return; } ReleaseMessages(write.Messages); - TaskCompletionSource promise = write.Promise; - Util.SafeSetFailure(promise, cause, Logger); + Util.SafeSetFailure(write, cause, Logger); this.Recycle(write, true); } @@ -162,7 +160,7 @@ public void RemoveAndFail(Exception cause) /// if something was written and null if the /// is empty. /// - public Task RemoveAndWriteAllAsync() + public ChannelFuture RemoveAndWriteAllAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); @@ -175,7 +173,7 @@ public Task RemoveAndWriteAllAsync() if (write == null) { // empty so just return null - return null; + return ChannelFuture.Completed; } // Guard against re-entrance by directly reset @@ -183,19 +181,19 @@ public Task RemoveAndWriteAllAsync() int currentSize = this.size; this.size = 0; - var tasks = new List(currentSize); + var tasks = new List(currentSize); while (write != null) { PendingWrite next = write.Next; object msg = write.Messages; - TaskCompletionSource promise = write.Promise; this.Recycle(write, false); - this.ctx.WriteAsync(msg).LinkOutcome(promise); - tasks.Add(promise.Task); + this.ctx.WriteAsync(msg).LinkOutcome(write); + + tasks.Add(write); write = next; } this.AssertEmpty(); - return Task.WhenAll(tasks); + return new ChannelFuture(new AggregatingPromise(tasks)); } void AssertEmpty() => Contract.Assert(this.tail == null && this.head == null && this.size == 0); @@ -208,27 +206,26 @@ public Task RemoveAndWriteAllAsync() /// if something was written and null if the /// is empty. /// - public Task RemoveAndWriteAsync() + public ChannelFuture RemoveAndWriteAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { - return null; + return ChannelFuture.Completed; } object msg = write.Messages; - TaskCompletionSource promise = write.Promise; this.Recycle(write, true); - this.ctx.WriteAsync(msg).LinkOutcome(promise); - return promise.Task; + this.ctx.WriteAsync(msg).LinkOutcome(write); + return new ChannelFuture(write); } /// /// Removes a pending write operation and release it's message via . /// /// of the pending write or null if the queue is empty. - public TaskCompletionSource Remove() + public IChannelPromise Remove() { Contract.Assert(this.ctx.Executor.InEventLoop); @@ -237,10 +234,10 @@ public TaskCompletionSource Remove() { return null; } - TaskCompletionSource promise = write.Promise; + //TaskCompletionSource promise = write.Promise; ReferenceCountUtil.SafeRelease(write.Messages); this.Recycle(write, true); - return promise; + return write; } /// @@ -303,7 +300,8 @@ void Recycle(PendingWrite write, bool update) } } - write.Recycle(); + //write.Recycle(); + // We need to guard against null as channel.unsafe().outboundBuffer() may returned null // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 @@ -319,27 +317,24 @@ static void ReleaseMessages(List messages) } /// Holds all meta-data and construct the linked-list structure. - sealed class PendingWrite + sealed class PendingWrite : AbstractRecyclableChannelPromise { static readonly ThreadLocalPool Pool = new ThreadLocalPool(handle => new PendingWrite(handle)); - readonly ThreadLocalPool.Handle handle; public PendingWrite Next; public long Size; - public TaskCompletionSource Promise; public readonly List Messages; - PendingWrite(ThreadLocalPool.Handle handle) + PendingWrite(ThreadLocalPool.Handle handle) : base(handle) { this.Messages = new List(); - this.handle = handle; } - public static PendingWrite NewInstance(object msg, int size, TaskCompletionSource promise) + public static PendingWrite NewInstance(IEventExecutor executor, object msg, int size) { PendingWrite write = Pool.Take(); + write.Init(executor); write.Add(msg, size); - write.Promise = promise; return write; } @@ -349,13 +344,13 @@ public void Add(object msg, int size) this.Size += size; } - public void Recycle() + protected override void Recycle() { this.Size = 0; this.Next = null; this.Messages.Clear(); - this.Promise = null; - this.handle.Release(this); + base.Recycle(); + //this.handle.Release(this); } } } diff --git a/src/DotNetty.Transport/Channels/ChannelHandlerAdapter.cs b/src/DotNetty.Transport/Channels/ChannelHandlerAdapter.cs index 243dfb657..e2ec79fc7 100644 --- a/src/DotNetty.Transport/Channels/ChannelHandlerAdapter.cs +++ b/src/DotNetty.Transport/Channels/ChannelHandlerAdapter.cs @@ -7,6 +7,7 @@ namespace DotNetty.Transport.Channels using System.Net; using System.Threading.Tasks; using DotNetty.Common.Utilities; + using DotNetty.Common.Concurrency; public class ChannelHandlerAdapter : IChannelHandler { @@ -47,7 +48,7 @@ public virtual void HandlerRemoved(IChannelHandlerContext context) public virtual void UserEventTriggered(IChannelHandlerContext context, object evt) => context.FireUserEventTriggered(evt); [Skip] - public virtual Task WriteAsync(IChannelHandlerContext context, object message) => context.WriteAsync(message); + public virtual ChannelFuture WriteAsync(IChannelHandlerContext context, object message) => context.WriteAsync(message); [Skip] public virtual void Flush(IChannelHandlerContext context) => context.Flush(); diff --git a/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs b/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs index 960091015..9162a04ce 100644 --- a/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs +++ b/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs @@ -11,7 +11,11 @@ namespace DotNetty.Transport.Channels using System; using System.Collections.Generic; using System.Diagnostics; + using System.ComponentModel; using System.Diagnostics.Contracts; + using System.Runtime.CompilerServices; + using System.Runtime.ExceptionServices; + using System.Security.Cryptography; using System.Threading; using DotNetty.Buffers; using DotNetty.Common; @@ -58,10 +62,9 @@ internal ChannelOutboundBuffer(IChannel channel) /// /// The message to add to the buffer. /// The size of the message. - /// The to notify once the message is written. - public void AddMessage(object msg, int size, TaskCompletionSource promise) + public ChannelFuture AddMessage(object msg, int size) { - Entry entry = Entry.NewInstance(msg, size, promise); + Entry entry = Entry.NewInstance(this.channel.EventLoop, msg, size); if (this.tailEntry == null) { this.flushedEntry = null; @@ -81,6 +84,8 @@ public void AddMessage(object msg, int size, TaskCompletionSource promise) // increment pending bytes after adding message to the unflushed arrays. // See https://github.com/netty/netty/issues/1619 this.IncrementPendingOutboundBytes(size, false); + + return entry; } /// @@ -104,7 +109,7 @@ public void AddFlush() do { this.flushed++; - if (!entry.Promise.SetUncancellable()) + if (!entry.SetUncancellable()) { // Was cancelled so make sure we free up memory and notify about the freed bytes int pending = entry.Cancel(); @@ -201,7 +206,6 @@ public bool Remove() } object msg = e.Message; - TaskCompletionSource promise = e.Promise; int size = e.PendingSize; this.RemoveEntry(e); @@ -210,13 +214,10 @@ public bool Remove() { // only release message, notify and decrement if it was not canceled before. ReferenceCountUtil.SafeRelease(msg); - SafeSuccess(promise); + Util.SafeSetSuccess(e, Logger); this.DecrementPendingOutboundBytes(size, false, true); } - // recycle the entry - e.Recycle(); - return true; } @@ -239,7 +240,7 @@ bool Remove0(Exception cause, bool notifyWritability) } object msg = e.Message; - TaskCompletionSource promise = e.Promise; + //TaskCompletionSource promise = e.Promise; int size = e.PendingSize; this.RemoveEntry(e); @@ -248,12 +249,14 @@ bool Remove0(Exception cause, bool notifyWritability) { // only release message, fail and decrement if it was not canceled before. ReferenceCountUtil.SafeRelease(msg); - SafeFail(promise, cause); + + Util.SafeSetFailure(e, cause, Logger); + this.DecrementPendingOutboundBytes(size, false, notifyWritability); } // recycle the entry - e.Recycle(); + //e.Recycle(); return true; } @@ -665,7 +668,7 @@ internal void Close(Exception cause, bool allowChannelOpen) if (!e.Cancelled) { ReferenceCountUtil.SafeRelease(e.Message); - SafeFail(e.Promise, cause); + Util.SafeSetFailure(e, cause, Logger); } e = e.RecycleAndGetNext(); } @@ -783,31 +786,29 @@ public interface IMessageProcessor bool ProcessMessage(object msg); } - sealed class Entry + sealed class Entry : AbstractRecyclableChannelPromise { static readonly ThreadLocalPool Pool = new ThreadLocalPool(h => new Entry(h)); - readonly ThreadLocalPool.Handle handle; public Entry Next; public object Message; public ArraySegment[] Buffers; public ArraySegment Buffer; - public TaskCompletionSource Promise; public int PendingSize; public int Count = -1; public bool Cancelled; - Entry(ThreadLocalPool.Handle handle) + Entry(ThreadLocalPool.Handle handle) + : base(handle) { - this.handle = handle; } - public static Entry NewInstance(object msg, int size, TaskCompletionSource promise) + public static Entry NewInstance(IEventExecutor executor, object msg, int size) { Entry entry = Pool.Take(); + entry.Init(executor); entry.Message = msg; entry.PendingSize = size; - entry.Promise = promise; return entry; } @@ -830,23 +831,23 @@ public int Cancel() return 0; } - public void Recycle() + protected override void Recycle() { this.Next = null; this.Buffers = null; this.Buffer = new ArraySegment(); this.Message = null; - this.Promise = null; this.PendingSize = 0; this.Count = -1; this.Cancelled = false; - this.handle.Release(this); + + base.Recycle(); } public Entry RecycleAndGetNext() { Entry next = this.Next; - this.Recycle(); + //this.Recycle(); return next; } } diff --git a/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs b/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs index 1531f5676..e1bf13ca9 100644 --- a/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs +++ b/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs @@ -822,7 +822,7 @@ public IChannelPipeline Read() return this; } - public Task WriteAsync(object msg) => this.tail.WriteAsync(msg); + public ChannelFuture WriteAsync(object msg) => this.tail.WriteAsync(msg); public IChannelPipeline Flush() { @@ -830,7 +830,7 @@ public IChannelPipeline Flush() return this; } - public Task WriteAndFlushAsync(object msg) => this.tail.WriteAndFlushAsync(msg); + public ChannelFuture WriteAndFlushAsync(object msg) => this.tail.WriteAndFlushAsync(msg); string FilterName(string name, IChannelHandler handler) { @@ -1049,7 +1049,7 @@ public void HandlerRemoved(IChannelHandlerContext context) public void UserEventTriggered(IChannelHandlerContext context, object evt) => ReferenceCountUtil.Release(evt); [Skip] - public Task WriteAsync(IChannelHandlerContext ctx, object message) => ctx.WriteAsync(message); + public ChannelFuture WriteAsync(IChannelHandlerContext ctx, object message) => ctx.WriteAsync(message); [Skip] public void Flush(IChannelHandlerContext context) => context.Flush(); @@ -1092,7 +1092,7 @@ public HeadContext(DefaultChannelPipeline pipeline) public void Read(IChannelHandlerContext context) => this.channelUnsafe.BeginRead(); - public Task WriteAsync(IChannelHandlerContext context, object message) => this.channelUnsafe.WriteAsync(message); + public ChannelFuture WriteAsync(IChannelHandlerContext context, object message) => this.channelUnsafe.WriteAsync(message); [Skip] public void HandlerAdded(IChannelHandlerContext context) diff --git a/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs b/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs index d3bd4ca07..d8d515124 100644 --- a/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs +++ b/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs @@ -11,6 +11,7 @@ namespace DotNetty.Transport.Channels.Embedded using System.Runtime.ExceptionServices; using System.Threading.Tasks; using DotNetty.Common; + using DotNetty.Common.Concurrency; using DotNetty.Common.Internal.Logging; using DotNetty.Common.Utilities; @@ -302,7 +303,7 @@ public bool WriteOutbound(params object[] msgs) { break; } - futures.Add(this.WriteAsync(m)); + futures.Add(this.WriteAsync(m).AsTask()); } // We need to call RunPendingTasks first as a IChannelHandler may have used IEventLoop.Execute(...) to // delay the write on the next event loop run. @@ -330,16 +331,15 @@ public bool WriteOutbound(params object[] msgs) return IsNotEmpty(this.outboundMessages); } - void RecordException(Task future) + void RecordException(ChannelFuture future) { - switch (future.Status) + try { - case TaskStatus.Canceled: - case TaskStatus.Faulted: - this.RecordException(future.Exception); - break; - default: - break; + future.GetResult(); + } + catch (Exception ex) + { + this.RecordException(ex); } } diff --git a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs index fc2b2565f..c04b3884b 100644 --- a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs +++ b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs @@ -54,13 +54,13 @@ public IChannel Find(IChannelId id) } } - public Task WriteAsync(object message) => this.WriteAsync(message, ChannelMatchers.All()); + public ChannelFuture WriteAsync(object message) => this.WriteAsync(message, ChannelMatchers.All()); - public Task WriteAsync(object message, IChannelMatcher matcher) + public ChannelFuture WriteAsync(object message, IChannelMatcher matcher) { Contract.Requires(message != null); Contract.Requires(matcher != null); - var futures = new Dictionary(); + var futures = new Dictionary(); foreach (IChannel c in this.nonServerChannels.Values) { if (matcher.Matches(c)) @@ -70,7 +70,7 @@ public Task WriteAsync(object message, IChannelMatcher matcher) } ReferenceCountUtil.Release(message); - return new DefaultChannelGroupCompletionSource(this, futures /*, this.executor*/).Task; + return new DefaultChannelGroupPromise(futures /*, this.executor*/); } public IChannelGroup Flush(IChannelMatcher matcher) @@ -144,13 +144,13 @@ public bool Remove(IChannel channel) IEnumerator IEnumerable.GetEnumerator() => new CombinedEnumerator(this.serverChannels.Values.GetEnumerator(), this.nonServerChannels.Values.GetEnumerator()); - public Task WriteAndFlushAsync(object message) => this.WriteAndFlushAsync(message, ChannelMatchers.All()); + public ChannelFuture WriteAndFlushAsync(object message) => this.WriteAndFlushAsync(message, ChannelMatchers.All()); - public Task WriteAndFlushAsync(object message, IChannelMatcher matcher) + public ChannelFuture WriteAndFlushAsync(object message, IChannelMatcher matcher) { Contract.Requires(message != null); Contract.Requires(matcher != null); - var futures = new Dictionary(); + var futures = new Dictionary(); foreach (IChannel c in this.nonServerChannels.Values) { if (matcher.Matches(c)) @@ -160,7 +160,7 @@ public Task WriteAndFlushAsync(object message, IChannelMatcher matcher) } ReferenceCountUtil.Release(message); - return new DefaultChannelGroupCompletionSource(this, futures /*, this.executor*/).Task; + return new DefaultChannelGroupPromise(futures /*, this.executor*/); } public Task DisconnectAsync() => this.DisconnectAsync(ChannelMatchers.All()); diff --git a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs new file mode 100644 index 000000000..b11657814 --- /dev/null +++ b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Transport.Channels.Groups +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Threading.Tasks; + using DotNetty.Common.Concurrency; + + public sealed class DefaultChannelGroupPromise : AbstractChannelPromise + { + readonly Dictionary futures; + int failureCount; + int successCount; + IList> failures; + + public DefaultChannelGroupPromise(Dictionary futures /*, IEventExecutor executor*/) + { + Contract.Requires(futures != null); + + this.futures = new Dictionary(); + foreach (KeyValuePair pair in futures) + { + this.futures.Add(pair.Key, pair.Value); + pair.Value.OnCompleted(() => + { + try + { + pair.Value.GetResult(); + this.successCount++; + } + catch(Exception ex) + { + this.failureCount++; + if (this.failures == null) + { + this.failures = new List>(); + } + this.failures.Add(new KeyValuePair(pair.Key, ex)); + } + + bool callSetDone = this.successCount + this.failureCount == futures.Count; + Contract.Assert(this.successCount + this.failureCount <= futures.Count); + + if (callSetDone) + { + if (this.failureCount > 0) + { + this.TryComplete(new ChannelGroupException(this.failures)); + } + else + { + this.TryComplete(); + } + } + }); + } + + // Done on arrival? + if (futures.Count == 0) + { + this.TryComplete(); + } + } + } +} \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/Groups/IChannelGroup.cs b/src/DotNetty.Transport/Channels/Groups/IChannelGroup.cs index 2256dbb12..8bb95f778 100644 --- a/src/DotNetty.Transport/Channels/Groups/IChannelGroup.cs +++ b/src/DotNetty.Transport/Channels/Groups/IChannelGroup.cs @@ -6,6 +6,7 @@ namespace DotNetty.Transport.Channels.Groups using System; using System.Collections.Generic; using System.Threading.Tasks; + using DotNetty.Common.Concurrency; public interface IChannelGroup : ICollection, IComparable { @@ -17,17 +18,17 @@ public interface IChannelGroup : ICollection, IComparable @@ -61,10 +62,10 @@ public interface IChannel : IAttributeMap, IComparable IChannel Read(); - Task WriteAsync(object message); + ChannelFuture WriteAsync(object message); IChannel Flush(); - Task WriteAndFlushAsync(object message); + ChannelFuture WriteAndFlushAsync(object message); } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/IChannelHandler.cs b/src/DotNetty.Transport/Channels/IChannelHandler.cs index c80c34640..866eb8d06 100644 --- a/src/DotNetty.Transport/Channels/IChannelHandler.cs +++ b/src/DotNetty.Transport/Channels/IChannelHandler.cs @@ -6,6 +6,7 @@ namespace DotNetty.Transport.Channels using System; using System.Net; using System.Threading.Tasks; + using DotNetty.Common.Concurrency; public interface IChannelHandler { @@ -39,7 +40,7 @@ public interface IChannelHandler void HandlerRemoved(IChannelHandlerContext context); - Task WriteAsync(IChannelHandlerContext context, object message); + ChannelFuture WriteAsync(IChannelHandlerContext context, object message); void Flush(IChannelHandlerContext context); diff --git a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs index 1a052cbf0..b8b5287ec 100644 --- a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs @@ -67,11 +67,11 @@ public interface IChannelHandlerContext : IAttributeMap IChannelHandlerContext Read(); - Task WriteAsync(object message); // todo: optimize: add flag saying if handler is interested in task, do not produce task if it isn't needed + ChannelFuture WriteAsync(object message); // todo: optimize: add flag saying if handler is interested in task, do not produce task if it isn't needed IChannelHandlerContext Flush(); - Task WriteAndFlushAsync(object message); + ChannelFuture WriteAndFlushAsync(object message); /// /// Request to bind to the given . diff --git a/src/DotNetty.Transport/Channels/IChannelPipeline.cs b/src/DotNetty.Transport/Channels/IChannelPipeline.cs index 074412650..047e98d31 100644 --- a/src/DotNetty.Transport/Channels/IChannelPipeline.cs +++ b/src/DotNetty.Transport/Channels/IChannelPipeline.cs @@ -683,7 +683,7 @@ public interface IChannelPipeline : IEnumerable /// once you want to request to flush all pending data to the actual transport. /// /// An await-able task. - Task WriteAsync(object msg); + ChannelFuture WriteAsync(object msg); /// /// Request to flush all pending messages. @@ -694,6 +694,6 @@ public interface IChannelPipeline : IEnumerable /// /// Shortcut for calling both and . /// - Task WriteAndFlushAsync(object msg); + ChannelFuture WriteAndFlushAsync(object msg); } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/IChannelUnsafe.cs b/src/DotNetty.Transport/Channels/IChannelUnsafe.cs index a28e833fd..2e2ec2e2c 100644 --- a/src/DotNetty.Transport/Channels/IChannelUnsafe.cs +++ b/src/DotNetty.Transport/Channels/IChannelUnsafe.cs @@ -5,6 +5,7 @@ namespace DotNetty.Transport.Channels { using System.Net; using System.Threading.Tasks; + using DotNetty.Common.Concurrency; public interface IChannelUnsafe { @@ -26,7 +27,7 @@ public interface IChannelUnsafe void BeginRead(); - Task WriteAsync(object message); + ChannelFuture WriteAsync(object message); void Flush(); diff --git a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs index 80441bb22..e9fa84662 100644 --- a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs @@ -70,7 +70,7 @@ public int Size /// /// The message to add to the . /// An await-able task. - public Task Add(object msg) + public ChannelFuture Add(object msg) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(msg != null); @@ -81,8 +81,8 @@ public Task Add(object msg) // Size may be unknow so just use 0 messageSize = 0; } - var promise = new TaskCompletionSource(); - PendingWrite write = PendingWrite.NewInstance(msg, messageSize, promise); + //var promise = new TaskCompletionSource(); + PendingWrite write = PendingWrite.NewInstance(this.ctx.Executor, msg, messageSize); PendingWrite currentTail = this.tail; if (currentTail == null) { @@ -98,7 +98,8 @@ public Task Add(object msg) // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 this.buffer?.IncrementPendingOutboundBytes(write.Size); - return promise.Task; + + return write; } /// @@ -120,9 +121,8 @@ public void RemoveAndFailAll(Exception cause) { PendingWrite next = write.Next; ReferenceCountUtil.SafeRelease(write.Msg); - TaskCompletionSource promise = write.Promise; this.Recycle(write, false); - Util.SafeSetFailure(promise, cause, Logger); + Util.SafeSetFailure(write, cause, Logger); write = next; } this.AssertEmpty(); @@ -145,8 +145,7 @@ public void RemoveAndFail(Exception cause) return; } ReferenceCountUtil.SafeRelease(write.Msg); - TaskCompletionSource promise = write.Promise; - Util.SafeSetFailure(promise, cause, Logger); + Util.SafeSetFailure(write, cause, Logger); this.Recycle(write, true); } @@ -154,7 +153,7 @@ public void RemoveAndFail(Exception cause) /// Removes all pending write operation and performs them via /// /// An await-able task. - public Task RemoveAndWriteAllAsync() + public ChannelFuture RemoveAndWriteAllAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); @@ -167,7 +166,7 @@ public Task RemoveAndWriteAllAsync() if (write == null) { // empty so just return null - return null; + return ChannelFuture.Completed; } // Guard against re-entrance by directly reset @@ -175,19 +174,19 @@ public Task RemoveAndWriteAllAsync() int currentSize = this.size; this.size = 0; - var tasks = new List(currentSize); + var tasks = new List(currentSize); + while (write != null) { PendingWrite next = write.Next; object msg = write.Msg; - TaskCompletionSource promise = write.Promise; this.Recycle(write, false); - this.ctx.WriteAsync(msg).LinkOutcome(promise); - tasks.Add(promise.Task); + this.ctx.WriteAsync(msg).LinkOutcome(write); + tasks.Add(write); write = next; } this.AssertEmpty(); - return Task.WhenAll(tasks); + return new ChannelFuture(new AggregatingPromise(tasks)); } void AssertEmpty() => Contract.Assert(this.tail == null && this.head == null && this.size == 0); @@ -196,20 +195,19 @@ public Task RemoveAndWriteAllAsync() /// Removes a pending write operation and performs it via . /// /// An await-able task. - public Task RemoveAndWriteAsync() + public ChannelFuture RemoveAndWriteAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { - return null; + return ChannelFuture.Completed; } object msg = write.Msg; - TaskCompletionSource promise = write.Promise; this.Recycle(write, true); - this.ctx.WriteAsync(msg).LinkOutcome(promise); - return promise.Task; + this.ctx.WriteAsync(msg).LinkOutcome(write); + return write; } /// @@ -219,19 +217,18 @@ public Task RemoveAndWriteAsync() /// /// The of the pending write, or null if the queue is empty. /// - public TaskCompletionSource Remove() + public ChannelFuture Remove() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { - return null; + return ChannelFuture.Completed; } - TaskCompletionSource promise = write.Promise; ReferenceCountUtil.SafeRelease(write.Msg); this.Recycle(write, true); - return promise; + return write; } /// @@ -269,7 +266,8 @@ void Recycle(PendingWrite write, bool update) } } - write.Recycle(); + //write.Recycle(); + // We need to guard against null as channel.unsafe().outboundBuffer() may returned null // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 @@ -279,37 +277,35 @@ void Recycle(PendingWrite write, bool update) /// /// Holds all meta-data and constructs the linked-list structure. /// - sealed class PendingWrite + sealed class PendingWrite : AbstractRecyclableChannelPromise { static readonly ThreadLocalPool Pool = new ThreadLocalPool(handle => new PendingWrite(handle)); - readonly ThreadLocalPool.Handle handle; public PendingWrite Next; public long Size; - public TaskCompletionSource Promise; public object Msg; PendingWrite(ThreadLocalPool.Handle handle) + : base(handle) { - this.handle = handle; } - public static PendingWrite NewInstance(object msg, int size, TaskCompletionSource promise) + public static PendingWrite NewInstance(IEventExecutor executor, object msg, int size) { PendingWrite write = Pool.Take(); + write.Init(executor); write.Size = size; write.Msg = msg; - write.Promise = promise; return write; } - public void Recycle() + protected override void Recycle() { this.Size = 0; this.Next = null; this.Msg = null; - this.Promise = null; - this.handle.Release(this); + base.Recycle(); + //this.handle.Release(this); } } } diff --git a/src/DotNetty.Transport/Channels/Util.cs b/src/DotNetty.Transport/Channels/Util.cs index 105860e87..f73e3cbfd 100644 --- a/src/DotNetty.Transport/Channels/Util.cs +++ b/src/DotNetty.Transport/Channels/Util.cs @@ -25,6 +25,17 @@ public static void SafeSetSuccess(TaskCompletionSource promise, IInternalLogger logger.Warn($"Failed to mark a promise as success because it is done already: {promise}"); } } + + /// + /// Marks the specified {@code promise} as success. If the {@code promise} is done already, log a message. + /// + public static void SafeSetSuccess(IChannelPromise promise, IInternalLogger logger) + { + if (!promise.TryComplete()) + { + logger.Warn($"Failed to mark a promise as success because it is done already: {promise}"); + } + } /// /// Marks the specified as failure. If the @@ -40,6 +51,17 @@ public static void SafeSetFailure(TaskCompletionSource promise, Exception cause, logger.Warn($"Failed to mark a promise as failure because it's done already: {promise}", cause); } } + + /// + /// Marks the specified {@code promise} as failure. If the {@code promise} is done already, log a message. + /// + public static void SafeSetFailure(IChannelPromise promise, Exception cause, IInternalLogger logger) + { + if (!promise.TryComplete(cause)) + { + logger.Warn($"Failed to mark a promise as failure because it's done already: {promise}", cause); + } + } public static void CloseSafe(this IChannel channel) { diff --git a/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs b/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs index 6ece234cc..9aa2a2d17 100644 --- a/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs +++ b/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs @@ -70,13 +70,12 @@ public void TestPrependAdjustedLength() public void TestPrependAdjustedLengthLessThanZero() { var ch = new EmbeddedChannel(new LengthFieldPrepender(4, -2)); - var ex = Assert.Throws(() => + Assert.Throws(() => { ch.WriteOutbound(this.msg); Assert.True(false, typeof(EncoderException).Name + " must be raised."); }); - Assert.IsType(ex.InnerExceptions.Single()); } [Fact] diff --git a/test/DotNetty.Tests.Common/ChannelExtensions.cs b/test/DotNetty.Tests.Common/ChannelExtensions.cs index 921613d0f..0055a735c 100644 --- a/test/DotNetty.Tests.Common/ChannelExtensions.cs +++ b/test/DotNetty.Tests.Common/ChannelExtensions.cs @@ -14,7 +14,7 @@ public static Task WriteAndFlushManyAsync(this IChannel channel, params object[] var list = new List(); foreach (object m in messages) { - list.Add(channel.WriteAsync(m)); + list.Add(Task.Run(async () => await channel.WriteAsync(m))); } IEnumerable tasks = list.ToArray(); channel.Flush(); diff --git a/test/DotNetty.Tests.Common/ChannelFutureExtensions.cs b/test/DotNetty.Tests.Common/ChannelFutureExtensions.cs new file mode 100644 index 000000000..de8a8506f --- /dev/null +++ b/test/DotNetty.Tests.Common/ChannelFutureExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Tests.Common +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using DotNetty.Common.Concurrency; + + public static class ChannelFutureExtensions + { + public static bool Wait(this ChannelFuture future, TimeSpan timeout) + { + return Task.Run(async () => await future).Wait(timeout); + + /*var mre = new ManualResetEventSlim(false); + future.OnCompleted(mre.Set); + return mre.Wait(timeout);*/ + } + } +} \ No newline at end of file diff --git a/test/DotNetty.Tests.End2End/End2EndTests.cs b/test/DotNetty.Tests.End2End/End2EndTests.cs index 39473d46b..37221edfc 100644 --- a/test/DotNetty.Tests.End2End/End2EndTests.cs +++ b/test/DotNetty.Tests.End2End/End2EndTests.cs @@ -90,7 +90,7 @@ public async Task EchoServerAndClient() string[] messages = { "message 1", string.Join(",", Enumerable.Range(1, 300)) }; foreach (string message in messages) { - await clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(message))).WithTimeout(DefaultTimeout); + await Task.Run(async () => await clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(message)))).WithTimeout(DefaultTimeout); var responseMessage = Assert.IsAssignableFrom(await readListener.ReceiveAsync()); Assert.Equal(message, responseMessage.ToString(Encoding.UTF8)); diff --git a/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs b/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs index ef09ff54c..e74e5aedc 100644 --- a/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs @@ -8,7 +8,9 @@ namespace DotNetty.Transport.Libuv.Tests using System.Threading; using System.Threading.Tasks; using DotNetty.Buffers; + using DotNetty.Common.Concurrency; using DotNetty.Common.Utilities; + using DotNetty.Tests.Common; using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using Xunit; @@ -73,7 +75,7 @@ void AutoReadOffDuringReadOnlyReadsOneTime0(bool readOutsideEventLoopThread, Ser Assert.NotNull(this.clientChannel.LocalAddress); // 3 bytes means 3 independent reads for TestRecvByteBufAllocator - Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[3])); + ChannelFuture writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[3])); Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); serverInitializer.AutoReadHandler.AssertSingleRead(); diff --git a/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs b/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs index e104f9f27..8738591d9 100644 --- a/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs @@ -4,10 +4,14 @@ namespace DotNetty.Transport.Libuv.Tests { using System; + using System.Diagnostics; + using System.Globalization; using System.Net; + using System.Runtime.CompilerServices; using System.Threading.Tasks; using DotNetty.Buffers; using DotNetty.Common.Concurrency; + using DotNetty.Tests.Common; using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using Xunit; @@ -27,7 +31,7 @@ public BufReleaseTests() } [Fact] - public void BufRelease() + public async Task BufRelease() { ServerBootstrap sb = new ServerBootstrap() .Group(this.group) @@ -103,7 +107,7 @@ public override void ChannelActive(IChannelHandlerContext ctx) // call retain on it so it can't be put back on the pool this.buf.WriteBytes(data).Retain(); - this.writeTask = ctx.Channel.WriteAndFlushAsync(this.buf); + this.writeTask = ctx.Channel.WriteAndFlushAsync(this.buf).AsTask(); } protected override void ChannelRead0(IChannelHandlerContext ctx, object msg) diff --git a/test/DotNetty.Transport.Libuv.Tests/CompositeBufferGatheringWriteTests.cs b/test/DotNetty.Transport.Libuv.Tests/CompositeBufferGatheringWriteTests.cs index ab276ce4a..c9fccce32 100644 --- a/test/DotNetty.Transport.Libuv.Tests/CompositeBufferGatheringWriteTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/CompositeBufferGatheringWriteTests.cs @@ -133,8 +133,7 @@ sealed class ServerHandler : ChannelHandlerAdapter { public override void ChannelActive(IChannelHandlerContext ctx) => ctx.WriteAndFlushAsync(NewCompositeBuffer(ctx.Allocator)) - .ContinueWith((t, s) => ((IChannelHandlerContext)s).CloseAsync(), - ctx, TaskContinuationOptions.ExecuteSynchronously); + .OnCompleted(() => ctx.CloseAsync()); } static IByteBuffer NewCompositeBuffer(IByteBufferAllocator alloc) diff --git a/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs b/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs index 3355c12b1..fa7eb1c64 100644 --- a/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs @@ -62,7 +62,7 @@ public void ClientCloseWithoutServerReadIsDetected() IByteBuffer buf = this.clientChannel.Allocator.Buffer(ExpectedBytes); buf.SetWriterIndex(buf.WriterIndex + ExpectedBytes); - this.clientChannel.WriteAndFlushAsync(buf).ContinueWith(_ => this.clientChannel.CloseAsync()); + this.clientChannel.WriteAndFlushAsync(buf).OnCompleted(() => this.clientChannel.CloseAsync()); Task completion = serverHandler.Completion; Assert.True(completion.Wait(DefaultTimeout)); @@ -172,7 +172,7 @@ public override void ChannelActive(IChannelHandlerContext ctx) { IByteBuffer buf = ctx.Allocator.Buffer(this.expectedBytesRead); buf.SetWriterIndex(buf.WriterIndex + this.expectedBytesRead); - ctx.WriteAndFlushAsync(buf).ContinueWith(_ => ctx.CloseAsync()); + ctx.WriteAndFlushAsync(buf).OnCompleted(() => ctx.CloseAsync()); ctx.FireChannelActive(); } diff --git a/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs b/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs index 2ac71cf76..1a7b803ed 100644 --- a/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs @@ -10,6 +10,7 @@ namespace DotNetty.Transport.Libuv.Tests using DotNetty.Buffers; using DotNetty.Common.Concurrency; using DotNetty.Common.Utilities; + using DotNetty.Tests.Common; using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using Xunit; @@ -60,7 +61,7 @@ void ReadPendingIsResetAfterEachRead0(ServerBootstrap sb, Bootstrap cb) this.clientChannel = task.Result; Assert.NotNull(this.clientChannel.LocalAddress); - Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[1024])); + ChannelFuture writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[1024])); Assert.True(writeTask.Wait(DefaultTimeout), "Write task timed out"); ExceptionHandler exceptionHandler = serverInitializer.ErrorHandler; diff --git a/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs b/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs index 0696eda56..edf64b1e9 100644 --- a/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs @@ -10,6 +10,7 @@ namespace DotNetty.Transport.Libuv.Tests using DotNetty.Buffers; using DotNetty.Common.Concurrency; using DotNetty.Common.Utilities; + using DotNetty.Tests.Common; using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using Xunit; @@ -70,7 +71,7 @@ void ReadPendingIsResetAfterEachRead0(ServerBootstrap sb, Bootstrap cb) Assert.NotNull(this.clientChannel.LocalAddress); // 4 bytes means 2 read loops for TestNumReadsRecvByteBufAllocator - Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[4])); + ChannelFuture writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[4])); Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); // 4 bytes means 2 read loops for TestNumReadsRecvByteBufAllocator diff --git a/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs b/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs index 8c91a0913..8404c87aa 100644 --- a/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs @@ -24,7 +24,7 @@ public WriteBeforeRegisteredTests() } [Fact] - public void WriteBeforeConnect() + public async Task WriteBeforeConnect() { Bootstrap cb = new Bootstrap() .Group(this.group) @@ -42,7 +42,7 @@ void WriteBeforeConnect0(Bootstrap cb) this.clientChannel = task.Result; Task connectTask = this.clientChannel.ConnectAsync(LoopbackAnyPort); - Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[] { 1 })); + Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[] { 1 })).AsTask(); var error = Assert.Throws(() => writeTask.Wait(DefaultTimeout)); Assert.Single(error.InnerExceptions); diff --git a/test/DotNetty.Transport.Tests.Performance/Sockets/SocketDatagramChannelPerfSpecs.cs b/test/DotNetty.Transport.Tests.Performance/Sockets/SocketDatagramChannelPerfSpecs.cs index 71695f7df..4c4d6b460 100644 --- a/test/DotNetty.Transport.Tests.Performance/Sockets/SocketDatagramChannelPerfSpecs.cs +++ b/test/DotNetty.Transport.Tests.Performance/Sockets/SocketDatagramChannelPerfSpecs.cs @@ -8,6 +8,7 @@ namespace DotNetty.Transport.Tests.Performance.Sockets using System.Threading; using System.Threading.Tasks; using DotNetty.Buffers; + using DotNetty.Common.Concurrency; using DotNetty.Common.Utilities; using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; @@ -85,7 +86,7 @@ public OutboundCounter(Counter writes) this.writes = writes; } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) { this.writes.Increment(); return context.WriteAsync(message); diff --git a/test/DotNetty.Transport.Tests.Performance/Utilities/CounterHandlerOutbound.cs b/test/DotNetty.Transport.Tests.Performance/Utilities/CounterHandlerOutbound.cs index a5d830d79..84468b5a4 100644 --- a/test/DotNetty.Transport.Tests.Performance/Utilities/CounterHandlerOutbound.cs +++ b/test/DotNetty.Transport.Tests.Performance/Utilities/CounterHandlerOutbound.cs @@ -4,6 +4,7 @@ namespace DotNetty.Transport.Tests.Performance.Utilities { using System.Threading.Tasks; + using DotNetty.Common.Concurrency; using DotNetty.Transport.Channels; using NBench; @@ -16,7 +17,7 @@ public CounterHandlerOutbound(Counter throughput) this.throughput = throughput; } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) { this.throughput.Increment(); return context.WriteAsync(message); diff --git a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs index 840c8ab33..5c2b3fdec 100644 --- a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs +++ b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs @@ -94,7 +94,7 @@ public static IEnumerable GetData() [Theory] [MemberData(nameof(GetData))] - public void Multicast(AddressFamily addressFamily, IByteBufferAllocator allocator) + public async Task Multicast(AddressFamily addressFamily, IByteBufferAllocator allocator) { SocketDatagramChannel serverChannel = null; IChannel clientChannel = null; @@ -155,7 +155,7 @@ public void Multicast(AddressFamily addressFamily, IByteBufferAllocator allocato Assert.True(joinTask.Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds * 5)), $"Multicast server join group {groupAddress} timed out!"); - clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)).Wait(); + await clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress));//.Wait(); Assert.True(multicastHandler.WaitForResult(), "Multicast server should have receivied the message."); Task leaveTask = serverChannel.LeaveGroup(groupAddress, loopback); @@ -166,7 +166,7 @@ public void Multicast(AddressFamily addressFamily, IByteBufferAllocator allocato Task.Delay(DefaultTimeOutInMilliseconds).Wait(); // we should not receive a message anymore as we left the group before - clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)).Wait(); + await clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)); //.Wait(); Assert.False(multicastHandler.WaitForResult(), "Multicast server should not receive the message."); } finally @@ -174,9 +174,11 @@ public void Multicast(AddressFamily addressFamily, IByteBufferAllocator allocato serverChannel?.CloseAsync().Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds)); clientChannel?.CloseAsync().Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds)); - Task.WaitAll( + await serverGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); + await clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); + /*Task.WaitAll( serverGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)), - clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1))); + clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));*/ } } } diff --git a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs index 908a03c15..b964441ec 100644 --- a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs +++ b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs @@ -146,7 +146,7 @@ public static IEnumerable GetData() [Theory] [MemberData(nameof(GetData))] - public void SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAllocator allocator, AddressFamily addressFamily, byte[] expectedData, int count) + public async Task SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAllocator allocator, AddressFamily addressFamily, byte[] expectedData, int count) { SocketDatagramChannel serverChannel = null; IChannel clientChannel = null; @@ -217,12 +217,12 @@ public void SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAllocator for (int i = 0; i < count; i++) { var packet = new DatagramPacket((IByteBuffer)source.Retain(), new IPEndPoint(address, endPoint.Port)); - clientChannel.WriteAndFlushAsync(packet).Wait(); + await clientChannel.WriteAndFlushAsync(packet);//.Wait(); Assert.True(handler.WaitForResult()); var duplicatedPacket = (DatagramPacket)packet.Duplicate(); duplicatedPacket.Retain(); - clientChannel.WriteAndFlushAsync(duplicatedPacket).Wait(); + await clientChannel.WriteAndFlushAsync(duplicatedPacket);//.Wait(); Assert.True(handler.WaitForResult()); } } @@ -232,9 +232,9 @@ public void SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAllocator clientChannel?.CloseAsync().Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds)); source.Release(); - Task.WaitAll( - serverGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)), - clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1))); + + await serverGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); + await clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); } } } From ff944b2db60c7f841a523ff9fc5c31ad7992aebf Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Fri, 9 Mar 2018 16:06:17 -0800 Subject: [PATCH 02/29] ChannelFuture -> ValueTask --- .nuget/NuGet.Config | 3 + examples/Telnet.Server/TelnetServerHandler.cs | 2 +- src/DotNetty.Buffers/DotNetty.Buffers.csproj | 3 +- src/DotNetty.Codecs/DotNetty.Codecs.csproj | 6 +- src/DotNetty.Codecs/MessageToByteEncoder.cs | 10 +-- .../MessageToMessageEncoder.cs | 14 +-- ...ctChannelPromise.cs => AbstractPromise.cs} | 79 +++++++++------- ...romise.cs => AbstractRecyclablePromise.cs} | 27 +++--- .../Concurrency/AggregatingPromise.cs | 81 +++++++++-------- .../Concurrency/ChannelFuture.cs | 80 ----------------- .../Concurrency/CompletedFuture.cs | 37 -------- .../Concurrency/IChannelFuture.cs | 17 ---- .../{IChannelPromise.cs => IPromise.cs} | 11 ++- src/DotNetty.Common/DotNetty.Common.csproj | 1 + src/DotNetty.Common/ThreadLocalObjectList.cs | 35 +++++++- src/DotNetty.Common/Utilities/TaskEx.cs | 90 ++++++++++++++++--- .../DotNetty.Handlers.csproj | 3 + .../Logging/LoggingHandler.cs | 5 +- .../Timeout/IdleStateHandler.cs | 12 ++- .../Timeout/WriteTimeoutHandler.cs | 18 ++-- src/DotNetty.Handlers/Tls/TlsHandler.cs | 16 ++-- .../Channels/AbstractChannel.cs | 11 ++- .../Channels/AbstractChannelHandlerContext.cs | 29 +++--- .../Channels/BatchingPendingWriteQueue.cs | 21 ++--- .../Channels/ChannelHandlerAdapter.cs | 2 +- .../Channels/ChannelOutboundBuffer.cs | 10 ++- .../Channels/DefaultChannelPipeline.cs | 8 +- .../Channels/Embedded/EmbeddedChannel.cs | 18 ++-- .../Channels/Groups/DefaultChannelGroup.cs | 12 +-- .../Groups/DefaultChannelGroupPromise.cs | 20 +++-- .../Channels/Groups/IChannelGroup.cs | 8 +- src/DotNetty.Transport/Channels/IChannel.cs | 4 +- .../Channels/IChannelHandler.cs | 2 +- .../Channels/IChannelHandlerContext.cs | 4 +- .../Channels/IChannelPipeline.cs | 4 +- .../Channels/IChannelUnsafe.cs | 2 +- .../Channels/PendingWriteQueue.cs | 21 ++--- src/DotNetty.Transport/Channels/Util.cs | 6 +- .../DotNetty.Common.Tests.csproj | 2 + .../ChannelFutureExtensions.cs | 22 ----- .../ValueTaskExtensions.cs | 37 ++++++++ .../AutoReadTests.cs | 6 +- .../CompositeBufferGatheringWriteTests.cs | 14 ++- .../DetectPeerCloseWithoutReadTests.cs | 5 +- .../ExceptionHandlingTests.cs | 4 +- .../ReadPendingTests.cs | 6 +- .../Sockets/SocketDatagramChannelPerfSpecs.cs | 2 +- .../Utilities/CounterHandlerOutbound.cs | 2 +- 48 files changed, 430 insertions(+), 402 deletions(-) rename src/DotNetty.Common/Concurrency/{AbstractChannelPromise.cs => AbstractPromise.cs} (63%) rename src/DotNetty.Common/Concurrency/{AbstractRecyclableChannelPromise.cs => AbstractRecyclablePromise.cs} (70%) delete mode 100644 src/DotNetty.Common/Concurrency/ChannelFuture.cs delete mode 100644 src/DotNetty.Common/Concurrency/CompletedFuture.cs delete mode 100644 src/DotNetty.Common/Concurrency/IChannelFuture.cs rename src/DotNetty.Common/Concurrency/{IChannelPromise.cs => IPromise.cs} (52%) delete mode 100644 test/DotNetty.Tests.Common/ChannelFutureExtensions.cs create mode 100644 test/DotNetty.Tests.Common/ValueTaskExtensions.cs diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config index 67f8ea046..36c859a9d 100644 --- a/.nuget/NuGet.Config +++ b/.nuget/NuGet.Config @@ -3,4 +3,7 @@ + + + \ No newline at end of file diff --git a/examples/Telnet.Server/TelnetServerHandler.cs b/examples/Telnet.Server/TelnetServerHandler.cs index 7361447f6..ea4c3a935 100644 --- a/examples/Telnet.Server/TelnetServerHandler.cs +++ b/examples/Telnet.Server/TelnetServerHandler.cs @@ -36,7 +36,7 @@ protected override async void ChannelRead0(IChannelHandlerContext contex, string response = "Did you say '" + msg + "'?\r\n"; } - ChannelFuture wait_close = contex.WriteAndFlushAsync(response); + ValueTask wait_close = contex.WriteAndFlushAsync(response); if (close) { await wait_close; diff --git a/src/DotNetty.Buffers/DotNetty.Buffers.csproj b/src/DotNetty.Buffers/DotNetty.Buffers.csproj index 1d68f3c2a..5ba59d926 100644 --- a/src/DotNetty.Buffers/DotNetty.Buffers.csproj +++ b/src/DotNetty.Buffers/DotNetty.Buffers.csproj @@ -1,4 +1,5 @@ - + + netstandard1.3;net45 true diff --git a/src/DotNetty.Codecs/DotNetty.Codecs.csproj b/src/DotNetty.Codecs/DotNetty.Codecs.csproj index 12a113965..7608729d5 100644 --- a/src/DotNetty.Codecs/DotNetty.Codecs.csproj +++ b/src/DotNetty.Codecs/DotNetty.Codecs.csproj @@ -1,4 +1,5 @@ - + + netstandard1.3;net45 true @@ -44,4 +45,7 @@ + + + \ No newline at end of file diff --git a/src/DotNetty.Codecs/MessageToByteEncoder.cs b/src/DotNetty.Codecs/MessageToByteEncoder.cs index 21231eb4a..9b4283b75 100644 --- a/src/DotNetty.Codecs/MessageToByteEncoder.cs +++ b/src/DotNetty.Codecs/MessageToByteEncoder.cs @@ -15,12 +15,12 @@ public abstract class MessageToByteEncoder : ChannelHandlerAdapter { public virtual bool AcceptOutboundMessage(object message) => message is T; - public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { Contract.Requires(context != null); IByteBuffer buffer = null; - ChannelFuture result; + ValueTask result; try { if (this.AcceptOutboundMessage(message)) @@ -53,13 +53,13 @@ public override ChannelFuture WriteAsync(IChannelHandlerContext context, object return context.WriteAsync(message); } } - catch (EncoderException e) + catch (EncoderException) { - throw;//return TaskEx.FromException(e); + throw; } catch (Exception ex) { - throw new EncoderException(ex);//return TaskEx.FromException(new EncoderException(ex)); + throw new EncoderException(ex); } finally { diff --git a/src/DotNetty.Codecs/MessageToMessageEncoder.cs b/src/DotNetty.Codecs/MessageToMessageEncoder.cs index 6633d9132..ed0d3c532 100644 --- a/src/DotNetty.Codecs/MessageToMessageEncoder.cs +++ b/src/DotNetty.Codecs/MessageToMessageEncoder.cs @@ -19,9 +19,9 @@ public abstract class MessageToMessageEncoder : ChannelHandlerAdapter /// public virtual bool AcceptOutboundMessage(object msg) => msg is T; - public override ChannelFuture WriteAsync(IChannelHandlerContext ctx, object msg) + public override ValueTask WriteAsync(IChannelHandlerContext ctx, object msg) { - ChannelFuture result; + ValueTask result; ThreadLocalObjectList output = null; try { @@ -51,13 +51,13 @@ public override ChannelFuture WriteAsync(IChannelHandlerContext ctx, object msg) return ctx.WriteAsync(msg); } } - catch (EncoderException e) + catch (EncoderException) { - throw;//return TaskEx.FromException(e); + throw; } catch (Exception ex) { - throw new EncoderException(ex);//return TaskEx.FromException(new EncoderException(ex)); // todo: we don't have a stack on EncoderException but it's present on inner exception. + throw new EncoderException(ex);// todo: we don't have a stack on EncoderException but it's present on inner exception. } finally { @@ -80,14 +80,14 @@ public override ChannelFuture WriteAsync(IChannelHandlerContext ctx, object msg) else { // 0 items in output - must never get here - result = default(ChannelFuture); + result = default(ValueTask); } output.Return(); } else { // output was reset during exception handling - must never get here - result = default(ChannelFuture); + result = default(ValueTask); } } return result; diff --git a/src/DotNetty.Common/Concurrency/AbstractChannelPromise.cs b/src/DotNetty.Common/Concurrency/AbstractPromise.cs similarity index 63% rename from src/DotNetty.Common/Concurrency/AbstractChannelPromise.cs rename to src/DotNetty.Common/Concurrency/AbstractPromise.cs index 1e28af054..2e22238c5 100644 --- a/src/DotNetty.Common/Concurrency/AbstractChannelPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractPromise.cs @@ -7,24 +7,33 @@ namespace DotNetty.Common.Concurrency using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; + using System.Threading.Tasks; + using System.Threading.Tasks.Sources; - public abstract class AbstractChannelPromise : IChannelFuture, IChannelPromise + public abstract class AbstractPromise : IPromise, IValueTaskSource { - protected static readonly Exception CompletedNoException = new Exception(); + const short SourceToken = 0; + static readonly Exception CanceledException = new OperationCanceledException(); + static readonly Exception CompletedNoException = new Exception(); + protected Exception exception; protected int callbackCount; - protected (Delegate, object)[] callbacks; + protected (Action, object)[] callbacks; - public virtual bool IsCompleted => this.exception != null; + public bool TryComplete() => this.TryComplete0(CompletedNoException); + + public bool TrySetException(Exception exception) => this.TryComplete0(exception); - public virtual bool TryComplete(Exception exception = null) + public bool TrySetCanceled() => this.TryComplete0(CanceledException); + + protected virtual bool TryComplete0(Exception exception) { if (this.exception == null) { // Set the exception object to the exception passed in or a sentinel value - this.exception = exception ?? CompletedNoException; + this.exception = exception; this.ExecuteCallbacks(); return true; } @@ -32,13 +41,33 @@ public virtual bool TryComplete(Exception exception = null) return false; } - public IChannelFuture Future => this; + public IValueTaskSource Future => this; public bool SetUncancellable() => true; + + public virtual ValueTaskSourceStatus GetStatus(short token) + { + if (this.exception == null) + { + return ValueTaskSourceStatus.Pending; + } + else if (this.exception == CompletedNoException) + { + return ValueTaskSourceStatus.Succeeded; + } + else if (this.exception == CanceledException) + { + return ValueTaskSourceStatus.Canceled; + } + else + { + return ValueTaskSourceStatus.Faulted; + } + } - public virtual void GetResult() + public virtual void GetResult(short token) { - if (!this.IsCompleted) + if (this.exception == null) { throw new InvalidOperationException("Attempt to get result on not yet completed promise"); //ThrowHelper.ThrowInvalidOperationException_GetResultNotCompleted(); @@ -58,17 +87,11 @@ public virtual void GetResult() */ } - public void UnsafeOnCompleted(Action continuation) => this.OnCompleted(continuation); - - public void OnCompleted(Action callback) => this.OnCompleted0(callback, null); - - public void OnCompleted(Action continuation, object state) => this.OnCompleted0(continuation, null); - - protected virtual void OnCompleted0(Delegate callback, object state) + public virtual void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { if (this.callbacks == null) { - this.callbacks = new (Delegate, object)[1]; + this.callbacks = new (Action, object)[1]; //this.callbacks = s_completionCallbackPool.Rent(InitialCallbacksSize); } @@ -77,20 +100,20 @@ protected virtual void OnCompleted0(Delegate callback, object state) if (newIndex == this.callbacks.Length) { - var newArray = new (Delegate, object)[this.callbacks.Length * 2]; + var newArray = new (Action, object)[this.callbacks.Length * 2]; Array.Copy(this.callbacks, newArray, this.callbacks.Length); this.callbacks = newArray; } - this.callbacks[newIndex] = (callback, state); + this.callbacks[newIndex] = (continuation, state); - if (this.IsCompleted) + if (this.exception != null) { this.ExecuteCallbacks(); } } - public static implicit operator ChannelFuture(AbstractChannelPromise promise) => new ChannelFuture(promise); + public static implicit operator ValueTask(AbstractPromise promise) => new ValueTask(promise, SourceToken); [MethodImpl(MethodImplOptions.AggressiveInlining)] bool IsCompletedOrThrow() @@ -126,18 +149,8 @@ void ExecuteCallbacks() { try { - (Delegate callback, object state) = this.callbacks[i]; - switch (callback) - { - case Action action: - action(); - break; - case Action action: - action(state); - break; - default: - throw new ArgumentException("action"); - } + (Action callback, object state) = this.callbacks[i]; + callback(state); } catch (Exception ex) { diff --git a/src/DotNetty.Common/Concurrency/AbstractRecyclableChannelPromise.cs b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs similarity index 70% rename from src/DotNetty.Common/Concurrency/AbstractRecyclableChannelPromise.cs rename to src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs index 01b6bb8ab..f60fe52a5 100644 --- a/src/DotNetty.Common/Concurrency/AbstractRecyclableChannelPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs @@ -5,42 +5,39 @@ namespace DotNetty.Common.Concurrency { using System; using System.Runtime.CompilerServices; + using System.Threading.Tasks.Sources; - public abstract class AbstractRecyclableChannelPromise : AbstractChannelPromise + public abstract class AbstractRecyclablePromise : AbstractPromise { protected IEventExecutor executor; protected bool recycled; - protected readonly ThreadLocalPool.Handle handle; - protected AbstractRecyclableChannelPromise(ThreadLocalPool.Handle handle) + protected AbstractRecyclablePromise(ThreadLocalPool.Handle handle) { this.handle = handle; } - public override bool IsCompleted + public override ValueTaskSourceStatus GetStatus(short token) { - get - { - this.ThrowIfRecycled(); - return base.IsCompleted; - } + this.ThrowIfRecycled(); + return base.GetStatus(token); } - public override void GetResult() + public override void GetResult(short token) { this.ThrowIfRecycled(); - base.GetResult(); + base.GetResult(token); } - public override bool TryComplete(Exception exception = null) + protected override bool TryComplete0(Exception exception) { this.ThrowIfRecycled(); bool completed; try { - completed = base.TryComplete(exception); + completed = base.TryComplete0(exception); } catch { @@ -72,10 +69,10 @@ protected virtual void Recycle() this.handle.Release(this); } - protected override void OnCompleted0(Delegate callback, object state) + public override void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { this.ThrowIfRecycled(); - base.OnCompleted0(callback, state); + base.OnCompleted(continuation, state, token, flags); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/DotNetty.Common/Concurrency/AggregatingPromise.cs b/src/DotNetty.Common/Concurrency/AggregatingPromise.cs index 0cbf0ed97..d28387664 100644 --- a/src/DotNetty.Common/Concurrency/AggregatingPromise.cs +++ b/src/DotNetty.Common/Concurrency/AggregatingPromise.cs @@ -6,54 +6,24 @@ namespace DotNetty.Common.Concurrency using System; using System.Collections.Generic; using System.Diagnostics.Contracts; + using System.Threading.Tasks.Sources; - public sealed class AggregatingPromise : AbstractChannelPromise + public sealed class AggregatingPromise : AbstractPromise { + readonly IList futures; int successCount; int failureCount; IList failures; - public AggregatingPromise(IList futures) + public AggregatingPromise(IList futures) { Contract.Requires(futures != null); + this.futures = futures; - foreach (ChannelFuture future in futures) + foreach (IValueTaskSource future in futures) { - future.OnCompleted( - () => - { - try - { - future.GetResult(); - this.successCount++; - } - catch(Exception ex) - { - this.failureCount++; - - if (this.failures == null) - { - this.failures = new List(); - } - this.failures.Add(ex); - } - - bool callSetDone = this.successCount + this.failureCount == futures.Count; - Contract.Assert(this.successCount + this.failureCount <= futures.Count); - - if (callSetDone) - { - if (this.failureCount > 0) - { - this.TryComplete(new AggregateException(this.failures)); - } - else - { - this.TryComplete(); - } - } - }); + future.OnCompleted(this.OnFutureCompleted, future, 0, ValueTaskSourceOnCompletedFlags.None); } // Done on arrival? @@ -62,5 +32,42 @@ public AggregatingPromise(IList futures) this.TryComplete(); } } + + void OnFutureCompleted(object obj) + { + IValueTaskSource future = obj as IValueTaskSource; + Contract.Assert(future != null); + + try + { + future.GetResult(0); + this.successCount++; + } + catch(Exception ex) + { + this.failureCount++; + + if (this.failures == null) + { + this.failures = new List(); + } + this.failures.Add(ex); + } + + bool callSetDone = this.successCount + this.failureCount == this.futures.Count; + Contract.Assert(this.successCount + this.failureCount <= this.futures.Count); + + if (callSetDone) + { + if (this.failureCount > 0) + { + this.TrySetException(new AggregateException(this.failures)); + } + else + { + this.TryComplete(); + } + } + } } } \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/ChannelFuture.cs b/src/DotNetty.Common/Concurrency/ChannelFuture.cs deleted file mode 100644 index 030cbfe8b..000000000 --- a/src/DotNetty.Common/Concurrency/ChannelFuture.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace DotNetty.Common.Concurrency -{ - using System; - using System.Runtime.CompilerServices; - using System.Runtime.ExceptionServices; - - public struct ChannelFuture : ICriticalNotifyCompletion - { - public static readonly ChannelFuture Completed = new ChannelFuture(); - - public static ChannelFuture FromException(Exception ex) => new ChannelFuture(ex); - - readonly object state; - - public ChannelFuture(IChannelFuture future) : this((object)future) - { - this.state = future; - } - - ChannelFuture(Exception ex) : this(ExceptionDispatchInfo.Capture(ex)) - { - - } - - ChannelFuture(object state) - { - this.state = state; - } - - public ChannelFuture GetAwaiter() => this; - - public bool IsCompleted => this.state is IChannelFuture future ? future.IsCompleted : true; - - public void GetResult() - { - switch (this.state) - { - case null: - break; - case ExceptionDispatchInfo edi: - edi.Throw(); - break; - case IChannelFuture future: - future.GetResult(); - break; - default: - throw new InvalidOperationException("should not come here"); - } - } - - public void OnCompleted(Action continuation) - { - if (this.state is IChannelFuture future) - { - future.OnCompleted(continuation); - } - else - { - continuation(); - } - } - - public void OnCompleted(Action continuation, object state) - { - if (this.state is IChannelFuture future) - { - future.OnCompleted(continuation, state); - } - else - { - continuation(state); - } - } - - public void UnsafeOnCompleted(Action continuation) => this.OnCompleted(continuation); - } -} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/CompletedFuture.cs b/src/DotNetty.Common/Concurrency/CompletedFuture.cs deleted file mode 100644 index d5947b3ac..000000000 --- a/src/DotNetty.Common/Concurrency/CompletedFuture.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace DotNetty.Common.Concurrency -{ - using System; - - public class CompletedFuture : IChannelFuture - { - public static readonly IChannelFuture Instance = new CompletedFuture(); - - CompletedFuture() - { - - } - - public void OnCompleted(Action continuation) => continuation(); - - public void UnsafeOnCompleted(Action continuation) => this.OnCompleted(continuation); - - public bool IsCompleted => true; - - public void GetResult() - { - - } - - public void OnCompleted(Action continuation, object state) => continuation(state); - - public void Init(IEventExecutor executor) - { - - } - - public void Recycle() - { - - } - } -} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/IChannelFuture.cs b/src/DotNetty.Common/Concurrency/IChannelFuture.cs deleted file mode 100644 index 73bf77065..000000000 --- a/src/DotNetty.Common/Concurrency/IChannelFuture.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace DotNetty.Common.Concurrency -{ - using System; - using System.Runtime.CompilerServices; - - public interface IChannelFuture : ICriticalNotifyCompletion - { - bool IsCompleted { get; } - - void GetResult(); - - void OnCompleted(Action continuation, object state); - } -} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/IChannelPromise.cs b/src/DotNetty.Common/Concurrency/IPromise.cs similarity index 52% rename from src/DotNetty.Common/Concurrency/IChannelPromise.cs rename to src/DotNetty.Common/Concurrency/IPromise.cs index b79ae8d97..6e8bf077f 100644 --- a/src/DotNetty.Common/Concurrency/IChannelPromise.cs +++ b/src/DotNetty.Common/Concurrency/IPromise.cs @@ -4,12 +4,17 @@ namespace DotNetty.Common.Concurrency { using System; + using System.Threading.Tasks.Sources; - public interface IChannelPromise + public interface IPromise { - bool TryComplete(Exception exception = null); + bool TryComplete(); + + bool TrySetException(Exception exception); - IChannelFuture Future { get; } + bool TrySetCanceled(); + + IValueTaskSource Future { get; } bool SetUncancellable(); } diff --git a/src/DotNetty.Common/DotNetty.Common.csproj b/src/DotNetty.Common/DotNetty.Common.csproj index e08763cda..8d41cb60b 100644 --- a/src/DotNetty.Common/DotNetty.Common.csproj +++ b/src/DotNetty.Common/DotNetty.Common.csproj @@ -32,6 +32,7 @@ + diff --git a/src/DotNetty.Common/ThreadLocalObjectList.cs b/src/DotNetty.Common/ThreadLocalObjectList.cs index f2e2b6102..6271e5469 100644 --- a/src/DotNetty.Common/ThreadLocalObjectList.cs +++ b/src/DotNetty.Common/ThreadLocalObjectList.cs @@ -5,7 +5,7 @@ namespace DotNetty.Common { using System.Collections.Generic; - public class ThreadLocalObjectList : List + public sealed class ThreadLocalObjectList : List { const int DefaultInitialCapacity = 8; @@ -13,7 +13,7 @@ public class ThreadLocalObjectList : List readonly ThreadLocalPool.Handle returnHandle; - ThreadLocalObjectList(ThreadLocalPool.Handle returnHandle) + protected ThreadLocalObjectList(ThreadLocalPool.Handle returnHandle) { this.returnHandle = returnHandle; } @@ -28,7 +28,38 @@ public static ThreadLocalObjectList NewInstance(int minCapacity) ret.Capacity = minCapacity; } return ret; + } + + public void Return() + { + this.Clear(); + this.returnHandle.Release(this); + } + } + + public class ThreadLocalObjectList : List + { + const int DefaultInitialCapacity = 8; + + static readonly ThreadLocalPool> Pool = new ThreadLocalPool>(handle => new ThreadLocalObjectList(handle)); + + readonly ThreadLocalPool.Handle returnHandle; + + protected ThreadLocalObjectList(ThreadLocalPool.Handle returnHandle) + { + this.returnHandle = returnHandle; + } + + public static ThreadLocalObjectList NewInstance() => NewInstance(DefaultInitialCapacity); + public static ThreadLocalObjectList NewInstance(int minCapacity) + { + ThreadLocalObjectList ret = Pool.Take(); + if (ret.Capacity < minCapacity) + { + ret.Capacity = minCapacity; + } + return ret; } public void Return() diff --git a/src/DotNetty.Common/Utilities/TaskEx.cs b/src/DotNetty.Common/Utilities/TaskEx.cs index 2f01243d4..e029cd9a6 100644 --- a/src/DotNetty.Common/Utilities/TaskEx.cs +++ b/src/DotNetty.Common/Utilities/TaskEx.cs @@ -4,6 +4,7 @@ namespace DotNetty.Common.Utilities { using System; + using System.Runtime.CompilerServices; using System.Threading.Tasks; using DotNetty.Common.Concurrency; @@ -19,6 +20,10 @@ public static class TaskEx public static readonly Task False = Task.FromResult(false); + public static ValueTask ToValueTask(this Exception ex) => new ValueTask(FromException(ex)); + + public static ValueTask ToValueTask(this Exception ex) => new ValueTask(FromException(ex)); + static Task CreateCancelledTask() { var tcs = new TaskCompletionSource(); @@ -40,7 +45,7 @@ public static Task FromException(Exception exception) return tcs.Task; } - static readonly Action LinkOutcomeContinuationAction = (t, tcs) => + static readonly Action LinkOutcomeTcs = (t, tcs) => { switch (t.Status) { @@ -57,7 +62,7 @@ public static Task FromException(Exception exception) throw new ArgumentOutOfRangeException(); } }; - + public static void LinkOutcome(this Task task, TaskCompletionSource taskCompletionSource) { switch (task.Status) @@ -72,22 +77,71 @@ public static void LinkOutcome(this Task task, TaskCompletionSource taskCompleti taskCompletionSource.TryUnwrap(task.Exception); break; default: - task.ContinueWith( - LinkOutcomeContinuationAction, - taskCompletionSource, - TaskContinuationOptions.ExecuteSynchronously); + task.ContinueWith(LinkOutcomeTcs, taskCompletionSource, TaskContinuationOptions.ExecuteSynchronously); + break; + } + } + + static readonly Action LinkOutcomePromise = (t, promise) => + { + switch (t.Status) + { + case TaskStatus.RanToCompletion: + ((IPromise)promise).TryComplete(); + break; + case TaskStatus.Canceled: + ((IPromise)promise).TrySetCanceled(); + break; + case TaskStatus.Faulted: + ((IPromise)promise).TryUnwrap(t.Exception); + break; + default: + throw new ArgumentOutOfRangeException(); + } + }; + + public static void LinkOutcome(this Task task, IPromise promise) + { + switch (task.Status) + { + case TaskStatus.RanToCompletion: + promise.TryComplete(); + break; + case TaskStatus.Canceled: + promise.TrySetCanceled(); + break; + case TaskStatus.Faulted: + promise.TryUnwrap(task.Exception); + break; + default: + task.ContinueWith(LinkOutcomePromise, promise, TaskContinuationOptions.ExecuteSynchronously); break; } } - public static void LinkOutcome(this ChannelFuture future, IChannelPromise promise) + public static async void LinkOutcome(this ValueTask future, IPromise promise) { - if (future.IsCompleted) + try + { + await future; + promise.TryComplete(); + } + catch (Exception ex) + { + promise.TrySetException(ex); + } + + /* + + if (future.IsCompletedSuccessfully) + { + promise.TryComplete(); + } + else if (future.IsFaulted || future.IsCanceled) { try { - future.GetResult(); - promise.TryComplete(); + future.GetAwaiter().GetResult(); } catch (Exception ex) { @@ -96,8 +150,8 @@ public static void LinkOutcome(this ChannelFuture future, IChannelPromise promis } else { - future.OnCompleted(() => LinkOutcome(future, promise)); - } + future.GetAwaiter().OnCompleted(() => LinkOutcome(future, promise)); + }*/ } @@ -153,6 +207,18 @@ public static void TryUnwrap(this TaskCompletionSource completionSource, E completionSource.TrySetException(exception); } } + + public static void TryUnwrap(this IPromise promise, Exception exception) + { + if (exception is AggregateException aggregateException) + { + promise.TrySetException(aggregateException.InnerException); + } + else + { + promise.TrySetException(exception); + } + } public static Exception Unwrap(this Exception exception) { diff --git a/src/DotNetty.Handlers/DotNetty.Handlers.csproj b/src/DotNetty.Handlers/DotNetty.Handlers.csproj index d9afdf486..32075b018 100644 --- a/src/DotNetty.Handlers/DotNetty.Handlers.csproj +++ b/src/DotNetty.Handlers/DotNetty.Handlers.csproj @@ -43,4 +43,7 @@ + + + \ No newline at end of file diff --git a/src/DotNetty.Handlers/Logging/LoggingHandler.cs b/src/DotNetty.Handlers/Logging/LoggingHandler.cs index f6e24557c..7363bafcd 100644 --- a/src/DotNetty.Handlers/Logging/LoggingHandler.cs +++ b/src/DotNetty.Handlers/Logging/LoggingHandler.cs @@ -209,8 +209,7 @@ public override void ChannelRead(IChannelHandlerContext ctx, object message) } ctx.FireChannelRead(message); } - - public override Task WriteAsync(IChannelHandlerContext ctx, object msg) + public override void ChannelReadComplete(IChannelHandlerContext ctx) { if (this.Logger.IsEnabled(this.InternalLevel)) @@ -253,7 +252,7 @@ public override void Read(IChannelHandlerContext ctx) ctx.Read(); } - public override ChannelFuture WriteAsync(IChannelHandlerContext ctx, object msg) + public override ValueTask WriteAsync(IChannelHandlerContext ctx, object msg) { if (this.Logger.IsEnabled(this.InternalLevel)) { diff --git a/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs b/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs index 62348081d..c9c584eba 100644 --- a/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs +++ b/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs @@ -303,18 +303,16 @@ public override void ChannelReadComplete(IChannelHandlerContext context) context.FireChannelReadComplete(); } - public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) + public override async ValueTask WriteAsync(IChannelHandlerContext context, object message) { if (this.writerIdleTime.Ticks > 0 || this.allIdleTime.Ticks > 0) { - ChannelFuture task = context.WriteAsync(message); - //task.ContinueWith(this.writeListener, TaskContinuationOptions.ExecuteSynchronously); - task.OnCompleted(this.writeListener); - - return task; + await context.WriteAsync(message); + this.writeListener(); + return; } - return context.WriteAsync(message); + await context.WriteAsync(message); } void Initialize(IChannelHandlerContext context) diff --git a/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs b/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs index 78d914762..76f616595 100644 --- a/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs +++ b/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs @@ -83,9 +83,9 @@ public WriteTimeoutHandler(TimeSpan timeout) : TimeSpan.Zero; } - public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { - ChannelFuture task = context.WriteAsync(message); + ValueTask task = context.WriteAsync(message); if (this.timeout.Ticks > 0) { @@ -106,20 +106,20 @@ public override void HandlerRemoved(IChannelHandlerContext context) } } - void ScheduleTimeout(IChannelHandlerContext context, ChannelFuture future) + async void ScheduleTimeout(IChannelHandlerContext context, ValueTask future) { // Schedule a timeout. var task = new WriteTimeoutTask(context, future, this); - task.ScheduledTask = context.Executor.Schedule(task, timeout); + task.ScheduledTask = context.Executor.Schedule(task, this.timeout); if (!task.ScheduledTask.Completion.IsCompleted) { this.AddWriteTimeoutTask(task); // Cancel the scheduled timeout if the flush promise is complete. - future.OnCompleted(WriteTimeoutTask.OperationCompleteAction, task); - // future.ContinueWith(WriteTimeoutTask.OperationCompleteAction, task, TaskContinuationOptions.ExecuteSynchronously); + await future; + WriteTimeoutTask.OperationCompleteAction(task); } } @@ -147,15 +147,15 @@ protected virtual void WriteTimedOut(IChannelHandlerContext context) } } - sealed class WriteTimeoutTask : AbstractChannelPromise, IRunnable + sealed class WriteTimeoutTask : AbstractPromise, IRunnable { readonly WriteTimeoutHandler handler; readonly IChannelHandlerContext context; - readonly ChannelFuture future; + readonly ValueTask future; public static readonly Action OperationCompleteAction = HandleOperationComplete; - public WriteTimeoutTask(IChannelHandlerContext context, ChannelFuture future, WriteTimeoutHandler handler) + public WriteTimeoutTask(IChannelHandlerContext context, ValueTask future, WriteTimeoutHandler handler) { this.context = context; this.future = future; diff --git a/src/DotNetty.Handlers/Tls/TlsHandler.cs b/src/DotNetty.Handlers/Tls/TlsHandler.cs index 39189d90e..f7e3f24aa 100644 --- a/src/DotNetty.Handlers/Tls/TlsHandler.cs +++ b/src/DotNetty.Handlers/Tls/TlsHandler.cs @@ -36,7 +36,7 @@ public sealed class TlsHandler : ByteToMessageDecoder int packetLength; volatile IChannelHandlerContext capturedContext; BatchingPendingWriteQueue pendingUnencryptedWrites; - ChannelFuture lastContextWriteTask; + Task lastContextWriteTask; bool firedChannelRead; IByteBuffer pendingSslStreamReadBuffer; Task pendingSslStreamReadFuture; @@ -508,7 +508,7 @@ bool EnsureAuthenticated() return oldState.Has(TlsHandlerState.Authenticated); } - public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { if (!(message is IByteBuffer)) { @@ -572,12 +572,12 @@ void Wrap(IChannelHandlerContext context) buf.ReadBytes(this.sslStream, buf.ReadableBytes); // this leads to FinishWrap being called 0+ times buf.Release(); - IChannelPromise promise = this.pendingUnencryptedWrites.Remove(); - ChannelFuture task = this.lastContextWriteTask; + IPromise promise = this.pendingUnencryptedWrites.Remove(); + Task task = this.lastContextWriteTask; if (!task.IsCompleted) { task.LinkOutcome(promise); - this.lastContextWriteTask = ChannelFuture.Completed; + this.lastContextWriteTask = TaskEx.Completed; } else { @@ -606,12 +606,12 @@ void FinishWrap(byte[] buffer, int offset, int count) output.WriteBytes(buffer, offset, count); } - this.lastContextWriteTask = this.capturedContext.WriteAsync(output); + this.lastContextWriteTask = this.capturedContext.WriteAsync(output).AsTask(); } - ChannelFuture FinishWrapNonAppDataAsync(byte[] buffer, int offset, int count) + ValueTask FinishWrapNonAppDataAsync(byte[] buffer, int offset, int count) { - var future = this.capturedContext.WriteAndFlushAsync(Unpooled.WrappedBuffer(buffer, offset, count)); + ValueTask future = this.capturedContext.WriteAndFlushAsync(Unpooled.WrappedBuffer(buffer, offset, count)); this.ReadIfNeeded(this.capturedContext); return future; } diff --git a/src/DotNetty.Transport/Channels/AbstractChannel.cs b/src/DotNetty.Transport/Channels/AbstractChannel.cs index 61a873abe..4145f0c30 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannel.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannel.cs @@ -189,9 +189,9 @@ public IChannel Read() return this; } - public ChannelFuture WriteAsync(object msg) => this.pipeline.WriteAsync(msg); + public ValueTask WriteAsync(object msg) => this.pipeline.WriteAsync(msg); - public ChannelFuture WriteAndFlushAsync(object message) => this.pipeline.WriteAndFlushAsync(message); + public ValueTask WriteAndFlushAsync(object message) => this.pipeline.WriteAndFlushAsync(message); public Task CloseCompletion => this.closeFuture.Task; @@ -670,7 +670,7 @@ public void BeginRead() } } - public ChannelFuture WriteAsync(object msg) + public ValueTask WriteAsync(object msg) { this.AssertEventLoop(); @@ -684,8 +684,7 @@ public ChannelFuture WriteAsync(object msg) // release message now to prevent resource-leak ReferenceCountUtil.Release(msg); - throw new ClosedChannelException(); - //return TaskEx.FromException(new ClosedChannelException()); + return new ClosedChannelException().ToValueTask(); } int size; @@ -701,7 +700,7 @@ public ChannelFuture WriteAsync(object msg) catch (Exception t) { ReferenceCountUtil.Release(msg); - throw; //return TaskEx.FromException(t); + return t.ToValueTask(); } return outboundBuffer.AddMessage(msg, size); diff --git a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs index 0265599d3..3af775513 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs @@ -793,16 +793,16 @@ void InvokeRead() } } - public ChannelFuture WriteAsync(object msg) + public ValueTask WriteAsync(object msg) { Contract.Requires(msg != null); // todo: check for cancellation return this.WriteAsync(msg, false); } - ChannelFuture InvokeWriteAsync(object msg) => this.Added ? this.InvokeWriteAsync0(msg) : this.WriteAsync(msg); + ValueTask InvokeWriteAsync(object msg) => this.Added ? this.InvokeWriteAsync0(msg) : this.WriteAsync(msg); - ChannelFuture InvokeWriteAsync0(object msg) + ValueTask InvokeWriteAsync0(object msg) { try { @@ -810,7 +810,7 @@ ChannelFuture InvokeWriteAsync0(object msg) } catch (Exception ex) { - return ChannelFuture.FromException(ex); + return ex.ToValueTask(); } } @@ -853,7 +853,7 @@ void InvokeFlush0() } } - public ChannelFuture WriteAndFlushAsync(object message) + public ValueTask WriteAndFlushAsync(object message) { Contract.Requires(message != null); // todo: check for cancellation @@ -861,18 +861,18 @@ public ChannelFuture WriteAndFlushAsync(object message) return this.WriteAsync(message, true); } - ChannelFuture InvokeWriteAndFlushAsync(object msg) + ValueTask InvokeWriteAndFlushAsync(object msg) { if (this.Added) { - ChannelFuture task = this.InvokeWriteAsync0(msg); + ValueTask task = this.InvokeWriteAsync0(msg); this.InvokeFlush0(); return task; } return this.WriteAndFlushAsync(msg); } - ChannelFuture WriteAsync(object msg, bool flush) + ValueTask WriteAsync(object msg, bool flush) { AbstractChannelHandlerContext next = this.FindContextOutbound(); object m = this.pipeline.Touch(msg, next); @@ -961,7 +961,7 @@ static void SafeExecuteOutbound(IEventExecutor executor, AbstractWriteTask task, { try { - task.TryComplete(cause); + task.TrySetException(cause); } finally { @@ -974,8 +974,7 @@ static void SafeExecuteOutbound(IEventExecutor executor, AbstractWriteTask task, public override string ToString() => $"{typeof(IChannelHandlerContext).Name} ({this.Name}, {this.Channel})"; - - abstract class AbstractWriteTask : AbstractRecyclableChannelPromise, IRunnable + abstract class AbstractWriteTask : AbstractRecyclablePromise, IRunnable { static readonly bool EstimateTaskSizeOnSubmit = SystemPropertyUtil.GetBoolean("io.netty.transport.estimateSizeOnSubmit", true); @@ -1035,7 +1034,7 @@ public void Run() } catch (Exception ex) { - this.TryComplete(ex); + this.TrySetException(ex); } finally { @@ -1048,7 +1047,7 @@ public void Run() } } - protected virtual ChannelFuture WriteAsync(AbstractChannelHandlerContext ctx, object msg) => ctx.InvokeWriteAsync(msg); + protected virtual ValueTask WriteAsync(AbstractChannelHandlerContext ctx, object msg) => ctx.InvokeWriteAsync(msg); /*public override void Recycle() { @@ -1091,9 +1090,9 @@ public static WriteAndFlushTask NewInstance(AbstractChannelHandlerContext ctx, o { } - protected override ChannelFuture WriteAsync(AbstractChannelHandlerContext ctx, object msg) + protected override ValueTask WriteAsync(AbstractChannelHandlerContext ctx, object msg) { - ChannelFuture result = base.WriteAsync(ctx, msg); + ValueTask result = base.WriteAsync(ctx, msg); ctx.InvokeFlush(); return result; } diff --git a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs index d727567a9..47ae0166a 100644 --- a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs @@ -7,6 +7,7 @@ namespace DotNetty.Transport.Channels using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Threading.Tasks; + using System.Threading.Tasks.Sources; using DotNetty.Common; using DotNetty.Common.Concurrency; using DotNetty.Common.Internal.Logging; @@ -64,7 +65,7 @@ public int Size } /// Add the given msg and returns for completion of processing msg. - public ChannelFuture Add(object msg) + public ValueTask Add(object msg) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(msg != null); @@ -160,7 +161,7 @@ public void RemoveAndFail(Exception cause) /// if something was written and null if the /// is empty. /// - public ChannelFuture RemoveAndWriteAllAsync() + public ValueTask RemoveAndWriteAllAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); @@ -173,7 +174,7 @@ public ChannelFuture RemoveAndWriteAllAsync() if (write == null) { // empty so just return null - return ChannelFuture.Completed; + return default(ValueTask); } // Guard against re-entrance by directly reset @@ -181,7 +182,7 @@ public ChannelFuture RemoveAndWriteAllAsync() int currentSize = this.size; this.size = 0; - var tasks = new List(currentSize); + var tasks = new List(currentSize); while (write != null) { PendingWrite next = write.Next; @@ -193,7 +194,7 @@ public ChannelFuture RemoveAndWriteAllAsync() write = next; } this.AssertEmpty(); - return new ChannelFuture(new AggregatingPromise(tasks)); + return new ValueTask(new AggregatingPromise(tasks), 0); } void AssertEmpty() => Contract.Assert(this.tail == null && this.head == null && this.size == 0); @@ -206,26 +207,26 @@ public ChannelFuture RemoveAndWriteAllAsync() /// if something was written and null if the /// is empty. /// - public ChannelFuture RemoveAndWriteAsync() + public ValueTask RemoveAndWriteAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { - return ChannelFuture.Completed; + return default(ValueTask); } object msg = write.Messages; this.Recycle(write, true); this.ctx.WriteAsync(msg).LinkOutcome(write); - return new ChannelFuture(write); + return write; } /// /// Removes a pending write operation and release it's message via . /// /// of the pending write or null if the queue is empty. - public IChannelPromise Remove() + public IPromise Remove() { Contract.Assert(this.ctx.Executor.InEventLoop); @@ -317,7 +318,7 @@ static void ReleaseMessages(List messages) } /// Holds all meta-data and construct the linked-list structure. - sealed class PendingWrite : AbstractRecyclableChannelPromise + sealed class PendingWrite : AbstractRecyclablePromise { static readonly ThreadLocalPool Pool = new ThreadLocalPool(handle => new PendingWrite(handle)); diff --git a/src/DotNetty.Transport/Channels/ChannelHandlerAdapter.cs b/src/DotNetty.Transport/Channels/ChannelHandlerAdapter.cs index e2ec79fc7..c6a53ea54 100644 --- a/src/DotNetty.Transport/Channels/ChannelHandlerAdapter.cs +++ b/src/DotNetty.Transport/Channels/ChannelHandlerAdapter.cs @@ -48,7 +48,7 @@ public virtual void HandlerRemoved(IChannelHandlerContext context) public virtual void UserEventTriggered(IChannelHandlerContext context, object evt) => context.FireUserEventTriggered(evt); [Skip] - public virtual ChannelFuture WriteAsync(IChannelHandlerContext context, object message) => context.WriteAsync(message); + public virtual ValueTask WriteAsync(IChannelHandlerContext context, object message) => context.WriteAsync(message); [Skip] public virtual void Flush(IChannelHandlerContext context) => context.Flush(); diff --git a/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs b/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs index 9162a04ce..ebdd7842f 100644 --- a/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs +++ b/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs @@ -17,6 +17,8 @@ namespace DotNetty.Transport.Channels using System.Runtime.ExceptionServices; using System.Security.Cryptography; using System.Threading; + using System.Threading.Tasks; + using System.Threading.Tasks.Sources; using DotNetty.Buffers; using DotNetty.Common; using DotNetty.Common.Concurrency; @@ -57,12 +59,12 @@ internal ChannelOutboundBuffer(IChannel channel) } /// - /// Adds the given message to this . The given - /// will be notified once the message was written. + /// Adds the given message to this . Returned + /// will be notified once the message was written. /// /// The message to add to the buffer. /// The size of the message. - public ChannelFuture AddMessage(object msg, int size) + public ValueTask AddMessage(object msg, int size) { Entry entry = Entry.NewInstance(this.channel.EventLoop, msg, size); if (this.tailEntry == null) @@ -786,7 +788,7 @@ public interface IMessageProcessor bool ProcessMessage(object msg); } - sealed class Entry : AbstractRecyclableChannelPromise + sealed class Entry : AbstractRecyclablePromise { static readonly ThreadLocalPool Pool = new ThreadLocalPool(h => new Entry(h)); diff --git a/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs b/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs index e1bf13ca9..7119aebab 100644 --- a/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs +++ b/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs @@ -822,7 +822,7 @@ public IChannelPipeline Read() return this; } - public ChannelFuture WriteAsync(object msg) => this.tail.WriteAsync(msg); + public ValueTask WriteAsync(object msg) => this.tail.WriteAsync(msg); public IChannelPipeline Flush() { @@ -830,7 +830,7 @@ public IChannelPipeline Flush() return this; } - public ChannelFuture WriteAndFlushAsync(object msg) => this.tail.WriteAndFlushAsync(msg); + public ValueTask WriteAndFlushAsync(object msg) => this.tail.WriteAndFlushAsync(msg); string FilterName(string name, IChannelHandler handler) { @@ -1049,7 +1049,7 @@ public void HandlerRemoved(IChannelHandlerContext context) public void UserEventTriggered(IChannelHandlerContext context, object evt) => ReferenceCountUtil.Release(evt); [Skip] - public ChannelFuture WriteAsync(IChannelHandlerContext ctx, object message) => ctx.WriteAsync(message); + public ValueTask WriteAsync(IChannelHandlerContext ctx, object message) => ctx.WriteAsync(message); [Skip] public void Flush(IChannelHandlerContext context) => context.Flush(); @@ -1092,7 +1092,7 @@ public HeadContext(DefaultChannelPipeline pipeline) public void Read(IChannelHandlerContext context) => this.channelUnsafe.BeginRead(); - public ChannelFuture WriteAsync(IChannelHandlerContext context, object message) => this.channelUnsafe.WriteAsync(message); + public ValueTask WriteAsync(IChannelHandlerContext context, object message) => this.channelUnsafe.WriteAsync(message); [Skip] public void HandlerAdded(IChannelHandlerContext context) diff --git a/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs b/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs index d8d515124..331e86b86 100644 --- a/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs +++ b/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs @@ -295,7 +295,7 @@ public bool WriteOutbound(params object[] msgs) return IsNotEmpty(this.outboundMessages); } - ThreadLocalObjectList futures = ThreadLocalObjectList.NewInstance(msgs.Length); + ThreadLocalObjectList futures = ThreadLocalObjectList.NewInstance(msgs.Length); foreach (object m in msgs) { @@ -324,6 +324,7 @@ public bool WriteOutbound(params object[] msgs) future.ContinueWith(t => this.RecordException(t)); } } + futures.Return(); this.RunPendingTasks(); @@ -331,15 +332,16 @@ public bool WriteOutbound(params object[] msgs) return IsNotEmpty(this.outboundMessages); } - void RecordException(ChannelFuture future) + void RecordException(Task future) { - try - { - future.GetResult(); - } - catch (Exception ex) + switch (future.Status) { - this.RecordException(ex); + case TaskStatus.Canceled: + case TaskStatus.Faulted: + this.RecordException(future.Exception); + break; + default: + break; } } diff --git a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs index c04b3884b..93c8debd7 100644 --- a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs +++ b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs @@ -54,13 +54,13 @@ public IChannel Find(IChannelId id) } } - public ChannelFuture WriteAsync(object message) => this.WriteAsync(message, ChannelMatchers.All()); + public ValueTask WriteAsync(object message) => this.WriteAsync(message, ChannelMatchers.All()); - public ChannelFuture WriteAsync(object message, IChannelMatcher matcher) + public ValueTask WriteAsync(object message, IChannelMatcher matcher) { Contract.Requires(message != null); Contract.Requires(matcher != null); - var futures = new Dictionary(); + var futures = new Dictionary(); foreach (IChannel c in this.nonServerChannels.Values) { if (matcher.Matches(c)) @@ -144,13 +144,13 @@ public bool Remove(IChannel channel) IEnumerator IEnumerable.GetEnumerator() => new CombinedEnumerator(this.serverChannels.Values.GetEnumerator(), this.nonServerChannels.Values.GetEnumerator()); - public ChannelFuture WriteAndFlushAsync(object message) => this.WriteAndFlushAsync(message, ChannelMatchers.All()); + public ValueTask WriteAndFlushAsync(object message) => this.WriteAndFlushAsync(message, ChannelMatchers.All()); - public ChannelFuture WriteAndFlushAsync(object message, IChannelMatcher matcher) + public ValueTask WriteAndFlushAsync(object message, IChannelMatcher matcher) { Contract.Requires(message != null); Contract.Requires(matcher != null); - var futures = new Dictionary(); + var futures = new Dictionary(); foreach (IChannel c in this.nonServerChannels.Values) { if (matcher.Matches(c)) diff --git a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs index b11657814..58ea6396e 100644 --- a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs +++ b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs @@ -6,29 +6,33 @@ namespace DotNetty.Transport.Channels.Groups using System; using System.Collections.Generic; using System.Diagnostics.Contracts; + using System.Runtime.CompilerServices; using System.Threading.Tasks; using DotNetty.Common.Concurrency; - public sealed class DefaultChannelGroupPromise : AbstractChannelPromise + public sealed class DefaultChannelGroupPromise : AbstractPromise { - readonly Dictionary futures; + readonly Dictionary futures; int failureCount; int successCount; IList> failures; - public DefaultChannelGroupPromise(Dictionary futures /*, IEventExecutor executor*/) + public DefaultChannelGroupPromise( + Dictionary futures + /*, IEventExecutor executor*/) { Contract.Requires(futures != null); - this.futures = new Dictionary(); - foreach (KeyValuePair pair in futures) + this.futures = new Dictionary(); + foreach (KeyValuePair pair in futures) { this.futures.Add(pair.Key, pair.Value); - pair.Value.OnCompleted(() => + ValueTaskAwaiter awaiter = pair.Value.GetAwaiter(); + awaiter.OnCompleted(() => { try { - pair.Value.GetResult(); + awaiter.GetResult(); this.successCount++; } catch(Exception ex) @@ -48,7 +52,7 @@ public DefaultChannelGroupPromise(Dictionary futures /* { if (this.failureCount > 0) { - this.TryComplete(new ChannelGroupException(this.failures)); + this.TrySetException(new ChannelGroupException(this.failures)); } else { diff --git a/src/DotNetty.Transport/Channels/Groups/IChannelGroup.cs b/src/DotNetty.Transport/Channels/Groups/IChannelGroup.cs index 8bb95f778..b998b6be0 100644 --- a/src/DotNetty.Transport/Channels/Groups/IChannelGroup.cs +++ b/src/DotNetty.Transport/Channels/Groups/IChannelGroup.cs @@ -18,17 +18,17 @@ public interface IChannelGroup : ICollection, IComparable IChannel Read(); - ChannelFuture WriteAsync(object message); + ValueTask WriteAsync(object message); IChannel Flush(); - ChannelFuture WriteAndFlushAsync(object message); + ValueTask WriteAndFlushAsync(object message); } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/IChannelHandler.cs b/src/DotNetty.Transport/Channels/IChannelHandler.cs index 866eb8d06..2155be5a2 100644 --- a/src/DotNetty.Transport/Channels/IChannelHandler.cs +++ b/src/DotNetty.Transport/Channels/IChannelHandler.cs @@ -40,7 +40,7 @@ public interface IChannelHandler void HandlerRemoved(IChannelHandlerContext context); - ChannelFuture WriteAsync(IChannelHandlerContext context, object message); + ValueTask WriteAsync(IChannelHandlerContext context, object message); void Flush(IChannelHandlerContext context); diff --git a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs index b8b5287ec..bb01c6a17 100644 --- a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs @@ -67,11 +67,11 @@ public interface IChannelHandlerContext : IAttributeMap IChannelHandlerContext Read(); - ChannelFuture WriteAsync(object message); // todo: optimize: add flag saying if handler is interested in task, do not produce task if it isn't needed + ValueTask WriteAsync(object message); // todo: optimize: add flag saying if handler is interested in task, do not produce task if it isn't needed IChannelHandlerContext Flush(); - ChannelFuture WriteAndFlushAsync(object message); + ValueTask WriteAndFlushAsync(object message); /// /// Request to bind to the given . diff --git a/src/DotNetty.Transport/Channels/IChannelPipeline.cs b/src/DotNetty.Transport/Channels/IChannelPipeline.cs index 047e98d31..1508e2465 100644 --- a/src/DotNetty.Transport/Channels/IChannelPipeline.cs +++ b/src/DotNetty.Transport/Channels/IChannelPipeline.cs @@ -683,7 +683,7 @@ public interface IChannelPipeline : IEnumerable /// once you want to request to flush all pending data to the actual transport. /// /// An await-able task. - ChannelFuture WriteAsync(object msg); + ValueTask WriteAsync(object msg); /// /// Request to flush all pending messages. @@ -694,6 +694,6 @@ public interface IChannelPipeline : IEnumerable /// /// Shortcut for calling both and . /// - ChannelFuture WriteAndFlushAsync(object msg); + ValueTask WriteAndFlushAsync(object msg); } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/IChannelUnsafe.cs b/src/DotNetty.Transport/Channels/IChannelUnsafe.cs index 2e2ec2e2c..f0eade341 100644 --- a/src/DotNetty.Transport/Channels/IChannelUnsafe.cs +++ b/src/DotNetty.Transport/Channels/IChannelUnsafe.cs @@ -27,7 +27,7 @@ public interface IChannelUnsafe void BeginRead(); - ChannelFuture WriteAsync(object message); + ValueTask WriteAsync(object message); void Flush(); diff --git a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs index e9fa84662..1d8549092 100644 --- a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs @@ -7,6 +7,7 @@ namespace DotNetty.Transport.Channels using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Threading.Tasks; + using System.Threading.Tasks.Sources; using DotNetty.Common; using DotNetty.Common.Concurrency; using DotNetty.Common.Internal.Logging; @@ -70,7 +71,7 @@ public int Size /// /// The message to add to the . /// An await-able task. - public ChannelFuture Add(object msg) + public ValueTask Add(object msg) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(msg != null); @@ -153,7 +154,7 @@ public void RemoveAndFail(Exception cause) /// Removes all pending write operation and performs them via /// /// An await-able task. - public ChannelFuture RemoveAndWriteAllAsync() + public ValueTask RemoveAndWriteAllAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); @@ -166,7 +167,7 @@ public ChannelFuture RemoveAndWriteAllAsync() if (write == null) { // empty so just return null - return ChannelFuture.Completed; + return default(ValueTask); } // Guard against re-entrance by directly reset @@ -174,7 +175,7 @@ public ChannelFuture RemoveAndWriteAllAsync() int currentSize = this.size; this.size = 0; - var tasks = new List(currentSize); + var tasks = new List(currentSize); while (write != null) { @@ -186,7 +187,7 @@ public ChannelFuture RemoveAndWriteAllAsync() write = next; } this.AssertEmpty(); - return new ChannelFuture(new AggregatingPromise(tasks)); + return new ValueTask(new AggregatingPromise(tasks), 0); } void AssertEmpty() => Contract.Assert(this.tail == null && this.head == null && this.size == 0); @@ -195,14 +196,14 @@ public ChannelFuture RemoveAndWriteAllAsync() /// Removes a pending write operation and performs it via . /// /// An await-able task. - public ChannelFuture RemoveAndWriteAsync() + public ValueTask RemoveAndWriteAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { - return ChannelFuture.Completed; + return default(ValueTask); } object msg = write.Msg; this.Recycle(write, true); @@ -217,14 +218,14 @@ public ChannelFuture RemoveAndWriteAsync() /// /// The of the pending write, or null if the queue is empty. /// - public ChannelFuture Remove() + public ValueTask Remove() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { - return ChannelFuture.Completed; + return default(ValueTask); } ReferenceCountUtil.SafeRelease(write.Msg); this.Recycle(write, true); @@ -277,7 +278,7 @@ void Recycle(PendingWrite write, bool update) /// /// Holds all meta-data and constructs the linked-list structure. /// - sealed class PendingWrite : AbstractRecyclableChannelPromise + sealed class PendingWrite : AbstractRecyclablePromise { static readonly ThreadLocalPool Pool = new ThreadLocalPool(handle => new PendingWrite(handle)); diff --git a/src/DotNetty.Transport/Channels/Util.cs b/src/DotNetty.Transport/Channels/Util.cs index f73e3cbfd..51f439f3d 100644 --- a/src/DotNetty.Transport/Channels/Util.cs +++ b/src/DotNetty.Transport/Channels/Util.cs @@ -29,7 +29,7 @@ public static void SafeSetSuccess(TaskCompletionSource promise, IInternalLogger /// /// Marks the specified {@code promise} as success. If the {@code promise} is done already, log a message. /// - public static void SafeSetSuccess(IChannelPromise promise, IInternalLogger logger) + public static void SafeSetSuccess(IPromise promise, IInternalLogger logger) { if (!promise.TryComplete()) { @@ -55,9 +55,9 @@ public static void SafeSetFailure(TaskCompletionSource promise, Exception cause, /// /// Marks the specified {@code promise} as failure. If the {@code promise} is done already, log a message. /// - public static void SafeSetFailure(IChannelPromise promise, Exception cause, IInternalLogger logger) + public static void SafeSetFailure(IPromise promise, Exception cause, IInternalLogger logger) { - if (!promise.TryComplete(cause)) + if (!promise.TrySetException(cause)) { logger.Warn($"Failed to mark a promise as failure because it's done already: {promise}", cause); } diff --git a/test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj b/test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj index 3570fb1f7..42ba6d491 100644 --- a/test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj +++ b/test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj @@ -12,6 +12,8 @@ + + diff --git a/test/DotNetty.Tests.Common/ChannelFutureExtensions.cs b/test/DotNetty.Tests.Common/ChannelFutureExtensions.cs deleted file mode 100644 index de8a8506f..000000000 --- a/test/DotNetty.Tests.Common/ChannelFutureExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace DotNetty.Tests.Common -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using DotNetty.Common.Concurrency; - - public static class ChannelFutureExtensions - { - public static bool Wait(this ChannelFuture future, TimeSpan timeout) - { - return Task.Run(async () => await future).Wait(timeout); - - /*var mre = new ManualResetEventSlim(false); - future.OnCompleted(mre.Set); - return mre.Wait(timeout);*/ - } - } -} \ No newline at end of file diff --git a/test/DotNetty.Tests.Common/ValueTaskExtensions.cs b/test/DotNetty.Tests.Common/ValueTaskExtensions.cs new file mode 100644 index 000000000..24ae3a963 --- /dev/null +++ b/test/DotNetty.Tests.Common/ValueTaskExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Tests.Common +{ + using System.Threading.Tasks; + using DotNetty.Transport.Channels; + + public static class ValueTaskExtensions + { + public static async void CloseOnComplete(this ValueTask task, IChannel channel) + { + try + { + await task; + } + finally + { + channel.CloseAsync(); + } + + } + + + public static async void CloseOnComplete(this ValueTask task, IChannelHandlerContext ctx) + { + try + { + await task; + } + finally + { + ctx.CloseAsync(); + } + } + } +} \ No newline at end of file diff --git a/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs b/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs index e74e5aedc..de5d4c4ab 100644 --- a/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs @@ -75,13 +75,13 @@ void AutoReadOffDuringReadOnlyReadsOneTime0(bool readOutsideEventLoopThread, Ser Assert.NotNull(this.clientChannel.LocalAddress); // 3 bytes means 3 independent reads for TestRecvByteBufAllocator - ChannelFuture writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[3])); - Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); + ValueTask writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[3])); + Assert.True(writeTask.AsTask().Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); serverInitializer.AutoReadHandler.AssertSingleRead(); // 3 bytes means 3 independent reads for TestRecvByteBufAllocator writeTask = serverInitializer.Channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[3])); - Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Server write task timed out"); + Assert.True(writeTask.AsTask().Wait(TimeSpan.FromSeconds(5)), "Server write task timed out"); clientInitializer.AutoReadHandler.AssertSingleRead(); if (readOutsideEventLoopThread) diff --git a/test/DotNetty.Transport.Libuv.Tests/CompositeBufferGatheringWriteTests.cs b/test/DotNetty.Transport.Libuv.Tests/CompositeBufferGatheringWriteTests.cs index c9fccce32..aa762a1bc 100644 --- a/test/DotNetty.Transport.Libuv.Tests/CompositeBufferGatheringWriteTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/CompositeBufferGatheringWriteTests.cs @@ -131,9 +131,17 @@ public void AssertReceived(IByteBuffer expected) sealed class ServerHandler : ChannelHandlerAdapter { - public override void ChannelActive(IChannelHandlerContext ctx) => - ctx.WriteAndFlushAsync(NewCompositeBuffer(ctx.Allocator)) - .OnCompleted(() => ctx.CloseAsync()); + public override async void ChannelActive(IChannelHandlerContext ctx) + { + try + { + await ctx.WriteAndFlushAsync(NewCompositeBuffer(ctx.Allocator)); + } + finally + { + ctx.CloseAsync(); + } + } } static IByteBuffer NewCompositeBuffer(IByteBufferAllocator alloc) diff --git a/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs b/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs index fa7eb1c64..53a473198 100644 --- a/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs @@ -9,6 +9,7 @@ namespace DotNetty.Transport.Libuv.Tests using System.Threading.Tasks; using DotNetty.Buffers; using DotNetty.Common.Concurrency; + using DotNetty.Tests.Common; using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using Xunit; @@ -62,7 +63,7 @@ public void ClientCloseWithoutServerReadIsDetected() IByteBuffer buf = this.clientChannel.Allocator.Buffer(ExpectedBytes); buf.SetWriterIndex(buf.WriterIndex + ExpectedBytes); - this.clientChannel.WriteAndFlushAsync(buf).OnCompleted(() => this.clientChannel.CloseAsync()); + this.clientChannel.WriteAndFlushAsync(buf).CloseOnComplete(this.clientChannel); Task completion = serverHandler.Completion; Assert.True(completion.Wait(DefaultTimeout)); @@ -172,7 +173,7 @@ public override void ChannelActive(IChannelHandlerContext ctx) { IByteBuffer buf = ctx.Allocator.Buffer(this.expectedBytesRead); buf.SetWriterIndex(buf.WriterIndex + this.expectedBytesRead); - ctx.WriteAndFlushAsync(buf).OnCompleted(() => ctx.CloseAsync()); + ctx.WriteAndFlushAsync(buf).CloseOnComplete(ctx); ctx.FireChannelActive(); } diff --git a/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs b/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs index 1a7b803ed..e503bb456 100644 --- a/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs @@ -61,8 +61,8 @@ void ReadPendingIsResetAfterEachRead0(ServerBootstrap sb, Bootstrap cb) this.clientChannel = task.Result; Assert.NotNull(this.clientChannel.LocalAddress); - ChannelFuture writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[1024])); - Assert.True(writeTask.Wait(DefaultTimeout), "Write task timed out"); + ValueTask writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[1024])); + Assert.True(writeTask.AsTask().Wait(DefaultTimeout), "Write task timed out"); ExceptionHandler exceptionHandler = serverInitializer.ErrorHandler; Assert.True(exceptionHandler.Inactive.Wait(DefaultTimeout), "Handler inactive timed out"); diff --git a/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs b/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs index edf64b1e9..772e3de2b 100644 --- a/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs @@ -71,13 +71,13 @@ void ReadPendingIsResetAfterEachRead0(ServerBootstrap sb, Bootstrap cb) Assert.NotNull(this.clientChannel.LocalAddress); // 4 bytes means 2 read loops for TestNumReadsRecvByteBufAllocator - ChannelFuture writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[4])); - Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); + ValueTask writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[4])); + Assert.True(writeTask.AsTask().Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); // 4 bytes means 2 read loops for TestNumReadsRecvByteBufAllocator Assert.True(serverInitializer.Initialize.Wait(DefaultTimeout), "Server initializer timed out"); writeTask = serverInitializer.Channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[4])); - Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Server write task timed out"); + Assert.True(writeTask.AsTask().Wait(TimeSpan.FromSeconds(5)), "Server write task timed out"); serverInitializer.Channel.Read(); serverInitializer.ReadPendingHandler.AssertAllRead(); diff --git a/test/DotNetty.Transport.Tests.Performance/Sockets/SocketDatagramChannelPerfSpecs.cs b/test/DotNetty.Transport.Tests.Performance/Sockets/SocketDatagramChannelPerfSpecs.cs index 4c4d6b460..2e154e211 100644 --- a/test/DotNetty.Transport.Tests.Performance/Sockets/SocketDatagramChannelPerfSpecs.cs +++ b/test/DotNetty.Transport.Tests.Performance/Sockets/SocketDatagramChannelPerfSpecs.cs @@ -86,7 +86,7 @@ public OutboundCounter(Counter writes) this.writes = writes; } - public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { this.writes.Increment(); return context.WriteAsync(message); diff --git a/test/DotNetty.Transport.Tests.Performance/Utilities/CounterHandlerOutbound.cs b/test/DotNetty.Transport.Tests.Performance/Utilities/CounterHandlerOutbound.cs index 84468b5a4..063489907 100644 --- a/test/DotNetty.Transport.Tests.Performance/Utilities/CounterHandlerOutbound.cs +++ b/test/DotNetty.Transport.Tests.Performance/Utilities/CounterHandlerOutbound.cs @@ -17,7 +17,7 @@ public CounterHandlerOutbound(Counter throughput) this.throughput = throughput; } - public override ChannelFuture WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { this.throughput.Increment(); return context.WriteAsync(message); From 2b6f37d961cd2aecb654528d20b8e74026109826 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Fri, 9 Mar 2018 19:48:33 -0800 Subject: [PATCH 03/29] tls fixes --- src/DotNetty.Handlers/Tls/TlsHandler.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/DotNetty.Handlers/Tls/TlsHandler.cs b/src/DotNetty.Handlers/Tls/TlsHandler.cs index f7e3f24aa..496e67aa3 100644 --- a/src/DotNetty.Handlers/Tls/TlsHandler.cs +++ b/src/DotNetty.Handlers/Tls/TlsHandler.cs @@ -512,7 +512,7 @@ public override ValueTask WriteAsync(IChannelHandlerContext context, object mess { if (!(message is IByteBuffer)) { - throw new UnsupportedMessageTypeException(message, typeof(IByteBuffer)); + return new UnsupportedMessageTypeException(message, typeof(IByteBuffer)).ToValueTask(); } return this.pendingUnencryptedWrites.Add(message); } @@ -574,10 +574,10 @@ void Wrap(IChannelHandlerContext context) IPromise promise = this.pendingUnencryptedWrites.Remove(); Task task = this.lastContextWriteTask; - if (!task.IsCompleted) + if (task != null) { task.LinkOutcome(promise); - this.lastContextWriteTask = TaskEx.Completed; + this.lastContextWriteTask = null; } else { @@ -814,10 +814,9 @@ IAsyncResult PrepareSyncReadResult(int readBytes, object state) public override void Write(byte[] buffer, int offset, int count) => this.owner.FinishWrap(buffer, offset, count); - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - await this.owner.FinishWrapNonAppDataAsync(buffer, offset, count); - } + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => this.owner.FinishWrapNonAppDataAsync(buffer, offset, count).AsTask(); + #if !NETSTANDARD1_3 static readonly Action WriteCompleteCallback = HandleChannelWriteComplete; From 47ff644ab650bd527a5ea80a53fd3f28b7435278 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Sat, 10 Mar 2018 11:35:18 -0800 Subject: [PATCH 04/29] single callback per future + callback execution on executor --- .../Concurrency/AbstractPromise.cs | 76 +++++-------------- .../Concurrency/AbstractRecyclablePromise.cs | 3 +- 2 files changed, 21 insertions(+), 58 deletions(-) diff --git a/src/DotNetty.Common/Concurrency/AbstractPromise.cs b/src/DotNetty.Common/Concurrency/AbstractPromise.cs index 2e22238c5..085a2d4b1 100644 --- a/src/DotNetty.Common/Concurrency/AbstractPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractPromise.cs @@ -5,6 +5,7 @@ namespace DotNetty.Common.Concurrency { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Threading.Tasks; @@ -17,10 +18,11 @@ public abstract class AbstractPromise : IPromise, IValueTaskSource static readonly Exception CanceledException = new OperationCanceledException(); static readonly Exception CompletedNoException = new Exception(); + protected IEventExecutor executor; protected Exception exception; - - protected int callbackCount; - protected (Action, object)[] callbacks; + + Action callback; + object callbackState; public bool TryComplete() => this.TryComplete0(CompletedNoException); @@ -34,7 +36,7 @@ protected virtual bool TryComplete0(Exception exception) { // Set the exception object to the exception passed in or a sentinel value this.exception = exception; - this.ExecuteCallbacks(); + this.TryExecuteCallback(); return true; } @@ -89,27 +91,13 @@ public virtual void GetResult(short token) public virtual void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { - if (this.callbacks == null) - { - this.callbacks = new (Action, object)[1]; - //this.callbacks = s_completionCallbackPool.Rent(InitialCallbacksSize); - } - - int newIndex = this.callbackCount; - this.callbackCount++; - - if (newIndex == this.callbacks.Length) - { - var newArray = new (Action, object)[this.callbacks.Length * 2]; - Array.Copy(this.callbacks, newArray, this.callbacks.Length); - this.callbacks = newArray; - } - - this.callbacks[newIndex] = (continuation, state); + this.callback = continuation; + this.callbackState = state; + //todo: context preservation if (this.exception != null) { - this.ExecuteCallbacks(); + this.TryExecuteCallback(); } } @@ -134,54 +122,30 @@ bool IsCompletedOrThrow() [MethodImpl(MethodImplOptions.NoInlining)] void ThrowLatchedException() => ExceptionDispatchInfo.Capture(this.exception).Throw(); - void ExecuteCallbacks() + bool TryExecuteCallback() { - if (this.callbacks == null || this.callbackCount == 0) + if (this.callback == null) { - return; + return false; } try { - List exceptions = null; - - for (int i = 0; i < this.callbackCount; i++) - { - try - { - (Action callback, object state) = this.callbacks[i]; - callback(state); - } - catch (Exception ex) - { - if (exceptions == null) - { - exceptions = new List(); - } - - exceptions.Add(ex); - } - } - - if (exceptions != null) - { - throw new AggregateException(exceptions); - } + Contract.Requires(this.executor != null); + this.executor.Execute(this.callback, this.callbackState); + return true; } finally { - this.ClearCallbacks(); + this.ClearCallback(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ClearCallbacks() + protected void ClearCallback() { - if (this.callbackCount > 0) - { - this.callbackCount = 0; - Array.Clear(this.callbacks, 0, this.callbacks.Length); - } + this.callback = null; + this.callbackState = null; } } } \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs index f60fe52a5..bb3e0022b 100644 --- a/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs @@ -9,7 +9,6 @@ namespace DotNetty.Common.Concurrency public abstract class AbstractRecyclablePromise : AbstractPromise { - protected IEventExecutor executor; protected bool recycled; protected readonly ThreadLocalPool.Handle handle; @@ -63,7 +62,7 @@ protected virtual void Recycle() { this.executor = null; this.exception = null; - this.ClearCallbacks(); + this.ClearCallback(); this.recycled = true; this.handle.Release(this); From 4f8f30e3b1c573f4626fb404667093e11e99ca71 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Sat, 10 Mar 2018 11:35:46 -0800 Subject: [PATCH 05/29] uv changes --- src/DotNetty.Transport.Libuv/Native/WriteRequest.cs | 3 ++- test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs b/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs index f0da3c1fc..461389d94 100644 --- a/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs +++ b/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs @@ -63,7 +63,7 @@ internal void DoWrite(NativeChannel.INativeUnsafe channelUnsafe, ChannelOutbound Debug.Assert(this.nativeUnsafe == null); this.nativeUnsafe = channelUnsafe; - input.ForEachFlushedMessage(this); + this.input.ForEachFlushedMessage(this); this.DoWrite(); } @@ -173,6 +173,7 @@ void Release() this.nativeUnsafe = null; this.count = 0; this.size = 0; + this.input = null; this.recyclerHandle.Release(this); } diff --git a/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs b/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs index 8738591d9..d5650f0ae 100644 --- a/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs @@ -31,7 +31,7 @@ public BufReleaseTests() } [Fact] - public async Task BufRelease() + public void BufRelease() { ServerBootstrap sb = new ServerBootstrap() .Group(this.group) From c70594a31a9a5b64b60c1af818d67f90ceb4aa86 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Sat, 10 Mar 2018 15:34:26 -0800 Subject: [PATCH 06/29] continuations and recycle are inlined --- .../Concurrency/AbstractPromise.cs | 6 +-- .../Concurrency/AbstractRecyclablePromise.cs | 14 ++--- .../Channels/AbstractChannel.cs | 2 + .../Channels/AbstractChannelHandlerContext.cs | 52 ++++++++++++------- .../Channels/BatchingPendingWriteQueue.cs | 2 +- .../Channels/ChannelOutboundBuffer.cs | 2 +- .../Channels/DefaultChannelPipeline.cs | 2 + src/DotNetty.Transport/Channels/IChannel.cs | 2 + .../Channels/IChannelHandlerContext.cs | 2 + .../Channels/IChannelPipeline.cs | 2 + .../Channels/PendingWriteQueue.cs | 2 +- 11 files changed, 50 insertions(+), 38 deletions(-) diff --git a/src/DotNetty.Common/Concurrency/AbstractPromise.cs b/src/DotNetty.Common/Concurrency/AbstractPromise.cs index 085a2d4b1..3c0833b78 100644 --- a/src/DotNetty.Common/Concurrency/AbstractPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractPromise.cs @@ -17,8 +17,7 @@ public abstract class AbstractPromise : IPromise, IValueTaskSource static readonly Exception CanceledException = new OperationCanceledException(); static readonly Exception CompletedNoException = new Exception(); - - protected IEventExecutor executor; + protected Exception exception; Action callback; @@ -131,8 +130,7 @@ bool TryExecuteCallback() try { - Contract.Requires(this.executor != null); - this.executor.Execute(this.callback, this.callbackState); + this.callback(this.callbackState); return true; } finally diff --git a/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs index bb3e0022b..ca322f632 100644 --- a/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs @@ -40,31 +40,28 @@ protected override bool TryComplete0(Exception exception) } catch { - this.executor.Execute(this.Recycle); + this.Recycle(); throw; } if (completed) { - this.executor.Execute(this.Recycle); + this.Recycle(); } return completed; } - protected void Init(IEventExecutor executor) + protected void Init() { - this.executor = executor; this.recycled = false; } protected virtual void Recycle() { - this.executor = null; this.exception = null; this.ClearCallback(); this.recycled = true; - this.handle.Release(this); } @@ -81,11 +78,6 @@ void ThrowIfRecycled() { throw new InvalidOperationException("Attempt to use recycled channel promise"); } - - if (this.executor == null) - { - throw new InvalidOperationException("Attempt to use recyclable channel promise without executor"); - } } } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/AbstractChannel.cs b/src/DotNetty.Transport/Channels/AbstractChannel.cs index 4145f0c30..3ec1cd41b 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannel.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannel.cs @@ -192,6 +192,8 @@ public IChannel Read() public ValueTask WriteAsync(object msg) => this.pipeline.WriteAsync(msg); public ValueTask WriteAndFlushAsync(object message) => this.pipeline.WriteAndFlushAsync(message); + + public ValueTask WriteAndFlushAsync(object message, bool notifyComplete) => this.pipeline.WriteAndFlushAsync(message, notifyComplete); public Task CloseCompletion => this.closeFuture.Task; diff --git a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs index 3af775513..b80a598d6 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs @@ -797,7 +797,7 @@ public ValueTask WriteAsync(object msg) { Contract.Requires(msg != null); // todo: check for cancellation - return this.WriteAsync(msg, false); + return this.WriteAsync(msg, FlushMode.NoFlush); } ValueTask InvokeWriteAsync(object msg) => this.Added ? this.InvokeWriteAsync0(msg) : this.WriteAsync(msg); @@ -853,41 +853,45 @@ void InvokeFlush0() } } - public ValueTask WriteAndFlushAsync(object message) + public ValueTask WriteAndFlushAsync(object message) => this.WriteAndFlushAsync(message, true); + + public ValueTask WriteAndFlushAsync(object message, bool notifyComplete) { Contract.Requires(message != null); // todo: check for cancellation - return this.WriteAsync(message, true); + return this.WriteAsync(message, notifyComplete ? FlushMode.Flush : FlushMode.VoidFlush); } - ValueTask InvokeWriteAndFlushAsync(object msg) + ValueTask InvokeWriteAndFlushAsync(object msg, bool notifyComplete) { if (this.Added) { ValueTask task = this.InvokeWriteAsync0(msg); + //flush can synchronously complete write, hence Task allocation required to capture result + task = notifyComplete ? task.Preserve() : default(ValueTask); this.InvokeFlush0(); return task; } return this.WriteAndFlushAsync(msg); } - ValueTask WriteAsync(object msg, bool flush) + ValueTask WriteAsync(object msg, FlushMode mode) { AbstractChannelHandlerContext next = this.FindContextOutbound(); object m = this.pipeline.Touch(msg, next); IEventExecutor nextExecutor = next.Executor; if (nextExecutor.InEventLoop) { - return flush - ? next.InvokeWriteAndFlushAsync(m) - : next.InvokeWriteAsync(m); + return mode == FlushMode.NoFlush + ? next.InvokeWriteAsync(m) + : next.InvokeWriteAndFlushAsync(m, mode == FlushMode.Flush); } else { - AbstractWriteTask task = flush - ? WriteAndFlushTask.NewInstance(next, m) - : (AbstractWriteTask)WriteTask.NewInstance(next, m); + AbstractWriteTask task = mode == FlushMode.NoFlush + ? WriteTask.NewInstance(next, m) + : (AbstractWriteTask)WriteAndFlushTask.NewInstance(next, m, mode == FlushMode.Flush); SafeExecuteOutbound(nextExecutor, task, msg); return task; } @@ -974,6 +978,13 @@ static void SafeExecuteOutbound(IEventExecutor executor, AbstractWriteTask task, public override string ToString() => $"{typeof(IChannelHandlerContext).Name} ({this.Name}, {this.Channel})"; + enum FlushMode : byte + { + NoFlush = 0, + VoidFlush = 1, + Flush = 2 + } + abstract class AbstractWriteTask : AbstractRecyclablePromise, IRunnable { static readonly bool EstimateTaskSizeOnSubmit = @@ -989,7 +1000,7 @@ abstract class AbstractWriteTask : AbstractRecyclablePromise, IRunnable protected static void Init(AbstractWriteTask task, AbstractChannelHandlerContext ctx, object msg) { - task.Init(ctx.Executor); + task.Init(); task.ctx = ctx; task.msg = msg; @@ -1047,7 +1058,7 @@ public void Run() } } - protected virtual ValueTask WriteAsync(AbstractChannelHandlerContext ctx, object msg) => ctx.InvokeWriteAsync(msg); + protected abstract ValueTask WriteAsync(AbstractChannelHandlerContext ctx, object msg); /*public override void Recycle() { @@ -1072,16 +1083,21 @@ public static WriteTask NewInstance(AbstractChannelHandlerContext ctx, object ms : base(handle) { } + + protected override ValueTask WriteAsync(AbstractChannelHandlerContext ctx, object msg) => ctx.InvokeWriteAsync(msg); } sealed class WriteAndFlushTask : AbstractWriteTask { + bool notifyComplete; + static readonly ThreadLocalPool Recycler = new ThreadLocalPool(handle => new WriteAndFlushTask(handle)); - public static WriteAndFlushTask NewInstance(AbstractChannelHandlerContext ctx, object msg) + public static WriteAndFlushTask NewInstance(AbstractChannelHandlerContext ctx, object msg, bool notifyComplete) { WriteAndFlushTask task = Recycler.Take(); Init(task, ctx, msg); + task.notifyComplete = notifyComplete; return task; } @@ -1089,13 +1105,9 @@ public static WriteAndFlushTask NewInstance(AbstractChannelHandlerContext ctx, o : base(handle) { } + + protected override ValueTask WriteAsync(AbstractChannelHandlerContext ctx, object msg) => ctx.InvokeWriteAndFlushAsync(msg, this.notifyComplete); - protected override ValueTask WriteAsync(AbstractChannelHandlerContext ctx, object msg) - { - ValueTask result = base.WriteAsync(ctx, msg); - ctx.InvokeFlush(); - return result; - } } } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs index 47ae0166a..a709f74f5 100644 --- a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs @@ -334,7 +334,7 @@ sealed class PendingWrite : AbstractRecyclablePromise public static PendingWrite NewInstance(IEventExecutor executor, object msg, int size) { PendingWrite write = Pool.Take(); - write.Init(executor); + write.Init(); write.Add(msg, size); return write; } diff --git a/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs b/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs index ebdd7842f..2f8659320 100644 --- a/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs +++ b/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs @@ -808,7 +808,7 @@ sealed class Entry : AbstractRecyclablePromise public static Entry NewInstance(IEventExecutor executor, object msg, int size) { Entry entry = Pool.Take(); - entry.Init(executor); + entry.Init(); entry.Message = msg; entry.PendingSize = size; return entry; diff --git a/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs b/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs index 7119aebab..71dbfe5df 100644 --- a/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs +++ b/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs @@ -831,6 +831,8 @@ public IChannelPipeline Flush() } public ValueTask WriteAndFlushAsync(object msg) => this.tail.WriteAndFlushAsync(msg); + + public ValueTask WriteAndFlushAsync(object msg, bool notifyComplete) => this.tail.WriteAndFlushAsync(msg, notifyComplete); string FilterName(string name, IChannelHandler handler) { diff --git a/src/DotNetty.Transport/Channels/IChannel.cs b/src/DotNetty.Transport/Channels/IChannel.cs index ef93a1dc9..5e910b1c0 100644 --- a/src/DotNetty.Transport/Channels/IChannel.cs +++ b/src/DotNetty.Transport/Channels/IChannel.cs @@ -67,5 +67,7 @@ public interface IChannel : IAttributeMap, IComparable IChannel Flush(); ValueTask WriteAndFlushAsync(object message); + + ValueTask WriteAndFlushAsync(object message, bool notifyComplete); } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs index bb01c6a17..b96a197a3 100644 --- a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs @@ -72,6 +72,8 @@ public interface IChannelHandlerContext : IAttributeMap IChannelHandlerContext Flush(); ValueTask WriteAndFlushAsync(object message); + + ValueTask WriteAndFlushAsync(object message, bool notifyComplete); /// /// Request to bind to the given . diff --git a/src/DotNetty.Transport/Channels/IChannelPipeline.cs b/src/DotNetty.Transport/Channels/IChannelPipeline.cs index 1508e2465..db8939784 100644 --- a/src/DotNetty.Transport/Channels/IChannelPipeline.cs +++ b/src/DotNetty.Transport/Channels/IChannelPipeline.cs @@ -695,5 +695,7 @@ public interface IChannelPipeline : IEnumerable /// Shortcut for calling both and . /// ValueTask WriteAndFlushAsync(object msg); + + ValueTask WriteAndFlushAsync(object msg, bool notifyComplete); } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs index 1d8549092..7684a825f 100644 --- a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs @@ -294,7 +294,7 @@ sealed class PendingWrite : AbstractRecyclablePromise public static PendingWrite NewInstance(IEventExecutor executor, object msg, int size) { PendingWrite write = Pool.Take(); - write.Init(executor); + write.Init(); write.Size = size; write.Msg = msg; return write; From eef526d10a14d46587ed9c25b09bdca84517f56e Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Sat, 10 Mar 2018 21:51:58 -0800 Subject: [PATCH 07/29] test fixes --- .../Channels/AbstractChannelHandlerContext.cs | 24 +++++++++++++++++-- test/DotNetty.Tests.End2End/End2EndTests.cs | 2 +- .../WriteBeforeRegisteredTests.cs | 2 +- .../Transport/AbstractPingPongPerfSpecs.cs | 6 ++--- .../SocketDatagramChannelMulticastTest.cs | 12 ++++------ .../SocketDatagramChannelUnicastTest.cs | 12 +++++----- 6 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs index b80a598d6..515701964 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs @@ -4,11 +4,14 @@ namespace DotNetty.Transport.Channels { using System; + using System.Collections; + using System.Diagnostics; using System.Diagnostics.Contracts; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; + using System.Threading.Tasks.Sources; using DotNetty.Buffers; using DotNetty.Common; using DotNetty.Common.Concurrency; @@ -873,7 +876,7 @@ ValueTask InvokeWriteAndFlushAsync(object msg, bool notifyComplete) this.InvokeFlush0(); return task; } - return this.WriteAndFlushAsync(msg); + return this.WriteAndFlushAsync(msg, notifyComplete); } ValueTask WriteAsync(object msg, FlushMode mode) @@ -892,8 +895,24 @@ ValueTask WriteAsync(object msg, FlushMode mode) AbstractWriteTask task = mode == FlushMode.NoFlush ? WriteTask.NewInstance(next, m) : (AbstractWriteTask)WriteAndFlushTask.NewInstance(next, m, mode == FlushMode.Flush); + + ValueTask result; + switch (mode) + { + case FlushMode.NoFlush: + result = task; + break; + case FlushMode.Flush: + result = ((ValueTask)task).Preserve(); + break; + case FlushMode.VoidFlush: + default: + result = default(ValueTask); + break; + } + SafeExecuteOutbound(nextExecutor, task, msg); - return task; + return result; } } @@ -1106,6 +1125,7 @@ public static WriteAndFlushTask NewInstance(AbstractChannelHandlerContext ctx, o { } + //notifyComplete is always true since continuation triggers WriteAndFlushTask completion protected override ValueTask WriteAsync(AbstractChannelHandlerContext ctx, object msg) => ctx.InvokeWriteAndFlushAsync(msg, this.notifyComplete); } diff --git a/test/DotNetty.Tests.End2End/End2EndTests.cs b/test/DotNetty.Tests.End2End/End2EndTests.cs index 37221edfc..18bbc450a 100644 --- a/test/DotNetty.Tests.End2End/End2EndTests.cs +++ b/test/DotNetty.Tests.End2End/End2EndTests.cs @@ -90,7 +90,7 @@ public async Task EchoServerAndClient() string[] messages = { "message 1", string.Join(",", Enumerable.Range(1, 300)) }; foreach (string message in messages) { - await Task.Run(async () => await clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(message)))).WithTimeout(DefaultTimeout); + await clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(message))).AsTask().WithTimeout(DefaultTimeout); var responseMessage = Assert.IsAssignableFrom(await readListener.ReceiveAsync()); Assert.Equal(message, responseMessage.ToString(Encoding.UTF8)); diff --git a/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs b/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs index 8404c87aa..539034b3b 100644 --- a/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs @@ -24,7 +24,7 @@ public WriteBeforeRegisteredTests() } [Fact] - public async Task WriteBeforeConnect() + public void WriteBeforeConnect() { Bootstrap cb = new Bootstrap() .Group(this.group) diff --git a/test/DotNetty.Transport.Tests.Performance/Transport/AbstractPingPongPerfSpecs.cs b/test/DotNetty.Transport.Tests.Performance/Transport/AbstractPingPongPerfSpecs.cs index 92f050f48..a8e9b336c 100644 --- a/test/DotNetty.Transport.Tests.Performance/Transport/AbstractPingPongPerfSpecs.cs +++ b/test/DotNetty.Transport.Tests.Performance/Transport/AbstractPingPongPerfSpecs.cs @@ -75,7 +75,7 @@ public void SetUp(BenchmarkContext context) public void RoundTrip(BenchmarkContext context) { this.clientHandler.Start(); - this.client.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.ASCII.GetBytes("PING"))); + this.client.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.ASCII.GetBytes("PING")), false); this.clientHandler.Completion.Wait(TimeSpan.FromSeconds(10)); } @@ -105,7 +105,7 @@ public override void ChannelRead(IChannelHandlerContext context, object message) this.counter.Increment(); if (this.stopwatch.Elapsed < this.duration) { - context.WriteAndFlushAsync(buffer); + context.WriteAndFlushAsync(buffer, false); } else { @@ -129,7 +129,7 @@ public override void ChannelRead(IChannelHandlerContext context, object message) { if (message is IByteBuffer buffer) { - context.WriteAndFlushAsync(buffer); + context.WriteAndFlushAsync(buffer, false); } else { diff --git a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs index 5c2b3fdec..4f72cc801 100644 --- a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs +++ b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs @@ -94,7 +94,7 @@ public static IEnumerable GetData() [Theory] [MemberData(nameof(GetData))] - public async Task Multicast(AddressFamily addressFamily, IByteBufferAllocator allocator) + public void Multicast(AddressFamily addressFamily, IByteBufferAllocator allocator) { SocketDatagramChannel serverChannel = null; IChannel clientChannel = null; @@ -155,7 +155,7 @@ public async Task Multicast(AddressFamily addressFamily, IByteBufferAllocator al Assert.True(joinTask.Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds * 5)), $"Multicast server join group {groupAddress} timed out!"); - await clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress));//.Wait(); + clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)).AsTask().Wait(); Assert.True(multicastHandler.WaitForResult(), "Multicast server should have receivied the message."); Task leaveTask = serverChannel.LeaveGroup(groupAddress, loopback); @@ -166,7 +166,7 @@ public async Task Multicast(AddressFamily addressFamily, IByteBufferAllocator al Task.Delay(DefaultTimeOutInMilliseconds).Wait(); // we should not receive a message anymore as we left the group before - await clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)); //.Wait(); + clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)).AsTask().Wait(); Assert.False(multicastHandler.WaitForResult(), "Multicast server should not receive the message."); } finally @@ -174,11 +174,9 @@ public async Task Multicast(AddressFamily addressFamily, IByteBufferAllocator al serverChannel?.CloseAsync().Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds)); clientChannel?.CloseAsync().Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds)); - await serverGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); - await clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); - /*Task.WaitAll( + Task.WaitAll( serverGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)), - clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));*/ + clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1))); } } } diff --git a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs index b964441ec..4f6ea9710 100644 --- a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs +++ b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs @@ -146,7 +146,7 @@ public static IEnumerable GetData() [Theory] [MemberData(nameof(GetData))] - public async Task SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAllocator allocator, AddressFamily addressFamily, byte[] expectedData, int count) + public void SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAllocator allocator, AddressFamily addressFamily, byte[] expectedData, int count) { SocketDatagramChannel serverChannel = null; IChannel clientChannel = null; @@ -217,12 +217,12 @@ public async Task SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAll for (int i = 0; i < count; i++) { var packet = new DatagramPacket((IByteBuffer)source.Retain(), new IPEndPoint(address, endPoint.Port)); - await clientChannel.WriteAndFlushAsync(packet);//.Wait(); + clientChannel.WriteAndFlushAsync(packet).AsTask().Wait(); Assert.True(handler.WaitForResult()); var duplicatedPacket = (DatagramPacket)packet.Duplicate(); duplicatedPacket.Retain(); - await clientChannel.WriteAndFlushAsync(duplicatedPacket);//.Wait(); + clientChannel.WriteAndFlushAsync(duplicatedPacket).AsTask().Wait(); Assert.True(handler.WaitForResult()); } } @@ -232,9 +232,9 @@ public async Task SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAll clientChannel?.CloseAsync().Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds)); source.Release(); - - await serverGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); - await clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); + Task.WaitAll( + serverGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)), + clientGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1))); } } } From 8abd8784bb7e0723a29d16a5eb68fd5fb4469f8c Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Sun, 11 Mar 2018 15:38:06 -0700 Subject: [PATCH 08/29] revert libuv changes --- src/DotNetty.Transport.Libuv/Native/WriteRequest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs b/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs index 461389d94..9b4391a8b 100644 --- a/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs +++ b/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs @@ -173,7 +173,6 @@ void Release() this.nativeUnsafe = null; this.count = 0; this.size = 0; - this.input = null; this.recyclerHandle.Release(this); } From cd0b11e7bb9ff049822bb0279fe3591d8cdc7222 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Sun, 11 Mar 2018 15:58:35 -0700 Subject: [PATCH 09/29] cleanup --- src/DotNetty.Codecs.Mqtt/MqttDecoder.cs | 2 +- .../MessageToMessageEncoder.cs | 2 +- .../Concurrency/AbstractPromise.cs | 2 - .../Concurrency/AggregatingPromise.cs | 2 +- src/DotNetty.Common/Concurrency/IPromise.cs | 3 - src/DotNetty.Common/ThreadLocalObjectList.cs | 32 ------- src/DotNetty.Handlers/Tls/TlsHandler.cs | 8 +- .../Channels/AbstractChannelHandlerContext.cs | 1 + .../Channels/BatchingPendingWriteQueue.cs | 12 +-- .../Channels/Embedded/EmbeddedChannel.cs | 2 +- .../Channels/Groups/DefaultChannelGroup.cs | 6 +- .../Groups/DefaultChannelGroupPromise.cs | 84 +++++++++---------- .../Channels/PendingWriteQueue.cs | 9 +- .../Frame/LengthFieldPrependerTest.cs | 3 +- .../ChannelExtensions.cs | 2 +- 15 files changed, 64 insertions(+), 106 deletions(-) diff --git a/src/DotNetty.Codecs.Mqtt/MqttDecoder.cs b/src/DotNetty.Codecs.Mqtt/MqttDecoder.cs index b2418db83..34894e704 100644 --- a/src/DotNetty.Codecs.Mqtt/MqttDecoder.cs +++ b/src/DotNetty.Codecs.Mqtt/MqttDecoder.cs @@ -241,7 +241,7 @@ static void DecodeConnectPacket(IByteBuffer buffer, ConnectPacket packet, ref in { var connAckPacket = new ConnAckPacket(); connAckPacket.ReturnCode = ConnectReturnCode.RefusedUnacceptableProtocolVersion; - context.WriteAndFlushAsync(connAckPacket); + context.WriteAndFlushAsync(connAckPacket, false); throw new DecoderException($"Unexpected protocol level. Expected: {Util.ProtocolLevel}. Actual: {packet.ProtocolLevel}"); } diff --git a/src/DotNetty.Codecs/MessageToMessageEncoder.cs b/src/DotNetty.Codecs/MessageToMessageEncoder.cs index ed0d3c532..812d632a5 100644 --- a/src/DotNetty.Codecs/MessageToMessageEncoder.cs +++ b/src/DotNetty.Codecs/MessageToMessageEncoder.cs @@ -73,7 +73,7 @@ public override ValueTask WriteAsync(IChannelHandlerContext ctx, object msg) for (int i = 0; i < lastItemIndex; i++) { // we don't care about output from these messages as failure while sending one of these messages will fail all messages up to the last message - which will be observed by the caller in Task result. - ctx.WriteAsync(output[i]); // todo: optimize: once IChannelHandlerContext allows, pass "not interested in task" flag + ctx.WriteAsync(output[i]); } result = ctx.WriteAsync(output[lastItemIndex]); } diff --git a/src/DotNetty.Common/Concurrency/AbstractPromise.cs b/src/DotNetty.Common/Concurrency/AbstractPromise.cs index 3c0833b78..aa21edd1f 100644 --- a/src/DotNetty.Common/Concurrency/AbstractPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractPromise.cs @@ -42,8 +42,6 @@ protected virtual bool TryComplete0(Exception exception) return false; } - public IValueTaskSource Future => this; - public bool SetUncancellable() => true; public virtual ValueTaskSourceStatus GetStatus(short token) diff --git a/src/DotNetty.Common/Concurrency/AggregatingPromise.cs b/src/DotNetty.Common/Concurrency/AggregatingPromise.cs index d28387664..5c140c435 100644 --- a/src/DotNetty.Common/Concurrency/AggregatingPromise.cs +++ b/src/DotNetty.Common/Concurrency/AggregatingPromise.cs @@ -35,7 +35,7 @@ public AggregatingPromise(IList futures) void OnFutureCompleted(object obj) { - IValueTaskSource future = obj as IValueTaskSource; + var future = obj as IValueTaskSource; Contract.Assert(future != null); try diff --git a/src/DotNetty.Common/Concurrency/IPromise.cs b/src/DotNetty.Common/Concurrency/IPromise.cs index 6e8bf077f..0c8f836ae 100644 --- a/src/DotNetty.Common/Concurrency/IPromise.cs +++ b/src/DotNetty.Common/Concurrency/IPromise.cs @@ -4,7 +4,6 @@ namespace DotNetty.Common.Concurrency { using System; - using System.Threading.Tasks.Sources; public interface IPromise { @@ -14,8 +13,6 @@ public interface IPromise bool TrySetCanceled(); - IValueTaskSource Future { get; } - bool SetUncancellable(); } } \ No newline at end of file diff --git a/src/DotNetty.Common/ThreadLocalObjectList.cs b/src/DotNetty.Common/ThreadLocalObjectList.cs index 6271e5469..71414ffa5 100644 --- a/src/DotNetty.Common/ThreadLocalObjectList.cs +++ b/src/DotNetty.Common/ThreadLocalObjectList.cs @@ -36,36 +36,4 @@ public void Return() this.returnHandle.Release(this); } } - - public class ThreadLocalObjectList : List - { - const int DefaultInitialCapacity = 8; - - static readonly ThreadLocalPool> Pool = new ThreadLocalPool>(handle => new ThreadLocalObjectList(handle)); - - readonly ThreadLocalPool.Handle returnHandle; - - protected ThreadLocalObjectList(ThreadLocalPool.Handle returnHandle) - { - this.returnHandle = returnHandle; - } - - public static ThreadLocalObjectList NewInstance() => NewInstance(DefaultInitialCapacity); - - public static ThreadLocalObjectList NewInstance(int minCapacity) - { - ThreadLocalObjectList ret = Pool.Take(); - if (ret.Capacity < minCapacity) - { - ret.Capacity = minCapacity; - } - return ret; - } - - public void Return() - { - this.Clear(); - this.returnHandle.Release(this); - } - } } \ No newline at end of file diff --git a/src/DotNetty.Handlers/Tls/TlsHandler.cs b/src/DotNetty.Handlers/Tls/TlsHandler.cs index 496e67aa3..fe90fa02b 100644 --- a/src/DotNetty.Handlers/Tls/TlsHandler.cs +++ b/src/DotNetty.Handlers/Tls/TlsHandler.cs @@ -609,11 +609,11 @@ void FinishWrap(byte[] buffer, int offset, int count) this.lastContextWriteTask = this.capturedContext.WriteAsync(output).AsTask(); } - ValueTask FinishWrapNonAppDataAsync(byte[] buffer, int offset, int count) + Task FinishWrapNonAppDataAsync(byte[] buffer, int offset, int count) { - ValueTask future = this.capturedContext.WriteAndFlushAsync(Unpooled.WrappedBuffer(buffer, offset, count)); + Task task = this.capturedContext.WriteAndFlushAsync(Unpooled.WrappedBuffer(buffer, offset, count), true).AsTask(); this.ReadIfNeeded(this.capturedContext); - return future; + return task; } public override Task CloseAsync(IChannelHandlerContext context) @@ -815,7 +815,7 @@ IAsyncResult PrepareSyncReadResult(int readBytes, object state) public override void Write(byte[] buffer, int offset, int count) => this.owner.FinishWrap(buffer, offset, count); public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => this.owner.FinishWrapNonAppDataAsync(buffer, offset, count).AsTask(); + => this.owner.FinishWrapNonAppDataAsync(buffer, offset, count); #if !NETSTANDARD1_3 diff --git a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs index 515701964..a103e16ac 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs @@ -903,6 +903,7 @@ ValueTask WriteAsync(object msg, FlushMode mode) result = task; break; case FlushMode.Flush: + //flush can synchronously complete write, hence Task allocation required to capture result result = ((ValueTask)task).Preserve(); break; case FlushMode.VoidFlush: diff --git a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs index a709f74f5..bd7f3aaac 100644 --- a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs @@ -87,7 +87,7 @@ public ValueTask Add(object msg) } } - PendingWrite write = PendingWrite.NewInstance(this.ctx.Executor, msg, messageSize); + PendingWrite write = PendingWrite.NewInstance(msg, messageSize); if (currentTail == null) { this.tail = this.head = write; @@ -124,9 +124,8 @@ public void RemoveAndFailAll(Exception cause) { PendingWrite next = write.Next; ReleaseMessages(write.Messages); - Util.SafeSetFailure(write, cause, Logger); this.Recycle(write, false); - + Util.SafeSetFailure(write, cause, Logger); write = next; } this.AssertEmpty(); @@ -194,7 +193,7 @@ public ValueTask RemoveAndWriteAllAsync() write = next; } this.AssertEmpty(); - return new ValueTask(new AggregatingPromise(tasks), 0); + return new AggregatingPromise(tasks); } void AssertEmpty() => Contract.Assert(this.tail == null && this.head == null && this.size == 0); @@ -301,8 +300,6 @@ void Recycle(PendingWrite write, bool update) } } - //write.Recycle(); - // We need to guard against null as channel.unsafe().outboundBuffer() may returned null // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 @@ -331,7 +328,7 @@ sealed class PendingWrite : AbstractRecyclablePromise this.Messages = new List(); } - public static PendingWrite NewInstance(IEventExecutor executor, object msg, int size) + public static PendingWrite NewInstance(object msg, int size) { PendingWrite write = Pool.Take(); write.Init(); @@ -351,7 +348,6 @@ protected override void Recycle() this.Next = null; this.Messages.Clear(); base.Recycle(); - //this.handle.Release(this); } } } diff --git a/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs b/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs index 331e86b86..29004d182 100644 --- a/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs +++ b/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs @@ -295,7 +295,7 @@ public bool WriteOutbound(params object[] msgs) return IsNotEmpty(this.outboundMessages); } - ThreadLocalObjectList futures = ThreadLocalObjectList.NewInstance(msgs.Length); + ThreadLocalObjectList futures = ThreadLocalObjectList.NewInstance(msgs.Length); foreach (object m in msgs) { diff --git a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs index 93c8debd7..bd5bd49a3 100644 --- a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs +++ b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroup.cs @@ -70,7 +70,7 @@ public ValueTask WriteAsync(object message, IChannelMatcher matcher) } ReferenceCountUtil.Release(message); - return new DefaultChannelGroupPromise(futures /*, this.executor*/); + return new DefaultChannelGroupPromise(futures); } public IChannelGroup Flush(IChannelMatcher matcher) @@ -155,12 +155,12 @@ public ValueTask WriteAndFlushAsync(object message, IChannelMatcher matcher) { if (matcher.Matches(c)) { - futures.Add(c, c.WriteAndFlushAsync(SafeDuplicate(message))); + futures.Add(c, c.WriteAndFlushAsync(SafeDuplicate(message), true)); } } ReferenceCountUtil.Release(message); - return new DefaultChannelGroupPromise(futures /*, this.executor*/); + return new DefaultChannelGroupPromise(futures); } public Task DisconnectAsync() => this.DisconnectAsync(ChannelMatchers.All()); diff --git a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs index 58ea6396e..5e5ab81c0 100644 --- a/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs +++ b/src/DotNetty.Transport/Channels/Groups/DefaultChannelGroupPromise.cs @@ -12,61 +12,61 @@ namespace DotNetty.Transport.Channels.Groups public sealed class DefaultChannelGroupPromise : AbstractPromise { - readonly Dictionary futures; + readonly int count; int failureCount; int successCount; IList> failures; - public DefaultChannelGroupPromise( - Dictionary futures - /*, IEventExecutor executor*/) + public DefaultChannelGroupPromise(Dictionary futures) { Contract.Requires(futures != null); - - this.futures = new Dictionary(); - foreach (KeyValuePair pair in futures) + + if (futures.Count == 0) + { + this.TryComplete(); + } + else { - this.futures.Add(pair.Key, pair.Value); - ValueTaskAwaiter awaiter = pair.Value.GetAwaiter(); - awaiter.OnCompleted(() => + this.count = futures.Count; + foreach (KeyValuePair pair in futures) { - try - { - awaiter.GetResult(); - this.successCount++; - } - catch(Exception ex) - { - this.failureCount++; - if (this.failures == null) - { - this.failures = new List>(); - } - this.failures.Add(new KeyValuePair(pair.Key, ex)); - } - - bool callSetDone = this.successCount + this.failureCount == futures.Count; - Contract.Assert(this.successCount + this.failureCount <= futures.Count); + this.Await(pair); + } + } + } - if (callSetDone) - { - if (this.failureCount > 0) - { - this.TrySetException(new ChannelGroupException(this.failures)); - } - else - { - this.TryComplete(); - } - } - }); + async void Await(KeyValuePair pair) + { + try + { + await pair.Value; + this.successCount++; + } + catch(Exception ex) + { + this.failureCount++; + if (this.failures == null) + { + this.failures = new List>(); + } + this.failures.Add(new KeyValuePair(pair.Key, ex)); } - // Done on arrival? - if (futures.Count == 0) + bool callSetDone = this.successCount + this.failureCount == this.count; + Contract.Assert(this.successCount + this.failureCount <= this.count); + + if (callSetDone) { - this.TryComplete(); + if (this.failureCount > 0) + { + this.TrySetException(new ChannelGroupException(this.failures)); + } + else + { + this.TryComplete(); + } } } + } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs index 7684a825f..a62f6ea8d 100644 --- a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs @@ -83,7 +83,7 @@ public ValueTask Add(object msg) messageSize = 0; } //var promise = new TaskCompletionSource(); - PendingWrite write = PendingWrite.NewInstance(this.ctx.Executor, msg, messageSize); + PendingWrite write = PendingWrite.NewInstance(msg, messageSize); PendingWrite currentTail = this.tail; if (currentTail == null) { @@ -187,7 +187,7 @@ public ValueTask RemoveAndWriteAllAsync() write = next; } this.AssertEmpty(); - return new ValueTask(new AggregatingPromise(tasks), 0); + return new AggregatingPromise(tasks); } void AssertEmpty() => Contract.Assert(this.tail == null && this.head == null && this.size == 0); @@ -266,8 +266,6 @@ void Recycle(PendingWrite write, bool update) Contract.Assert(this.size > 0); } } - - //write.Recycle(); // We need to guard against null as channel.unsafe().outboundBuffer() may returned null // if the channel was already closed when constructing the PendingWriteQueue. @@ -291,7 +289,7 @@ sealed class PendingWrite : AbstractRecyclablePromise { } - public static PendingWrite NewInstance(IEventExecutor executor, object msg, int size) + public static PendingWrite NewInstance(object msg, int size) { PendingWrite write = Pool.Take(); write.Init(); @@ -306,7 +304,6 @@ protected override void Recycle() this.Next = null; this.Msg = null; base.Recycle(); - //this.handle.Release(this); } } } diff --git a/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs b/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs index 9aa2a2d17..6ece234cc 100644 --- a/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs +++ b/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs @@ -70,12 +70,13 @@ public void TestPrependAdjustedLength() public void TestPrependAdjustedLengthLessThanZero() { var ch = new EmbeddedChannel(new LengthFieldPrepender(4, -2)); - Assert.Throws(() => + var ex = Assert.Throws(() => { ch.WriteOutbound(this.msg); Assert.True(false, typeof(EncoderException).Name + " must be raised."); }); + Assert.IsType(ex.InnerExceptions.Single()); } [Fact] diff --git a/test/DotNetty.Tests.Common/ChannelExtensions.cs b/test/DotNetty.Tests.Common/ChannelExtensions.cs index 0055a735c..fd19d3f27 100644 --- a/test/DotNetty.Tests.Common/ChannelExtensions.cs +++ b/test/DotNetty.Tests.Common/ChannelExtensions.cs @@ -14,7 +14,7 @@ public static Task WriteAndFlushManyAsync(this IChannel channel, params object[] var list = new List(); foreach (object m in messages) { - list.Add(Task.Run(async () => await channel.WriteAsync(m))); + list.Add(channel.WriteAsync(m).AsTask()); } IEnumerable tasks = list.ToArray(); channel.Flush(); From 1cef415bdcea1cbe25ce15e264c02e35582a9ef6 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Mon, 12 Mar 2018 12:12:22 -0700 Subject: [PATCH 10/29] update packages --- src/DotNetty.Codecs/DotNetty.Codecs.csproj | 2 +- src/DotNetty.Handlers/DotNetty.Handlers.csproj | 3 ++- test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj | 2 -- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/DotNetty.Codecs/DotNetty.Codecs.csproj b/src/DotNetty.Codecs/DotNetty.Codecs.csproj index 7608729d5..491c779af 100644 --- a/src/DotNetty.Codecs/DotNetty.Codecs.csproj +++ b/src/DotNetty.Codecs/DotNetty.Codecs.csproj @@ -46,6 +46,6 @@ - + \ No newline at end of file diff --git a/src/DotNetty.Handlers/DotNetty.Handlers.csproj b/src/DotNetty.Handlers/DotNetty.Handlers.csproj index 32075b018..d4a715db4 100644 --- a/src/DotNetty.Handlers/DotNetty.Handlers.csproj +++ b/src/DotNetty.Handlers/DotNetty.Handlers.csproj @@ -44,6 +44,7 @@ - + + \ No newline at end of file diff --git a/test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj b/test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj index 42ba6d491..3570fb1f7 100644 --- a/test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj +++ b/test/DotNetty.Common.Tests/DotNetty.Common.Tests.csproj @@ -12,8 +12,6 @@ - - From 5bed79f0446c3f6788fbe2f5359dfb81db9a8f37 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Mon, 12 Mar 2018 12:12:46 -0700 Subject: [PATCH 11/29] multi continuation support returned back --- .../Concurrency/AbstractPromise.cs | 81 ++++++++++++------- .../Concurrency/AbstractRecyclablePromise.cs | 2 +- .../Channels/BatchingPendingWriteQueue.cs | 1 - 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/DotNetty.Common/Concurrency/AbstractPromise.cs b/src/DotNetty.Common/Concurrency/AbstractPromise.cs index aa21edd1f..e1d38745b 100644 --- a/src/DotNetty.Common/Concurrency/AbstractPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractPromise.cs @@ -5,7 +5,6 @@ namespace DotNetty.Common.Concurrency { using System; using System.Collections.Generic; - using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Threading.Tasks; @@ -19,9 +18,9 @@ public abstract class AbstractPromise : IPromise, IValueTaskSource static readonly Exception CompletedNoException = new Exception(); protected Exception exception; - - Action callback; - object callbackState; + + int callbackCount; + (Action, object)[] callbacks; public bool TryComplete() => this.TryComplete0(CompletedNoException); @@ -35,7 +34,7 @@ protected virtual bool TryComplete0(Exception exception) { // Set the exception object to the exception passed in or a sentinel value this.exception = exception; - this.TryExecuteCallback(); + this.TryExecuteCallbacks(); return true; } @@ -69,32 +68,34 @@ public virtual void GetResult(short token) if (this.exception == null) { throw new InvalidOperationException("Attempt to get result on not yet completed promise"); - //ThrowHelper.ThrowInvalidOperationException_GetResultNotCompleted(); } this.IsCompletedOrThrow(); - /* - // Change the state from to be canceled -> observed - if (_writerAwaitable.ObserveCancelation()) - { - result._resultFlags |= ResultFlags.Canceled; - } - if (_readerCompletion.IsCompletedOrThrow()) - { - result._resultFlags |= ResultFlags.Completed; - } - */ } public virtual void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { - this.callback = continuation; - this.callbackState = state; //todo: context preservation + if (this.callbacks == null) + { + this.callbacks = new (Action, object)[1]; + } + + int newIndex = this.callbackCount; + this.callbackCount++; + + if (newIndex == this.callbacks.Length) + { + var newArray = new (Action, object)[this.callbacks.Length * 2]; + Array.Copy(this.callbacks, newArray, this.callbacks.Length); + this.callbacks = newArray; + } + + this.callbacks[newIndex] = (continuation, state); if (this.exception != null) { - this.TryExecuteCallback(); + this.TryExecuteCallbacks(); } } @@ -119,29 +120,49 @@ bool IsCompletedOrThrow() [MethodImpl(MethodImplOptions.NoInlining)] void ThrowLatchedException() => ExceptionDispatchInfo.Capture(this.exception).Throw(); - bool TryExecuteCallback() + bool TryExecuteCallbacks() { - if (this.callback == null) + if (this.callbackCount == 0 || this.callbacks == null) { return false; } - try + List exceptions = null; + + for (int i = 0; i < this.callbackCount; i++) { - this.callback(this.callbackState); - return true; + try + { + (Action callback, object state) = this.callbacks[i]; + callback(state); + } + catch (Exception ex) + { + if (exceptions == null) + { + exceptions = new List(); + } + + exceptions.Add(ex); + } } - finally + + if (exceptions == null) { - this.ClearCallback(); + return true; } + + throw new AggregateException(exceptions); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ClearCallback() + protected void ClearCallbacks() { - this.callback = null; - this.callbackState = null; + if (this.callbackCount > 0) + { + this.callbackCount = 0; + Array.Clear(this.callbacks, 0, this.callbacks.Length); + } } } } \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs index ca322f632..756aa4c29 100644 --- a/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs @@ -60,7 +60,7 @@ protected void Init() protected virtual void Recycle() { this.exception = null; - this.ClearCallback(); + this.ClearCallbacks(); this.recycled = true; this.handle.Release(this); } diff --git a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs index bd7f3aaac..7387202d4 100644 --- a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs @@ -234,7 +234,6 @@ public IPromise Remove() { return null; } - //TaskCompletionSource promise = write.Promise; ReferenceCountUtil.SafeRelease(write.Messages); this.Recycle(write, true); return write; From d0836b033c8e89f600e9527e77445491c0ec4863 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Mon, 12 Mar 2018 12:40:35 -0700 Subject: [PATCH 12/29] using nuget.config on pkg restore --- build.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cake b/build.cake index f8528a2fa..4314f00cd 100644 --- a/build.cake +++ b/build.cake @@ -50,7 +50,7 @@ Task("Restore-NuGet-Packages") .Description("Restores dependencies") .Does(() => { - DotNetCoreRestore(); + DotNetCoreRestore(new DotNetCoreRestoreSettings { ConfigFile = ".nuget\\nuget.config" }); int result = StartProcess("dotnet", new ProcessSettings { Arguments = "restore -r win-x64" } ); if (result != 0) From ed79cce7fac906c9bf008030c413d1f60e4034a0 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Mon, 12 Mar 2018 12:46:28 -0700 Subject: [PATCH 13/29] add nuget.org source --- .nuget/NuGet.Config | 1 + 1 file changed, 1 insertion(+) diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config index 36c859a9d..e7814482c 100644 --- a/.nuget/NuGet.Config +++ b/.nuget/NuGet.Config @@ -4,6 +4,7 @@ + \ No newline at end of file From 208e69cc0de01a05540d24461f99519e218c9382 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Tue, 13 Mar 2018 11:33:55 -0700 Subject: [PATCH 14/29] rename --- examples/Telnet.Server/TelnetServerHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Telnet.Server/TelnetServerHandler.cs b/examples/Telnet.Server/TelnetServerHandler.cs index ea4c3a935..8e7020b4c 100644 --- a/examples/Telnet.Server/TelnetServerHandler.cs +++ b/examples/Telnet.Server/TelnetServerHandler.cs @@ -36,10 +36,10 @@ protected override async void ChannelRead0(IChannelHandlerContext contex, string response = "Did you say '" + msg + "'?\r\n"; } - ValueTask wait_close = contex.WriteAndFlushAsync(response); + ValueTask waitClose = contex.WriteAndFlushAsync(response); if (close) { - await wait_close; + await waitClose; contex.CloseAsync(); } } From c9d0c8a0ca7255ca82838e43d08598820a2e022b Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Wed, 14 Mar 2018 16:15:18 -0700 Subject: [PATCH 15/29] execution and sync context preservation --- .../Concurrency/AbstractPromise.cs | 101 +++++++++++++++--- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/src/DotNetty.Common/Concurrency/AbstractPromise.cs b/src/DotNetty.Common/Concurrency/AbstractPromise.cs index e1d38745b..1989f1092 100644 --- a/src/DotNetty.Common/Concurrency/AbstractPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractPromise.cs @@ -7,12 +7,33 @@ namespace DotNetty.Common.Concurrency using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; + using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Sources; public abstract class AbstractPromise : IPromise, IValueTaskSource { + struct CompletionData + { + public Action Continuation { get; } + public object State { get; } + public ExecutionContext ExecutionContext { get; } + public SynchronizationContext SynchronizationContext { get; } + + public CompletionData(Action continuation, object state, ExecutionContext executionContext, SynchronizationContext synchronizationContext) + { + this.Continuation = continuation; + this.State = state; + this.ExecutionContext = executionContext; + this.SynchronizationContext = synchronizationContext; + } + } + const short SourceToken = 0; + + static readonly ContextCallback ExecutionContextCallback = Execute; + static readonly SendOrPostCallback SyncContextCallbackWithExecutionContext = ExecuteWithExecutionContext; + static readonly SendOrPostCallback SyncContextCallback = Execute; static readonly Exception CanceledException = new OperationCanceledException(); static readonly Exception CompletedNoException = new Exception(); @@ -20,7 +41,7 @@ public abstract class AbstractPromise : IPromise, IValueTaskSource protected Exception exception; int callbackCount; - (Action, object)[] callbacks; + CompletionData[] completions; public bool TryComplete() => this.TryComplete0(CompletedNoException); @@ -34,7 +55,7 @@ protected virtual bool TryComplete0(Exception exception) { // Set the exception object to the exception passed in or a sentinel value this.exception = exception; - this.TryExecuteCallbacks(); + this.TryExecuteCompletions(); return true; } @@ -75,27 +96,31 @@ public virtual void GetResult(short token) public virtual void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { - //todo: context preservation - if (this.callbacks == null) + if (this.completions == null) { - this.callbacks = new (Action, object)[1]; + this.completions = new CompletionData[1]; } int newIndex = this.callbackCount; this.callbackCount++; - if (newIndex == this.callbacks.Length) + if (newIndex == this.completions.Length) { - var newArray = new (Action, object)[this.callbacks.Length * 2]; - Array.Copy(this.callbacks, newArray, this.callbacks.Length); - this.callbacks = newArray; + var newArray = new CompletionData[this.completions.Length * 2]; + Array.Copy(this.completions, newArray, this.completions.Length); + this.completions = newArray; } - this.callbacks[newIndex] = (continuation, state); + this.completions[newIndex] = new CompletionData( + continuation, + state, + (flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0 ? ExecutionContext.Capture() : null, + (flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0 ? SynchronizationContext.Current : null + ); if (this.exception != null) { - this.TryExecuteCallbacks(); + this.TryExecuteCompletions(); } } @@ -120,9 +145,9 @@ bool IsCompletedOrThrow() [MethodImpl(MethodImplOptions.NoInlining)] void ThrowLatchedException() => ExceptionDispatchInfo.Capture(this.exception).Throw(); - bool TryExecuteCallbacks() + bool TryExecuteCompletions() { - if (this.callbackCount == 0 || this.callbacks == null) + if (this.callbackCount == 0 || this.completions == null) { return false; } @@ -133,8 +158,8 @@ bool TryExecuteCallbacks() { try { - (Action callback, object state) = this.callbacks[i]; - callback(state); + CompletionData completion = this.completions[i]; + ExecuteCompletion(completion); } catch (Exception ex) { @@ -154,15 +179,57 @@ bool TryExecuteCallbacks() throw new AggregateException(exceptions); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void ClearCallbacks() { if (this.callbackCount > 0) { this.callbackCount = 0; - Array.Clear(this.callbacks, 0, this.callbacks.Length); + Array.Clear(this.completions, 0, this.completions.Length); } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ExecuteCompletion(CompletionData completion) + { + if (completion.SynchronizationContext == null) + { + if (completion.ExecutionContext == null) + { + completion.Continuation(completion.State); + } + else + { + //boxing + ExecutionContext.Run(completion.ExecutionContext, ExecutionContextCallback, completion); + } + } + else + { + if (completion.ExecutionContext == null) + { + //boxing + completion.SynchronizationContext.Post(SyncContextCallback, completion); + } + else + { + //boxing + completion.SynchronizationContext.Post(SyncContextCallbackWithExecutionContext, completion); + } + } + } + + static void Execute(object state) + { + CompletionData completion = (CompletionData)state; + completion.Continuation(completion.State); + } + + static void ExecuteWithExecutionContext(object state) + { + CompletionData completion = (CompletionData)state; + ExecutionContext.Run(completion.ExecutionContext, ExecutionContextCallback, state); } } } \ No newline at end of file From 67b306b86ab985e627388dc9b82e32555fb88762 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Thu, 22 Mar 2018 00:22:14 -0700 Subject: [PATCH 16/29] context propagation fixes --- .../Concurrency/AbstractPromise.cs | 219 ++++++++---------- .../Concurrency/AbstractRecyclablePromise.cs | 64 +++-- src/DotNetty.Common/Concurrency/IPromise.cs | 6 +- src/DotNetty.Common/Utilities/TaskEx.cs | 25 +- .../Timeout/IdleStateHandler.cs | 17 +- src/DotNetty.Handlers/Tls/TlsHandler.cs | 2 +- .../Channels/AbstractChannelHandlerContext.cs | 2 +- .../Channels/BatchingPendingWriteQueue.cs | 36 +-- .../Channels/ChannelOutboundBuffer.cs | 2 +- .../Channels/Embedded/EmbeddedChannel.cs | 40 +--- .../Channels/PendingWriteQueue.cs | 8 +- .../Frame/LengthFieldPrependerTest.cs | 4 +- 12 files changed, 197 insertions(+), 228 deletions(-) diff --git a/src/DotNetty.Common/Concurrency/AbstractPromise.cs b/src/DotNetty.Common/Concurrency/AbstractPromise.cs index 1989f1092..1563bb8f4 100644 --- a/src/DotNetty.Common/Concurrency/AbstractPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractPromise.cs @@ -5,57 +5,55 @@ namespace DotNetty.Common.Concurrency { using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; + using System.Runtime.InteropServices.ComTypes; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Sources; public abstract class AbstractPromise : IPromise, IValueTaskSource { - struct CompletionData - { - public Action Continuation { get; } - public object State { get; } - public ExecutionContext ExecutionContext { get; } - public SynchronizationContext SynchronizationContext { get; } - - public CompletionData(Action continuation, object state, ExecutionContext executionContext, SynchronizationContext synchronizationContext) - { - this.Continuation = continuation; - this.State = state; - this.ExecutionContext = executionContext; - this.SynchronizationContext = synchronizationContext; - } - } - - const short SourceToken = 0; - static readonly ContextCallback ExecutionContextCallback = Execute; - static readonly SendOrPostCallback SyncContextCallbackWithExecutionContext = ExecuteWithExecutionContext; static readonly SendOrPostCallback SyncContextCallback = Execute; + static readonly SendOrPostCallback SyncContextCallbackWithExecutionContext = ExecuteWithExecutionContext; + static readonly Action TaskSchedulerCallback = Execute; + static readonly Action TaskScheduleCallbackWithExecutionContext = ExecuteWithExecutionContext; - static readonly Exception CanceledException = new OperationCanceledException(); - static readonly Exception CompletedNoException = new Exception(); + static readonly Exception CompletedSentinel = new Exception(); + short currentId; protected Exception exception; + + Action continuation; + object state; + ExecutionContext executionContext; + object schedulingContext; + + public ValueTask ValueTask => new ValueTask(this, this.currentId); - int callbackCount; - CompletionData[] completions; - - public bool TryComplete() => this.TryComplete0(CompletedNoException); + public bool TryComplete() => this.TryComplete0(CompletedSentinel, out _); - public bool TrySetException(Exception exception) => this.TryComplete0(exception); + public bool TrySetException(Exception exception) => this.TryComplete0(exception, out _); - public bool TrySetCanceled() => this.TryComplete0(CanceledException); + public bool TrySetCanceled(CancellationToken cancellationToken = default(CancellationToken)) => this.TryComplete0(new OperationCanceledException(cancellationToken), out _); - protected virtual bool TryComplete0(Exception exception) + protected virtual bool TryComplete0(Exception exception, out bool continuationInvoked) { + continuationInvoked = false; + if (this.exception == null) { // Set the exception object to the exception passed in or a sentinel value this.exception = exception; - this.TryExecuteCompletions(); + + if (this.continuation != null) + { + this.ExecuteContinuation(); + continuationInvoked = true; + } return true; } @@ -66,15 +64,17 @@ protected virtual bool TryComplete0(Exception exception) public virtual ValueTaskSourceStatus GetStatus(short token) { + this.EnsureValidToken(token); + if (this.exception == null) { return ValueTaskSourceStatus.Pending; } - else if (this.exception == CompletedNoException) + else if (this.exception == CompletedSentinel) { return ValueTaskSourceStatus.Succeeded; } - else if (this.exception == CanceledException) + else if (this.exception is OperationCanceledException) { return ValueTaskSourceStatus.Canceled; } @@ -86,150 +86,117 @@ public virtual ValueTaskSourceStatus GetStatus(short token) public virtual void GetResult(short token) { + this.EnsureValidToken(token); + if (this.exception == null) { throw new InvalidOperationException("Attempt to get result on not yet completed promise"); } - this.IsCompletedOrThrow(); - } + this.currentId++; - public virtual void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - if (this.completions == null) - { - this.completions = new CompletionData[1]; - } - - int newIndex = this.callbackCount; - this.callbackCount++; - - if (newIndex == this.completions.Length) - { - var newArray = new CompletionData[this.completions.Length * 2]; - Array.Copy(this.completions, newArray, this.completions.Length); - this.completions = newArray; - } - - this.completions[newIndex] = new CompletionData( - continuation, - state, - (flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0 ? ExecutionContext.Capture() : null, - (flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0 ? SynchronizationContext.Current : null - ); - - if (this.exception != null) + if (this.exception != CompletedSentinel) { - this.TryExecuteCompletions(); + this.ThrowLatchedException(); } } - public static implicit operator ValueTask(AbstractPromise promise) => new ValueTask(promise, SourceToken); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - bool IsCompletedOrThrow() + public virtual void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { - if (this.exception == null) - { - return false; - } + this.EnsureValidToken(token); - if (this.exception != CompletedNoException) + if (this.continuation != null) { - this.ThrowLatchedException(); + throw new InvalidOperationException("Attempt to subscribe same promise twice"); } - return true; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - void ThrowLatchedException() => ExceptionDispatchInfo.Capture(this.exception).Throw(); - - bool TryExecuteCompletions() - { - if (this.callbackCount == 0 || this.completions == null) - { - return false; - } + this.continuation = continuation; + this.state = state; + this.executionContext = (flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0 ? ExecutionContext.Capture() : null; - List exceptions = null; - - for (int i = 0; i < this.callbackCount; i++) + if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0) { - try + SynchronizationContext sc = SynchronizationContext.Current; + if (sc != null && sc.GetType() != typeof(SynchronizationContext)) { - CompletionData completion = this.completions[i]; - ExecuteCompletion(completion); + this.schedulingContext = sc; } - catch (Exception ex) + else { - if (exceptions == null) + TaskScheduler ts = TaskScheduler.Current; + if (ts != TaskScheduler.Default) { - exceptions = new List(); + this.schedulingContext = ts; } - - exceptions.Add(ex); } } - if (exceptions == null) + if (this.exception != null) { - return true; + this.ExecuteContinuation(); } - - throw new AggregateException(exceptions); } + + public static implicit operator ValueTask(AbstractPromise promise) => promise.ValueTask; + + [MethodImpl(MethodImplOptions.NoInlining)] + void ThrowLatchedException() => ExceptionDispatchInfo.Capture(this.exception).Throw(); [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void ClearCallbacks() + protected void ClearCallback() { - if (this.callbackCount > 0) + this.continuation = null; + this.state = null; + this.executionContext = null; + this.schedulingContext = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void EnsureValidToken(short token) + { + if (this.currentId != token) { - this.callbackCount = 0; - Array.Clear(this.completions, 0, this.completions.Length); + throw new InvalidOperationException("Incorrect ValueTask token"); } - } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void ExecuteCompletion(CompletionData completion) + void ExecuteContinuation() { - if (completion.SynchronizationContext == null) + ExecutionContext executionContext = this.executionContext; + object schedulingContext = this.schedulingContext; + + if (schedulingContext == null) { - if (completion.ExecutionContext == null) + if (executionContext == null) { - completion.Continuation(completion.State); + this.ExecuteContinuation0(); } else { - //boxing - ExecutionContext.Run(completion.ExecutionContext, ExecutionContextCallback, completion); + ExecutionContext.Run(executionContext, ExecutionContextCallback, this); } } + else if (schedulingContext is SynchronizationContext sc) + { + sc.Post(executionContext == null ? SyncContextCallback : SyncContextCallbackWithExecutionContext, this); + } else { - if (completion.ExecutionContext == null) - { - //boxing - completion.SynchronizationContext.Post(SyncContextCallback, completion); - } - else - { - //boxing - completion.SynchronizationContext.Post(SyncContextCallbackWithExecutionContext, completion); - } + TaskScheduler ts = (TaskScheduler)schedulingContext; + Contract.Assert(ts != null, "Expected a TaskScheduler"); + Task.Factory.StartNew(executionContext == null ? TaskSchedulerCallback : TaskScheduleCallbackWithExecutionContext, this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts); } } - static void Execute(object state) - { - CompletionData completion = (CompletionData)state; - completion.Continuation(completion.State); - } - - static void ExecuteWithExecutionContext(object state) + static void Execute(object state) => ((AbstractPromise)state).ExecuteContinuation0(); + + static void ExecuteWithExecutionContext(object state) => ExecutionContext.Run(((AbstractPromise)state).executionContext, ExecutionContextCallback, state); + + protected virtual void ExecuteContinuation0() { - CompletionData completion = (CompletionData)state; - ExecutionContext.Run(completion.ExecutionContext, ExecutionContextCallback, state); + Contract.Assert(this.continuation != null); + this.continuation(this.state); } } } \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs index 756aa4c29..cfee262b3 100644 --- a/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractRecyclablePromise.cs @@ -4,14 +4,19 @@ namespace DotNetty.Common.Concurrency { using System; + using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Threading.Tasks.Sources; public abstract class AbstractRecyclablePromise : AbstractPromise { - protected bool recycled; + static readonly Action RecycleAction = Recycle; + protected readonly ThreadLocalPool.Handle handle; - + + protected bool recycled; + protected IEventExecutor executor; + protected AbstractRecyclablePromise(ThreadLocalPool.Handle handle) { this.handle = handle; @@ -29,38 +34,39 @@ public override void GetResult(short token) base.GetResult(token); } - protected override bool TryComplete0(Exception exception) + protected override bool TryComplete0(Exception exception, out bool continuationInvoked) { + Contract.Assert(this.executor.InEventLoop, "must be invoked from an event loop"); this.ThrowIfRecycled(); - bool completed; try { - completed = base.TryComplete0(exception); + bool completed = base.TryComplete0(exception, out continuationInvoked); + if (!continuationInvoked) + { + this.Recycle(); + } + return completed; } catch { this.Recycle(); throw; } - - if (completed) - { - this.Recycle(); - } - - return completed; } - - protected void Init() + + protected void Init(IEventExecutor executor) { + this.executor = executor; this.recycled = false; } protected virtual void Recycle() { + Contract.Assert(this.executor.InEventLoop, "must be invoked from an event loop"); this.exception = null; - this.ClearCallbacks(); + this.ClearCallback(); + this.executor = null; this.recycled = true; this.handle.Release(this); } @@ -68,9 +74,28 @@ protected virtual void Recycle() public override void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) { this.ThrowIfRecycled(); - base.OnCompleted(continuation, state, token, flags); + base.OnCompleted(continuation,state, token, flags); } + protected override void ExecuteContinuation0() + { + try + { + base.ExecuteContinuation0(); + } + finally + { + if (this.executor.InEventLoop) + { + this.Recycle(); + } + else + { + this.executor.Execute(RecycleAction, this); + } + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] void ThrowIfRecycled() { @@ -79,5 +104,12 @@ void ThrowIfRecycled() throw new InvalidOperationException("Attempt to use recycled channel promise"); } } + + static void Recycle(object state) + { + AbstractRecyclablePromise promise = (AbstractRecyclablePromise)state; + Contract.Assert(promise.executor.InEventLoop, "must be invoked from an event loop"); + promise.Recycle(); + } } } \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/IPromise.cs b/src/DotNetty.Common/Concurrency/IPromise.cs index 0c8f836ae..f51e1c28d 100644 --- a/src/DotNetty.Common/Concurrency/IPromise.cs +++ b/src/DotNetty.Common/Concurrency/IPromise.cs @@ -4,14 +4,18 @@ namespace DotNetty.Common.Concurrency { using System; + using System.Threading; + using System.Threading.Tasks; public interface IPromise { + ValueTask ValueTask { get; } + bool TryComplete(); bool TrySetException(Exception exception); - bool TrySetCanceled(); + bool TrySetCanceled(CancellationToken cancellationToken = default(CancellationToken)); bool SetUncancellable(); } diff --git a/src/DotNetty.Common/Utilities/TaskEx.cs b/src/DotNetty.Common/Utilities/TaskEx.cs index e029cd9a6..f3c29541a 100644 --- a/src/DotNetty.Common/Utilities/TaskEx.cs +++ b/src/DotNetty.Common/Utilities/TaskEx.cs @@ -123,35 +123,14 @@ public static async void LinkOutcome(this ValueTask future, IPromise promise) { try { - await future; + //context capturing not required since callback executed synchrounusly on completion in eventloop + await future.ConfigureAwait(false); promise.TryComplete(); } catch (Exception ex) { promise.TrySetException(ex); } - - /* - - if (future.IsCompletedSuccessfully) - { - promise.TryComplete(); - } - else if (future.IsFaulted || future.IsCanceled) - { - try - { - future.GetAwaiter().GetResult(); - } - catch (Exception ex) - { - promise.TryComplete(ex); - } - } - else - { - future.GetAwaiter().OnCompleted(() => LinkOutcome(future, promise)); - }*/ } diff --git a/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs b/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs index c9c584eba..0e509f967 100644 --- a/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs +++ b/src/DotNetty.Handlers/Timeout/IdleStateHandler.cs @@ -96,7 +96,7 @@ public class IdleStateHandler : ChannelDuplexHandler { static readonly TimeSpan MinTimeout = TimeSpan.FromMilliseconds(1); - readonly Action writeListener; + readonly Action writeListener; readonly bool observeOutput; readonly TimeSpan readerIdleTime; @@ -201,7 +201,7 @@ public IdleStateHandler(bool observeOutput, ? TimeUtil.Max(allIdleTime, IdleStateHandler.MinTimeout) : TimeSpan.Zero; - this.writeListener = new Action(() => + this.writeListener = new Action(t => { this.lastWriteTime = this.Ticks(); this.firstWriterIdleEvent = this.firstAllIdleEvent = true; @@ -303,16 +303,17 @@ public override void ChannelReadComplete(IChannelHandlerContext context) context.FireChannelReadComplete(); } - public override async ValueTask WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { + ValueTask future = context.WriteAsync(message); if (this.writerIdleTime.Ticks > 0 || this.allIdleTime.Ticks > 0) { - await context.WriteAsync(message); - this.writeListener(); - return; + //task allocation since we attach continuation and returning task to a caller + Task task = future.AsTask(); + task.ContinueWith(this.writeListener, TaskContinuationOptions.ExecuteSynchronously); + return new ValueTask(task); } - - await context.WriteAsync(message); + return future; } void Initialize(IChannelHandlerContext context) diff --git a/src/DotNetty.Handlers/Tls/TlsHandler.cs b/src/DotNetty.Handlers/Tls/TlsHandler.cs index fe90fa02b..d220bd1b6 100644 --- a/src/DotNetty.Handlers/Tls/TlsHandler.cs +++ b/src/DotNetty.Handlers/Tls/TlsHandler.cs @@ -514,7 +514,7 @@ public override ValueTask WriteAsync(IChannelHandlerContext context, object mess { return new UnsupportedMessageTypeException(message, typeof(IByteBuffer)).ToValueTask(); } - return this.pendingUnencryptedWrites.Add(message); + return new ValueTask(this.pendingUnencryptedWrites.Add(message)); } public override void Flush(IChannelHandlerContext context) diff --git a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs index a103e16ac..d374b77e8 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs @@ -1020,7 +1020,7 @@ abstract class AbstractWriteTask : AbstractRecyclablePromise, IRunnable protected static void Init(AbstractWriteTask task, AbstractChannelHandlerContext ctx, object msg) { - task.Init(); + task.Init(ctx.Executor); task.ctx = ctx; task.msg = msg; diff --git a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs index 7387202d4..eb5ec0b99 100644 --- a/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/BatchingPendingWriteQueue.cs @@ -6,6 +6,8 @@ namespace DotNetty.Transport.Channels using System; using System.Collections.Generic; using System.Diagnostics.Contracts; + using System.Globalization; + using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Threading.Tasks.Sources; using DotNetty.Common; @@ -65,7 +67,7 @@ public int Size } /// Add the given msg and returns for completion of processing msg. - public ValueTask Add(object msg) + public Task Add(object msg) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(msg != null); @@ -83,11 +85,11 @@ public ValueTask Add(object msg) if (canBundle) { currentTail.Add(msg, messageSize); - return currentTail; + return currentTail.Task; } } - PendingWrite write = PendingWrite.NewInstance(msg, messageSize); + PendingWrite write = PendingWrite.NewInstance(this.ctx.Executor, msg, messageSize); if (currentTail == null) { this.tail = this.head = write; @@ -102,7 +104,7 @@ public ValueTask Add(object msg) // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 this.buffer?.IncrementPendingOutboundBytes(messageSize); - return write; + return write.Task; } /// @@ -160,7 +162,7 @@ public void RemoveAndFail(Exception cause) /// if something was written and null if the /// is empty. /// - public ValueTask RemoveAndWriteAllAsync() + public Task RemoveAndWriteAllAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); @@ -173,7 +175,7 @@ public ValueTask RemoveAndWriteAllAsync() if (write == null) { // empty so just return null - return default(ValueTask); + return null; } // Guard against re-entrance by directly reset @@ -181,7 +183,7 @@ public ValueTask RemoveAndWriteAllAsync() int currentSize = this.size; this.size = 0; - var tasks = new List(currentSize); + var tasks = new List(currentSize); while (write != null) { PendingWrite next = write.Next; @@ -189,11 +191,11 @@ public ValueTask RemoveAndWriteAllAsync() this.Recycle(write, false); this.ctx.WriteAsync(msg).LinkOutcome(write); - tasks.Add(write); + tasks.Add(write.Task); write = next; } this.AssertEmpty(); - return new AggregatingPromise(tasks); + return Task.WhenAll(tasks); } void AssertEmpty() => Contract.Assert(this.tail == null && this.head == null && this.size == 0); @@ -206,19 +208,19 @@ public ValueTask RemoveAndWriteAllAsync() /// if something was written and null if the /// is empty. /// - public ValueTask RemoveAndWriteAsync() + public Task RemoveAndWriteAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { - return default(ValueTask); + return null; } object msg = write.Messages; this.Recycle(write, true); this.ctx.WriteAsync(msg).LinkOutcome(write); - return write; + return write.Task; } /// @@ -318,6 +320,8 @@ sealed class PendingWrite : AbstractRecyclablePromise { static readonly ThreadLocalPool Pool = new ThreadLocalPool(handle => new PendingWrite(handle)); + Task task; + public PendingWrite Next; public long Size; public readonly List Messages; @@ -327,10 +331,10 @@ sealed class PendingWrite : AbstractRecyclablePromise this.Messages = new List(); } - public static PendingWrite NewInstance(object msg, int size) + public static PendingWrite NewInstance(IEventExecutor executor, object msg, int size) { PendingWrite write = Pool.Take(); - write.Init(); + write.Init(executor); write.Add(msg, size); return write; } @@ -341,11 +345,15 @@ public void Add(object msg, int size) this.Size += size; } + //pendingwrite instances can be returned to a caller times but AbstractPromise supports single continuation only + public Task Task => this.task ?? (this.task = this.ValueTask.AsTask()); + protected override void Recycle() { this.Size = 0; this.Next = null; this.Messages.Clear(); + this.task = null; base.Recycle(); } } diff --git a/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs b/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs index 2f8659320..ebdd7842f 100644 --- a/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs +++ b/src/DotNetty.Transport/Channels/ChannelOutboundBuffer.cs @@ -808,7 +808,7 @@ sealed class Entry : AbstractRecyclablePromise public static Entry NewInstance(IEventExecutor executor, object msg, int size) { Entry entry = Pool.Take(); - entry.Init(); + entry.Init(executor); entry.Message = msg; entry.PendingSize = size; return entry; diff --git a/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs b/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs index 29004d182..47155cb2d 100644 --- a/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs +++ b/src/DotNetty.Transport/Channels/Embedded/EmbeddedChannel.cs @@ -295,54 +295,34 @@ public bool WriteOutbound(params object[] msgs) return IsNotEmpty(this.outboundMessages); } - ThreadLocalObjectList futures = ThreadLocalObjectList.NewInstance(msgs.Length); - foreach (object m in msgs) { if (m == null) { break; } - futures.Add(this.WriteAsync(m).AsTask()); + WriteAsync(m); } // We need to call RunPendingTasks first as a IChannelHandler may have used IEventLoop.Execute(...) to // delay the write on the next event loop run. this.RunPendingTasks(); this.Flush(); + this.RunPendingTasks(); + this.CheckException(); + return IsNotEmpty(this.outboundMessages); - int size = futures.Count; - for (int i = 0; i < size; i++) + async void WriteAsync(object message) { - var future = (Task)futures[i]; - if (future.IsCompleted) + try { - this.RecordException(future); + //context capturing not required since callback executed synchrounusly on completion in eventloop + await this.WriteAsync(message).ConfigureAwait(false); } - else + catch (Exception e) { - // The write may be delayed to run later by runPendingTasks() - future.ContinueWith(t => this.RecordException(t)); + this.RecordException(e); } } - - futures.Return(); - - this.RunPendingTasks(); - this.CheckException(); - return IsNotEmpty(this.outboundMessages); - } - - void RecordException(Task future) - { - switch (future.Status) - { - case TaskStatus.Canceled: - case TaskStatus.Faulted: - this.RecordException(future.Exception); - break; - default: - break; - } } void RecordException(Exception cause) diff --git a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs index a62f6ea8d..f09a9dd3a 100644 --- a/src/DotNetty.Transport/Channels/PendingWriteQueue.cs +++ b/src/DotNetty.Transport/Channels/PendingWriteQueue.cs @@ -82,8 +82,8 @@ public ValueTask Add(object msg) // Size may be unknow so just use 0 messageSize = 0; } - //var promise = new TaskCompletionSource(); - PendingWrite write = PendingWrite.NewInstance(msg, messageSize); + + PendingWrite write = PendingWrite.NewInstance(this.ctx.Executor, msg, messageSize); PendingWrite currentTail = this.tail; if (currentTail == null) { @@ -289,10 +289,10 @@ sealed class PendingWrite : AbstractRecyclablePromise { } - public static PendingWrite NewInstance(object msg, int size) + public static PendingWrite NewInstance(IEventExecutor executor, object msg, int size) { PendingWrite write = Pool.Take(); - write.Init(); + write.Init(executor); write.Size = size; write.Msg = msg; return write; diff --git a/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs b/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs index 6ece234cc..cfa0456f0 100644 --- a/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs +++ b/test/DotNetty.Codecs.Tests/Frame/LengthFieldPrependerTest.cs @@ -70,13 +70,11 @@ public void TestPrependAdjustedLength() public void TestPrependAdjustedLengthLessThanZero() { var ch = new EmbeddedChannel(new LengthFieldPrepender(4, -2)); - var ex = Assert.Throws(() => + var ex = Assert.Throws(() => { ch.WriteOutbound(this.msg); Assert.True(false, typeof(EncoderException).Name + " must be raised."); }); - - Assert.IsType(ex.InnerExceptions.Single()); } [Fact] From 619bfb885abbbb35df47dbf4f352365e32f61623 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Thu, 22 Mar 2018 00:29:14 -0700 Subject: [PATCH 17/29] post rebase fixes --- src/DotNetty.Transport.Libuv/Native/WriteRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs b/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs index 9b4391a8b..f0da3c1fc 100644 --- a/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs +++ b/src/DotNetty.Transport.Libuv/Native/WriteRequest.cs @@ -63,7 +63,7 @@ internal void DoWrite(NativeChannel.INativeUnsafe channelUnsafe, ChannelOutbound Debug.Assert(this.nativeUnsafe == null); this.nativeUnsafe = channelUnsafe; - this.input.ForEachFlushedMessage(this); + input.ForEachFlushedMessage(this); this.DoWrite(); } From baa5e3ad120c32cd154de929db42173d33d65e4d Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Thu, 22 Mar 2018 00:40:08 -0700 Subject: [PATCH 18/29] multicontinuation fix for timeout handler --- .../Timeout/WriteTimeoutHandler.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs b/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs index 76f616595..afc0e612e 100644 --- a/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs +++ b/src/DotNetty.Handlers/Timeout/WriteTimeoutHandler.cs @@ -85,14 +85,17 @@ public WriteTimeoutHandler(TimeSpan timeout) public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { - ValueTask task = context.WriteAsync(message); + ValueTask future = context.WriteAsync(message); if (this.timeout.Ticks > 0) { + //allocating task cause we need to attach continuation + Task task = future.AsTask(); this.ScheduleTimeout(context, task); + return new ValueTask(task); } - return task; + return future; } public override void HandlerRemoved(IChannelHandlerContext context) @@ -106,20 +109,19 @@ public override void HandlerRemoved(IChannelHandlerContext context) } } - async void ScheduleTimeout(IChannelHandlerContext context, ValueTask future) + void ScheduleTimeout(IChannelHandlerContext context, Task future) { // Schedule a timeout. var task = new WriteTimeoutTask(context, future, this); - task.ScheduledTask = context.Executor.Schedule(task, this.timeout); + task.ScheduledTask = context.Executor.Schedule(task, timeout); if (!task.ScheduledTask.Completion.IsCompleted) { this.AddWriteTimeoutTask(task); // Cancel the scheduled timeout if the flush promise is complete. - await future; - WriteTimeoutTask.OperationCompleteAction(task); + future.ContinueWith(WriteTimeoutTask.OperationCompleteAction, task, TaskContinuationOptions.ExecuteSynchronously); } } @@ -147,22 +149,22 @@ protected virtual void WriteTimedOut(IChannelHandlerContext context) } } - sealed class WriteTimeoutTask : AbstractPromise, IRunnable + sealed class WriteTimeoutTask : IRunnable { readonly WriteTimeoutHandler handler; readonly IChannelHandlerContext context; - readonly ValueTask future; + readonly Task future; - public static readonly Action OperationCompleteAction = HandleOperationComplete; + public static readonly Action OperationCompleteAction = HandleOperationComplete; - public WriteTimeoutTask(IChannelHandlerContext context, ValueTask future, WriteTimeoutHandler handler) + public WriteTimeoutTask(IChannelHandlerContext context, Task future, WriteTimeoutHandler handler) { this.context = context; this.future = future; this.handler = handler; } - static void HandleOperationComplete(object state) + static void HandleOperationComplete(Task future, object state) { var writeTimeoutTask = (WriteTimeoutTask) state; From 3428e2f03631fd287f666b47452518a0887158e6 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Tue, 10 Apr 2018 12:21:16 -0700 Subject: [PATCH 19/29] switch to nuget.org for tasks.extensions package --- .nuget/NuGet.Config | 1 - src/DotNetty.Codecs/DotNetty.Codecs.csproj | 2 +- src/DotNetty.Common/DotNetty.Common.csproj | 5 ----- src/DotNetty.Handlers/DotNetty.Handlers.csproj | 3 +-- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config index e7814482c..d0b0427aa 100644 --- a/.nuget/NuGet.Config +++ b/.nuget/NuGet.Config @@ -5,6 +5,5 @@ - \ No newline at end of file diff --git a/src/DotNetty.Codecs/DotNetty.Codecs.csproj b/src/DotNetty.Codecs/DotNetty.Codecs.csproj index 491c779af..9ddecdefa 100644 --- a/src/DotNetty.Codecs/DotNetty.Codecs.csproj +++ b/src/DotNetty.Codecs/DotNetty.Codecs.csproj @@ -46,6 +46,6 @@ - + \ No newline at end of file diff --git a/src/DotNetty.Common/DotNetty.Common.csproj b/src/DotNetty.Common/DotNetty.Common.csproj index 8d41cb60b..b871a6cdf 100644 --- a/src/DotNetty.Common/DotNetty.Common.csproj +++ b/src/DotNetty.Common/DotNetty.Common.csproj @@ -43,9 +43,4 @@ - - - C:\Users\mkim\.nuget\packages\system.valuetuple\4.3.0\lib\netstandard1.0\System.ValueTuple.dll - - \ No newline at end of file diff --git a/src/DotNetty.Handlers/DotNetty.Handlers.csproj b/src/DotNetty.Handlers/DotNetty.Handlers.csproj index d4a715db4..9eb460600 100644 --- a/src/DotNetty.Handlers/DotNetty.Handlers.csproj +++ b/src/DotNetty.Handlers/DotNetty.Handlers.csproj @@ -44,7 +44,6 @@ - - + \ No newline at end of file From eb5ab1b45aae1239de5328f71875a6883a7d6dde Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Wed, 11 Apr 2018 10:36:33 -0700 Subject: [PATCH 20/29] default WriteAndFlushAsync returns Task --- src/DotNetty.Transport/Channels/AbstractChannel.cs | 2 +- .../Channels/AbstractChannelHandlerContext.cs | 2 +- src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs | 2 +- src/DotNetty.Transport/Channels/IChannel.cs | 2 +- src/DotNetty.Transport/Channels/IChannelHandlerContext.cs | 2 +- src/DotNetty.Transport/Channels/IChannelPipeline.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DotNetty.Transport/Channels/AbstractChannel.cs b/src/DotNetty.Transport/Channels/AbstractChannel.cs index 3ec1cd41b..92496b2ec 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannel.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannel.cs @@ -191,7 +191,7 @@ public IChannel Read() public ValueTask WriteAsync(object msg) => this.pipeline.WriteAsync(msg); - public ValueTask WriteAndFlushAsync(object message) => this.pipeline.WriteAndFlushAsync(message); + public Task WriteAndFlushAsync(object message) => this.pipeline.WriteAndFlushAsync(message); public ValueTask WriteAndFlushAsync(object message, bool notifyComplete) => this.pipeline.WriteAndFlushAsync(message, notifyComplete); diff --git a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs index d374b77e8..10036c5fa 100644 --- a/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/AbstractChannelHandlerContext.cs @@ -856,7 +856,7 @@ void InvokeFlush0() } } - public ValueTask WriteAndFlushAsync(object message) => this.WriteAndFlushAsync(message, true); + public Task WriteAndFlushAsync(object message) => this.WriteAndFlushAsync(message, true).AsTask(); public ValueTask WriteAndFlushAsync(object message, bool notifyComplete) { diff --git a/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs b/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs index 71dbfe5df..3186fc3e6 100644 --- a/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs +++ b/src/DotNetty.Transport/Channels/DefaultChannelPipeline.cs @@ -830,7 +830,7 @@ public IChannelPipeline Flush() return this; } - public ValueTask WriteAndFlushAsync(object msg) => this.tail.WriteAndFlushAsync(msg); + public Task WriteAndFlushAsync(object msg) => this.tail.WriteAndFlushAsync(msg); public ValueTask WriteAndFlushAsync(object msg, bool notifyComplete) => this.tail.WriteAndFlushAsync(msg, notifyComplete); diff --git a/src/DotNetty.Transport/Channels/IChannel.cs b/src/DotNetty.Transport/Channels/IChannel.cs index 5e910b1c0..786c3042e 100644 --- a/src/DotNetty.Transport/Channels/IChannel.cs +++ b/src/DotNetty.Transport/Channels/IChannel.cs @@ -66,7 +66,7 @@ public interface IChannel : IAttributeMap, IComparable IChannel Flush(); - ValueTask WriteAndFlushAsync(object message); + Task WriteAndFlushAsync(object message); ValueTask WriteAndFlushAsync(object message, bool notifyComplete); } diff --git a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs index b96a197a3..b3e17749d 100644 --- a/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs +++ b/src/DotNetty.Transport/Channels/IChannelHandlerContext.cs @@ -71,7 +71,7 @@ public interface IChannelHandlerContext : IAttributeMap IChannelHandlerContext Flush(); - ValueTask WriteAndFlushAsync(object message); + Task WriteAndFlushAsync(object message); ValueTask WriteAndFlushAsync(object message, bool notifyComplete); diff --git a/src/DotNetty.Transport/Channels/IChannelPipeline.cs b/src/DotNetty.Transport/Channels/IChannelPipeline.cs index db8939784..270ad3eea 100644 --- a/src/DotNetty.Transport/Channels/IChannelPipeline.cs +++ b/src/DotNetty.Transport/Channels/IChannelPipeline.cs @@ -694,7 +694,7 @@ public interface IChannelPipeline : IEnumerable /// /// Shortcut for calling both and . /// - ValueTask WriteAndFlushAsync(object msg); + Task WriteAndFlushAsync(object msg); ValueTask WriteAndFlushAsync(object msg, bool notifyComplete); } From 4dcb8bbb552812d1c191fbfa42606339e6519767 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Wed, 11 Apr 2018 17:58:55 -0700 Subject: [PATCH 21/29] post rebase fixes --- examples/Telnet.Server/TelnetServerHandler.cs | 8 +- src/DotNetty.Codecs.Http/Cors/CorsHandler.cs | 13 +- .../HttpClientUpgradeHandler.cs | 6 +- .../HttpServerExpectContinueHandler.cs | 16 +- .../HttpServerKeepAliveHandler.cs | 11 +- .../Compression/JZlibEncoder.cs | 3 +- src/DotNetty.Codecs/MessageAggregator.cs | 29 ++-- src/DotNetty.Codecs/MessageToMessageCodec.cs | 2 +- src/DotNetty.Codecs/TaskExtensions.cs | 59 +++++++ .../Concurrency/AbstractPromise.cs | 2 +- .../Streams/ChunkedWriteHandler.cs | 157 +++++++++--------- .../Channels/CombinedChannelDuplexHandler.cs | 6 +- .../HttpContentCompressorTest.cs | 7 +- .../ValueTaskExtensions.cs | 37 ----- test/DotNetty.Tests.End2End/End2EndTests.cs | 2 +- .../AutoReadTests.cs | 6 +- .../BufReleaseTests.cs | 2 +- .../DetectPeerCloseWithoutReadTests.cs | 1 + .../ExceptionHandlingTests.cs | 4 +- .../ReadPendingTests.cs | 6 +- .../WriteBeforeRegisteredTests.cs | 2 +- .../SocketDatagramChannelMulticastTest.cs | 4 +- .../SocketDatagramChannelUnicastTest.cs | 4 +- 23 files changed, 193 insertions(+), 194 deletions(-) create mode 100644 src/DotNetty.Codecs/TaskExtensions.cs delete mode 100644 test/DotNetty.Tests.Common/ValueTaskExtensions.cs diff --git a/examples/Telnet.Server/TelnetServerHandler.cs b/examples/Telnet.Server/TelnetServerHandler.cs index 8e7020b4c..2e85ae8a4 100644 --- a/examples/Telnet.Server/TelnetServerHandler.cs +++ b/examples/Telnet.Server/TelnetServerHandler.cs @@ -6,6 +6,7 @@ namespace Telnet.Server using System; using System.Net; using System.Threading.Tasks; + using DotNetty.Codecs; using DotNetty.Common.Concurrency; using DotNetty.Transport.Channels; @@ -17,7 +18,7 @@ public override void ChannelActive(IChannelHandlerContext contex) contex.WriteAndFlushAsync(string.Format("It is {0} now !\r\n", DateTime.Now)); } - protected override async void ChannelRead0(IChannelHandlerContext contex, string msg) + protected override void ChannelRead0(IChannelHandlerContext context, string msg) { // Generate and write a response. string response; @@ -36,11 +37,10 @@ protected override async void ChannelRead0(IChannelHandlerContext contex, string response = "Did you say '" + msg + "'?\r\n"; } - ValueTask waitClose = contex.WriteAndFlushAsync(response); + Task waitClose = context.WriteAndFlushAsync(response); if (close) { - await waitClose; - contex.CloseAsync(); + waitClose.CloseOnComplete(context); } } diff --git a/src/DotNetty.Codecs.Http/Cors/CorsHandler.cs b/src/DotNetty.Codecs.Http/Cors/CorsHandler.cs index c6e38d2e0..feb5453f6 100644 --- a/src/DotNetty.Codecs.Http/Cors/CorsHandler.cs +++ b/src/DotNetty.Codecs.Http/Cors/CorsHandler.cs @@ -167,7 +167,7 @@ void SetExposeHeaders(IHttpResponse response) void SetMaxAge(IHttpResponse response) => response.Headers.Set(HttpHeaderNames.AccessControlMaxAge, this.config.MaxAge); - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { if (this.config.IsCorsSupportEnabled && message is IHttpResponse response) { @@ -177,7 +177,7 @@ public override Task WriteAsync(IChannelHandlerContext context, object message) this.SetExposeHeaders(response); } } - return context.WriteAndFlushAsync(message); + return context.WriteAndFlushAsync(message, true); } static void Forbidden(IChannelHandlerContext ctx, IHttpRequest request) @@ -197,15 +197,8 @@ static void Respond(IChannelHandlerContext ctx, IHttpRequest request, IHttpRespo Task task = ctx.WriteAndFlushAsync(response); if (!keepAlive) { - task.ContinueWith(CloseOnComplete, ctx, - TaskContinuationOptions.ExecuteSynchronously); + task.CloseOnComplete(ctx); } } - - static void CloseOnComplete(Task task, object state) - { - var ctx = (IChannelHandlerContext)state; - ctx.CloseAsync(); - } } } diff --git a/src/DotNetty.Codecs.Http/HttpClientUpgradeHandler.cs b/src/DotNetty.Codecs.Http/HttpClientUpgradeHandler.cs index 9f10eeea3..122fd78e4 100644 --- a/src/DotNetty.Codecs.Http/HttpClientUpgradeHandler.cs +++ b/src/DotNetty.Codecs.Http/HttpClientUpgradeHandler.cs @@ -72,7 +72,7 @@ public HttpClientUpgradeHandler(ISourceCodec sourceCodec, IUpgradeCodec upgradeC this.upgradeCodec = upgradeCodec; } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { if (!(message is IHttpRequest)) { @@ -81,14 +81,14 @@ public override Task WriteAsync(IChannelHandlerContext context, object message) if (this.upgradeRequested) { - return TaskEx.FromException(new InvalidOperationException("Attempting to write HTTP request with upgrade in progress")); + return new ValueTask(TaskEx.FromException(new InvalidOperationException("Attempting to write HTTP request with upgrade in progress"))); } this.upgradeRequested = true; this.SetUpgradeRequestHeaders(context, (IHttpRequest)message); // Continue writing the request. - Task task = context.WriteAsync(message); + ValueTask task = context.WriteAsync(message); // Notify that the upgrade request was issued. context.FireUserEventTriggered(UpgradeEvent.UpgradeIssued); diff --git a/src/DotNetty.Codecs.Http/HttpServerExpectContinueHandler.cs b/src/DotNetty.Codecs.Http/HttpServerExpectContinueHandler.cs index de4e41746..6ac07f518 100644 --- a/src/DotNetty.Codecs.Http/HttpServerExpectContinueHandler.cs +++ b/src/DotNetty.Codecs.Http/HttpServerExpectContinueHandler.cs @@ -39,27 +39,15 @@ public override void ChannelRead(IChannelHandlerContext context, object message) // the expectation failed so we refuse the request. IHttpResponse rejection = this.RejectResponse(req); ReferenceCountUtil.Release(message); - context.WriteAndFlushAsync(rejection) - .ContinueWith(CloseOnFailure, context, TaskContinuationOptions.ExecuteSynchronously); + context.WriteAndFlushAsync(rejection).CloseOnFailure(context); return; } - context.WriteAndFlushAsync(accept) - .ContinueWith(CloseOnFailure, context, TaskContinuationOptions.ExecuteSynchronously); + context.WriteAndFlushAsync(accept).CloseOnFailure(context); req.Headers.Remove(HttpHeaderNames.Expect); } base.ChannelRead(context, message); } } - - static Task CloseOnFailure(Task task, object state) - { - if (task.IsFaulted) - { - var context = (IChannelHandlerContext)state; - return context.CloseAsync(); - } - return TaskEx.Completed; - } } } diff --git a/src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs b/src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs index 981ff209b..cbec7c65c 100644 --- a/src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs +++ b/src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs @@ -31,7 +31,7 @@ public override void ChannelRead(IChannelHandlerContext context, object message) base.ChannelRead(context, message); } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { // modify message on way out to add headers if needed if (message is IHttpResponse response) @@ -52,18 +52,11 @@ public override Task WriteAsync(IChannelHandlerContext context, object message) } if (message is ILastHttpContent && !this.ShouldKeepAlive()) { - return base.WriteAsync(context, message) - .ContinueWith(CloseOnComplete, context, TaskContinuationOptions.ExecuteSynchronously); + return new ValueTask(base.WriteAsync(context, message).CloseOnComplete(context)); } return base.WriteAsync(context, message); } - static Task CloseOnComplete(Task task, object state) - { - var context = (IChannelHandlerContext)state; - return context.CloseAsync(); - } - void TrackResponse(IHttpResponse response) { if (!IsInformational(response)) diff --git a/src/DotNetty.Codecs/Compression/JZlibEncoder.cs b/src/DotNetty.Codecs/Compression/JZlibEncoder.cs index 901bfcf80..19bdc4df0 100644 --- a/src/DotNetty.Codecs/Compression/JZlibEncoder.cs +++ b/src/DotNetty.Codecs/Compression/JZlibEncoder.cs @@ -241,8 +241,7 @@ Task FinishEncode(IChannelHandlerContext context) this.z.next_out = null; } - return context.WriteAndFlushAsync(footer) - .ContinueWith(_ => context.CloseAsync()); + return context.WriteAndFlushAsync(footer).CloseOnComplete(context); } public override void HandlerAdded(IChannelHandlerContext context) => this.ctx = context; diff --git a/src/DotNetty.Codecs/MessageAggregator.cs b/src/DotNetty.Codecs/MessageAggregator.cs index 79f5e9c62..76106dd03 100644 --- a/src/DotNetty.Codecs/MessageAggregator.cs +++ b/src/DotNetty.Codecs/MessageAggregator.cs @@ -130,13 +130,10 @@ protected internal override void Decode(IChannelHandlerContext context, TMessage bool closeAfterWrite = this.CloseAfterContinueResponse(continueResponse); this.handlingOversizedMessage = this.IgnoreContentAfterContinueResponse(continueResponse); - Task task = context - .WriteAndFlushAsync(continueResponse) - .ContinueWith(ContinueResponseWriteAction, context, TaskContinuationOptions.ExecuteSynchronously); + WriteContinueResponse(context, continueResponse, closeAfterWrite); if (closeAfterWrite) { - task.ContinueWith(CloseAfterWriteAction, context, TaskContinuationOptions.ExecuteSynchronously); return; } @@ -245,19 +242,21 @@ protected internal override void Decode(IChannelHandlerContext context, TMessage throw new MessageAggregationException("Unknown aggregation state."); } } - - static void CloseAfterWriteAction(Task task, object state) + + static async void WriteContinueResponse(IChannelHandlerContext ctx, object message, bool closeAfterWrite) { - var ctx = (IChannelHandlerContext)state; - ctx.Channel.CloseAsync(); - } - - static void ContinueResponseWriteAction(Task task, object state) - { - if (task.IsFaulted) + try + { + await ctx.WriteAndFlushAsync(message); + } + catch (Exception ex) + { + ctx.FireExceptionCaught(ex); + } + + if (closeAfterWrite) { - var ctx = (IChannelHandlerContext)state; - ctx.FireExceptionCaught(task.Exception); + ctx.Channel.CloseAsync(); } } diff --git a/src/DotNetty.Codecs/MessageToMessageCodec.cs b/src/DotNetty.Codecs/MessageToMessageCodec.cs index a990193a2..5b4368323 100644 --- a/src/DotNetty.Codecs/MessageToMessageCodec.cs +++ b/src/DotNetty.Codecs/MessageToMessageCodec.cs @@ -50,7 +50,7 @@ protected MessageToMessageCodec() public sealed override void ChannelRead(IChannelHandlerContext context, object message) => this.decoder.ChannelRead(context, message); - public sealed override Task WriteAsync(IChannelHandlerContext context, object message) => + public sealed override ValueTask WriteAsync(IChannelHandlerContext context, object message) => this.encoder.WriteAsync(context, message); public virtual bool AcceptInboundMessage(object msg) => msg is TInbound; diff --git a/src/DotNetty.Codecs/TaskExtensions.cs b/src/DotNetty.Codecs/TaskExtensions.cs new file mode 100644 index 000000000..b5af165d0 --- /dev/null +++ b/src/DotNetty.Codecs/TaskExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace DotNetty.Codecs +{ + using System; + using System.Threading.Tasks; + using DotNetty.Common.Utilities; + using DotNetty.Transport.Channels; + + public static class TaskExtensions + { + public static async Task CloseOnComplete(this ValueTask task, IChannelHandlerContext ctx) + { + try + { + await task; + } + finally + { + await ctx.CloseAsync(); + } + } + + static readonly Func CloseOnCompleteContinuation = Close; + static readonly Func CloseOnFailureContinuation = CloseOnFailure; + + public static Task CloseOnComplete(this Task task, IChannelHandlerContext ctx) + => task.ContinueWith(CloseOnCompleteContinuation, ctx, TaskContinuationOptions.ExecuteSynchronously); + + public static Task CloseOnComplete(this Task task, IChannel channel) + => task.ContinueWith(CloseOnCompleteContinuation, channel, TaskContinuationOptions.ExecuteSynchronously); + + public static Task CloseOnFailure(this Task task, IChannelHandlerContext ctx) + => task.ContinueWith(CloseOnFailureContinuation, ctx, TaskContinuationOptions.ExecuteSynchronously); + + static Task Close(Task task, object state) + { + switch (state) + { + case IChannelHandlerContext ctx: + return ctx.CloseAsync(); + case IChannel ch: + return ch.CloseAsync(); + default: + throw new InvalidOperationException("must never get here"); + } + } + + static Task CloseOnFailure(Task task, object state) + { + if (task.IsFaulted) + { + return Close(task, state); + } + return TaskEx.Completed; + } + } +} \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/AbstractPromise.cs b/src/DotNetty.Common/Concurrency/AbstractPromise.cs index 1563bb8f4..e02c9bff7 100644 --- a/src/DotNetty.Common/Concurrency/AbstractPromise.cs +++ b/src/DotNetty.Common/Concurrency/AbstractPromise.cs @@ -22,7 +22,7 @@ public abstract class AbstractPromise : IPromise, IValueTaskSource static readonly Action TaskSchedulerCallback = Execute; static readonly Action TaskScheduleCallbackWithExecutionContext = ExecuteWithExecutionContext; - static readonly Exception CompletedSentinel = new Exception(); + protected static readonly Exception CompletedSentinel = new Exception(); short currentId; protected Exception exception; diff --git a/src/DotNetty.Handlers/Streams/ChunkedWriteHandler.cs b/src/DotNetty.Handlers/Streams/ChunkedWriteHandler.cs index 438856ae5..bf7120af8 100644 --- a/src/DotNetty.Handlers/Streams/ChunkedWriteHandler.cs +++ b/src/DotNetty.Handlers/Streams/ChunkedWriteHandler.cs @@ -7,6 +7,7 @@ namespace DotNetty.Handlers.Streams using System.Collections.Generic; using System.Threading.Tasks; using DotNetty.Buffers; + using DotNetty.Common; using DotNetty.Common.Concurrency; using DotNetty.Common.Internal.Logging; using DotNetty.Common.Utilities; @@ -39,11 +40,11 @@ public void ResumeTransfer() } } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { - var pendingWrite = new PendingWrite(message); + var pendingWrite = PendingWrite.NewInstance(context.Executor, message); this.queue.Enqueue(pendingWrite); - return pendingWrite.PendingTask; + return pendingWrite; } public override void Flush(IChannelHandlerContext context) => this.DoFlush(context); @@ -97,16 +98,16 @@ void Discard(Exception cause = null) cause = new ClosedChannelException(); } - current.Fail(cause); + current.TrySetException(cause); } else { - current.Success(); + current.TryComplete(); } } catch (Exception exception) { - current.Fail(exception); + current.TrySetException(exception); Logger.Warn($"{StringUtil.SimpleClassName(typeof(ChunkedWriteHandler))}.IsEndOfInput failed", exception); } finally @@ -121,7 +122,7 @@ void Discard(Exception cause = null) cause = new ClosedChannelException(); } - current.Fail(cause); + current.TrySetException(cause); } } } @@ -197,7 +198,7 @@ void DoFlush(IChannelHandlerContext context) ReferenceCountUtil.Release(message); } - current.Fail(exception); + current.TrySetException(exception); CloseInput(chunks); break; @@ -218,7 +219,7 @@ void DoFlush(IChannelHandlerContext context) message = Unpooled.Empty; } - Task future = context.WriteAsync(message); + ValueTask writeFuture = context.WriteAsync(message); if (endOfInput) { this.currentWrite = null; @@ -228,54 +229,62 @@ void DoFlush(IChannelHandlerContext context) // be closed before its not written. // // See https://github.com/netty/netty/issues/303 - future.ContinueWith((_, state) => + CloseOnComplete(writeFuture, current, chunks); + + async void CloseOnComplete(ValueTask future, PendingWrite promise, IChunkedInput input) + { + try { - var pendingTask = (PendingWrite)state; - CloseInput((IChunkedInput)pendingTask.Message); - pendingTask.Success(); - }, - current, - TaskContinuationOptions.ExecuteSynchronously); + await future; + } + finally + { + promise.Progress(input.Progress, input.Length); + promise.TryComplete(); + CloseInput(input); + } + } } else if (channel.IsWritable) { - future.ContinueWith((task, state) => + ProgressOnComplete(writeFuture, current, chunks); + + async void ProgressOnComplete(ValueTask future, PendingWrite promise, IChunkedInput input) + { + try { - var pendingTask = (PendingWrite)state; - if (task.IsFaulted) - { - CloseInput((IChunkedInput)pendingTask.Message); - pendingTask.Fail(task.Exception); - } - else - { - pendingTask.Progress(chunks.Progress, chunks.Length); - } - }, - current, - TaskContinuationOptions.ExecuteSynchronously); + await future; + promise.Progress(input.Progress, input.Length); + } + catch(Exception ex) + { + CloseInput((IChunkedInput)promise.Message); + promise.TrySetException(ex); + } + } } else { - future.ContinueWith((task, state) => + ProgressAndResumeOnComplete(writeFuture, this, channel, chunks); + + async void ProgressAndResumeOnComplete(ValueTask future, ChunkedWriteHandler handler, IChannel ch, IChunkedInput input) { - var handler = (ChunkedWriteHandler) state; - if (task.IsFaulted) - { - CloseInput((IChunkedInput)handler.currentWrite.Message); - handler.currentWrite.Fail(task.Exception); - } - else + PendingWrite promise = handler.currentWrite; + try { - handler.currentWrite.Progress(chunks.Progress, chunks.Length); - if (channel.IsWritable) + await future; + promise.Progress(input.Progress, input.Length); + if (ch.IsWritable) { handler.ResumeTransfer(); } } - }, - this, - TaskContinuationOptions.ExecuteSynchronously); + catch(Exception ex) + { + CloseInput((IChunkedInput)promise.Message); + promise.TrySetException(ex); + } + } } // Flush each chunk to conserve memory @@ -284,22 +293,7 @@ void DoFlush(IChannelHandlerContext context) } else { - context.WriteAsync(pendingMessage) - .ContinueWith((task, state) => - { - var pendingTask = (PendingWrite)state; - if (task.IsFaulted) - { - pendingTask.Fail(task.Exception); - } - else - { - pendingTask.Success(); - } - }, - current, - TaskContinuationOptions.ExecuteSynchronously); - + context.WriteAsync(pendingMessage).LinkOutcome(current); this.currentWrite = null; requiresFlush = true; } @@ -332,37 +326,48 @@ static void CloseInput(IChunkedInput chunks) } } - sealed class PendingWrite + sealed class PendingWrite : AbstractRecyclablePromise { - readonly TaskCompletionSource promise; - - public PendingWrite(object msg) + static readonly ThreadLocalPool Pool = new ThreadLocalPool(h => new PendingWrite(h)); + + PendingWrite(ThreadLocalPool.Handle handle) + : base(handle) { - this.Message = msg; - this.promise = new TaskCompletionSource(); + } + + public static PendingWrite NewInstance(IEventExecutor executor, object msg) + { + PendingWrite entry = Pool.Take(); + entry.Init(executor); + entry.Message = msg; + return entry; } - public object Message { get; } - - public void Success() => this.promise.TryComplete(); + public object Message { get; private set; } - public void Fail(Exception error) + protected override bool TryComplete0(Exception exception, out bool continuationInvoked) { - ReferenceCountUtil.Release(this.Message); - this.promise.TrySetException(error); + if (exception != CompletedSentinel) + { + ReferenceCountUtil.Release(this.Message); + } + + return base.TryComplete0(exception, out continuationInvoked); } public void Progress(long progress, long total) { - if (progress < total) + /*if (progress < total) { return; - } - - this.Success(); + }*/ } - public Task PendingTask => this.promise.Task; + protected override void Recycle() + { + this.Message = null; + base.Recycle(); + } } } } diff --git a/src/DotNetty.Transport/Channels/CombinedChannelDuplexHandler.cs b/src/DotNetty.Transport/Channels/CombinedChannelDuplexHandler.cs index 0f5dce1a9..bc6ab0c25 100644 --- a/src/DotNetty.Transport/Channels/CombinedChannelDuplexHandler.cs +++ b/src/DotNetty.Transport/Channels/CombinedChannelDuplexHandler.cs @@ -354,7 +354,7 @@ public override void Read(IChannelHandlerContext context) } } - public override Task WriteAsync(IChannelHandlerContext context, object message) + public override ValueTask WriteAsync(IChannelHandlerContext context, object message) { Contract.Assert(context == this.outboundCtx.InnerContext); @@ -491,7 +491,7 @@ public IChannelHandlerContext Read() return this; } - public Task WriteAsync(object message) => this.ctx.WriteAsync(message); + public ValueTask WriteAsync(object message) => this.ctx.WriteAsync(message); public IChannelHandlerContext Flush() { @@ -500,6 +500,8 @@ public IChannelHandlerContext Flush() } public Task WriteAndFlushAsync(object message) => this.ctx.WriteAndFlushAsync(message); + + public ValueTask WriteAndFlushAsync(object message, bool notifyComplete) => this.ctx.WriteAndFlushAsync(message, notifyComplete); public IAttribute GetAttribute(AttributeKey key) where T : class => this.ctx.GetAttribute(key); diff --git a/test/DotNetty.Codecs.Http.Tests/HttpContentCompressorTest.cs b/test/DotNetty.Codecs.Http.Tests/HttpContentCompressorTest.cs index ed0bcd240..e5695cc10 100644 --- a/test/DotNetty.Codecs.Http.Tests/HttpContentCompressorTest.cs +++ b/test/DotNetty.Codecs.Http.Tests/HttpContentCompressorTest.cs @@ -366,12 +366,9 @@ public void TooManyResponses() ch.WriteOutbound(new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.OK, Unpooled.Empty)); Assert.True(false, "Should not get here, expecting exception thrown"); } - catch (AggregateException e) + catch (EncoderException e) { - Assert.Single(e.InnerExceptions); - Assert.IsType(e.InnerExceptions[0]); - Exception exception = e.InnerExceptions[0]; - Assert.IsType(exception.InnerException); + Assert.IsType(e.InnerException); } Assert.True(ch.Finish()); diff --git a/test/DotNetty.Tests.Common/ValueTaskExtensions.cs b/test/DotNetty.Tests.Common/ValueTaskExtensions.cs deleted file mode 100644 index 24ae3a963..000000000 --- a/test/DotNetty.Tests.Common/ValueTaskExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace DotNetty.Tests.Common -{ - using System.Threading.Tasks; - using DotNetty.Transport.Channels; - - public static class ValueTaskExtensions - { - public static async void CloseOnComplete(this ValueTask task, IChannel channel) - { - try - { - await task; - } - finally - { - channel.CloseAsync(); - } - - } - - - public static async void CloseOnComplete(this ValueTask task, IChannelHandlerContext ctx) - { - try - { - await task; - } - finally - { - ctx.CloseAsync(); - } - } - } -} \ No newline at end of file diff --git a/test/DotNetty.Tests.End2End/End2EndTests.cs b/test/DotNetty.Tests.End2End/End2EndTests.cs index 18bbc450a..39473d46b 100644 --- a/test/DotNetty.Tests.End2End/End2EndTests.cs +++ b/test/DotNetty.Tests.End2End/End2EndTests.cs @@ -90,7 +90,7 @@ public async Task EchoServerAndClient() string[] messages = { "message 1", string.Join(",", Enumerable.Range(1, 300)) }; foreach (string message in messages) { - await clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(message))).AsTask().WithTimeout(DefaultTimeout); + await clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(Encoding.UTF8.GetBytes(message))).WithTimeout(DefaultTimeout); var responseMessage = Assert.IsAssignableFrom(await readListener.ReceiveAsync()); Assert.Equal(message, responseMessage.ToString(Encoding.UTF8)); diff --git a/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs b/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs index de5d4c4ab..c8f3bc6b2 100644 --- a/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/AutoReadTests.cs @@ -75,13 +75,13 @@ void AutoReadOffDuringReadOnlyReadsOneTime0(bool readOutsideEventLoopThread, Ser Assert.NotNull(this.clientChannel.LocalAddress); // 3 bytes means 3 independent reads for TestRecvByteBufAllocator - ValueTask writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[3])); - Assert.True(writeTask.AsTask().Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); + Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[3])); + Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); serverInitializer.AutoReadHandler.AssertSingleRead(); // 3 bytes means 3 independent reads for TestRecvByteBufAllocator writeTask = serverInitializer.Channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[3])); - Assert.True(writeTask.AsTask().Wait(TimeSpan.FromSeconds(5)), "Server write task timed out"); + Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Server write task timed out"); clientInitializer.AutoReadHandler.AssertSingleRead(); if (readOutsideEventLoopThread) diff --git a/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs b/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs index d5650f0ae..91220118c 100644 --- a/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/BufReleaseTests.cs @@ -107,7 +107,7 @@ public override void ChannelActive(IChannelHandlerContext ctx) // call retain on it so it can't be put back on the pool this.buf.WriteBytes(data).Retain(); - this.writeTask = ctx.Channel.WriteAndFlushAsync(this.buf).AsTask(); + this.writeTask = ctx.Channel.WriteAndFlushAsync(this.buf); } protected override void ChannelRead0(IChannelHandlerContext ctx, object msg) diff --git a/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs b/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs index 53a473198..e89d8140e 100644 --- a/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/DetectPeerCloseWithoutReadTests.cs @@ -8,6 +8,7 @@ namespace DotNetty.Transport.Libuv.Tests using System.Threading; using System.Threading.Tasks; using DotNetty.Buffers; + using DotNetty.Codecs; using DotNetty.Common.Concurrency; using DotNetty.Tests.Common; using DotNetty.Transport.Bootstrapping; diff --git a/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs b/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs index e503bb456..2d5e865aa 100644 --- a/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/ExceptionHandlingTests.cs @@ -61,8 +61,8 @@ void ReadPendingIsResetAfterEachRead0(ServerBootstrap sb, Bootstrap cb) this.clientChannel = task.Result; Assert.NotNull(this.clientChannel.LocalAddress); - ValueTask writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[1024])); - Assert.True(writeTask.AsTask().Wait(DefaultTimeout), "Write task timed out"); + Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[1024])); + Assert.True(writeTask.Wait(DefaultTimeout), "Write task timed out"); ExceptionHandler exceptionHandler = serverInitializer.ErrorHandler; Assert.True(exceptionHandler.Inactive.Wait(DefaultTimeout), "Handler inactive timed out"); diff --git a/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs b/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs index 772e3de2b..a3644787c 100644 --- a/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/ReadPendingTests.cs @@ -71,13 +71,13 @@ void ReadPendingIsResetAfterEachRead0(ServerBootstrap sb, Bootstrap cb) Assert.NotNull(this.clientChannel.LocalAddress); // 4 bytes means 2 read loops for TestNumReadsRecvByteBufAllocator - ValueTask writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[4])); - Assert.True(writeTask.AsTask().Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); + Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[4])); + Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Client write task timed out"); // 4 bytes means 2 read loops for TestNumReadsRecvByteBufAllocator Assert.True(serverInitializer.Initialize.Wait(DefaultTimeout), "Server initializer timed out"); writeTask = serverInitializer.Channel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[4])); - Assert.True(writeTask.AsTask().Wait(TimeSpan.FromSeconds(5)), "Server write task timed out"); + Assert.True(writeTask.Wait(TimeSpan.FromSeconds(5)), "Server write task timed out"); serverInitializer.Channel.Read(); serverInitializer.ReadPendingHandler.AssertAllRead(); diff --git a/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs b/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs index 539034b3b..8c91a0913 100644 --- a/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs +++ b/test/DotNetty.Transport.Libuv.Tests/WriteBeforeRegisteredTests.cs @@ -42,7 +42,7 @@ void WriteBeforeConnect0(Bootstrap cb) this.clientChannel = task.Result; Task connectTask = this.clientChannel.ConnectAsync(LoopbackAnyPort); - Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[] { 1 })).AsTask(); + Task writeTask = this.clientChannel.WriteAndFlushAsync(Unpooled.WrappedBuffer(new byte[] { 1 })); var error = Assert.Throws(() => writeTask.Wait(DefaultTimeout)); Assert.Single(error.InnerExceptions); diff --git a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs index 4f72cc801..840c8ab33 100644 --- a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs +++ b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelMulticastTest.cs @@ -155,7 +155,7 @@ public void Multicast(AddressFamily addressFamily, IByteBufferAllocator allocato Assert.True(joinTask.Wait(TimeSpan.FromMilliseconds(DefaultTimeOutInMilliseconds * 5)), $"Multicast server join group {groupAddress} timed out!"); - clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)).AsTask().Wait(); + clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)).Wait(); Assert.True(multicastHandler.WaitForResult(), "Multicast server should have receivied the message."); Task leaveTask = serverChannel.LeaveGroup(groupAddress, loopback); @@ -166,7 +166,7 @@ public void Multicast(AddressFamily addressFamily, IByteBufferAllocator allocato Task.Delay(DefaultTimeOutInMilliseconds).Wait(); // we should not receive a message anymore as we left the group before - clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)).AsTask().Wait(); + clientChannel.WriteAndFlushAsync(new DatagramPacket(Unpooled.Buffer().WriteInt(1), groupAddress)).Wait(); Assert.False(multicastHandler.WaitForResult(), "Multicast server should not receive the message."); } finally diff --git a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs index 4f6ea9710..908a03c15 100644 --- a/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs +++ b/test/DotNetty.Transport.Tests/Channel/Sockets/SocketDatagramChannelUnicastTest.cs @@ -217,12 +217,12 @@ public void SimpleSend(IByteBuffer source, bool bindClient, IByteBufferAllocator for (int i = 0; i < count; i++) { var packet = new DatagramPacket((IByteBuffer)source.Retain(), new IPEndPoint(address, endPoint.Port)); - clientChannel.WriteAndFlushAsync(packet).AsTask().Wait(); + clientChannel.WriteAndFlushAsync(packet).Wait(); Assert.True(handler.WaitForResult()); var duplicatedPacket = (DatagramPacket)packet.Duplicate(); duplicatedPacket.Retain(); - clientChannel.WriteAndFlushAsync(duplicatedPacket).AsTask().Wait(); + clientChannel.WriteAndFlushAsync(duplicatedPacket).Wait(); Assert.True(handler.WaitForResult()); } } From 78ec006e88322a1505c04d2c8f879d14400cac91 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Thu, 12 Apr 2018 14:30:39 -0700 Subject: [PATCH 22/29] remove nuget config --- .nuget/NuGet.Config | 9 --------- build.cake | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 .nuget/NuGet.Config diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config deleted file mode 100644 index d0b0427aa..000000000 --- a/.nuget/NuGet.Config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/build.cake b/build.cake index 4314f00cd..f8528a2fa 100644 --- a/build.cake +++ b/build.cake @@ -50,7 +50,7 @@ Task("Restore-NuGet-Packages") .Description("Restores dependencies") .Does(() => { - DotNetCoreRestore(new DotNetCoreRestoreSettings { ConfigFile = ".nuget\\nuget.config" }); + DotNetCoreRestore(); int result = StartProcess("dotnet", new ProcessSettings { Arguments = "restore -r win-x64" } ); if (result != 0) From fc447d78c5fff765112c7c25a719169e147cce30 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Tue, 5 Jun 2018 11:05:15 -0700 Subject: [PATCH 23/29] upgrade packages to stable version --- src/DotNetty.Codecs/DotNetty.Codecs.csproj | 2 +- src/DotNetty.Handlers/DotNetty.Handlers.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetty.Codecs/DotNetty.Codecs.csproj b/src/DotNetty.Codecs/DotNetty.Codecs.csproj index 9ddecdefa..cc40985af 100644 --- a/src/DotNetty.Codecs/DotNetty.Codecs.csproj +++ b/src/DotNetty.Codecs/DotNetty.Codecs.csproj @@ -46,6 +46,6 @@ - + \ No newline at end of file diff --git a/src/DotNetty.Handlers/DotNetty.Handlers.csproj b/src/DotNetty.Handlers/DotNetty.Handlers.csproj index 9eb460600..c138869f0 100644 --- a/src/DotNetty.Handlers/DotNetty.Handlers.csproj +++ b/src/DotNetty.Handlers/DotNetty.Handlers.csproj @@ -44,6 +44,6 @@ - + \ No newline at end of file From 03e7b1d3d7ca6bb80f69ac8b4daadef42bfaa4b1 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Tue, 9 Oct 2018 16:18:35 -0700 Subject: [PATCH 24/29] ws fixes --- .../WebSocketClientExtensionHandler.cs | 2 +- .../WebSocketServerExtensionHandler.cs | 57 ++++++++++--------- .../HttpServerUpgradeHandlerTest.cs | 28 +++++---- .../WebSocketServerProtocolHandlerTest.cs | 4 +- 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketClientExtensionHandler.cs b/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketClientExtensionHandler.cs index 5a72c6d1c..922ce7756 100644 --- a/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketClientExtensionHandler.cs +++ b/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketClientExtensionHandler.cs @@ -19,7 +19,7 @@ public WebSocketClientExtensionHandler(params IWebSocketClientExtensionHandshake this.extensionHandshakers = new List(extensionHandshakers); } - public override Task WriteAsync(IChannelHandlerContext ctx, object msg) + public override ValueTask WriteAsync(IChannelHandlerContext ctx, object msg) { if (msg is IHttpRequest request && WebSocketExtensionUtil.IsWebsocketUpgrade(request.Headers)) { diff --git a/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketServerExtensionHandler.cs b/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketServerExtensionHandler.cs index e0b07e36b..681b04316 100644 --- a/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketServerExtensionHandler.cs +++ b/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketServerExtensionHandler.cs @@ -15,12 +15,13 @@ public class WebSocketServerExtensionHandler : ChannelHandlerAdapter readonly List extensionHandshakers; List validExtensions; + Action upgradeCompletedContinuation; public WebSocketServerExtensionHandler(params IWebSocketServerExtensionHandshaker[] extensionHandshakers) { Contract.Requires(extensionHandshakers != null && extensionHandshakers.Length > 0); - this.extensionHandshakers = new List(extensionHandshakers); + this.upgradeCompletedContinuation = this.OnUpgradeCompleted; } public override void ChannelRead(IChannelHandlerContext ctx, object msg) @@ -67,16 +68,16 @@ public override void ChannelRead(IChannelHandlerContext ctx, object msg) base.ChannelRead(ctx, msg); } - public override Task WriteAsync(IChannelHandlerContext ctx, object msg) + public override ValueTask WriteAsync(IChannelHandlerContext ctx, object msg) { - Action continuationAction = null; - + HttpHeaders responseHeaders; + string headerValue = null; + if (msg is IHttpResponse response - && WebSocketExtensionUtil.IsWebsocketUpgrade(response.Headers) + && WebSocketExtensionUtil.IsWebsocketUpgrade(responseHeaders = response.Headers) && this.validExtensions != null) { - string headerValue = null; - if (response.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value)) + if (responseHeaders.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value)) { headerValue = value?.ToString(); } @@ -88,31 +89,33 @@ public override Task WriteAsync(IChannelHandlerContext ctx, object msg) extensionData.Name, extensionData.Parameters); } - continuationAction = promise => - { - if (promise.Status == TaskStatus.RanToCompletion) - { - foreach (IWebSocketServerExtension extension in this.validExtensions) - { - WebSocketExtensionDecoder decoder = extension.NewExtensionDecoder(); - WebSocketExtensionEncoder encoder = extension.NewExtensionEncoder(); - ctx.Channel.Pipeline.AddAfter(ctx.Name, decoder.GetType().Name, decoder); - ctx.Channel.Pipeline.AddAfter(ctx.Name, encoder.GetType().Name, encoder); - } - } - ctx.Channel.Pipeline.Remove(ctx.Name); - }; - if (headerValue != null) { - response.Headers.Set(HttpHeaderNames.SecWebsocketExtensions, headerValue); + responseHeaders.Set(HttpHeaderNames.SecWebsocketExtensions, headerValue); } + + Task task = base.WriteAsync(ctx, msg).AsTask(); + task.ContinueWith(this.upgradeCompletedContinuation, ctx); + return new ValueTask(task); } - return continuationAction == null - ? base.WriteAsync(ctx, msg) - : base.WriteAsync(ctx, msg) - .ContinueWith(continuationAction, TaskContinuationOptions.ExecuteSynchronously); + return base.WriteAsync(ctx, msg); + } + + void OnUpgradeCompleted(Task task, object state) + { + var ctx = (IChannelHandlerContext)state; + if (task.Status == TaskStatus.RanToCompletion) + { + foreach (IWebSocketServerExtension extension in this.validExtensions) + { + WebSocketExtensionDecoder decoder = extension.NewExtensionDecoder(); + WebSocketExtensionEncoder encoder = extension.NewExtensionEncoder(); + ctx.Channel.Pipeline.AddAfter(ctx.Name, decoder.GetType().Name, decoder); + ctx.Channel.Pipeline.AddAfter(ctx.Name, encoder.GetType().Name, encoder); + } + } + ctx.Channel.Pipeline.Remove(ctx.Name); } } } diff --git a/test/DotNetty.Codecs.Http.Tests/HttpServerUpgradeHandlerTest.cs b/test/DotNetty.Codecs.Http.Tests/HttpServerUpgradeHandlerTest.cs index cb8f3a6a2..90fd2edb3 100644 --- a/test/DotNetty.Codecs.Http.Tests/HttpServerUpgradeHandlerTest.cs +++ b/test/DotNetty.Codecs.Http.Tests/HttpServerUpgradeHandlerTest.cs @@ -68,7 +68,7 @@ public override void ChannelRead(IChannelHandlerContext ctx, object msg) } } - public override Task WriteAsync(IChannelHandlerContext ctx, object msg) + public override ValueTask WriteAsync(IChannelHandlerContext ctx, object msg) { // We ensure that we're in the read call and defer the write so we can // make sure the pipeline was reformed irrespective of the flush completing. @@ -76,22 +76,20 @@ public override Task WriteAsync(IChannelHandlerContext ctx, object msg) this.writeUpgradeMessage = true; var completion = new TaskCompletionSource(); - ctx.Channel.EventLoop.Execute(() => + ctx.Channel.EventLoop.Execute(async () => { - ctx.WriteAsync(msg) - .ContinueWith(t => - { - if (t.Status == TaskStatus.RanToCompletion) - { - this.writeFlushed = true; - completion.TryComplete(); - return; - } - completion.TrySetException(new InvalidOperationException($"Invalid WriteAsync task status {t.Status}")); - }, - TaskContinuationOptions.ExecuteSynchronously); + try + { + await ctx.WriteAsync(msg); + this.writeFlushed = true; + completion.TryComplete(); + } + catch(Exception ex) + { + completion.TrySetException(ex); + } }); - return completion.Task; + return new ValueTask(completion.Task); } } diff --git a/test/DotNetty.Codecs.Http.Tests/WebSockets/WebSocketServerProtocolHandlerTest.cs b/test/DotNetty.Codecs.Http.Tests/WebSockets/WebSocketServerProtocolHandlerTest.cs index 9fe1705a3..341d1dd27 100644 --- a/test/DotNetty.Codecs.Http.Tests/WebSockets/WebSocketServerProtocolHandlerTest.cs +++ b/test/DotNetty.Codecs.Http.Tests/WebSockets/WebSocketServerProtocolHandlerTest.cs @@ -140,10 +140,10 @@ public MockOutboundHandler(WebSocketServerProtocolHandlerTest owner) this.owner = owner; } - public override Task WriteAsync(IChannelHandlerContext ctx, object msg) + public override ValueTask WriteAsync(IChannelHandlerContext ctx, object msg) { this.owner.responses.Enqueue((IFullHttpResponse)msg); - return TaskEx.Completed; + return new ValueTask(); } public override void Flush(IChannelHandlerContext ctx) From 5fab1492627514641f3412438f58a9dcd31da128 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Tue, 9 Oct 2018 22:20:07 -0700 Subject: [PATCH 25/29] bump up package versions --- src/DotNetty.Codecs/DotNetty.Codecs.csproj | 2 +- src/DotNetty.Common/DotNetty.Common.csproj | 2 +- src/DotNetty.Handlers/DotNetty.Handlers.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DotNetty.Codecs/DotNetty.Codecs.csproj b/src/DotNetty.Codecs/DotNetty.Codecs.csproj index cc40985af..ef53cc113 100644 --- a/src/DotNetty.Codecs/DotNetty.Codecs.csproj +++ b/src/DotNetty.Codecs/DotNetty.Codecs.csproj @@ -46,6 +46,6 @@ - + \ No newline at end of file diff --git a/src/DotNetty.Common/DotNetty.Common.csproj b/src/DotNetty.Common/DotNetty.Common.csproj index b871a6cdf..d3539fb00 100644 --- a/src/DotNetty.Common/DotNetty.Common.csproj +++ b/src/DotNetty.Common/DotNetty.Common.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/DotNetty.Handlers/DotNetty.Handlers.csproj b/src/DotNetty.Handlers/DotNetty.Handlers.csproj index c138869f0..37ad674b2 100644 --- a/src/DotNetty.Handlers/DotNetty.Handlers.csproj +++ b/src/DotNetty.Handlers/DotNetty.Handlers.csproj @@ -44,6 +44,6 @@ - + \ No newline at end of file From 6c99f3aff314ad2e98cc109b576c7857d769e568 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Wed, 10 Oct 2018 00:52:08 -0700 Subject: [PATCH 26/29] http test fixes --- .../DotNetty.Codecs.Http.csproj | 2 +- .../HttpServerUpgradeHandler.cs | 50 ++++++++----------- .../WebSocketServerExtensionHandler.cs | 2 +- src/DotNetty.Codecs/TaskExtensions.cs | 3 ++ src/DotNetty.Common/DotNetty.Common.csproj | 2 +- .../HttpServerUpgradeHandlerTest.cs | 10 ++-- 6 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj b/src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj index 50d94aa4c..5cec59de0 100644 --- a/src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj +++ b/src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/DotNetty.Codecs.Http/HttpServerUpgradeHandler.cs b/src/DotNetty.Codecs.Http/HttpServerUpgradeHandler.cs index d47663731..3eee5b769 100644 --- a/src/DotNetty.Codecs.Http/HttpServerUpgradeHandler.cs +++ b/src/DotNetty.Codecs.Http/HttpServerUpgradeHandler.cs @@ -254,34 +254,28 @@ bool Upgrade(IChannelHandlerContext ctx, IFullHttpRequest request) var upgradeEvent = new UpgradeEvent(upgradeProtocol, request); IUpgradeCodec finalUpgradeCodec = upgradeCodec; - ctx.WriteAndFlushAsync(upgradeResponse).ContinueWith(t => - { - try - { - if (t.Status == TaskStatus.RanToCompletion) - { - // Perform the upgrade to the new protocol. - this.sourceCodec.UpgradeFrom(ctx); - finalUpgradeCodec.UpgradeTo(ctx, request); - - // Notify that the upgrade has occurred. Retain the event to offset - // the release() in the finally block. - ctx.FireUserEventTriggered(upgradeEvent.Retain()); - - // Remove this handler from the pipeline. - ctx.Channel.Pipeline.Remove(this); - } - else - { - ctx.Channel.CloseAsync(); - } - } - finally - { - // Release the event if the upgrade event wasn't fired. - upgradeEvent.Release(); - } - }, TaskContinuationOptions.ExecuteSynchronously); + try + { + Task writeTask = ctx.WriteAndFlushAsync(upgradeResponse); + + // Perform the upgrade to the new protocol. + this.sourceCodec.UpgradeFrom(ctx); + finalUpgradeCodec.UpgradeTo(ctx, request); + + // Remove this handler from the pipeline. + ctx.Channel.Pipeline.Remove(this); + + // Notify that the upgrade has occurred. Retain the event to offset + // the release() in the finally block. + ctx.FireUserEventTriggered(upgradeEvent.Retain()); + + writeTask.CloseOnFailure(ctx.Channel); + } + finally + { + // Release the event if the upgrade event wasn't fired. + upgradeEvent.Release(); + } return true; } diff --git a/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketServerExtensionHandler.cs b/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketServerExtensionHandler.cs index 681b04316..6d6b0c422 100644 --- a/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketServerExtensionHandler.cs +++ b/src/DotNetty.Codecs.Http/WebSockets/Extensions/WebSocketServerExtensionHandler.cs @@ -95,7 +95,7 @@ public override ValueTask WriteAsync(IChannelHandlerContext ctx, object msg) } Task task = base.WriteAsync(ctx, msg).AsTask(); - task.ContinueWith(this.upgradeCompletedContinuation, ctx); + task.ContinueWith(this.upgradeCompletedContinuation, ctx, TaskContinuationOptions.ExecuteSynchronously); return new ValueTask(task); } diff --git a/src/DotNetty.Codecs/TaskExtensions.cs b/src/DotNetty.Codecs/TaskExtensions.cs index b5af165d0..d8356c148 100644 --- a/src/DotNetty.Codecs/TaskExtensions.cs +++ b/src/DotNetty.Codecs/TaskExtensions.cs @@ -33,6 +33,9 @@ public static Task CloseOnComplete(this Task task, IChannel channel) public static Task CloseOnFailure(this Task task, IChannelHandlerContext ctx) => task.ContinueWith(CloseOnFailureContinuation, ctx, TaskContinuationOptions.ExecuteSynchronously); + + public static Task CloseOnFailure(this Task task, IChannel channel) + => task.ContinueWith(CloseOnFailureContinuation, channel, TaskContinuationOptions.ExecuteSynchronously); static Task Close(Task task, object state) { diff --git a/src/DotNetty.Common/DotNetty.Common.csproj b/src/DotNetty.Common/DotNetty.Common.csproj index d3539fb00..6bf88c2e1 100644 --- a/src/DotNetty.Common/DotNetty.Common.csproj +++ b/src/DotNetty.Common/DotNetty.Common.csproj @@ -31,7 +31,7 @@ - + diff --git a/test/DotNetty.Codecs.Http.Tests/HttpServerUpgradeHandlerTest.cs b/test/DotNetty.Codecs.Http.Tests/HttpServerUpgradeHandlerTest.cs index 90fd2edb3..dda9c5186 100644 --- a/test/DotNetty.Codecs.Http.Tests/HttpServerUpgradeHandlerTest.cs +++ b/test/DotNetty.Codecs.Http.Tests/HttpServerUpgradeHandlerTest.cs @@ -59,8 +59,8 @@ public override void ChannelRead(IChannelHandlerContext ctx, object msg) // written the upgrade response, and upgraded the pipeline. Assert.True(this.writeUpgradeMessage); Assert.False(this.writeFlushed); - //Assert.Null(ctx.Channel.Pipeline.Get()); - //Assert.NotNull(ctx.Channel.Pipeline.Get("marker")); + Assert.Null(ctx.Channel.Pipeline.Get()); + Assert.NotNull(ctx.Channel.Pipeline.Get("marker")); } finally { @@ -111,13 +111,11 @@ public void UpgradesPipelineInSameMethodInvocation() IByteBuffer upgrade = Unpooled.CopiedBuffer(Encoding.ASCII.GetBytes(UpgradeString)); Assert.False(channel.WriteInbound(upgrade)); - //Assert.Null(channel.Pipeline.Get()); - //Assert.NotNull(channel.Pipeline.Get("marker")); - - channel.Flush(); Assert.Null(channel.Pipeline.Get()); Assert.NotNull(channel.Pipeline.Get("marker")); + channel.Flush(); + var upgradeMessage = channel.ReadOutbound(); const string ExpectedHttpResponse = "HTTP/1.1 101 Switching Protocols\r\n" + "connection: upgrade\r\n" + From 05e4aa6b0b7b922fff12b22dd6addd6e4df845c6 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Wed, 10 Oct 2018 01:11:36 -0700 Subject: [PATCH 27/29] fix keep alive implementation --- src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs b/src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs index cbec7c65c..aae9166bc 100644 --- a/src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs +++ b/src/DotNetty.Codecs.Http/HttpServerKeepAliveHandler.cs @@ -52,7 +52,9 @@ public override ValueTask WriteAsync(IChannelHandlerContext context, object mess } if (message is ILastHttpContent && !this.ShouldKeepAlive()) { - return new ValueTask(base.WriteAsync(context, message).CloseOnComplete(context)); + Task task = base.WriteAsync(context, message).AsTask(); + task.CloseOnComplete(context.Channel); + return new ValueTask(task); } return base.WriteAsync(context, message); } From 7f08e1db9bd2ebbdc9cd73a95e1ac6599a273d54 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Wed, 10 Oct 2018 14:47:03 -0700 Subject: [PATCH 28/29] disable configureawait --- src/DotNetty.Common/Utilities/TaskEx.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetty.Common/Utilities/TaskEx.cs b/src/DotNetty.Common/Utilities/TaskEx.cs index f3c29541a..96b097125 100644 --- a/src/DotNetty.Common/Utilities/TaskEx.cs +++ b/src/DotNetty.Common/Utilities/TaskEx.cs @@ -123,8 +123,8 @@ public static async void LinkOutcome(this ValueTask future, IPromise promise) { try { - //context capturing not required since callback executed synchrounusly on completion in eventloop - await future.ConfigureAwait(false); + //context capturing not required since callback executed synchronously on completion in eventloop + await future; promise.TryComplete(); } catch (Exception ex) From c1c9865e7ec22214278cb74f423297c4b50412d8 Mon Sep 17 00:00:00 2001 From: Maxim Kim Date: Wed, 10 Oct 2018 15:07:54 -0700 Subject: [PATCH 29/29] package version fixes --- src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj | 2 +- src/DotNetty.Common/DotNetty.Common.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj b/src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj index 5cec59de0..50d94aa4c 100644 --- a/src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj +++ b/src/DotNetty.Codecs.Http/DotNetty.Codecs.Http.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/DotNetty.Common/DotNetty.Common.csproj b/src/DotNetty.Common/DotNetty.Common.csproj index 6bf88c2e1..d3539fb00 100644 --- a/src/DotNetty.Common/DotNetty.Common.csproj +++ b/src/DotNetty.Common/DotNetty.Common.csproj @@ -31,7 +31,7 @@ - +