Go-style concurrency primitives and functional result pipelines for 10 applications.
- Highlights
- When to use Hugo
- Supported runtimes
- Trimming & AOT considerations
- Install
- Quick start
- Observability in one call
- Documentation
- Samples & benchmarks
- Project layout
- Support
- Contributing
- License
- Deterministic concurrency: Channels, wait groups, mutexes, RW locks, timers, defer, and select-style helpers mirror Go semantics while respecting
CancellationTokenandTimeProvider. - Railway-oriented results:
Result<T>pipelines keep success/failure explicit withThen,Map,Recover,Ensure, streaming helpers, sagas, and tiered fallbacks. ValueTask-first extensions such asThenValueTaskAsync,MapValueTaskAsync, andTapValueTaskAsynckeep async pipelines allocation-free when your delegates already returnValueTask<Result<T>>. - Observability first:
GoDiagnosticsships counters, histograms, and activity sources that plug directly into OpenTelemetry exporters or custom meter providers. - Source-generated error catalog: Hugo’s well-known error codes are generated at compile time, ensuring consistent metadata for logs, metrics, and documentation.
- Batteries included: Prioritised channels, task queue leasing, retry policies, deterministic version gates, workflow telemetry, and profiling recipes ship alongside the core library.
- Testability: Fakeable time providers, deterministic effect capture, structured errors, and cancellation metadata keep async workflows reproducible in CI.
- You need Go-like coordination primitives (channels/select/wait groups) without leaving C#.
- You want predictable error handling with structured metadata instead of exceptions for control flow.
- You are building workflow orchestrations that must replay deterministically across retries and recoveries.
- You plan to publish metrics and traces for concurrency-heavy workloads without custom instrumentation.
- You require reusable timeout/retry/cancellation playbooks that can be enforced across teams.
- Targets
net10.0. - Works with generic host builders, ASP.NET background services, worker services, and isolated Azure Functions workers.
- Verified with the .NET 10 SDK;
Hugo is tested with the .NET NativeAOT toolchain and ships with trimming/AOT analyzers enabled, but two APIs depend on System.Text.Json to round-trip arbitrary runtime types:
DeterministicEffectStoreandVersionGatepersist workflow payloads as JSON.Error/ErrorJsonConverterpreserve metadata dictionaries that can contain any CLR object.
When publishing with PublishTrimmed=true or PublishAot=true, make sure the types you capture in effect stores or error metadata aren’t trimmed away. Typical approaches include:
- Prefer source generation – register a
JsonSerializerContextthat lists the effect payloads and pass it toDeterministicEffectStore/VersionGate. - Root the types manually – use
DynamicDependency,PreserveDependency, or reflection registration files to keep rare payloads alive. - Keep metadata simple – favor primitive types, records, or known DTOs in
Error.Metadataso the linker can see them.
If the linker/AOT compiler warns about RequiresUnreferencedCode on these APIs, it means a payload needs to be preserved before trimming continues.
dotnet add package Hugo- Plays nicely with central package management (
Directory.Packages.props). - To iterate locally, run
dotnet pack src/Hugo/Hugo.csproj -o ./artifactsand point a local feed at the generated.nupkg.
These steps mirror the getting started tutorial but demonstrate channels, fan-in coordination, and Result<T> in a single console app.
-
Bootstrap a sample
dotnet new console -n HugoQuickstart cd HugoQuickstart dotnet add package Hugo -
Compose channels, wait groups, and results
Replace
Program.cswith:using Hugo; using static Hugo.Go; using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var metrics = MakeChannel<int>(capacity: 8); var jobs = MakeChannel<string>(capacity: 8); var merged = MakeChannel<object>(capacity: 16); var workers = new WaitGroup(); workers.Go(async () => { using var complete = Defer(() => metrics.Writer.TryComplete()); for (var i = 0; i < 3; i++) { await metrics.Writer.WriteAsync(i, cts.Token).ConfigureAwait(false); } }); workers.Go(async () => { using var complete = Defer(() => jobs.Writer.TryComplete()); foreach (var name in new[] { "build", "deploy", "notify" }) { await jobs.Writer.WriteAsync(name, cts.Token).ConfigureAwait(false); } }); var relay = FanInAsync( sources: new[] { metrics.Reader, jobs.Reader }, destination: merged.Writer, completeDestination: true, cancellationToken: cts.Token); var messages = new List<string>(); await foreach (var payload in merged.Reader.ReadAllAsync(cts.Token)) { var result = payload switch { int sample => Ok(sample) .Ensure(static value => value >= 0, value => Error.From($"negative sample {value}", "error.validation")) .Map(static value => $"metric={value}"), string job => Ok(job) .Ensure(static value => !string.IsNullOrWhiteSpace(value)) .Map(static value => $"job={value}"), _ => Err<string>("unsupported payload", "error.validation") }; var handled = await result .TapAsync(static (value, token) => { Console.WriteLine($"processed {value}"); return ValueTask.CompletedTask; }, cts.Token); if (handled.IsSuccess) { messages.Add(handled.Value); } else { Console.WriteLine($"skipped: {handled.Error}"); } } var fanInResult = await relay; if (fanInResult.IsFailure) { Console.WriteLine($"fan-in failed: {fanInResult.Error}"); } await workers.WaitAsync(cts.Token); Console.WriteLine(string.Join(", ", messages));
-
Run it
dotnet run
Lower the timeout or cancel manually to see how
Error.Canceledflows through the pipeline without throwing.
- Apply timeout, retry, and cancellation policies with the playbook templates.
- Stream merged channel output into a background worker or
TaskQueue<T>using Coordinate fan-in workflows. - Swap in a fake
TimeProviderto deterministically test deadlines and ticker behaviour. - Wire observability with Publish metrics and traces to OpenTelemetry.
using Hugo.Diagnostics.OpenTelemetry;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services
.AddOpenTelemetry()
.AddHugoDiagnostics(options =>
{
options.ServiceName = builder.Environment.ApplicationName;
options.OtlpEndpoint = new Uri("http://localhost:4317");
options.AddPrometheusExporter = true;
});
var app = builder.Build();
app.Run();- Metrics:
waitgroup.*,channel.select.*,taskqueue.*,workflow.*,result.*, and more (see Diagnostics reference). - Traces: rate-limited spans that carry workflow metadata and channel coordination tags.
- Ready-made collector recipes live in the worker sample walkthrough.
Hugo follows the Divio documentation system so you can find the right level of guidance quickly:
- Tutorials – step-by-step introductions such as Getting started with channels and results.
- How-to guides – task-oriented recipes for fan-in coordination, OpenTelemetry export, workflow visibility, profiling, and retry playbooks.
- Reference – definitive API docs covering concurrency primitives, result pipelines, deterministic coordination, diagnostics, and the full API catalogue.
Start at docs/index.md for navigation and search hints.
samples/Hugo.WorkerSample– a background worker showcasing task queues, deterministic workflows, and OpenTelemetry integration.benchmarks/Hugo.Benchmarks– BenchmarkDotNet suites comparing Hugo primitives to baseline .NET constructs (mutex vsSemaphoreSlim, prioritized channels, result pipelines, select latency, object pools, timers).
Run the worker sample with a local collector:
docker run --rm \
-p 4317:4317 -p 4318:4318 -p 9464:9464 \
-v "${PWD}/otel-collector.yaml:/etc/otelcol/config.yaml" \
otel/opentelemetry-collector:0.103.1
dotnet run --project samples/Hugo.WorkerSample/Hugo.WorkerSample.csprojBenchmark results are written to BenchmarkDotNet.Artifacts/results/.
src/– library source for concurrency primitives, result pipelines, policies, sagas, deterministic coordination, and diagnostics.tests/– unit, integration, and deterministic timing tests.docs/– tutorials, how-to guides, references, explanations, roadmap, and audit trackers (rendered via DocFX).samples/– runnable end-to-end scenarios.benchmarks/– performance harnesses executed with BenchmarkDotNet.tools/– profiling scripts, analyzers, and auxiliary automation.
- Questions & bugs: open an issue on GitHub.
- Security disclosures: contact the maintainer privately before filing a public issue.
- Review CONTRIBUTING.md for environment setup, coding standards, and workflow expectations.
- Run
dotnet build Hugo.slnxanddotnet testfortests/Hugo.UnitTests,tests/Hugo.IntegrationTests, andtests/Hugo.FeatureTestsbefore submitting a pull request. - Collect coverage with
dotnet test --collect:"XPlat Code Coverage"to match CI.
Hugo is licensed under the MIT License. Use, modify, and distribute it within those terms.
