Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions Unhinged.Playground/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@

#pragma warning disable CA2014

// dotnet publish -c Release /p:PublishAot=true /p:OptimizationPreference=Speed

[SkipLocalsInit]
internal static class Program
{
public static void Main(string[] args)
{
var builder = UnhingedEngine
.CreateBuilder()
.SetNWorkersSolver(() => Environment.ProcessorCount / 2)
.SetNWorkersSolver(() => (Environment.ProcessorCount / 2) - 2)
.SetBacklog(16384)
.SetMaxEventsPerWake(512)
.SetMaxNumberConnectionsPerWorker(512)
Expand All @@ -28,13 +30,25 @@ public static void Main(string[] args)
engine.Run();
}

private static void RequestHandler(Connection connection)
private static ValueTask RequestHandler(Connection connection)
{
if(connection.HashedRoute == 291830056) // /json
/*if(connection.HashedRoute == 291830056) // /json
CommitJsonResponse(connection);

else if (connection.HashedRoute == 3454831873) // /plaintext
CommitPlainTextResponse(connection);*/

if (connection.H1HeaderData.Route.Equals("/json"))
{
CommitJsonResponse(connection);
}

else if (connection.H1HeaderData.Route.Equals("/plaintext"))
{
CommitPlainTextResponse(connection);
}

return ValueTask.CompletedTask;
}

[ThreadStatic] private static Utf8JsonWriter? _tUtf8JsonWriter;
Expand Down
33 changes: 30 additions & 3 deletions Unhinged/ABI/Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,12 @@ internal static unsafe class Native
/// </summary>
[DllImport("libc", SetLastError = true)] internal static extern int eventfd(uint initval, int flags);

[DllImport("libc", SetLastError = true)] internal static extern int sched_setaffinity(int pid, nuint cpusetsize, ref ulong mask);


[DllImport("libc", SetLastError = true)] internal static extern int sched_setaffinity(int pid, IntPtr cpusetsize, ref ulong mask);

[DllImport("libc", SetLastError = true)] internal static extern int sched_setaffinity(int pid, IntPtr cpusetsize, ref cpu_set_t mask);

[DllImport("libc")] internal static extern int gettid(); // Linux thread id

// =========================
// Struct definitions
// =========================
Expand Down Expand Up @@ -264,4 +267,28 @@ internal struct Linger
internal const int EPIPE = 32;
internal const int ECONNABORTED = 103;
internal const int ECONNRESET = 104;

public static void PinCurrentThreadToCpu(int cpuIndex)
{
if (cpuIndex < 0 || cpuIndex >= Environment.ProcessorCount)
throw new ArgumentOutOfRangeException(nameof(cpuIndex));

unsafe
{
var set = new cpu_set_t();
int word = cpuIndex / 64;
int bit = cpuIndex % 64;
set.Bits[word] = 1UL << bit;

int tid = gettid();
int ret = sched_setaffinity(tid, (IntPtr)sizeof(cpu_set_t), ref set);
if (ret != 0)
throw new InvalidOperationException($"sched_setaffinity failed with errno {Marshal.GetLastPInvokeError()}");
}
}
}

internal unsafe struct cpu_set_t
{
public fixed ulong Bits[16]; // 1024 bits (enough for up to 1024 CPUs)
}
12 changes: 10 additions & 2 deletions Unhinged/Engine/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@
/// <summary>Writer over the send slab.</summary>
public readonly FixedBufferWriter WriteBuffer;

// <summary>Fnv1a32 hashed route</summary>
public uint HashedRoute { get; set; }
/// <summary>
/// Header data, no allocations
/// </summary>
public BinaryH1HeaderData BinaryH1HeaderData { get; set; }
public H1HeaderData H1HeaderData { get; set; }

/// <param name="maxConnections">Used to size the slabs (typically per-worker slab size).</param>
/// <param name="inSlabSize">Bytes per connection for receive.</param>
/// <param name="outSlabSize">Bytes per connection for send.</param>
public Connection(int maxConnections, int inSlabSize, int outSlabSize)

Check warning on line 36 in Unhinged/Engine/Connection.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'H1HeaderData' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 36 in Unhinged/Engine/Connection.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'H1HeaderData' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
{
//AlignedAlloc(size, 64) ensures your memory starts at an address that’s a multiple of 64,
//matching CPU cache-line size, reducing false sharing and improving SIMD/cache performance.
Expand All @@ -40,6 +43,11 @@
outSlabSize);
}

public void Clear()
{
H1HeaderData?.Clear();
}

/// <summary>
/// Frees the unmanaged slabs. Call exactly once when the connection is permanently done.
/// </summary>
Expand Down
8 changes: 5 additions & 3 deletions Unhinged/Engine/UnhingedEngine.Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,23 @@
private static int _maxEventsPerWake = 512;
private static int _maxStackSizePerThread = 1024 * 1024;
private static int _listenFd;
private static int _acceptBlocking;

Check warning on line 39 in Unhinged/Engine/UnhingedEngine.Builder.cs

View workflow job for this annotation

GitHub Actions / build

The field 'UnhingedEngine._acceptBlocking' is never used

Check warning on line 39 in Unhinged/Engine/UnhingedEngine.Builder.cs

View workflow job for this annotation

GitHub Actions / build

The field 'UnhingedEngine._acceptBlocking' is never used
private static int _nWorkers;

private static Func<int>? _calculateNumberWorkers;

// Default request handler (overridden via builder). Writes a minimal plaintext response.
private static Action<Connection> _sRequestHandler = DefaultRequestHandler;
private static Func<Connection, ValueTask> _sRequestHandler = DefaultRequestHandler;

private static void DefaultRequestHandler(Connection connection)
private static ValueTask DefaultRequestHandler(Connection connection)
{
connection.WriteBuffer.WriteUnmanaged("HTTP/1.1 200 OK\r\n"u8 +
"Server: W\r\n"u8 +
"Content-Type: text/plain\r\n"u8 +
"Content-Length: 28\r\n\r\n"u8 +
"Request handler was not set!"u8 );

return ValueTask.CompletedTask;
}

private UnhingedEngine() { }
Expand Down Expand Up @@ -134,7 +136,7 @@
/// <summary>
/// Inject the request handler used by workers to serve requests.
/// </summary>
public UnhingedBuilder InjectRequestHandler(Action<Connection> requestHandler)
public UnhingedBuilder InjectRequestHandler(Func<Connection, ValueTask> requestHandler)
{
_sRequestHandler = requestHandler;
return this;
Expand Down
9 changes: 8 additions & 1 deletion Unhinged/Engine/UnhingedEngine.Runner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@ public void Run()
// - IsBackground=true so the process can exit if only workers remain.
// - Name aids debugging and logs.
// - Stack size is configurable to accommodate stackalloc-heavy hot paths.
var t = new Thread(() => WorkerLoop(W[iCap]), _maxStackSizePerThread) // 1MB
var t = new Thread(() =>
{
// Performance seems to be lower when pinning threads to cpu core
//PinCurrentThreadToCpu(iCap);
//Console.WriteLine($"Thread {iCap} pinned to CPU {iCap}");

WorkerLoop(W[iCap]);
}, _maxStackSizePerThread) // 1MB
{
IsBackground = true,
Name = $"worker-{iCap}"
Expand Down
Loading
Loading