9 releases

0.2.2 Dec 18, 2023
0.2.1 Nov 25, 2023
0.1.5 Oct 30, 2023
0.1.4 Sep 15, 2023

#369 in Concurrency

AGPL-3.0-or-later

72KB
1K SLoC

Flue is an efficient and secure actor runtime library.

Flue's purpose is to be a support library for concurrency-oriented programs using processes: concurrent execution threads with private memory. Processes may only communicate by passing signals between each other. Signals are sent to routes and received using mailboxes. All routes in a route group are killed if any of them are killed. The post office contains all of the routes in a set of communicating processes. Capabilities both reference and limit access to routes using fine- grained permission flags. Tables are stores of integer- addressed, unforgeable capabilities.

Although Flue provides the low-level support framework and data model for building process-based concurrent programs, it does NOT provide a high-level definition or data type for processes themselves. Users of the Flue library must combine Flue's components with their application's specific data model in order to find the best architecture for their concurrent system.

Please note that signals may only be sent to routes and that mailboxes may only receive signals through the routes that they are bound to.

Capabilities may be used to perform the following operations on their routes, each of which are guarded by their respective permission flag:

  1. Kill: Forcibly terminate the route and all of the other routes in its route group, preventing them from receiving any further messages.
  2. Send: Send a "message" signal to the destination route comprised of data (a simple byte buffer) and a list of capabilities to be imported into the route's mailbox's table.
  3. Monitor: Configures a given mailbox to monitor the capability's route. When the route is closed, either by choice or because of it being killed, the mailbox receives a "down" signal with a permission-less capability to the monitored route. If the route is already closed at the time of monitoring, the mailbox will immediately receive the down signal.
  4. Link: Links a given route group to the capability's route group. When either route group dies, the other will also be killed. If either group is already dead, the other will be immediately killed. A link can be removed at any time by unlinking the two route groups. Unlike monitoring, a link between two route groups persists even if the linked capability's route is closed.

A capability may also be "demoted" to a new capability that refers to the same route but with a subset of the original's permissions. This can be used to limit another's process access to a route by restricting the permission flags on the route's capability that is sent to that process.

Flue is made for the purpose of efficiently executing potentially untrusted process code, so to support running that code, Flue's security model has the following axioms:

  • Routes can only be accessed by a process if a capability to that route has explicitly been passed to that process in a signal. You may sandbox a process on a fine-grained level by limiting which capabilities get passed to it.
  • Capabilities may only be created from other capabilities with either an identical set or a subset of the original's permissions. Permission escalation by untrusted processes is impossible.

Here's a box diagram of two processes who each have a mailbox and a capability to the other's mailbox to help explain the mental model of a full Flue-based actor system:

        Process A              Post Office               Process B
┌───────────────────────┐    ┌─────────────┐     ┌───────────────────────┐
│         Table         │    │             │     │         Table         │
│ ┌───────────────────┐ │    │ ┌─────────┐ │     │ ┌───────────────────┐ │
│ │                   │ │    │ │         │ │     │ │                   │ │
│ │ ┌───────────────┐ │ │ ┌──┼─► Route B ├─┼───┐ │ │ ┌───────────────┐ │ │
│ │ │ Capability A  ├─┼─┼─┘  │ │         │ │   │ │ │ │ Capability B  ├─┼─┼─┐
│ │ └───────────────┘ │ │    │ └─────────┘ │   │ │ │ └───────────────┘ │ │ │
│ │                   │ │    │             │   │ │ │                   │ │ │
│ └────────▲──────────┘ │    │ ┌─────────┐ │   │ │ └────────▲──────────┘ │ │
│          │            │    │ │         │ │   │ │          │            │ │
│   ┌──────┴───────┐    │ ┌──┼─┤ Route A ◄─┼─┐ │ │   ┌──────┴───────┐    │ │
│   │   Mailbox A  ◄────┼─┘  │ │         │ │ │ └─┼───►   Mailbox B  │    │ │
│   └──────────────┘    │    │ └─────────┘ │ │   │   └──────────────┘    │ │
│                       │    │             │ │   │                       │ │
└───────────────────────┘    └─────────────┘ │   └───────────────────────┘ │
                                             │                             │
                                             └─────────────────────────────┘

Both processes share a post office, and mailboxes A and B have associated routes A and B in that mailbox. Capability A belongs in process A's table and references route B, and capability B lives in process B's table and references route A. Mailboxes A and B reference their associated process's table so that when they receive capabilities, they can insert those capabilities into their process's table.

To send a message signal from process A to process B using capability A, Flue performs the following steps on that message:

  1. Process A's table confirms that capability A does indeed have the permission to send messages.
  2. Process A's table sends the message to route B's address in the post office.
  3. The post office looks up route B by address, finds mailbox B's zero-copy message channel, and uses it to send the message.
  4. The message waits in mailbox B's queue until mailbox B receives it.
  5. Mailbox B processes the message and inserts any capabilities inside of it into process B's table.

Dependencies

~3–9.5MB
~68K SLoC