Polymorph Thread Fabric
Jakob Jenkov |
Polymorph Thread Fabric is the thread execution toolkit that comes with Polymorph Fabric. Polymorph Thread Fabric is designed around a single-threaded concurrency model - meaning you can do task switching within a single thread. Polymorph Thread Fabric is designed for use inside P2P nodes, which will function as both servers and routers. Routers have different needs than servers, so a single-threaded server / router design using non-blocking IO is best (in my opinion). So - that is what Polymorph Thread Fabric is designed to support.
The Agent Model
Overall, I call the single-threaded concurrency design model the Thread Fabric is based on, for The Agent Model.
An Agent is the execution unit of the agent model. An agent can represent either a short-lived task, or a long-lived job. The lifespan of an agent is up to the developer implementing it to decide.
An agent is "activated" by calling a method on it called execInc() - which
is short for executed increment. When the
execInc()
method is called, the agent executes an increment of its work.
If the workload is small, the entire workload can be executed within a single call
to execInc()
. If the workload is larger, the agent can break down the
workload into multiple increments, one of which is executed each time
execInc()
is called - until the agent eventually completes the full
workload.
Increment Granularity is Up to You
It is up to you to decide how much work should be executed in a single call to
an Agent's execInc()
method. That depends entirely on what the needs
are of your system. Too small increments results in a lot of CPU time being spent
switching between Agents. Too large increments results in Agents with big workloads
"blocking" the CPU for Agents with smaller workloads - causing the latter Agents
to wait longer to be given the chance to execute.
Agents are Not Reactive
Agents are not reactive. In a reactive design a reactive component is typically called whenever an event occurs that is relevant for that component.
Agents, on the other hand, are called whenever there is CPU time available by the Thread Loop executing it. Thread Loops are explained in the next section. Because Agents are not necessarily called in response to some domain event, I would not really call it a reactive design - although there are some similarities.
Thread Loops
A Thread Loop is a Java Runnable implementation that runs in a loop until it is requested to stop.
Inside the loop the Thread Loop calls one or more Agents repeatedly. Each Agent's execInc()
method is called, giving the Agent an opportunity to execute an increment of its total workload.
A Thread Loop can contain more than one Agent. Thus, the Thread Loop can switch between executing the
different Agents - by switching between calling execInc()
on the different Agents.
Obviously, task switching (switching between Agents) only really occurs if the Agents have a larger workload
than what is executed within a single increment - a single call to execInc()
.
If the entire workload can be executed within a single call to execInc()
, then Agents will
end up being executed sequentially, not interleaved.
Thread Loop Groups
Thread Loops can be grouped into Thread Loop Groups. A Thread Loop Group typically consists of a set of Thread Loops that are responsible for similar functionality. For instance, you could have a group of thread loops that are responsible for calling REST services asynchronously.
Thread Loop Communication
Thread Loops can communicate with other Thread Loops in three ways:
The first way is to send an Agent for execution to a specific Thread Loop. Every Thread Loop has a an Agent Port and a Message Port. These ports are like inboxes which each Thread Loop reads from when it has capacity to take on more work. If Thread Loop A has a reference to Thread Loop B, then Thread Loop A can send Agents or messages to Thread Loop B directly, via these ports.
The second way is that an Agent running in one Thread Loop can send messages to other Thread Loops. These messages are typically intended for a specific Agent within that Thread Loop - but since an Agent may pause itself while waiting for a message, the messages are sent to the Thread Loop which detects the message and forwards it to the paused Agent. The Agent can then process the message and remain paused, or decide to resume itself, or even to terminate itself.
The third way is to send an Agent to the Agent port of a Thread Loop Group. The Thread Loops within that Thread Loop Group can take Agents from the Thread Loop Group Agent port if their own Agent ports are empty.
Thread Fabric
A Thread Fabric typically consists of a management thread, and multiple Thread Loop Groups - each with a designated purpose. The management thread manages the scaling up and down of the Thread Loop Groups, as well as shutting them all down when the Thread Fabric is requested to shutdown.
The most common Thread Loop Group purposes would be:
- Foreground tasks
- Compute tasks
- Network tasks
- File system tasks
Foreground tasks are tasks that react to the application's interface, for instance listening for incoming messages on network connections, or responding to UI events.
Compute tasks are tasks that are heavy on calculations, such as calculating a hash of a block of data, or compressing / decompressing data, or encrypting / decrypting data.
Network tasks are tasks that require network communication, such as calling a remote REST service or downloading a file from a server.
File system tasks are tasks that access the local file system of the computer the Thread Fabric is running on.
Tweet | |
Jakob Jenkov |