├── .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 | [![codecov](https://codecov.io/gh/slawlor/ractor/branch/main/graph/badge.svg?token=61AGYYPWBA)](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, 37 | { 38 | let start = Instant::now(); 39 | while start.elapsed() < timeout { 40 | if check().await { 41 | break; 42 | } 43 | sleep(Duration::from_millis(50)).await; 44 | } 45 | 46 | let backtrace = backtrace::Backtrace::new(); 47 | assert!( 48 | check().await, 49 | "Async periodic check failed.\n{:?}", 50 | backtrace 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /ractor/src/concurrency.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 | //! Shared concurrency primitives utilized within the library for different frameworks (tokio, async-std, etc) 7 | 8 | /// A timeout error 9 | #[derive(Debug)] 10 | pub struct Timeout; 11 | impl std::error::Error for Timeout {} 12 | impl std::fmt::Display for Timeout { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | write!(f, "Timeout") 15 | } 16 | } 17 | 18 | /// A notification 19 | pub type Notify = tokio::sync::Notify; 20 | 21 | /// A one-use sender 22 | pub type OneshotSender = tokio::sync::oneshot::Sender; 23 | /// A one-use receiver 24 | pub type OneshotReceiver = tokio::sync::oneshot::Receiver; 25 | 26 | /// A bounded MP;SC sender 27 | pub type MpscSender = tokio::sync::mpsc::Sender; 28 | /// A bounded MP;SC receiver 29 | pub type MpscReceiver = tokio::sync::mpsc::Receiver; 30 | 31 | /// A bounded MP;SC sender 32 | pub type MpscUnboundedSender = tokio::sync::mpsc::UnboundedSender; 33 | /// A bounded MP;SC receiver 34 | pub type MpscUnboundedReceiver = tokio::sync::mpsc::UnboundedReceiver; 35 | 36 | /// A bounded broadcast sender 37 | pub type BroadcastSender = tokio::sync::broadcast::Sender; 38 | /// A bounded broadcast receiver 39 | pub type BroadcastReceiver = tokio::sync::broadcast::Receiver; 40 | 41 | /// MPSC bounded channel 42 | pub fn mpsc_bounded(buffer: usize) -> (MpscSender, MpscReceiver) { 43 | tokio::sync::mpsc::channel(buffer) 44 | } 45 | 46 | /// MPSC unbounded channel 47 | pub fn mpsc_unbounded() -> (MpscUnboundedSender, MpscUnboundedReceiver) { 48 | tokio::sync::mpsc::unbounded_channel() 49 | } 50 | 51 | /// Oneshot channel 52 | pub fn oneshot() -> (OneshotSender, OneshotReceiver) { 53 | tokio::sync::oneshot::channel() 54 | } 55 | 56 | /// Broadcast channel 57 | pub fn broadcast(buffer: usize) -> (BroadcastSender, BroadcastReceiver) { 58 | tokio::sync::broadcast::channel(buffer) 59 | } 60 | 61 | #[cfg(all( 62 | not(all(target_arch = "wasm32", target_os = "unknown")), 63 | not(feature = "async-std") 64 | ))] 65 | pub mod tokio_primitives; 66 | #[cfg(all( 67 | not(all(target_arch = "wasm32", target_os = "unknown")), 68 | not(feature = "async-std") 69 | ))] 70 | pub use self::tokio_primitives::*; 71 | 72 | #[cfg(all( 73 | not(all(target_arch = "wasm32", target_os = "unknown")), 74 | feature = "async-std" 75 | ))] 76 | pub mod async_std_primitives; 77 | #[cfg(all( 78 | not(all(target_arch = "wasm32", target_os = "unknown")), 79 | feature = "async-std" 80 | ))] 81 | pub use self::async_std_primitives::*; 82 | 83 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 84 | pub mod tokio_with_wasm_primitives; 85 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 86 | pub use self::tokio_with_wasm_primitives::*; 87 | 88 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 89 | mod target_specific { 90 | /// A wrapper for [std::marker::Send] on non-`wasm32-unknown-unknown` targets, or an empty trait on `wasm32-unknown-unknown` targets. 91 | /// Introduced for compatibility between wasm32 and other targets 92 | #[cfg(not(feature = "async-trait"))] 93 | pub trait MaybeSend {} 94 | #[cfg(not(feature = "async-trait"))] 95 | impl MaybeSend for T {} 96 | pub(crate) use web_time::SystemTime; 97 | } 98 | #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] 99 | mod target_specific { 100 | /// A wrapper for [std::marker::Send] on non-`wasm32-unknown-unknown` targets, or an empty trait on `wasm32-unknown-unknown` targets. 101 | /// Introduced for compatibility between wasm32 and other targets 102 | #[cfg(not(feature = "async-trait"))] 103 | pub trait MaybeSend: Send {} 104 | #[cfg(not(feature = "async-trait"))] 105 | impl MaybeSend for T where T: Send {} 106 | pub(crate) use std::time::SystemTime; 107 | } 108 | 109 | #[cfg(not(feature = "async-trait"))] 110 | pub use target_specific::MaybeSend; 111 | pub(crate) use target_specific::SystemTime; 112 | -------------------------------------------------------------------------------- /ractor/src/concurrency/tokio_primitives.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 | //! Concurrency primitives based on `tokio` 7 | 8 | use std::future::Future; 9 | 10 | /// Represents a task JoinHandle 11 | pub type JoinHandle = tokio::task::JoinHandle; 12 | 13 | /// A duration of time 14 | pub type Duration = tokio::time::Duration; 15 | 16 | /// An instant measured on system time 17 | pub type Instant = tokio::time::Instant; 18 | 19 | /// Sleep the task for a duration of time 20 | pub async fn sleep(dur: Duration) { 21 | tokio::time::sleep(dur).await; 22 | } 23 | 24 | /// An asynchronous interval calculation which waits until 25 | /// a checkpoint time to tick 26 | pub type Interval = tokio::time::Interval; 27 | 28 | /// Build a new interval at the given duration starting at now 29 | /// 30 | /// Ticks 1 time immediately 31 | pub fn interval(dur: Duration) -> Interval { 32 | tokio::time::interval(dur) 33 | } 34 | 35 | /// A set of futures to join on, in an unordered fashion 36 | /// (first-completed, first-served) 37 | pub type JoinSet = tokio::task::JoinSet; 38 | 39 | /// Spawn a task on the executor runtime 40 | pub fn spawn(future: F) -> JoinHandle 41 | where 42 | F: Future + Send + 'static, 43 | F::Output: Send + 'static, 44 | { 45 | spawn_named(None, future) 46 | } 47 | 48 | /// Spawn a (possibly) named task on the executor runtime 49 | pub fn spawn_named(name: Option<&str>, future: F) -> JoinHandle 50 | where 51 | F: Future + Send + 'static, 52 | F::Output: Send + 'static, 53 | { 54 | #[cfg(tokio_unstable)] 55 | { 56 | let mut builder = tokio::task::Builder::new(); 57 | if let Some(name) = name { 58 | builder = builder.name(name); 59 | } 60 | builder.spawn(future).expect("Tokio task spawn failed") 61 | } 62 | 63 | #[cfg(not(tokio_unstable))] 64 | { 65 | let _ = name; 66 | tokio::task::spawn(future) 67 | } 68 | } 69 | 70 | /// Execute the future up to a timeout 71 | /// 72 | /// * `dur`: The duration of time to allow the future to execute for 73 | /// * `future`: The future to execute 74 | /// 75 | /// Returns [Ok(_)] if the future succeeded before the timeout, [Err(super::Timeout)] otherwise 76 | pub async fn timeout(dur: super::Duration, future: F) -> Result 77 | where 78 | F: Future, 79 | { 80 | tokio::time::timeout(dur, future) 81 | .await 82 | .map_err(|_| super::Timeout) 83 | } 84 | 85 | macro_rules! select { 86 | ($($tokens:tt)*) => {{ 87 | tokio::select! { 88 | // Biased ensures that we poll the ports in the order they appear, giving 89 | // priority to our message reception operations. See: 90 | // https://docs.rs/tokio/latest/tokio/macro.select.html#fairness 91 | // for more information 92 | biased; 93 | 94 | $( $tokens )* 95 | } 96 | }} 97 | } 98 | 99 | pub(crate) use select; 100 | // test macro 101 | pub use tokio::test; 102 | -------------------------------------------------------------------------------- /ractor/src/concurrency/tokio_with_wasm_primitives.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 | //! Concurrency primitives based on `tokio` 7 | 8 | use std::future::Future; 9 | 10 | use tokio_with_wasm::alias as tokio; 11 | 12 | /// Represents a task JoinHandle 13 | pub type JoinHandle = tokio::task::JoinHandle; 14 | 15 | /// A duration of time 16 | pub type Duration = std::time::Duration; 17 | 18 | /// An instant measured on system time 19 | pub type Instant = web_time::Instant; 20 | 21 | /// Sleep the task for a duration of time 22 | pub async fn sleep(dur: Duration) { 23 | tokio::time::sleep(dur).await; 24 | } 25 | 26 | /// An asynchronous interval calculation which waits until 27 | /// a checkpoint time to tick 28 | pub type Interval = tokio::time::Interval; 29 | 30 | /// Build a new interval at the given duration starting at now 31 | /// 32 | /// Ticks 1 time immediately 33 | pub fn interval(dur: Duration) -> Interval { 34 | tokio::time::interval(dur) 35 | } 36 | 37 | /// A set of futures to join on, in an unordered fashion 38 | /// (first-completed, first-served) 39 | pub type JoinSet = tokio::task::JoinSet; 40 | 41 | /// Spawn a task on the executor runtime 42 | pub fn spawn(future: F) -> JoinHandle 43 | where 44 | F: Future + 'static, 45 | F::Output: 'static, 46 | { 47 | spawn_named(None, future) 48 | } 49 | 50 | /// Spawn a (possibly) named task on the executor runtime 51 | pub fn spawn_named(name: Option<&str>, future: F) -> JoinHandle 52 | where 53 | F: Future + 'static, 54 | F::Output: 'static, 55 | { 56 | #[cfg(tokio_unstable)] 57 | { 58 | let mut builder = tokio::task::Builder::new(); 59 | if let Some(name) = name { 60 | builder = builder.name(name); 61 | } 62 | builder.spawn(future).expect("Tokio task spawn failed") 63 | } 64 | 65 | #[cfg(not(tokio_unstable))] 66 | { 67 | let _ = name; 68 | tokio::task::spawn(future) 69 | } 70 | } 71 | 72 | /// Execute the future up to a timeout 73 | /// 74 | /// * `dur`: The duration of time to allow the future to execute for 75 | /// * `future`: The future to execute 76 | /// 77 | /// Returns [Ok(_)] if the future succeeded before the timeout, [Err(super::Timeout)] otherwise 78 | pub async fn timeout(dur: super::Duration, future: F) -> Result 79 | where 80 | F: Future, 81 | { 82 | tokio::time::timeout(dur, future) 83 | .await 84 | .map_err(|_| super::Timeout) 85 | } 86 | 87 | macro_rules! select { 88 | ($($tokens:tt)*) => {{ 89 | tokio::select! { 90 | // Biased ensures that we poll the ports in the order they appear, giving 91 | // priority to our message reception operations. See: 92 | // https://docs.rs/tokio/latest/tokio/macro.select.html#fairness 93 | // for more information 94 | biased; 95 | 96 | $( $tokens )* 97 | } 98 | }} 99 | } 100 | 101 | pub(crate) use select; 102 | // test macro 103 | #[cfg(test)] 104 | pub use wasm_bindgen_test::wasm_bindgen_test as test; 105 | -------------------------------------------------------------------------------- /ractor/src/factory/hash.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 | //! Factory's defaulting routing hash mechanism. Hashes a [super::JobKey] to a finite 7 | //! space 8 | 9 | use std::collections::hash_map::DefaultHasher; 10 | use std::hash::Hash; 11 | use std::hash::Hasher; 12 | 13 | /// Hash a key into a finite space 14 | pub fn hash_with_max(key: &TKey, excluded_max: usize) -> usize 15 | where 16 | TKey: Hash, 17 | { 18 | let mut dh = DefaultHasher::new(); 19 | key.hash(&mut dh); 20 | let bounded_hash = dh.finish() % (excluded_max as u64); 21 | bounded_hash as usize 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | 27 | use super::*; 28 | 29 | #[derive(Hash)] 30 | struct TestHashable(u128); 31 | 32 | #[test] 33 | fn test_bounded_hashing() { 34 | let test_values = [ 35 | TestHashable(0), 36 | TestHashable(10), 37 | TestHashable(23), 38 | TestHashable(128), 39 | TestHashable(u64::MAX as u128), 40 | ]; 41 | 42 | let test_results = [0usize, 2, 1, 0, 0]; 43 | 44 | for (test_value, test_result) in test_values.iter().zip(test_results) { 45 | let hashed = hash_with_max(test_value, 3); 46 | assert_eq!(test_result, hashed); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ractor/src/factory/lifecycle.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 | //! Lifecycle hooks support interjecting external logic into the factory's 7 | //! lifecycle (startup/shutdown/etc) such that users can intercept and 8 | //! adjust factory functionality at key interjection points. 9 | 10 | #[cfg(not(feature = "async-trait"))] 11 | use futures::future::BoxFuture; 12 | #[cfg(not(feature = "async-trait"))] 13 | use futures::FutureExt; 14 | 15 | use super::FactoryMessage; 16 | use super::JobKey; 17 | use crate::ActorProcessingErr; 18 | use crate::ActorRef; 19 | use crate::Message; 20 | use crate::State; 21 | 22 | /// Hooks for [crate::factory::Factory] lifecycle events based on the 23 | /// underlying actor's lifecycle. 24 | #[cfg_attr(feature = "async-trait", crate::async_trait)] 25 | pub trait FactoryLifecycleHooks: State + Sync 26 | where 27 | TKey: JobKey, 28 | TMsg: Message, 29 | { 30 | /// Called when the factory has completed it's startup routine but 31 | /// PRIOR to processing any messages. Just before this point, the factory 32 | /// is ready to accept and process requests and all workers are started. 33 | /// 34 | /// This hook is there to provide custom startup logic you want to make sure has run 35 | /// prior to processing messages on workers 36 | /// 37 | /// WARNING: An error or panic returned here WILL shutdown the factory and notify supervisors 38 | #[allow(unused_variables)] 39 | #[cfg(feature = "async-trait")] 40 | async fn on_factory_started( 41 | &self, 42 | factory_ref: ActorRef>, 43 | ) -> Result<(), ActorProcessingErr> { 44 | Ok(()) 45 | } 46 | 47 | /// Called when the factory has completed it's startup routine but 48 | /// PRIOR to processing any messages. Just before this point, the factory 49 | /// is ready to accept and process requests and all workers are started. 50 | /// 51 | /// This hook is there to provide custom startup logic you want to make sure has run 52 | /// prior to processing messages on workers 53 | /// 54 | /// WARNING: An error or panic returned here WILL shutdown the factory and notify supervisors 55 | #[allow(unused_variables)] 56 | #[cfg(not(feature = "async-trait"))] 57 | fn on_factory_started( 58 | &self, 59 | factory_ref: ActorRef>, 60 | ) -> BoxFuture<'_, Result<(), ActorProcessingErr>> { 61 | async { Ok(()) }.boxed() 62 | } 63 | 64 | /// Called when the factory has completed it's shutdown routine but 65 | /// PRIOR to fully exiting and notifying any relevant supervisors. Just prior 66 | /// to this call the factory has processed its last message and will process 67 | /// no more messages. 68 | /// 69 | /// This hook is there to provide custom shutdown logic you want to make sure has run 70 | /// prior to the factory fully exiting 71 | #[cfg(feature = "async-trait")] 72 | async fn on_factory_stopped(&self) -> Result<(), ActorProcessingErr> { 73 | Ok(()) 74 | } 75 | 76 | /// Called when the factory has completed it's shutdown routine but 77 | /// PRIOR to fully exiting and notifying any relevant supervisors. Just prior 78 | /// to this call the factory has processed its last message and will process 79 | /// no more messages. 80 | /// 81 | /// This hook is there to provide custom shutdown logic you want to make sure has run 82 | /// prior to the factory fully exiting 83 | #[cfg(not(feature = "async-trait"))] 84 | fn on_factory_stopped(&self) -> BoxFuture<'_, Result<(), ActorProcessingErr>> { 85 | async { Ok(()) }.boxed() 86 | } 87 | 88 | /// Called when the factory has received a signal to drain requests and exit after 89 | /// draining has completed. 90 | /// 91 | /// This hook is to provide the ability to notify external services that the factory 92 | /// is in the process of shutting down. If the factory is never "drained" formally, 93 | /// this hook won't be called. 94 | /// 95 | /// WARNING: An error or panic returned here WILL shutdown the factory and notify supervisors 96 | #[allow(unused_variables)] 97 | #[cfg(feature = "async-trait")] 98 | async fn on_factory_draining( 99 | &self, 100 | factory_ref: ActorRef>, 101 | ) -> Result<(), ActorProcessingErr> { 102 | Ok(()) 103 | } 104 | 105 | /// Called when the factory has received a signal to drain requests and exit after 106 | /// draining has completed. 107 | /// 108 | /// This hook is to provide the ability to notify external services that the factory 109 | /// is in the process of shutting down. If the factory is never "drained" formally, 110 | /// this hook won't be called. 111 | /// 112 | /// WARNING: An error or panic returned here WILL shutdown the factory and notify supervisors 113 | #[allow(unused_variables)] 114 | #[cfg(not(feature = "async-trait"))] 115 | fn on_factory_draining( 116 | &self, 117 | factory_ref: ActorRef>, 118 | ) -> BoxFuture<'_, Result<(), ActorProcessingErr>> { 119 | async { Ok(()) }.boxed() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /ractor/src/factory/tests.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 | //! Factory functionality tests 7 | 8 | mod basic; 9 | mod draining_requests; 10 | mod dynamic_discarding; 11 | mod dynamic_pool; 12 | mod dynamic_settings; 13 | mod lifecycle; 14 | mod priority_queueing; 15 | mod ratelim; 16 | #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] 17 | mod worker_lifecycle; 18 | -------------------------------------------------------------------------------- /ractor/src/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 | //! Port implementations for signaling and reception of messages in the Ractor environment 7 | //! 8 | //! Most of the ports we utilize are direct aliases of [tokio]'s channels 9 | //! (in the `sync` feature of the crate), however there are some helpful wrappers 10 | //! and utilities to make working with mailbox processing in `ractor` easier in 11 | //! the actor framework. 12 | 13 | use crate::concurrency; 14 | use crate::MessagingErr; 15 | 16 | // ============ Output Ports ============ // 17 | pub mod output; 18 | pub use output::*; 19 | 20 | // ============ Rpc (one-use) Ports ============ // 21 | 22 | /// A remote procedure call's reply port. Wrapper of [concurrency::OneshotSender] with a 23 | /// consistent error type 24 | #[derive(Debug)] 25 | pub struct RpcReplyPort { 26 | port: concurrency::OneshotSender, 27 | timeout: Option, 28 | } 29 | 30 | impl RpcReplyPort { 31 | /// Read the timeout of this RPC reply port 32 | /// 33 | /// Returns [Some(concurrency::Duration)] if a timeout is set, [None] otherwise 34 | pub fn get_timeout(&self) -> Option { 35 | self.timeout 36 | } 37 | 38 | /// Send a message to the Rpc reply port. This consumes the port 39 | /// 40 | /// * `msg` - The message to send 41 | /// 42 | /// Returns [Ok(())] if the message send was successful, [Err(MessagingErr)] otherwise 43 | pub fn send(self, msg: TMsg) -> Result<(), MessagingErr> { 44 | self.port.send(msg).map_err(|t| MessagingErr::SendErr(t)) 45 | } 46 | 47 | /// Determine if the port is closed (i.e. the receiver has been dropped) 48 | /// 49 | /// Returns [true] if the receiver has been dropped and the channel is 50 | /// closed, this means sends will fail, [false] if channel is open and 51 | /// receiving messages 52 | pub fn is_closed(&self) -> bool { 53 | self.port.is_closed() 54 | } 55 | } 56 | 57 | impl From> for RpcReplyPort { 58 | fn from(value: concurrency::OneshotSender) -> Self { 59 | Self { 60 | port: value, 61 | timeout: None, 62 | } 63 | } 64 | } 65 | 66 | impl From<(concurrency::OneshotSender, concurrency::Duration)> for RpcReplyPort { 67 | fn from((value, timeout): (concurrency::OneshotSender, concurrency::Duration)) -> Self { 68 | Self { 69 | port: value, 70 | timeout: Some(timeout), 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ractor/src/registry.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 | //! Represents an actor registry. 7 | //! 8 | //! It allows unique naming of actors via `String` 9 | //! so it works more or less like an Erlang `atom()` 10 | //! 11 | //! Actors are automatically registered into the global registry, if they 12 | //! provide a name, upon construction. Actors are also 13 | //! automatically unenrolled from the registry upon being dropped, therefore freeing 14 | //! the name for subsequent registration. 15 | //! 16 | //! You can then retrieve actors by name with [where_is]. Note: this 17 | //! function only returns the [ActorCell] reference to the actor, it 18 | //! additionally requires knowledge of the [crate::Actor] in order 19 | //! to send messages to it (since you need to know the message type) 20 | //! or agents will runtime panic on message reception, and supervision 21 | //! processes would need to restart the actors. 22 | //! 23 | //! ## Examples 24 | //! 25 | //! **Basic actor retrieval** 26 | //! ```rust 27 | //! async fn test() { 28 | //! let maybe_actor = ractor::registry::where_is("my_actor".to_string()); 29 | //! if let Some(actor) = maybe_actor { 30 | //! // send a message, or interact with the actor 31 | //! // but you'll need to know the actor's strong type 32 | //! } 33 | //! } 34 | //! ``` 35 | //! 36 | //! **Full example** 37 | //! 38 | //! ```rust 39 | //! use ractor::registry; 40 | //! use ractor::Actor; 41 | //! use ractor::ActorProcessingErr; 42 | //! use ractor::ActorRef; 43 | //! 44 | //! struct ExampleActor; 45 | //! 46 | //! #[cfg_attr(feature = "async-trait", ractor::async_trait)] 47 | //! impl Actor for ExampleActor { 48 | //! type Msg = (); 49 | //! type State = (); 50 | //! type Arguments = (); 51 | //! 52 | //! async fn pre_start( 53 | //! &self, 54 | //! _myself: ActorRef, 55 | //! _args: Self::Arguments, 56 | //! ) -> Result { 57 | //! println!("Starting"); 58 | //! Ok(()) 59 | //! } 60 | //! } 61 | //! 62 | //! #[tokio::main] 63 | //! async fn main() { 64 | //! let (actor, handle) = Actor::spawn(Some("my_actor".to_string()), ExampleActor, ()) 65 | //! .await 66 | //! .expect("Failed to startup dummy actor"); 67 | //! 68 | //! // Retrieve the actor by name from the registry 69 | //! let who: ActorRef<()> = registry::where_is("my_actor".to_string()) 70 | //! .expect("Failed to find actor") 71 | //! .into(); 72 | //! who.cast(()).expect("Failed to send message"); 73 | //! 74 | //! // wait for actor exit 75 | //! actor.stop(None); 76 | //! handle.await.unwrap(); 77 | //! 78 | //! // Automatically removed from the registry upon shutdown 79 | //! assert!(registry::where_is("my_actor".to_string()).is_none()); 80 | //! } 81 | //! ``` 82 | 83 | use std::sync::Arc; 84 | 85 | use dashmap::mapref::entry::Entry::Occupied; 86 | use dashmap::mapref::entry::Entry::Vacant; 87 | use dashmap::DashMap; 88 | use once_cell::sync::OnceCell; 89 | 90 | use crate::ActorCell; 91 | use crate::ActorName; 92 | 93 | #[cfg(feature = "cluster")] 94 | pub mod pid_registry; 95 | #[cfg(feature = "cluster")] 96 | pub use pid_registry::get_all_pids; 97 | #[cfg(feature = "cluster")] 98 | pub use pid_registry::where_is_pid; 99 | #[cfg(feature = "cluster")] 100 | pub use pid_registry::PidLifecycleEvent; 101 | 102 | #[cfg(test)] 103 | mod tests; 104 | 105 | /// Errors involving the [crate::registry]'s actor registry 106 | #[derive(Debug)] 107 | pub enum ActorRegistryErr { 108 | /// Actor already registered 109 | AlreadyRegistered(ActorName), 110 | } 111 | 112 | /// The name'd actor registry 113 | static ACTOR_REGISTRY: OnceCell>> = OnceCell::new(); 114 | 115 | /// Retrieve the named actor registry handle 116 | fn get_actor_registry<'a>() -> &'a Arc> { 117 | ACTOR_REGISTRY.get_or_init(|| Arc::new(DashMap::new())) 118 | } 119 | 120 | /// Put an actor into the registry 121 | pub(crate) fn register(name: ActorName, actor: ActorCell) -> Result<(), ActorRegistryErr> { 122 | match get_actor_registry().entry(name.clone()) { 123 | Occupied(_) => Err(ActorRegistryErr::AlreadyRegistered(name)), 124 | Vacant(vacancy) => { 125 | vacancy.insert(actor); 126 | Ok(()) 127 | } 128 | } 129 | } 130 | 131 | /// Remove an actor from the registry given it's actor name 132 | pub(crate) fn unregister(name: ActorName) { 133 | if let Some(reg) = ACTOR_REGISTRY.get() { 134 | let _ = reg.remove(&name); 135 | } 136 | } 137 | 138 | /// Try and retrieve an actor from the registry 139 | /// 140 | /// * `name` - The name of the [ActorCell] to try and retrieve 141 | /// 142 | /// Returns: Some(actor) on successful identification of an actor, None if 143 | /// actor not registered 144 | pub fn where_is(name: ActorName) -> Option { 145 | let reg = get_actor_registry(); 146 | reg.get(&name).map(|v| v.value().clone()) 147 | } 148 | 149 | /// Returns a list of names that have been registered 150 | /// 151 | /// Returns: A [`Vec`] of actor names which are registered 152 | /// currently 153 | pub fn registered() -> Vec { 154 | let reg = get_actor_registry(); 155 | reg.iter().map(|kvp| kvp.key().clone()).collect::>() 156 | } 157 | -------------------------------------------------------------------------------- /ractor/src/registry/pid_registry.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 | //! Represents a PID-based registration. Includes all LOCAL actors and their associated pids. It's kept in 7 | //! sync via actor spawn + death management. 8 | 9 | use std::fmt::Debug; 10 | use std::sync::Arc; 11 | 12 | use dashmap::mapref::entry::Entry::Occupied; 13 | use dashmap::mapref::entry::Entry::Vacant; 14 | use dashmap::DashMap; 15 | use once_cell::sync::OnceCell; 16 | 17 | use crate::ActorCell; 18 | use crate::ActorId; 19 | use crate::SupervisionEvent; 20 | 21 | /// Represents a change ocurring to some actor in the global process registry. Only relevant in 22 | /// cluster enabled functionality. 23 | /// 24 | /// It represents actors spawning and exiting, irrespective of procress groups. 25 | #[derive(Clone)] 26 | pub enum PidLifecycleEvent { 27 | /// Some actors joined a group 28 | Spawn(ActorCell), 29 | /// Some actors left a group 30 | Terminate(ActorCell), 31 | } 32 | 33 | impl Debug for PidLifecycleEvent { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | match self { 36 | Self::Spawn(who) => { 37 | write!(f, "Spawn {}", who.get_id()) 38 | } 39 | Self::Terminate(who) => { 40 | write!(f, "Terminate {}", who.get_id()) 41 | } 42 | } 43 | } 44 | } 45 | 46 | static PID_REGISTRY: OnceCell>> = OnceCell::new(); 47 | static PID_REGISTRY_LISTENERS: OnceCell>> = OnceCell::new(); 48 | 49 | fn get_pid_registry<'a>() -> &'a Arc> { 50 | PID_REGISTRY.get_or_init(|| Arc::new(DashMap::new())) 51 | } 52 | 53 | fn get_pid_listeners<'a>() -> &'a Arc> { 54 | PID_REGISTRY_LISTENERS.get_or_init(|| Arc::new(DashMap::new())) 55 | } 56 | 57 | pub(crate) fn register_pid(id: ActorId, actor: ActorCell) -> Result<(), super::ActorRegistryErr> { 58 | if id.is_local() { 59 | match get_pid_registry().entry(id) { 60 | Occupied(_o) => Err(super::ActorRegistryErr::AlreadyRegistered(format!( 61 | "PID {id} already alive" 62 | ))), 63 | Vacant(v) => { 64 | v.insert(actor.clone()); 65 | // notify lifecycle listeners 66 | for listener in get_pid_listeners().iter() { 67 | let _ = 68 | listener 69 | .value() 70 | .send_supervisor_evt(SupervisionEvent::PidLifecycleEvent( 71 | PidLifecycleEvent::Spawn(actor.clone()), 72 | )); 73 | } 74 | Ok(()) 75 | } 76 | } 77 | } else { 78 | Ok(()) 79 | } 80 | } 81 | 82 | pub(crate) fn unregister_pid(id: ActorId) { 83 | if id.is_local() { 84 | if let Some((_, cell)) = get_pid_registry().remove(&id) { 85 | // notify lifecycle listeners 86 | for listener in get_pid_listeners().iter() { 87 | let _ = listener 88 | .value() 89 | .send_supervisor_evt(SupervisionEvent::PidLifecycleEvent( 90 | PidLifecycleEvent::Terminate(cell.clone()), 91 | )); 92 | } 93 | } 94 | } 95 | } 96 | 97 | /// Retrieve all currently registered [crate::Actor]s from the registry 98 | /// 99 | /// Returns [Vec<_>] of [crate::ActorCell]s representing the current actors 100 | /// registered 101 | pub fn get_all_pids() -> Vec { 102 | get_pid_registry() 103 | .iter() 104 | .map(|v| v.value().clone()) 105 | .collect::>() 106 | } 107 | 108 | /// Retrieve an actor from the global registry of all local actors 109 | /// 110 | /// * `id` - The **local** id of the actor to retrieve 111 | /// 112 | /// Returns [Some(_)] if the actor exists locally, [None] otherwise 113 | pub fn where_is_pid(id: ActorId) -> Option { 114 | if id.is_local() { 115 | get_pid_registry().get(&id).map(|v| v.value().clone()) 116 | } else { 117 | None 118 | } 119 | } 120 | 121 | /// Subscribes the provided [crate::Actor] to the PID registry lifecycle 122 | /// events 123 | /// 124 | /// * `actor` - The [ActorCell] representing who will receive updates 125 | pub fn monitor(actor: ActorCell) { 126 | get_pid_listeners().insert(actor.get_id(), actor); 127 | } 128 | 129 | /// Unsubscribes the provided [crate::Actor] from the PID registry lifecycle 130 | /// events 131 | /// 132 | /// * `actor` - The [ActorCell] representing who was receiving updates 133 | pub fn demonitor(actor: ActorId) { 134 | let _ = get_pid_listeners().remove(&actor); 135 | } 136 | -------------------------------------------------------------------------------- /ractor/src/tests.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 | //! Basic tests of errors, error conversions, etc 7 | 8 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 9 | use getrandom as _; 10 | // It was used by examples 11 | use ractor_example_entry_proc as _; 12 | 13 | use crate::concurrency::Duration; 14 | use crate::Actor; 15 | use crate::ActorCell; 16 | use crate::ActorProcessingErr; 17 | use crate::ActorRef; 18 | use crate::RactorErr; 19 | 20 | #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] 21 | wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); 22 | #[test] 23 | #[cfg_attr( 24 | not(all(target_arch = "wasm32", target_os = "unknown")), 25 | tracing_test::traced_test 26 | )] 27 | fn test_error_conversions() { 28 | let messaging = crate::MessagingErr::<()>::InvalidActorType; 29 | let ractor_err = RactorErr::<()>::from(crate::MessagingErr::InvalidActorType); 30 | assert_eq!(messaging.to_string(), ractor_err.to_string()); 31 | 32 | let actor = crate::ActorErr::Cancelled; 33 | let ractor_err = RactorErr::<()>::from(crate::ActorErr::Cancelled); 34 | assert_eq!(actor.to_string(), ractor_err.to_string()); 35 | 36 | let call_result = crate::rpc::CallResult::<()>::Timeout; 37 | let other = format!("{:?}", RactorErr::<()>::from(call_result)); 38 | assert_eq!("Timeout".to_string(), other); 39 | 40 | let call_result = crate::rpc::CallResult::<()>::SenderError; 41 | let other = format!("{}", RactorErr::<()>::from(call_result)); 42 | assert_eq!( 43 | RactorErr::<()>::from(crate::MessagingErr::ChannelClosed).to_string(), 44 | other 45 | ); 46 | } 47 | 48 | #[crate::concurrency::test] 49 | #[cfg_attr( 50 | not(all(target_arch = "wasm32", target_os = "unknown")), 51 | tracing_test::traced_test 52 | )] 53 | async fn test_error_message_extraction() { 54 | struct TestActor; 55 | 56 | #[cfg_attr(feature = "async-trait", crate::async_trait)] 57 | impl Actor for TestActor { 58 | type Msg = (); 59 | type State = (); 60 | type Arguments = (); 61 | 62 | async fn pre_start( 63 | &self, 64 | _: ActorRef, 65 | _: Self::Arguments, 66 | ) -> Result { 67 | Ok(()) 68 | } 69 | } 70 | 71 | let (actor, handle) = Actor::spawn(None, TestActor, ()) 72 | .await 73 | .expect("Failed to start test actor"); 74 | // stop the actor, and wait for death which will free the message channels 75 | actor.stop(None); 76 | handle.await.unwrap(); 77 | 78 | let err = crate::cast!(actor, ()).expect_err("Not an error!"); 79 | assert!(err.has_message()); 80 | assert!(err.try_get_message().is_some()); 81 | 82 | let cell: ActorCell = actor.into(); 83 | let bad_message_actor: ActorRef = cell.into(); 84 | 85 | let err = crate::cast!(bad_message_actor, 0u32).expect_err("Not an error!"); 86 | assert!(!err.has_message()); 87 | assert!(err.try_get_message().is_none()); 88 | } 89 | 90 | #[crate::concurrency::test] 91 | async fn test_platform_sleep_works() { 92 | crate::concurrency::sleep(Duration::from_millis(100)).await; 93 | } 94 | -------------------------------------------------------------------------------- /ractor_cluster/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ractor_cluster" 3 | version = "0.15.6" 4 | authors = ["Sean Lawlor "] 5 | description = "Distributed cluster environment of Ractor actors" 6 | documentation = "https://docs.rs/ractor" 7 | license = "MIT" 8 | edition = "2021" 9 | keywords = ["actor", "ractor", "cluster"] 10 | repository = "https://github.com/slawlor/ractor" 11 | readme = "README.md" 12 | homepage = "https://github.com/slawlor/ractor" 13 | categories = ["asynchronous"] 14 | build = "src/build.rs" 15 | rust-version = "1.64" 16 | 17 | [features] 18 | monitors = ["ractor/monitors"] 19 | message_span_propogation = ["ractor/message_span_propogation"] 20 | async-trait = ["dep:async-trait", "ractor/async-trait"] 21 | 22 | default = [] 23 | 24 | [build-dependencies] 25 | protoc-bin-vendored = "3" 26 | prost-build = { version = "0.13" } 27 | 28 | [dependencies] 29 | ## Required dependencies 30 | bytes = { version = "1" } 31 | prost = { version = "0.13" } 32 | prost-types = { version = "0.13" } 33 | ractor = { version = "0.15.0", default-features = false, features = ["tokio_runtime", "message_span_propogation", "cluster"], path = "../ractor" } 34 | ractor_cluster_derive = { version = "0.15.0", path = "../ractor_cluster_derive" } 35 | rand = "0.8" 36 | sha2 = "0.10" 37 | tokio = { version = "1.30", features = ["rt", "time", "sync", "macros", "net", "io-util", "tracing"]} 38 | tokio-rustls = { version = "0.26" } 39 | tracing = "0.1" 40 | ## Optional dependencies 41 | async-trait = { version = "0.1", optional = true } 42 | 43 | [dev-dependencies] 44 | tokio = { version = "1.30", features = ["rt", "time", "sync", "macros", "net", "io-util", "rt-multi-thread"] } 45 | -------------------------------------------------------------------------------- /ractor_cluster/src/build.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 is the pre-compilation build script for the crate `ractor` when running in distributed 7 | //! mode. It's used to compile protobuf into Rust code prior to compilation. 8 | 9 | /// The shared-path for all protobuf specifications 10 | const PROTOBUF_BASE_DIRECTORY: &str = "src/protocol"; 11 | /// The list of protobuf files to generate inside PROBUF_BASE_DIRECTORY 12 | const PROTOBUF_FILES: [&str; 4] = ["meta", "node", "auth", "control"]; 13 | 14 | fn build_protobufs() { 15 | let path = protoc_bin_vendored::protoc_bin_path().expect("Failed to find protoc installation"); 16 | std::env::set_var("PROTOC", path); 17 | 18 | let mut protobuf_files = Vec::with_capacity(PROTOBUF_FILES.len()); 19 | 20 | for file in PROTOBUF_FILES.iter() { 21 | let proto_file = format!("{PROTOBUF_BASE_DIRECTORY}/{file}.proto"); 22 | println!("cargo:rerun-if-changed={proto_file}"); 23 | protobuf_files.push(proto_file); 24 | } 25 | 26 | prost_build::compile_protos(&protobuf_files, &[PROTOBUF_BASE_DIRECTORY]).unwrap(); 27 | } 28 | 29 | fn main() { 30 | // compile the spec files into Rust code 31 | build_protobufs(); 32 | } 33 | -------------------------------------------------------------------------------- /ractor_cluster/src/hash.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 | //! Hashing utilities mainly used around challenge computation 7 | 8 | pub(crate) const DIGEST_BYTES: usize = 32; 9 | pub(crate) type Digest = [u8; DIGEST_BYTES]; 10 | 11 | /// Compute a challenge digest 12 | pub(crate) fn challenge_digest(secret: &'_ str, challenge: u32) -> Digest { 13 | use sha2::Digest; 14 | 15 | let secret_bytes = secret.as_bytes(); 16 | let mut data = vec![0u8; secret_bytes.len() + 4]; 17 | 18 | let challenge_bytes = challenge.to_be_bytes(); 19 | data[0..4].copy_from_slice(&challenge_bytes); 20 | data[4..].copy_from_slice(secret_bytes); 21 | 22 | let hash = sha2::Sha256::digest(&data); 23 | 24 | hash.into() 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | 30 | use super::challenge_digest; 31 | use super::DIGEST_BYTES; 32 | 33 | #[test] 34 | fn test_challenge_digest_generation() { 35 | let secret = "cookie"; 36 | let challenge: u32 = 42; 37 | let digest = challenge_digest(secret, challenge); 38 | assert_eq!(DIGEST_BYTES, digest.len()); 39 | assert_eq!( 40 | digest, 41 | [ 42 | 20, 62, 0, 217, 211, 179, 29, 157, 36, 69, 47, 133, 172, 4, 68, 137, 83, 8, 26, 2, 43 | 237, 2, 39, 46, 89, 44, 91, 19, 205, 66, 46, 247 44 | ] 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ractor_cluster/src/lib.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 | //! # Support for remote nodes in a distributed cluster. 7 | //! 8 | //! A **node** is the same as [Erlang's definition](https://www.erlang.org/doc/reference_manual/distributed.html) 9 | //! for distributed Erlang, in that it's a remote "hosting" process in the distributed pool of processes. 10 | //! 11 | //! In this realization, nodes are simply actors which handle an external connection to the other nodes in the pool. 12 | //! When nodes connect and are authenticated, they spawn their remote-supporting local actors on the remote system 13 | //! as `RemoteActor`s. The additionally handle synchronizing PG groups so the groups can contain both local 14 | //! and remote actors. 15 | //! 16 | //! We have chosen protobuf for our inter-node defined protocol, however you can chose whatever medium you like 17 | //! for binary serialization + deserialization. The "remote" actor will simply encode your message type and send it 18 | //! over the wire for you 19 | //! 20 | //! (Future) When nodes connect, they identify all of the nodes the remote node is also connected to and additionally connect 21 | //! to them as well. 22 | //! 23 | //! ## Important note on message serialization 24 | //! 25 | //! An important note on usage, when utilizing `ractor_cluster` and [ractor] in the cluster configuration 26 | //! (i.e. `ractor/cluster`), you no longer receive the auto-implementation for all types for [ractor::Message]. This 27 | //! is due to specialization (see: ). Ideally we'd have the trait have a 28 | //! "default" non-serializable implementation for all types that could be messages, and specific implementations for 29 | //! those that can be messages sent over the network. However this is presently a `+nightly` only functionality and 30 | //! has a soundness hole in it's definition and usage. Therefore as a workaround, when the `cluster` feature is enabled 31 | //! on [ractor] the default implementation, specifically 32 | //! 33 | //! ```text 34 | //! impl ractor::Message for T {} 35 | //! ``` 36 | //! is disabled. 37 | //! 38 | //! This means that you need to specify the implementation of the [ractor::Message] trait on all message types, and when 39 | //! they're not network supported messages, this is just a default empty implementation. When they **are** potentially 40 | //! sent over a network in a dist protocol, then you need to fill out the implementation details for how the message 41 | //! serialization is handled. There however is a procedural macro in `ractor_cluster_derive` to facilitate this, which is 42 | //! re-exposed on this crate under the same naming. Simply derive [RactorMessage] or [RactorClusterMessage] if you want local or 43 | //! remote-supporting messages, respectively. 44 | 45 | #![warn( 46 | dead_code, 47 | missing_debug_implementations, 48 | missing_docs, 49 | rust_2018_idioms, 50 | rustdoc::all, 51 | rustdoc::missing_crate_level_docs, 52 | unreachable_pub, 53 | unused_imports, 54 | unused_variables, 55 | unused_crate_dependencies, 56 | clippy::mod_module_files, 57 | unsafe_code 58 | )] 59 | #![cfg_attr(docsrs, feature(doc_cfg))] 60 | 61 | mod hash; 62 | mod net; 63 | mod protocol; 64 | mod remote_actor; 65 | 66 | pub mod macros; 67 | pub mod node; 68 | 69 | /// Node's are representing by an integer id 70 | pub type NodeId = u64; 71 | 72 | // Satisfy dependencies transitively imported 73 | #[cfg(feature = "async-trait")] 74 | use async_trait as _; 75 | // ============== Re-exports ============== // 76 | pub use net::{IncomingEncryptionMode, NetworkStream}; 77 | pub use node::client::connect as client_connect; 78 | pub use node::client::connect_enc as client_connect_enc; 79 | pub use node::client::ClientConnectErr; 80 | pub use node::NodeEventSubscription; 81 | pub use node::NodeServer; 82 | pub use node::NodeServerMessage; 83 | pub use node::NodeSession; 84 | pub use node::NodeSessionMessage; 85 | pub use ractor::serialization::*; 86 | // Re-export the procedural macros so people don't need to reference them directly 87 | pub use ractor_cluster_derive::RactorClusterMessage; 88 | pub use ractor_cluster_derive::RactorMessage; 89 | -------------------------------------------------------------------------------- /ractor_cluster/src/macros.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 | //! Macro helpers for remote actors 7 | 8 | /// Derive [crate::BytesConvertable] for a given prost type. This utilizes the prost 9 | /// extensions 10 | /// 11 | /// Since by Rust's logic, prost::Message could be implemented for any 12 | /// type in the future, this **could** cause a conflicting implementation 13 | /// by defining this generic here, therefore we can't do it for non-concrete types 14 | #[macro_export] 15 | macro_rules! derive_serialization_for_prost_type { 16 | {$ty:ty} => { 17 | #[allow(non_local_definitions)] 18 | impl $crate::BytesConvertable for $ty { 19 | fn into_bytes(self) -> Vec { 20 | ::encode_length_delimited_to_vec(&self) 21 | } 22 | fn from_bytes(bytes: Vec) -> Self { 23 | let buffer = bytes::Bytes::from(bytes); 24 | ::decode_length_delimited(buffer).unwrap() 25 | } 26 | } 27 | }; 28 | } 29 | 30 | #[cfg(test)] 31 | mod test { 32 | use ractor::BytesConvertable; 33 | 34 | use crate::derive_serialization_for_prost_type; 35 | 36 | #[test] 37 | fn test_protobuf_message_serialization() { 38 | derive_serialization_for_prost_type! {crate::protocol::NetworkMessage} 39 | 40 | let original = crate::protocol::NetworkMessage { 41 | message: Some(crate::protocol::meta::network_message::Message::Node( 42 | crate::protocol::node::NodeMessage { 43 | msg: Some(crate::protocol::node::node_message::Msg::Cast( 44 | crate::protocol::node::Cast { 45 | to: 3, 46 | what: vec![0, 1, 2], 47 | variant: "something".to_string(), 48 | metadata: None, 49 | }, 50 | )), 51 | }, 52 | )), 53 | }; 54 | 55 | let bytes = original.clone().into_bytes(); 56 | let decoded = ::from_bytes(bytes); 57 | assert_eq!(original, decoded); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ractor_cluster/src/net.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 | //! TCP server and session actors which transmit [prost::Message] encoded messages 7 | 8 | use std::net::SocketAddr; 9 | 10 | use tokio::net::TcpStream; 11 | 12 | mod listener; 13 | mod session; 14 | 15 | pub(crate) use listener::Listener; 16 | pub(crate) use listener::ListenerMessage; 17 | pub(crate) use session::Session; 18 | pub(crate) use session::SessionMessage; 19 | 20 | /// A network port 21 | pub(crate) type NetworkPort = u16; 22 | 23 | /// A network data stream which can either be 24 | /// 1. unencrypted 25 | /// 2. encrypted and the server-side of the session 26 | /// 3. encrypted and the client-side of the session 27 | #[derive(Debug)] 28 | pub enum NetworkStream { 29 | /// Unencrypted session 30 | Raw { 31 | /// The peer's address 32 | peer_addr: SocketAddr, 33 | /// The local address 34 | local_addr: SocketAddr, 35 | /// The stream 36 | stream: TcpStream, 37 | }, 38 | /// Encrypted as the server-side of the session 39 | TlsServer { 40 | /// The peer's address 41 | peer_addr: SocketAddr, 42 | /// The local address 43 | local_addr: SocketAddr, 44 | /// The stream 45 | stream: tokio_rustls::server::TlsStream, 46 | }, 47 | /// Encrypted as the client-side of the session 48 | TlsClient { 49 | /// The peer's address 50 | peer_addr: SocketAddr, 51 | /// The local address 52 | local_addr: SocketAddr, 53 | /// The stream 54 | stream: tokio_rustls::client::TlsStream, 55 | }, 56 | } 57 | 58 | impl NetworkStream { 59 | pub(crate) fn peer_addr(&self) -> SocketAddr { 60 | match self { 61 | Self::Raw { peer_addr, .. } => *peer_addr, 62 | Self::TlsServer { peer_addr, .. } => *peer_addr, 63 | Self::TlsClient { peer_addr, .. } => *peer_addr, 64 | } 65 | } 66 | 67 | pub(crate) fn local_addr(&self) -> SocketAddr { 68 | match self { 69 | Self::Raw { local_addr, .. } => *local_addr, 70 | Self::TlsServer { local_addr, .. } => *local_addr, 71 | Self::TlsClient { local_addr, .. } => *local_addr, 72 | } 73 | } 74 | } 75 | 76 | /// Incoming encryption mode 77 | #[derive(Clone)] 78 | pub enum IncomingEncryptionMode { 79 | /// Accept sockets raw, with no encryption 80 | Raw, 81 | /// Accept sockets and establish a secure connection 82 | Tls(tokio_rustls::TlsAcceptor), 83 | } 84 | 85 | impl std::fmt::Debug for IncomingEncryptionMode { 86 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 87 | let mut wip = f.debug_struct("IncomingEncryptionMode"); 88 | 89 | match &self { 90 | Self::Raw => { 91 | _ = wip.field("mode", &"Raw"); 92 | } 93 | Self::Tls(config) => { 94 | _ = wip.field("mode", &"Tls").field("config", &config.config()); 95 | } 96 | } 97 | 98 | wip.finish() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ractor_cluster/src/net/listener.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 | //! TCP Server to accept incoming sessions 7 | 8 | use ractor::cast; 9 | use ractor::Actor; 10 | use ractor::ActorProcessingErr; 11 | use ractor::ActorRef; 12 | use tokio::net::TcpListener; 13 | 14 | use super::IncomingEncryptionMode; 15 | use crate::node::NodeServerMessage; 16 | 17 | /// A Tcp Socket [Listener] responsible for accepting new connections and spawning [super::session::Session]s 18 | /// which handle the message sending and receiving over the socket. 19 | /// 20 | /// The [Listener] supervises all of the TCP [super::session::Session] actors and is responsible for logging 21 | /// connects and disconnects as well as tracking the current open [super::session::Session] actors. 22 | pub(crate) struct Listener { 23 | port: super::NetworkPort, 24 | session_manager: ActorRef, 25 | encryption: IncomingEncryptionMode, 26 | } 27 | 28 | impl Listener { 29 | /// Create a new `Listener` 30 | pub(crate) fn new( 31 | port: super::NetworkPort, 32 | session_manager: ActorRef, 33 | encryption: IncomingEncryptionMode, 34 | ) -> Self { 35 | Self { 36 | port, 37 | session_manager, 38 | encryption, 39 | } 40 | } 41 | } 42 | 43 | /// The Node listener's state 44 | pub(crate) struct ListenerState { 45 | listener: Option, 46 | } 47 | 48 | #[derive(crate::RactorMessage)] 49 | pub(crate) struct ListenerMessage; 50 | 51 | #[cfg_attr(feature = "async-trait", ractor::async_trait)] 52 | impl Actor for Listener { 53 | type Msg = ListenerMessage; 54 | type Arguments = ActorRef; 55 | type State = ListenerState; 56 | 57 | async fn pre_start( 58 | &self, 59 | myself: ActorRef, 60 | node_server: Self::Arguments, 61 | ) -> Result { 62 | let addr = format!("[::]:{}", self.port); 63 | let listener = match TcpListener::bind(&addr).await { 64 | Ok(l) => { 65 | // If the used port differs from the user-specified port, inform the node server. 66 | let local_addr = l.local_addr()?; 67 | if local_addr.port() != self.port { 68 | node_server.send_message(NodeServerMessage::PortChanged { 69 | port: local_addr.port(), 70 | })?; 71 | } 72 | 73 | l 74 | } 75 | Err(err) => { 76 | return Err(From::from(err)); 77 | } 78 | }; 79 | 80 | // startup the event processing loop by sending an initial msg 81 | let _ = myself.cast(ListenerMessage); 82 | 83 | // create the initial state 84 | Ok(Self::State { 85 | listener: Some(listener), 86 | }) 87 | } 88 | 89 | async fn post_stop( 90 | &self, 91 | _myself: ActorRef, 92 | state: &mut Self::State, 93 | ) -> Result<(), ActorProcessingErr> { 94 | // close the listener properly, in case anyone else has handles to the actor stopping 95 | // total droppage 96 | drop(state.listener.take()); 97 | Ok(()) 98 | } 99 | 100 | async fn handle( 101 | &self, 102 | myself: ActorRef, 103 | _message: Self::Msg, 104 | state: &mut Self::State, 105 | ) -> Result<(), ActorProcessingErr> { 106 | if let Some(listener) = &mut state.listener { 107 | match listener.accept().await { 108 | Ok((stream, addr)) => { 109 | let local = stream.local_addr()?; 110 | 111 | let session = match &self.encryption { 112 | IncomingEncryptionMode::Raw => Some(super::NetworkStream::Raw { 113 | peer_addr: addr, 114 | local_addr: local, 115 | stream, 116 | }), 117 | IncomingEncryptionMode::Tls(acceptor) => { 118 | match acceptor.accept(stream).await { 119 | Ok(enc_stream) => Some(super::NetworkStream::TlsServer { 120 | peer_addr: addr, 121 | local_addr: local, 122 | stream: enc_stream, 123 | }), 124 | Err(some_err) => { 125 | tracing::warn!("Error establishing secure socket: {some_err}"); 126 | None 127 | } 128 | } 129 | } 130 | }; 131 | 132 | if let Some(stream) = session { 133 | let _ = cast!( 134 | self.session_manager, 135 | NodeServerMessage::ConnectionOpened { 136 | stream: Box::new(stream), 137 | is_server: true 138 | } 139 | ); 140 | tracing::info!("TCP Session opened for {addr}"); 141 | } 142 | } 143 | Err(socket_accept_error) => { 144 | tracing::warn!("Error accepting socket {socket_accept_error} on Node server"); 145 | } 146 | } 147 | } 148 | 149 | // continue accepting new sockets 150 | let _ = myself.cast(ListenerMessage); 151 | Ok(()) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /ractor_cluster/src/node/client.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 contains the logic for initiating client requests to other [super::NodeServer]s 7 | 8 | use std::fmt::Display; 9 | 10 | use ractor::ActorRef; 11 | use ractor::MessagingErr; 12 | use tokio::net::TcpStream; 13 | use tokio::net::ToSocketAddrs; 14 | use tokio_rustls::rustls::pki_types::ServerName; 15 | 16 | /// A client connection error. Possible issues are Socket connection 17 | /// problems or failure to talk to the [super::NodeServer] 18 | #[derive(Debug)] 19 | pub enum ClientConnectErr { 20 | /// Socket failed to bind, returning the underlying tokio error 21 | Socket(tokio::io::Error), 22 | /// Error communicating to the [super::NodeServer] actor. Actor receiving port is 23 | /// closed 24 | Messaging(MessagingErr), 25 | /// Some error with encryption has occurred 26 | Encryption(tokio::io::Error), 27 | } 28 | 29 | impl std::error::Error for ClientConnectErr { 30 | fn cause(&self) -> Option<&dyn std::error::Error> { 31 | match self { 32 | Self::Socket(cause) => Some(cause), 33 | Self::Messaging(cause) => Some(cause), 34 | Self::Encryption(cause) => Some(cause), 35 | } 36 | } 37 | } 38 | 39 | impl Display for ClientConnectErr { 40 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 41 | write!(f, "{self:?}") 42 | } 43 | } 44 | 45 | impl From for ClientConnectErr { 46 | fn from(value: tokio::io::Error) -> Self { 47 | Self::Socket(value) 48 | } 49 | } 50 | 51 | impl From> for ClientConnectErr { 52 | fn from(value: MessagingErr) -> Self { 53 | Self::Messaging(value) 54 | } 55 | } 56 | 57 | /// Connect to another [super::NodeServer] instance 58 | /// 59 | /// * `node_server` - The [super::NodeServer] which will own this new connection session 60 | /// * `address` - The network address to send the connection to. Must implement [ToSocketAddrs] 61 | /// 62 | /// Returns: [Ok(())] if the connection was successful and the [super::NodeSession] was started. Handshake will continue 63 | /// automatically. Results in a [Err(ClientConnectError)] if any part of the process failed to initiate 64 | pub async fn connect( 65 | node_server: &ActorRef, 66 | address: T, 67 | ) -> Result<(), ClientConnectErr> 68 | where 69 | T: ToSocketAddrs, 70 | { 71 | // connect to the socket 72 | let stream = TcpStream::connect(address).await?; 73 | 74 | // Notify the NodeServer that a new connection is opened 75 | let addr = stream.peer_addr()?; 76 | let local = stream.local_addr()?; 77 | 78 | node_server.cast(super::NodeServerMessage::ConnectionOpened { 79 | stream: Box::new(crate::net::NetworkStream::Raw { 80 | stream, 81 | peer_addr: addr, 82 | local_addr: local, 83 | }), 84 | is_server: false, 85 | })?; 86 | 87 | tracing::info!("TCP Session opened for {addr}"); 88 | Ok(()) 89 | } 90 | 91 | /// Connect to another [super::NodeServer] instance with network encryption 92 | /// 93 | /// * `node_server` - The [super::NodeServer] which will own this new connection session 94 | /// * `address` - The network address to send the connection to. Must implement [ToSocketAddrs] 95 | /// * `encryption_settings` - The [tokio_rustls::TlsConnector] which is configured to encrypt the socket 96 | /// * `domain` - The server name we're connecting to ([ServerName]) 97 | /// 98 | /// Returns: [Ok(())] if the connection was successful and the [super::NodeSession] was started. Handshake will continue 99 | /// automatically. Results in a [Err(ClientConnectError)] if any part of the process failed to initiate 100 | pub async fn connect_enc( 101 | node_server: &ActorRef, 102 | address: T, 103 | encryption_settings: tokio_rustls::TlsConnector, 104 | domain: ServerName<'static>, 105 | ) -> Result<(), ClientConnectErr> 106 | where 107 | T: ToSocketAddrs, 108 | { 109 | // connect to the socket 110 | let stream = TcpStream::connect(address).await?; 111 | 112 | let addr = stream.peer_addr()?; 113 | let local = stream.local_addr()?; 114 | 115 | // encrypt the socket 116 | let enc_stream = encryption_settings 117 | .connect(domain, stream) 118 | .await 119 | .map_err(ClientConnectErr::Encryption)?; 120 | 121 | node_server.cast(super::NodeServerMessage::ConnectionOpened { 122 | stream: Box::new(crate::net::NetworkStream::TlsClient { 123 | stream: enc_stream, 124 | peer_addr: addr, 125 | local_addr: local, 126 | }), 127 | is_server: false, 128 | })?; 129 | 130 | tracing::info!("TCP Session opened for {addr}"); 131 | Ok(()) 132 | } 133 | -------------------------------------------------------------------------------- /ractor_cluster/src/protocol.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 | //! Protobuf specifications for over-the-wire intercommuncation 7 | //! between nodes. Generated via [prost] 8 | 9 | /// Node authentication protocol 10 | pub(crate) mod auth { 11 | #![allow(unreachable_pub)] 12 | include!(concat!(env!("OUT_DIR"), "/auth.rs")); 13 | } 14 | 15 | /// Node actor inter-communication protocol 16 | pub(crate) mod node { 17 | #![allow(unreachable_pub)] 18 | include!(concat!(env!("OUT_DIR"), "/node.rs")); 19 | } 20 | 21 | /// Control messages between nodes 22 | pub(crate) mod control { 23 | #![allow(unreachable_pub)] 24 | include!(concat!(env!("OUT_DIR"), "/control.rs")); 25 | } 26 | 27 | /// Meta types which include all base network protocol message types 28 | pub(crate) mod meta { 29 | #![allow(unreachable_pub)] 30 | include!(concat!(env!("OUT_DIR"), "/meta.rs")); 31 | } 32 | 33 | pub(crate) use meta::NetworkMessage; 34 | -------------------------------------------------------------------------------- /ractor_cluster/src/protocol/auth.proto: -------------------------------------------------------------------------------- 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 | //! Authentication handshake messages for connecting `node()`s together. 7 | //! The protocol messages defined here roughly follow the Erlang distributed systems guide 8 | //! found at: https://www.erlang.org/doc/apps/erts/erl_dist_protocol.html#distribution-handshake 9 | 10 | // NOTE: We make heavy use of oneof syntax here in order to deal with a rust-like enum 11 | // type. You can see https://developers.google.com/protocol-buffers/docs/proto3#oneof for 12 | // the syntax and guide 13 | 14 | syntax = "proto3"; 15 | 16 | package auth; 17 | 18 | // Placeholder to represent a node's flags 19 | message NodeFlags { 20 | // The node version 21 | uint32 version = 1; 22 | } 23 | 24 | // A message containing the node's name 25 | message NameMessage { 26 | // The node's full name 27 | // Format: `node_name@hostname` 28 | string name = 1; 29 | 30 | // The node's capability flags 31 | NodeFlags flags = 2; 32 | 33 | // This node's connection details 34 | string connection_string = 3; 35 | } 36 | 37 | // Server -> Client: `SendStatus` is the server replying with the handshake status to the client 38 | message ServerStatus { 39 | // Status types 40 | enum Status { 41 | // The handshake will continue 42 | OK = 0; 43 | // The handshake will continue, but A is informed that B has another ongoing 44 | // connection attempt that will be shut down (simultaneous connect where A's 45 | // name is greater than B's name, compared literally). 46 | OK_SIMULTANEOUS = 1; 47 | // The handshake will not continue, as B already has an ongoing handshake, which 48 | // it itself has initiated (simultaneous connect where B's name is greater than A's). 49 | NOT_OK = 2; 50 | // The connection is disallowed for some (unspecified) security reason. 51 | NOT_ALLOWED = 3; 52 | // A connection to the node is already active, which either means that node A is confused 53 | // or that the TCP connection breakdown of a previous node with this name has not yet 54 | // reached node B. 55 | ALIVE = 4; 56 | 57 | // Skipped NAMED = 5; 58 | } 59 | 60 | // The status 61 | Status status = 1; 62 | } 63 | 64 | // The client's status reply if the `ServerStatus` was ALIVE 65 | // 66 | // If status was alive, node A answers with another status message containing either true, 67 | // which means that the connection is to continue (the old connection from this node is 68 | // broken), or false, which means that the connection is to be closed (the connection 69 | // attempt was a mistake. 70 | message ClientStatus { 71 | // The status 72 | bool status = 1; 73 | } 74 | 75 | // The server's initial challenge request 76 | message Challenge { 77 | // The server's name 78 | string name = 1; 79 | // The node's capability flags 80 | NodeFlags flags = 2; 81 | // The challenge value 82 | uint32 challenge = 3; 83 | // The node's incoming connection string 84 | string connection_string = 4; 85 | } 86 | 87 | // The reply to the server's challenge. 88 | message ChallengeReply { 89 | // The client's own challenge for the server to handle 90 | uint32 challenge = 1; 91 | // An MD5 digest that the client constructed from the server's 92 | // challenge value 93 | bytes digest = 2; 94 | } 95 | 96 | // The server's reply to the client about their own 97 | // challenge 98 | message ChallengeAck { 99 | // Another MD5 digest that the server constructed from the 100 | // client's challenge value 101 | bytes digest = 1; 102 | } 103 | 104 | // A authentication message 105 | message AuthenticationMessage { 106 | // The inner message type 107 | oneof msg { 108 | // Send the name 109 | NameMessage name = 1; 110 | // Send the status 111 | ServerStatus server_status = 2; 112 | // Send the client status 113 | ClientStatus client_status = 3; 114 | // Server's challenge to the client 115 | Challenge server_challenge = 4; 116 | // Client's reply to server's challenge and 117 | // client's own challenge to the server 118 | ChallengeReply client_challenge = 5; 119 | // Server's reply to the client's challenge 120 | ChallengeAck server_ack = 6; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ractor_cluster/src/protocol/control.proto: -------------------------------------------------------------------------------- 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 | //! Control messages for authenticated `NodeSession`s. These manage the synchronization 7 | //! of actors and their lifecycles over the remote link 8 | 9 | // NOTE: We make heavy use of oneof syntax here in order to deal with a rust-like enum 10 | // type. You can see https://developers.google.com/protocol-buffers/docs/proto3#oneof for 11 | // the syntax and guide 12 | 13 | syntax = "proto3"; 14 | 15 | package control; 16 | 17 | import "google/protobuf/timestamp.proto"; 18 | import "auth.proto"; 19 | 20 | // Represents a single actor 21 | message Actor { 22 | // The local PID of the actor 23 | uint64 pid = 1; 24 | // The optional name of the actor 25 | optional string name = 2; 26 | } 27 | 28 | // A heartbeat between actors 29 | message Ping { 30 | // The original time of the ping send, returned in the `Pong` message 31 | // to measure latency 32 | google.protobuf.Timestamp timestamp = 1; 33 | } 34 | 35 | // Reply to a ping 36 | message Pong { 37 | // The original time of the ping send, set by the original sender 38 | google.protobuf.Timestamp timestamp = 1; 39 | } 40 | 41 | // Actor(s) spawn notification 42 | message Spawn { 43 | // The actors to spawn 44 | repeated Actor actors = 1; 45 | } 46 | 47 | // Actor(s) termination event 48 | message Terminate { 49 | // The remote actors' PIDs 50 | repeated uint64 ids = 1; 51 | } 52 | 53 | // Process group join occurred 54 | message PgJoin { 55 | // The group 56 | string group = 1; 57 | // The actors 58 | repeated Actor actors = 2; 59 | // the scope 60 | string scope = 3; 61 | } 62 | 63 | // Process group leave occurred 64 | message PgLeave { 65 | // The group 66 | string group = 1; 67 | // The actors 68 | repeated Actor actors = 2; 69 | // The scope 70 | string scope = 3; 71 | } 72 | 73 | // A collection of NodeSession endpoints 74 | message NodeSessions { 75 | // The list of sessions 76 | repeated auth.NameMessage sessions = 1; 77 | } 78 | 79 | // All state for initial sync has been pushed 80 | message Ready { 81 | } 82 | 83 | // Control messages between authenticated `node()`s which are dist-connected 84 | message ControlMessage { 85 | // The message payload 86 | oneof msg { 87 | // Spawn an actor 88 | Spawn spawn = 1; 89 | // A actor terminated 90 | Terminate terminate = 2; 91 | // A ping 92 | Ping ping = 3; 93 | // A pong 94 | Pong pong = 4; 95 | // A PG group join event 96 | PgJoin pg_join = 5; 97 | // A PG group leave event 98 | PgLeave pg_leave = 6; 99 | // Enumerate the node sessions on the remote peer 100 | auth.NameMessage enumerate_node_sessions = 7; 101 | // The list of node sessions on the remote host for transitive connections 102 | NodeSessions node_sessions = 8; 103 | // All state for initial sync has been pushed 104 | Ready ready = 9; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ractor_cluster/src/protocol/meta.proto: -------------------------------------------------------------------------------- 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 | //! Meta-messages which all bundle together into a single `NetworkMessage` 7 | //! payload 8 | 9 | syntax = "proto3"; 10 | 11 | import "auth.proto"; 12 | import "node.proto"; 13 | import "control.proto"; 14 | 15 | package meta; 16 | 17 | // Represents a message over the network 18 | message NetworkMessage { 19 | // The inner message 20 | oneof message { 21 | // An authentication message 22 | auth.AuthenticationMessage auth = 1; 23 | // An inter-node message 24 | node.NodeMessage node = 2; 25 | // A control message 26 | control.ControlMessage control = 3; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ractor_cluster/src/protocol/node.proto: -------------------------------------------------------------------------------- 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 | // NOTE: We make heavy use of oneof syntax here in order to deal with a rust-like enum 7 | // type. You can see https://developers.google.com/protocol-buffers/docs/proto3#oneof for 8 | // the syntax and guide 9 | 10 | syntax = "proto3"; 11 | 12 | package node; 13 | 14 | // Represents a cast to a remote actor 15 | message Cast { 16 | // `to` is the intended actor 17 | uint64 to = 1; 18 | // `what` is the payload for the cast operation 19 | bytes what = 2; 20 | // Index into the variant 21 | string variant = 3; 22 | // Optional metadata for the call (helps supported nested encodings) 23 | optional bytes metadata = 6; 24 | } 25 | 26 | // An outgoing remote procedure call 27 | message Call { 28 | // `to` is the intended actor 29 | uint64 to = 1; 30 | // `what` is the serialized arguments to the call 31 | bytes what = 2; 32 | // `tag` is a unique request tag which the RemoteActor applied in order 33 | // to match requests back up to replies 34 | uint64 tag = 3; 35 | // `timeout_ms` is the timeout in milliseconds for the call to complete 36 | optional uint64 timeout_ms = 4; 37 | // Index into the variant 38 | string variant = 5; 39 | // Optional metadata for the call (helps supported nested encodings) 40 | optional bytes metadata = 6; 41 | } 42 | 43 | // A reply to a remote procedure call 44 | message CallReply { 45 | // `to` is the intended RemoteActor 46 | uint64 to = 1; 47 | // `tag` is a unique request tag which the RemoteActor applied in order 48 | // to match requests back up to replies 49 | uint64 tag = 2; 50 | // `what` is the payload for the call reply 51 | bytes what = 3; 52 | } 53 | 54 | // An inter-node message for inter-actor communications 55 | message NodeMessage { 56 | // The message payload 57 | oneof msg { 58 | // A cast to a remote actor 59 | Cast cast = 1; 60 | // A call to a remote actor 61 | Call call = 2; 62 | // A reply to a call from the remote actor 63 | CallReply reply = 3; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ractor_cluster/src/remote_actor/tests.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 | use super::*; 7 | 8 | struct FakeNodeSession; 9 | 10 | impl FakeNodeSession { 11 | async fn get_node_session() -> (ActorRef, JoinHandle<()>) { 12 | let (r, h) = Actor::spawn(None, FakeNodeSession, ()) 13 | .await 14 | .expect("Failed to start fake node session"); 15 | let cell: ractor::ActorCell = r.into(); 16 | (cell.into(), h) 17 | } 18 | } 19 | 20 | #[cfg_attr(feature = "async-trait", ractor::async_trait)] 21 | impl Actor for FakeNodeSession { 22 | type Msg = crate::node::NodeSessionMessage; 23 | type State = (); 24 | type Arguments = (); 25 | async fn pre_start( 26 | &self, 27 | _: ActorRef, 28 | _: Self::Arguments, 29 | ) -> Result { 30 | Ok(()) 31 | } 32 | } 33 | 34 | #[ractor::concurrency::test] 35 | async fn remote_actor_serialized_message_handling() { 36 | // setup 37 | let (actor, handle) = FakeNodeSession::get_node_session().await; 38 | let (remote_actor_ref, remote_actor_handle) = Actor::spawn(None, RemoteActor, actor.clone()) 39 | .await 40 | .expect("Failed to spawn remote actor"); 41 | 42 | let remote_actor_instance = RemoteActor; 43 | let mut remote_actor_state = RemoteActorState { 44 | message_tag: 0, 45 | pending_requests: HashMap::new(), 46 | session: actor.clone(), 47 | }; 48 | 49 | // act & verify 50 | let bad_handler = remote_actor_instance 51 | .handle( 52 | remote_actor_ref.clone(), 53 | RemoteActorMessage, 54 | &mut remote_actor_state, 55 | ) 56 | .await; 57 | assert!(bad_handler.is_err()); 58 | 59 | let cast = SerializedMessage::Cast { 60 | variant: "A".to_string(), 61 | args: vec![1, 2, 3], 62 | metadata: None, 63 | }; 64 | let cast_output = remote_actor_instance 65 | .handle_serialized(remote_actor_ref.clone(), cast, &mut remote_actor_state) 66 | .await; 67 | assert!(cast_output.is_ok()); 68 | // cast's don't have pending requests 69 | assert_eq!(0, remote_actor_state.message_tag); 70 | 71 | let (tx, _rx) = ractor::concurrency::oneshot(); 72 | let call = SerializedMessage::Call { 73 | variant: "B".to_string(), 74 | args: vec![1, 2, 3], 75 | reply: tx.into(), 76 | metadata: None, 77 | }; 78 | let call_output = remote_actor_instance 79 | .handle_serialized(remote_actor_ref.clone(), call, &mut remote_actor_state) 80 | .await; 81 | assert!(call_output.is_ok()); 82 | assert_eq!(1, remote_actor_state.message_tag); 83 | assert!(remote_actor_state.pending_requests.contains_key(&1)); 84 | 85 | let reply = SerializedMessage::CallReply(1, vec![3, 4, 5]); 86 | let reply_output = remote_actor_instance 87 | .handle_serialized(remote_actor_ref.clone(), reply, &mut remote_actor_state) 88 | .await; 89 | assert!(reply_output.is_ok()); 90 | assert!(!remote_actor_state.pending_requests.contains_key(&1)); 91 | 92 | // cleanup 93 | remote_actor_ref.stop(None); 94 | actor.stop(None); 95 | remote_actor_handle.await.unwrap(); 96 | handle.await.unwrap(); 97 | } 98 | -------------------------------------------------------------------------------- /ractor_cluster_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ractor_cluster_derive" 3 | version = "0.15.6" 4 | authors = ["Sean Lawlor "] 5 | description = "Derives for ractor_cluster" 6 | license = "MIT" 7 | edition = "2021" 8 | categories = [] 9 | keywords = ["actor", "ractor"] 10 | repository = "https://github.com/slawlor/ractor" 11 | readme = "../README.md" 12 | rust-version = "1.64" 13 | 14 | [lib] 15 | name = "ractor_cluster_derive" 16 | path = "src/lib.rs" 17 | proc-macro = true 18 | 19 | [dependencies] 20 | quote = "1" 21 | syn = { version = "2", features = ["extra-traits"] } 22 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ractor_cluster_integration_tests" 3 | version = "0.0.0" 4 | authors = ["Sean Lawlor "] 5 | description = "Integration tests for ractor_cluster" 6 | license = "MIT" 7 | edition = "2018" 8 | keywords = ["actor", "ractor"] 9 | repository = "https://github.com/slawlor/ractor" 10 | readme = "../README.md" 11 | publish = false 12 | 13 | [features] 14 | blanket_serde = ["ractor/blanket_serde"] 15 | cluster = [] 16 | async-trait = ["ractor_cluster/async-trait"] 17 | 18 | default = [] 19 | 20 | [dependencies] 21 | anyhow = "1" 22 | async-trait = "0.1" 23 | clap = { version = "4", features = ["derive"] } 24 | ractor = { default-features = false, features = ["tokio_runtime", "message_span_propogation", "cluster"], path = "../ractor" } 25 | ractor_cluster = { path = "../ractor_cluster" } 26 | rand = "0.8" 27 | tokio-rustls = { version = "0.26" } 28 | rustls-pemfile = "2.1" 29 | rustyrepl = { version = "0.2", features = ["async"] } 30 | tokio = { version = "1", features = ["rt", "time", "sync", "macros", "rt-multi-thread", "signal", "tracing"] } 31 | tracing = "0.1" 32 | tracing-glog = "0.4" 33 | tracing-subscriber = { version = "0.3", features = ["env-filter"]} 34 | webpki = "0.22" -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build from root of repository with 2 | # 3 | # docker build -f ractor_cluster_integration_tests/Dockerfile --build-arg FEATURES="list,of,features" . -t ractor_integration_tests:latest 4 | # docker run -it ractor_integration_tests:latest help 5 | 6 | FROM rust:latest 7 | WORKDIR /usr/src/app 8 | RUN apt-get update && apt-get install -y cmake build-essential 9 | COPY . . 10 | ARG FEATURES 11 | RUN cargo install --path ractor_cluster_integration_tests/ -F $FEATURES 12 | RUN mv /usr/local/cargo/bin/ractor_cluster_integration_tests /usr/local/bin/ractor_cluster_integration_tests 13 | RUN mv /usr/src/app/ractor_cluster_integration_tests/test-ca/ /usr/local/bin/test-ca 14 | WORKDIR /usr/local/bin 15 | 16 | ENTRYPOINT [ "ractor_cluster_integration_tests" ] 17 | # ENTRYPOINT ["/bin/bash"] 18 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/envs/auth-handshake.env: -------------------------------------------------------------------------------- 1 | A_TEST="auth-handshake 8199" 2 | B_TEST="auth-handshake 8198 8199 node-a" 3 | C_TEST="nan" -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/envs/dist-connect.env: -------------------------------------------------------------------------------- 1 | A_TEST="dist-connect node-a 8199" 2 | B_TEST="dist-connect node-b 8198 8199 node-a" 3 | C_TEST="dist-connect node-c 0 8199 node-a" -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/envs/encryption.env: -------------------------------------------------------------------------------- 1 | A_TEST="encryption 8199" 2 | B_TEST="encryption 8198 8199 node-a" 3 | C_TEST="nan" -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/envs/pg-groups.env: -------------------------------------------------------------------------------- 1 | A_TEST="pg-groups 8199" 2 | B_TEST="pg-groups 8198 8199 node-a" 3 | C_TEST="nan" -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/src/derive_macro_tests.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 | use ractor::message::SerializedMessage; 7 | use ractor::Message; 8 | use ractor::RpcReplyPort; 9 | use ractor_cluster::RactorClusterMessage; 10 | use ractor_cluster::RactorMessage; 11 | 12 | #[test] 13 | fn test_non_serializable_generation() { 14 | #[derive(RactorMessage)] 15 | enum TheMessage { 16 | A, 17 | } 18 | assert!(!TheMessage::serializable()); 19 | 20 | let serialize = TheMessage::A.serialize(); 21 | assert!(serialize.is_err()); 22 | let data = SerializedMessage::Cast { 23 | variant: "A".to_string(), 24 | args: vec![], 25 | metadata: None, 26 | }; 27 | assert!(TheMessage::deserialize(data).is_err()); 28 | } 29 | 30 | #[test] 31 | fn test_serializable_generation() { 32 | #[derive(RactorClusterMessage)] 33 | enum TheMessage { 34 | A, 35 | } 36 | assert!(TheMessage::serializable()); 37 | 38 | let serialize = TheMessage::A.serialize(); 39 | assert!(matches!(serialize, Ok(SerializedMessage::Cast { .. }))); 40 | 41 | let data = SerializedMessage::Cast { 42 | variant: "A".to_string(), 43 | args: vec![], 44 | metadata: None, 45 | }; 46 | assert!(matches!(TheMessage::deserialize(data), Ok(TheMessage::A))); 47 | } 48 | 49 | #[ractor::concurrency::test] 50 | async fn test_complex_serializable_generation() { 51 | #[derive(RactorClusterMessage, Debug)] 52 | enum TheMessage { 53 | A, 54 | #[rpc] 55 | B(RpcReplyPort), 56 | C(Vec), 57 | #[rpc] 58 | D(Vec, Vec, RpcReplyPort>), 59 | } 60 | assert!(TheMessage::serializable()); 61 | 62 | // Variant A 63 | let serialize = TheMessage::A.serialize(); 64 | assert!(matches!(serialize, Ok(SerializedMessage::Cast { .. }))); 65 | let data = SerializedMessage::Cast { 66 | variant: "A".to_string(), 67 | args: vec![], 68 | metadata: None, 69 | }; 70 | assert!(matches!(TheMessage::deserialize(data), Ok(TheMessage::A))); 71 | let data = SerializedMessage::Cast { 72 | variant: "B".to_string(), 73 | args: vec![], 74 | metadata: None, 75 | }; 76 | assert!(TheMessage::deserialize(data).is_err()); 77 | 78 | // Variant B 79 | let serialize = TheMessage::B(ractor::concurrency::oneshot().0.into()).serialize(); 80 | assert!(matches!(serialize, Ok(SerializedMessage::Call { .. }))); 81 | let data = SerializedMessage::Call { 82 | variant: "B".to_string(), 83 | reply: ractor::concurrency::oneshot().0.into(), 84 | args: vec![], 85 | metadata: None, 86 | }; 87 | assert!(matches!( 88 | TheMessage::deserialize(data), 89 | Ok(TheMessage::B(_)) 90 | )); 91 | let data = SerializedMessage::Call { 92 | variant: "A".to_string(), 93 | args: vec![], 94 | reply: ractor::concurrency::oneshot().0.into(), 95 | metadata: None, 96 | }; 97 | assert!(TheMessage::deserialize(data).is_err()); 98 | 99 | // Variant C 100 | let serialize = TheMessage::C(vec![0u128, 1u128]).serialize(); 101 | assert!(matches!(serialize, Ok(SerializedMessage::Cast { .. }))); 102 | let data = serialize.unwrap(); 103 | if let TheMessage::C(data) = 104 | TheMessage::deserialize(data).expect("Failed to deserialize serialized message") 105 | { 106 | assert_eq!(data, vec![0u128, 1u128]); 107 | } else { 108 | panic!("Deserialized message to incorrect type"); 109 | } 110 | 111 | // Variant D 112 | let serialize = TheMessage::D( 113 | vec![0u8, 5u8], 114 | vec![1, 2, 3, 4], 115 | ractor::concurrency::oneshot().0.into(), 116 | ) 117 | .serialize(); 118 | assert!(matches!(serialize, Ok(SerializedMessage::Call { .. }))); 119 | if let TheMessage::D(data1, data2, _port) = TheMessage::deserialize(serialize.unwrap()) 120 | .expect("Failed to deserialize previously serialized message") 121 | { 122 | assert_eq!(data1, vec![0u8, 5u8]); 123 | assert_eq!(data2, vec![1, 2, 3, 4]); 124 | } else { 125 | panic!("Deserialized message to incorrect type"); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/src/main.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 | use std::env; 7 | use std::io::stderr; 8 | use std::io::IsTerminal; 9 | 10 | use clap::Parser; 11 | use rustyrepl::Repl; 12 | use rustyrepl::ReplCommandProcessor; 13 | use tracing_glog::Glog; 14 | use tracing_glog::GlogFields; 15 | use tracing_subscriber::filter::Directive; 16 | use tracing_subscriber::filter::EnvFilter; 17 | use tracing_subscriber::layer::SubscriberExt; 18 | use tracing_subscriber::Registry; 19 | 20 | mod repl; 21 | mod tests; 22 | 23 | // other tests mainly related to the procedural macros on `Ractor*Message` 24 | // derivation, which can't be inside the `ractor_cluster` crate due to imported 25 | // namespaces 26 | #[cfg(test)] 27 | mod derive_macro_tests; 28 | #[cfg(test)] 29 | mod ractor_forward_port_tests; 30 | 31 | #[derive(Debug, clap::Subcommand)] 32 | enum Command { 33 | /// Run a test-case directly 34 | #[command(subcommand)] 35 | Test(tests::TestCase), 36 | /// Start the Read-evaluate-print-loop interface 37 | Repl, 38 | } 39 | 40 | /// Test-suite runner 41 | #[derive(Parser, Debug)] 42 | #[command(author, version, about, long_about = None)] 43 | struct Args { 44 | #[command(subcommand)] 45 | command: Command, 46 | 47 | /// Set the logging level based on the set of filter directives. 48 | /// 49 | /// Normal logging levels are supported (e.g. trace, debug, info, warn, 50 | /// error), but it's possible to set verbosity for specific spans and 51 | /// events. 52 | #[clap(short, long, default_value = "info", use_value_delimiter = true)] 53 | log: Vec, 54 | } 55 | 56 | // MAIN // 57 | #[tokio::main(flavor = "multi_thread", worker_threads = 4)] 58 | async fn main() { 59 | let args = Args::parse(); 60 | 61 | // if it's not set, set the log level to debug 62 | if env::var("RUST_LOG").is_err() { 63 | env::set_var("RUST_LOG", "debug"); 64 | } 65 | init_logging(args.log); 66 | 67 | let repl_processor = repl::TestRepl; 68 | match args.command { 69 | Command::Repl => { 70 | let processor: Box> = 71 | Box::new(repl_processor); 72 | let mut repl = Repl::::new(processor, None, Some(">>".to_string())) 73 | .expect("Failed to create REPL"); 74 | repl.process().await.expect("REPL exited with error"); 75 | } 76 | 77 | Command::Test(case) => { 78 | tokio::select! { 79 | out = repl_processor.process_command(case) => { 80 | out.expect("Failed to process command"); 81 | } 82 | _ = tokio::signal::ctrl_c() => { 83 | tracing::info!("CTRL-C pressed, exiting test"); 84 | } 85 | } 86 | } 87 | } 88 | tracing::info!("Test exiting"); 89 | } 90 | 91 | fn init_logging(directives: Vec) { 92 | let fmt = tracing_subscriber::fmt::Layer::default() 93 | .with_ansi(stderr().is_terminal()) 94 | .with_writer(std::io::stderr) 95 | .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default())) 96 | .fmt_fields(GlogFields::default().compact()); 97 | 98 | let filter = directives 99 | .into_iter() 100 | .fold(EnvFilter::from_default_env(), |filter, directive| { 101 | filter.add_directive(directive) 102 | }); 103 | 104 | let subscriber = Registry::default().with(filter).with(fmt); 105 | tracing::subscriber::set_global_default(subscriber).expect("to set global subscriber"); 106 | } 107 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/src/ractor_forward_port_tests.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 | use ractor::concurrency::Duration; 7 | use ractor::message::SerializedMessage; 8 | use ractor::Message; 9 | use ractor::RpcReplyPort; 10 | use ractor_cluster::RactorClusterMessage; 11 | 12 | #[derive(RactorClusterMessage)] 13 | enum MessageType { 14 | Cast(String), 15 | #[rpc] 16 | Call(String, RpcReplyPort), 17 | } 18 | 19 | fn get_len_encoded_string(data: Vec) -> String { 20 | let mut ptr = 0; 21 | let mut len_bytes = [0u8; 8]; 22 | len_bytes.copy_from_slice(&data[ptr..ptr + 8]); 23 | let len = u64::from_be_bytes(len_bytes) as usize; 24 | ptr += 8; 25 | let data_bytes = data[ptr..ptr + len].to_vec(); 26 | 27 | ::from_bytes(data_bytes) 28 | } 29 | 30 | #[ractor::concurrency::test] 31 | async fn no_timeout_rpc() { 32 | let (tx, rx) = ractor::concurrency::oneshot(); 33 | let no_timeout = MessageType::Call("test".to_string(), tx.into()); 34 | let no_timeout_serialized = no_timeout.serialize().expect("Failed to serialize"); 35 | match no_timeout_serialized { 36 | SerializedMessage::Call { args, reply, .. } => { 37 | let str = get_len_encoded_string(args); 38 | let _ = reply.send(::into_bytes( 39 | str, 40 | )); 41 | } 42 | _ => panic!("Invalid"), 43 | } 44 | 45 | let no_timeout_reply = rx.await.expect("Receive error"); 46 | assert_eq!(no_timeout_reply, "test".to_string()); 47 | } 48 | 49 | #[ractor::concurrency::test] 50 | async fn with_timeout_rpc() { 51 | let (tx, rx) = ractor::concurrency::oneshot(); 52 | let duration = Duration::from_millis(10); 53 | let with_timeout = MessageType::Call("test".to_string(), (tx, duration).into()); 54 | 55 | let with_timeout_serialized = with_timeout.serialize().expect("Failed to serialize"); 56 | match with_timeout_serialized { 57 | SerializedMessage::Call { args, reply, .. } => { 58 | let str = get_len_encoded_string(args); 59 | let _ = reply.send(::into_bytes( 60 | str, 61 | )); 62 | } 63 | _ => panic!("Invalid"), 64 | } 65 | 66 | let with_timeout_reply = rx.await.expect("Receive error"); 67 | assert_eq!(with_timeout_reply, "test".to_string()); 68 | } 69 | 70 | #[ractor::concurrency::test] 71 | async fn timeout_rpc() { 72 | let (tx, rx) = ractor::concurrency::oneshot(); 73 | let duration = Duration::from_millis(10); 74 | let with_timeout = MessageType::Call("test".to_string(), (tx, duration).into()); 75 | 76 | let with_timeout_serialized = with_timeout.serialize().expect("Failed to serialize"); 77 | match with_timeout_serialized { 78 | SerializedMessage::Call { args, reply, .. } => { 79 | ractor::concurrency::sleep(Duration::from_millis(50)).await; 80 | let _ = reply.send(args); 81 | } 82 | _ => panic!("Invalid"), 83 | } 84 | 85 | let result = rx.await; 86 | assert!(result.is_err()); 87 | } 88 | 89 | #[ractor::concurrency::test] 90 | async fn no_timeout_rpc_decoded_reply() { 91 | let (tx, rx) = ractor::concurrency::oneshot(); 92 | 93 | let no_timeout = MessageType::Call("test".to_string(), tx.into()); 94 | let no_timeout_serialized = no_timeout.serialize().expect("Failed to serialize"); 95 | let no_timeout_deserialized = 96 | MessageType::deserialize(no_timeout_serialized).expect("Failed to deserialize port"); 97 | if let MessageType::Call(args, reply) = no_timeout_deserialized { 98 | let _ = reply.send(args); 99 | } else { 100 | panic!("Failed to decode with `MessageType`"); 101 | } 102 | 103 | let no_timeout_reply = rx.await.expect("Receive error"); 104 | assert_eq!(no_timeout_reply, "test".to_string()); 105 | } 106 | 107 | #[ractor::concurrency::test] 108 | async fn with_timeout_rpc_decoded_reply() { 109 | let (tx, rx) = ractor::concurrency::oneshot(); 110 | 111 | let initial_call = 112 | MessageType::Call("test".to_string(), (tx, Duration::from_millis(50)).into()); 113 | let serialized_call = initial_call.serialize().expect("Failed to serialize"); 114 | let deserialized_call = 115 | MessageType::deserialize(serialized_call).expect("Failed to deserialize port"); 116 | if let MessageType::Call(args, reply) = deserialized_call { 117 | let _ = reply.send(args); 118 | } else { 119 | panic!("Failed to decode with `MessageType`"); 120 | } 121 | 122 | let message_reply = rx.await.expect("Receive error"); 123 | assert_eq!(message_reply, "test".to_string()); 124 | } 125 | 126 | #[ractor::concurrency::test] 127 | async fn with_timeout_rpc_decoded_timeout() { 128 | let (tx, rx) = ractor::concurrency::oneshot(); 129 | 130 | let initial_call = 131 | MessageType::Call("test".to_string(), (tx, Duration::from_millis(50)).into()); 132 | let serialized_call = initial_call.serialize().expect("Failed to serialize"); 133 | let deserialized_call = 134 | MessageType::deserialize(serialized_call).expect("Failed to deserialize port"); 135 | if let MessageType::Call(args, reply) = deserialized_call { 136 | ractor::concurrency::sleep(Duration::from_millis(100)).await; 137 | let _ = reply.send(args); 138 | } else { 139 | panic!("Failed to decode with `MessageType`"); 140 | } 141 | 142 | assert!(rx.await.is_err()); 143 | } 144 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/src/repl.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 | use anyhow::Result; 7 | use rustyrepl::*; 8 | 9 | use crate::tests::TestCase; 10 | 11 | #[derive(Debug)] 12 | pub struct TestRepl; 13 | 14 | #[async_trait::async_trait] 15 | impl ReplCommandProcessor for TestRepl { 16 | fn is_quit(&self, command: &str) -> bool { 17 | matches!(command, "quit" | "exit") 18 | } 19 | 20 | async fn process_command(&self, command: TestCase) -> Result<()> { 21 | let code = match command { 22 | TestCase::AuthHandshake(config) => crate::tests::auth_handshake::test(config).await, 23 | TestCase::PgGroups(config) => crate::tests::pg_groups::test(config).await, 24 | TestCase::Encryption(config) => crate::tests::encryption::test(config).await, 25 | TestCase::DistConnect(config) => crate::tests::dist_connect::test(config).await, 26 | 27 | TestCase::Nan => { 28 | ractor::concurrency::sleep(ractor::concurrency::Duration::from_secs(2)).await; 29 | 0 30 | } 31 | }; 32 | 33 | if code < 0 { 34 | tracing::error!("Test failed with code {code}"); 35 | // BLOW UP THE WORLD! 36 | std::process::exit(code); 37 | } 38 | 39 | Ok(()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/src/tests/auth_handshake.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 | //! Test the authentication handshake, making sure 2 nodes can interconnect together and authenticate 7 | //! with the secret cookie challenge scheme 8 | 9 | use clap::Args; 10 | use ractor::concurrency::sleep; 11 | use ractor::concurrency::Duration; 12 | use ractor::concurrency::Instant; 13 | use ractor::Actor; 14 | 15 | const AUTH_TIME_ALLOWANCE_MS: u128 = 1500; 16 | 17 | /// Configuration 18 | #[derive(Args, Debug, Clone)] 19 | pub struct AuthHandshakeConfig { 20 | /// Server port 21 | server_port: u16, 22 | /// If specified, represents the client to connect to 23 | client_port: Option, 24 | /// If specified, represents the client to connect to 25 | client_host: Option, 26 | } 27 | 28 | #[derive(Debug)] 29 | struct SubscriptionEventLogger; 30 | 31 | impl ractor_cluster::NodeEventSubscription for SubscriptionEventLogger { 32 | fn node_session_authenicated(&self, ses: ractor_cluster::node::NodeServerSessionInformation) { 33 | tracing::warn!( 34 | "[SubscriptionEventLogger] Node {} ({}) authenticated", 35 | ses.node_id, 36 | ses.peer_addr 37 | ); 38 | } 39 | fn node_session_disconnected(&self, ses: ractor_cluster::node::NodeServerSessionInformation) { 40 | tracing::warn!( 41 | "[SubscriptionEventLogger] Node {} ({}) disconnected", 42 | ses.node_id, 43 | ses.peer_addr 44 | ); 45 | } 46 | fn node_session_opened(&self, ses: ractor_cluster::node::NodeServerSessionInformation) { 47 | tracing::warn!( 48 | "[SubscriptionEventLogger] Node {} ({}) opened", 49 | ses.node_id, 50 | ses.peer_addr 51 | ); 52 | } 53 | } 54 | 55 | pub async fn test(config: AuthHandshakeConfig) -> i32 { 56 | let cookie = "cookie".to_string(); 57 | let hostname = "localhost".to_string(); 58 | 59 | let server = ractor_cluster::NodeServer::new( 60 | config.server_port, 61 | cookie, 62 | super::random_name(), 63 | hostname.clone(), 64 | None, 65 | None, 66 | ); 67 | 68 | tracing::info!("Starting NodeServer on port {}", config.server_port); 69 | 70 | let (actor, handle) = Actor::spawn(None, server, ()) 71 | .await 72 | .expect("Failed to start NodeServer"); 73 | 74 | let log_sub = Box::new(SubscriptionEventLogger); 75 | actor 76 | .cast(ractor_cluster::NodeServerMessage::SubscribeToEvents { 77 | id: "logger".to_string(), 78 | subscription: log_sub, 79 | }) 80 | .expect("Failed to send log subscription"); 81 | 82 | if let (Some(client_host), Some(client_port)) = (config.client_host, config.client_port) { 83 | tracing::info!("Connecting to remote NodeServer at {client_host}:{client_port}"); 84 | if let Err(error) = 85 | ractor_cluster::client_connect(&actor, format!("{client_host}:{client_port}")).await 86 | { 87 | tracing::error!("Failed to connect with error {error}"); 88 | return -3; 89 | } else { 90 | tracing::info!("Client connected NodeServer b to NodeServer a"); 91 | } 92 | } 93 | 94 | let mut err_code = -1; 95 | tracing::info!("Waiting for NodeSession status updates"); 96 | 97 | let mut rpc_reply = ractor::call_t!(actor, ractor_cluster::NodeServerMessage::GetSessions, 200); 98 | let mut tic = None; 99 | 100 | while rpc_reply.is_ok() { 101 | if let Some(timestamp) = tic { 102 | let time: Duration = Instant::now() - timestamp; 103 | if time.as_millis() > AUTH_TIME_ALLOWANCE_MS { 104 | err_code = -2; 105 | tracing::error!( 106 | "The authentcation time has been going on for over > {}ms. Failing the test", 107 | time.as_millis() 108 | ); 109 | break; 110 | } 111 | } 112 | 113 | if let Some(item) = rpc_reply 114 | .unwrap() 115 | .into_values() 116 | .collect::>() 117 | .first() 118 | .cloned() 119 | { 120 | // we got an actor, track how long it took to auth, maxing out at 500ms 121 | if tic.is_none() { 122 | tic = Some(Instant::now()); 123 | } 124 | 125 | let is_authenticated = ractor::call_t!( 126 | item.actor, 127 | ractor_cluster::NodeSessionMessage::GetAuthenticationState, 128 | 200 129 | ); 130 | match is_authenticated { 131 | Err(err) => { 132 | tracing::warn!("NodeSession returned error on rpc query {err}"); 133 | break; 134 | } 135 | Ok(false) => { 136 | // Still waiting 137 | } 138 | Ok(true) => { 139 | err_code = 0; 140 | tracing::info!("Authentication succeeded. Exiting test"); 141 | break; 142 | } 143 | } 144 | } 145 | // try again 146 | rpc_reply = ractor::call_t!(actor, ractor_cluster::NodeServerMessage::GetSessions, 200); 147 | } 148 | 149 | tracing::info!("Terminating test - code {err_code}"); 150 | 151 | sleep(Duration::from_millis(250)).await; 152 | 153 | // cleanup 154 | actor.stop(None); 155 | handle.await.unwrap(); 156 | 157 | err_code 158 | } 159 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/src/tests/dist_connect.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 | //! Test the transitive dist-connect functionality of the cluster. If B -> A and C -> A 7 | //! then C should auto-connect to B 8 | 9 | use clap::Args; 10 | use ractor::concurrency::sleep; 11 | use ractor::concurrency::Duration; 12 | use ractor::concurrency::Instant; 13 | use ractor::Actor; 14 | 15 | const DIST_CONNECT_TIME_ALLOWANCE_MS: u128 = 2000; 16 | 17 | /// Configuration 18 | #[derive(Args, Debug, Clone)] 19 | pub struct DistConnectConfig { 20 | /// Node's name (also DNS name) 21 | node_name: String, 22 | /// Server port 23 | server_port: u16, 24 | /// If specified, represents the client to connect to 25 | client_port: Option, 26 | /// If specified, represents the client to connect to 27 | client_host: Option, 28 | } 29 | 30 | pub async fn test(config: DistConnectConfig) -> i32 { 31 | let cookie = "cookie".to_string(); 32 | 33 | let server = ractor_cluster::NodeServer::new( 34 | config.server_port, 35 | cookie, 36 | super::random_name(), 37 | config.node_name.clone(), 38 | None, 39 | if config.node_name.as_str() == "node-c" { 40 | Some(ractor_cluster::node::NodeConnectionMode::Transitive) 41 | } else { 42 | Some(ractor_cluster::node::NodeConnectionMode::Isolated) 43 | }, 44 | ); 45 | 46 | tracing::info!("Starting NodeServer on port {}", config.server_port); 47 | // startup the node server 48 | let (actor, handle) = Actor::spawn(None, server, ()) 49 | .await 50 | .expect("Failed to start NodeServer"); 51 | 52 | // let the nodeserver startup and start listening on the port 53 | sleep(Duration::from_millis(100)).await; 54 | 55 | // if you're Node-c, wait for Node-b to be fully authenticated first 56 | if config.node_name.as_str() == "node-c" { 57 | // Let B fully authenticate to A before starting C so it'll be available in the listing. 58 | sleep(Duration::from_millis(100)).await; 59 | } 60 | 61 | // If this server should connect to a client server, initiate that connection 62 | if let (Some(client_host), Some(client_port)) = (config.client_host, config.client_port) { 63 | tracing::info!("Connecting to remote NodeServer at {client_host}:{client_port}"); 64 | if let Err(error) = 65 | ractor_cluster::node::client::connect(&actor, format!("{client_host}:{client_port}")) 66 | .await 67 | { 68 | tracing::error!("Failed to connect with error {error}"); 69 | return -3; 70 | } else { 71 | tracing::info!( 72 | "Client connected {} to {client_host}:{client_port}", 73 | config.node_name 74 | ); 75 | } 76 | } 77 | 78 | let mut err_code = -1; 79 | tracing::info!("Waiting for NodeSession status updates"); 80 | 81 | let mut rpc_reply = ractor::call_t!(actor, ractor_cluster::NodeServerMessage::GetSessions, 200); 82 | let tic = Instant::now(); 83 | 84 | while rpc_reply.is_ok() { 85 | let time: Duration = Instant::now() - tic; 86 | if time.as_millis() > DIST_CONNECT_TIME_ALLOWANCE_MS { 87 | err_code = -2; 88 | tracing::error!( 89 | "The dist-connect test time has been going on for over > {}ms. Failing the test", 90 | time.as_millis() 91 | ); 92 | break; 93 | } 94 | 95 | let values = rpc_reply 96 | .unwrap() 97 | .into_values() 98 | .filter_map(|v| v.peer_name) 99 | .collect::>(); 100 | if values.len() >= 2 { 101 | // Our node as at least 2 connections 102 | tracing::debug!("Connected session information: {:?}", values); 103 | tracing::info!("Transitive connections succeeded. Exiting"); 104 | err_code = 0; 105 | break; 106 | } 107 | 108 | // try again 109 | rpc_reply = ractor::call_t!(actor, ractor_cluster::NodeServerMessage::GetSessions, 200); 110 | } 111 | 112 | tracing::info!("Terminating test - code {err_code}"); 113 | 114 | // Let the other nodes exist for some time to make sure we get to a stable network state before actually terminating nodes 115 | sleep(Duration::from_millis(500)).await; 116 | 117 | // cleanup 118 | actor.stop(None); 119 | handle.await.unwrap(); 120 | 121 | err_code 122 | } 123 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/src/tests/mod.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 | //! Different test scenarios are defined here 7 | 8 | use clap::Parser; 9 | use rand::distributions::Alphanumeric; 10 | use rand::thread_rng; 11 | use rand::Rng; 12 | 13 | pub mod auth_handshake; 14 | pub mod dist_connect; 15 | pub mod encryption; 16 | pub mod pg_groups; 17 | 18 | fn random_name() -> String { 19 | thread_rng() 20 | .sample_iter(&Alphanumeric) 21 | .take(30) 22 | .map(char::from) 23 | .collect() 24 | } 25 | 26 | #[derive(Parser, Debug, Clone)] 27 | pub enum TestCase { 28 | /// Test auth handshake 29 | AuthHandshake(auth_handshake::AuthHandshakeConfig), 30 | /// Test pg groups through a ractor cluster 31 | PgGroups(pg_groups::PgGroupsConfig), 32 | /// Test encrypted socket communications (through the auth handshake) 33 | Encryption(encryption::EncryptionConfig), 34 | /// Test the transitive connection of a cluster 35 | DistConnect(dist_connect::DistConnectConfig), 36 | /// Not-a-Node: Don't run any test and exit this node with code 0 37 | Nan, 38 | } 39 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Joseph Birr-Pixton 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/README.md: -------------------------------------------------------------------------------- 1 | # Rustls Test CA 2 | 3 | This directory contains various test certificate authorities, intermediates, 4 | end-entity, and client certificates that are used by Rustls integration tests. 5 | 6 | You can regenerate the data in this directory by running the 7 | `rustls/examples/internal/test_ca.rs` tool: 8 | 9 | ```bash 10 | cargo run -p rustls --example test_ca 11 | ``` 12 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/ca.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDPzCCAiegAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 3 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 4 | MFowHzEdMBsGA1UEAwwUcG9ueXRvd24gUlNBIDIwNDggQ0EwggEiMA0GCSqGSIb3 5 | DQEBAQUAA4IBDwAwggEKAoIBAQCy4ZPzrQwfu3tz3ll0xHzlHtAQEHUyXzSo3VIn 6 | ULFPC9D84LWe9zpQBYGxOtl8OVcMdmEbzoIOHO6afNSnzbcQdZymJ4+FMpmusWdV 7 | CcWQ7Knu4Ok0mWlw7HXgtnhunjX0BRMy5M6WXWk6MH44G7n3DCQDINAJvGAwirIT 8 | kUfekbnwDCIEmIZNQQKyGdndn6mQGHt+oWdmgiWxI0dPjU/vsyFQDFomX2IF/fn4 9 | j/lKvhGPEccO/2zQThUrbVck1t7kRilCDZbShGav3+87h42mISriK5yqVOwPzPL4 10 | HWjIL+G2sMDY7RdFKQHBCY4Gis5rrLtOOhKtoDmos7oCCs1BAgMBAAGjgYMwgYAw 11 | HwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9ELYwDgYDVR0PAQH/BAQDAgH+ 12 | MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUZ8fKnr69 13 | bZrvOOQ1Jii60vG9ELYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC 14 | AQEAhTU7Iv/HBuSOaIAOTrgqbvwKUoZsGG0vb1DVOYINasnDDVx+celU9jvzSHRy 15 | +5kZUgflytZIy1TmE/RvQi8/vd2s6BL4DGF39VyFO49g746EcBUEmNGZFS0J/hVa 16 | iZj3yNO/tO5V3nzrGzTrrx9cRnFykLKE/qDnrIOfCiRk5KYui1M5JYBM61r3MDLA 17 | lrRCZh4u04jdNVHPF14km5VcgJvhpGkX2mpe3J9ZtOwBdc96e5zJ0PPKofAuVhuD 18 | J8t+QNnjXKSyf1Ud/5WAOKinqGK902mRAwEFISQko0OajUkUzjZabr6xnf4fYWId 19 | JPT9si0E6DxRUmBTQ0Dy4zU1AQ== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/ca.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slawlor/ractor/38faea5de8347f6a02fdea45d7480625ab3b5f79/ractor_cluster_integration_tests/test-ca/rsa-2048/ca.der -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCy4ZPzrQwfu3tz 3 | 3ll0xHzlHtAQEHUyXzSo3VInULFPC9D84LWe9zpQBYGxOtl8OVcMdmEbzoIOHO6a 4 | fNSnzbcQdZymJ4+FMpmusWdVCcWQ7Knu4Ok0mWlw7HXgtnhunjX0BRMy5M6WXWk6 5 | MH44G7n3DCQDINAJvGAwirITkUfekbnwDCIEmIZNQQKyGdndn6mQGHt+oWdmgiWx 6 | I0dPjU/vsyFQDFomX2IF/fn4j/lKvhGPEccO/2zQThUrbVck1t7kRilCDZbShGav 7 | 3+87h42mISriK5yqVOwPzPL4HWjIL+G2sMDY7RdFKQHBCY4Gis5rrLtOOhKtoDmo 8 | s7oCCs1BAgMBAAECggEAEA1QEojP0rUnQeGNE35iWskmEJr5eVizyAd8TMzf7GD6 9 | EwczJR24XN/xoG7Qf2ZB2ntxAWbJouyTuMdE6f361H5JXVuldGVzBwsiY64pzXR/ 10 | V1FVfAPcQ2bkuRKGwh5uZwRhkaqYo0OkorUhTnWmfXXYcXNrV8RQuWpoynq6AU3a 11 | An2kduxMN5q3s7Dmzsmumwt1RN6rmsfVRCmhZeQvh69xAmu3swx6QsO2jczUGIS2 12 | FybheUXyjSQ6SKplR/pjC5wuJzamNuJbwHuCMUpwvTRt0fHGdooR3yIoP6sbRnb+ 13 | ywk2Vqm4QS60ac6kuuU6oF3xbQHNWxVkaDMBNh4lwQKBgQDW+jXMB4FD/XfRKIvj 14 | 0e28rG16aR3/vz84WAnrXCo60d95AAZA/B/wvlrLBDZF9LBiMirR4LozK+/JV0TF 15 | JGuWB2rtA7Qq7g0OZ/ox5dC/VuvmW0jOeOWn4z6wZ41EduzqbG0cUeOLEwBSrimg 16 | tUCD1xZrLPnJvRV48Pb5mfrM+QKBgQDVBAsNfI6cuk6DObG4mEaQLqrcKAQlYY0D 17 | X9nH7Qy+f1XTU4SigywDv27j18cu2/XN1wFPR/pyv+ORC83E3zhafhTsQwP/N+Nm 18 | 9ReNVHe6x6X3Pq/x1Jr+/vqApWYB3D27LH1Nu+x5IlDWDejqEqb8z1PwUo+Jiwp9 19 | 9eFXqv78iQKBgDnJvsZeyhfNvfSikqAmIWMTcsZyFVdXQJZTzC7RcqSnIAx1vjif 20 | zbI4aU2jjzdU5EkxNSN/5m21BNs5s0nPVYBZ9BtMYJUdsrLJro7VOT0GD5m3tiWS 21 | iCGOS56951KdKWiGxvnS5XjQHckb+iKyNmp6WhNrXqYlRu1g0PMdujZ5AoGBANHY 22 | w9KrbAq19vaLdar2SD1t2+A6g6sXqMU0PeUYf7+Czdf7pOQ7+LZQdjt6rLxa8JGx 23 | R5cEv5Mful1/lVxm0qtDHfWKJ3vbYOhidqtCFRkp6TPiy42e9npSUhE933nhr+ae 24 | c2qKwPCsf5n/5f4CFgNjc7uCeejz0og4G6Az0jKRAoGBAJoHfLXoJM4hpBEfYAto 25 | Ym4pHVvN1yIVj0Ub8nKLXR7hax9bUCKVAtscJkyGPbAS5QtmdXkcsiWzKO5aT0cq 26 | oOsNi/Ck2FRGNA6wf/1ZryZLuzGwS11kQDmzCIw/tp8ZzYhuM7hYu5FZ0EGsR9We 27 | Qmdhx+D8PyKntvcL49Qzteh6 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/client.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDZzCCAk+gAwIBAgIBFjANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDDCZwb255 3 | dG93biBSU0EgMjA0OCBsZXZlbCAyIGludGVybWVkaWF0ZTAgFw03NTAxMDEwMDAw 4 | MDBaGA80MDk2MDEwMTAwMDAwMFowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArqDXNAioAyu2uybTeBFJ 6 | QFjKu3zeMReGZ8V9H6R4yWc5wl65p1oPJmk0rLbk1dn5gloc+aiKOtRFnS64DdrI 7 | mWnVweY392AXiIa+RggVbpoNRTWVf+O0GTuGU+ktnBjbN679lscn4vVxU0ekm8A6 8 | CkVpPKOzAx4CsYG1hDLJQKAnaWgg+ifnMWKZgVmz2wxNIAIqgik5O6zVYbwq3Wj2 9 | MqItcPi5MKhygu8BlnWF0TvDV/AmMQIR0waRbruJVpMqucBNvu/M6C2TulftC8MF 10 | uuo1mb11NCDVN0t0gJ2sHOnbDpH1Pj7B+JQIVXZGdXp1Q/P3vZ2sz4brhSbyBMvz 11 | xQIDAQABo4GeMIGbMB8GA1UdIwQYMBaAFIxn7nsnNGnsFe8RzLNFaBQLBet/MFMG 12 | A1UdEQRMMEqCDnRlc3RzZXJ2ZXIuY29tghVzZWNvbmQudGVzdHNlcnZlci5jb22C 13 | CWxvY2FsaG9zdIcExjNkAYcQIAENuAAAAAAAAAAAAAAAATAOBgNVHQ8BAf8EBAMC 14 | BsAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAJ4A14SX 15 | FetIpTvlWXj4PKZvQsLTtBcH4hakRIyJ81XHpNvEah2RFCquijJzoGZFvQug7YD/ 16 | FecOoucwzp9x4lbyD2cpVxcYi8wkHk54OG8aeHqJGWuQZKXsAkyWpclQQ2epxUhM 17 | 1el3jxbgFq+5d9zLnNyxkkCbfAnkTVpYw/wb8uQOsLC2SccymGgw6+1v6gGZNIKO 18 | WYczu6Lf2XVH0yW+tevFqDDw/i7Q2TRy1oPSRzXUi6Jh500e9UbAtZ976B972D63 19 | wLVUDewTYPHw0XrddKflvXugeRZTuXqIHclb3JVtu4gG6Gk2spso2LMnH2VImWtN 20 | 9HTtCrQrGaSm6kk= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/client.chain: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDUTCCAjmgAwIBAgIBCDANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 3 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 4 | MFowMTEvMC0GA1UEAwwmcG9ueXRvd24gUlNBIDIwNDggbGV2ZWwgMiBpbnRlcm1l 5 | ZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPjnWAwXrjPM3N 6 | q2QmVaaxu5/EdMk5DCCTomaKYE9dEGa1hd+ZXuiqvdgX1J5jEn9RaTxY5gUcNVyT 7 | Utx6si7WqPTb+h1IBUT0hNZpXT7AOo4wbNg/9vjmTJ1GfX8EuLmaK+hkFH3IUGJL 8 | Mh7I47pIEo5WiPcb/tBT4fHN87vQdknT+S1K8BX1eGFDr+mmMbj2Kg5sxybAZmO4 9 | YbHeT1W1MKsewp/gBI3jZwXAtM3CDWrWMl/HXmiM01VVi57T/ZaUHIth5TOMIIkF 10 | wF52MeCFH9fHLgx3LJBTkeO76dbjn9CO6OwfvMmOzSJbDim9kAPB2FeM6xILGhKy 11 | T+Cq1k8dAgMBAAGjgYMwgYAwHwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9 12 | ELYwDgYDVR0PAQH/BAQDAgH+MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD 13 | AjAdBgNVHQ4EFgQUjGfueyc0aewV7xHMs0VoFAsF638wDwYDVR0TAQH/BAUwAwEB 14 | /zANBgkqhkiG9w0BAQsFAAOCAQEAiqcCAXqzbGQ08wxaWiUZ0ahZ2giTkGXgcluN 15 | P7LXnMgve3M1gac4XBHAnokuME33+ppxVvIiX8c0OxR8LUPPmKQTGMtivDbSTFJG 16 | UALdyol1xalNuCuX81rvd/UY8tV6UQg/PzJb6QZFAxeMrodhS/dcCyNVMLFbutyM 17 | Vq3RW+ZjIxY+RVSYiEqWvStcjga//ateG8ndUioAGsfX8cs3IVkHdSqCJ4YCgml0 18 | /scRGaep2uP6y+yW9XQGxDLI5FHIs5EVn5nmo23HfmvLw7fYIgGdguCJGnWzy+kS 19 | O2+9pDFibt7UieLOUKlxyw8PbtvHJrRVvy1ylgEaNLKncssxLg== 20 | -----END CERTIFICATE----- 21 | -----BEGIN CERTIFICATE----- 22 | MIIDPzCCAiegAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 23 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 24 | MFowHzEdMBsGA1UEAwwUcG9ueXRvd24gUlNBIDIwNDggQ0EwggEiMA0GCSqGSIb3 25 | DQEBAQUAA4IBDwAwggEKAoIBAQCy4ZPzrQwfu3tz3ll0xHzlHtAQEHUyXzSo3VIn 26 | ULFPC9D84LWe9zpQBYGxOtl8OVcMdmEbzoIOHO6afNSnzbcQdZymJ4+FMpmusWdV 27 | CcWQ7Knu4Ok0mWlw7HXgtnhunjX0BRMy5M6WXWk6MH44G7n3DCQDINAJvGAwirIT 28 | kUfekbnwDCIEmIZNQQKyGdndn6mQGHt+oWdmgiWxI0dPjU/vsyFQDFomX2IF/fn4 29 | j/lKvhGPEccO/2zQThUrbVck1t7kRilCDZbShGav3+87h42mISriK5yqVOwPzPL4 30 | HWjIL+G2sMDY7RdFKQHBCY4Gis5rrLtOOhKtoDmos7oCCs1BAgMBAAGjgYMwgYAw 31 | HwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9ELYwDgYDVR0PAQH/BAQDAgH+ 32 | MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUZ8fKnr69 33 | bZrvOOQ1Jii60vG9ELYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC 34 | AQEAhTU7Iv/HBuSOaIAOTrgqbvwKUoZsGG0vb1DVOYINasnDDVx+celU9jvzSHRy 35 | +5kZUgflytZIy1TmE/RvQi8/vd2s6BL4DGF39VyFO49g746EcBUEmNGZFS0J/hVa 36 | iZj3yNO/tO5V3nzrGzTrrx9cRnFykLKE/qDnrIOfCiRk5KYui1M5JYBM61r3MDLA 37 | lrRCZh4u04jdNVHPF14km5VcgJvhpGkX2mpe3J9ZtOwBdc96e5zJ0PPKofAuVhuD 38 | J8t+QNnjXKSyf1Ud/5WAOKinqGK902mRAwEFISQko0OajUkUzjZabr6xnf4fYWId 39 | JPT9si0E6DxRUmBTQ0Dy4zU1AQ== 40 | -----END CERTIFICATE----- 41 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/client.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slawlor/ractor/38faea5de8347f6a02fdea45d7480625ab3b5f79/ractor_cluster_integration_tests/test-ca/rsa-2048/client.der -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/client.expired.crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIIBrDCBlQIBATANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDDCZwb255dG93biBS 3 | U0EgMjA0OCBsZXZlbCAyIGludGVybWVkaWF0ZRcNMjQwNzAzMTA0MjQ1WhcNMjQw 4 | NzAzMTA0MzQ1WqAwMC4wHwYDVR0jBBgwFoAUjGfueyc0aewV7xHMs0VoFAsF638w 5 | CwYDVR0UBAQCAgTSMA0GCSqGSIb3DQEBCwUAA4IBAQDJuuLoPB8ToYioEsKI/z+y 6 | TkZRyBzvptD4OBFEfSdYwWc6tBsRzP1zMHQbWIcwfwm35iu3zOHWzX+pQMlLtwwd 7 | iITCPU8oDcuP/B2BLjPpqt/1qoOQrEQl/ImqxZVtjIu+jz9/sSwm2UX9pX3dAVxK 8 | T29t9q8qbvwOUO87n7dm6rkhMDs0T9vbU7Av9LasTDKVmhFCg80xswIpN017jWiC 9 | WtAX4svLf4oqoxk/AhQ12oilT8E9BaIiInpS1dN0fk6Qiei6qHQt6NB37pDq3cp8 10 | CXiE94nXkXgaDnAruqDpRShCLX21xDNQY+zhGItbn8AgdIWRFPJjJlS0yPhI0Ald 11 | -----END X509 CRL----- 12 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/client.fullchain: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDZzCCAk+gAwIBAgIBFjANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDDCZwb255 3 | dG93biBSU0EgMjA0OCBsZXZlbCAyIGludGVybWVkaWF0ZTAgFw03NTAxMDEwMDAw 4 | MDBaGA80MDk2MDEwMTAwMDAwMFowGjEYMBYGA1UEAwwPcG9ueXRvd24gY2xpZW50 5 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArqDXNAioAyu2uybTeBFJ 6 | QFjKu3zeMReGZ8V9H6R4yWc5wl65p1oPJmk0rLbk1dn5gloc+aiKOtRFnS64DdrI 7 | mWnVweY392AXiIa+RggVbpoNRTWVf+O0GTuGU+ktnBjbN679lscn4vVxU0ekm8A6 8 | CkVpPKOzAx4CsYG1hDLJQKAnaWgg+ifnMWKZgVmz2wxNIAIqgik5O6zVYbwq3Wj2 9 | MqItcPi5MKhygu8BlnWF0TvDV/AmMQIR0waRbruJVpMqucBNvu/M6C2TulftC8MF 10 | uuo1mb11NCDVN0t0gJ2sHOnbDpH1Pj7B+JQIVXZGdXp1Q/P3vZ2sz4brhSbyBMvz 11 | xQIDAQABo4GeMIGbMB8GA1UdIwQYMBaAFIxn7nsnNGnsFe8RzLNFaBQLBet/MFMG 12 | A1UdEQRMMEqCDnRlc3RzZXJ2ZXIuY29tghVzZWNvbmQudGVzdHNlcnZlci5jb22C 13 | CWxvY2FsaG9zdIcExjNkAYcQIAENuAAAAAAAAAAAAAAAATAOBgNVHQ8BAf8EBAMC 14 | BsAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAJ4A14SX 15 | FetIpTvlWXj4PKZvQsLTtBcH4hakRIyJ81XHpNvEah2RFCquijJzoGZFvQug7YD/ 16 | FecOoucwzp9x4lbyD2cpVxcYi8wkHk54OG8aeHqJGWuQZKXsAkyWpclQQ2epxUhM 17 | 1el3jxbgFq+5d9zLnNyxkkCbfAnkTVpYw/wb8uQOsLC2SccymGgw6+1v6gGZNIKO 18 | WYczu6Lf2XVH0yW+tevFqDDw/i7Q2TRy1oPSRzXUi6Jh500e9UbAtZ976B972D63 19 | wLVUDewTYPHw0XrddKflvXugeRZTuXqIHclb3JVtu4gG6Gk2spso2LMnH2VImWtN 20 | 9HTtCrQrGaSm6kk= 21 | -----END CERTIFICATE----- 22 | -----BEGIN CERTIFICATE----- 23 | MIIDUTCCAjmgAwIBAgIBCDANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 24 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 25 | MFowMTEvMC0GA1UEAwwmcG9ueXRvd24gUlNBIDIwNDggbGV2ZWwgMiBpbnRlcm1l 26 | ZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPjnWAwXrjPM3N 27 | q2QmVaaxu5/EdMk5DCCTomaKYE9dEGa1hd+ZXuiqvdgX1J5jEn9RaTxY5gUcNVyT 28 | Utx6si7WqPTb+h1IBUT0hNZpXT7AOo4wbNg/9vjmTJ1GfX8EuLmaK+hkFH3IUGJL 29 | Mh7I47pIEo5WiPcb/tBT4fHN87vQdknT+S1K8BX1eGFDr+mmMbj2Kg5sxybAZmO4 30 | YbHeT1W1MKsewp/gBI3jZwXAtM3CDWrWMl/HXmiM01VVi57T/ZaUHIth5TOMIIkF 31 | wF52MeCFH9fHLgx3LJBTkeO76dbjn9CO6OwfvMmOzSJbDim9kAPB2FeM6xILGhKy 32 | T+Cq1k8dAgMBAAGjgYMwgYAwHwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9 33 | ELYwDgYDVR0PAQH/BAQDAgH+MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD 34 | AjAdBgNVHQ4EFgQUjGfueyc0aewV7xHMs0VoFAsF638wDwYDVR0TAQH/BAUwAwEB 35 | /zANBgkqhkiG9w0BAQsFAAOCAQEAiqcCAXqzbGQ08wxaWiUZ0ahZ2giTkGXgcluN 36 | P7LXnMgve3M1gac4XBHAnokuME33+ppxVvIiX8c0OxR8LUPPmKQTGMtivDbSTFJG 37 | UALdyol1xalNuCuX81rvd/UY8tV6UQg/PzJb6QZFAxeMrodhS/dcCyNVMLFbutyM 38 | Vq3RW+ZjIxY+RVSYiEqWvStcjga//ateG8ndUioAGsfX8cs3IVkHdSqCJ4YCgml0 39 | /scRGaep2uP6y+yW9XQGxDLI5FHIs5EVn5nmo23HfmvLw7fYIgGdguCJGnWzy+kS 40 | O2+9pDFibt7UieLOUKlxyw8PbtvHJrRVvy1ylgEaNLKncssxLg== 41 | -----END CERTIFICATE----- 42 | -----BEGIN CERTIFICATE----- 43 | MIIDPzCCAiegAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 44 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 45 | MFowHzEdMBsGA1UEAwwUcG9ueXRvd24gUlNBIDIwNDggQ0EwggEiMA0GCSqGSIb3 46 | DQEBAQUAA4IBDwAwggEKAoIBAQCy4ZPzrQwfu3tz3ll0xHzlHtAQEHUyXzSo3VIn 47 | ULFPC9D84LWe9zpQBYGxOtl8OVcMdmEbzoIOHO6afNSnzbcQdZymJ4+FMpmusWdV 48 | CcWQ7Knu4Ok0mWlw7HXgtnhunjX0BRMy5M6WXWk6MH44G7n3DCQDINAJvGAwirIT 49 | kUfekbnwDCIEmIZNQQKyGdndn6mQGHt+oWdmgiWxI0dPjU/vsyFQDFomX2IF/fn4 50 | j/lKvhGPEccO/2zQThUrbVck1t7kRilCDZbShGav3+87h42mISriK5yqVOwPzPL4 51 | HWjIL+G2sMDY7RdFKQHBCY4Gis5rrLtOOhKtoDmos7oCCs1BAgMBAAGjgYMwgYAw 52 | HwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9ELYwDgYDVR0PAQH/BAQDAgH+ 53 | MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUZ8fKnr69 54 | bZrvOOQ1Jii60vG9ELYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC 55 | AQEAhTU7Iv/HBuSOaIAOTrgqbvwKUoZsGG0vb1DVOYINasnDDVx+celU9jvzSHRy 56 | +5kZUgflytZIy1TmE/RvQi8/vd2s6BL4DGF39VyFO49g746EcBUEmNGZFS0J/hVa 57 | iZj3yNO/tO5V3nzrGzTrrx9cRnFykLKE/qDnrIOfCiRk5KYui1M5JYBM61r3MDLA 58 | lrRCZh4u04jdNVHPF14km5VcgJvhpGkX2mpe3J9ZtOwBdc96e5zJ0PPKofAuVhuD 59 | J8t+QNnjXKSyf1Ud/5WAOKinqGK902mRAwEFISQko0OajUkUzjZabr6xnf4fYWId 60 | JPT9si0E6DxRUmBTQ0Dy4zU1AQ== 61 | -----END CERTIFICATE----- 62 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCuoNc0CKgDK7a7 3 | JtN4EUlAWMq7fN4xF4ZnxX0fpHjJZznCXrmnWg8maTSstuTV2fmCWhz5qIo61EWd 4 | LrgN2siZadXB5jf3YBeIhr5GCBVumg1FNZV/47QZO4ZT6S2cGNs3rv2Wxyfi9XFT 5 | R6SbwDoKRWk8o7MDHgKxgbWEMslAoCdpaCD6J+cxYpmBWbPbDE0gAiqCKTk7rNVh 6 | vCrdaPYyoi1w+LkwqHKC7wGWdYXRO8NX8CYxAhHTBpFuu4lWkyq5wE2+78zoLZO6 7 | V+0LwwW66jWZvXU0INU3S3SAnawc6dsOkfU+PsH4lAhVdkZ1enVD8/e9nazPhuuF 8 | JvIEy/PFAgMBAAECggEAM0pYixbzyiKfxC5uNDjXjAfIW/McC80+E+KkkpOrSnSA 9 | OX+7BdVk18JUz1nUsp8YMtom4olHOGo6nCUpuCAHhU/Zkd79lzHpF5eD8WvUZ6zl 10 | sus/ItYNgs/B74lwdQWz9ZH3N779DyNKCszjIz/9jCDA/SeTn5nD7sELQG3uQIWN 11 | aGnCMjYmBDR6MtpVseLRupqH2QkJ5Bz+YeFuVljp2M1vN+B5fqhl+XZVzDwauWJn 12 | gaLMyzGFezx+Gu4s1X+7q5id3yKmsaPcQ+IK7vCTfoTaMWlibgqNc+QOMN0BRsDk 13 | Ao+fY18di3LlG/BXK8mp+wGnZdz+T5/HjockZwN/bQKBgQDag3BJG12zxhjcDw8m 14 | HmChzNpjAv3//mB3n0OlXNkrEfYs6J/1x6q1NBWmTr3DMq8RS2yqf/Wg54UIVE6v 15 | H+6KkyHTu3zW0sDguH4fiGDNFTJvZCIEzwEQ6NHxtsCWn8cy5vmhX6MWUEcNd2Ti 16 | M0ClzmN2AK0unolOPHtM14niFwKBgQDMlhOT4ll44IrO/0wupLYnfuFdxbk/sAig 17 | N+gqwAXnHQLEXKz08tc+HU4Ah+OAonHRomgv5gCpgxfCzby358UnVbVYaMHDLtvl 18 | j8cenPcFvGpSioQgl87tMj8gHxVg33tu9hYWXCXMlEd+QR2m93xFQZ815eV3JdTP 19 | OBBE9CAOgwKBgHGEIsr32n/QA44ejKIi2dR0VWdmYIminn7GUCAR0gfb9uVxm9fU 20 | +EvWWQDimxZSuDvl0PfqLbJ/U8717UJw9j0rHkGefsYjCmwzq+G5uYAQ6PiRLlMN 21 | LuKX8Kj/ZaFaM+OvtjMtOc4XujG2usQAGC+KvoTI6S700SPBq9RJjKnVAoGBAMdB 22 | H543pPaiku7aHQOMrlA3Nh3TVgIeW7hX2wnP7a/TRH+k11rWLSa0+eRwdPYywHHx 23 | IFIOS/TM1daH9FaVKCKQSkfp91U3DTo+1ysMq1Xt1QvBFsB7ujCwcGTDgVnRL3G5 24 | wXdTlgjzHdJsbwnQHjuBlyOyXdO2h9aufsH5lkMrAoGBAL1qljpSkIOKGkhhoEPT 25 | qPRPoLc5GI0rruem6eT4deTe4TTHVTBYGjZwUx+aVVJxcVhqeqK5Y5mL0S1dvTOw 26 | dzF86yg2uYCIlNQgs3ehQYMpZlTlT+6ImDb0zdg7GKLP1wRIl1chrEyyDj2BXZrA 27 | VA7HrjPZZyOBEii6X0i1Wz+h 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/client.revoked.crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIIB0jCBuwIBATANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDDCZwb255dG93biBS 3 | U0EgMjA0OCBsZXZlbCAyIGludGVybWVkaWF0ZRcNMjQwNzAzMTA0MzQ1WhgPMjEy 4 | NDA2MDkxMDQzNDVaMCIwIAIBFhcNMjQwNzAzMTA0MzQ1WjAMMAoGA1UdFQQDCgEB 5 | oDAwLjAfBgNVHSMEGDAWgBSMZ+57JzRp7BXvEcyzRWgUCwXrfzALBgNVHRQEBAIC 6 | BNIwDQYJKoZIhvcNAQELBQADggEBAJnyh7ONZvs/PWXbjWDDtD2KxIc18w3VuI+9 7 | nQJb7iDwP7/d+H3vVNrmshc3DHJ6BxGUQPC3FRcxMJtQi+14+SOu3ddTqRSg58bg 8 | C7/hWEVmer5YF9WjMFkONFX5khP9Y9vMN8n/ezMcTf/qNqZftyschNFmWMZI8NsS 9 | daZPama09aY60wUrNCpzZbeVtwcRH4cH3WKZ11lAbgfXWOC7bPSixBSY4bvlBzp5 10 | +cTKPfumSG7TnaM497GRL0stacIsy8BQxMYYBppjqtxkpvM1gOw1qJu7MUmnYMmB 11 | f3jF7QBSx06CsisA2riSottVJpXJEYNsSCUwxMm/2RXX1Q1Q8i0= 12 | -----END X509 CRL----- 13 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/client.spki.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArqDXNAioAyu2uybTeBFJ 3 | QFjKu3zeMReGZ8V9H6R4yWc5wl65p1oPJmk0rLbk1dn5gloc+aiKOtRFnS64DdrI 4 | mWnVweY392AXiIa+RggVbpoNRTWVf+O0GTuGU+ktnBjbN679lscn4vVxU0ekm8A6 5 | CkVpPKOzAx4CsYG1hDLJQKAnaWgg+ifnMWKZgVmz2wxNIAIqgik5O6zVYbwq3Wj2 6 | MqItcPi5MKhygu8BlnWF0TvDV/AmMQIR0waRbruJVpMqucBNvu/M6C2TulftC8MF 7 | uuo1mb11NCDVN0t0gJ2sHOnbDpH1Pj7B+JQIVXZGdXp1Q/P3vZ2sz4brhSbyBMvz 8 | xQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/end.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDUTCCAjmgAwIBAgIBDzANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDDCZwb255 3 | dG93biBSU0EgMjA0OCBsZXZlbCAyIGludGVybWVkaWF0ZTAgFw03NTAxMDEwMDAw 4 | MDBaGA80MDk2MDEwMTAwMDAwMFowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20w 5 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHvQSjQATPtE602Qzx3wez 6 | 0oeELGoWqnj3lZw2V/gNPqmSwKB1bIqTSgWJerpvZfLZLWIadq7rrq4d16b6/nxN 7 | zBPzsdohLfAJeTbT4fHM6+wZ74qxfathFtjN8vzntla0rpF1P3YB8umkYUe4zXeT 8 | X+8kNPntt7GAPa8tNj95XiTT1YKhaYn7kiI4gT1jb9owoMA3viXqTPTVmmimN7k/ 9 | cyI6Hh0llTH5qa6+8l6TYhjJbOrpaP0X04MwXbQBOBWtLojDYBWoQ92g/mN1lIeq 10 | CYT8vddAJtalCfW4gZr2Zgh6CQxFiacbYtUPcpbxbqMlHVp+xEa67ZWbsLYMDHcZ 11 | AgMBAAGjgYkwgYYwHwYDVR0jBBgwFoAUjGfueyc0aewV7xHMs0VoFAsF638wUwYD 12 | VR0RBEwwSoIOdGVzdHNlcnZlci5jb22CFXNlY29uZC50ZXN0c2VydmVyLmNvbYIJ 13 | bG9jYWxob3N0hwTGM2QBhxAgAQ24AAAAAAAAAAAAAAABMA4GA1UdDwEB/wQEAwIG 14 | wDANBgkqhkiG9w0BAQsFAAOCAQEAoKQ9S8pjx4ETM1xaL3OfwtteGDGBumdMvqWQ 15 | +vvEy1ckMiYzpED8Rp3FM5J8Z9o2ZHoUABaLGb7pQClk5ig9MeuCDG4MYUUWbycn 16 | y+dxmqAS0p/4MtzXKd2GSEFICZR6FMkz7zj68We1orRi5sXl8PLJcZ8kfEg8wLOL 17 | j1iBwpw1GOB1In7eQ2iyNydXohEGz9qCkUgT/c/tY4cog+FzTEp3yTDGZLmNKUSz 18 | ttcg6HqitJ6Hn/dvbLNJC5wlY7MXWxjit5RBWVNTgf4QTAVpvdOymlM+jSfx/mq+ 19 | jgPosvpIjD8xXuVD7AA+lpMjsbD50prLecxwADPmg+bZmcQv3g== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/end.chain: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDUTCCAjmgAwIBAgIBCDANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 3 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 4 | MFowMTEvMC0GA1UEAwwmcG9ueXRvd24gUlNBIDIwNDggbGV2ZWwgMiBpbnRlcm1l 5 | ZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPjnWAwXrjPM3N 6 | q2QmVaaxu5/EdMk5DCCTomaKYE9dEGa1hd+ZXuiqvdgX1J5jEn9RaTxY5gUcNVyT 7 | Utx6si7WqPTb+h1IBUT0hNZpXT7AOo4wbNg/9vjmTJ1GfX8EuLmaK+hkFH3IUGJL 8 | Mh7I47pIEo5WiPcb/tBT4fHN87vQdknT+S1K8BX1eGFDr+mmMbj2Kg5sxybAZmO4 9 | YbHeT1W1MKsewp/gBI3jZwXAtM3CDWrWMl/HXmiM01VVi57T/ZaUHIth5TOMIIkF 10 | wF52MeCFH9fHLgx3LJBTkeO76dbjn9CO6OwfvMmOzSJbDim9kAPB2FeM6xILGhKy 11 | T+Cq1k8dAgMBAAGjgYMwgYAwHwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9 12 | ELYwDgYDVR0PAQH/BAQDAgH+MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD 13 | AjAdBgNVHQ4EFgQUjGfueyc0aewV7xHMs0VoFAsF638wDwYDVR0TAQH/BAUwAwEB 14 | /zANBgkqhkiG9w0BAQsFAAOCAQEAiqcCAXqzbGQ08wxaWiUZ0ahZ2giTkGXgcluN 15 | P7LXnMgve3M1gac4XBHAnokuME33+ppxVvIiX8c0OxR8LUPPmKQTGMtivDbSTFJG 16 | UALdyol1xalNuCuX81rvd/UY8tV6UQg/PzJb6QZFAxeMrodhS/dcCyNVMLFbutyM 17 | Vq3RW+ZjIxY+RVSYiEqWvStcjga//ateG8ndUioAGsfX8cs3IVkHdSqCJ4YCgml0 18 | /scRGaep2uP6y+yW9XQGxDLI5FHIs5EVn5nmo23HfmvLw7fYIgGdguCJGnWzy+kS 19 | O2+9pDFibt7UieLOUKlxyw8PbtvHJrRVvy1ylgEaNLKncssxLg== 20 | -----END CERTIFICATE----- 21 | -----BEGIN CERTIFICATE----- 22 | MIIDPzCCAiegAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 23 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 24 | MFowHzEdMBsGA1UEAwwUcG9ueXRvd24gUlNBIDIwNDggQ0EwggEiMA0GCSqGSIb3 25 | DQEBAQUAA4IBDwAwggEKAoIBAQCy4ZPzrQwfu3tz3ll0xHzlHtAQEHUyXzSo3VIn 26 | ULFPC9D84LWe9zpQBYGxOtl8OVcMdmEbzoIOHO6afNSnzbcQdZymJ4+FMpmusWdV 27 | CcWQ7Knu4Ok0mWlw7HXgtnhunjX0BRMy5M6WXWk6MH44G7n3DCQDINAJvGAwirIT 28 | kUfekbnwDCIEmIZNQQKyGdndn6mQGHt+oWdmgiWxI0dPjU/vsyFQDFomX2IF/fn4 29 | j/lKvhGPEccO/2zQThUrbVck1t7kRilCDZbShGav3+87h42mISriK5yqVOwPzPL4 30 | HWjIL+G2sMDY7RdFKQHBCY4Gis5rrLtOOhKtoDmos7oCCs1BAgMBAAGjgYMwgYAw 31 | HwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9ELYwDgYDVR0PAQH/BAQDAgH+ 32 | MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUZ8fKnr69 33 | bZrvOOQ1Jii60vG9ELYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC 34 | AQEAhTU7Iv/HBuSOaIAOTrgqbvwKUoZsGG0vb1DVOYINasnDDVx+celU9jvzSHRy 35 | +5kZUgflytZIy1TmE/RvQi8/vd2s6BL4DGF39VyFO49g746EcBUEmNGZFS0J/hVa 36 | iZj3yNO/tO5V3nzrGzTrrx9cRnFykLKE/qDnrIOfCiRk5KYui1M5JYBM61r3MDLA 37 | lrRCZh4u04jdNVHPF14km5VcgJvhpGkX2mpe3J9ZtOwBdc96e5zJ0PPKofAuVhuD 38 | J8t+QNnjXKSyf1Ud/5WAOKinqGK902mRAwEFISQko0OajUkUzjZabr6xnf4fYWId 39 | JPT9si0E6DxRUmBTQ0Dy4zU1AQ== 40 | -----END CERTIFICATE----- 41 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/end.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slawlor/ractor/38faea5de8347f6a02fdea45d7480625ab3b5f79/ractor_cluster_integration_tests/test-ca/rsa-2048/end.der -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/end.expired.crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIIBrDCBlQIBATANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDDCZwb255dG93biBS 3 | U0EgMjA0OCBsZXZlbCAyIGludGVybWVkaWF0ZRcNMjQwNzAzMTA0MjQzWhcNMjQw 4 | NzAzMTA0MzQzWqAwMC4wHwYDVR0jBBgwFoAUjGfueyc0aewV7xHMs0VoFAsF638w 5 | CwYDVR0UBAQCAgTSMA0GCSqGSIb3DQEBCwUAA4IBAQC6ZyHMTezfFKt35wvO//+/ 6 | M9g4AZc8e+xW3gWvu4UFlfAjNYIhyjeO5BI/YPzaAL/hC8mtAaaoXpq2TvXdqyGV 7 | KgdIS/zuPC23+KgEJUrCUbf2WvG0Kgoc02SbVC/KBPmH7GMr6nIGYiZCCkwoGLTD 8 | ZAOc2QMKUKm7LsPwgEvlutqEDQ4WnoK4jBchFRSBinT5bOfJF6ljIXVTsXU+xoYE 9 | z7Jv0CCidZM8ln1UEvnU0LuuEg2tbhQmq/BuwL2oC5vZsZ2AcriK6d+tXlq+BVRC 10 | 4jyDSWSGuzidppfnVt6lO+oPVgFk8S13kGHlho3G4liJ1wrlQuNBa3+Ol3KZSlXz 11 | -----END X509 CRL----- 12 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/end.fullchain: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDUTCCAjmgAwIBAgIBDzANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDDCZwb255 3 | dG93biBSU0EgMjA0OCBsZXZlbCAyIGludGVybWVkaWF0ZTAgFw03NTAxMDEwMDAw 4 | MDBaGA80MDk2MDEwMTAwMDAwMFowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20w 5 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHvQSjQATPtE602Qzx3wez 6 | 0oeELGoWqnj3lZw2V/gNPqmSwKB1bIqTSgWJerpvZfLZLWIadq7rrq4d16b6/nxN 7 | zBPzsdohLfAJeTbT4fHM6+wZ74qxfathFtjN8vzntla0rpF1P3YB8umkYUe4zXeT 8 | X+8kNPntt7GAPa8tNj95XiTT1YKhaYn7kiI4gT1jb9owoMA3viXqTPTVmmimN7k/ 9 | cyI6Hh0llTH5qa6+8l6TYhjJbOrpaP0X04MwXbQBOBWtLojDYBWoQ92g/mN1lIeq 10 | CYT8vddAJtalCfW4gZr2Zgh6CQxFiacbYtUPcpbxbqMlHVp+xEa67ZWbsLYMDHcZ 11 | AgMBAAGjgYkwgYYwHwYDVR0jBBgwFoAUjGfueyc0aewV7xHMs0VoFAsF638wUwYD 12 | VR0RBEwwSoIOdGVzdHNlcnZlci5jb22CFXNlY29uZC50ZXN0c2VydmVyLmNvbYIJ 13 | bG9jYWxob3N0hwTGM2QBhxAgAQ24AAAAAAAAAAAAAAABMA4GA1UdDwEB/wQEAwIG 14 | wDANBgkqhkiG9w0BAQsFAAOCAQEAoKQ9S8pjx4ETM1xaL3OfwtteGDGBumdMvqWQ 15 | +vvEy1ckMiYzpED8Rp3FM5J8Z9o2ZHoUABaLGb7pQClk5ig9MeuCDG4MYUUWbycn 16 | y+dxmqAS0p/4MtzXKd2GSEFICZR6FMkz7zj68We1orRi5sXl8PLJcZ8kfEg8wLOL 17 | j1iBwpw1GOB1In7eQ2iyNydXohEGz9qCkUgT/c/tY4cog+FzTEp3yTDGZLmNKUSz 18 | ttcg6HqitJ6Hn/dvbLNJC5wlY7MXWxjit5RBWVNTgf4QTAVpvdOymlM+jSfx/mq+ 19 | jgPosvpIjD8xXuVD7AA+lpMjsbD50prLecxwADPmg+bZmcQv3g== 20 | -----END CERTIFICATE----- 21 | -----BEGIN CERTIFICATE----- 22 | MIIDUTCCAjmgAwIBAgIBCDANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 23 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 24 | MFowMTEvMC0GA1UEAwwmcG9ueXRvd24gUlNBIDIwNDggbGV2ZWwgMiBpbnRlcm1l 25 | ZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPjnWAwXrjPM3N 26 | q2QmVaaxu5/EdMk5DCCTomaKYE9dEGa1hd+ZXuiqvdgX1J5jEn9RaTxY5gUcNVyT 27 | Utx6si7WqPTb+h1IBUT0hNZpXT7AOo4wbNg/9vjmTJ1GfX8EuLmaK+hkFH3IUGJL 28 | Mh7I47pIEo5WiPcb/tBT4fHN87vQdknT+S1K8BX1eGFDr+mmMbj2Kg5sxybAZmO4 29 | YbHeT1W1MKsewp/gBI3jZwXAtM3CDWrWMl/HXmiM01VVi57T/ZaUHIth5TOMIIkF 30 | wF52MeCFH9fHLgx3LJBTkeO76dbjn9CO6OwfvMmOzSJbDim9kAPB2FeM6xILGhKy 31 | T+Cq1k8dAgMBAAGjgYMwgYAwHwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9 32 | ELYwDgYDVR0PAQH/BAQDAgH+MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD 33 | AjAdBgNVHQ4EFgQUjGfueyc0aewV7xHMs0VoFAsF638wDwYDVR0TAQH/BAUwAwEB 34 | /zANBgkqhkiG9w0BAQsFAAOCAQEAiqcCAXqzbGQ08wxaWiUZ0ahZ2giTkGXgcluN 35 | P7LXnMgve3M1gac4XBHAnokuME33+ppxVvIiX8c0OxR8LUPPmKQTGMtivDbSTFJG 36 | UALdyol1xalNuCuX81rvd/UY8tV6UQg/PzJb6QZFAxeMrodhS/dcCyNVMLFbutyM 37 | Vq3RW+ZjIxY+RVSYiEqWvStcjga//ateG8ndUioAGsfX8cs3IVkHdSqCJ4YCgml0 38 | /scRGaep2uP6y+yW9XQGxDLI5FHIs5EVn5nmo23HfmvLw7fYIgGdguCJGnWzy+kS 39 | O2+9pDFibt7UieLOUKlxyw8PbtvHJrRVvy1ylgEaNLKncssxLg== 40 | -----END CERTIFICATE----- 41 | -----BEGIN CERTIFICATE----- 42 | MIIDPzCCAiegAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 43 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 44 | MFowHzEdMBsGA1UEAwwUcG9ueXRvd24gUlNBIDIwNDggQ0EwggEiMA0GCSqGSIb3 45 | DQEBAQUAA4IBDwAwggEKAoIBAQCy4ZPzrQwfu3tz3ll0xHzlHtAQEHUyXzSo3VIn 46 | ULFPC9D84LWe9zpQBYGxOtl8OVcMdmEbzoIOHO6afNSnzbcQdZymJ4+FMpmusWdV 47 | CcWQ7Knu4Ok0mWlw7HXgtnhunjX0BRMy5M6WXWk6MH44G7n3DCQDINAJvGAwirIT 48 | kUfekbnwDCIEmIZNQQKyGdndn6mQGHt+oWdmgiWxI0dPjU/vsyFQDFomX2IF/fn4 49 | j/lKvhGPEccO/2zQThUrbVck1t7kRilCDZbShGav3+87h42mISriK5yqVOwPzPL4 50 | HWjIL+G2sMDY7RdFKQHBCY4Gis5rrLtOOhKtoDmos7oCCs1BAgMBAAGjgYMwgYAw 51 | HwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9ELYwDgYDVR0PAQH/BAQDAgH+ 52 | MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUZ8fKnr69 53 | bZrvOOQ1Jii60vG9ELYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC 54 | AQEAhTU7Iv/HBuSOaIAOTrgqbvwKUoZsGG0vb1DVOYINasnDDVx+celU9jvzSHRy 55 | +5kZUgflytZIy1TmE/RvQi8/vd2s6BL4DGF39VyFO49g746EcBUEmNGZFS0J/hVa 56 | iZj3yNO/tO5V3nzrGzTrrx9cRnFykLKE/qDnrIOfCiRk5KYui1M5JYBM61r3MDLA 57 | lrRCZh4u04jdNVHPF14km5VcgJvhpGkX2mpe3J9ZtOwBdc96e5zJ0PPKofAuVhuD 58 | J8t+QNnjXKSyf1Ud/5WAOKinqGK902mRAwEFISQko0OajUkUzjZabr6xnf4fYWId 59 | JPT9si0E6DxRUmBTQ0Dy4zU1AQ== 60 | -----END CERTIFICATE----- 61 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/end.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHvQSjQATPtE60 3 | 2Qzx3wez0oeELGoWqnj3lZw2V/gNPqmSwKB1bIqTSgWJerpvZfLZLWIadq7rrq4d 4 | 16b6/nxNzBPzsdohLfAJeTbT4fHM6+wZ74qxfathFtjN8vzntla0rpF1P3YB8umk 5 | YUe4zXeTX+8kNPntt7GAPa8tNj95XiTT1YKhaYn7kiI4gT1jb9owoMA3viXqTPTV 6 | mmimN7k/cyI6Hh0llTH5qa6+8l6TYhjJbOrpaP0X04MwXbQBOBWtLojDYBWoQ92g 7 | /mN1lIeqCYT8vddAJtalCfW4gZr2Zgh6CQxFiacbYtUPcpbxbqMlHVp+xEa67ZWb 8 | sLYMDHcZAgMBAAECggEAA+3v1mR1R1Q8ykRM/KTT61NQAHkH/dLF5xIhVc3NhCq+ 9 | ngIB2aymLArnyLQG7VCAm1EeVQw5vQOEpzoku/ejNgLvqFOXULxmJl9W9zVQ3cgN 10 | LvQ3NlH4yiBTIhQZVR6myPk/b2KYFm2PmqR3LvIE+En0kEAJjpRPiXOii5aE0W/C 11 | MTR2dWxKoIGMWdJxkt4XtHKoRPHk18+lFfA7GgYuFe7QJD2Yc0a+EBe7ybJMrgZ4 12 | YlwkldAOp//tcJ3xsmmA8fAXCncvrZkOZgjV46aGr6Ul5N610Qy/ADi4Ewpe1gAp 13 | Rsos/0lMv9zd6rjo1pFpzdDsemj3180MDMaULEYeLQKBgQD2JYRac+XihJLZemTZ 14 | b4zw0WfgV90qbnFF9X4IIhGqtQov5xwVlbgZXBNyTnhXPDsVG6IejsBByRouFNM0 15 | PaNgnNGaKvhKx/pxQQbHykKydfnlwb1IOEV9pK4Po2LMH6ybO5AqeFQb+XCSK0iF 16 | EwG1yQYCqXdFlAAIg9IKjaEoDQKBgQDPu+o6YoudM8BD7mrfwc5bj73PIDejpHzG 17 | 7D+JHrlf/LGdUJsSu/GL2u59lz/htzaCO1A+oG84/lYIePURLhVKOXwdeKFB0NMX 18 | Arnd24T+WnFka8x+jdrXJA40VBLelVsNRhT79qMwb+2pOVMLDPnF9h1igAD9Zzzq 19 | RJopW7acPQKBgEiKUfEeStdw3p49Fc+GsnCeJhDVlBZXKgbGDHh1IeO/KttnLr2l 20 | u/WtTwXW3lxHIdpcpHCA/WPl0lNWS9APgkCMtrDKjRXszX/m/AqRLwrM/fKaICBS 21 | hRg8Ghf9vLYPd5zf9uKtR4xe/K/3FY4yRcS/E4hhTQcyULgxsQrvJllZAoGBAKrS 22 | mDYSu0Q2PjCeafw+XvckKK2ejKJgrMrXkxeq0D2CinwWwK6TJnofOmbEbZ+rDYPi 23 | SoZ1X4C6hPzKmpvaDV/QDaQeglyg4DTX5QfvcFnI/d3aV3rqI78uww9841dt8kXk 24 | D3Vyx5cZM5IotYUs0mhbjqZlJGs87fanGy5PYMbxAoGBAJXYPGf12yb51wKZ+peG 25 | MfPsWBgkZRyp3dYdV5/NqceLKG4v/UyHfgJ9c78SZy9cA+uE54TnR+eC1lvEz3HK 26 | Rh5nfmhv0ltFM7+M1Wl/LMWNMa+H+M6NWIAwwhUHw/5Vkipns9AK4WaLHLryvb8W 27 | KuaZzzAm3i/zKV83Nznrv8r3 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/end.revoked.crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIIB0jCBuwIBATANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDDCZwb255dG93biBS 3 | U0EgMjA0OCBsZXZlbCAyIGludGVybWVkaWF0ZRcNMjQwNzAzMTA0MzQzWhgPMjEy 4 | NDA2MDkxMDQzNDNaMCIwIAIBDxcNMjQwNzAzMTA0MzQzWjAMMAoGA1UdFQQDCgEB 5 | oDAwLjAfBgNVHSMEGDAWgBSMZ+57JzRp7BXvEcyzRWgUCwXrfzALBgNVHRQEBAIC 6 | BNIwDQYJKoZIhvcNAQELBQADggEBAD69pE4WdSKSLvbhHWzqfyLe3FncuV1LozjG 7 | 6Q615/eXdlGzFwaG50fzriBe210kx2BVq2Z37JgOjIwLdpu5vs6/3YsGsMp2OWqD 8 | 6fl9LmV38q8reUtNhdQeFKkF0is9UioLHWhtcakPVrvHoRkJxDK+xK5I+PyBq5Fn 9 | 18io6S6lPz7rX6fJ+N080cbNk5LFYFYwYlFELMjmwbKuGdbssL+VVoNU8VEB8CpF 10 | BrSRlctq5NJFsefCDMU0ddBlXmfkrSOgukVrosEOU/h4eyhbVWdeFzuOu7+3Z0v1 11 | 1BMamq2T2uCP65b90PrZPYxHkG3YXWm/oej67oHrfJvJ7/6C/B8= 12 | -----END X509 CRL----- 13 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/end.spki.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx70Eo0AEz7ROtNkM8d8H 3 | s9KHhCxqFqp495WcNlf4DT6pksCgdWyKk0oFiXq6b2Xy2S1iGnau666uHdem+v58 4 | TcwT87HaIS3wCXk20+HxzOvsGe+KsX2rYRbYzfL857ZWtK6RdT92AfLppGFHuM13 5 | k1/vJDT57bexgD2vLTY/eV4k09WCoWmJ+5IiOIE9Y2/aMKDAN74l6kz01Zpopje5 6 | P3MiOh4dJZUx+amuvvJek2IYyWzq6Wj9F9ODMF20ATgVrS6Iw2AVqEPdoP5jdZSH 7 | qgmE/L3XQCbWpQn1uIGa9mYIegkMRYmnG2LVD3KW8W6jJR1afsRGuu2Vm7C2DAx3 8 | GQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/inter.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDUTCCAjmgAwIBAgIBCDANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255 3 | dG93biBSU0EgMjA0OCBDQTAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAw 4 | MFowMTEvMC0GA1UEAwwmcG9ueXRvd24gUlNBIDIwNDggbGV2ZWwgMiBpbnRlcm1l 5 | ZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPjnWAwXrjPM3N 6 | q2QmVaaxu5/EdMk5DCCTomaKYE9dEGa1hd+ZXuiqvdgX1J5jEn9RaTxY5gUcNVyT 7 | Utx6si7WqPTb+h1IBUT0hNZpXT7AOo4wbNg/9vjmTJ1GfX8EuLmaK+hkFH3IUGJL 8 | Mh7I47pIEo5WiPcb/tBT4fHN87vQdknT+S1K8BX1eGFDr+mmMbj2Kg5sxybAZmO4 9 | YbHeT1W1MKsewp/gBI3jZwXAtM3CDWrWMl/HXmiM01VVi57T/ZaUHIth5TOMIIkF 10 | wF52MeCFH9fHLgx3LJBTkeO76dbjn9CO6OwfvMmOzSJbDim9kAPB2FeM6xILGhKy 11 | T+Cq1k8dAgMBAAGjgYMwgYAwHwYDVR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9 12 | ELYwDgYDVR0PAQH/BAQDAgH+MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD 13 | AjAdBgNVHQ4EFgQUjGfueyc0aewV7xHMs0VoFAsF638wDwYDVR0TAQH/BAUwAwEB 14 | /zANBgkqhkiG9w0BAQsFAAOCAQEAiqcCAXqzbGQ08wxaWiUZ0ahZ2giTkGXgcluN 15 | P7LXnMgve3M1gac4XBHAnokuME33+ppxVvIiX8c0OxR8LUPPmKQTGMtivDbSTFJG 16 | UALdyol1xalNuCuX81rvd/UY8tV6UQg/PzJb6QZFAxeMrodhS/dcCyNVMLFbutyM 17 | Vq3RW+ZjIxY+RVSYiEqWvStcjga//ateG8ndUioAGsfX8cs3IVkHdSqCJ4YCgml0 18 | /scRGaep2uP6y+yW9XQGxDLI5FHIs5EVn5nmo23HfmvLw7fYIgGdguCJGnWzy+kS 19 | O2+9pDFibt7UieLOUKlxyw8PbtvHJrRVvy1ylgEaNLKncssxLg== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/inter.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slawlor/ractor/38faea5de8347f6a02fdea45d7480625ab3b5f79/ractor_cluster_integration_tests/test-ca/rsa-2048/inter.der -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/inter.expired.crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIIBmjCBgwIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255dG93biBS 3 | U0EgMjA0OCBDQRcNMjQwNzAzMTA0MjM5WhcNMjQwNzAzMTA0MzM5WqAwMC4wHwYD 4 | VR0jBBgwFoAUZ8fKnr69bZrvOOQ1Jii60vG9ELYwCwYDVR0UBAQCAgTSMA0GCSqG 5 | SIb3DQEBCwUAA4IBAQA8/dEz/rlf9Ko7KsTBw59sUybzIl7A73N4mbIrryPqJTWn 6 | lsv/dF2tG+dac1ddeXmnuZaZ/5At47HRl9yts7YvwgHlLopqBUfI/bqacY2bcQ65 7 | 5dn5DjyWz4dVSVxlim1OY8LoOzYwjeEom+IrA+5kMLsLTGL2fw0G50cwv8556lVC 8 | glO428fAAidM2OtjnF2XCyTdmRz5SqRwH+XOyTiWqrsTQJurzVzuqkyYGxhEBD8J 9 | xDZkwH6l64BKIMfuorZ43fwRIV8lDmiDCZ+pEScjJ+t4pkJENSxCeOfrPjLWpg9B 10 | z/kxEtbgL00l7kfzju13Nt1wNTvi7dcx4B8PILLr 11 | -----END X509 CRL----- 12 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/inter.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPjnWAwXrjPM3N 3 | q2QmVaaxu5/EdMk5DCCTomaKYE9dEGa1hd+ZXuiqvdgX1J5jEn9RaTxY5gUcNVyT 4 | Utx6si7WqPTb+h1IBUT0hNZpXT7AOo4wbNg/9vjmTJ1GfX8EuLmaK+hkFH3IUGJL 5 | Mh7I47pIEo5WiPcb/tBT4fHN87vQdknT+S1K8BX1eGFDr+mmMbj2Kg5sxybAZmO4 6 | YbHeT1W1MKsewp/gBI3jZwXAtM3CDWrWMl/HXmiM01VVi57T/ZaUHIth5TOMIIkF 7 | wF52MeCFH9fHLgx3LJBTkeO76dbjn9CO6OwfvMmOzSJbDim9kAPB2FeM6xILGhKy 8 | T+Cq1k8dAgMBAAECggEAEs7fPX1q6Bwaryw8jU++JQVyRyBpGpsLea1h+uCa2H1T 9 | vVfGCRUlYtGEuzYYazRiJquxXQHtDR8VxkDgO1sZrPiPBb8Ot7fIhZri/hXFybAU 10 | sfUvDG7SI7ZmGxzkrXYa1YpsiuMjecs0+Qjv6rbeKslJrjRHpjkLQkqreYUAbDWd 11 | +wQBHomZFajgg3WKpeLMbBXKxm6XnuQYJsJfjoVPspqXTNI8KcCaVOe5YLZsn1y2 12 | 5Pk96/7S0UgxSiDDUYaK3Hutp2fEFZp9Wgg9JUhUPJo1oGrNcFxfLIUdHtKPYa9R 13 | kKWnMrSpqKzuVuviBxLJBQsftUg5vJhoY8F0wwpaaQKBgQD4WcpK4pV4pjg+kOKi 14 | DCoR02L2OZPiAuTNxjetZhpy9wcHfnU2PWa2tYD6pMUMw2BnmFVY0aptG8ze2Njp 15 | 6LPKv3i2Yf3qcSfVP4dwNGLFlt1gxJChl5McD6NCRnLrjZr1ImMmc4o9gJK9uaCT 16 | YyhtWVFtaINJ90odeh0u+dvSOQKBgQDV8wMMwhfALr0rfQ5jRL7dW7vnUW8/ziWa 17 | 3c5Q2/YR33X+/qEP2C/OmcgScKOUpbBlcQDtxnVS6UgiAXQmgO2ummlwJdoCVy13 18 | f9jGBBpTWX1qClhHz1LznjewxncM8t1qG859NKAb0Ar0GJkHwg1RbGPAEDZ3dvvf 19 | nbMN6xTUBQKBgG/P7TXd4bHaFESG8qJGm8I+y7VTovMEnoLOlU6qQ4ozEaUcGCV9 20 | a3sjKJiwCk9+vy6k9PfyHarV81z5gF9/d0L3BaAHakxc+bQW0CGCVF3lWHIyzXMT 21 | TRY7LCOwmBjZPkTTYJR1PG4ILwjgRB1XbWId7n01qrZocaPn0K/PqlpxAoGBAME3 22 | T//LoYtsKmcF72JzCTP7znyfLKsgzxFHUzXvEBEgD/4Tkv0uLi6U7vzIQNLTt7RF 23 | E6Oo4PfZuc0MJwtG0GApDyO662HFvUf4a5qYxfWUEhEb+pIPiEh/EKhS89z70D4t 24 | kpO8XQQBJINBW6KH+2MXUhZ4Ddhh2iIe1z+wmATJAoGBAIvZVmxcAdi4pE/VSVv0 25 | DHIYJiYyQITyk9ZdJzj1vucfcHio9L4fbqraWWKtBnSPJvyqIxBe3iZ4DICo8e4E 26 | 5FtqJkkWYg9GZT5Ty4crER+isGJNsQYhrp8vWsBfIRR1QXuSj6QpLnXlylZghFEm 27 | ZEqr8T8NKSyTLJO5zGYMtqcO 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /ractor_cluster_integration_tests/test-ca/rsa-2048/inter.revoked.crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIIBwDCBqQIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRwb255dG93biBS 3 | U0EgMjA0OCBDQRcNMjQwNzAzMTA0MzM5WhgPMjEyNDA2MDkxMDQzMzlaMCIwIAIB 4 | CBcNMjQwNzAzMTA0MzM5WjAMMAoGA1UdFQQDCgEBoDAwLjAfBgNVHSMEGDAWgBRn 5 | x8qevr1tmu845DUmKLrS8b0QtjALBgNVHRQEBAICBNIwDQYJKoZIhvcNAQELBQAD 6 | ggEBALJI891iUYO8tzGHSIJdPqs2dNSnKaPdFsuNnOdMs/nt9UYts8tZzUsXlBkd 7 | 3KeBLZTI50BGbFfRCcvhhekpqECwKOE58kWaZk1VQ0s2D+ga6GZy+R1b39EvOXkS 8 | f0HqY5qhrjeksGhfKnHZ7HemR+vRPflm8vhJ3y2wfXIyJZVZIKxp78q9Ou27jZZN 9 | iO0kAlC72333iNKA77WdjC6g6hp3NXjXdCj6mp+WyOWLe5iekulXsV3qMgPTXcbT 10 | CWZnvRnpHE0hKifQkKavbaVpR2BYSfYvokR6D/o5m1fx9Dt3m65v50Vc5Jx2c/1O 11 | U93u3eVhy7fKtdITZ82GdNXLgug= 12 | -----END X509 CRL----- 13 | -------------------------------------------------------------------------------- /ractor_example_entry_proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ractor_example_entry_proc" 3 | version = "0.0.0" 4 | authors = ["Sean Lawlor "] 5 | description = "A wrapper for tokio::main which uses different arguments on wasm" 6 | license = "MIT" 7 | edition = "2021" 8 | categories = [] 9 | keywords = ["actor", "ractor"] 10 | repository = "https://github.com/slawlor/ractor" 11 | readme = "../README.md" 12 | rust-version = "1.64" 13 | 14 | [lib] 15 | name = "ractor_example_entry_proc" 16 | path = "src/lib.rs" 17 | proc-macro = true 18 | 19 | [dependencies] 20 | quote = "1" 21 | syn = { version = "2", features = ["full"] } 22 | -------------------------------------------------------------------------------- /ractor_example_entry_proc/src/lib.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 | use proc_macro::TokenStream; 7 | use quote::quote; 8 | use syn::parse_macro_input; 9 | use syn::ItemFn; 10 | 11 | /// Expands to `#[tokio::main(flavor = "current_thread")]` on `wasm32-unknown-unknown`, `#[tokio::main]` on other platforms 12 | #[proc_macro_attribute] 13 | pub fn ractor_example_entry(_: TokenStream, input: TokenStream) -> TokenStream { 14 | let input_fn = parse_macro_input!(input as ItemFn); 15 | 16 | (quote! { 17 | #[cfg_attr( 18 | all(target_arch = "wasm32", target_os = "unknown"), 19 | tokio::main(flavor = "current_thread") 20 | )] 21 | #[cfg_attr(not(all(target_arch = "wasm32", target_os = "unknown")), tokio::main)] 22 | 23 | #input_fn 24 | }) 25 | .into() 26 | } 27 | -------------------------------------------------------------------------------- /ractor_playground/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ractor_playground" 3 | version = "0.0.0" 4 | authors = ["Sean Lawlor "] 5 | description = "A actor framework for Rust - REPL" 6 | license = "MIT" 7 | edition = "2018" 8 | keywords = ["actor", "ractor"] 9 | repository = "https://github.com/slawlor/ractor" 10 | readme = "../README.md" 11 | publish = false 12 | 13 | [dependencies] 14 | clap = { version = "4", features = ["derive"] } 15 | ractor = { path="../ractor", features = ["cluster"] } 16 | ractor_cluster = { path = "../ractor_cluster" } 17 | # ractor_cluster_derive = { path = "../ractor_cluster_derive" } 18 | # rustyrepl = "0.1" 19 | tokio = { version = "1", features = ["rt", "time", "sync", "macros", "rt-multi-thread", "signal", "tracing"] } 20 | tracing = "0.1" 21 | tracing-glog = "0.4" 22 | tracing-subscriber = { version = "0.3", features = ["env-filter"]} 23 | -------------------------------------------------------------------------------- /ractor_playground/src/distributed.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 | //! Distributed cluster playground 7 | 8 | use ractor::concurrency::Duration; 9 | use ractor::Actor; 10 | use ractor::ActorProcessingErr; 11 | use ractor::ActorRef; 12 | use ractor::RpcReplyPort; 13 | use ractor_cluster::RactorClusterMessage; 14 | 15 | /// Run test with 16 | /// 17 | /// ```bash 18 | /// RUST_LOG=debug cargo run -p ractor_playground -- cluster-handshake 8198 8199 true 19 | /// ``` 20 | pub(crate) async fn test_auth_handshake(port_a: u16, port_b: u16, valid_cookies: bool) { 21 | let cookie_a = "cookie".to_string(); 22 | let cookie_b = if valid_cookies { 23 | cookie_a.clone() 24 | } else { 25 | "bad cookie".to_string() 26 | }; 27 | let hostname = "localhost".to_string(); 28 | 29 | let server_a = ractor_cluster::NodeServer::new( 30 | port_a, 31 | cookie_a, 32 | "node_a".to_string(), 33 | hostname.clone(), 34 | None, 35 | None, 36 | ); 37 | let server_b = ractor_cluster::NodeServer::new( 38 | port_b, 39 | cookie_b, 40 | "node_b".to_string(), 41 | hostname, 42 | None, 43 | None, 44 | ); 45 | 46 | let (actor_a, handle_a) = Actor::spawn(None, server_a, ()) 47 | .await 48 | .expect("Failed to start NodeServer A"); 49 | let (actor_b, handle_b) = Actor::spawn(None, server_b, ()) 50 | .await 51 | .expect("Failed to start NodeServer B"); 52 | 53 | if let Err(error) = 54 | ractor_cluster::node::client::connect(&actor_b, format!("127.0.0.1:{port_a}")).await 55 | { 56 | tracing::error!("Failed to connect with error {error}") 57 | } else { 58 | tracing::info!("Client connected NodeServer b to NodeServer a"); 59 | } 60 | 61 | ractor::concurrency::sleep(Duration::from_millis(10000)).await; 62 | tracing::warn!("Terminating test"); 63 | 64 | // cleanup 65 | actor_a.stop(None); 66 | actor_b.stop(None); 67 | handle_a.await.unwrap(); 68 | handle_b.await.unwrap(); 69 | } 70 | 71 | // TODO: protocol integration tests 72 | 73 | struct PingPongActor; 74 | 75 | #[derive(RactorClusterMessage)] 76 | enum PingPongActorMessage { 77 | Ping, 78 | #[rpc] 79 | Rpc(String, RpcReplyPort), 80 | } 81 | 82 | impl Actor for PingPongActor { 83 | type Msg = PingPongActorMessage; 84 | type State = (); 85 | type Arguments = (); 86 | 87 | async fn pre_start( 88 | &self, 89 | myself: ActorRef, 90 | _: (), 91 | ) -> Result { 92 | ractor::pg::join("test".to_string(), vec![myself.get_cell()]); 93 | Ok(()) 94 | } 95 | 96 | async fn handle( 97 | &self, 98 | _myself: ActorRef, 99 | message: Self::Msg, 100 | _state: &mut Self::State, 101 | ) -> Result<(), ActorProcessingErr> { 102 | let group = "test".to_string(); 103 | let remote_actors = ractor::pg::get_members(&group) 104 | .into_iter() 105 | .filter(|actor| !actor.get_id().is_local()) 106 | .map(ActorRef::::from) 107 | .collect::>(); 108 | match message { 109 | Self::Msg::Ping => { 110 | tracing::info!( 111 | "Received a ping, replying in kind to {} remote actors", 112 | remote_actors.len() 113 | ); 114 | for act in remote_actors { 115 | act.cast(PingPongActorMessage::Ping)?; 116 | } 117 | } 118 | Self::Msg::Rpc(request, reply) => { 119 | tracing::info!( 120 | "Received an RPC of '{request}' replying in kind to {} remote actors", 121 | remote_actors.len() 122 | ); 123 | let reply_msg = format!("{request}."); 124 | reply.send(reply_msg.clone())?; 125 | for act in remote_actors { 126 | let _reply = 127 | ractor::call_t!(act, PingPongActorMessage::Rpc, 100, reply_msg.clone())?; 128 | } 129 | } 130 | } 131 | 132 | Ok(()) 133 | } 134 | } 135 | 136 | pub(crate) async fn startup_ping_pong_test_node(port: u16, connect_client: Option) { 137 | let cookie = "cookie".to_string(); 138 | let hostname = "localhost".to_string(); 139 | 140 | let server = 141 | ractor_cluster::NodeServer::new(port, cookie, "node_a".to_string(), hostname, None, None); 142 | 143 | let (actor, handle) = Actor::spawn(None, server, ()) 144 | .await 145 | .expect("Failed to start NodeServer A"); 146 | 147 | let (test_actor, test_handle) = Actor::spawn(None, PingPongActor, ()) 148 | .await 149 | .expect("Ping pong actor failed to start up!"); 150 | 151 | if let Some(cport) = connect_client { 152 | if let Err(error) = 153 | ractor_cluster::node::client::connect(&actor, format!("127.0.0.1:{cport}")).await 154 | { 155 | tracing::error!("Failed to connect with error {error}") 156 | } else { 157 | tracing::info!("Client connected to NdoeServer"); 158 | } 159 | } 160 | 161 | // wait for server startup to complete (and in the event of a client, wait for auth to complete), then startup the test actor 162 | ractor::concurrency::sleep(Duration::from_millis(1000)).await; 163 | // test_actor.cast(PingPongActorMessage::Ping).unwrap(); 164 | let _ = test_actor 165 | .call( 166 | |tx| PingPongActorMessage::Rpc("".to_string(), tx), 167 | Some(Duration::from_millis(50)), 168 | ) 169 | .await 170 | .unwrap(); 171 | 172 | // wait for exit 173 | tokio::signal::ctrl_c() 174 | .await 175 | .expect("failed to listen for event"); 176 | 177 | // cleanup 178 | test_actor.stop(None); 179 | test_handle.await.unwrap(); 180 | actor.stop(None); 181 | handle.await.unwrap(); 182 | } 183 | -------------------------------------------------------------------------------- /ractor_playground/src/main.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 | use std::env; 7 | use std::io::stderr; 8 | use std::io::IsTerminal; 9 | 10 | use clap::Parser; 11 | use tracing_glog::Glog; 12 | use tracing_glog::GlogFields; 13 | use tracing_subscriber::filter::Directive; 14 | use tracing_subscriber::filter::EnvFilter; 15 | use tracing_subscriber::layer::SubscriberExt; 16 | use tracing_subscriber::Registry; 17 | 18 | mod distributed; 19 | mod ping_pong; 20 | 21 | #[derive(Debug, clap::Subcommand)] 22 | enum Cli { 23 | /// Run the ping-pong actor test 24 | PingPong, 25 | /// Run two dist nodes and try and have them connect together 26 | ClusterHandshake { 27 | /// The server port for the first node 28 | port_a: u16, 29 | /// The server port for the second node 30 | port_b: u16, 31 | /// Flag indicating if the cookies between the hosts match 32 | valid_cookies: Option, 33 | }, 34 | 35 | /// Represents a "remote" cluster based ping-pong using paging groups 36 | RemotePingPong { 37 | /// The host port for this NodeServer 38 | port: u16, 39 | /// The remote server port to connect to (if Some) 40 | remote_port: Option, 41 | }, 42 | } 43 | 44 | #[derive(Parser, Debug)] 45 | struct Args { 46 | #[command(subcommand)] 47 | command: Cli, 48 | 49 | /// Set the logging level based on the set of filter directives. 50 | /// 51 | /// Normal logging levels are supported (e.g. trace, debug, info, warn, 52 | /// error), but it's possible to set verbosity for specific spans and 53 | /// events. 54 | #[clap(short, long, default_value = "info", use_value_delimiter = true)] 55 | log: Vec, 56 | } 57 | 58 | // MAIN // 59 | #[tokio::main(flavor = "multi_thread", worker_threads = 4)] 60 | async fn main() { 61 | let args = Args::parse(); 62 | 63 | // if it's not set, set the log level to debug 64 | if env::var("RUST_LOG").is_err() { 65 | env::set_var("RUST_LOG", "debug"); 66 | } 67 | init_logging(args.log); 68 | 69 | // parse the CLI and run the correct playground scenario 70 | match args.command { 71 | Cli::PingPong => { 72 | ping_pong::run_ping_pong().await; 73 | } 74 | Cli::ClusterHandshake { 75 | port_a, 76 | port_b, 77 | valid_cookies, 78 | } => { 79 | distributed::test_auth_handshake(port_a, port_b, valid_cookies.unwrap_or(true)).await; 80 | } 81 | Cli::RemotePingPong { port, remote_port } => { 82 | distributed::startup_ping_pong_test_node(port, remote_port).await; 83 | } 84 | } 85 | } 86 | 87 | fn init_logging(directives: Vec) { 88 | let fmt = tracing_subscriber::fmt::Layer::default() 89 | .with_ansi(stderr().is_terminal()) 90 | .with_writer(std::io::stderr) 91 | .event_format(Glog::default().with_timer(tracing_glog::LocalTime::default())) 92 | .fmt_fields(GlogFields::default().compact()); 93 | 94 | let filter = directives 95 | .into_iter() 96 | .fold(EnvFilter::from_default_env(), |filter, directive| { 97 | filter.add_directive(directive) 98 | }); 99 | 100 | let subscriber = Registry::default().with(filter).with(fmt); 101 | tracing::subscriber::set_global_default(subscriber).expect("to set global subscriber"); 102 | } 103 | -------------------------------------------------------------------------------- /ractor_playground/src/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 | //! A ping-pong actor implementation 7 | 8 | use ractor::Actor; 9 | use ractor::ActorProcessingErr; 10 | use ractor::ActorRef; 11 | 12 | pub struct PingPong; 13 | 14 | #[derive(Debug, Clone)] 15 | pub enum Message { 16 | Ping, 17 | Pong, 18 | } 19 | impl ractor::Message for Message {} 20 | 21 | impl Message { 22 | fn next(&self) -> Self { 23 | match self { 24 | Self::Ping => Self::Pong, 25 | Self::Pong => Self::Ping, 26 | } 27 | } 28 | 29 | fn print(&self) { 30 | match self { 31 | Self::Ping => print!("ping.."), 32 | Self::Pong => print!("pong.."), 33 | } 34 | } 35 | } 36 | 37 | impl Actor for PingPong { 38 | type Msg = Message; 39 | type Arguments = (); 40 | type State = u8; 41 | 42 | async fn pre_start( 43 | &self, 44 | myself: ActorRef, 45 | _: (), 46 | ) -> Result { 47 | tracing::info!("pre_start called"); 48 | // startup the event processing 49 | myself.send_message(Message::Ping).unwrap(); 50 | // create the initial state 51 | Ok(0u8) 52 | } 53 | 54 | async fn post_start( 55 | &self, 56 | _this_actor: ActorRef, 57 | _state: &mut Self::State, 58 | ) -> Result<(), ActorProcessingErr> { 59 | tracing::info!("post_start called"); 60 | Ok(()) 61 | } 62 | 63 | /// Invoked after an actor has been stopped. 64 | async fn post_stop( 65 | &self, 66 | _this_actor: ActorRef, 67 | _state: &mut Self::State, 68 | ) -> Result<(), ActorProcessingErr> { 69 | tracing::info!("post_stop called"); 70 | Ok(()) 71 | } 72 | 73 | async fn handle( 74 | &self, 75 | myself: ActorRef, 76 | message: Self::Msg, 77 | state: &mut Self::State, 78 | ) -> Result<(), ActorProcessingErr> { 79 | if *state < 10u8 { 80 | message.print(); 81 | myself.send_message(message.next()).unwrap(); 82 | *state += 1; 83 | } else { 84 | tracing::info!(""); 85 | myself.stop(None); 86 | // don't send another message, rather stop the agent after 10 iterations 87 | } 88 | Ok(()) 89 | } 90 | } 91 | 92 | /// Run the ping-pong actor test with 93 | /// 94 | /// ```bash 95 | /// cargo run -p ractor_playground -- ping-pong 96 | /// ``` 97 | pub(crate) async fn run_ping_pong() { 98 | let (_, actor_handle) = Actor::spawn(None, PingPong, ()) 99 | .await 100 | .expect("Failed to start actor"); 101 | actor_handle.await.expect("Actor failed to exit cleanly"); 102 | } 103 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # To format doc tests, you need to run cargo +nightly fmt 2 | # and uncomment the following line 3 | # 4 | # format_code_in_doc_comments = true 5 | # imports_granularity = "Item" 6 | # reorder_imports = true 7 | # group_imports = "StdExternalCrate" -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | default-run = "xtask" 4 | version = "0.1.0" 5 | edition = "2021" 6 | publish = false 7 | 8 | [dependencies] 9 | xtaskops = "0.4" 10 | anyhow = "1" 11 | -------------------------------------------------------------------------------- /xtask/README.md: -------------------------------------------------------------------------------- 1 | This package is included here to support the automatic reporting of code coverage on 2 | Github. To view code coverage locally: 3 | 4 | Do this once to set it up: 5 | ``` 6 | rustup component add llvm-tools-preview 7 | cargo install grcov 8 | ``` 9 | 10 | Subsequently, run: 11 | ``` 12 | cargo xtask coverage --dev 13 | ``` -------------------------------------------------------------------------------- /xtask/src/main.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 | fn main() -> Result<(), anyhow::Error> { 7 | xtaskops::tasks::main() 8 | } 9 | --------------------------------------------------------------------------------