Skip to content

Cancellation Propagation ​

SignalARRR supports server-initiated cancellation of client operations. The server can pass a CancellationToken to a client method and cancel it remotely. On the TypeScript client, CancellationToken is converted to an AbortSignal.

Server side ​

When a server method calls a client method with a CancellationToken parameter, SignalARRR serializes a CancellationTokenReference marker and tracks it:

csharp
public async Task StartLongRunningTask()
{
    using var cts = new CancellationTokenSource();

    var client = ClientContext.GetTypedMethods<IWorkerClient>();

    // The CancellationToken is propagated to the client
    var task = client.ProcessData("data", cts.Token);

    // Cancel after 5 seconds
    cts.CancelAfter(TimeSpan.FromSeconds(5));

    await task;
}

When cts.Cancel() is called, the server sends a CancelTokenFromServer message to the client with the matching cancellation GUID.

.NET client ​

Declare the CancellationToken on the contract, and the handler receives a standard CancellationToken:

csharp
[SignalARRRContract]
public interface IWorkerClient
{
    Task<string> ProcessData(string data, CancellationToken ct);
}

public class WorkerClientHandler : IWorkerClient
{
    public Task<string> ProcessData(string data, CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            // Process chunks...
        }
        return Task.FromResult("completed");
    }
}

// Before StartAsync()
connection.RegisterInterface<IWorkerClient, WorkerClientHandler>();

The token is also cancelled when the connection itself drops, so a handler notices its caller's world ending even if the server never sends an explicit cancellation.

TypeScript client ​

On the TypeScript client, CancellationToken parameters are converted to AbortSignal:

ts
// The contract's wire name, not the bare method name — see /guide/server/contracts-wire-names
connection.onServerMethod('MyApp.Contracts.IWorkerClient|ProcessData', async (data: string, signal: AbortSignal) => {
    for (let i = 0; i < 100; i++) {
        if (signal.aborted) {
            throw new Error('Cancelled');
        }
        await processChunk(data, i);
    }
    return 'done';
});

How it works ​

Internal flow ​

  1. Server serializes CancellationToken parameters as CancellationTokenReference { Id: guid }
  2. Server stores the CancellationGuid in the ServerRequestMessage
  3. Client detects CancellationTokenReference in the arguments
  4. Client creates an AbortController (TypeScript) or CancellationTokenSource (.NET) mapped to the GUID
  5. Client passes the AbortSignal/CancellationToken to the handler
  6. When the server cancels, it sends CancelTokenFromServer with the GUID
  7. Client triggers the abort/cancellation

CancellationManager (TypeScript internals) ​

The CancellationManager class maps GUIDs to AbortController instances:

MethodDescription
create(id)Creates an AbortController, returns its AbortSignal
cancel(id)Calls abort() on the controller, removes from map
remove(id)Removes the controller without aborting

Next steps ​

Released under the Apache-2.0 License.