Network Buffers
Kompact uses a BufferPool system to serialize network messages. This section describes the BufferPools briefly and how they can be configured with different parameters.
Before we begin describing the Network Buffers we remind the reader that there are two different methods for sending messages over the network in Kompact:
- Lazy serialisation
dst.tell(msg: M, from: S);
- Eager serialisation
dst.tell_serialised(msg: M, from: &self);
With lazy serialisation the Actor moves the data to the heap, and transfers it unserialised to the NetworkDispatcher
, which later serialises the message into its (the NetworkDispatcher
‘s) own buffers.
Eager serialisation serialises the data immediately into the Actor’s buffers, and then transfers ownership of the serialised the data to the NetworkDispatcher
.
Lazy serialisation may fail due to two reasons: A serialisation error, or there are no available buffers. Both will be unnoticeable to the actor initiating the message sending, and both will lead to the message being lost.
How the Buffer Pools work
Buffer Pool locations
In a Kompact system where many actors use eager serialisation there will be many BufferPool
instances. If the actors in the system only use lazy serialisation there will be a single pool, owned by the NetworkThread
for serialising and receiving data.
BufferPool, BufferChunk, and ChunkLease
Each BufferPool
(pool) consists of more than one BufferChunk
(chunks). A chunk is the concrete memory area used for serialising data into. There may be many messages serialised into a single chunk, and discrete slices of the chunks (i.e. individual messages) can be extracted and sent to other threads/actors through the smart-pointer ChunkLease
(lease). When a chunk runs out of space it will be locked and returned to the pool. If and only if all outstanding leases created from a chunk have been dropped may the chunk be unlocked and reused, or deallocated.
When a pool is created it will pre-allocate a configurable amount of chunks, and will attempt to reuse those as long as possible, and only when it needs to will it allocate more chunks, up to a configurable maximum number of chunks.
BufferPool interface
Actors access their pool through the EncodeBuffer
wrapper which maintains a single active chunk at a time, and automatically swaps the active buffer with the local BufferPool
when necessary.
The method tell_serialised(msg, &self)
automatically uses the EncodeBuffer
interface such that users of Kompact do not need to use the interfaces of the pool (which is why the method requires a self
reference).
BufferPool initialization
Actors initialize their local buffers automatically when the first invocation of tell_serialised(...)
occurs. If an Actor never invokes the method it will not allocate any buffers.
An actor may call the initialization method through the call self.ctx.borrow().init_buffers(None, None);
1 to explicitly initialize the local BufferPool
without sending a message.
BufferConfig
Parameters
There are four configurable parameters in the BufferConfig:
chunk_size
: The size (in bytes) of theBufferChunks
. Default value is 128KB.initial_chunk_count
: How manyBufferChunks
theBufferPool
will pre-allocate. Default value is 2.max_chunk_count
: The maximum number ofBufferChunks
theBufferPool
may have allocated simultaneously. Default value is 1000.encode_buf_min_free_space
: When an Actor begins serialising a message theEncodeBuffer
will compare how much space (in bytes) is left in the active chunk and compare it to this parameter, if there is less free space the active chunk will be replaced with a new one from the pool before continuing the serialisation. Default value is 64 bytes.
Configuring the Buffers
Individual Actor Configuration
If no BufferConfig
is specified Kompact will use the default settings for all BufferPools
. Actors may be configured with individual BufferConfigs
through the init_buffer(Some(config), None)
1 config. It is important that the call is made before any calls to tell_serialised(...)
. For example, the on_start()
function of the ComponentLifecycle
may be used to ensure this, as in the following example:
impl ComponentLifecycle for CustomBufferConfigActor {
fn on_start(&mut self) -> Handled {
let mut buffer_config = BufferConfig::default();
buffer_config.encode_buf_min_free_space(128);
buffer_config.max_chunk_count(5);
buffer_config.initial_chunk_count(4);
buffer_config.chunk_size(256*1024);
self.ctx.borrow().init_buffers(Some(buffer_config), None);
Handled::Ok
}
...
}
Configuring All Actors
If a programmer wishes for all actors to use the same BufferConfig
configuration, a Hocon string can be inserted into the KompactConfig
or loaded from a Hocon-file (see configuration chapter on loading configurations), for example:
let mut cfg = KompactConfig::new();
cfg.load_config_str(
r#"{
buffer_config {
chunk_size: "256KB",
initial_chunk_count: 3,
max_chunk_count: 4,
encode_min_remaining: "20B",
}
}"#,
);
...
let system = cfg.build().expect("KompactSystem");
If a BufferConfig
is loaded into the systems KompactConfig
then all actors will use that configuration instead of the default BufferConfig
, however individual actors may still override the configuration by using the init_buffers(...)
method.
Configuring the NetworkDispatcher and NetworkThread
The NetworkDispatcher
and NetworkThread
are configured separately from the Actors and use their buffers for lazy serialisation and receiving data from the network. To configure their buffers the NetworkConfig
may be created using the method ::with_buffer_config(...)
as in the example below:
let mut cfg = KompactConfig::new();
let mut network_buffer_config = BufferConfig::default();
network_buffer_config.chunk_size(512);
network_buffer_config.initial_chunk_count(2);
network_buffer_config.max_chunk_count(3);
network_buffer_config.encode_buf_min_free_space(10);
cfg.system_components(DeadletterBox::new, {
NetworkConfig::with_buffer_config(
"127.0.0.1:0".parse().expect("Address should work"),
network_buffer_config,
)
.build()
});
let system = cfg.build().expect("KompactSystem");
BufferConfig Validation
BufferConfig
implements the method validate()
which causes a panic if the set of parameters is invalid. It is invoked whenever a BufferPool
is created from the given configuration. The validation checks that the following conditions hold true:
chunk_size > encode_buf_min_free_space
chunk_size > 127
max_chunk_count >= initial_chunk_count
The method init_buffers(...)
takes two Option
arguments, of which the second argument has not been covered. The second argument allows users of Kompact to specify a CustomAllocator
: a poorly tested, experimental feature which is left undocumented for the time being.