{"id":18113360,"url":"https://github.com/jvdwrf/tiny-actor","last_synced_at":"2025-08-22T06:32:23.483Z","repository":{"id":44152350,"uuid":"511882345","full_name":"jvdwrf/tiny-actor","owner":"jvdwrf","description":"A minimal and unopinionated actor framework for Rust.","archived":false,"fork":false,"pushed_at":"2022-12-31T00:16:16.000Z","size":287,"stargazers_count":70,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-11T00:03:47.222Z","etag":null,"topics":["rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jvdwrf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-08T12:11:22.000Z","updated_at":"2024-12-04T11:11:23.000Z","dependencies_parsed_at":"2023-01-31T17:16:09.649Z","dependency_job_id":null,"html_url":"https://github.com/jvdwrf/tiny-actor","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jvdwrf%2Ftiny-actor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jvdwrf%2Ftiny-actor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jvdwrf%2Ftiny-actor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jvdwrf%2Ftiny-actor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jvdwrf","download_url":"https://codeload.github.com/jvdwrf/tiny-actor/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230568586,"owners_count":18246378,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rust"],"created_at":"2024-11-01T02:08:00.725Z","updated_at":"2024-12-20T10:08:09.726Z","avatar_url":"https://github.com/jvdwrf.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tiny-actor\n[![Crates.io](https://img.shields.io/crates/v/tiny-actor)](https://crates.io/crates/tiny-actor)\n[![Documentation](https://docs.rs/tiny-actor/badge.svg)](https://docs.rs/tiny-actor)\n\nTiny-actor is a minimal and unopinionated actor library for Rust.\n\nThis library merges the concepts of `Inboxes` and `tasks`, which results in actors: This basic building-block allows us to build simple pools and supervision-trees with reliable shutdown behaviour.\n\nTiny-actor will not be going for more advanced API's, but acts as a simple way to write well-behaving tokio-actors. (as nicely explained [here](https://ryhl.io/blog/actors-with-tokio/)) \n\nIf you're looking for a fully-fledged actor-framework, then please take a look at [Zestors](https://github.com/Zestors/zestors). It builds further on building blocks of tiny-actor.\n\n# Concepts\nThe following gives a quick overview of all concepts of tiny-actor. For more detailed information about usage, please refer to the crate [documentation](https://docs.rs/tiny-actor).\n\n## Channel\nA `Channel` is that which couples `Inboxes`, `Addresses` and `Children` together. Every `Channel` contains the following rust-structs: \n* A single `Child(Pool)`.\n* One or more `Addresses`.\n* One or more `Inboxes`.\n\nThe following diagram shows a visual representation of the naming used:\n```other\n|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|\n|                            Channel                          |\n|  |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|  |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|  |\n|  |              actor                |  |   Child(Pool)  |  |\n|  |  |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|  |  |________________|  |\n|  |  |         process(es)         |  |                      |\n|  |  |  |¯¯¯¯¯¯¯¯¯¯|  |¯¯¯¯¯¯¯¯¯|  |  |  |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|  |\n|  |  |  |   task   |  |  Inbox  |  |  |  |  Address(es)   |  |\n|  |  |  |__________|  |_________|  |  |  |________________|  |\n|  |  |_____________________________|  |                      |\n|  |___________________________________|                      |\n|_____________________________________________________________|\n```\n\n## Actor\nThe term `actor` is used to describe a group of `processes` belonging to a single `Channel`.\n\n## Process\nThe term `process` is used to describe the a `task` paired with an `Inbox`. \n\n## Inbox\nAn `Inbox` is a receiver to the `Channel`, and is primarily used to take messages out of the `Channel`. `Inboxes` can be created by spawning new `processes` and should stay coupled to the `task` they were spawned with: An `Inbox` should only be dropped when the `task` is exiting.\n\n## Address\nAn `Address` is the clone-able sender of a `Channel`, and is primarily used to send messages to the `actor`. `Addresses` can be awaited, which returns once the `actor` exits.\n\n## Child(Pool)\nA `Child` is a handle to an `actor` consisting of one `process`. It can be awaited to return the exit-value of the spawned `task`. The `Child` is not clone-able, and therefore unique to the `Channel`. When it is dropped, the `actor` will be `halted` and subsequently `aborted`, this behaviour can be by detaching the `Child`. \n\nA `ChildPool` is similar to a `Child`, except that the `actor` consist of multiple `processes`. The `ChildPool` can be streamed to get the exit-values of all spawned `tasks`. More `processes` can be spawned after the `actor` has been spawned, and it's also possible to `halt` a portion of the `processes` of the `actor`.\n\n## Closing\nOnce `Channel` is `closed`, it is not longer possible to send new messages into it, it is still possible to take out any messages that are left. The `processes` of a closed `Channel` do not have to exit necessarily, but can continue running. Any senders are notified with a `SendError::Closed`, while receivers will receive `RecvError::ClosedAndEmpty` once the `Channel` has been emptied.\n\n## Halting\nA `process` can be `halted` exactly once, by receiving a `RecvError::Halted`, after which it should exit. An `actor` can be partially halted, meaning that only some of it's `processeses` have been `halted`.\n\n## Aborting\nAn `actor` can be `aborted` through tokio's [abort](https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html#method.abort) method. This causes the `tasks` to exit abruptly, and can leave bad state behind, wherever possible, use `halt` instead of `abort`.\n\n## Exiting\nAn `exit` can refer to two seperate events which, with good practise, always occur at the same time:\n* A `process` can exit by dropping it's `Inbox`, once all `Inboxes` of a `Channel` have been dropped the `actor` has `exited`. This type of exit can be retrieved from the `Channel` at any time using `has_exited`.\n* A `task` can exit, which means the `task` is no longer alive. This can only be queried from the `Child(Pool)` by awaiting it or by calling `is_finished`. \n\n## Link\nAn `actor` can either be `attached` or `detached`, which indicates what should happen when the `Child(Pool)` is dropped:\n * If it is `attached` then it will automatically `halt` all `processes`. After the `abort-timer` expires all processes will be `aborted`. \n * If it is `detached`, then nothing happens when the `Child(Pool)` is dropped.\n\n## Capacity\nA `Channel` can either be `bounded` or `unbounded`. \n* A bounded `Channel` can receive messages until it's capacity has been reached. After reaching the capacity, senders must wait until space is available. \n* An unbounded `Channel` does not have this limit, but instead applies a backpressure-algorithm: The more messages in the `Channel`, the longer the sender must wait before it is allowed to send. \n\n## Id\nEvery actor has a unique id generated when it is spawned, this id can not be changed after it's creation.\n\n# Getting started\n\n## Basic example\n```rust\nuse std::time::Duration;\nuse tiny_actor::*;\n\n#[tokio::main]\nasync fn main() {\n    // First we spawn an actor with a default config, and an inbox which receives u32 messages.\n    let (mut child, address) = spawn(Config::default(), |mut inbox: Inbox\u003cu32\u003e| async move {\n        loop {\n            // This loops and receives messages\n            match inbox.recv().await {\n                Ok(msg) =\u003e println!(\"Received message: {msg}\"),\n                Err(error) =\u003e match error {\n                    RecvError::Halted =\u003e {\n                        println!(\"actor has received halt signal - Exiting now...\");\n                        break \"Halt\";\n                    }\n                    RecvError::ClosedAndEmpty =\u003e {\n                        println!(\"Channel is closed - Exiting now...\");\n                        break \"Closed\";\n                    }\n                },\n            }\n        }\n    });\n\n    // Then we can send it messages\n    address.send(10).await.unwrap();\n    address.send(5).await.unwrap();\n\n    tokio::time::sleep(Duration::from_millis(10)).await;\n\n    // And finally shut the actor down, \n    // we give it 1 second to exit before aborting it.\n    match child.shutdown(Duration::from_secs(1)).await {\n        Ok(exit) =\u003e {\n            assert_eq!(exit, \"Halt\");\n            println!(\"actor exited with message: {exit}\")\n        }\n        Err(error) =\u003e match error {\n            ExitError::Panic(_) =\u003e todo!(),\n            ExitError::Abort =\u003e todo!(),\n        },\n    }\n}\n\n```\n\n## Example with ChildPool and custom Config\n```rust\nuse futures::stream::StreamExt;\nuse std::time::Duration;\nuse tiny_actor::*;\n\n#[tokio::main]\nasync fn main() {\n    // First we spawn an actor with a custom config, and an inbox which receives u32 messages.\n    // This will spawn 3 processes, with i = {0, 1, 2}.\n    let (mut pool, address) = spawn_many(\n        0..3,\n        Config {\n            link: Link::Attached(Duration::from_secs(1)),\n            capacity: Capacity::Unbounded(BackPressure::exponential(\n                5,\n                Duration::from_nanos(25),\n                1.3,\n            )),\n        },\n        |i, mut inbox: Inbox\u003cu32\u003e| async move {\n            loop {\n                // Now every actor loops in the same way as in the basic example\n                match inbox.recv().await {\n                    Ok(msg) =\u003e println!(\"Received message on actor {i}: {msg}\"),\n                    Err(error) =\u003e match error {\n                        RecvError::Halted =\u003e {\n                            println!(\"actor has received halt signal - Exiting now...\");\n                            break \"Halt\";\n                        }\n                        RecvError::ClosedAndEmpty =\u003e {\n                            println!(\"Channel is closed - Exiting now...\");\n                            break \"Closed\";\n                        }\n                    },\n                }\n            }\n        },\n    );\n\n    tokio::time::sleep(Duration::from_millis(10)).await;\n\n    // Send it the numbers 0..10, they will be spread across all processes.\n    for num in 0..10 {\n        address.send(num).await.unwrap()\n    }\n\n    // And finally shut the actor down, giving it 1 second before aborting.\n    let exits = pool\n        .shutdown(Duration::from_secs(1))\n        .collect::\u003cVec\u003c_\u003e\u003e() // Await all processes (using `futures::StreamExt::collect`)\n        .await;\n\n    // And assert that every exit is `Ok(\"Halt\")`\n    for exit in exits {\n        match exit {\n            Ok(exit) =\u003e {\n                assert_eq!(exit, \"Halt\");\n                println!(\"actor exited with message: {exit}\")\n            }\n            Err(error) =\u003e match error {\n                ExitError::Panic(_) =\u003e todo!(),\n                ExitError::Abort =\u003e todo!(),\n            },\n        }\n    }\n}\n\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjvdwrf%2Ftiny-actor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjvdwrf%2Ftiny-actor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjvdwrf%2Ftiny-actor/lists"}