├── .cargo
└── config.toml
├── .dockerignore
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
├── wasm-test-runner
│ ├── .gitignore
│ ├── index.js
│ ├── package-lock.json
│ └── package.json
└── workflows
│ ├── ci.yaml
│ ├── coverage.yml
│ ├── integration.yaml
│ ├── publish.yaml
│ ├── wait_for_crate_dependency.sh
│ └── wasm.yaml
├── .gitignore
├── .vscode
└── settings.json
├── CODE_COVERAGE.md
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── README.md
├── codecov.yml
├── config
├── docker-compose.yml
├── docs
└── ractor_logo.svg
├── ractor
├── Cargo.toml
├── benches
│ ├── actor.rs
│ └── async_traits.rs
├── examples
│ ├── a_whole_lotta.rs
│ ├── a_whole_lotta_messages.rs
│ ├── counter.rs
│ ├── monte_carlo.rs
│ ├── output_port.rs
│ ├── philosophers.rs
│ ├── ping_pong.rs
│ └── supervisor.rs
└── src
│ ├── actor.rs
│ ├── actor
│ ├── actor_cell.rs
│ ├── actor_id.rs
│ ├── actor_properties.rs
│ ├── actor_ref.rs
│ ├── derived_actor.rs
│ ├── messages.rs
│ ├── supervision.rs
│ ├── supervision_tests.rs
│ └── tests.rs
│ ├── common_test.rs
│ ├── concurrency.rs
│ ├── concurrency
│ ├── async_std_primitives.rs
│ ├── tokio_primitives.rs
│ └── tokio_with_wasm_primitives.rs
│ ├── errors.rs
│ ├── factory.rs
│ ├── factory
│ ├── discard.rs
│ ├── factoryimpl.rs
│ ├── hash.rs
│ ├── job.rs
│ ├── lifecycle.rs
│ ├── queues.rs
│ ├── ratelim.rs
│ ├── routing.rs
│ ├── stats.rs
│ ├── tests.rs
│ ├── tests
│ │ ├── basic.rs
│ │ ├── draining_requests.rs
│ │ ├── dynamic_discarding.rs
│ │ ├── dynamic_pool.rs
│ │ ├── dynamic_settings.rs
│ │ ├── lifecycle.rs
│ │ ├── priority_queueing.rs
│ │ ├── ratelim.rs
│ │ └── worker_lifecycle.rs
│ └── worker.rs
│ ├── lib.rs
│ ├── macros.rs
│ ├── message.rs
│ ├── pg.rs
│ ├── pg
│ └── tests.rs
│ ├── port.rs
│ ├── port
│ ├── output.rs
│ └── output
│ │ └── tests.rs
│ ├── registry.rs
│ ├── registry
│ ├── pid_registry.rs
│ └── tests.rs
│ ├── rpc.rs
│ ├── rpc
│ ├── call_result.rs
│ └── tests.rs
│ ├── serialization.rs
│ ├── tests.rs
│ ├── thread_local.rs
│ ├── thread_local
│ ├── inner.rs
│ ├── supervision_tests.rs
│ └── tests.rs
│ ├── time.rs
│ └── time
│ └── tests.rs
├── ractor_cluster
├── Cargo.toml
├── README.md
└── src
│ ├── build.rs
│ ├── hash.rs
│ ├── lib.rs
│ ├── macros.rs
│ ├── net.rs
│ ├── net
│ ├── listener.rs
│ └── session.rs
│ ├── node.rs
│ ├── node
│ ├── auth.rs
│ ├── client.rs
│ ├── node_session.rs
│ └── node_session
│ │ └── tests.rs
│ ├── protocol.rs
│ ├── protocol
│ ├── auth.proto
│ ├── control.proto
│ ├── meta.proto
│ └── node.proto
│ ├── remote_actor.rs
│ └── remote_actor
│ └── tests.rs
├── ractor_cluster_derive
├── Cargo.toml
└── src
│ └── lib.rs
├── ractor_cluster_integration_tests
├── Cargo.toml
├── Dockerfile
├── envs
│ ├── auth-handshake.env
│ ├── dist-connect.env
│ ├── encryption.env
│ └── pg-groups.env
├── src
│ ├── derive_macro_tests.rs
│ ├── main.rs
│ ├── ractor_forward_port_tests.rs
│ ├── repl.rs
│ └── tests
│ │ ├── auth_handshake.rs
│ │ ├── dist_connect.rs
│ │ ├── encryption.rs
│ │ ├── mod.rs
│ │ └── pg_groups.rs
└── test-ca
│ ├── LICENSE-MIT
│ ├── README.md
│ └── rsa-2048
│ ├── ca.cert
│ ├── ca.der
│ ├── ca.key
│ ├── client.cert
│ ├── client.chain
│ ├── client.der
│ ├── client.expired.crl.pem
│ ├── client.fullchain
│ ├── client.key
│ ├── client.revoked.crl.pem
│ ├── client.spki.pem
│ ├── end.cert
│ ├── end.chain
│ ├── end.der
│ ├── end.expired.crl.pem
│ ├── end.fullchain
│ ├── end.key
│ ├── end.revoked.crl.pem
│ ├── end.spki.pem
│ ├── inter.cert
│ ├── inter.der
│ ├── inter.expired.crl.pem
│ ├── inter.key
│ └── inter.revoked.crl.pem
├── ractor_example_entry_proc
├── Cargo.toml
└── src
│ └── lib.rs
├── ractor_playground
├── Cargo.toml
└── src
│ ├── distributed.rs
│ ├── main.rs
│ └── ping_pong.rs
├── rustfmt.toml
├── unit-tests-for-wasm32-unknown-unknown.md
└── xtask
├── Cargo.toml
├── README.md
└── src
└── main.rs
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [alias]
2 | xtask = "run --package xtask --"
3 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7 | Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 |
12 | # Remove all target compilation folders from all sub-folders as well
13 | **/target/
14 |
15 | # Remove code-coverage generated files from git
16 | debug/
17 | coverage/
18 | **/*.profraw
19 |
20 | .github/
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Additional context**
24 | Add any other context about the problem here (e.g. Rust version, other crates imported, etc)
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 |
8 | updates:
9 | - package-ecosystem: cargo
10 | directory: /
11 | schedule:
12 | interval: daily
13 |
14 | - package-ecosystem: github-actions
15 | directory: /
16 | schedule:
17 | interval: daily
18 |
--------------------------------------------------------------------------------
/.github/wasm-test-runner/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/.github/wasm-test-runner/index.js:
--------------------------------------------------------------------------------
1 | import puppeteer from "puppeteer";
2 | import Xvfb from "xvfb";
3 | import { spawn } from "child_process";
4 | (async () => {
5 | const workingDir = process.env.WORKING_DIR;
6 | if (workingDir === undefined) {
7 | console.error("Set `WORKING_DIR` to the directory of ractor");
8 | return;
9 | }
10 | const cargoRunner = spawn("wasm-pack test --chrome ./ractor", { cwd: workingDir, stdio: "pipe", shell: true });
11 | cargoRunner.stdout.setEncoding("utf-8");
12 | cargoRunner.stderr.setEncoding("utf-8");
13 | const flagPromise = new Promise((resolve) => {
14 | cargoRunner.stdout.on("data", (data) => {
15 | console.log(data);
16 | if (data.includes("http://127.0.0.1:8000")) {
17 | console.log("flag captured");
18 | resolve();
19 | }
20 | });
21 | });
22 | cargoRunner.stderr.on("data", (data) => {
23 | process.stderr.write(data);
24 | });
25 | const xvfb = new Xvfb({
26 | silent: true,
27 | xvfb_args: ["-screen", "0", '1280x720x24', "-ac"],
28 | });
29 | await new Promise((resolve) => xvfb.start(resolve));
30 | const browser = await puppeteer.launch({
31 | headless: false,
32 | defaultViewport: null,
33 | args: ['--no-sandbox', '--start-fullscreen', '--display=' + xvfb.display()]
34 | });
35 | console.log(await browser.version());
36 | const page = await browser.newPage();
37 | await Promise.race([
38 | flagPromise,
39 | new Promise((_, rej) => setTimeout(() => {
40 | rej(new Error("Timed out when launching wasm-pack test"))
41 | }, 5 * 60 * 1000))
42 | ]);
43 | await page.goto(`http://127.0.0.1:8000`);
44 | await Promise.race([
45 | (async () => {
46 | let lastLog = null;
47 | while (true) {
48 | const logOutput = await page.$("#output");
49 |
50 | if (logOutput === null) {
51 | console.log("#output not found, waiting..");
52 | } else {
53 | const log = await logOutput.evaluate((hd) => hd.innerText);
54 | let currentLogLine;
55 | if (lastLog === null) {
56 | currentLogLine = log
57 | } else {
58 | currentLogLine = log.slice(lastLog.length);
59 | }
60 | lastLog = log;
61 | process.stdout.write(currentLogLine);
62 | if (currentLogLine.includes("test result: FAILED")) {
63 | throw new Error("Test failed");
64 | } else if (currentLogLine.includes("test result: ok")) {
65 | return;
66 | }
67 | }
68 |
69 | await new Promise(res => setTimeout(res, 1000));
70 | }
71 | })(),
72 | new Promise((_, rej) => setTimeout(() => {
73 | rej(new Error("Timed out when running tests.."))
74 | }, 5 * 60 * 1000))
75 | ]);
76 |
77 | await page.close();
78 | await browser.close()
79 | await new Promise((resolve) => xvfb.stop(resolve));
80 | cargoRunner.kill();
81 | process.exit(0);
82 | })()
83 |
--------------------------------------------------------------------------------
/.github/wasm-test-runner/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xvfb-run-test",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1"
7 | },
8 | "author": "",
9 | "license": "ISC",
10 | "description": "",
11 | "dependencies": {
12 | "puppeteer": "^24.6.1",
13 | "xvfb": "^0.4.0"
14 | },
15 | "type": "module"
16 | }
17 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | types: [opened, reopened, synchronize]
8 |
9 | jobs:
10 | test:
11 | name: ${{matrix.name}}
12 | runs-on: ubuntu-latest
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | include:
17 | - name: Run the default tests
18 | package: ractor
19 | # flags:
20 | - name: Test ractor with async-trait
21 | package: ractor
22 | flags: -F async-trait
23 | - name: Test ractor without span propogation
24 | package: ractor
25 | flags: --no-default-features -F tokio_runtime
26 | - name: Test ractor with the `cluster` feature
27 | package: ractor
28 | flags: -F cluster
29 | - name: Test ractor with the `blanket_serde` feature
30 | package: ractor
31 | flags: -F blanket_serde
32 | - name: Test ractor with async-std runtime
33 | package: ractor
34 | flags: --no-default-features -F async-std,message_span_propogation
35 | - name: Test ractor with async-std runtime but no span propagation
36 | package: ractor
37 | flags: --no-default-features -F async-std
38 | - name: Test ractor with async-std runtime and async-trait
39 | package: ractor
40 | flags: --no-default-features -F async-std,async-trait
41 | - name: Test ractor_cluster with native async traits
42 | package: ractor_cluster
43 | # flags:
44 | - name: Test ractor_cluster with async_trait
45 | package: ractor_cluster
46 | flags: -F async-trait
47 | - name: Test ractor with the monitor API
48 | package: ractor
49 | flags: -F monitors
50 |
51 | steps:
52 | - uses: actions/checkout@main
53 |
54 | - name: Install rust
55 | uses: actions-rs/toolchain@v1
56 | with:
57 | toolchain: stable
58 | override: true
59 |
60 | - name: ${{matrix.name}}
61 | uses: actions-rs/cargo@v1
62 | with:
63 | command: test
64 | args: --package ${{matrix.package}} ${{matrix.flags}}
65 |
66 | - name: Test everything
67 | uses: actions-rs/cargo@v1
68 | with:
69 | command: test
70 |
71 | clippy:
72 | name: Clippy
73 | runs-on: ubuntu-latest
74 | steps:
75 | - uses: actions/checkout@main
76 | - name: Install minimal stable with clippy
77 | uses: actions-rs/toolchain@v1
78 | with:
79 | profile: minimal
80 | toolchain: stable
81 | components: clippy
82 | override: true
83 |
84 | - name: Run Clippy
85 | uses: actions-rs/cargo@v1
86 | with:
87 | command: clippy
88 | args: --all -- -D clippy::all -D warnings
89 |
90 | rustfmt:
91 | name: rustfmt
92 | runs-on: ubuntu-latest
93 | steps:
94 | - uses: actions/checkout@main
95 | - name: Install minimal stable with rustfmt
96 | uses: actions-rs/toolchain@v1
97 | with:
98 | profile: minimal
99 | toolchain: stable
100 | components: rustfmt
101 | override: true
102 |
103 | - name: Run rustfmt
104 | uses: actions-rs/cargo@v1
105 | with:
106 | command: fmt
107 | args: --all -- --check
108 |
109 | docs:
110 | name: docs
111 | runs-on: ubuntu-latest
112 | steps:
113 | - uses: actions/checkout@main
114 | - name: Install minimal stable with rustfmt
115 | uses: actions-rs/toolchain@v1
116 | with:
117 | profile: minimal
118 | toolchain: stable
119 | components: rustfmt
120 | override: true
121 |
122 | - name: Run cargo doc on release profile
123 | uses: actions-rs/cargo@v1
124 | with:
125 | command: doc
126 | args: --lib -r
127 |
128 | benches:
129 | name: benches
130 | runs-on: ubuntu-latest
131 | steps:
132 | - uses: actions/checkout@main
133 | - name: Install rust
134 | uses: actions-rs/toolchain@v1
135 | with:
136 | toolchain: stable
137 | override: true
138 |
139 | - name: Compile benchmarks
140 | uses: actions-rs/cargo@v1
141 | with:
142 | command: bench
143 | args: --no-run
144 |
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | name: Code Coverage
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | types: [opened, reopened, synchronize]
8 |
9 | jobs:
10 | coverage:
11 | name: Coverage using xtask
12 | strategy:
13 | matrix:
14 | os: [ubuntu-latest]
15 | rust: [stable]
16 | runs-on: ${{ matrix.os }}
17 | steps:
18 | - name: Checkout sources
19 | uses: actions/checkout@v4
20 |
21 | - name: Install stable toolchain
22 | uses: actions-rs/toolchain@v1
23 | with:
24 | toolchain: ${{ matrix.rust }}
25 | override: true
26 | components: llvm-tools-preview
27 |
28 | - uses: Swatinem/rust-cache@v2
29 |
30 | - name: Download grcov
31 | run: |
32 | mkdir -p "${HOME}/.local/bin"
33 | curl -sL https://github.com/mozilla/grcov/releases/download/v0.8.10/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - -C "${HOME}/.local/bin"
34 | echo "$HOME/.local/bin" >> $GITHUB_PATH
35 | - name: Run xtask coverage
36 | uses: actions-rs/cargo@v1
37 | with:
38 | command: run
39 | args: --bin xtask coverage
40 |
41 |
42 | - name: Upload to codecov.io
43 | uses: codecov/codecov-action@v5
44 | with:
45 | fail_ci_if_error: true # optional (default = false)
46 | files: coverage/*.lcov
47 | token: ${{ secrets.CODECOV_TOKEN }} # required
48 | verbose: true # optional (default = false)
49 | name: ractor # optional
50 |
51 |
--------------------------------------------------------------------------------
/.github/workflows/integration.yaml:
--------------------------------------------------------------------------------
1 | name: Ractor Cluster integration tests
2 | on:
3 | push:
4 | branches:
5 | - main
6 | paths:
7 | - 'ractor_cluster*/**'
8 | pull_request:
9 | types: [opened, reopened, synchronize]
10 | paths:
11 | - 'ractor_cluster*/**'
12 |
13 | jobs:
14 | test:
15 | name: Test networked cluster
16 | runs-on: ubuntu-latest
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | features:
21 | - 'blanket_serde,cluster'
22 | - 'blanket_serde,cluster,async-trait'
23 | steps:
24 | - uses: actions/checkout@main
25 |
26 | - name: Build the docker image
27 | working-directory: .
28 | run: |
29 | FEATURES=${{matrix.features}} docker compose build
30 |
31 | - name: Authentication Handshake
32 | working-directory: .
33 | run: |
34 | FEATURES=${{matrix.features}} docker compose --env-file ./ractor_cluster_integration_tests/envs/auth-handshake.env up --exit-code-from node-b
35 |
36 | - name: Process Groups
37 | working-directory: .
38 | run: |
39 | FEATURES=${{matrix.features}} docker compose --env-file ./ractor_cluster_integration_tests/envs/pg-groups.env up --exit-code-from node-b
40 |
41 | - name: Encrypted communications
42 | working-directory: .
43 | run: |
44 | FEATURES=${{matrix.features}} docker compose --env-file ./ractor_cluster_integration_tests/envs/encryption.env up --exit-code-from node-b
45 |
46 | - name: Transitive connections
47 | working-directory: .
48 | run: |
49 | FEATURES=${{matrix.features}} docker compose --env-file ./ractor_cluster_integration_tests/envs/dist-connect.env up --exit-code-from node-c
50 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yaml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | publish:
9 | runs-on: ${{ matrix.os }}
10 | strategy:
11 | matrix:
12 | os: [ubuntu-latest]
13 | rust: [stable]
14 |
15 | steps:
16 | - uses: hecrj/setup-rust-action@v2
17 | with:
18 | rust-version: ${{ matrix.rust }}
19 |
20 | - uses: actions/checkout@main
21 |
22 | - name: Login to crates.io
23 | run: cargo login $CRATES_IO_TOKEN
24 | env:
25 | CRATES_IO_TOKEN: ${{ secrets.crates_io_token }}
26 |
27 | - name: Dry run publish ractor
28 | run: cargo publish --dry-run --manifest-path Cargo.toml -p ractor
29 | - name: Publish crate ractor
30 | run: cargo publish --manifest-path Cargo.toml -p ractor
31 | env:
32 | CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }}
33 |
34 | - name: Dry run publish ractor_cluster_derive
35 | run: cargo publish --dry-run --manifest-path Cargo.toml -p ractor_cluster_derive
36 | - name: Publish ractor_cluster_derive
37 | run: cargo publish --manifest-path Cargo.toml -p ractor_cluster_derive
38 | env:
39 | CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }}
40 |
41 | - name: Wait for necessary ractor version to be available
42 | run: bash ./.github/workflows/wait_for_crate_dependency.sh ractor_cluster ractor
43 | - name: Wait for necessary ractor version to be available
44 | run: bash ./.github/workflows/wait_for_crate_dependency.sh ractor_cluster ractor_cluster_derive
45 |
46 | - name: Dry run publish ractor_cluster
47 | run: cargo publish --dry-run --manifest-path Cargo.toml -p ractor_cluster
48 | - name: Publish ractor_cluster
49 | run: cargo publish --manifest-path Cargo.toml -p ractor_cluster
50 | env:
51 | CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }}
52 |
--------------------------------------------------------------------------------
/.github/workflows/wait_for_crate_dependency.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Finds expected version of a crate in another crate's Cargo.toml file
3 | get_crate_expected_version_number()
4 | {
5 | local INDEX_CRATE=$1
6 | local TARGET_CRATE=$2
7 |
8 | local INDEX_TOML_FILE="$INDEX_CRATE/Cargo.toml"
9 | # Issue #174. The script is looking for multiple entries if the dependency is listed multiple times
10 | # Additionally this regex with grep works for both the notations
11 | # 1. crate = { some_other_options ... version = "x.y.z" ... other_options }
12 | # 2. crate = "x.y.z"
13 | local EXPECTED_VERSION=$(grep "$TARGET_CRATE" $INDEX_TOML_FILE | grep -o '[0-9]\.[0-9\.]\+' | head -n 1)
14 | echo $EXPECTED_VERSION
15 | }
16 |
17 | # Get published versions of a crate from https://github.com/rust-lang/crates.io-index/
18 | get_crate_published_versions()
19 | {
20 | local CRATE_INDEX_URL=$1
21 |
22 | local PUBLISHED_VERSIONS=$(curl -sS "$CRATE_INDEX_URL" | jq .vers)
23 | echo "$PUBLISHED_VERSIONS"
24 | }
25 |
26 | # Retrieve the raw github url for a given crate based on the crate name following
27 | # crates.io's strange indexing strategy
28 | get_crate_raw_github_url() {
29 | local CRATE=$1
30 |
31 | local STR_LEN=$(echo "$CRATE" | wc -c)
32 | STR_LEN=$((STR_LEN - 1))
33 | if (($STR_LEN > 3)); then
34 | local FIRST_TWO=$(echo ${CRATE:0:2})
35 | local SECOND_TWO=$(echo ${CRATE:2:2})
36 | echo "https://raw.githubusercontent.com/rust-lang/crates.io-index/master/$FIRST_TWO/$SECOND_TWO/$CRATE"
37 | else
38 | local FIRST_ONE=$(echo ${CRATE:0:1})
39 | echo "https://raw.githubusercontent.com/rust-lang/crates.io-index/master/$STR_LEN/$FIRST_ONE/$CRATE"
40 | fi
41 | }
42 |
43 | # Wait for a specific crate version to be published to crates.io.
44 | # See https://github.com/novifinancial/akd/issues/116.
45 | # Must be run in the project root folder.
46 | INDEX_CRATE=$1
47 | TARGET_CRATE=$2
48 |
49 | if [ "$INDEX_CRATE" == "" ] || [ "$TARGET_CRATE" == "" ]
50 | then
51 | echo "Both the target crate and index crate are required arguments."
52 | echo "Usage:"
53 | echo "bash ./.github/workflows/wait-for-crate-dependency.sh INDEX_CRATE TARGET_CRATE"
54 | echo " - INDEX_CRATE : The crate which contains the dependency version specification"
55 | echo " - TARGET_CRATE : The crate which version needs to be published to build the INDEX_CRATE"
56 | exit 1
57 | fi
58 |
59 | EXPECTED_VERSION=$(get_crate_expected_version_number "$INDEX_CRATE" "$TARGET_CRATE" || exit 1)
60 | echo "Expecting $TARGET_CRATE = { version = $EXPECTED_VERSION } for $INDEX_CRATE"
61 | TARGET_URL=$(get_crate_raw_github_url "$TARGET_CRATE" || exit 1)
62 | echo "Target URL for $TARGET_CRATE is $TARGET_URL"
63 | WAIT_TIME=1
64 | while sleep $WAIT_TIME;
65 | do
66 | PUBLISHED_VERSIONS=$(get_crate_published_versions "$TARGET_URL" | tr '\n' " ")
67 | echo "Available $TARGET_CRATE versions: $PUBLISHED_VERSIONS"
68 | EXISTS=$(echo $PUBLISHED_VERSIONS | grep "\"$EXPECTED_VERSION\"")
69 | if [[ $EXISTS != "" ]]; then
70 | echo "Expected version of $TARGET_CRATE ($EXPECTED_VERSION) has been published"
71 | break
72 | fi
73 | echo "Expected version of $TARGET_CRATE ($EXPECTED_VERSION) is not yet published. Retrying after a wait"
74 | WAIT_TIME=$((WAIT_TIME+1))
75 | if [[ $WAIT_TIME == 42 ]]; then
76 | echo "Giving up after 42 wait periods"
77 | exit 1
78 | fi
79 | done
80 |
--------------------------------------------------------------------------------
/.github/workflows/wasm.yaml:
--------------------------------------------------------------------------------
1 | name: Unittests for wasm32-unknown-unknown
2 | on:
3 | push:
4 | branches: main
5 | workflow_dispatch:
6 | pull_request:
7 | types: [opened, reopened, synchronize]
8 |
9 | jobs:
10 | unit-test:
11 | name: Run unit tests in browser
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@main
15 |
16 | - name: Install rust
17 | uses: actions-rs/toolchain@v1
18 | with:
19 | toolchain: stable
20 | override: true
21 |
22 | - name: Install wasm-pack
23 | run: cargo install wasm-pack
24 |
25 | - uses: actions/setup-node@v4
26 | with:
27 | node-version: 20
28 |
29 | - name: Setup test runner
30 | run: |
31 | cd ./.github/wasm-test-runner
32 | npm install
33 | - name: Run tests
34 | run: |
35 | export WORKING_DIR=$(pwd)
36 | cd ./.github/wasm-test-runner
37 | node ./index.js
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7 | Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 |
12 | # Remove all target compilation folders from all sub-folders as well
13 | **/target/
14 |
15 | # Remove code-coverage generated files from git
16 | debug/
17 | coverage/
18 | **/*.profraw
19 | **/.DS_Store
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "rust-analyzer.showUnlinkedFileNotification": false
3 | }
--------------------------------------------------------------------------------
/CODE_COVERAGE.md:
--------------------------------------------------------------------------------
1 | # Current code coverage
2 |
3 | ## Current code coverage
4 |
5 | [](https://codecov.io/gh/slawlor/ractor)
6 |
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to this library
2 |
3 | We want to make contributing to this project as easy and transparent as
4 | possible.
5 |
6 | ## Be respectful
7 |
8 | Duh.
9 |
10 | ## Pull Requests
11 |
12 | We actively welcome your pull requests!
13 |
14 | 1. Fork the repo and create your branch from `main`.
15 | 2. If you've added code that should be tested, add tests.
16 | 3. If you've changed APIs, update the documentation.
17 | 4. Ensure the test suite passes.
18 |
19 | ## Issues
20 |
21 | We use GitHub issues to track issues.
22 |
23 | For reported bugs, please ensure your description is clear and has sufficient instructions to be able to reproduce the issue.
24 |
25 | For feature requests, please try to be detailed in what you'd like to see so we can address it properly!
26 |
27 | ## License
28 |
29 | By contributing to `ractor`, you agree that your contributions will be
30 | licensed under the LICENSE file in the root directory of this source tree.
31 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "ractor",
4 | "ractor_cluster",
5 | "ractor_cluster_derive",
6 | "ractor_playground",
7 | "ractor_cluster_integration_tests",
8 | "ractor_example_entry_proc",
9 | "xtask",
10 | ]
11 | resolver = "2"
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Sean Lawlor
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | require_ci_to_pass: false
3 |
4 | ignore:
5 | # This is the code coverage tool.
6 | - "xtask"
7 | # Tests are not covered
8 | - "ractor_playground"
9 | # Tests are not covered
10 | - "ractor/examples"
11 | # Tests are not covered
12 | - "ractor/benches"
13 | # Tests are not covered
14 | - "ractor_cluster_integration_tests"
15 | # Protocol is all generated code and should be excluded from code coverage
16 | - "ractor_cluster/src/protocol"
--------------------------------------------------------------------------------
/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | xtask = "run --package xtask --"
3 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 |
2 | services:
3 | node-a:
4 | container_name: "node-a"
5 | build:
6 | context: .
7 | dockerfile: ractor_cluster_integration_tests/Dockerfile
8 | args:
9 | - FEATURES=${FEATURES}
10 | image: ractor_cluster_tests:latest
11 | networks:
12 | - test-net
13 | entrypoint: ''
14 | command: ractor_cluster_integration_tests test ${A_TEST}
15 | environment:
16 | RUST_LOG: debug
17 | node-b:
18 | depends_on:
19 | - node-a
20 | container_name: "node-b"
21 | build:
22 | context: .
23 | dockerfile: ractor_cluster_integration_tests/Dockerfile
24 | args:
25 | - FEATURES=${FEATURES}
26 | image: ractor_cluster_tests:latest
27 | networks:
28 | - test-net
29 | entrypoint: ''
30 | command: ractor_cluster_integration_tests test ${B_TEST}
31 | environment:
32 | RUST_LOG: debug
33 |
34 | node-c:
35 | depends_on:
36 | - node-b
37 | container_name: "node-c"
38 | build:
39 | context: .
40 | dockerfile: ractor_cluster_integration_tests/Dockerfile
41 | args:
42 | - FEATURES=${FEATURES}
43 | image: ractor_cluster_tests:latest
44 | networks:
45 | - test-net
46 | entrypoint: ''
47 | command: ractor_cluster_integration_tests test ${C_TEST}
48 | environment:
49 | RUST_LOG: debug
50 | networks:
51 | test-net:
52 | external: false
--------------------------------------------------------------------------------
/ractor/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "ractor"
3 | version = "0.15.6"
4 | authors = ["Sean Lawlor", "Evan Au", "Dillon George"]
5 | description = "A actor framework for Rust"
6 | documentation = "https://docs.rs/ractor"
7 | license = "MIT"
8 | edition = "2021"
9 | keywords = ["actor", "ractor"]
10 | repository = "https://github.com/slawlor/ractor"
11 | readme = "../README.md"
12 | homepage = "https://github.com/slawlor/ractor"
13 | categories = ["asynchronous"]
14 | rust-version = "1.64"
15 |
16 | [lints.rust]
17 | unexpected_cfgs = { level = "warn", check-cfg = [
18 | 'cfg(tokio_unstable)',
19 | 'cfg(rust_analyzer)',
20 | ] }
21 |
22 | [features]
23 | ### Other features
24 | cluster = []
25 | monitors = []
26 | message_span_propogation = []
27 | tokio_runtime = ["tokio/time", "tokio/rt", "tokio/macros", "tokio/tracing"]
28 | blanket_serde = ["serde", "pot", "cluster"]
29 | async-trait = ["dep:async-trait"]
30 |
31 | default = ["tokio_runtime", "message_span_propogation"]
32 |
33 | [dependencies]
34 | ## Required dependencies
35 | bon = "2.2"
36 | dashmap = "6"
37 | futures = "0.3"
38 | once_cell = "1"
39 | strum = { version = "0.26", features = ["derive"] }
40 |
41 | ## Configurable dependencies
42 | # Tracing feature requires --cfg=tokio_unstable
43 | async-std = { version = "1", features = ["attributes"], optional = true }
44 | async-trait = { version = "0.1", optional = true }
45 | tokio = { version = "1.30", features = ["sync"] }
46 | tracing = { version = "0.1", features = ["attributes"] }
47 |
48 | ## Blanket Serde
49 | serde = { version = "1", optional = true }
50 | pot = { version = "3.0", optional = true }
51 |
52 |
53 | [target.'cfg(all(target_arch = "wasm32",target_os = "unknown"))'.dependencies]
54 | tokio_with_wasm = { version = "0.8.2", features = [
55 | "macros",
56 | "sync",
57 | "rt",
58 | "time",
59 | ] }
60 | web-time = "1.1.0"
61 |
62 | [dev-dependencies]
63 | backtrace = "0.3"
64 |
65 | function_name = "0.3"
66 | paste = "1"
67 | serial_test = "3.0.0"
68 | rand = "0.8"
69 | tracing-glog = "0.4"
70 | tracing-subscriber = { version = "0.3", features = ["env-filter"] }
71 |
72 | ractor_example_entry_proc = { path = "../ractor_example_entry_proc" }
73 |
74 | [target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies]
75 | tokio = { version = "1.30", features = [
76 | "rt",
77 | "time",
78 | "sync",
79 | "macros",
80 | "rt-multi-thread",
81 | "tracing",
82 | ] }
83 | criterion = "0.5"
84 | tracing-test = "0.2"
85 |
86 | [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies]
87 | tokio = { version = "1.30", features = ["rt", "time", "sync", "macros"] }
88 | wasm-bindgen-test = "0.3.50"
89 | getrandom = { version = "0.2.15", features = ["js"] }
90 | criterion = { version = "0.5", default-features = false }
91 |
92 | [[bench]]
93 | name = "actor"
94 | harness = false
95 | required-features = []
96 |
97 | [[bench]]
98 | name = "async_traits"
99 | harness = false
100 | required-features = []
101 |
--------------------------------------------------------------------------------
/ractor/benches/async_traits.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | //! Benchmarks for specifically keeping large data on the stack. If the future
7 | //! doesn't get boxed, this measures the relative performance impact
8 | //!
9 | //! Comparison of
10 | //! `cargo bench --bench async_traits -p ractor --no-default-features -F tokio_runtime`
11 | //! against
12 | //! `cargo bench --bench async_traits -p ractor --no-default-features -F tokio_runtime,async-trait`
13 |
14 | #[macro_use]
15 | extern crate criterion;
16 |
17 | use criterion::BatchSize;
18 | use criterion::Criterion;
19 | use ractor::Actor;
20 | use ractor::ActorProcessingErr;
21 | use ractor::ActorRef;
22 | #[cfg(feature = "cluster")]
23 | use ractor::Message;
24 |
25 | #[allow(clippy::async_yields_async)]
26 | fn big_stack_futures(c: &mut Criterion) {
27 | const NUM_MSGS: usize = 50;
28 | const NUM_BYTES: usize = 50_000;
29 |
30 | struct LargeFutureActor {
31 | num_msgs: usize,
32 | }
33 |
34 | struct LargeFutureActorState {
35 | cmsg: usize,
36 | data: [u64; NUM_BYTES],
37 | }
38 |
39 | struct LargeFutureActorMessage;
40 | #[cfg(feature = "cluster")]
41 | impl Message for LargeFutureActorMessage {}
42 |
43 | #[cfg_attr(feature = "async-trait", ractor::async_trait)]
44 | impl Actor for LargeFutureActor {
45 | type Msg = LargeFutureActorMessage;
46 |
47 | type State = LargeFutureActorState;
48 |
49 | type Arguments = ();
50 |
51 | async fn pre_start(
52 | &self,
53 | myself: ActorRef,
54 | _: (),
55 | ) -> Result {
56 | let _ = myself.cast(LargeFutureActorMessage);
57 | Ok(LargeFutureActorState {
58 | cmsg: 0usize,
59 | data: [0; NUM_BYTES],
60 | })
61 | }
62 |
63 | async fn handle(
64 | &self,
65 | myself: ActorRef,
66 | _message: Self::Msg,
67 | state: &mut Self::State,
68 | ) -> Result<(), ActorProcessingErr> {
69 | state.cmsg += 1;
70 | state.data[state.cmsg] = state.cmsg as u64;
71 | if state.cmsg >= self.num_msgs {
72 | myself.stop(None);
73 | } else {
74 | let _ = myself.cast(LargeFutureActorMessage);
75 | }
76 | Ok(())
77 | }
78 | }
79 |
80 | let id =
81 | format!("Waiting on {NUM_MSGS} messages with large data in the Future to be processed");
82 | #[cfg(not(feature = "async-std"))]
83 | let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap();
84 | #[cfg(feature = "async-std")]
85 | let _ = async_std::task::block_on(async {});
86 | c.bench_function(&id, move |b| {
87 | b.iter_batched(
88 | || {
89 | #[cfg(not(feature = "async-std"))]
90 | {
91 | runtime.block_on(async move {
92 | let (_, handle) =
93 | Actor::spawn(None, LargeFutureActor { num_msgs: NUM_MSGS }, ())
94 | .await
95 | .expect("Failed to create test actor");
96 | handle
97 | })
98 | }
99 | #[cfg(feature = "async-std")]
100 | {
101 | async_std::task::block_on(async move {
102 | let (_, handle) =
103 | Actor::spawn(None, LargeFutureActor { num_msgs: NUM_MSGS }, ())
104 | .await
105 | .expect("Failed to create test actor");
106 | handle
107 | })
108 | }
109 | },
110 | |handle| {
111 | #[cfg(not(feature = "async-std"))]
112 | {
113 | runtime.block_on(async move {
114 | let _ = handle.await;
115 | })
116 | }
117 | #[cfg(feature = "async-std")]
118 | {
119 | async_std::task::block_on(async move {
120 | let _ = handle.await;
121 | })
122 | }
123 | },
124 | BatchSize::PerIteration,
125 | );
126 | });
127 | }
128 |
129 | criterion_group! {
130 | name=async_traits;
131 | config = Criterion::default()
132 | .sample_size(100);
133 | targets=big_stack_futures
134 | }
135 | criterion_main!(async_traits);
136 |
--------------------------------------------------------------------------------
/ractor/examples/a_whole_lotta.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | //! Just creates a LOT of actors. Useful for measuring max memory util
7 | //!
8 | //! Execute with
9 | //!
10 | //! ```text
11 | //! cargo run --example a_whole_lotta
12 | //! ```
13 |
14 | #![allow(clippy::incompatible_msrv)]
15 |
16 | extern crate ractor;
17 |
18 | use ractor::Actor;
19 | use ractor::ActorProcessingErr;
20 | use ractor::ActorRef;
21 |
22 | struct Counter;
23 |
24 | #[cfg_attr(feature = "async-trait", ractor::async_trait)]
25 | impl Actor for Counter {
26 | type Msg = ();
27 | type State = ();
28 | type Arguments = ();
29 |
30 | async fn pre_start(
31 | &self,
32 | _myself: ActorRef,
33 | _: (),
34 | ) -> Result {
35 | tracing::info!("Starting the actor");
36 | // create the initial state
37 | Ok(())
38 | }
39 | }
40 |
41 | fn init_logging() {
42 | let dir = tracing_subscriber::filter::Directive::from(tracing::Level::DEBUG);
43 |
44 | use std::io::stderr;
45 | use std::io::IsTerminal;
46 |
47 | use tracing_glog::Glog;
48 | use tracing_glog::GlogFields;
49 | use tracing_subscriber::filter::EnvFilter;
50 | use tracing_subscriber::layer::SubscriberExt;
51 | use tracing_subscriber::Registry;
52 |
53 | let fmt = tracing_subscriber::fmt::Layer::default()
54 | .with_ansi(stderr().is_terminal())
55 | .with_writer(std::io::stderr)
56 | .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default()))
57 | .fmt_fields(GlogFields::default().compact());
58 |
59 | let filter = vec![dir]
60 | .into_iter()
61 | .fold(EnvFilter::from_default_env(), |filter, directive| {
62 | filter.add_directive(directive)
63 | });
64 |
65 | let subscriber = Registry::default().with(filter).with(fmt);
66 | tracing::subscriber::set_global_default(subscriber).expect("to set global subscriber");
67 | }
68 |
69 | #[ractor_example_entry_proc::ractor_example_entry]
70 | async fn main() {
71 | init_logging();
72 |
73 | let mut actors = Vec::new();
74 |
75 | for _ in 0..100000 {
76 | actors.push(
77 | Actor::spawn(None, Counter, ())
78 | .await
79 | .expect("Failed to start actor!"),
80 | );
81 | }
82 |
83 | for act in actors.iter() {
84 | act.0.stop(None);
85 | }
86 | for (_, h) in actors.into_iter() {
87 | h.await.expect("Failed to wait for actor shutdown");
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/ractor/examples/a_whole_lotta_messages.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | //! Just creates a LOT of actors. Useful for measuring max memory util
7 | //!
8 | //! Execute with
9 | //!
10 | //! ```text
11 | //! cargo run --example a_whole_lotta_messages
12 | //! ```
13 |
14 | #![allow(clippy::incompatible_msrv)]
15 |
16 | extern crate ractor;
17 |
18 | use ractor::concurrency::Duration;
19 | use ractor::Actor;
20 | use ractor::ActorProcessingErr;
21 | use ractor::ActorRef;
22 |
23 | struct Counter;
24 |
25 | #[cfg_attr(feature = "async-trait", ractor::async_trait)]
26 | impl Actor for Counter {
27 | type Msg = u32;
28 | type State = ();
29 | type Arguments = ();
30 |
31 | async fn pre_start(
32 | &self,
33 | _myself: ActorRef,
34 | _: (),
35 | ) -> Result {
36 | tracing::info!("Starting the actor");
37 | // create the initial state
38 | Ok(())
39 | }
40 |
41 | async fn handle(
42 | &self,
43 | _: ActorRef,
44 | _: Self::Msg,
45 | _: &mut Self::State,
46 | ) -> Result<(), ActorProcessingErr> {
47 | // block the loop so we won't process messages
48 | ractor::concurrency::sleep(Duration::from_secs(10000)).await;
49 | Ok(())
50 | }
51 | }
52 |
53 | fn init_logging() {
54 | let dir = tracing_subscriber::filter::Directive::from(tracing::Level::DEBUG);
55 |
56 | use std::io::stderr;
57 | use std::io::IsTerminal;
58 |
59 | use tracing_glog::Glog;
60 | use tracing_glog::GlogFields;
61 | use tracing_subscriber::filter::EnvFilter;
62 | use tracing_subscriber::layer::SubscriberExt;
63 | use tracing_subscriber::Registry;
64 |
65 | let fmt = tracing_subscriber::fmt::Layer::default()
66 | .with_ansi(stderr().is_terminal())
67 | .with_writer(std::io::stderr)
68 | .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default()))
69 | .fmt_fields(GlogFields::default().compact());
70 |
71 | let filter = vec![dir]
72 | .into_iter()
73 | .fold(EnvFilter::from_default_env(), |filter, directive| {
74 | filter.add_directive(directive)
75 | });
76 |
77 | let subscriber = Registry::default().with(filter).with(fmt);
78 | tracing::subscriber::set_global_default(subscriber).expect("to set global subscriber");
79 | }
80 |
81 | #[ractor_example_entry_proc::ractor_example_entry]
82 | async fn main() {
83 | init_logging();
84 |
85 | tracing::info!("test");
86 |
87 | let (actor, handle) = Actor::spawn(None, Counter, ())
88 | .await
89 | .expect("Failed to start actor!");
90 |
91 | for i in 1..1_000_000 {
92 | actor.cast(i).expect("Failed to enqueue message to actor");
93 | }
94 |
95 | actor.kill();
96 | handle.await.expect("Failed to join handle");
97 | }
98 |
--------------------------------------------------------------------------------
/ractor/examples/counter.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | //! A basic counting agent. Demonstrates remote procedure calls to interact
7 | //! with the agent externally and safely acquire the "count"
8 | //!
9 | //! Execute with
10 | //!
11 | //! ```text
12 | //! cargo run --example counter
13 | //! ```
14 |
15 | #![allow(clippy::incompatible_msrv)]
16 |
17 | extern crate ractor;
18 |
19 | use ractor::call_t;
20 | use ractor::Actor;
21 | use ractor::ActorProcessingErr;
22 | use ractor::ActorRef;
23 | use ractor::RpcReplyPort;
24 |
25 | struct Counter;
26 |
27 | #[derive(Clone)]
28 | struct CounterState {
29 | count: i64,
30 | }
31 |
32 | enum CounterMessage {
33 | Increment(i64),
34 | Decrement(i64),
35 | Retrieve(RpcReplyPort),
36 | }
37 | #[cfg(feature = "cluster")]
38 | impl ractor::Message for CounterMessage {}
39 |
40 | #[cfg_attr(feature = "async-trait", ractor::async_trait)]
41 | impl Actor for Counter {
42 | type Msg = CounterMessage;
43 |
44 | type State = CounterState;
45 | type Arguments = ();
46 |
47 | async fn pre_start(
48 | &self,
49 | _myself: ActorRef,
50 | _: (),
51 | ) -> Result {
52 | tracing::info!("Starting the counter actor");
53 | // create the initial state
54 | Ok(CounterState { count: 0 })
55 | }
56 |
57 | async fn handle(
58 | &self,
59 | _myself: ActorRef,
60 | message: Self::Msg,
61 | state: &mut Self::State,
62 | ) -> Result<(), ActorProcessingErr> {
63 | tracing::info!("Counting actor handle message...");
64 | match message {
65 | CounterMessage::Increment(how_much) => {
66 | state.count += how_much;
67 | }
68 | CounterMessage::Decrement(how_much) => {
69 | state.count -= how_much;
70 | }
71 | CounterMessage::Retrieve(reply_port) => {
72 | if !reply_port.is_closed() {
73 | reply_port.send(state.count).unwrap();
74 | }
75 | }
76 | }
77 | Ok(())
78 | }
79 | }
80 |
81 | fn init_logging() {
82 | let dir = tracing_subscriber::filter::Directive::from(tracing::Level::DEBUG);
83 |
84 | use std::io::stderr;
85 | use std::io::IsTerminal;
86 |
87 | use tracing_glog::Glog;
88 | use tracing_glog::GlogFields;
89 | use tracing_subscriber::filter::EnvFilter;
90 | use tracing_subscriber::layer::SubscriberExt;
91 | use tracing_subscriber::Registry;
92 |
93 | let fmt = tracing_subscriber::fmt::Layer::default()
94 | .with_ansi(stderr().is_terminal())
95 | .with_writer(std::io::stderr)
96 | .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default()))
97 | .fmt_fields(GlogFields::default().compact());
98 |
99 | let filter = vec![dir]
100 | .into_iter()
101 | .fold(EnvFilter::from_default_env(), |filter, directive| {
102 | filter.add_directive(directive)
103 | });
104 |
105 | let subscriber = Registry::default().with(filter).with(fmt);
106 | tracing::subscriber::set_global_default(subscriber).expect("to set global subscriber");
107 | }
108 |
109 | #[ractor_example_entry_proc::ractor_example_entry]
110 | async fn main() {
111 | init_logging();
112 | let (actor, handle) = Actor::spawn(Some("test_name".to_string()), Counter, ())
113 | .await
114 | .expect("Failed to start actor!");
115 |
116 | // +5 +10 -5 a few times, printing the value via RPC
117 | for _i in 0..4 {
118 | actor
119 | .send_message(CounterMessage::Increment(5))
120 | .expect("Failed to send message");
121 | actor
122 | .send_message(CounterMessage::Increment(10))
123 | .expect("Failed to send message");
124 | actor
125 | .send_message(CounterMessage::Decrement(5))
126 | .expect("Failed to send message");
127 |
128 | let rpc_result = call_t!(actor, CounterMessage::Retrieve, 10);
129 |
130 | tracing::info!(
131 | "Count is: {}",
132 | rpc_result.expect("RPC failed to reply successfully")
133 | );
134 | }
135 |
136 | actor.stop(None);
137 | handle.await.expect("Actor failed to exit cleanly");
138 | }
139 |
--------------------------------------------------------------------------------
/ractor/examples/output_port.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | //! An agent which communicates to some set of subscribers via an "Output port"
7 | //!
8 | //! Execute with
9 | //!
10 | //! ```text
11 | //! cargo run --example output_port
12 | //! ```
13 |
14 | #![allow(clippy::incompatible_msrv)]
15 |
16 | extern crate ractor;
17 |
18 | use std::sync::Arc;
19 |
20 | use ractor::Actor;
21 | use ractor::ActorProcessingErr;
22 | use ractor::ActorRef;
23 | use ractor::OutputPort;
24 | use tokio::time::timeout;
25 | use tokio::time::Duration;
26 |
27 | enum PublisherMessage {
28 | Publish(String),
29 | }
30 | #[cfg(feature = "cluster")]
31 | impl ractor::Message for PublisherMessage {}
32 |
33 | #[derive(Clone)]
34 | struct Output(String);
35 | #[cfg(feature = "cluster")]
36 | impl ractor::Message for Output {}
37 |
38 | struct Publisher;
39 |
40 | #[cfg_attr(feature = "async-trait", ractor::async_trait)]
41 | impl Actor for Publisher {
42 | type Msg = PublisherMessage;
43 |
44 | type State = Arc>;
45 | type Arguments = Arc>;
46 |
47 | async fn pre_start(
48 | &self,
49 | _myself: ActorRef,
50 | port: Arc>,
51 | ) -> Result {
52 | Ok(port)
53 | }
54 |
55 | async fn handle(
56 | &self,
57 | _myself: ActorRef,
58 | message: Self::Msg,
59 | state: &mut Self::State,
60 | ) -> Result<(), ActorProcessingErr> {
61 | match message {
62 | Self::Msg::Publish(msg) => {
63 | tracing::info!("Publishing {msg}");
64 | state.send(Output(format!("Published: {msg}")));
65 | }
66 | }
67 | Ok(())
68 | }
69 | }
70 |
71 | struct Subscriber;
72 |
73 | enum SubscriberMessage {
74 | Published(String),
75 | }
76 | #[cfg(feature = "cluster")]
77 | impl ractor::Message for SubscriberMessage {}
78 |
79 | #[cfg_attr(feature = "async-trait", ractor::async_trait)]
80 | impl Actor for Subscriber {
81 | type Msg = SubscriberMessage;
82 |
83 | type State = ();
84 | type Arguments = ();
85 |
86 | async fn pre_start(
87 | &self,
88 | _myself: ActorRef,
89 | _: (),
90 | ) -> Result {
91 | Ok(())
92 | }
93 |
94 | async fn handle(
95 | &self,
96 | myself: ActorRef,
97 | message: Self::Msg,
98 | _state: &mut Self::State,
99 | ) -> Result<(), ActorProcessingErr> {
100 | match message {
101 | Self::Msg::Published(msg) => {
102 | tracing::info!("Subscriber ({myself:?}) received published message '{msg}'");
103 | }
104 | }
105 | Ok(())
106 | }
107 | }
108 |
109 | fn init_logging() {
110 | let dir = tracing_subscriber::filter::Directive::from(tracing::Level::DEBUG);
111 |
112 | use std::io::stderr;
113 | use std::io::IsTerminal;
114 |
115 | use tracing_glog::Glog;
116 | use tracing_glog::GlogFields;
117 | use tracing_subscriber::filter::EnvFilter;
118 | use tracing_subscriber::layer::SubscriberExt;
119 | use tracing_subscriber::Registry;
120 |
121 | let fmt = tracing_subscriber::fmt::Layer::default()
122 | .with_ansi(stderr().is_terminal())
123 | .with_writer(std::io::stderr)
124 | .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default()))
125 | .fmt_fields(GlogFields::default().compact());
126 |
127 | let filter = vec![dir]
128 | .into_iter()
129 | .fold(EnvFilter::from_default_env(), |filter, directive| {
130 | filter.add_directive(directive)
131 | });
132 |
133 | let subscriber = Registry::default().with(filter).with(fmt);
134 | tracing::subscriber::set_global_default(subscriber).expect("to set global subscriber");
135 | }
136 |
137 | #[ractor_example_entry_proc::ractor_example_entry]
138 | async fn main() {
139 | init_logging();
140 |
141 | let port = Arc::new(OutputPort::default());
142 |
143 | let (publisher_ref, publisher_handle) = Actor::spawn(None, Publisher, port.clone())
144 | .await
145 | .expect("Failed to start publisher");
146 |
147 | let mut subscriber_refs = vec![];
148 | let mut subscriber_handles = vec![];
149 |
150 | // spawn + setup the subscribers (NOT SUPERVISION LINKAGE)
151 | for _ in 0..10 {
152 | let (actor_ref, actor_handle) = Actor::spawn(None, Subscriber, ())
153 | .await
154 | .expect("Failed to start subscriber");
155 |
156 | // TODO: there has to be a better syntax than keeping an arc to the port?
157 | port.subscribe(actor_ref.clone(), |msg| {
158 | Some(SubscriberMessage::Published(msg.0))
159 | });
160 |
161 | subscriber_refs.push(actor_ref);
162 | subscriber_handles.push(actor_handle);
163 | }
164 |
165 | // send some messages (we should see the subscribers printout)
166 | for i in 0..3 {
167 | publisher_ref
168 | .cast(PublisherMessage::Publish(format!("Something {i}")))
169 | .expect("Send failed");
170 | tokio::time::sleep(Duration::from_millis(500)).await;
171 | }
172 |
173 | // clean up everything
174 | publisher_ref.stop(None);
175 | for actor in subscriber_refs {
176 | actor.stop(None);
177 | }
178 | // wait for exits
179 | timeout(Duration::from_millis(50), publisher_handle)
180 | .await
181 | .expect("Actor failed to exit cleanly")
182 | .unwrap();
183 | for s in subscriber_handles.into_iter() {
184 | timeout(Duration::from_millis(50), s)
185 | .await
186 | .expect("Actor failed to exit cleanly")
187 | .unwrap();
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/ractor/examples/ping_pong.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | //! An example ping-pong actor which posts back to itself to pass the
7 | //! ping or pong back + forth
8 | //!
9 | //! Execute with
10 | //!
11 | //! ```text
12 | //! cargo run --example ping_pong
13 | //! ```
14 |
15 | #![allow(clippy::incompatible_msrv)]
16 |
17 | extern crate ractor;
18 |
19 | use ractor::cast;
20 | use ractor::Actor;
21 | use ractor::ActorProcessingErr;
22 | use ractor::ActorRef;
23 |
24 | pub struct PingPong;
25 |
26 | #[derive(Debug, Clone)]
27 | pub enum Message {
28 | Ping,
29 | Pong,
30 | }
31 | #[cfg(feature = "cluster")]
32 | impl ractor::Message for Message {}
33 |
34 | impl Message {
35 | fn next(&self) -> Self {
36 | match self {
37 | Self::Ping => Self::Pong,
38 | Self::Pong => Self::Ping,
39 | }
40 | }
41 |
42 | fn print(&self) {
43 | match self {
44 | Self::Ping => print!("ping.."),
45 | Self::Pong => print!("pong.."),
46 | }
47 | }
48 | }
49 |
50 | #[cfg_attr(feature = "async-trait", ractor::async_trait)]
51 | impl Actor for PingPong {
52 | type Msg = Message;
53 |
54 | type State = u8;
55 | type Arguments = ();
56 |
57 | async fn pre_start(
58 | &self,
59 | myself: ActorRef,
60 | _: (),
61 | ) -> Result {
62 | // startup the event processing
63 | cast!(myself, Message::Ping).unwrap();
64 | // create the initial state
65 | Ok(0u8)
66 | }
67 |
68 | async fn handle(
69 | &self,
70 | myself: ActorRef,
71 | message: Self::Msg,
72 | state: &mut Self::State,
73 | ) -> Result<(), ActorProcessingErr> {
74 | if *state < 10u8 {
75 | message.print();
76 | cast!(myself, message.next()).unwrap();
77 | *state += 1;
78 | } else {
79 | tracing::info!("");
80 | myself.stop(None);
81 | }
82 | Ok(())
83 | }
84 | }
85 |
86 | fn init_logging() {
87 | let dir = tracing_subscriber::filter::Directive::from(tracing::Level::DEBUG);
88 |
89 | use std::io::stderr;
90 | use std::io::IsTerminal;
91 |
92 | use tracing_glog::Glog;
93 | use tracing_glog::GlogFields;
94 | use tracing_subscriber::filter::EnvFilter;
95 | use tracing_subscriber::layer::SubscriberExt;
96 | use tracing_subscriber::Registry;
97 |
98 | let fmt = tracing_subscriber::fmt::Layer::default()
99 | .with_ansi(stderr().is_terminal())
100 | .with_writer(std::io::stderr)
101 | .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default()))
102 | .fmt_fields(GlogFields::default().compact());
103 |
104 | let filter = vec![dir]
105 | .into_iter()
106 | .fold(EnvFilter::from_default_env(), |filter, directive| {
107 | filter.add_directive(directive)
108 | });
109 |
110 | let subscriber = Registry::default().with(filter).with(fmt);
111 | tracing::subscriber::set_global_default(subscriber).expect("to set global subscriber");
112 | }
113 |
114 | #[ractor_example_entry_proc::ractor_example_entry]
115 | async fn main() {
116 | init_logging();
117 |
118 | let (_actor, handle) = Actor::spawn(None, PingPong, ())
119 | .await
120 | .expect("Failed to start ping-pong actor");
121 | handle
122 | .await
123 | .expect("Ping-pong actor failed to exit properly");
124 | }
125 |
--------------------------------------------------------------------------------
/ractor/src/actor/actor_id.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | //! This module handles everything around actor id's. In the event you have a
7 | //! remote actor, the actor's ID will denote not only the actor's instance id, but
8 | //! the id of the remote node the actor is running on.
9 | //!
10 | //! ActorIds are generally helpful in logging and pattern matching, but not directly
11 | //! used in addressing outside of `ractor_cluster` network-based call internals.
12 |
13 | use std::fmt::Display;
14 | use std::sync::atomic::AtomicU64;
15 |
16 | /// An actor's globally unique identifier
17 | #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
18 | pub enum ActorId {
19 | /// A local pid
20 | Local(u64),
21 |
22 | /// A remote actor on another system (system, id)
23 | Remote {
24 | /// The remote node id
25 | node_id: u64,
26 | /// The local id on the remote system
27 | pid: u64,
28 | },
29 | }
30 |
31 | impl ActorId {
32 | /// Determine if this actor id is a local or remote actor
33 | ///
34 | /// Returns [true] if it is a local actor, [false] otherwise
35 | pub const fn is_local(&self) -> bool {
36 | matches!(self, ActorId::Local(_))
37 | }
38 |
39 | /// Retrieve the actor's PID
40 | ///
41 | /// Returns the actor's [u64] instance identifier (process id).
42 | pub const fn pid(&self) -> u64 {
43 | match self {
44 | ActorId::Local(pid) => *pid,
45 | ActorId::Remote { pid, .. } => *pid,
46 | }
47 | }
48 |
49 | /// Retrieve the node id of this PID. 0 = a local actor, while
50 | /// any non-zero value is the ide of the remote node running this actor
51 | pub const fn node(&self) -> u64 {
52 | match self {
53 | ActorId::Local(_) => 0,
54 | ActorId::Remote { node_id, .. } => *node_id,
55 | }
56 | }
57 | }
58 |
59 | impl Display for ActorId {
60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 | match self {
62 | ActorId::Local(id) => write!(f, "0.{id}"),
63 | ActorId::Remote { node_id, pid } => write!(f, "{node_id}.{pid}"),
64 | }
65 | }
66 | }
67 |
68 | /// The local id allocator for actors
69 | static ACTOR_ID_ALLOCATOR: AtomicU64 = AtomicU64::new(0u64);
70 |
71 | /// Retrieve a new local id
72 | pub(crate) fn get_new_local_id() -> ActorId {
73 | ActorId::Local(ACTOR_ID_ALLOCATOR.fetch_add(1, std::sync::atomic::Ordering::AcqRel))
74 | }
75 |
76 | #[cfg(test)]
77 | mod tests {
78 | use super::*;
79 |
80 | #[test]
81 | fn test_pid() {
82 | let actor_id = ActorId::Local(123);
83 | assert_eq!(123, actor_id.pid());
84 | let actor_id = ActorId::Remote {
85 | node_id: 1,
86 | pid: 123,
87 | };
88 | assert_eq!(123, actor_id.pid());
89 | }
90 |
91 | #[test]
92 | fn test_is_local() {
93 | let actor_id = ActorId::Local(123);
94 | assert!(actor_id.is_local());
95 | let actor_id = ActorId::Remote {
96 | node_id: 1,
97 | pid: 123,
98 | };
99 | assert!(!actor_id.is_local());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/ractor/src/actor/actor_ref.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | //! [ActorRef] is a strongly-typed wrapper over an [ActorCell]
7 |
8 | use std::marker::PhantomData;
9 |
10 | use super::ActorCell;
11 | use crate::ActorName;
12 | use crate::Message;
13 | use crate::MessagingErr;
14 | use crate::SupervisionEvent;
15 |
16 | /// An [ActorRef] is a strongly-typed wrapper over an [ActorCell]
17 | /// to provide some syntactic wrapping on the requirement to pass
18 | /// the actor's message type everywhere.
19 | ///
20 | /// An [ActorRef] is the primary means of communication typically used
21 | /// when interfacing with [super::Actor]s
22 | pub struct ActorRef {
23 | pub(crate) inner: ActorCell,
24 | _tactor: PhantomData TMessage>,
25 | }
26 |
27 | impl Clone for ActorRef {
28 | fn clone(&self) -> Self {
29 | ActorRef {
30 | inner: self.inner.clone(),
31 | _tactor: PhantomData,
32 | }
33 | }
34 | }
35 |
36 | impl std::ops::Deref for ActorRef {
37 | type Target = ActorCell;
38 |
39 | fn deref(&self) -> &Self::Target {
40 | &self.inner
41 | }
42 | }
43 |
44 | impl From for ActorRef {
45 | fn from(value: ActorCell) -> Self {
46 | Self {
47 | inner: value,
48 | _tactor: PhantomData,
49 | }
50 | }
51 | }
52 |
53 | impl From> for ActorCell {
54 | fn from(value: ActorRef) -> Self {
55 | value.inner
56 | }
57 | }
58 |
59 | impl std::fmt::Debug for ActorRef {
60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 | self.inner.fmt(f)
62 | }
63 | }
64 |
65 | impl ActorRef {
66 | /// Retrieve a cloned [ActorCell] representing this [ActorRef]
67 | pub fn get_cell(&self) -> ActorCell {
68 | self.inner.clone()
69 | }
70 |
71 | /// Notify the supervisor and all monitors that a supervision event occurred.
72 | /// Monitors receive a reduced copy of the supervision event which won't contain
73 | /// the [crate::actor::BoxedState] and collapses the [crate::ActorProcessingErr]
74 | /// exception to a [String]
75 | ///
76 | /// * `evt` - The event to send to this [crate::Actor]'s supervisors
77 | pub fn notify_supervisor_and_monitors(&self, evt: SupervisionEvent) {
78 | self.inner.notify_supervisor(evt)
79 | }
80 | }
81 |
82 | impl crate::actor::ActorRef
83 | where
84 | TMessage: Message,
85 | {
86 | /// Send a strongly-typed message, constructing the boxed message on the fly
87 | ///
88 | /// * `message` - The message to send
89 | ///
90 | /// Returns [Ok(())] on successful message send, [Err(MessagingErr)] otherwise
91 | pub fn send_message(&self, message: TMessage) -> Result<(), MessagingErr> {
92 | self.inner.send_message::(message)
93 | }
94 |
95 | // ========================== General Actor Operation Aliases ========================== //
96 |
97 | // -------------------------- ActorRegistry -------------------------- //
98 |
99 | /// Try and retrieve a strongly-typed actor from the registry.
100 | ///
101 | /// Alias of [crate::registry::where_is]
102 | pub fn where_is(name: ActorName) -> Option> {
103 | if let Some(actor) = crate::registry::where_is(name) {
104 | // check the type id when pulling from the registry
105 | let check = actor.is_message_type_of::();
106 | if check.is_none() || matches!(check, Some(true)) {
107 | return Some(actor.into());
108 | }
109 | }
110 | None
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/ractor/src/common_test.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Sean Lawlor
2 | //
3 | // This source code is licensed under both the MIT license found in the
4 | // LICENSE-MIT file in the root directory of this source tree.
5 |
6 | // TODO #124 (slawlor): Redesign this without usage of core time primatives (i.e.
7 | // use concurrency instants)
8 |
9 | use std::future::Future;
10 |
11 | use crate::concurrency::sleep;
12 | use crate::concurrency::Duration;
13 | use crate::concurrency::Instant;
14 |
15 | /// Periodic check for condition
16 | pub async fn periodic_check(check: F, timeout: Duration)
17 | where
18 | F: Fn() -> bool,
19 | {
20 | let start = Instant::now();
21 | while start.elapsed() < timeout {
22 | if check() {
23 | break;
24 | }
25 | sleep(Duration::from_millis(50)).await;
26 | }
27 |
28 | let backtrace = backtrace::Backtrace::new();
29 | assert!(check(), "Periodic check failed.\n{:?}", backtrace);
30 | }
31 |
32 | /// Periodic check of Future for condition
33 | pub async fn periodic_async_check(check: F, timeout: Duration)
34 | where
35 | F: Fn() -> Fut,
36 | Fut: Future