Table of Contents

Interface ILiveRecordHandler

Namespace
DatabentoDotNet.Extensions.Hosting
Assembly
DatabentoDotNet.Extensions.Hosting.dll

Receives the records a live session decodes. Registered with AddRecordHandler<THandler>().

public interface ILiveRecordHandler

Examples

internal sealed class TradePrinter : ILiveRecordHandler
{
    private readonly List<string> _batch = [];

    public void OnRecord(scoped RecordRef record)
    {
        // Copy out what you need. The RecordRef points into the decoder's buffer and is valid
        // for this call only — the next fill may shift it.
        if (record.TryGet(out TradeMsg trade))
        {
            _batch.Add($"{DbnTime.ToInstant(trade.IndexTs)} {trade.Price} x {trade.Size}");
        }
    }

    public async ValueTask OnFlushAsync(CancellationToken cancellationToken)
    {
        if (_batch.Count == 0)
        {
            return;   // an already-completed ValueTask allocates nothing
        }

        await WriteAsync(_batch, cancellationToken);
        _batch.Clear();
    }
}

Remarks

Two methods, because there is no third option. An async method cannot take a ref struct across an await — CS4007 — so a record can only be handed over synchronously. OnRecord(scoped RecordRef) is that hand-over and OnFlushAsync(CancellationToken) is where the I/O goes.

The alternative costs two allocations per record and was rejected for that. LiveClient.RecordsAsync yields an OwnedRecord and is public; a caller who wants it needs no help from this package. What this package promises is the guarantee LiveAllocationTests asserts, in the one package whose reason to exist is that guarantee.

Implementations are singletons. A DI scope per record would allocate and defeat the contract. A handler needing scoped services opens a scope inside OnFlushAsync(CancellationToken), which is where its I/O belongs anyway.

An exception from either method ends the session. Swallowing it would lose market data invisibly, which is the failure class this codebase exists to convert into loud ones. A handler that wants to carry on catches its own.

Methods

OnFlushAsync(CancellationToken)

Called once per socket fill, after every buffered record has been drained. Where I/O goes.

ValueTask OnFlushAsync(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

Cancelled when the session is stopping.

Returns

ValueTask

Remarks

Awaiting an already-completed ValueTask allocates nothing, so a handler with nothing to flush costs nothing.

OnRecord(scoped RecordRef)

Called once per record, inside the drain. The record is valid for this call only.

void OnRecord(scoped RecordRef record)

Parameters

record RecordRef

The record, reinterpreted in place over the decoder's buffer. Copy out what you need; do not keep the reference.