Commit 8349c04f authored by mgravell's avatar mgravell

add option to use the thread-pool for scheduling

parent 6b20e13f
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
...@@ -768,7 +769,7 @@ private bool PushToBacklog(Message message, bool onlyIfExists) ...@@ -768,7 +769,7 @@ private bool PushToBacklog(Message message, bool onlyIfExists)
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void StartBacklogProcessor() private void StartBacklogProcessor()
{ {
var sched = Multiplexer.SocketManager?.SchedulerPool ?? DedicatedThreadPoolPipeScheduler.Default; var sched = Multiplexer.SocketManager?.Scheduler ?? PipeScheduler.ThreadPool;
#if DEBUG #if DEBUG
_backlogProcessorRequestedTime = Environment.TickCount; _backlogProcessorRequestedTime = Environment.TickCount;
#endif #endif
......
...@@ -24,7 +24,7 @@ public sealed partial class SocketManager : IDisposable ...@@ -24,7 +24,7 @@ public sealed partial class SocketManager : IDisposable
/// </summary> /// </summary>
/// <param name="name">The name for this <see cref="SocketManager"/>.</param> /// <param name="name">The name for this <see cref="SocketManager"/>.</param>
public SocketManager(string name) public SocketManager(string name)
: this(name, DEFAULT_WORKERS, false) { } : this(name, DEFAULT_WORKERS, SocketManagerOptions.None) { }
/// <summary> /// <summary>
/// Creates a new <see cref="SocketManager"/> instance /// Creates a new <see cref="SocketManager"/> instance
...@@ -32,7 +32,7 @@ public SocketManager(string name) ...@@ -32,7 +32,7 @@ public SocketManager(string name)
/// <param name="name">The name for this <see cref="SocketManager"/>.</param> /// <param name="name">The name for this <see cref="SocketManager"/>.</param>
/// <param name="useHighPrioritySocketThreads">Whether this <see cref="SocketManager"/> should use high priority sockets.</param> /// <param name="useHighPrioritySocketThreads">Whether this <see cref="SocketManager"/> should use high priority sockets.</param>
public SocketManager(string name, bool useHighPrioritySocketThreads) public SocketManager(string name, bool useHighPrioritySocketThreads)
: this(name, DEFAULT_WORKERS, useHighPrioritySocketThreads) { } : this(name, DEFAULT_WORKERS, UseHighPrioritySocketThreads(useHighPrioritySocketThreads)) { }
/// <summary> /// <summary>
/// Creates a new (optionally named) <see cref="SocketManager"/> instance /// Creates a new (optionally named) <see cref="SocketManager"/> instance
...@@ -40,11 +40,45 @@ public SocketManager(string name, bool useHighPrioritySocketThreads) ...@@ -40,11 +40,45 @@ public SocketManager(string name, bool useHighPrioritySocketThreads)
/// <param name="name">The name for this <see cref="SocketManager"/>.</param> /// <param name="name">The name for this <see cref="SocketManager"/>.</param>
/// <param name="workerCount">the number of dedicated workers for this <see cref="SocketManager"/>.</param> /// <param name="workerCount">the number of dedicated workers for this <see cref="SocketManager"/>.</param>
/// <param name="useHighPrioritySocketThreads">Whether this <see cref="SocketManager"/> should use high priority sockets.</param> /// <param name="useHighPrioritySocketThreads">Whether this <see cref="SocketManager"/> should use high priority sockets.</param>
public SocketManager(string name = null, int workerCount = 0, bool useHighPrioritySocketThreads = false) public SocketManager(string name, int workerCount, bool useHighPrioritySocketThreads)
: this(name, workerCount, UseHighPrioritySocketThreads(useHighPrioritySocketThreads)) {}
private static SocketManagerOptions UseHighPrioritySocketThreads(bool value)
=> value ? SocketManagerOptions.UseHighPrioritySocketThreads : SocketManagerOptions.None;
/// <summary>
/// Additional options for configuring the socket manager
/// </summary>
[Flags]
public enum SocketManagerOptions
{
/// <summary>
/// No additional options
/// </summary>
None = 0,
/// <summary>
/// Whether the <see cref="SocketManager"/> should use high priority sockets.
/// </summary>
UseHighPrioritySocketThreads = 1 << 0,
/// <summary>
/// Use the regular thread-pool for all scheduling
/// </summary>
UseThreadPool = 1 << 1,
}
/// <summary>
/// Creates a new (optionally named) <see cref="SocketManager"/> instance
/// </summary>
/// <param name="name">The name for this <see cref="SocketManager"/>.</param>
/// <param name="workerCount">the number of dedicated workers for this <see cref="SocketManager"/>.</param>
/// <param name="options"></param>
public SocketManager(string name = null, int workerCount = 0, SocketManagerOptions options = SocketManagerOptions.None)
{ {
if (string.IsNullOrWhiteSpace(name)) name = GetType().Name; if (string.IsNullOrWhiteSpace(name)) name = GetType().Name;
if (workerCount <= 0) workerCount = DEFAULT_WORKERS; if (workerCount <= 0) workerCount = DEFAULT_WORKERS;
Name = name; Name = name;
bool useHighPrioritySocketThreads = (options & SocketManagerOptions.UseHighPrioritySocketThreads) != 0,
useThreadPool = (options & SocketManagerOptions.UseThreadPool) != 0;
const long Receive_PauseWriterThreshold = 4L * 1024 * 1024 * 1024; // receive: let's give it up to 4GiB of buffer for now const long Receive_PauseWriterThreshold = 4L * 1024 * 1024 * 1024; // receive: let's give it up to 4GiB of buffer for now
const long Receive_ResumeWriterThreshold = 3L * 1024 * 1024 * 1024; // (large replies get crazy big) const long Receive_ResumeWriterThreshold = 3L * 1024 * 1024 * 1024; // (large replies get crazy big)
...@@ -58,21 +92,25 @@ public SocketManager(string name = null, int workerCount = 0, bool useHighPriori ...@@ -58,21 +92,25 @@ public SocketManager(string name = null, int workerCount = 0, bool useHighPriori
Send_PauseWriterThreshold / 2, Send_PauseWriterThreshold / 2,
defaultPipeOptions.ResumeWriterThreshold); defaultPipeOptions.ResumeWriterThreshold);
_schedulerPool = new DedicatedThreadPoolPipeScheduler(name + ":IO", Scheduler = PipeScheduler.ThreadPool;
workerCount: workerCount, if (!useThreadPool)
priority: useHighPrioritySocketThreads ? ThreadPriority.AboveNormal : ThreadPriority.Normal); {
Scheduler = new DedicatedThreadPoolPipeScheduler(name + ":IO",
workerCount: workerCount,
priority: useHighPrioritySocketThreads ? ThreadPriority.AboveNormal : ThreadPriority.Normal);
}
SendPipeOptions = new PipeOptions( SendPipeOptions = new PipeOptions(
pool: defaultPipeOptions.Pool, pool: defaultPipeOptions.Pool,
readerScheduler: _schedulerPool, readerScheduler: Scheduler,
writerScheduler: _schedulerPool, writerScheduler: Scheduler,
pauseWriterThreshold: Send_PauseWriterThreshold, pauseWriterThreshold: Send_PauseWriterThreshold,
resumeWriterThreshold: Send_ResumeWriterThreshold, resumeWriterThreshold: Send_ResumeWriterThreshold,
minimumSegmentSize: Math.Max(defaultPipeOptions.MinimumSegmentSize, MINIMUM_SEGMENT_SIZE), minimumSegmentSize: Math.Max(defaultPipeOptions.MinimumSegmentSize, MINIMUM_SEGMENT_SIZE),
useSynchronizationContext: false); useSynchronizationContext: false);
ReceivePipeOptions = new PipeOptions( ReceivePipeOptions = new PipeOptions(
pool: defaultPipeOptions.Pool, pool: defaultPipeOptions.Pool,
readerScheduler: _schedulerPool, readerScheduler: Scheduler,
writerScheduler: _schedulerPool, writerScheduler: Scheduler,
pauseWriterThreshold: Receive_PauseWriterThreshold, pauseWriterThreshold: Receive_PauseWriterThreshold,
resumeWriterThreshold: Receive_ResumeWriterThreshold, resumeWriterThreshold: Receive_ResumeWriterThreshold,
minimumSegmentSize: Math.Max(defaultPipeOptions.MinimumSegmentSize, MINIMUM_SEGMENT_SIZE), minimumSegmentSize: Math.Max(defaultPipeOptions.MinimumSegmentSize, MINIMUM_SEGMENT_SIZE),
...@@ -80,23 +118,44 @@ public SocketManager(string name = null, int workerCount = 0, bool useHighPriori ...@@ -80,23 +118,44 @@ public SocketManager(string name = null, int workerCount = 0, bool useHighPriori
} }
/// <summary> /// <summary>
/// Default / shared socket manager /// Default / shared socket manager using a dedicated thread-pool
/// </summary> /// </summary>
public static SocketManager Shared public static SocketManager Shared
{ {
get get
{ {
var shared = _shared; var shared = s_shared;
if (shared != null) return _shared; if (shared != null) return shared;
try try
{ {
// note: we'll allow a higher max thread count on the shared one // note: we'll allow a higher max thread count on the shared one
shared = new SocketManager("DefaultSocketManager", DEFAULT_WORKERS * 2, false); shared = new SocketManager("DefaultSocketManager", DEFAULT_WORKERS * 2, false);
if (Interlocked.CompareExchange(ref _shared, shared, null) == null) if (Interlocked.CompareExchange(ref s_shared, shared, null) == null)
shared = null;
}
finally { shared?.Dispose(); }
return Volatile.Read(ref s_shared);
}
}
/// <summary>
/// Shared socket manager using the main thread-pool
/// </summary>
public static SocketManager ThreadPool
{
get
{
var shared = s_threadPool;
if (shared != null) return shared;
try
{
// note: we'll allow a higher max thread count on the shared one
shared = new SocketManager("ThreadPoolSocketManager", options: SocketManagerOptions.UseThreadPool);
if (Interlocked.CompareExchange(ref s_threadPool, shared, null) == null)
shared = null; shared = null;
} }
finally { shared?.Dispose(); } finally { shared?.Dispose(); }
return Volatile.Read(ref _shared); return Volatile.Read(ref s_threadPool);
} }
} }
...@@ -105,18 +164,19 @@ public static SocketManager Shared ...@@ -105,18 +164,19 @@ public static SocketManager Shared
public override string ToString() public override string ToString()
{ {
var scheduler = SchedulerPool; var scheduler = SchedulerPool;
if (scheduler == null) return Name;
return $"scheduler - queue: {scheduler?.TotalServicedByQueue}, pool: {scheduler?.TotalServicedByPool}"; return $"{Name} - queue: {scheduler?.TotalServicedByQueue}, pool: {scheduler?.TotalServicedByPool}";
} }
private static SocketManager _shared; private static SocketManager s_shared, s_threadPool;
private const int DEFAULT_WORKERS = 5, MINIMUM_SEGMENT_SIZE = 8 * 1024; private const int DEFAULT_WORKERS = 5, MINIMUM_SEGMENT_SIZE = 8 * 1024;
private DedicatedThreadPoolPipeScheduler _schedulerPool;
internal readonly PipeOptions SendPipeOptions, ReceivePipeOptions; internal readonly PipeOptions SendPipeOptions, ReceivePipeOptions;
internal DedicatedThreadPoolPipeScheduler SchedulerPool => _schedulerPool; internal PipeScheduler Scheduler { get; private set; }
internal DedicatedThreadPoolPipeScheduler SchedulerPool => Scheduler as DedicatedThreadPoolPipeScheduler;
private enum CallbackOperation private enum CallbackOperation
{ {
...@@ -134,8 +194,9 @@ private void Dispose(bool disposing) ...@@ -134,8 +194,9 @@ private void Dispose(bool disposing)
// note: the scheduler *can't* be collected by itself - there will // note: the scheduler *can't* be collected by itself - there will
// be threads, and those threads will be rooting the DedicatedThreadPool; // be threads, and those threads will be rooting the DedicatedThreadPool;
// but: we can lend a hand! We need to do this even in the finalizer // but: we can lend a hand! We need to do this even in the finalizer
try { _schedulerPool?.Dispose(); } catch { } var tmp = SchedulerPool;
_schedulerPool = null; Scheduler = PipeScheduler.ThreadPool;
try { tmp?.Dispose(); } catch { }
if (disposing) if (disposing)
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
...@@ -167,7 +228,7 @@ internal static Socket CreateSocket(EndPoint endpoint) ...@@ -167,7 +228,7 @@ internal static Socket CreateSocket(EndPoint endpoint)
internal string GetState() internal string GetState()
{ {
var s = _schedulerPool; var s = SchedulerPool;
return s == null ? null : $"{s.AvailableCount} of {s.WorkerCount} available"; return s == null ? null : $"{s.AvailableCount} of {s.WorkerCount} available";
} }
} }
......
using System; using System;
using System.IO; using System.IO;
using System.IO.Pipelines;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Threading.Tasks; using System.Threading.Tasks;
using Pipelines.Sockets.Unofficial;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
...@@ -403,5 +405,28 @@ public void EndpointIteratorIsReliableOverChanges() ...@@ -403,5 +405,28 @@ public void EndpointIteratorIsReliableOverChanges()
Assert.Equal(8001, ((IPEndPoint)iter.Current).Port); Assert.Equal(8001, ((IPEndPoint)iter.Current).Port);
Assert.False(iter.MoveNext()); Assert.False(iter.MoveNext());
} }
[Fact]
public void ThreadPoolManagerIsDetected()
{
var config = new ConfigurationOptions
{
EndPoints = { { IPAddress.Loopback, 6379 } },
SocketManager = SocketManager.ThreadPool
};
using var muxer = ConnectionMultiplexer.Connect(config);
Assert.Same(PipeScheduler.ThreadPool, muxer.SocketManager.Scheduler);
}
[Fact]
public void DefaultThreadPoolManagerIsDetected()
{
var config = new ConfigurationOptions
{
EndPoints = { { IPAddress.Loopback, 6379 } },
};
using var muxer = ConnectionMultiplexer.Connect(config);
Assert.Same(SocketManager.Shared.Scheduler, muxer.SocketManager.Scheduler);
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment