Cloudtoid Interprocess is a cross-platform shared memory queue for fast communication between processes (Interprocess Communication or IPC). It uses a shared memory-mapped file for extremely fast and efficient communication between processes and it is used internally by Microsoft.
The NuGet package for this library is published here.
Note: To improve performance, this library only supports 64-bit CLR with 64-bit processor architectures. Attempting to use this library on 32-bit processors, 32-bit operating systems, or on WOW64 may throw a
NotSupportedException
.
This library supports .NET 8.0+. It is optimized for .NET dependency injection but can also be used without DI.
Creating a message queue factory:
var factory = new QueueFactory();
Creating a message queue publisher:
var options = new QueueOptions(
queueName: "my-queue",
bytesCapacity: 1024 * 1024);
using var publisher = factory.CreatePublisher(options);
publisher.TryEnqueue(message);
Creating a message queue subscriber:
options = new QueueOptions(
queueName: "my-queue",
bytesCapacity: 1024 * 1024);
using var subscriber = factory.CreateSubscriber(options);
subscriber.TryDequeue(messageBuffer, cancellationToken, out var message);
Adding the queue factory to the DI container:
services
.AddInterprocessQueue() // adding the queue related components
.AddLogging(); // optionally, we can enable logging
Creating a message queue publisher using an instance of IQueueFactory
retrieved from the DI container:
var options = new QueueOptions(
queueName: "my-queue",
bytesCapacity: 1024 * 1024);
using var publisher = factory.CreatePublisher(options);
publisher.TryEnqueue(message);
Creating a message queue subscriber using an instance of IQueueFactory
retrieved from the DI container:
var options = new QueueOptions(
queueName: "my-queue",
bytesCapacity: 1024 * 1024);
using var subscriber = factory.CreateSubscriber(options);
subscriber.TryDequeue(messageBuffer, cancellationToken, out var message);
To see a sample implementation of a publisher and a subscriber process, try out the following two projects. You can run them side by side and see them in action:
Please note that you can start multiple publishers and subscribers sending and receiving messages to and from the same message queue.
A lot has gone into optimizing the implementation of this library. For instance, it is mostly heap-memory allocation free, reducing the need for garbage collection induced pauses.
Summary: A full enqueue followed by a dequeue takes ~250 ns
on Linux, ~650 ns
on MacOS, and ~300 ns
on Windows.
Details: To benchmark the performance and memory usage, we use BenchmarkDotNet and perform the following runs:
Method | Description |
---|---|
Message enqueue | Benchmarks the performance of enqueuing a message. |
Message enqueue and dequeue | Benchmarks the performance of sending a message to a client and receiving that message. It is inclusive of the duration to enqueue and dequeue a message. |
Message enqueue and dequeue - no message buffer | Benchmarks the performance of sending a message to a client and receiving that message. It is inclusive of the duration to enqueue and dequeue a message and memory allocation for the received message. |
You can replicate the results by running the following command:
dotnet run Interprocess.Benchmark.csproj -c Release
You can also be explicit about the .NET SDK and Runtime(s) versions:
dotnet run Interprocess.Benchmark.csproj -c Release -f net7.0 --runtimes net7.0 net6.0 netcoreapp3.1
Host:
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000
Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK=6.0.201
[Host] : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT
.NET 6.0 : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT
Results:
Method | Mean (ns) | Error (ns) | StdDev (ns) | Allocated |
---|---|---|---|---|
Message enqueue | 192.7 |
3.61 |
3.21 |
- |
Message enqueue and dequeue | 305.6 |
5.96 |
6.62 |
- |
Message enqueue and dequeue - no message buffer | 311.5 |
5.90 |
9.85 |
32 B |
Host:
BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.6 (20G165) [Darwin 20.6.0]
Intel Core i5-8279U CPU 2.40GHz (Coffee Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.401
[Host] : .NET 5.0.10 (5.0.1021.41214), X64 RyuJIT
.NET 5.0 : .NET 5.0.10 (5.0.1021.41214), X64 RyuJIT
Results:
Method | Mean (ns) | Error (ns) | StdDev (ns) | Allocated |
---|---|---|---|---|
Message enqueue | 487.50 |
4.75 |
3.96 |
- |
Message enqueue and dequeue | 666.10 |
10.91 |
10.20 |
- |
Message enqueue and dequeue - no message buffer | 689.33 |
13.38 |
15.41 |
32 B |
Host:
BenchmarkDotNet=v0.13.2, OS=ubuntu 20.04
Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK=6.0.403
[Host] : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.11 (6.0.1122.52304), X64 RyuJIT AVX2
Results:
Method | Mean (ns) | Error (ns) | StdDev (ns) | Allocated |
---|---|---|---|---|
Message enqueue | 5.3 |
- |
- |
- |
Message enqueue and dequeue | 169.9 |
3.08 |
4.01 |
- |
Message enqueue and dequeue - no message buffer | 179.4 |
1.91 |
1.60 |
32 B |
This library relies on Named Semaphores To signal the existence of a new message to all message subscribers and to do it across process boundaries. Named semaphores are synchronization constructs accessible across processes.
.NET Core 3.1 and .NET 6/7 do not support named semaphores on Unix-based OSs (Linux, macOS, etc.). Instead we are using P/Invoke and relying on operating system's POSIX semaphore implementation. (Linux and MacOS implementations).
This implementation will be replaced with System.Threading.Semaphore
once .NET adds support for named semaphores on all platforms.
main
.Pedram Rezaei is a software architect at Microsoft with years of experience building highly scalable and reliable cloud-native applications for Microsoft.
Here are a couple of items that we are working on.