diff --git a/src/DotNetty.Common/Concurrency/AbstractEventExecutor.cs b/src/DotNetty.Common/Concurrency/AbstractEventExecutor.cs index 9a0efe00d..36a2ddd2e 100644 --- a/src/DotNetty.Common/Concurrency/AbstractEventExecutor.cs +++ b/src/DotNetty.Common/Concurrency/AbstractEventExecutor.cs @@ -4,6 +4,7 @@ namespace DotNetty.Common.Concurrency { using System; + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using DotNetty.Common.Internal.Logging; @@ -46,6 +47,11 @@ protected AbstractEventExecutor(IEventExecutorGroup parent) /// public bool InEventLoop => this.IsInEventLoop(Thread.CurrentThread); + /// + public IEnumerable Items => this.GetItems(); + + protected abstract IEnumerable GetItems(); + /// public abstract bool IsInEventLoop(Thread thread); diff --git a/src/DotNetty.Common/Concurrency/AbstractEventExecutorGroup.cs b/src/DotNetty.Common/Concurrency/AbstractEventExecutorGroup.cs index c9e7653e5..5cbe1149d 100644 --- a/src/DotNetty.Common/Concurrency/AbstractEventExecutorGroup.cs +++ b/src/DotNetty.Common/Concurrency/AbstractEventExecutorGroup.cs @@ -4,6 +4,7 @@ namespace DotNetty.Common.Concurrency { using System; + using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -20,6 +21,8 @@ public abstract class AbstractEventExecutorGroup : IEventExecutorGroup public abstract Task TerminationCompletion { get; } + public IEnumerable Items => this.GetItems(); + public abstract IEventExecutor GetNext(); public void Execute(IRunnable task) => this.GetNext().Execute(task); @@ -65,5 +68,7 @@ public abstract class AbstractEventExecutorGroup : IEventExecutorGroup public Task ShutdownGracefullyAsync() => this.ShutdownGracefullyAsync(DefaultShutdownQuietPeriod, DefaultShutdownTimeout); public abstract Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan timeout); + + protected abstract IEnumerable GetItems(); } } \ No newline at end of file diff --git a/src/DotNetty.Common/Concurrency/IEventExecutorGroup.cs b/src/DotNetty.Common/Concurrency/IEventExecutorGroup.cs index 1d0af9c9c..091cbf966 100644 --- a/src/DotNetty.Common/Concurrency/IEventExecutorGroup.cs +++ b/src/DotNetty.Common/Concurrency/IEventExecutorGroup.cs @@ -4,6 +4,7 @@ namespace DotNetty.Common.Concurrency { using System; + using System.Collections.Generic; using System.Threading.Tasks; /// @@ -11,6 +12,11 @@ namespace DotNetty.Common.Concurrency /// public interface IEventExecutorGroup : IScheduledExecutorService { + /// + /// Returns list of owned event executors. + /// + IEnumerable Items { get; } + /// /// Returns true if and only if this executor is being shut down via . /// diff --git a/src/DotNetty.Common/Concurrency/SingleThreadEventExecutor.cs b/src/DotNetty.Common/Concurrency/SingleThreadEventExecutor.cs index 7cd814dc6..225871962 100644 --- a/src/DotNetty.Common/Concurrency/SingleThreadEventExecutor.cs +++ b/src/DotNetty.Common/Concurrency/SingleThreadEventExecutor.cs @@ -45,6 +45,7 @@ public class SingleThreadEventExecutor : AbstractScheduledEventExecutor PreciseTimeSpan gracefulShutdownQuietPeriod; PreciseTimeSpan gracefulShutdownTimeout; readonly ISet shutdownHooks = new HashSet(); + long progress; /// Creates a new instance of . public SingleThreadEventExecutor(string threadName, TimeSpan breakoutInterval) @@ -86,6 +87,21 @@ protected SingleThreadEventExecutor(IEventExecutorGroup parent, string threadNam /// public TaskScheduler Scheduler => this.scheduler; + /// + /// Allows to track whether executor is progressing through its backlog. Useful for diagnosing / mitigating stalls due to blocking calls in conjunction with IsBacklogEmpty property. + /// + public long Progress => Volatile.Read(ref this.progress); + + /// + /// Indicates whether executor's backlog is empty. Useful for diagnosing / mitigating stalls due to blocking calls in conjunction with Progress property. + /// + public bool IsBacklogEmpty => this.taskQueue.IsEmpty; + + /// + /// Gets length of backlog of tasks queued for immediate execution. + /// + public int BacklogLength => this.taskQueue.Count; + void Loop() { this.SetCurrentExecutor(this); @@ -140,6 +156,8 @@ public override void Execute(IRunnable task) } } + protected override IEnumerable GetItems() => new[] { this }; + protected void WakeUp(bool inEventLoop) { if (!inEventLoop || (this.executionState == ST_SHUTTING_DOWN)) @@ -152,12 +170,12 @@ protected void WakeUp(bool inEventLoop) /// Adds an which will be executed on shutdown of this instance. /// /// The to run on shutdown. - public void AddShutdownHook(Action action) + public void AddShutdownHook(Action action) { - if (this.InEventLoop) + if (this.InEventLoop) { this.shutdownHooks.Add(action); - } + } else { this.Execute(() => this.shutdownHooks.Add(action)); @@ -169,53 +187,53 @@ public void AddShutdownHook(Action action) /// executed on shutdown of this instance. /// /// The to remove. - public void RemoveShutdownHook(Action action) + public void RemoveShutdownHook(Action action) { - if (this.InEventLoop) + if (this.InEventLoop) { this.shutdownHooks.Remove(action); - } + } else { this.Execute(() => this.shutdownHooks.Remove(action)); } } - bool RunShutdownHooks() + bool RunShutdownHooks() { bool ran = false; - + // Note shutdown hooks can add / remove shutdown hooks. - while (this.shutdownHooks.Count > 0) + while (this.shutdownHooks.Count > 0) { var copy = this.shutdownHooks.ToArray(); this.shutdownHooks.Clear(); for (var i = 0; i < copy.Length; i++) { - try + try { copy[i](); - } - catch (Exception ex) + } + catch (Exception ex) { Logger.Warn("Shutdown hook raised an exception.", ex); - } - finally + } + finally { ran = true; } } } - if (ran) + if (ran) { this.lastExecutionTime = PreciseTimeSpan.FromStart; } return ran; } - + /// public override Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan timeout) @@ -398,6 +416,7 @@ protected bool RunAllTasks() while (true) { + Volatile.Write(ref this.progress, this.progress + 1); // volatile write is enough as this is the only thread ever writing SafeExecute(task); task = this.PollTask(); if (task == null) diff --git a/src/DotNetty.Transport.Libuv/DispatcherEventLoop.cs b/src/DotNetty.Transport.Libuv/DispatcherEventLoop.cs index d05550acf..788598694 100644 --- a/src/DotNetty.Transport.Libuv/DispatcherEventLoop.cs +++ b/src/DotNetty.Transport.Libuv/DispatcherEventLoop.cs @@ -4,6 +4,7 @@ namespace DotNetty.Transport.Libuv { using System; + using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.InteropServices; @@ -69,5 +70,7 @@ internal void Dispatch(NativeHandle handle) public Task RegisterAsync(IChannel channel) => channel.Unsafe.RegisterAsync(this); public new IEventLoopGroup Parent => (IEventLoopGroup)base.Parent; + + public new IEnumerable Items => new[] { this }; } } \ No newline at end of file diff --git a/src/DotNetty.Transport.Libuv/DispatcherEventLoopGroup.cs b/src/DotNetty.Transport.Libuv/DispatcherEventLoopGroup.cs index 646ad6b45..3b9010096 100644 --- a/src/DotNetty.Transport.Libuv/DispatcherEventLoopGroup.cs +++ b/src/DotNetty.Transport.Libuv/DispatcherEventLoopGroup.cs @@ -6,6 +6,7 @@ namespace DotNetty.Transport.Libuv { using System; + using System.Collections.Generic; using System.Threading.Tasks; using DotNetty.Common.Concurrency; using DotNetty.Transport.Channels; @@ -29,6 +30,10 @@ public DispatcherEventLoopGroup() internal DispatcherEventLoop Dispatcher => this.dispatcherEventLoop; + protected override IEnumerable GetItems() => new[] { this.dispatcherEventLoop }; + + public new IEnumerable Items => new[] { this.dispatcherEventLoop }; + IEventLoop IEventLoopGroup.GetNext() => (IEventLoop)this.GetNext(); public override IEventExecutor GetNext() => this.dispatcherEventLoop; diff --git a/src/DotNetty.Transport.Libuv/EventLoop.cs b/src/DotNetty.Transport.Libuv/EventLoop.cs index f3cade01d..fcced02e8 100644 --- a/src/DotNetty.Transport.Libuv/EventLoop.cs +++ b/src/DotNetty.Transport.Libuv/EventLoop.cs @@ -3,6 +3,7 @@ namespace DotNetty.Transport.Libuv { + using System.Collections.Generic; using System.Threading.Tasks; using DotNetty.Transport.Channels; @@ -19,5 +20,7 @@ public EventLoop(IEventLoopGroup parent, string threadName) public Task RegisterAsync(IChannel channel) => channel.Unsafe.RegisterAsync(this); public new IEventLoopGroup Parent => (IEventLoopGroup)base.Parent; + + public new IEnumerable Items => new[] { this }; } } \ No newline at end of file diff --git a/src/DotNetty.Transport.Libuv/EventLoopGroup.cs b/src/DotNetty.Transport.Libuv/EventLoopGroup.cs index 1df143da1..d58e8bded 100644 --- a/src/DotNetty.Transport.Libuv/EventLoopGroup.cs +++ b/src/DotNetty.Transport.Libuv/EventLoopGroup.cs @@ -6,6 +6,7 @@ namespace DotNetty.Transport.Libuv { using System; + using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -27,6 +28,8 @@ public sealed class EventLoopGroup : AbstractEventExecutorGroup, IEventLoopGroup public override Task TerminationCompletion { get; } + public new IEnumerable Items => this.eventLoops; + public EventLoopGroup() : this(DefaultEventLoopCount) { @@ -119,5 +122,7 @@ public override Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan time } return this.TerminationCompletion; } + + protected override IEnumerable GetItems() => this.eventLoops; } } \ No newline at end of file diff --git a/src/DotNetty.Transport.Libuv/LoopExecutor.cs b/src/DotNetty.Transport.Libuv/LoopExecutor.cs index 6f4c0f410..373624c37 100644 --- a/src/DotNetty.Transport.Libuv/LoopExecutor.cs +++ b/src/DotNetty.Transport.Libuv/LoopExecutor.cs @@ -7,6 +7,7 @@ namespace DotNetty.Transport.Libuv { using System; + using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Threading.Tasks; @@ -17,7 +18,6 @@ namespace DotNetty.Transport.Libuv using System.Threading; using DotNetty.Common; using DotNetty.Transport.Libuv.Native; - using Timer = Native.Timer; class LoopExecutor : AbstractScheduledEventExecutor @@ -297,7 +297,7 @@ void RunAllTasks(long timeout) long runTasks = 0; long executionTime; this.wakeUp = false; - for (;;) + for (; ; ) { SafeExecute(task); @@ -402,7 +402,7 @@ static bool RunAllTasksFrom(IQueue taskQueue) { return false; } - for (;;) + for (; ; ) { SafeExecute(task); task = PollTaskFrom(taskQueue); @@ -488,7 +488,7 @@ public override Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan time bool inEventLoop = this.InEventLoop; bool wakeUpLoop; int oldState; - for (;;) + for (; ; ) { if (this.IsShuttingDown) { @@ -540,5 +540,7 @@ public override Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan time return this.TerminationCompletion; } + + protected override IEnumerable GetItems() => new[] { this }; } } diff --git a/src/DotNetty.Transport.Libuv/WorkerEventLoop.cs b/src/DotNetty.Transport.Libuv/WorkerEventLoop.cs index df0f8b408..214818460 100644 --- a/src/DotNetty.Transport.Libuv/WorkerEventLoop.cs +++ b/src/DotNetty.Transport.Libuv/WorkerEventLoop.cs @@ -7,6 +7,7 @@ namespace DotNetty.Transport.Libuv { using System; + using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Threading.Tasks; @@ -112,6 +113,8 @@ void OnRead(Pipe handle, int status) public new IEventLoopGroup Parent => (IEventLoopGroup)base.Parent; + IEnumerable IEventLoopGroup.Items => new[] { this }; + sealed class PipeConnect : ConnectRequest { const int MaximumRetryCount = 10; diff --git a/src/DotNetty.Transport.Libuv/WorkerEventLoopGroup.cs b/src/DotNetty.Transport.Libuv/WorkerEventLoopGroup.cs index 87ac1c1df..a8986ca17 100644 --- a/src/DotNetty.Transport.Libuv/WorkerEventLoopGroup.cs +++ b/src/DotNetty.Transport.Libuv/WorkerEventLoopGroup.cs @@ -6,6 +6,7 @@ namespace DotNetty.Transport.Libuv { using System; + using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Linq; @@ -84,6 +85,8 @@ public WorkerEventLoopGroup(DispatcherEventLoopGroup eventLoopGroup, int eventLo internal string PipeName { get; } + IEnumerable IEventLoopGroup.Items => this.eventLoops; + internal void Accept(NativeHandle handle) { Debug.Assert(this.dispatcherLoop != null); @@ -126,5 +129,7 @@ public override Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan time } return this.TerminationCompletion; } + + protected override IEnumerable GetItems() => this.eventLoops; } } \ No newline at end of file diff --git a/src/DotNetty.Transport/Channels/AffinitizedEventLoopGroup.cs b/src/DotNetty.Transport/Channels/AffinitizedEventLoopGroup.cs index 25d487f3b..fa72871f2 100644 --- a/src/DotNetty.Transport/Channels/AffinitizedEventLoopGroup.cs +++ b/src/DotNetty.Transport/Channels/AffinitizedEventLoopGroup.cs @@ -4,6 +4,7 @@ namespace DotNetty.Transport.Channels { using System; + using System.Collections.Generic; using System.Threading.Tasks; using DotNetty.Common.Concurrency; @@ -23,6 +24,10 @@ public class AffinitizedEventLoopGroup : AbstractEventExecutorGroup, IEventLoopG /// public override Task TerminationCompletion => this.innerGroup.TerminationCompletion; + protected override IEnumerable GetItems() => this.innerGroup.Items; + + public new IEnumerable Items => ((IEventLoopGroup)this.innerGroup).Items; + /// /// Creates a new instance of . /// diff --git a/src/DotNetty.Transport/Channels/Embedded/EmbeddedEventLoop.cs b/src/DotNetty.Transport/Channels/Embedded/EmbeddedEventLoop.cs index 9419861e5..6e5b0f557 100644 --- a/src/DotNetty.Transport/Channels/Embedded/EmbeddedEventLoop.cs +++ b/src/DotNetty.Transport/Channels/Embedded/EmbeddedEventLoop.cs @@ -28,6 +28,10 @@ sealed class EmbeddedEventLoop : AbstractScheduledEventExecutor, IEventLoop public new IEventLoopGroup Parent => (IEventLoopGroup)base.Parent; + protected override IEnumerable GetItems() => new[] { this }; + + public new IEnumerable Items => new[] { this }; + public override bool IsInEventLoop(Thread thread) => true; public override void Execute(IRunnable command) @@ -48,7 +52,7 @@ public override Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan time internal void RunTasks() { - for (;;) + for (; ; ) { // have to perform an additional check since Queue throws upon empty dequeue in .NET if (this.tasks.Count == 0) @@ -67,7 +71,7 @@ internal void RunTasks() internal PreciseTimeSpan RunScheduledTasks() { PreciseTimeSpan time = GetNanos(); - for (;;) + for (; ; ) { IRunnable task = this.PollScheduledTask(time); if (task == null) diff --git a/src/DotNetty.Transport/Channels/IEventLoopGroup.cs b/src/DotNetty.Transport/Channels/IEventLoopGroup.cs index 43e8a2865..cdc5b99dd 100644 --- a/src/DotNetty.Transport/Channels/IEventLoopGroup.cs +++ b/src/DotNetty.Transport/Channels/IEventLoopGroup.cs @@ -3,6 +3,7 @@ namespace DotNetty.Transport.Channels { + using System.Collections.Generic; using System.Threading.Tasks; using DotNetty.Common.Concurrency; @@ -13,7 +14,12 @@ namespace DotNetty.Transport.Channels public interface IEventLoopGroup : IEventExecutorGroup { /// - /// Returns . + /// Returns list of owned event loops. + /// + new IEnumerable Items { get; } + + /// + /// Returns one of owned event loops. /// new IEventLoop GetNext(); diff --git a/src/DotNetty.Transport/Channels/MultithreadEventLoopGroup.cs b/src/DotNetty.Transport/Channels/MultithreadEventLoopGroup.cs index 4ef18c90d..24b9b6d5f 100644 --- a/src/DotNetty.Transport/Channels/MultithreadEventLoopGroup.cs +++ b/src/DotNetty.Transport/Channels/MultithreadEventLoopGroup.cs @@ -4,6 +4,7 @@ namespace DotNetty.Transport.Channels { using System; + using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -29,6 +30,12 @@ public sealed class MultithreadEventLoopGroup : AbstractEventExecutorGroup, IEve /// public override Task TerminationCompletion { get; } + /// + protected override IEnumerable GetItems() => this.eventLoops; + + /// + public new IEnumerable Items => this.eventLoops; + /// Creates a new instance of . public MultithreadEventLoopGroup() : this(DefaultEventLoopFactory, DefaultEventLoopThreadCount) diff --git a/src/DotNetty.Transport/Channels/SingleThreadEventLoop.cs b/src/DotNetty.Transport/Channels/SingleThreadEventLoop.cs index 360b1527b..ddf2c25df 100644 --- a/src/DotNetty.Transport/Channels/SingleThreadEventLoop.cs +++ b/src/DotNetty.Transport/Channels/SingleThreadEventLoop.cs @@ -4,6 +4,7 @@ namespace DotNetty.Transport.Channels { using System; + using System.Collections.Generic; using System.Threading.Tasks; using DotNetty.Common.Concurrency; using DotNetty.Common.Internal; @@ -70,5 +71,7 @@ protected SingleThreadEventLoop(IEventLoopGroup parent, string threadName, TimeS /// public new IEventLoopGroup Parent => (IEventLoopGroup)base.Parent; + + public new IEnumerable Items => new[] { this }; } } \ No newline at end of file