sockscape/server/Libraries/Kneesocks/Pool.cs

176 lines
5.4 KiB
C#
Raw Normal View History

2017-04-21 21:04:03 +00:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace Kneesocks {
public class Pool<T> where T : Connection {
// number of threads that should be started when the pool is created
// these threads will run for as long as the pool exists
public int InitialCount { get; set; } = 3;
// amount of connections that should initially be allowed per thread
public int InitialSize { get; set; } = 3;
// amount of additional connections that each thread can handle after
// a new thread is created
public int SizeGrowth { get; set; } = 1;
// maximum amount of connections that a single thread will be assigned
2017-05-03 21:10:50 +00:00
// 0 means no limit
public int MaxSize { get; set; } = 10;
// maximum number of threads that will be spawned
// 0 means no limit
public int MaxCount { get; set; } = 0;
// maximum number of connections in a thread that exceeds the calculated
// amount for the pool's thread count before the connection redistribution
// function is called
// 0 means never redistribute
public int Tolerance { get; set; } = 0;
2017-04-21 21:04:03 +00:00
2017-05-12 21:05:18 +00:00
private bool Disposed = false;
2017-04-21 21:04:03 +00:00
private int _fullThreadCount;
2017-05-04 21:09:38 +00:00
private volatile bool updateFullThreadCount = true;
2017-04-21 21:04:03 +00:00
private List<ThreadContext> Threads
= new List<ThreadContext>();
2017-05-03 21:10:50 +00:00
2017-05-04 21:09:38 +00:00
private long InternalCounter = 0;
2017-05-11 21:03:28 +00:00
private Dictionary<UInt64, T> Connections
= new Dictionary<UInt64, T>();
2017-04-21 21:04:03 +00:00
public Pool() {
2017-04-23 04:55:29 +00:00
for(var i = 0; i < InitialCount; ++i)
2017-05-04 21:09:38 +00:00
CreateThread(runWithNoClients: true);
2017-04-21 21:04:03 +00:00
}
2017-05-12 21:05:18 +00:00
public T this[UInt64 id] {
get {
if(HasConnection(id))
return Connections[id];
else return null;
}
}
2017-05-20 23:33:39 +00:00
public bool HasConnection(UInt64 id) => Connections.ContainsKey(id);
2017-05-12 21:05:18 +00:00
private void IndexConnection(T connection) {
2017-05-03 21:10:50 +00:00
lock(Connections) {
2017-05-12 21:05:18 +00:00
if(connection.IsIdNull)
connection.Id = (ulong)Interlocked.Increment(ref InternalCounter);
2017-05-04 21:09:38 +00:00
2017-05-12 21:05:18 +00:00
Connections.Add(connection.Id, connection);
}
2017-05-03 21:10:50 +00:00
}
2017-05-12 21:05:18 +00:00
internal void InvalidateConnection(UInt64 id) {
2017-05-04 21:09:38 +00:00
lock(Connections) {
Connections.Remove(id);
}
}
2017-05-11 21:03:28 +00:00
public void Broadcast(byte[] message) {
2017-05-12 21:05:18 +00:00
if(Disposed)
return;
2017-05-11 21:03:28 +00:00
lock(Connections) {
foreach(var conn in Connections)
conn.Value.Send(message);
}
}
2017-05-12 21:05:18 +00:00
public bool AddConnection(T connection) {
if(Disposed)
return false;
2017-05-03 21:10:50 +00:00
lock(Threads) {
foreach(var thread in Threads) {
if(thread.Stack.Count < FullThreadSize) {
thread.Stack.AddClient(connection);
2017-05-12 21:05:18 +00:00
IndexConnection(connection);
2017-05-03 21:10:50 +00:00
return true;
}
}
2017-04-21 21:04:03 +00:00
2017-05-03 21:10:50 +00:00
if(MaxCount == 0 || Threads.Count < MaxCount) {
CreateThread(connection);
2017-05-12 21:05:18 +00:00
IndexConnection(connection);
2017-05-03 21:10:50 +00:00
return true;
}
2017-04-21 21:04:03 +00:00
}
return false;
2017-04-21 21:04:03 +00:00
}
2017-05-12 21:05:18 +00:00
internal void InvalidateThread(Stack<T> stackRef) {
2017-05-03 21:10:50 +00:00
lock(Threads) {
var ctx = Threads.FirstOrDefault(x => Object.ReferenceEquals(x.Stack, stackRef));
if(ctx != null) {
Threads.Remove(ctx);
updateFullThreadCount = true;
}
}
}
2017-05-11 21:03:28 +00:00
private ThreadContext CreateThread(T initialConnection = null, bool runWithNoClients = false) {
var stack = new Stack<T>(this, runWithNoClients, initialConnection);
2017-04-21 21:04:03 +00:00
var ctx = new ThreadContext {
Stack = stack,
Thread = new Thread(new ThreadStart(stack.ManageStack))
};
2017-05-08 21:06:17 +00:00
ctx.Thread.Start();
2017-04-21 21:04:03 +00:00
Threads.Add(ctx);
2017-04-23 04:55:29 +00:00
updateFullThreadCount = true;
2017-04-21 21:04:03 +00:00
return ctx;
}
private int FullThreadSize {
2017-04-21 21:04:03 +00:00
get {
if(updateFullThreadCount) {
_fullThreadCount = Math.Min(
2017-05-03 21:10:50 +00:00
MaxSize == 0 ? int.MaxValue : MaxSize,
2017-04-21 21:04:03 +00:00
InitialSize + SizeGrowth * (Threads.Count - InitialCount)
);
}
return _fullThreadCount;
}
}
2017-05-12 21:05:18 +00:00
public void Dispose() {
if(Disposed)
return;
Disposed = true;
lock(Threads) {
foreach(var thread in Threads)
thread.Stack.StopThread();
}
while(true) {
Thread.Sleep(100);
lock(Threads) {
if(Threads.Count == 0)
break;
}
}
lock(Connections) {
foreach(var conn in Connections) {
conn.Value.Disconnect(Frame.kClosingReason.Normal, "Server shutting down.");
}
}
}
~Pool() {
Dispose();
}
2017-04-21 21:04:03 +00:00
class ThreadContext {
public Thread Thread { get; set; }
public Stack<T> Stack { get; set; }
2017-04-21 21:04:03 +00:00
}
}
}