Signalynx.Core
1.0.3
dotnet add package Signalynx.Core --version 1.0.3
NuGet\Install-Package Signalynx.Core -Version 1.0.3
<PackageReference Include="Signalynx.Core" Version="1.0.3" />
<PackageVersion Include="Signalynx.Core" Version="1.0.3" />
<PackageReference Include="Signalynx.Core" />
paket add Signalynx.Core --version 1.0.3
#r "nuget: Signalynx.Core, 1.0.3"
#:package Signalynx.Core@1.0.3
#addin nuget:?package=Signalynx.Core&version=1.0.3
#tool nuget:?package=Signalynx.Core&version=1.0.3
Signalynx: High-Performance .NET Mediator and Durable Messaging
Website: signalynx.inilesh.dev · Source: github.com/it-nilesh/Signalynx · License: MIT
Signalynx is a high-performance, strongly typed .NET mediator, CQRS dispatcher, and durable messaging toolkit for .NET 8, .NET 9, and .NET 10. It supports commands, queries, request/response messages, notifications, domain events, pipeline behaviors, bulk processing, transactional inbox/outbox patterns, retries, scheduling, dead letters, and broker transports.
Signalynx.Core is the main runtime package. Optional packages add Microsoft dependency injection, FluentValidation, logging, source generation, durable stores, and RabbitMQ, Azure Service Bus, Amazon SQS, or Kafka transports.
dotnet add package Signalynx.Core
Signalynx is async-only, built from scratch, and does not require ASP.NET Core or an external messaging framework.
Contents
- Why Signalynx?
- Choose the right API
- Installation
- Package ecosystem
- Quick start
- Durable messaging
- Production readiness
- Build and test
Why Signalynx?
- Strongly typed APIs: commands, queries, requests, notifications, and domain events use explicit contracts.
- Low dispatch overhead: handlers return
ValueTask, discovery happens once at startup, and the hot path avoidsMethodInfo.Invoke. - Composable pipelines: add validation, logging, authorization, metrics, or transactions without putting infrastructure in handlers.
- Optional infrastructure: mediator dispatch remains independent from durable messaging, databases, and message brokers.
- NativeAOT support: source-generated registration avoids runtime assembly scanning in trimmed applications.
- Production messaging: add inbox/outbox delivery, retries, scheduling, deduplication, dead letters, and replay when a boundary requires them.
Signalynx is designed to keep mediator overhead low. It does not claim that real business logic, database access, serialization, or network I/O becomes free; benchmark the complete workload for your application.
Choose the right API
| Requirement | Signalynx API | Use it for |
|---|---|---|
| One handler and an immediate result | DispatchAsync, QueryAsync, or RequestAsync |
Commands, reads, and request/response work inside the current process |
| Multiple local handlers | PublishAsync or PublishEventAsync |
Notifications and domain events |
| A large local workload | ISignalynxBulkProcessor |
Bounded sequential or parallel processing without per-item mediator semantics |
| Delay, retry, durability, or another service | ISignalynxMessageBus |
Scheduled work, integration messages, and broker delivery |
Installation
Main runtime
Install the main package when you want the mediator, publishers, handler registry, diagnostics, and bulk processor:
dotnet add package Signalynx.Core
Microsoft dependency injection
Install the DI integration when using AddSignalynx(...). It includes Signalynx.Core and Signalynx.Abstractions transitively, so you do not need to install them separately:
dotnet add package Signalynx.DependencyInjection
Add only the optional packages required by your application. For example:
dotnet add package Signalynx.Validation
dotnet add package Signalynx.Logging
dotnet add package Signalynx.SourceGeneration
dotnet add package Signalynx.Messaging
Package ecosystem
Runtime and application integrations
| Package | Purpose |
|---|---|
Signalynx.Core |
Main runtime package: mediator dispatch, publishers, registry, diagnostics, and bulk processor |
Signalynx.Abstractions |
Dependency-free messages, handlers, pipelines, and mediator contracts |
Signalynx.DependencyInjection |
Microsoft DI registration and startup assembly scanning; includes Core transitively |
Signalynx.Validation |
Optional FluentValidation pipeline behavior |
Signalynx.Logging |
Optional Microsoft.Extensions.Logging pipeline behavior |
Signalynx.SourceGeneration |
Compile-time handler registration for trimming and NativeAOT |
Signalynx.Messaging |
Durable messaging, workers, envelopes, retries, scheduling, inbox/outbox, and dead-letter operations |
Durable stores
| Package | Purpose |
|---|---|
Signalynx.Stores.SqlServer |
SQL Server inbox, outbox, and dead-letter stores |
Signalynx.Stores.PostgreSql |
PostgreSQL inbox, outbox, and dead-letter stores |
Message transports
| Package | Purpose |
|---|---|
Signalynx.Transports.InMemory |
Development/test transport and non-persistent stores; not for production durability |
Signalynx.Transports.RabbitMQ |
RabbitMQ transport adapter |
Signalynx.Transports.AzureServiceBus |
Azure Service Bus transport adapter |
Signalynx.Transports.AmazonSqs |
Amazon SQS transport adapter |
Signalynx.Transports.Kafka |
Kafka transport adapter |
Quick start
This example uses Microsoft DI, so install Signalynx.DependencyInjection. The package includes the Core runtime automatically.
1. Define a command and handler
public sealed record CreateOrderCommand(Guid CustomerId, decimal Amount)
: ICommand<Guid>;
public sealed class CreateOrderHandler
: ICommandHandler<CreateOrderCommand, Guid>
{
public ValueTask<Guid> HandleAsync(
CreateOrderCommand command,
CancellationToken cancellationToken = default)
=> ValueTask.FromResult(Guid.NewGuid());
}
2. Register Signalynx
builder.Services.AddSignalynx(options =>
{
options.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
3. Dispatch the command
var id = await signalynx.DispatchAsync<CreateOrderCommand, Guid>(
command,
cancellationToken);
Messages and APIs
Use ICommand or ICommand<TResult> for state-changing operations, IQuery<TResult> for reads, and IRequest<TResult> for general request/response interactions.
Signalynx is async-only. APIs are DispatchAsync, QueryAsync, and RequestAsync; handlers expose HandleAsync and return ValueTask or ValueTask<TResult>.
Notifications and domain events allow multiple handlers:
await signalynx.PublishAsync(new OrderCreated(orderId), cancellationToken);
await signalynx.PublishEventAsync(new OrderConfirmed(orderId), cancellationToken);
Sequential publishing executes registrations in order and stops at the first exception. Parallel starts handlers concurrently and aggregates failures.
Pipeline behaviors
Implement IPipelineBehavior<TRequest, TResult>. Behaviors execute in registration order and can add validation, logging, authorization, metrics, or transactions.
options.AddOpenBehavior(typeof(LoggingBehavior<,>));
Validation is opt-in:
options.AddOpenBehavior(typeof(ValidationBehavior<,>));
Register your IValidator<T> implementations with the DI container. If no validators exist, the behavior immediately calls the next stage.
Bulk processing
ISignalynxBulkProcessor is deliberately separate from mediator dispatch. It supports sequential and parallel asynchronous processing, batching, cancellation, maximum concurrency, and stop/continue/collect exception strategies.
await bulk.ProcessParallelAsync(
orders,
static (order, token) => PersistAsync(order, token),
maxDegreeOfParallelism: 8,
cancellationToken);
Do not automatically dispatch every element of a million-item loop through the mediator. Benchmark the complete workload and use bulk APIs when per-item mediator semantics add no value.
Durable messaging
Signalynx.Messaging adds asynchronous message delivery without coupling the
mediator hot path to a broker or database. It includes:
- message envelopes with headers, correlation, causation, destination, and scheduling;
- outbox and inbox persistence contracts;
- transport send, receive, acknowledgement, retry, and dead-letter contracts;
- hosted outbox and receiver workers;
- exponential-backoff retries;
- duplicate-delivery protection through the inbox;
- dead-letter browsing and replay through
IMessageOperations; System.Diagnostics.Metricscounters underSignalynx.Messaging.
Define a message handler:
public sealed record OrderSubmitted(Guid OrderId);
public sealed class OrderSubmittedHandler : IMessageHandler<OrderSubmitted>
{
public ValueTask HandleAsync(OrderSubmitted message, MessageContext context)
{
// Application work. Use context.MessageId for idempotency/auditing.
return ValueTask.CompletedTask;
}
}
Configure the development transport:
services.AddSignalynxInMemoryTransport();
services.AddSignalynxMessaging(options =>
{
options.RegisterMessage<OrderSubmitted>();
options.MaxDeliveryAttempts = 5;
});
services.AddSignalynxMessageHandler<OrderSubmitted, OrderSubmittedHandler>();
Enqueue or schedule:
var id = await bus.EnqueueAsync(
new OrderSubmitted(orderId),
destination: "orders",
cancellationToken: cancellationToken);
await bus.ScheduleAsync(
new OrderSubmitted(orderId),
DateTimeOffset.UtcNow.AddMinutes(5),
destination: "orders",
cancellationToken: cancellationToken);
The in-memory provider is intentionally for development and tests. It loses
messages when the process exits and is not a production durability guarantee.
Production deployments must provide persistent implementations of
IOutboxStore, IInboxStore, and IDeadLetterStore, plus an
IMessageTransport adapter for the chosen broker.
A true transactional outbox also requires the application data update and outbox insert to participate in the same database transaction. The interfaces support that architecture, but transaction enlistment belongs in each database provider.
Signalynx ships durable store adapters for SQL Server and PostgreSQL. Each
store implements IOutboxStore, IInboxStore, and IDeadLetterStore over a
database-specific client abstraction so applications can bind raw ADO.NET,
Dapper, EF Core, or a transaction-enlisted implementation.
Example SQL Server registration:
builder.Services.AddSingleton<ISqlServerMessageStoreClient, SqlServerStoreClient>();
builder.Services.AddSignalynxSqlServerStores(options =>
{
options.Schema = "messaging";
options.OutboxTable = "outbox";
options.InboxTable = "inbox";
options.DeadLetterTable = "dead_letters";
});
PostgreSQL uses the same shape through IPostgreSqlMessageStoreClient and
AddSignalynxPostgreSqlStores.
For high-throughput workloads, the SQL Server and PostgreSQL stores also implement optional batch interfaces:
IBatchOutboxStoreIBatchInboxStoreIBatchDeadLetterStore
Provider clients should implement these batch methods with bulk insert/update
commands, table-valued parameters, COPY, array parameters, partition-aware
queries, or equivalent database-specific primitives. Avoid one database
round-trip per message when processing large streams.
Signalynx ships transport adapter packages for RabbitMQ, Azure Service Bus,
Amazon SQS, and Kafka. Each adapter implements IMessageTransport over a
small broker-specific client abstraction, so applications can bind the official
broker SDK client, a managed wrapper, or a test double without coupling
Signalynx.Messaging to vendor packages.
Example RabbitMQ registration:
builder.Services.AddSingleton<IRabbitMqTransportClient, YourRabbitMqTransportClient>();
builder.Services.AddSignalynxRabbitMqTransport(options =>
{
options.QueueName = "orders";
});
The same pattern is available through IAzureServiceBusTransportClient,
IAmazonSqsTransportClient, and IKafkaTransportClient.
Production Configuration
Register validation, logging, and handlers:
builder.Services.AddValidatorsFromAssembly(
typeof(CreateOrderValidator).Assembly);
builder.Services.AddSignalynx(options =>
{
options.RegisterServicesFromAssembly(
typeof(CreateOrderCommand).Assembly);
options.AddOpenBehavior(typeof(ValidationBehavior<,>));
options.AddOpenBehavior(typeof(LoggingBehavior<,>));
options.NotificationPublishStrategy =
SignalynxPublishStrategy.Sequential;
options.EventPublishStrategy =
SignalynxPublishStrategy.Sequential;
options.ValidateHandlersOnStartup = true;
});
Sequential publishing is the safer default. Use parallel publishing only when handlers are independent and thread-safe. Handlers are resolved through DI; use scoped handlers for database contexts and request-scoped dependencies.
Register consumed messages with stable wire names:
builder.Services.AddSignalynxMessaging(options =>
{
options.RegisterMessage<OrderSubmitted>("orders.submitted.v1");
options.OutboxBatchSize = 100;
options.OutboxPollingInterval = TimeSpan.FromMilliseconds(250);
options.OutboxLockDuration = TimeSpan.FromSeconds(30);
options.MaxDeliveryAttempts = 5;
options.BaseRetryDelay = TimeSpan.FromSeconds(1);
});
builder.Services
.AddSignalynxMessageHandler<OrderSubmitted, OrderSubmittedHandler>();
Explicit wire names are recommended because assembly-qualified names can change during refactoring.
Production deployments must register real providers:
builder.Services.AddSingleton<IMessageTransport, ProductionTransport>();
builder.Services.AddSingleton<ISqlServerMessageStoreClient, SqlServerStoreClient>();
builder.Services.AddSignalynxSqlServerStores();
These implementations are application- or vendor-specific. Do not register
Signalynx.Transports.InMemory outside local development and tests.
Idempotent Message Handlers
Most brokers provide at-least-once delivery, so handlers must tolerate duplicates:
public sealed class OrderSubmittedHandler(
OrdersDbContext database,
ILogger<OrderSubmittedHandler> logger)
: IMessageHandler<OrderSubmitted>
{
public async ValueTask HandleAsync(
OrderSubmitted message,
MessageContext context)
{
var alreadyProcessed = await database.ProcessedMessages
.AnyAsync(
x => x.MessageId == context.MessageId,
context.CancellationToken);
if (alreadyProcessed)
{
return;
}
await ApplyBusinessChangeAsync(
message,
context.CancellationToken);
database.ProcessedMessages.Add(
new ProcessedMessage(
context.MessageId,
DateTimeOffset.UtcNow));
await database.SaveChangesAsync(context.CancellationToken);
logger.LogInformation(
"Processed message {MessageId} on attempt {Attempt}",
context.MessageId,
context.Attempt);
}
}
Record the message ID and business update in one transaction. Signalynx's inbox prevents completed messages from being handled twice by the same configured store, but business-level idempotency remains necessary when handlers call external systems.
Transactional Outbox
For a true transactional outbox, the business write and outbox insert must commit through the same database connection and transaction:
await using var transaction =
await database.Database.BeginTransactionAsync(cancellationToken);
database.Orders.Add(order);
await database.SaveChangesAsync(cancellationToken);
await messageBus.EnqueueAsync(
new OrderSubmitted(order.Id),
destination: "orders",
headers: new Dictionary<string, string>
{
["tenant-id"] = tenantId,
["schema-version"] = "1"
},
cancellationToken: cancellationToken);
await transaction.CommitAsync(cancellationToken);
This is atomic only when the selected IOutboxStore enlists in that same
transaction. A provider opening a separate connection is not transactional.
Retries and Dead Letters
The default policy uses bounded exponential backoff. Replace IRetryPolicy
when permanent and transient exceptions require different handling:
builder.Services.AddSingleton<IRetryPolicy, ApplicationRetryPolicy>();
Dead letters can be inspected and replayed:
app.MapGet("/admin/messaging/dead-letters", async (
IMessageOperations operations,
CancellationToken cancellationToken) =>
await operations.GetDeadLettersAsync(
maxCount: 100,
cancellationToken));
app.MapPost("/admin/messaging/dead-letters/{id:guid}/replay",
async (
Guid id,
IMessageOperations operations,
CancellationToken cancellationToken) =>
{
await operations.ReplayDeadLetterAsync(
id,
cancellationToken);
return Results.Accepted();
});
Protect these endpoints with administrative authorization and audit every replay.
Environment-Specific Providers
if (builder.Environment.IsDevelopment())
{
builder.Services.AddSignalynxInMemoryTransport(options =>
{
options.Capacity = 10_000;
});
}
else
{
// Application-defined extension that registers the selected production
// IMessageTransport and persistent stores.
builder.Services.AddProductionSignalynxTransport(
builder.Configuration);
}
Integration tests should cover successful delivery, retries, retry exhaustion, duplicate delivery, poison messages, scheduling, dead-letter replay, and process restart behavior.
Production Observability
Enable core mediator diagnostics when you want dispatch and publish telemetry:
builder.Services.AddSignalynx(options =>
{
options.RegisterServicesFromAssembly(typeof(Program).Assembly);
options.EnableDiagnostics = true;
});
Signalynx emits dispatch and publish activities through the Signalynx
activity source and metrics through the Signalynx meter:
signalynx.dispatch.callssignalynx.dispatch.failuressignalynx.dispatch.durationsignalynx.publish.callssignalynx.publish.failuressignalynx.publish.duration
Signalynx emits these metrics through the Signalynx.Messaging meter:
signalynx.messaging.enqueuedsignalynx.messaging.sentsignalynx.messaging.handledsignalynx.messaging.retriedsignalynx.messaging.dead_letteredsignalynx.messaging.handler.duration
OpenTelemetry example:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource(SignalynxDiagnostics.ActivitySourceName);
})
.WithMetrics(metrics =>
{
metrics.AddMeter(SignalynxDiagnostics.MeterName);
metrics.AddMeter(SignalynxMessagingDiagnostics.MeterName);
metrics.AddPrometheusExporter();
});
This requires OpenTelemetry.Extensions.Hosting and the chosen exporter.
Alert on dead-letter growth, retry rate, outbox depth and age, handler latency,
and transport availability.
Deployment and Scaling
- Run database migrations before enabling workers.
- Ensure only a worker holding the outbox lock publishes a row.
- Set lock duration above normal send latency and recover expired locks.
- Scale consumers according to partitioning and ordering requirements.
- Use graceful shutdown before terminating workers.
- Set broker acknowledgement timeouts above expected handler duration.
- Use bounded queues and broker quotas.
- Version contracts additively and deploy compatible consumers first.
Security
- Use TLS and workload identity for broker and database connections.
- Never place credentials, access tokens, or unnecessary personal data in messages.
- Treat message headers as untrusted input.
- Validate tenant and authorization context inside handlers.
- Restrict dead-letter payload access and define retention policies.
- Keep Signalynx, .NET, serializers, broker clients, and providers patched.
Production Readiness Checklist
- A persistent transport and inbox/outbox/dead-letter provider is installed.
- Business writes and outbox inserts share one transaction.
- Message wire names and schema versions are stable.
- Handlers are idempotent.
- Retry policy distinguishes transient and permanent failures.
- Dead-letter access and replay are authorized and audited.
- Metrics, logs, dashboards, and alerts are configured.
- Broker and database identities use least privilege.
- Retention, privacy, backup, and recovery policies are defined.
- Load, failure, duplicate-delivery, and restart tests have passed.
- Package versions are pinned and dependency licenses are reviewed.
Signalynx supplies the runtime and provider contracts. Production reliability also depends on the selected broker, persistence provider, transaction design, handler idempotency, deployment, and operations.
Source generation
Add Signalynx.SourceGeneration as an analyzer to generate:
services.AddSignalynxGenerated(options =>
{
options.AddOpenBehavior(typeof(LoggingBehavior<,>));
});
The generator emits DI registrations, HandlerDescriptor metadata, a static
handler map, and duplicate single-handler diagnostic SLX001.
AddSignalynxGenerated registers the generated descriptors without runtime
assembly scanning, which is the preferred path for trimmed and NativeAOT
applications.
Runtime assembly scanning remains fully supported through AddSignalynx(options => options.RegisterServicesFromAssembly(...)), but those APIs are annotated
with RequiresUnreferencedCode because linkers cannot statically preserve every
handler discovered by reflection.
Minimal APIs and ASP.NET Core
The sample in samples/Signalynx.Samples.Api shows asynchronous order endpoints. ASP.NET Core is not required by the libraries; it is only one possible host.
app.MapPost("/orders", async (
CreateOrderCommand command,
ISignalynx signalynx,
CancellationToken token) =>
Results.Ok(await signalynx.DispatchAsync<CreateOrderCommand, Guid>(command, token)));
Build, test, and benchmark
dotnet restore
dotnet build Signalynx.slnx -c Release
dotnet test tests/Signalynx.Tests -c Release
dotnet run -c Release --project benchmarks/Signalynx.Performance
dotnet run --project samples/Signalynx.Samples.Api
dotnet publish samples/Signalynx.Samples.NativeAot -c Release -r linux-x64 --self-contained true
dotnet pack src/Signalynx.Core -c Release
Release automation
GitHub Actions runs full validation for pull requests and pushes to develop
and main: restore, build, unit tests, Docker-backed provider integration
tests, and NativeAOT sample publish/run validation.
Pushes to develop and main also generate package artifacts for inspection.
NuGet publishing is intentionally limited to release tags so package versions
cannot be accidentally burned during normal development.
To publish a release:
- Add the repository secret
NUGET_API_KEYwith push permission forSignalynx.*packages on nuget.org. - Merge the finalized code into
main. - Create and push a tag such as
v1.0.3.
The tag workflow packs all source projects using the tag version, publishes the packages to nuget.org, and creates or updates the GitHub Release with package assets and NuGet links.
Safety precautions:
- Secrets are read only from GitHub Actions secrets and are never stored in the repository.
- Pull requests and normal branch pushes never receive NuGet publishing credentials.
- Publishing runs only in the canonical
it-nilesh/Signalynxrepository. - Release tags must point to commits already contained in
origin/main. - Workflow concurrency prevents two runs for the same ref from publishing at the same time.
BenchmarkDotNet scenarios include direct calls, cached delegates, reflection fallback, ValueTask dispatch, generated descriptor dispatch, one-million generated dispatch load tests, commands, queries, requests, notifications, events, diagnostics overhead, sequential/parallel publishing, one/three behavior pipelines, serialization, and enqueue cost. Dispatch, generated dispatch, pipeline, diagnostics, and messaging benchmarks emit allocation measurements; selected dispatch benchmarks also emit disassembly reports through BenchmarkDotNet. Always run benchmarks in Release mode without a debugger.
Docker provider load results
The repository includes opt-in Docker-backed provider benchmarks for RabbitMQ, Kafka, PostgreSQL, and SQL Server. These were run on local Docker with .NET 9.0.17 on Apple M5 Pro. Results vary by host, Docker resources, broker/database settings, message size, durability settings, and batching strategy.
Raw provider write/read benchmarks use broker or database primitives directly:
| Provider | Path | 10k tested time | Approx throughput | Projected 1M time |
|---|---|---|---|---|
| RabbitMQ | publish + push-consume, auto-ack | 103.420 ms | ~96,693 msg/s | ~10.3 s |
| Kafka | batched produce + consume | 50.098 ms | ~199,609 msg/s | ~5.0 s |
| PostgreSQL | insert rows + read rows | 1.247 s | ~8,019 rows/s | ~2 min 5 s |
| SQL Server | insert rows + read rows | 2.723 s | ~3,673 rows/s | ~4 min 32 s |
The RabbitMQ and Kafka rows are optimized raw broker benchmarks, not the full durable messaging pipeline. They intentionally exclude database outbox/inbox work, handler execution, retries, and JSON deserialization in the timed path. The PostgreSQL and SQL Server rows are primitive insert/read checks and are not bulk-loader or table-valued-parameter implementations.
Run the broker/provider primitive benchmarks:
docker compose -f docker-compose.integration.yml up -d --wait
SIGNALYNX_PROVIDER_LOAD_BENCHMARK=1 DOTNET_ROLL_FORWARD=Major \
dotnet run -c Release --no-build --project benchmarks/Signalynx.Performance \
-- --filter '*RabbitMqTransportLoadBenchmarks.PublishAndConsume*' --join \
--warmupCount 1 --iterationCount 1
SIGNALYNX_PROVIDER_LOAD_BENCHMARK=1 DOTNET_ROLL_FORWARD=Major \
dotnet run -c Release --no-build --project benchmarks/Signalynx.Performance \
-- --filter '*KafkaTransportLoadBenchmarks.ProduceAndConsume*' --join \
--warmupCount 1 --iterationCount 1
SIGNALYNX_PROVIDER_LOAD_BENCHMARK=1 DOTNET_ROLL_FORWARD=Major \
dotnet run -c Release --no-build --project benchmarks/Signalynx.Performance \
-- --filter '*PostgreSqlPrimitiveLoadBenchmarks.InsertAndRead*' --join \
--warmupCount 1 --iterationCount 1
SIGNALYNX_PROVIDER_LOAD_BENCHMARK=1 DOTNET_ROLL_FORWARD=Major \
dotnet run -c Release --no-build --project benchmarks/Signalynx.Performance \
-- --filter '*SqlServerPrimitiveLoadBenchmarks.InsertAndRead*' --join \
--warmupCount 1 --iterationCount 1
The full provider-backed messaging benchmark covers transport, outbox, inbox, retry, and handler execution across RabbitMQ/PostgreSQL, RabbitMQ/SQL Server, Kafka/PostgreSQL, and Kafka/SQL Server:
SIGNALYNX_PROVIDER_LOAD_BENCHMARK=1 DOTNET_ROLL_FORWARD=Major \
dotnet run -c Release --no-build --project benchmarks/Signalynx.Performance \
-- --filter '*ProviderBackedMessagingLoadBenchmarks.TransportOutboxInboxRetryAndHandlerExecution*' \
--join --warmupCount 1 --iterationCount 1
Testing
Create a ServiceCollection, call AddSignalynx, and resolve ISignalynx. Tests should assert handler results, behavior ordering, cancellation flow, publisher strategy, and expected exceptions. The repository uses xUnit.
Performance design
- Typed handler calls instead of reflection invocation during dispatch
- Startup assembly scanning and immutable handler metadata
ValueTask-first async contracts- No LINQ in core dispatch loops
- Async-only API with cancellation propagation
- Sequential publishing as the predictable low-overhead default
- Optional source-generated registration
- Cached direct dispatch delegates and no-behavior pipeline fast paths
- BenchmarkDotNet with allocation and GC measurements
EnableDelegateCaching is enabled by default for optimized dispatch caches.
EnableDiagnostics turns on core dispatch and publish activities and metrics.
Roadmap
Completed foundation:
- Generated descriptor registration and cached dispatch delegates
- NativeAOT/trimming annotations on the core registration path
- Real NativeAOT sample app with publish/run validation in CI
- Diagnostic events, metrics, and OpenTelemetry documentation
- Benchmark comparisons, allocation measurements, and generated dispatch load benchmarks
- RabbitMQ, Azure Service Bus, Amazon SQS, and Kafka transport adapters
- SQL Server and PostgreSQL durable inbox, outbox, and dead-letter store adapters
- Docker-backed integration tests for RabbitMQ, Kafka, SQL Server, and PostgreSQL providers
- End-to-end messaging load benchmark covering transport, outbox, inbox, retries, and handler execution
- Provider implementation samples for official broker/database SDKs
- Durable store concurrency validation for leases, duplicate delivery, retry races, and dead-letter replay
- API compatibility checks and public API approval files
- .NET 10 target support after the support baseline adoption
Next milestones:
- Source Link, signed packages, deterministic package validation, and release automation
License
Signalynx is distributed under the MIT License. Third-party package licenses are listed in THIRD-PARTY-NOTICES.md.
The software is provided without warranty. Applications are responsible for their own security, privacy, regulatory, and industry-specific compliance.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Signalynx.Abstractions (>= 1.0.3)
-
net8.0
- Signalynx.Abstractions (>= 1.0.3)
-
net9.0
- Signalynx.Abstractions (>= 1.0.3)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Signalynx.Core:
| Package | Downloads |
|---|---|
|
Signalynx.DependencyInjection
Microsoft.Extensions.DependencyInjection integration for Signalynx. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.3 | 112 | 7/12/2026 |
| 1.0.2 | 120 | 7/4/2026 |
| 1.0.1 | 120 | 7/4/2026 |
| 0.1.0-alpha.1 | 70 | 6/29/2026 |