Reference C# and verification cases
The showcase uses ordinary CLR application contracts. The native reference host supplies queue coordination, controller methods and executable checks, while the DirectSharp pages show the corresponding language design. The contracts below connect the worker, HTTP boundary and processing examples.
Application ports
Reporting source uses one project-level namespace setting. This is language source configuration, not an invented build command:
project
root_namespace DirectSharp.Showcase
end project
The reporting module references the following C# declarations in namespace DirectSharp.Showcase. Keep the customer-registration declarations in their original application module; the report request connects to them by CustomerId. It does not introduce a second Customer type.
public enum ReportState { Queued, Running, Succeeded, Cancelled, Failed }
public sealed record CustomerReport(Guid CustomerId, int InvoiceCount, decimal Outstanding);
public sealed record ReportSnapshot(Guid Id, Guid CustomerId, ReportState State,
bool CancellationRequested, CustomerReport? Result, string? Error);
public interface IReportRenderer
{
Task<CustomerReport> RenderAsync(Guid customerId, CancellationToken cancellationToken = default);
}
public sealed record InvoiceSnapshot(Guid Id, DateTimeOffset DueAt, IReadOnlyList<decimal> LineAmounts, decimal Paid);
public sealed record StatementRow(Guid Id, DateTimeOffset DueAt, decimal Outstanding);
public interface IInvoiceSource
{
Task<IReadOnlyList<InvoiceSnapshot>> ReadAsync(Guid customerId, CancellationToken cancellationToken);
}
public interface ICustomerAccess
{
Task<bool> CanReadAsync(string owner, Guid customerId, CancellationToken cancellationToken);
}
The excerpt assumes System, System.Collections.Generic, System.Threading and System.Threading.Tasks imports. ICustomerAccess applies the real application's customer-access policy. IInvoiceSource returns bounded snapshots and honors cancellation during acquisition; it must release its connection/reader before returning. The source and renderer are scoped per job. No live cursor or mutable DbContext is shared between worker bodies.
For a database-free exercise, DemoCustomerAccess and SnapshotInvoiceSource are complete native implementations. The access map uses ordinal subject identity. The source copies invoice line values into read-only snapshots at construction and rejects batches over 1000 invoices or 100,000 line amounts. Unknown customer data yields an empty snapshot; customer existence/access remains the admission policy's responsibility. Mutating the input lists later cannot change the copied data. These controlled integrations do not authenticate users or query a real invoice database.
Complete source map
| Reference source | What it supplies |
|---|---|
| ShowcaseReports.cs | Channel, bounded history, job transitions, cancellation-source lifetime, four native consumers and BackgroundService |
| ShowcaseReportApi.cs | DTO, access-policy interface and complete authenticated-controller declaration |
| ShowcaseProcessing.cs | Invoice contracts, explicit CPU scheduling, arithmetic, export and native renderer |
| ShowcaseDemoPorts.cs | Copied in-memory invoice snapshots and explicit customer-access grants |
| ShowcaseReportChecks.cs | Deterministic application checks with controlled completion/cancellation signals |
The DirectSharp ReportWorkers replaces the native ReportWorkers alternative. CustomerReportRenderer replaces ReferenceReportRenderer. Native ReferenceReportCapacity uses SemaphoreSlim for permit behavior; the DirectSharp renderer accepts the two host-owned Gates. This reference substitution does not implement Gate's separate disposal/arbitration contract.
The queue itself has this application-facing surface:
| Member | Contract |
|---|---|
| TrySubmit(owner, customerId) | A snapshot if accepted, null if closed/full/tracking capacity unavailable; reject empty identifiers |
| Reader | Borrowed ChannelReader<ReportJob> consumed by workers |
| Find(owner, id) | Owner-filtered snapshot or null |
| CancelAsync(owner, id) | Task<bool?>: null missing/unowned, false terminal, true cancellation accepted |
| Close() | Reject further admission and complete the channel writer |
| CancelOutstandingAsync() | Request/observe cancellation for tracked unfinished jobs |
| CancellationFailures | Bounded operational buffer of the latest 64 callback failures; not a language error-history API |
Cancellation and publication share one decision boundary
ReportJob's private lock serializes claim, cancellation intent and terminal publication. It never holds that lock while awaiting work. CancelAsync marks its native token cancelled and executes callbacks asynchronously. The first request retains one callback-observation task; terminal publication prevents a later request from accessing a disposed source.
These methods belong to the complete ReportJob above; its private fields and helper implementations remain in the linked file:
public async Task<bool> RequestCancellationAsync()
{
Task callbacks;
bool queued;
lock (sync)
{
if (IsTerminal(state)) return false;
queued = state == ReportState.Queued;
if (!cancellationRequested)
{
cancellationRequested = true;
// CancelAsync marks the source cancelled and runs callbacks asynchronously.
cancellation = ObserveCancellationAsync(stop.CancelAsync());
}
if (queued) state = ReportState.Cancelled; // TryStart can no longer claim it.
callbacks = cancellation;
}
await callbacks; // Never synchronously wait for callbacks while holding sync.
if (queued) DisposeSource();
return true;
}
public async Task FinishAsync(CustomerReport? value, Exception? failure, bool wasCancelled)
{
Task callbacks;
lock (sync)
{
if (state != ReportState.Running) throw new InvalidOperationException("Job is not running.");
// Application policy: a genuine failure remains a failure even if cancellation races it.
if (failure is not null) { state = ReportState.Failed; error = "Report generation failed"; }
else if (wasCancelled || cancellationRequested) state = ReportState.Cancelled;
else
{
result = value ?? throw new InvalidOperationException("Successful report is required.");
state = ReportState.Succeeded;
}
callbacks = cancellation; // Terminal state now rejects new cancellation requests.
}
await callbacks;
DisposeSource();
}
A queued cancellation prevents TryStart before releasing the lock. A running cancellation leaves the worker responsible for finishing and disposing its scope. FinishAsync publishes only after that cleanup and chooses failure, cancellation or success under the same lock. A successful result arriving after an accepted cancellation is discarded rather than published. If success publishes first, a later cancellation receives false.
Cancellation callbacks are observed before source disposal. Callback exceptions are recorded in the bounded diagnostic buffer without substituting for the job's actual result/failure. Registry eviction requires a terminal job whose source is already disposed, so a still-settling callback cannot be orphaned merely to free a history slot. The channel can contain cancelled entries; consumers skip them safely without reading their disposed token sources.
Cancellation does not undo completed I/O. This sample publishes a small in-memory summary only after computation; exporting a file requires a separate temporary-output/publication policy. It does not promise a distributed transaction or durable restart handling.
Owned shutdown
The native hosting adapter calls the ordinary CLR method generated for the DirectSharp worker:
public sealed class ReportHostedService(ReportQueue queue, ReportWorkers workers) : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken) =>
workers.RunAsync(queue.Reader, stoppingToken);
public override async Task StopAsync(CancellationToken cancellationToken)
{
queue.Close();
Task cancellation = queue.CancelOutstandingAsync();
Task settlement = base.StopAsync(CancellationToken.None);
// Deliberate join policy: the host deadline does not abandon owned work.
await Task.WhenAll(cancellation, settlement);
}
}
The excerpt assumes the standard hosting/threading/task imports. Closing admission and requesting cancellation cover queued work as well as active work. Base.StopAsync signals the worker's host token. Both paths are observed before the adapter completes. Its deliberate cooperative-join policy can exceed the supplied shutdown deadline; forcibly stopping the process remains different from finishing the jobs. Shared Gates and scoped dependencies must remain alive through this settlement.
Projection and export lowering
Concurrent for_each has one independent projection per invoice, a per-report limit of four and a shared projection limit of eight. The native reference uses a four-permit semaphore, the shared permit and retained tasks. Task.WhenAll joins all started tasks and preserves result-array order. After a failed join, it retains genuine producer failures in source order, rethrows one original failure or aggregates several; caller cancellation remains cancellation if no genuine failure overrides it. This is a bounded application pattern, not the complete general DirectSharp coordinator or first/any implementation.
The optional statement_rows feature lowers to ordinary Enumerable.Where, Enumerable.OrderBy and StatementRows.Write calls. Its reference checks validate the predicate/order/export outcome, including invariant Decimal formatting under a different process culture. They do not exercise DirectSharp feature discovery or binding.
Cases exercised by the native reference host
| Case | Checked outcome |
|---|---|
| Queue full or admission closed | No new job; controller returns 429 |
| Invalid/inaccessible customer | Rejected before admission |
| Request cancelled after successful admission | Job remains independent of that request token |
| Another subject reads/cancels a job | 404; ownership is unchanged |
| Cancel before claim | Consumer skips the cancelled job |
| Cancellation before successful publication | Cancelled with no result |
| Success before cancellation request | Success retained; later request rejected |
| Native cancellation callback throws | Failure observed separately; job/source settlement completes |
| Eight reports with blocked renderers | Exactly four active job scopes at once |
| A scope is awaiting async disposal | Its worker slot remains occupied until cleanup completes |
| One renderer or its async scope cleanup fails | That job fails; another queued job takes its worker slot |
| Host shutdown with running and queued jobs | Active work cooperatively stops; queued jobs do not start; scopes settle |
| Four simultaneous snapshot reads | Shared database capacity never exceeds two |
| Projection and export ordering | Result positions retain source identity; export explicitly sorts invoice identifiers |
| Invalid monetary inputs in several invoices | Genuine failures aggregate after settlement |
| Empty invoices | Successful zero-count, zero-total report |
| Export under French process culture | Decimal fields still use invariant dot notation and identifier ordering |
| Input collections mutated after demo-source construction | Stored invoice and access snapshots remain unchanged |
The reference checks use controlled completion signals rather than timing sleeps. Live infrastructure integration requires separate application tests.
Previous: parallel processing. Return to the showcase.