Project · Runtime

Owned channel and lease contracts

Run six real bounded Channel and SemaphoreSlim ownership scenarios without OS threads, arbitrary sleeps, or invented worker behavior.

All C# source files below are loaded together in the playground. Files written by a run live in its virtual directory.

Program.cs

var checks = await PipelineChecks.RunAsync();
foreach (var check in checks) Console.WriteLine($"{check.Name}:{(check.Passed ? "PASS" : "FAIL")}");
Console.WriteLine($"passed:{checks.Count(check => check.Passed)}/{checks.Count}");
return checks.All(check => check.Passed) ? 0 : 1;

Runtime/Lease.cs

public sealed class Lease : IAsyncDisposable
{
    private SemaphoreSlim? gate;
    private Lease(SemaphoreSlim gate) { this.gate = gate; }
    public static async ValueTask<Lease> TakeAsync(SemaphoreSlim gate) { await gate.WaitAsync().ConfigureAwait(false); return new Lease(gate); }
    public ValueTask DisposeAsync() { Interlocked.Exchange(ref gate, null)?.Release(); return ValueTask.CompletedTask; }
}

Checks/PipelineChecks.cs

using System.Threading.Channels;
public static class PipelineChecks
{
    public static async Task<List<(string Name, bool Passed)>> RunAsync()
    {
        List<(string Name, bool Passed)> checks = [];
        var success = Channel.CreateBounded<int>(2);
        success.Writer.TryWrite(2); success.Writer.TryWrite(4); success.Writer.TryComplete();
        var items = new List<int>();
        await foreach (int item in success.Reader.ReadAllAsync()) items.Add(item);
        await success.Reader.Completion;
        checks.Add(("drain", items.SequenceEqual(new[] { 2, 4 }) && success.Reader.Completion.IsCompletedSuccessfully));
        var closed = Channel.CreateBounded<int>(1);
        closed.Writer.TryComplete(); await closed.Reader.Completion;
        checks.Add(("write-after", !closed.Writer.TryWrite(7)));
        var bounded = Channel.CreateBounded<int>(2);
        bool capacity = bounded.Writer.TryWrite(1) && bounded.Writer.TryWrite(2) && !bounded.Writer.TryWrite(3);
        while (bounded.Reader.TryRead(out _)) { }
        bounded.Writer.TryComplete(); await bounded.Reader.Completion;
        checks.Add(("capacity", capacity));
        var canceled = Channel.CreateBounded<int>(1);
        using var source = new CancellationTokenSource(); source.Cancel();
        bool observed = false;
        try { await canceled.Reader.ReadAsync(source.Token); } catch (OperationCanceledException error) { observed = error.CancellationToken == source.Token; }
        bool admitted = canceled.Writer.TryWrite(7); int retained = await canceled.Reader.ReadAsync();
        canceled.Writer.TryComplete(); await canceled.Reader.Completion;
        checks.Add(("wait-cancel", observed && admitted && retained == 7));
        var failed = Channel.CreateBounded<int>(1);
        failed.Writer.TryWrite(9); failed.Writer.TryComplete(new InvalidOperationException("controlled"));
        bool drained = failed.Reader.TryRead(out int buffered) && buffered == 9;
        bool terminal = false;
        try { await failed.Reader.Completion; } catch (InvalidOperationException) { terminal = true; }
        checks.Add(("failure-drains", drained && terminal && !failed.Writer.TryWrite(10)));
        using var gate = new SemaphoreSlim(1, 1);
        var lease = await Lease.TakeAsync(gate);
        await lease.DisposeAsync(); await lease.DisposeAsync();
        checks.Add(("lease-once", gate.CurrentCount == 1));
        return checks;
    }
}

ConsoleLab.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <LangVersion>13.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>