Table of Contents

Class Transport<T>

Namespace
MarymoorStudios.Core.Promises
Assembly
MarymoorStudios.Core.Promises.dll

A FIFO queue of ordered discrete items.

public abstract class Transport<T> : IAsyncDisposable

Type Parameters

T

The item type.

Inheritance
Transport<T>
Implements
Derived
Inherited Members

Remarks

The Transport<T> is a FIFO for communicating a totally ordered series of "items" from a producer to a consumer. Though the Transport<T> is thread-safe, it supports only a single producer and a single consumer at one time. The producer and consumer MAY (and usually do) operate in separate threads. (The producer and consumer MAY operate in a single thread, but care should be taken when waiting on the handles (see below) to ensure the thread is not live-locked.)

A Transport<T> takes "ownership" of any item written to the queue and subsequently transfers "ownership" to the consumer when the item is read. The consumer is responsible for releasing or disposing any linear resources transferred through the queue (including memory buffers).

The Transport<T> supports efficient blocking for both producer and consumer operations through the ProducerEvent and ConsumerEvent properties respectively. The producer handle is guaranteed to be signaled ONLY AFTER a failed enqueue operation due to the queue being full. Similarly, the consumer handle is guaranteed to be signaled ONLY AFTER a failed dequeue operation due to the queue being empty. Both the producer and the consumer should loop performing their operations until a blocking condition is so indicated before attempting to wait on their respective handle. The wait handles MAY over-signal (i.e. signal even though the corresponding operation will fail due to resource constraints). The caller should be prepared to wait again if that happens.

All events will be signaled at least once if the buffer is "closed" by either party. Events are NOT guaranteed to be signaled MORE THAN ONCE after a Close(TransportFlags), so both parties MUST aggressively respond to closure indications. It is "safe" to call any operation after a successful "close" by either (or both) parties. Producers will receive a persistent AbortedException on all enqueue attempts after closure. Consumers will receive a persistent AbortedException on all dequeue attempts ONLY AFTER all pending items have been delivered (allowing the consumer to release and dispose all pending resources safely). Both parties MUST call Close(TransportFlags) at least once.

In general the consumer "owns" the Transport<T> and is responsible for calling DisposeAsync() when it is no longer needed. The consumer MUST ensure that the producer has released any references to the ProducerEvent which it does so by calling Close(TransportFlags) on its side and receiving a true response. The producer should NEVER access any part of the Transport<T> or its handles AFTER calling Close(TransportFlags) for its side. The consumer MUST release any references to the ConsumerEvent BEFORE calling DisposeAsync(). If the consumer initiates closure it should:

  1. Call TryDequeue(out T) in a loop until the queue is empty, then
  2. Call Close(TransportFlags) again. If Close(TransportFlags) returns true then it is "safe" to dispose, otherwise.
  3. Wait on ConsumerEvent until it signals (indicating that the producer has called Close(TransportFlags)) and then dispose.

It is "safe" for the producer to dispose the Transport<T> ONLY IF the consumer was never successfully started.

Constructors

Transport()

protected Transport()

Properties

ConsumerEvent

Event to efficiently signal the consumer when items are available to read.

public abstract WaitHandle ConsumerEvent { get; }

Property Value

WaitHandle

Remarks

This event only signals on the transition between an empty queue and having at least one item, so the caller should read the queue to completion before waiting on this event.

ProducerEvent

Event to efficiently signal the producer when slots are available to write.

public abstract WaitHandle ProducerEvent { get; }

Property Value

WaitHandle

Remarks

This event only signals on the transition between a full queue and having at least threshold items, so the caller should write to the queue until full before waiting on this event.

Methods

Close(TransportFlags)

Marks the buffer as closed atomically.

public abstract bool Close(TransportFlags flags = TransportFlags.Closed)

Parameters

flags TransportFlags

Indicates which side(s) to close.

Returns

bool

True if the both sides have closed, false if only one side has closed as of this call.

Remarks

No more items can be inserted after this call returns.

Either side (producer or consumer) can close the buffer.

Typically, the consumer will continue to read items until TryDequeue(out T) indicates the closure has been seen. This ensures that all buffered items have been removed, regardless of which side closed first.

Both sides should close the buffer before the last side to close can then safely dispose.

DisposeAsync()

public ValueTask DisposeAsync()

Returns

ValueTask

DisposeAsync(bool)

Standard dispose pattern for subclasses.

protected virtual ValueTask DisposeAsync(bool disposing)

Parameters

disposing bool

True if both native and managed resources are being disposed. False if only native resources (i.e. in finalizer). When native only then MUST be prompt.

Returns

ValueTask

~Transport()

Finalizer.

protected ~Transport()

TryDequeue(out T)

Attempt to remove an item from the front of the buffer.

public abstract bool TryDequeue(out T item)

Parameters

item T

If successful, the item removed from the buffer, default otherwise.

Returns

bool

True if an item was successfully remove, false if the buffer is empty.

Remarks

If item is successfully removed the caller takes ownership of it and is responsible for releasing its resources. If the attempt to dequeue fails then item is default.

Exceptions

AbortedException

If the buffer has been closed AND all buffered items have been read.

TryEnqueue(ref T)

Attempt to insert an item at the end of the buffer.

public abstract bool TryEnqueue(ref T item)

Parameters

item T

The item to be inserted.

Returns

bool

True if the item was inserted, false if the buffer is full.

Remarks

On a successful insert the buffer takes ownership of item and its value is set to default. If item cannot be inserted (either because 'full' or 'closed') then false is returned and item is unmodified (still owned by the caller); the caller is then responsible for releasing any resources.

Exceptions

AbortedException

If the buffer has already been closed.