-
Notifications
You must be signed in to change notification settings - Fork 898
Data Structures
To support the transport of messages from senders to receivers a number of data structures are required. These data structures need to conform to a number of design principles for the common transport path, exceptional cases can be afforded an exceptional path, e.g. large messages:
- Copy-Free: The send and receive buffer is directly used.
- Allocation-Free: The send and receive buffer is pre-allocated so that the transport path is free from the allocation or reclamation of memory.
- Lock-Free: Lock-free techniques are employed to manage concurrent access.
- Wait-Free: Concurrent algorithms will complete within a finite number of steps.
- Persistent/Immutable: Message buffers for send and receive record immutable history to allow replay and resend semantics.
- O(1) Cost: All operations are constant time regardless of buffer size, senders, receivers, contention, etc.
The sender needs to be able to support the efficient retransmission of messages. Messages may need to be retransmitted to cope with loss or late joiners to a communications stream. Each archive represents a term in the communication history from the source.
Sender Archive - Memory Mapped File +--------------------------------------------+---------------------+---------------+ | Message buffer (64-byte aligned units) | Index (msg offsets) | State Trailer | +--------------------------------------------+---------------------+---------------+
Messages are stored to the buffer in preparation for transmission as a sequential stream with FIFO semantics. The next n bytes of the buffer are claimed by performing an atomic increment of the tail counter stored in the state trailer. The algorithm is designed to take advantage of LOCK XADD on x86 to avoid spinning CAS (LOCK CMPXCHG) loops.
The producers of message call send(bytes)
on a source object passing the bytes to be sent. The send call then follows the following algorithm:
- IF message is bigger than transmission frame size THEN
- Send in chunks
- END IF
- WHILE next capacity claim < message size
- Mark claimed capacity with tombstone
- Move on to next term
- END WHILE
- Copy in message
- Mark record in archive as complete
A sender thread observes the sender archive and repeatedly performs the following algorithm:
- Assign message sequence
- Update index for message sequence offset
- Send message on underlying network layer
Each archive contains an index of messages that can be used for the fast lookup of messages from sequence numbers.
The state trailer contains the variables describing the current state of the archive for the communications term. Variables are stored with consideration for false sharing issues when concurrent access is required.
- tail: buffer offset at which the next message can be added. An atomic get-and-increment operation is used to advance the tail for claiming capacity in the buffer.
- head: the offset in the buffer up to which messages have be transported.
- term: current term of the archive
- source: identifier for the source of messages
The receiver archive is a mirror of the sender archive with reciprocal semantics for assembling a sequence of messages from a sender over an unreliable network layer.
A receiver thread receives messages from the network layer into the receiver archive.