Concurrency
Concurrency in AEL, the Agent Engineering Language, is built on agents that own their state and exchange messages. On microcontrollers it is safe, with bounded mailboxes, timers, cooperative scheduling, supervision and restarts. This page describes the model; the operations themselves belong to the Standard library and core types.
One handler at a time
- An agent's private state is used by at most one handler at a time. A handler keeps sole use of that state, even while it waits for a reply or a timer.
- While one handler waits, other handlers run.
- Agents share no changeable state. They cooperate by sending each other messages.
- A start-up handler prepares its agent and returns. Work that repeats is driven by a timer or by messages, never by a start-up handler that loops forever.
Mailboxes
Every agent receives messages through a mailbox of fixed capacity.
- A mailbox hands over its messages in the order it accepted them. Messages from different senders have no other order than that.
- Sending never waits. The result says whether the message was accepted, or the mailbox was full or closed.
- A message that was not accepted comes back to the sender, which can retry, send it elsewhere or drop it. It is never lost and never owned twice.
- Accepted means queued: it does not mean the receiver has handled the message.
Requests and replies
A request is a message that expects one reply.
- Before the request is sent, room for its reply is reserved. If that fails, nothing is sent and you get your request back.
- Every request has a finite deadline.
- A request ends exactly once: with its reply, an error, a timeout or a cancellation.
- A reply that arrives late, or twice, is discarded safely and cannot complete the request a second time.
Cancellation
Cancelling work, directly or through a timeout:
- stops it from starting new child work;
- releases the messages it had queued;
- cancels the child work already running.
Cancellation cannot undo an effect outside the program, such as a payment already sent to another service. Such an effect is reported as uncertain, for your code to reconcile.
Timers
- Timers measure time that only moves forward, and survive the wraparound of a hardware counter.
- On a microcontroller the number of timers is fixed when you build, and running out of timers is a typed result, not a crash.
- A timer that is cancelled or reused can never wake the wait it used to belong to.
Time describes timers, deadlines and schedules in the standard library.
Cooperative scheduling
A handler runs until it waits for something: a reply, a message or a timer. Nothing interrupts a handler that never waits, so:
- keep the work between two waits bounded;
- keep a hardware watchdog for the case where a handler does not return;
- on a microcontroller, let interrupt handlers only record that something happened, and do the work in an ordinary handler.
These rules keep agents from racing on shared state and keep messages from being lost or owned twice. On their own, they do not guarantee that work meets a deadline.
Waiting for several results
You can start several pieces of work and then wait for all of them, take the first to finish, or wait until enough of them succeed. The number of such children is bounded, and when one result decides the outcome, the remaining children are cancelled and cleaned up before the outcome is reported.
An agent spawns subagents at run time: short-lived agents that each do one task, return a typed result to the agent that spawned them and end, leaving nothing running. One agent can spawn more than a hundred subagents for a single request, while only a bounded number run at the same time.
spawnstarts a subagent and gives you its handle. When as many run as the limit allows, it waits for a free slot within the deadline;try_spawnnever waits.joinwaits for one subagent's result,join_allfor the results of a list of handles, in the order of the list, andjoin_anyfor the first subagent to end, cancelling the others.- Waiting is cooperative, like every wait on this page: other handlers run meanwhile, and no operating-system thread is created for a spawn.
Subagents describes the operations and their results.
Supervision
Failures come in three kinds, and supervision treats them differently:
| Failure | Handling |
|---|---|
A Result error | Returned to the caller like any value. |
| A fault that the supervisor may recover from | The agent restarts, within limits you set. |
| A fatal defect, such as corrupted state | No restart. The program stops, or a board goes to its safe state. |
A restart policy states how many restarts are allowed, with what delay between them, and what happens when the allowance runs out. On a restart:
- the agent's state is reset or, when the agent declares a storage point, restored from a checkpoint saved there: a run is durable only when its agent declares a storage point (Bindings);
- the messages waiting in its mailbox are discarded with failure replies unless you choose to keep or replay them;
- replies meant for the agent before its restart cannot reach the restarted agent;
- the restart spends from the same budget and keeps the same deadline as the original work.
An agent that drives an actuator restarts only through a safe state that you define.
On microcontrollers
On the smallest boards, every agent, mailbox and timer is known when you build, each with its fixed capacity, and nothing needs a heap. Microcontroller boards describes how the boards run this model.
Ownership across waits
A value in a message moves to the receiver. References never travel in messages and never survive a wait; see Ownership.