├── .github └── workflows │ ├── ci.yml │ ├── heph.yml │ ├── http.yml │ ├── inbox.yml │ ├── remote.yml │ ├── rt.yml │ ├── test │ └── action.yml │ ├── test_miri │ └── action.yml │ └── test_sanitizer │ └── action.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── Makefile ├── Makefile.include ├── README.md ├── benches └── timers_container │ ├── Cargo.toml │ ├── README.md │ └── bench.rs ├── doc ├── Fault Isolation.md ├── Remote Message Passing.md ├── Scheduler.md └── Trace Format.md ├── examples ├── 1_hello_world.rs ├── 2_rpc.rs ├── 3_sync_actor.rs ├── 4_restart_supervisor.rs ├── README.md └── runtime │ └── mod.rs ├── http ├── Cargo.toml ├── Makefile ├── examples │ ├── my_ip.rs │ └── route.rs ├── src │ ├── body.rs │ ├── client.rs │ ├── handler.rs │ ├── head │ │ ├── header.rs │ │ ├── method.rs │ │ ├── mod.rs │ │ ├── status_code.rs │ │ └── version.rs │ ├── lib.rs │ ├── parse_headers.bash │ ├── request.rs │ ├── response.rs │ ├── route.rs │ ├── server.rs │ ├── str.rs │ └── transform.rs └── tests │ ├── functional.rs │ └── functional │ ├── body.rs │ ├── client.rs │ ├── from_header_value.rs │ ├── header.rs │ ├── message.rs │ ├── method.rs │ ├── route.rs │ ├── server.rs │ ├── status_code.rs │ ├── transform.rs │ └── version.rs ├── inbox ├── CHANGELOG.md ├── Cargo.toml ├── Makefile ├── README.md ├── src │ ├── lib.rs │ ├── oneshot.rs │ ├── tests.rs │ └── waker.rs └── tests │ ├── drop.rs │ ├── functional.rs │ ├── oneshot.rs │ ├── regression.rs │ ├── threaded.rs │ └── util │ └── mod.rs ├── remote ├── Cargo.toml ├── Makefile └── src │ ├── lib.rs │ └── net_relay │ ├── mod.rs │ ├── routers.rs │ ├── tcp.rs │ ├── udp.rs │ └── uuid.rs ├── rt ├── CHANGELOG.md ├── Cargo.toml ├── Makefile ├── README.md ├── examples │ ├── 1_hello_world.rs │ ├── 2_my_ip.rs │ ├── 3_rpc.rs │ ├── 4_sync_actor.rs │ ├── 6_process_signals.rs │ ├── 7_restart_supervisor.rs │ ├── 8_tracing.rs │ ├── 99_stress_memory.rs │ ├── 9_systemd.rs │ ├── README.md │ └── redis.rs ├── src │ ├── access.rs │ ├── channel.rs │ ├── coordinator.rs │ ├── error.rs │ ├── fs │ │ ├── mod.rs │ │ └── watch.rs │ ├── io │ │ ├── buf.rs │ │ ├── buf_pool.rs │ │ ├── futures.rs │ │ ├── mod.rs │ │ └── traits.rs │ ├── lib.rs │ ├── local │ │ └── mod.rs │ ├── log.rs │ ├── net │ │ ├── futures.rs │ │ ├── mod.rs │ │ ├── tcp │ │ │ ├── listener.rs │ │ │ ├── mod.rs │ │ │ ├── server.rs │ │ │ └── stream.rs │ │ ├── udp.rs │ │ └── uds │ │ │ ├── datagram.rs │ │ │ ├── listener.rs │ │ │ ├── mod.rs │ │ │ └── stream.rs │ ├── pipe.rs │ ├── scheduler │ │ ├── cfs.rs │ │ ├── inactive.rs │ │ ├── mod.rs │ │ ├── process.rs │ │ ├── shared │ │ │ ├── inactive.rs │ │ │ ├── mod.rs │ │ │ ├── runqueue.rs │ │ │ └── tests.rs │ │ └── tests.rs │ ├── setup.rs │ ├── shared │ │ └── mod.rs │ ├── signal.rs │ ├── spawn │ │ ├── mod.rs │ │ └── options.rs │ ├── sync_worker.rs │ ├── systemd.rs │ ├── test.rs │ ├── timer.rs │ ├── timers │ │ ├── mod.rs │ │ ├── shared.rs │ │ └── tests.rs │ ├── trace.rs │ ├── util.rs │ ├── wakers │ │ ├── mod.rs │ │ ├── shared.rs │ │ └── tests.rs │ └── worker.rs └── tests │ ├── data │ ├── hello_world │ └── lorem_ipsum │ ├── examples.rs │ ├── functional.rs │ ├── functional │ ├── access.rs │ ├── actor.rs │ ├── actor_context.rs │ ├── actor_group.rs │ ├── actor_ref.rs │ ├── from_message.rs │ ├── fs.rs │ ├── fs_watch.rs │ ├── future.rs │ ├── io.rs │ ├── pipe.rs │ ├── restart_supervisor.rs │ ├── runtime.rs │ ├── signal.rs │ ├── spawn.rs │ ├── sync_actor.rs │ ├── tcp │ │ ├── listener.rs │ │ ├── mod.rs │ │ ├── server.rs │ │ └── stream.rs │ ├── test.rs │ ├── timer.rs │ ├── udp.rs │ └── uds │ │ ├── datagram.rs │ │ ├── listener.rs │ │ ├── mod.rs │ │ └── stream.rs │ ├── process_signals.rs │ ├── regression.rs │ ├── regression │ ├── issue_145.rs │ ├── issue_294.rs │ └── issue_323.rs │ └── util │ └── mod.rs ├── src ├── actor │ ├── context.rs │ ├── mod.rs │ └── tests.rs ├── actor_ref │ ├── mod.rs │ └── rpc.rs ├── future.rs ├── lib.rs ├── messages.rs ├── quick_start.rs ├── supervisor.rs ├── sync.rs └── test.rs ├── tests ├── examples.rs ├── functional.rs ├── functional │ ├── actor.rs │ ├── actor_group.rs │ ├── actor_ref.rs │ ├── restart_supervisor.rs │ ├── stop_supervisor.rs │ ├── sync_actor.rs │ └── test.rs └── message_loss.rs └── tools ├── Cargo.toml └── src └── bin └── convert_trace.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ main ] 5 | pull_request: 6 | branches: [ main ] 7 | permissions: 8 | contents: read 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUST_BACKTRACE: full 12 | jobs: 13 | Clippy: 14 | runs-on: ubuntu-24.04 15 | timeout-minutes: 2 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: dtolnay/rust-toolchain@nightly 19 | with: 20 | components: clippy 21 | - name: Run Clippy 22 | run: make clippy_all 23 | Rustfmt: 24 | runs-on: ubuntu-24.04 25 | timeout-minutes: 2 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: dtolnay/rust-toolchain@nightly 29 | with: 30 | components: rustfmt 31 | - name: Check formatting 32 | run: cargo fmt --all -- --check 33 | Docs: 34 | runs-on: ubuntu-24.04 35 | timeout-minutes: 2 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: dtolnay/rust-toolchain@nightly 39 | - name: Check docs 40 | run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspace --all-features 41 | -------------------------------------------------------------------------------- /.github/workflows/heph.yml: -------------------------------------------------------------------------------- 1 | name: Heph 2 | on: 3 | push: 4 | branches: [ main ] 5 | paths: 6 | - "src/**" 7 | - "test/**" 8 | - "examples/**" 9 | - "Makefile" 10 | - ".github/workflows/heph.yml" 11 | pull_request: 12 | branches: [ main ] 13 | paths: 14 | - "src/**" 15 | - "test/**" 16 | - "examples/**" 17 | - "Makefile" 18 | - ".github/workflows/heph.yml" 19 | permissions: 20 | contents: read 21 | env: 22 | CARGO_TERM_COLOR: always 23 | RUST_BACKTRACE: full 24 | jobs: 25 | Test: 26 | runs-on: ubuntu-24.04 27 | timeout-minutes: 10 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: ./.github/workflows/test 31 | with: 32 | working-directory: ./ 33 | Miri: 34 | runs-on: ubuntu-24.04 35 | timeout-minutes: 10 36 | # TODO: enable . 37 | if: false 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: ./.github/workflows/test_miri 41 | with: 42 | working-directory: ./ 43 | Sanitiser: 44 | runs-on: ubuntu-24.04 45 | timeout-minutes: 10 46 | # TODO: enable . 47 | if: false 48 | strategy: 49 | fail-fast: false 50 | matrix: 51 | # LeakSanitizer is broken, see 52 | # . 53 | sanitiser: [address, memory, thread] # leak 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: ./.github/workflows/test_sanitizer 57 | with: 58 | sanitizer: ${{ matrix.sanitiser }} 59 | working-directory: ./ 60 | -------------------------------------------------------------------------------- /.github/workflows/http.yml: -------------------------------------------------------------------------------- 1 | name: Heph-HTTP 2 | on: 3 | push: 4 | branches: [ main ] 5 | paths: 6 | - "http/**" 7 | - ".github/workflows/http.yml" 8 | pull_request: 9 | branches: [ main ] 10 | paths: 11 | - "http/**" 12 | - ".github/workflows/http.yml" 13 | permissions: 14 | contents: read 15 | env: 16 | CARGO_TERM_COLOR: always 17 | RUST_BACKTRACE: full 18 | jobs: 19 | Test: 20 | runs-on: ubuntu-24.04 21 | timeout-minutes: 10 22 | # TODO: enable . 23 | if: false 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: ./.github/workflows/test 27 | with: 28 | working-directory: http 29 | Sanitiser: 30 | runs-on: ubuntu-24.04 31 | timeout-minutes: 10 32 | # TODO: enable . 33 | if: false 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | # LeakSanitizer is broken, see 38 | # . 39 | sanitiser: [address, memory, thread] # leak 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: ./.github/workflows/test_sanitizer 43 | with: 44 | sanitizer: ${{ matrix.sanitiser }} 45 | working-directory: http 46 | -------------------------------------------------------------------------------- /.github/workflows/inbox.yml: -------------------------------------------------------------------------------- 1 | name: Heph-inbox 2 | on: 3 | push: 4 | branches: [ main ] 5 | paths: 6 | - "inbox/**" 7 | - ".github/workflows/inbox.yml" 8 | pull_request: 9 | branches: [ main ] 10 | paths: 11 | - "inbox/**" 12 | - ".github/workflows/inbox.yml" 13 | permissions: 14 | contents: read 15 | env: 16 | CARGO_TERM_COLOR: always 17 | RUST_BACKTRACE: full 18 | jobs: 19 | Test: 20 | runs-on: ubuntu-24.04 21 | timeout-minutes: 10 22 | # This often run too many iterations on CI, which is not a real error. 23 | continue-on-error: ${{ matrix.release == 'release' }} 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | release: ['', '--release'] # '' => debug. 28 | steps: 29 | - uses: actions/checkout@v4 30 | - uses: ./.github/workflows/test 31 | with: 32 | test-flags: ${{ matrix.release }} 33 | working-directory: inbox 34 | Miri: 35 | runs-on: ubuntu-24.04 36 | timeout-minutes: 10 37 | steps: 38 | - uses: actions/checkout@v4 39 | - uses: ./.github/workflows/test_miri 40 | with: 41 | working-directory: inbox 42 | Sanitiser: 43 | runs-on: ubuntu-24.04 44 | timeout-minutes: 10 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | # LeakSanitizer is broken, see 49 | # . 50 | sanitiser: [address, memory, thread] # leak 51 | steps: 52 | - uses: actions/checkout@v4 53 | - uses: ./.github/workflows/test_sanitizer 54 | with: 55 | sanitizer: ${{ matrix.sanitiser }} 56 | working-directory: inbox 57 | -------------------------------------------------------------------------------- /.github/workflows/remote.yml: -------------------------------------------------------------------------------- 1 | name: Heph-remote 2 | on: 3 | push: 4 | branches: [ main ] 5 | paths: 6 | - "remote/**" 7 | - ".github/workflows/remote.yml" 8 | pull_request: 9 | branches: [ main ] 10 | paths: 11 | - "remote/**" 12 | - ".github/workflows/remote.yml" 13 | permissions: 14 | contents: read 15 | env: 16 | CARGO_TERM_COLOR: always 17 | RUST_BACKTRACE: full 18 | jobs: 19 | Test: 20 | runs-on: ubuntu-24.04 21 | timeout-minutes: 10 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Install Cargo-hack 25 | run: cargo install --debug cargo-hack 26 | - uses: ./.github/workflows/test 27 | with: 28 | working-directory: remote 29 | Sanitiser: 30 | runs-on: ubuntu-24.04 31 | timeout-minutes: 10 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | # LeakSanitizer is broken, see 36 | # . 37 | sanitiser: [address, memory, thread] # leak 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: ./.github/workflows/test_sanitizer 41 | with: 42 | sanitizer: ${{ matrix.sanitiser }} 43 | working-directory: remote 44 | -------------------------------------------------------------------------------- /.github/workflows/rt.yml: -------------------------------------------------------------------------------- 1 | name: Heph-rt 2 | on: 3 | push: 4 | branches: [ main ] 5 | paths: 6 | - "rt/**" 7 | - ".github/workflows/rt.yml" 8 | pull_request: 9 | branches: [ main ] 10 | paths: 11 | - "rt/**" 12 | - ".github/workflows/rt.yml" 13 | permissions: 14 | contents: read 15 | env: 16 | CARGO_TERM_COLOR: always 17 | RUST_BACKTRACE: full 18 | jobs: 19 | Test: 20 | runs-on: ubuntu-24.04 21 | timeout-minutes: 10 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: ./.github/workflows/test 25 | with: 26 | working-directory: rt 27 | Sanitiser: 28 | runs-on: ubuntu-24.04 29 | timeout-minutes: 10 30 | # TODO: enable . 31 | if: false 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | # LeakSanitizer is broken, see 36 | # . 37 | sanitiser: [address, memory, thread] # leak 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: ./.github/workflows/test_sanitizer 41 | with: 42 | sanitizer: ${{ matrix.sanitiser }} 43 | working-directory: rt 44 | -------------------------------------------------------------------------------- /.github/workflows/test/action.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | description: 'Run tests using `make test`.' 3 | inputs: 4 | test-flags: 5 | description: 'Additional flags to pass using TEST_FLAGS (see Makefile).' 6 | required: false 7 | working-directory: 8 | description: 'Working directory.' 9 | required: true 10 | runs: 11 | using: 'composite' 12 | steps: 13 | - uses: dtolnay/rust-toolchain@nightly 14 | - name: Run tests 15 | shell: bash 16 | run: | 17 | cd "${{ inputs.working-directory }}" 18 | TEST_FLAGS="${{ inputs.test-flags }}" make test 19 | -------------------------------------------------------------------------------- /.github/workflows/test_miri/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Test with miri' 2 | description: 'Run tests using `make test_miri`.' 3 | inputs: 4 | test-flags: 5 | description: 'Additional flags to pass using TEST_FLAGS (see Makefile).' 6 | required: false 7 | working-directory: 8 | description: 'Working directory.' 9 | required: true 10 | runs: 11 | using: 'composite' 12 | steps: 13 | - uses: dtolnay/rust-toolchain@nightly 14 | with: 15 | components: miri 16 | - name: Run tests 17 | shell: bash 18 | run: | 19 | cd "${{ inputs.working-directory }}" 20 | TEST_FLAGS="${{ inputs.test-flags }}" make test_miri 21 | -------------------------------------------------------------------------------- /.github/workflows/test_sanitizer/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Test with sanitizer' 2 | description: 'Run tests using `make test_sanitizer`.' 3 | inputs: 4 | sanitizer: 5 | description: 'Sanitizer to use.' 6 | required: true 7 | test-flags: 8 | description: 'Additional flags to pass using TEST_FLAGS (see Makefile).' 9 | required: false 10 | working-directory: 11 | description: 'Working directory.' 12 | required: true 13 | runs: 14 | using: 'composite' 15 | steps: 16 | - uses: dtolnay/rust-toolchain@nightly 17 | with: 18 | components: rust-src 19 | - name: Run tests with sanitiser 20 | shell: bash 21 | run: | 22 | cd "${{ inputs.working-directory }}" 23 | TEST_FLAGS="${{ inputs.test-flags }}" make test_sanitizer sanitizer="${{ inputs.sanitizer }}" 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heph" 3 | description = "Heph is an actor framework based on asynchronous functions." 4 | version = "0.5.0" 5 | publish = false # In development. 6 | authors = ["Thomas de Zeeuw "] 7 | license = "MIT" 8 | documentation = "https://docs.rs/heph" 9 | repository = "https://github.com/Thomasdezeeuw/heph" 10 | readme = "README.md" 11 | keywords = ["actor", "async", "functions"] 12 | categories = ["asynchronous", "web-programming"] 13 | include = ["/Cargo.toml", "/src/**/*.rs", "/README.md", "/LICENSE"] 14 | edition = "2021" 15 | 16 | [features] 17 | default = [] 18 | 19 | # Feature that enables the `test` module. 20 | test = ["getrandom"] 21 | 22 | [dependencies] 23 | heph-inbox = { version = "0.2.3", path = "./inbox", default-features = false } 24 | log = { version = "0.4.21", default-features = false, features = ["kv_std"] } 25 | 26 | # Optional dependencies, enabled by features. 27 | # Required by the `test` feature. 28 | getrandom = { version = "0.2.2", default-features = false, features = ["std"], optional = true } 29 | 30 | [dev-dependencies] 31 | # NOTE: the following two dependencies may only used by a limited number of examples. 32 | heph-rt = { version = "0.5.0", path = "./rt", default-features = false } 33 | std-logger = { version = "0.5.3", default-features = false } 34 | 35 | [[test]] 36 | name = "examples" 37 | 38 | [[test]] 39 | name = "functional" 40 | required-features = ["test"] 41 | 42 | [[test]] 43 | name = "message_loss" 44 | required-features = ["test"] 45 | 46 | [workspace] 47 | members = [ 48 | "http", 49 | "inbox", 50 | "remote", 51 | "rt", 52 | "tools", 53 | 54 | "benches/timers_container", 55 | ] 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017-2025 Thomas de Zeeuw 2 | 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include Makefile.include 2 | 3 | # Crates in this repo. 4 | CRATES := ./ inbox rt remote http 5 | # Target that run the target in all $CRATES. 6 | TARGETS := test_all test_sanitizers_all test_sanitizer_all check_all clippy_all 7 | 8 | # This little construct simply runs the target ($MAKECMDGOALS) for all crates 9 | # $CRATES. 10 | $(TARGETS): $(CRATES) 11 | $(CRATES): 12 | @$(MAKE) -C $@ $(patsubst %_all,%,$(MAKECMDGOALS)) 13 | 14 | coverage_all: 15 | $(MAKE) coverage test=test_all 16 | 17 | lint_all: clippy_all 18 | 19 | doc_all: 20 | cargo doc --all-features --workspace 21 | 22 | doc_all_private: 23 | cargo doc --all-features --workspace --document-private-items 24 | 25 | clean_all: 26 | cargo clean 27 | 28 | .PHONY: $(TARGETS) $(CRATES) test_all test_sanitizers_all test_sanitizer_all check_all clippy_all lint_all doc_all doc_private_all clean_all 29 | -------------------------------------------------------------------------------- /benches/timers_container/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benches" 3 | version = "0.1.0" 4 | authors = ["Thomas de Zeeuw "] 5 | edition = "2021" 6 | 7 | [dev-dependencies] 8 | criterion = { version = "0.3.4", default-features = false, features = ["html_reports", "cargo_bench_support"] } 9 | rand = { version = "0.8.3", default-features = false } 10 | rand_xoshiro = { version = "0.6.0", default-features = false } 11 | 12 | [[bench]] 13 | name = "timers_container" 14 | path = "bench.rs" 15 | harness = false 16 | -------------------------------------------------------------------------------- /benches/timers_container/README.md: -------------------------------------------------------------------------------- 1 | Benchmarks for the container used in the timers implementation. 2 | 3 | It focusses on three operations: 4 | * Removing the next timer to expire. 5 | * Adding a new timer, with an expectation that the deadline is after the last 6 | deadline added. 7 | * Removing an arbitrary timer. 8 | -------------------------------------------------------------------------------- /doc/Fault Isolation.md: -------------------------------------------------------------------------------- 1 | # How Heph does actor isolation. 2 | 3 | There are two kinds of faults: errors and failures. An error is a problem in 4 | regular processing and should be handled via the `Result` type. It should never 5 | bring down the system and the error should be returned to the actor's 6 | supervisor, if it can't be handled by the actor itself. 7 | 8 | A failure is a problem which can not be corrected and should not occur in 9 | regular processing, for example dividing by zero. The most common form of a 10 | failure in Rust is a `panic!`. These are often caused by programmer error (i.e. 11 | bugs) and are hard or nearly impossible to recover from, as such Heph makes no 12 | attempt to do so. Heph simply doesn't handle these kinds of faults. Thus if a 13 | failure occurs the system halts as it is pointless to continue. 14 | -------------------------------------------------------------------------------- /doc/Remote Message Passing.md: -------------------------------------------------------------------------------- 1 | # Remote Message Passing 2 | 3 | Current ideas for remote message passing for Heph. 4 | 5 | The communication must be encrypted by default to protect the messages from 6 | being tempered with. 7 | 8 | Since the guarantees provided for sending messages don't include guaranteed 9 | delivery we can get away with using UDP for the messages. This does limit the 10 | message size to ~65k (minus encryption overhead), but this should be enough for 11 | most messages. And if at some point it isn't enough for some use cases we can 12 | introduce message fragmentation and send a message over multiple UDP packets 13 | (you know reinvention part of TCP). 14 | 15 | 16 | ## Requirements 17 | 18 | There are a number of requirement we need to satisfy to ensure proper 19 | communication: 20 | 21 | 22 | ### Authentication 23 | 24 | We need some form of authentication to ensure the origins of a message is the 25 | actual sender. We don't want to process messages from an (evil) third party and 26 | compromise our system. 27 | 28 | 29 | ### Integrity 30 | 31 | Any message we accept must be checked for tempering from an (evil) third party. 32 | 33 | 34 | ## Possible solution 35 | 36 | There many (partial) solution to this problem, just to name a few: 37 | 38 | * DTLS for encrypting UDP packets/connections. 39 | * PGP used for communication of email. 40 | * HMAC for signing messages. 41 | * Mosh SSH uses UDP and public/private key authentication. 42 | 43 | 44 | ### Current idea 45 | 46 | Two way private-public key usage. Each Heph process generates its own 47 | private-public key pair on startup, it shares it (somehow) with all other 48 | processes, possibly via some central shared authentication checker. 49 | 50 | When a process wants to communication with another process it requests it public 51 | key and uses it to send it messages. It encrypts the message using the public 52 | and then it's own private key. The receiving process requests the public key for 53 | the source address and decrypt the message with the public key of the sender and 54 | its own private key. 55 | -------------------------------------------------------------------------------- /doc/Scheduler.md: -------------------------------------------------------------------------------- 1 | # Scheduler Design 2 | 3 | The design of the scheduler is based on Linux' Completely Fair Scheduler (CFS). 4 | [1] 5 | 6 | Processes are ordered based on fair\_runtime, that is `runtime * priority`. For 7 | example If a process runs for 10 milliseconds with a normal priority (10) it's 8 | fair\_runtime will be 100 milliseconds. The process with the lowest 9 | fair\_runtime that is ready will be run next. 10 | 11 | Since processes (well futures) aren't preemptible they can run for ever if they 12 | want, it's up to the developer to make sure they don't. However if a long 13 | running process does at some point return then at least it won't be scheduled 14 | again quickly after, thus reducing the impact of badly behaving processes. 15 | 16 | Using this way of scheduling processes that do very little CPU intensive work 17 | and mostly do I/O (and thus go into a unready state very quickly) will be 18 | favoured in this algorithm. 19 | 20 | Scheduling processes to be run is based on `ProcessId`. There are currently 21 | three sources of processes being scheduled; 22 | 23 | - The system poller (`mio`). The system poller is used to receive events about 24 | system resources, e.g. TCP sockets, and can trigger the scheduling of a 25 | process. Here a batch of processes are scheduled at the same time, without a 26 | process running. 27 | 28 | - Sending message to an actor (process). This is only possible for 29 | `ActorPrcocess`es. This can be done when another process is running. 30 | 31 | - Future's `Waker`. Actors can wake itself or another actor via the future's 32 | `Waker`. This can be done when another process is running. 33 | 34 | The scheduler must also be able to add new process, without it it wouldn't be 35 | very useful. But it must be able to this while a process is running, e.g. in 36 | case of an `Initiator` it must possible for it to add an actor to the system. 37 | Effectively we need mutable access to a process in the scheduler to run it, 38 | while being able to add another process to it. 39 | 40 | Which brings us back to the scheduling of processes. The last two scheduling 41 | sources, sending messages and the futures' `Waker`, can be implemented via two 42 | methods, 1) directly on the scheduler, or 2) via the system poller. 43 | 44 | Scheduling directly on the scheduler would avoid some overhead and would likely 45 | be faster. However letting the system poller deal with it is easier to implement 46 | (it is already implemented in fact), and it reduce the need for mutable access 47 | to the scheduler while a process is running. 48 | 49 | 50 | ## Requirements 51 | 52 | The following operations need to be supported, and need to be optimised for. 53 | 54 | - Scheduling of a process, based on `ProcessId`, possibly while a process 55 | (possibly itself) is running. 56 | - Adding new processes while another process is running. 57 | - Getting the next process to run, based on the lowest fair\_runtime. 58 | 59 | 60 | ## Priority 61 | 62 | The priority of a process influence how often a process is run and the time 63 | between two runs. Party due to this it is a good idea to give `Initiators` a low 64 | priority to make sure that already accepted requests are handled before new 65 | requests are accepted, to prevent the system being flooded with new requests 66 | without the older requests being processed. 67 | 68 | 69 | ## Open questions 70 | 71 | - When to run a process vs. calling the system poller. Should this be an 72 | option? Maybe poll userspace events more often, would need to be added to 73 | mio? 74 | - What data structure to use for the `Scheduler`, Linux' CFS uses a Red-black 75 | tree. 76 | - How to schedule a process, when sending message or using a futures' `Waker`, 77 | directly on the scheduler or via the system poller. 78 | - Can (very) long process overflow the `Duration` type, especially with the 79 | priority multiplier? Thinking along the lines of processes that should be 80 | able to run for year on end. 81 | 82 | 83 | ## Resources 84 | 85 | Completely Fair Scheduler on Wikipedia. [1] 86 | 87 | Inside the Linux 2.6 Completely Fair Scheduler, by M. Tim Jones. [2] 88 | 89 | [1]: https://wikipedia.org/wiki/Completely_Fair_Scheduler 90 | [2]: https://www.ibm.com/developerworks/linux/library/l-completely-fair-scheduler 91 | -------------------------------------------------------------------------------- /examples/1_hello_world.rs: -------------------------------------------------------------------------------- 1 | use heph::actor::{self, actor_fn}; 2 | use heph::future::ActorFuture; 3 | use heph::supervisor::NoSupervisor; 4 | 5 | mod runtime; // Replace this with your favourite `Future` runtime. 6 | 7 | fn main() { 8 | // First we wrap our actor in an `ActorFuture` so that it implements the 9 | // `Future` trait, which also gives us an `ActorRef` so we can send it 10 | // messages. 11 | // 12 | // All actors need supervision, however our actor doesn't return an error 13 | // (it uses `!`, the never type, as error), because of this we'll use the 14 | // `NoSupervisor`, which is a supervisor that does nothing and can't be 15 | // called. For more information on actor supervision see the `supervisor` 16 | // module. 17 | let supervisor = NoSupervisor; 18 | // An unfortunate implementation detail requires us to convert our 19 | // asynchronous function to an `NewActor` implementation (trait that defines 20 | // how actors are (re)started). 21 | // The easiest way to do this is to use the `actor_fn` helper function. See 22 | // the documentation of `actor_fn` for more details on why this is required. 23 | let actor = actor_fn(greeter_actor); 24 | // We'll also supply the argument to start the actor, in our case this is 25 | // `()` since our actor doesn't accept any arguments. 26 | let args = (); 27 | 28 | // Now that we have all the arguments prepare we can create out `Future` 29 | // that represents our actor. 30 | let (future, actor_ref) = ActorFuture::new(supervisor, actor, args).unwrap(); 31 | 32 | // Now we can send our actor a message using its `actor_ref`. 33 | actor_ref.try_send("World").unwrap(); 34 | 35 | // We need to drop the `actor_ref` to ensure the actor stops. 36 | drop(actor_ref); 37 | 38 | // We run our `future` on our runtime. 39 | runtime::block_on(future); 40 | } 41 | 42 | /// Our greeter actor. 43 | /// 44 | /// We'll receive a single message and print it. 45 | async fn greeter_actor(mut ctx: actor::Context<&'static str>) { 46 | // All actors have an actor context, which give the actor access to, among 47 | // other things, its inbox from which it can receive a message. 48 | while let Ok(name) = ctx.receive_next().await { 49 | println!("Hello {name}"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/2_rpc.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use heph::actor::{self, actor_fn}; 4 | use heph::actor_ref::{ActorRef, RpcMessage}; 5 | use heph::future::ActorFuture; 6 | use heph::supervisor::NoSupervisor; 7 | 8 | mod runtime; // Replace this with your favourite `Future` runtime. 9 | 10 | fn main() { 11 | // See example 1 for the creation of `ActorFuture`s. 12 | let (pong_future, pong_ref) = ActorFuture::new(NoSupervisor, actor_fn(pong_actor), ()).unwrap(); 13 | let (ping_future, _) = ActorFuture::new(NoSupervisor, actor_fn(ping_actor), pong_ref).unwrap(); 14 | 15 | // We run ours futures on our runtime. 16 | runtime::block_on2(ping_future, pong_future); 17 | } 18 | 19 | async fn ping_actor(_: actor::Context<()>, actor_ref: ActorRef) { 20 | // Make a Remote Procedure Call (RPC) and await the response. 21 | match actor_ref.rpc(Ping).await { 22 | Ok(response) => println!("Got a RPC response: {response}"), 23 | Err(err) => eprintln!("RPC request error: {err}"), 24 | } 25 | } 26 | 27 | // Message type to support the ping-pong RPC. 28 | type PongMessage = RpcMessage; 29 | 30 | async fn pong_actor(mut ctx: actor::Context) { 31 | // Await a message, same as all other messages. 32 | while let Ok(msg) = ctx.receive_next().await { 33 | // Next we respond to the request. 34 | let res = msg 35 | .handle(|request| async move { 36 | println!("Got a RPC request: {request}"); 37 | // Return a response. 38 | Pong 39 | }) 40 | .await; 41 | 42 | if let Err(err) = res { 43 | eprintln!("failed to respond to RPC: {err}"); 44 | } 45 | } 46 | } 47 | 48 | struct Ping; 49 | 50 | impl fmt::Display for Ping { 51 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 52 | f.write_str("Ping") 53 | } 54 | } 55 | 56 | struct Pong; 57 | 58 | impl fmt::Display for Pong { 59 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 60 | f.write_str("Pong") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/3_sync_actor.rs: -------------------------------------------------------------------------------- 1 | use heph::actor::actor_fn; 2 | use heph::supervisor::NoSupervisor; 3 | use heph::sync::{self, SyncActorRunner}; 4 | 5 | fn main() { 6 | // `SyncActorRunner` can be used to run synchronous actors. This is similar 7 | // to `ActorFuture`, but runs sync actors (instead of async actors). 8 | let (actor, actor_ref) = SyncActorRunner::new(NoSupervisor, actor_fn(sync_actor)); 9 | 10 | // Just like with any actor reference we can send the actor a message. 11 | actor_ref.try_send("Hello world".to_string()).unwrap(); 12 | // We need to drop the reference here to ensure the actor stops. 13 | drop(actor_ref); 14 | 15 | // Unlike asynchronous actor we don't need a `Future` runtime to run 16 | // synchronous actors, they can be run like a normal function. 17 | let args = "Bye"; 18 | actor.run(args); 19 | } 20 | 21 | fn sync_actor(mut ctx: sync::Context, exit_msg: &'static str) { 22 | while let Ok(msg) = ctx.receive_next() { 23 | println!("Got a message: {msg}"); 24 | } 25 | println!("{exit_msg}"); 26 | } 27 | -------------------------------------------------------------------------------- /examples/4_restart_supervisor.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use heph::actor::{self, actor_fn}; 4 | use heph::future::ActorFuture; 5 | use heph::supervisor::restart_supervisor; 6 | use heph::sync::{self, SyncActorRunner}; 7 | 8 | mod runtime; 9 | 10 | fn main() { 11 | // Enable logging. 12 | std_logger::Config::logfmt().init(); 13 | 14 | // See example 1 Hell World for more information on starting and running 15 | // asynchronous actors. 16 | let arg = "Hello, World!"; 17 | let supervisor = PrintSupervisor::new(arg); 18 | let (future, _) = ActorFuture::new(supervisor, actor_fn(print_actor), arg).unwrap(); 19 | 20 | // We run our `future` on our runtime. 21 | runtime::block_on(future); 22 | 23 | // See example 3 Sync Actors for more information on starting and running 24 | // synchronous actors. 25 | let supervisor = PrintSupervisor::new(arg); 26 | let (sync_actor, _) = SyncActorRunner::new(supervisor, actor_fn(sync_print_actor)); 27 | sync_actor.run(arg); 28 | } 29 | 30 | // Create a restart supervisor for the actors below. 31 | restart_supervisor!( 32 | PrintSupervisor, // Name of the supervisor type. 33 | &'static str, // Argument for the actor. 34 | 5, // Maximum number of restarts. 35 | Duration::from_secs(30), // Time to reset the max. reset counter. 36 | ); 37 | 38 | /// A very bad printing actor. 39 | async fn print_actor(_: actor::Context<()>, msg: &'static str) -> Result<(), String> { 40 | Err(format!("can't print message '{msg}'")) 41 | } 42 | 43 | /// A very bad synchronous printing actor. 44 | fn sync_print_actor(_: sync::Context<()>, msg: &'static str) -> Result<(), String> { 45 | Err(format!("can't print message synchronously '{msg}'")) 46 | } 47 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains a number of examples that highlight certain parts of 4 | Heph. Note that the Heph-rt crate has even more examples in its [examples 5 | directory]. 6 | 7 | [examples directory]: ../rt/examples 8 | 9 | 10 | ## 1. Hello World 11 | 12 | Conforming to the tradition that is "Hello World", a simple program that prints 13 | "Hello World". 14 | 15 | The code can be found in `1_hello_world.rs` and run with `cargo run --example 16 | 1_hello_world`, and it should print "Hello World". 17 | 18 | 19 | ## 2. RPC 20 | 21 | The second example shows something more interesting: how Heph makes Remote 22 | Procedure Calls (RPC) easy. 23 | 24 | 25 | ## 3. Synchronous Actor 26 | 27 | The third example shows how to use synchronous actors. These are actors that 28 | have the thread all to themselves, which means that can do heavy computation and 29 | blocking I/O without stalling other actors. 30 | 31 | 32 | ## 4. Restart Supervisor Macro 33 | 34 | Example four shows how the `restart_supervisor!` macro can be used to easily 35 | create a new `Supervisor` implementation that attempts to restart the actor with 36 | cloned arguments. 37 | -------------------------------------------------------------------------------- /examples/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | //! A bad [`Future`] runtime implementation. This is just here for the examples, 2 | //! don't actually use this. Consider the Heph-rt crate if you want a proper 3 | //! implementation. 4 | 5 | #![allow(dead_code)] // Not all examples use all functions. 6 | 7 | use std::future::{Future, IntoFuture}; 8 | use std::pin::{pin, Pin}; 9 | use std::task::{self, Poll}; 10 | 11 | /// Block on the `future`, expecting polling `ring` to drive it forward. 12 | pub fn block_on(future: Fut) -> Fut::Output 13 | where 14 | Fut: IntoFuture, 15 | { 16 | let mut future = future.into_future(); 17 | let mut future = pin!(future); 18 | 19 | loop { 20 | match poll_future(future.as_mut()) { 21 | Poll::Ready(output) => return output, 22 | Poll::Pending => { 23 | // We're just going to poll again. 24 | // This is why this implementation is bad. 25 | } 26 | } 27 | } 28 | } 29 | 30 | /// Block on the `future`, expecting polling `ring` to drive it forward. 31 | pub fn block_on2(future: Fut1, future2: Fut2) 32 | where 33 | Fut1: IntoFuture, 34 | Fut2: IntoFuture, 35 | { 36 | let future1 = pin!(future.into_future()); 37 | let mut future1 = Some(future1); 38 | let future2 = pin!(future2.into_future()); 39 | let mut future2 = Some(future2); 40 | 41 | loop { 42 | if let Some(fut1) = future1.as_mut() { 43 | if poll_future(fut1.as_mut()).is_ready() { 44 | future1 = None; 45 | } 46 | } 47 | if let Some(fut2) = future2.as_mut() { 48 | if poll_future(fut2.as_mut()).is_ready() { 49 | future2 = None; 50 | } 51 | } 52 | 53 | if future1.is_none() && future2.is_none() { 54 | return; 55 | } 56 | } 57 | } 58 | 59 | /// Since we only have a single future we don't need to be awoken. 60 | fn poll_future(fut: Pin<&mut Fut>) -> Poll 61 | where 62 | Fut: Future, 63 | { 64 | let waker = task::Waker::noop(); 65 | let mut ctx = task::Context::from_waker(&waker); 66 | fut.poll(&mut ctx) 67 | } 68 | -------------------------------------------------------------------------------- /http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heph-http" 3 | description = "Heph-HTTP is a HTTP library build on top of Heph." 4 | version = "0.1.0" 5 | publish = false # In development. 6 | authors = ["Thomas de Zeeuw "] 7 | license = "MIT" 8 | documentation = "https://docs.rs/heph-http" 9 | repository = "https://github.com/Thomasdezeeuw/heph/tree/main/http" 10 | readme = "README.md" 11 | keywords = ["http", "async"] 12 | categories = ["asynchronous", "web-programming"] 13 | include = ["/Cargo.toml", "/src/**/*.rs", "/README.md", "/LICENSE"] 14 | edition = "2021" 15 | 16 | [dependencies] 17 | heph = { version = "0.5.0", default-features = false, path = "../" } 18 | heph-rt = { version = "0.5.0", default-features = false, path = "../rt" } 19 | httparse = { version = "1.8.0", default-features = false } 20 | httpdate = { version = "1.0.2", default-features = false } 21 | log = { version = "0.4.17", default-features = false } 22 | itoa = { version = "1.0.6", default-features = false } 23 | 24 | [dev-dependencies] 25 | std-logger = { version = "0.5.3", default-features = false, features = ["log-panic", "nightly"] } 26 | 27 | [dev-dependencies.heph] 28 | path = "../" 29 | features = ["test"] 30 | 31 | 32 | [dev-dependencies.heph-rt] 33 | path = "../rt" 34 | features = ["test"] 35 | -------------------------------------------------------------------------------- /http/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | -------------------------------------------------------------------------------- /http/examples/my_ip.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use std::borrow::Cow; 4 | use std::io; 5 | use std::time::Duration; 6 | 7 | use heph::actor::{self, actor_fn}; 8 | use heph::supervisor::SupervisorStrategy; 9 | use heph_http::body::OneshotBody; 10 | use heph_http::{self as http, server, Header, HeaderName, Headers, Method, StatusCode}; 11 | use heph_rt::net::TcpStream; 12 | use heph_rt::spawn::options::{ActorOptions, Priority}; 13 | use heph_rt::timer::Deadline; 14 | use heph_rt::{Runtime, ThreadLocal}; 15 | use log::{debug, error, info, warn}; 16 | 17 | fn main() -> Result<(), heph_rt::Error> { 18 | // Enable logging. 19 | std_logger::Config::logfmt().init(); 20 | 21 | let actor = actor_fn(http_actor); 22 | let address = "127.0.0.1:7890".parse().unwrap(); 23 | let server = server::setup(address, conn_supervisor, actor, ActorOptions::default()) 24 | .map_err(heph_rt::Error::setup)?; 25 | 26 | let mut runtime = Runtime::setup().use_all_cores().build()?; 27 | runtime.run_on_workers(move |mut runtime_ref| -> io::Result<()> { 28 | let options = ActorOptions::default().with_priority(Priority::LOW); 29 | let server_ref = runtime_ref.spawn_local(server_supervisor, server, (), options); 30 | 31 | runtime_ref.receive_signals(server_ref.try_map()); 32 | Ok(()) 33 | })?; 34 | info!("listening on http://{address}"); 35 | runtime.start() 36 | } 37 | 38 | fn server_supervisor(err: server::Error) -> SupervisorStrategy<()> { 39 | match err { 40 | // When we hit an error accepting a connection we'll drop the old 41 | // server and create a new one. 42 | server::Error::Accept(err) => { 43 | error!("error accepting new connection: {err}"); 44 | SupervisorStrategy::Restart(()) 45 | } 46 | // Async function never return an error creating a new actor. 47 | server::Error::NewActor(_) => unreachable!(), 48 | } 49 | } 50 | 51 | fn conn_supervisor(err: io::Error) -> SupervisorStrategy { 52 | error!("error handling connection: {err}"); 53 | SupervisorStrategy::Stop 54 | } 55 | 56 | const READ_TIMEOUT: Duration = Duration::from_secs(10); 57 | const ALIVE_TIMEOUT: Duration = Duration::from_secs(120); 58 | const WRITE_TIMEOUT: Duration = Duration::from_secs(10); 59 | 60 | async fn http_actor( 61 | ctx: actor::Context, 62 | mut connection: http::Connection, 63 | ) -> io::Result<()> { 64 | let address = connection.peer_addr()?; 65 | info!("accepted connection: source={address}"); 66 | connection.set_nodelay(true)?; 67 | 68 | let mut read_timeout = READ_TIMEOUT; 69 | let mut headers = Headers::EMPTY; 70 | loop { 71 | let fut = Deadline::after( 72 | ctx.runtime_ref().clone(), 73 | read_timeout, 74 | connection.next_request(), 75 | ); 76 | let (code, body, should_close) = match fut.await { 77 | Ok(Some(request)) => { 78 | info!("received request: {request:?}: source={address}"); 79 | if request.path() != "/" { 80 | (StatusCode::NOT_FOUND, "Not found".into(), false) 81 | } else if !matches!(request.method(), Method::Get | Method::Head) { 82 | headers.append(Header::new(HeaderName::ALLOW, b"GET, HEAD")); 83 | let body = "Method not allowed".into(); 84 | (StatusCode::METHOD_NOT_ALLOWED, body, false) 85 | } else if !request.body().is_empty() { 86 | let body = Cow::from("Not expecting a body"); 87 | (StatusCode::PAYLOAD_TOO_LARGE, body, true) 88 | } else { 89 | // This will allocate a new string which isn't the most 90 | // efficient way to do this, but it's the easiest so we'll 91 | // keep this for sake of example. 92 | let body = Cow::from(address.ip().to_string()); 93 | (StatusCode::OK, body, false) 94 | } 95 | } 96 | // No more requests. 97 | Ok(None) => return Ok(()), 98 | Err(err) => { 99 | warn!("error reading request: {err}: source={address}"); 100 | let code = err.proper_status_code(); 101 | let body = Cow::from(format!("Bad request: {err}")); 102 | (code, body, err.should_close()) 103 | } 104 | }; 105 | 106 | if should_close { 107 | headers.append(Header::new(HeaderName::CONNECTION, b"close")); 108 | } 109 | 110 | debug!("sending response: code={code}, body='{body}', source={address}"); 111 | let body = OneshotBody::new(body); 112 | let write_response = connection.respond(code, &headers, body); 113 | Deadline::after(ctx.runtime_ref().clone(), WRITE_TIMEOUT, write_response).await?; 114 | 115 | if should_close { 116 | warn!("closing connection: source={address}"); 117 | return Ok(()); 118 | } 119 | 120 | // Now that we've read a single request we can wait a little for the 121 | // next one so that we can reuse the resources for the next request. 122 | read_timeout = ALIVE_TIMEOUT; 123 | headers.clear(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /http/examples/route.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use std::io; 4 | use std::time::Duration; 5 | 6 | use heph::actor::{self, actor_fn}; 7 | use heph::supervisor::SupervisorStrategy; 8 | use heph_http::body::OneshotBody; 9 | use heph_http::{self as http, route, server, Request, Response}; 10 | use heph_rt::net::TcpStream; 11 | use heph_rt::spawn::options::{ActorOptions, Priority}; 12 | use heph_rt::timer::Deadline; 13 | use heph_rt::{Runtime, ThreadLocal}; 14 | use log::{error, info, warn}; 15 | 16 | fn main() -> Result<(), heph_rt::Error> { 17 | // Enable logging. 18 | std_logger::Config::logfmt().init(); 19 | 20 | let actor = actor_fn(http_actor); 21 | let address = "127.0.0.1:7890".parse().unwrap(); 22 | let server = server::setup(address, conn_supervisor, actor, ActorOptions::default()) 23 | .map_err(heph_rt::Error::setup)?; 24 | 25 | let mut runtime = Runtime::setup().use_all_cores().build()?; 26 | runtime.run_on_workers(move |mut runtime_ref| -> io::Result<()> { 27 | let options = ActorOptions::default().with_priority(Priority::LOW); 28 | let server_ref = runtime_ref.spawn_local(server_supervisor, server, (), options); 29 | 30 | runtime_ref.receive_signals(server_ref.try_map()); 31 | Ok(()) 32 | })?; 33 | info!("listening on http://{address}"); 34 | runtime.start() 35 | } 36 | 37 | fn server_supervisor(err: server::Error) -> SupervisorStrategy<()> { 38 | match err { 39 | // When we hit an error accepting a connection we'll drop the old 40 | // server and create a new one. 41 | server::Error::Accept(err) => { 42 | error!("error accepting new connection: {err}"); 43 | SupervisorStrategy::Restart(()) 44 | } 45 | // Async function never return an error creating a new actor. 46 | server::Error::NewActor(_) => unreachable!(), 47 | } 48 | } 49 | 50 | fn conn_supervisor(err: io::Error) -> SupervisorStrategy { 51 | error!("error handling connection: {err}"); 52 | SupervisorStrategy::Stop 53 | } 54 | 55 | const READ_TIMEOUT: Duration = Duration::from_secs(10); 56 | const ALIVE_TIMEOUT: Duration = Duration::from_secs(120); 57 | const WRITE_TIMEOUT: Duration = Duration::from_secs(10); 58 | 59 | async fn http_actor( 60 | ctx: actor::Context, 61 | mut connection: http::Connection, 62 | ) -> io::Result<()> { 63 | let address = connection.peer_addr()?; 64 | info!("accepted connection: source={address}"); 65 | connection.set_nodelay(true)?; 66 | 67 | let mut read_timeout = READ_TIMEOUT; 68 | loop { 69 | let fut = Deadline::after( 70 | ctx.runtime_ref().clone(), 71 | read_timeout, 72 | connection.next_request(), 73 | ); 74 | 75 | let response = match fut.await { 76 | Ok(Some(request)) => { 77 | info!("received request: {request:?}: source={address}"); 78 | route!(match request { 79 | GET | HEAD "/" => index, 80 | GET | HEAD "/other_page" => other_page, 81 | POST "/post" => post, 82 | _ => not_found, 83 | }) 84 | } 85 | // No more requests. 86 | Ok(None) => return Ok(()), 87 | Err(err) => { 88 | warn!("error reading request: {err}: source={address}"); 89 | err.response().with_body(OneshotBody::new("Bad request")) 90 | } 91 | }; 92 | 93 | let write_response = connection.respond_with(response); 94 | Deadline::after(ctx.runtime_ref().clone(), WRITE_TIMEOUT, write_response).await?; 95 | 96 | // Now that we've read a single request we can wait a little for the 97 | // next one so that we can reuse the resources for the next request. 98 | read_timeout = ALIVE_TIMEOUT; 99 | } 100 | } 101 | 102 | async fn index(_req: Request) -> Response> { 103 | Response::ok().with_body(OneshotBody::new("Index")) 104 | } 105 | 106 | async fn other_page(_req: Request) -> Response> { 107 | Response::ok().with_body(OneshotBody::new("Other page!")) 108 | } 109 | 110 | async fn post(_req: Request) -> Response> { 111 | Response::ok().with_body(OneshotBody::new("POST")) 112 | } 113 | 114 | async fn not_found(_req: Request) -> Response> { 115 | Response::not_found().with_body(OneshotBody::new("Page not found")) 116 | } 117 | -------------------------------------------------------------------------------- /http/src/handler.rs: -------------------------------------------------------------------------------- 1 | //! Module with the [`Handler`] and [`Middleware`] traits. 2 | //! 3 | //! [`Handler`]s are used to process a single request. 4 | //! 5 | //! [`Middleware`] is a `Handler` that wraps another handler to processes the 6 | //! request. The middleware itself can transform the request and/or have side 7 | //! effects such as logging the request. 8 | 9 | use std::future::Future; 10 | 11 | /// Handler is the trait that defines how a single request is handled. 12 | /// 13 | /// It's basically an asynchronous function that accepts a request and 14 | /// returns a response. In fact the function below is a valid `Handler`. 15 | /// 16 | /// ``` 17 | /// # use heph_http::body::EmptyBody; 18 | /// # use heph_http::handler::Handler; 19 | /// # use heph_http::{Request, Response}; 20 | /// # 21 | /// /// Echo the request body back as response. 22 | /// async fn echo_handler(request: Request) -> Response { 23 | /// let (_, request_body) = request.split(); 24 | /// Response::ok().with_body(request_body) 25 | /// } 26 | /// # 27 | /// # fn assert_handler, Req>(_: H) {} 28 | /// # assert_handler::<_, (Request,)>(echo_handler); 29 | /// ``` 30 | /// 31 | /// Note one awkward implementation detail is that the example function has 32 | /// type `Fn(Req) -> Res`, **however** type handler is of type 33 | /// `Handler<(Req,)>`, that is uses a tuple with 1 field as request type. This 34 | /// is required to support multiple arguments in function handlers. 35 | pub trait Handler { 36 | /// Type of response the handler returns. 37 | type Response; 38 | /// [`Future`] returned by `handle`. 39 | type Future: Future; 40 | 41 | /// Returns a [`Future`] that handles a single `request` and returns a 42 | /// response. 43 | fn handle(&self, request: Req) -> Self::Future; 44 | } 45 | 46 | macro_rules! impl_handler_for_fn_tuple { 47 | ( $($T: ident),+ ) => { 48 | impl Handler<($($T,)+)> for F 49 | where 50 | F: Fn($($T,)+) -> Fut, 51 | Fut: Future, 52 | { 53 | type Response = Res; 54 | type Future = Fut; 55 | 56 | #[allow(non_snake_case)] // $T is uppercase. 57 | fn handle(&self, request: ($($T,)+)) -> Self::Future { 58 | let ($($T,)+) = request; 59 | (self)($($T,)+) 60 | } 61 | } 62 | }; 63 | } 64 | 65 | impl_handler_for_fn_tuple!(Req0); 66 | impl_handler_for_fn_tuple!(Req0, Req1); 67 | impl_handler_for_fn_tuple!(Req0, Req1, Req2); 68 | impl_handler_for_fn_tuple!(Req0, Req1, Req2, Req3); 69 | impl_handler_for_fn_tuple!(Req0, Req1, Req2, Req3, Req4); 70 | impl_handler_for_fn_tuple!(Req0, Req1, Req2, Req3, Req4, Req5); 71 | impl_handler_for_fn_tuple!(Req0, Req1, Req2, Req3, Req4, Req5, Req6); 72 | impl_handler_for_fn_tuple!(Req0, Req1, Req2, Req3, Req4, Req5, Req6, Req7); 73 | 74 | /// Middleware wraps other [`Handler`]s to transform the request and/or 75 | /// response. In addition to transforming the requests and/or response they can 76 | /// also have side effects, e.g. logging the request. 77 | /// 78 | /// What middleware allows to do is create composable components that can be run 79 | /// for any route having little or no knowledge of the underlying handler. An 80 | /// example is logging the request, it doesn't have to know anything about how 81 | /// the request is processed, all it sees is the request and the response. 82 | /// 83 | /// Middleware is not in any way special, they are simply [`Handler`]s, but 84 | /// instead of creating a response themselves they call another handler to do 85 | /// that. 86 | pub trait Middleware: Handler { 87 | /// Wrap `handler` to create new middleware. 88 | fn wrap(handler: H) -> Self 89 | where 90 | H: Handler; 91 | } 92 | -------------------------------------------------------------------------------- /http/src/head/version.rs: -------------------------------------------------------------------------------- 1 | //! Version related types. 2 | 3 | use std::fmt; 4 | use std::str::FromStr; 5 | 6 | /// HTTP version. 7 | /// 8 | /// RFC 9110 section 2.5. 9 | #[non_exhaustive] 10 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 11 | pub enum Version { 12 | /// HTTP/1.0. 13 | /// 14 | /// RFC 1945. 15 | Http10, 16 | /// HTTP/1.1. 17 | /// 18 | /// RFC 9112. 19 | Http11, 20 | } 21 | 22 | impl Version { 23 | /// Returns the major version. 24 | pub const fn major(self) -> u8 { 25 | match self { 26 | Version::Http10 | Version::Http11 => 1, 27 | } 28 | } 29 | 30 | /// Returns the minor version. 31 | pub const fn minor(self) -> u8 { 32 | match self { 33 | Version::Http10 => 0, 34 | Version::Http11 => 1, 35 | } 36 | } 37 | 38 | /// Returns the highest minor version with the same major version as `self`. 39 | #[must_use] 40 | pub const fn highest_minor(self) -> Version { 41 | match self { 42 | Version::Http10 | Version::Http11 => Version::Http11, 43 | } 44 | } 45 | 46 | /// Returns the version as string. 47 | pub const fn as_str(self) -> &'static str { 48 | match self { 49 | Version::Http10 => "HTTP/1.0", 50 | Version::Http11 => "HTTP/1.1", 51 | } 52 | } 53 | } 54 | 55 | impl fmt::Display for Version { 56 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 57 | f.write_str(self.as_str()) 58 | } 59 | } 60 | 61 | /// Error returned by the [`FromStr`] implementation for [`Version`]. 62 | #[derive(Copy, Clone, Debug)] 63 | pub struct UnknownVersion; 64 | 65 | impl fmt::Display for UnknownVersion { 66 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 67 | f.write_str("unknown HTTP version") 68 | } 69 | } 70 | 71 | impl FromStr for Version { 72 | type Err = UnknownVersion; 73 | 74 | fn from_str(method: &str) -> Result { 75 | match method { 76 | "HTTP/1.0" => Ok(Version::Http10), 77 | "HTTP/1.1" => Ok(Version::Http11), 78 | _ => Err(UnknownVersion), 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /http/src/parse_headers.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Get the csv file with registered headers from 4 | # . 5 | # Remove the header from the file and run: 6 | # $ cat field-names.csv | ./src/parse_headers.bash 7 | 8 | set -eu 9 | 10 | clean_reference_partial() { 11 | local reference="$1" 12 | 13 | # Remove '[' .. ']'. 14 | if [[ "${reference:0:1}" == "[" ]]; then 15 | if [[ "${reference: -1}" == "]" ]]; then 16 | reference="${reference:1:-1}" 17 | else 18 | reference="${reference:1}" 19 | fi 20 | fi 21 | 22 | # Wrap links in '<' .. '>'. 23 | if [[ "$reference" == http* ]]; then 24 | reference="<$reference>" 25 | fi 26 | 27 | if [[ "${reference:0:3}" == "RFC" ]]; then 28 | # Add a space after 'RFC'. 29 | reference="RFC ${reference:3}" 30 | # Remove double space 31 | # Lowercase "Section" 32 | # Remove ": $section_name" part. 33 | reference=$(echo "$reference" | sed -e 's/Section/section/g' -e 's/:.*//' -e 's/ / /g') 34 | fi 35 | 36 | echo -n "$reference" 37 | } 38 | 39 | clean_reference() { 40 | local reference="$1" 41 | local partial="${2:-false}" 42 | 43 | # Remove '"' .. '"'. 44 | if [[ "${reference:0:1}" == "\"" ]]; then 45 | reference="${reference:1:-1}" 46 | fi 47 | 48 | # Some references are actually multiple references inside '[' .. ']'. 49 | # Clean them one by one. 50 | IFS="]" read -ra refs <<< "$reference" 51 | reference="" 52 | for ref in "${refs[@]}"; do 53 | reference+="$(clean_reference_partial "$ref")" 54 | reference+=', ' 55 | done 56 | 57 | echo "${reference:0:-2}" # Remove last ', '. 58 | } 59 | 60 | # Collect all known header name by length in `header_names`. 61 | declare -a header_names 62 | while IFS=$',' read -r -a values; do 63 | # The reference column may contain commas, so we have to use an array to 64 | # extract the columns we're interested in. Specifically in the case of 65 | # the reference column we need to join multiple columns. 66 | 67 | name="${values[0]}" 68 | 69 | # Only include "permanent" headers, ignoring deprecated and obsoleted 70 | # headers. 71 | status="${values[2]}" 72 | if [[ "permanent" != "$status" ]]; then 73 | continue; 74 | fi 75 | 76 | # Stitch together the reference. 77 | reference='' 78 | if [[ "${#values[@]}" == 5 ]]; then 79 | reference="${values[3]}" 80 | else 81 | unset values[-1] # Remove the comment. 82 | reference=$(echo "${values[@]:3}" | xargs) 83 | fi 84 | 85 | reference="$(clean_reference "$reference")" 86 | const_name="${name^^}" # To uppercase. 87 | const_name="${const_name//\-/\_}" # '-' -> '_'. 88 | const_name="${const_name// /\_}" # ' ' -> '_'. 89 | const_value="${name,,}" # To lowercase. 90 | value_length="${#const_value}" # Value length. 91 | docs="#[doc = \"$name.\\\\n\\\\n$reference.\"]" 92 | 93 | # NOTE: can't assign arrays/list to array values, so we have to use a 94 | # string and parse that below (which is a little error prone). 95 | header_names[$value_length]+="$docs|$const_name|$const_value 96 | " 97 | done 98 | 99 | # Add non-standard headers. 100 | # X-Request-ID. 101 | header_names[12]+="#[doc = \"X-Request-ID.\"]|X_REQUEST_ID|x-request-id 102 | " 103 | 104 | for value_length in "${!header_names[@]}"; do 105 | values="${header_names[$value_length]}" 106 | echo " $value_length: [" 107 | while IFS=$'|' read -r docs const_name const_value; do 108 | printf " $docs\n ($const_name, \"$const_value\"),\n" 109 | done <<< "${values:0:-1}" # Remove last new line. 110 | echo " ],"; 111 | done 112 | -------------------------------------------------------------------------------- /http/src/request.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | use crate::body::EmptyBody; 5 | use crate::head::RequestHead; 6 | use crate::{Headers, Method, Version}; 7 | 8 | /// HTTP request. 9 | pub struct Request { 10 | head: RequestHead, 11 | body: B, 12 | } 13 | 14 | impl Request { 15 | /// Create a new request. 16 | pub const fn new( 17 | method: Method, 18 | path: String, 19 | version: Version, 20 | headers: Headers, 21 | body: B, 22 | ) -> Request { 23 | Request::from_head(RequestHead::new(method, path, version, headers), body) 24 | } 25 | 26 | /// Create a new request from a [`RequestHead`] and a `body`. 27 | pub const fn from_head(head: RequestHead, body: B) -> Request { 28 | Request { head, body } 29 | } 30 | 31 | /// The request body. 32 | pub const fn body(&self) -> &B { 33 | &self.body 34 | } 35 | 36 | /// Mutable access to the request body. 37 | pub const fn body_mut(&mut self) -> &mut B { 38 | &mut self.body 39 | } 40 | 41 | /// Map the body from type `B` to `U` by calling `map`. 42 | pub fn map_body(self, map: F) -> Request 43 | where 44 | F: FnOnce(B) -> U, 45 | { 46 | Request { 47 | head: self.head, 48 | body: map(self.body), 49 | } 50 | } 51 | 52 | /// Split the request in the head and the body. 53 | // TODO: mark as constant once destructors (that this doesn't have) can be 54 | // run in constant functions. 55 | pub fn split(self) -> (RequestHead, B) { 56 | (self.head, self.body) 57 | } 58 | } 59 | 60 | /// Builder pattern for `Request`. 61 | /// 62 | /// These methods create a new `Request` using HTTP/1.1, no headers and an empty 63 | /// body. Use [`Request::with_body`] to add a body to the request. 64 | impl Request { 65 | /// Create a GET request. 66 | pub const fn get(path: String) -> Request { 67 | Request::build_new(Method::Get, path) 68 | } 69 | 70 | /// Create a HEAD request. 71 | pub const fn head(path: String) -> Request { 72 | Request::build_new(Method::Head, path) 73 | } 74 | 75 | /// Create a POST request. 76 | pub const fn post(path: String) -> Request { 77 | Request::build_new(Method::Post, path) 78 | } 79 | 80 | /// Create a PUT request. 81 | pub const fn put(path: String) -> Request { 82 | Request::build_new(Method::Put, path) 83 | } 84 | 85 | /// Create a DELETE request. 86 | pub const fn delete(path: String) -> Request { 87 | Request::build_new(Method::Delete, path) 88 | } 89 | 90 | /// Simple version of [`Request::new`] used by the build functions. 91 | const fn build_new(method: Method, path: String) -> Request { 92 | Request::new(method, path, Version::Http11, Headers::EMPTY, EmptyBody) 93 | } 94 | 95 | /// Add a body to the request. 96 | pub fn with_body(self, body: B) -> Request { 97 | Request { 98 | head: self.head, 99 | body, 100 | } 101 | } 102 | } 103 | 104 | impl Deref for Request { 105 | type Target = RequestHead; 106 | 107 | fn deref(&self) -> &Self::Target { 108 | &self.head 109 | } 110 | } 111 | 112 | impl DerefMut for Request { 113 | fn deref_mut(&mut self) -> &mut Self::Target { 114 | &mut self.head 115 | } 116 | } 117 | 118 | #[allow(clippy::missing_fields_in_debug)] 119 | impl fmt::Debug for Request { 120 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 121 | f.debug_struct("Request").field("head", &self.head).finish() 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /http/src/str.rs: -------------------------------------------------------------------------------- 1 | //! Module with [`Str`] types. 2 | //! 3 | //! The [`Str`] type supports borrowed and owned (heap allocated) strings in the 4 | //! same type. 5 | 6 | use std::marker::PhantomData; 7 | use std::panic::{RefUnwindSafe, UnwindSafe}; 8 | use std::{fmt, slice, str}; 9 | 10 | /// Marker on [`Str::length`] to indicate the string is heap allocated. 11 | const MARK_OWNED: usize = 1 << (usize::BITS - 1); 12 | 13 | /// `Str` is combination of `str` and `String`. 14 | /// 15 | /// It supports both borrowed and owned (heap allocated) strings in the same 16 | /// type. 17 | pub(crate) struct Str<'a> { 18 | /// Pointer must always be valid and point to atleast `length` bytes that 19 | /// are valid UTF-8. 20 | ptr: *const u8, 21 | /// Number of bytes that are valid UTF-8. 22 | /// 23 | /// This is marked, use [`Str::len()`] to ge the correct length. 24 | length: usize, 25 | _phantom: PhantomData<&'a str>, 26 | } 27 | 28 | impl Str<'static> { 29 | /// Create an owned `Str` from `string`. 30 | /// 31 | /// # Notes 32 | /// 33 | /// This "leaks" the unused capacity of `string`. 34 | pub(crate) fn from_string(string: String) -> Str<'static> { 35 | let bytes = string.into_bytes().leak(); 36 | Str { 37 | ptr: bytes.as_ptr(), 38 | length: bytes.len() | MARK_OWNED, 39 | _phantom: PhantomData, 40 | } 41 | } 42 | } 43 | 44 | impl<'a> Str<'a> { 45 | /// Create a borrowed `Str` from `string`. 46 | pub(crate) const fn from_str(string: &'a str) -> Str<'a> { 47 | Str { 48 | ptr: string.as_ptr(), 49 | length: string.len(), 50 | _phantom: PhantomData, 51 | } 52 | } 53 | 54 | /// Borrow the string for a shorter lifetime. 55 | pub(crate) const fn borrow<'b>(&self) -> Str<'b> 56 | where 57 | 'a: 'b, 58 | { 59 | Str { 60 | ptr: self.ptr, 61 | length: self.len(), 62 | _phantom: PhantomData, 63 | } 64 | } 65 | 66 | /// Returns the `Str` as `str`. 67 | const fn as_str(&self) -> &'a str { 68 | // SAFETY: safe based on the docs of `ptr` and `length`. 69 | unsafe { str::from_utf8_unchecked(slice::from_raw_parts(self.ptr, self.len())) } 70 | } 71 | 72 | /// Returns the length of the string. 73 | const fn len(&self) -> usize { 74 | self.length & !MARK_OWNED 75 | } 76 | 77 | /// Returns `true` if `self` is heap allocated. 78 | pub(crate) const fn is_heap_allocated(&self) -> bool { 79 | self.length & MARK_OWNED != 0 80 | } 81 | } 82 | 83 | impl<'a> AsRef for Str<'a> { 84 | fn as_ref(&self) -> &str { 85 | self.as_str() 86 | } 87 | } 88 | 89 | impl<'a> Clone for Str<'a> { 90 | fn clone(&self) -> Str<'a> { 91 | Str::from_str(self.as_str()) 92 | } 93 | } 94 | 95 | impl<'a> Eq for Str<'a> {} 96 | 97 | impl<'a> PartialEq> for Str<'a> { 98 | fn eq(&self, other: &Str<'a>) -> bool { 99 | self.as_ref() == other.as_ref() 100 | } 101 | } 102 | 103 | impl<'a> PartialEq for Str<'a> { 104 | fn eq(&self, other: &str) -> bool { 105 | self.as_ref() == other 106 | } 107 | } 108 | 109 | impl<'a> PartialEq<&'_ str> for Str<'a> { 110 | fn eq(&self, other: &&str) -> bool { 111 | self.eq(*other) 112 | } 113 | } 114 | 115 | // SAFETY: Since this is just a `String`/`&str` it's safe to send accross 116 | // threads. 117 | unsafe impl<'a> Send for Str<'a> {} 118 | unsafe impl<'a> Sync for Str<'a> {} 119 | 120 | impl<'a> UnwindSafe for Str<'a> {} 121 | impl<'a> RefUnwindSafe for Str<'a> {} 122 | 123 | impl<'a> Drop for Str<'a> { 124 | fn drop(&mut self) { 125 | if self.is_heap_allocated() { 126 | let len = self.len(); 127 | unsafe { drop(Vec::::from_raw_parts(self.ptr.cast_mut(), len, len)) } 128 | } 129 | } 130 | } 131 | 132 | impl<'a> fmt::Debug for Str<'a> { 133 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 134 | f.write_str(self.as_ref()) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /http/tests/functional.rs: -------------------------------------------------------------------------------- 1 | //! Functional tests. 2 | 3 | #![feature(async_iterator, never_type)] 4 | 5 | #[track_caller] 6 | fn assert_size(expected: usize) { 7 | assert_eq!(size_of::(), expected); 8 | } 9 | 10 | fn assert_send() {} 11 | 12 | fn assert_sync() {} 13 | 14 | #[path = "functional"] // rustfmt can't find the files. 15 | mod functional { 16 | mod body; 17 | mod client; 18 | mod from_header_value; 19 | mod header; 20 | mod message; 21 | mod method; 22 | mod route; 23 | mod server; 24 | mod status_code; 25 | mod transform; 26 | mod version; 27 | } 28 | -------------------------------------------------------------------------------- /http/tests/functional/body.rs: -------------------------------------------------------------------------------- 1 | use std::async_iter::AsyncIterator; 2 | use std::mem::replace; 3 | use std::pin::Pin; 4 | use std::task::{self, Poll}; 5 | 6 | use heph_http::body::{Body, BodyLength, ChunkedBody, EmptyBody, OneshotBody, StreamingBody}; 7 | 8 | use crate::{assert_send, assert_size, assert_sync}; 9 | 10 | const BODY0: &[u8] = b""; 11 | const BODY1: &[u8] = b"Hello world!"; 12 | 13 | struct EmptyStream; 14 | 15 | impl AsyncIterator for EmptyStream { 16 | type Item = &'static [u8]; 17 | 18 | fn poll_next(self: Pin<&mut Self>, _: &mut task::Context<'_>) -> Poll> { 19 | Poll::Ready(None) 20 | } 21 | } 22 | 23 | struct SingleStream<'a>(&'a [u8]); 24 | 25 | impl<'a> AsyncIterator for SingleStream<'a> { 26 | type Item = &'a [u8]; 27 | 28 | fn poll_next(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> Poll> { 29 | if !self.0.is_empty() { 30 | Poll::Ready(Some(replace(&mut self.0, &[]))) 31 | } else { 32 | Poll::Ready(None) 33 | } 34 | } 35 | } 36 | 37 | #[test] 38 | fn size() { 39 | assert_size::>(0); 40 | assert_size::(0); 41 | assert_size::>(16); 42 | assert_size::>(8); 43 | } 44 | 45 | #[test] 46 | fn send() { 47 | assert_send::>(); 48 | assert_send::(); 49 | assert_send::>(); 50 | assert_send::>(); 51 | } 52 | 53 | #[test] 54 | fn sync() { 55 | assert_sync::>(); 56 | assert_sync::(); 57 | assert_sync::>(); 58 | assert_sync::>(); 59 | } 60 | 61 | #[test] 62 | fn empty_body() { 63 | assert_eq!(EmptyBody.length(), BodyLength::Known(0)); 64 | } 65 | 66 | #[test] 67 | fn oneshot_body() { 68 | assert_eq!( 69 | OneshotBody::new(BODY0).length(), 70 | BodyLength::Known(BODY0.len()) 71 | ); 72 | assert_eq!( 73 | OneshotBody::new(BODY1).length(), 74 | BodyLength::Known(BODY1.len()) 75 | ); 76 | assert_eq!(OneshotBody::new("abc").length(), BodyLength::Known(3)); 77 | } 78 | 79 | #[test] 80 | fn streaming_body() { 81 | assert_eq!( 82 | StreamingBody::new(0, EmptyStream).length(), 83 | BodyLength::Known(0) 84 | ); 85 | assert_eq!( 86 | StreamingBody::new(0, SingleStream(BODY0)).length(), 87 | BodyLength::Known(0) 88 | ); 89 | assert_eq!( 90 | // NOTE: wrong length! 91 | StreamingBody::new(0, SingleStream(BODY1)).length(), 92 | BodyLength::Known(0) 93 | ); 94 | } 95 | 96 | #[test] 97 | fn chunked_body() { 98 | assert_eq!(ChunkedBody::new(EmptyStream).length(), BodyLength::Chunked); 99 | assert_eq!( 100 | ChunkedBody::new(SingleStream(BODY0)).length(), 101 | BodyLength::Chunked 102 | ); 103 | assert_eq!( 104 | // NOTE: wrong length! 105 | ChunkedBody::new(SingleStream(BODY1)).length(), 106 | BodyLength::Chunked 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /http/tests/functional/from_header_value.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::time::SystemTime; 3 | 4 | use heph_http::head::header::{FromHeaderValue, ParseIntError, ParseTimeError}; 5 | 6 | #[test] 7 | fn str() { 8 | test_parse(b"123", "123"); 9 | test_parse(b"abc", "abc"); 10 | } 11 | 12 | #[test] 13 | fn str_not_utf8() { 14 | test_parse_fail::<&str>(&[0, 255]); 15 | } 16 | 17 | #[test] 18 | fn integers() { 19 | test_parse(b"123", 123u8); 20 | test_parse(b"123", 123u16); 21 | test_parse(b"123", 123u32); 22 | test_parse(b"123", 123u64); 23 | test_parse(b"123", 123usize); 24 | 25 | test_parse(b"255", u8::MAX); 26 | test_parse(b"65535", u16::MAX); 27 | test_parse(b"4294967295", u32::MAX); 28 | test_parse(b"18446744073709551615", u64::MAX); 29 | test_parse(b"18446744073709551615", usize::MAX); 30 | } 31 | 32 | #[test] 33 | fn integers_overflow() { 34 | // In multiplication. 35 | test_parse_fail::(b"300"); 36 | test_parse_fail::(b"70000"); 37 | test_parse_fail::(b"5000000000"); 38 | test_parse_fail::(b"20000000000000000000"); 39 | test_parse_fail::(b"20000000000000000000"); 40 | 41 | // In addition. 42 | test_parse_fail::(b"257"); 43 | test_parse_fail::(b"65537"); 44 | test_parse_fail::(b"4294967297"); 45 | test_parse_fail::(b"18446744073709551616"); 46 | test_parse_fail::(b"18446744073709551616"); 47 | } 48 | 49 | #[test] 50 | fn empty_integers() { 51 | test_parse_fail::(b""); 52 | test_parse_fail::(b""); 53 | test_parse_fail::(b""); 54 | test_parse_fail::(b""); 55 | test_parse_fail::(b""); 56 | } 57 | 58 | #[test] 59 | fn invalid_integers() { 60 | test_parse_fail::(b"abc"); 61 | test_parse_fail::(b"abc"); 62 | test_parse_fail::(b"abc"); 63 | test_parse_fail::(b"abc"); 64 | test_parse_fail::(b"abc"); 65 | 66 | test_parse_fail::(b"2a"); 67 | test_parse_fail::(b"2a"); 68 | test_parse_fail::(b"2a"); 69 | test_parse_fail::(b"2a"); 70 | test_parse_fail::(b"2a"); 71 | } 72 | 73 | #[test] 74 | fn system_time() { 75 | test_parse(b"Thu, 01 Jan 1970 00:00:00 GMT", SystemTime::UNIX_EPOCH); // IMF-fixdate. 76 | test_parse(b"Thursday, 01-Jan-70 00:00:00 GMT", SystemTime::UNIX_EPOCH); // RFC 850. 77 | test_parse(b"Thu Jan 1 00:00:00 1970", SystemTime::UNIX_EPOCH); // ANSI C’s `asctime`. 78 | } 79 | 80 | #[test] 81 | fn invalid_system_time() { 82 | test_parse_fail::(b"\xa0\xa1"); // Invalid UTF-8. 83 | test_parse_fail::(b"ABC, 01 Jan 1970 00:00:00 GMT"); // Invalid format. 84 | } 85 | 86 | #[track_caller] 87 | fn test_parse<'a, T>(value: &'a [u8], expected: T) 88 | where 89 | T: FromHeaderValue<'a> + fmt::Debug + PartialEq, 90 | >::Err: fmt::Debug, 91 | { 92 | assert_eq!(T::from_bytes(value).unwrap(), expected); 93 | } 94 | 95 | #[track_caller] 96 | fn test_parse_fail<'a, T>(value: &'a [u8]) 97 | where 98 | T: FromHeaderValue<'a> + fmt::Debug + PartialEq, 99 | >::Err: fmt::Debug, 100 | { 101 | assert!(T::from_bytes(value).is_err()); 102 | } 103 | 104 | #[test] 105 | fn parse_int_error_fmt_display() { 106 | assert_eq!(ParseIntError.to_string(), "invalid integer"); 107 | } 108 | 109 | #[test] 110 | fn parse_time_error_fmt_display() { 111 | assert_eq!(ParseTimeError.to_string(), "invalid time"); 112 | } 113 | -------------------------------------------------------------------------------- /http/tests/functional/method.rs: -------------------------------------------------------------------------------- 1 | use heph_http::head::method::UnknownMethod; 2 | use heph_http::Method::{self, *}; 3 | 4 | use crate::assert_size; 5 | 6 | #[test] 7 | fn size() { 8 | assert_size::(1); 9 | } 10 | 11 | #[test] 12 | fn is_safe() { 13 | let safe = &[Get, Head, Options, Trace]; 14 | for method in safe { 15 | assert!(method.is_safe()); 16 | } 17 | let not_safe = &[Post, Put, Delete, Connect, Patch]; 18 | for method in not_safe { 19 | assert!(!method.is_safe()); 20 | } 21 | } 22 | 23 | #[test] 24 | fn is_idempotent() { 25 | let idempotent = &[Get, Head, Put, Delete, Options, Trace]; 26 | for method in idempotent { 27 | assert!(method.is_idempotent()); 28 | } 29 | let not_idempotent = &[Post, Connect, Patch]; 30 | for method in not_idempotent { 31 | assert!(!method.is_idempotent()); 32 | } 33 | } 34 | 35 | #[test] 36 | fn expects_body() { 37 | let no_body = &[Head]; 38 | for method in no_body { 39 | assert!(!method.expects_body()); 40 | } 41 | let has_body = &[Get, Post, Put, Delete, Connect, Options, Trace, Patch]; 42 | for method in has_body { 43 | assert!(method.expects_body()); 44 | } 45 | } 46 | 47 | #[test] 48 | fn as_str() { 49 | let tests = &[ 50 | (Get, "GET"), 51 | (Head, "HEAD"), 52 | (Post, "POST"), 53 | (Put, "PUT"), 54 | (Delete, "DELETE"), 55 | (Connect, "CONNECT"), 56 | (Options, "OPTIONS"), 57 | (Trace, "TRACE"), 58 | (Patch, "PATCH"), 59 | ]; 60 | for (method, expected) in tests { 61 | assert_eq!(method.as_str(), *expected); 62 | } 63 | } 64 | 65 | #[test] 66 | fn from_str() { 67 | let tests = &[ 68 | (Get, "GET"), 69 | (Head, "HEAD"), 70 | (Post, "POST"), 71 | (Put, "PUT"), 72 | (Delete, "DELETE"), 73 | (Connect, "CONNECT"), 74 | (Options, "OPTIONS"), 75 | (Trace, "TRACE"), 76 | (Patch, "PATCH"), 77 | ]; 78 | for (expected, input) in tests { 79 | let got: Method = input.parse().unwrap(); 80 | assert_eq!(got, *expected); 81 | // Must be case-insensitive. 82 | let got: Method = input.to_lowercase().parse().unwrap(); 83 | assert_eq!(got, *expected); 84 | } 85 | } 86 | 87 | #[test] 88 | fn from_invalid_str() { 89 | let tests = &["abc", "abcd", "abcde", "abcdef", "abcdefg", "abcdefgh"]; 90 | for input in tests { 91 | assert!(input.parse::().is_err()); 92 | } 93 | } 94 | 95 | #[test] 96 | fn cmp_with_string() { 97 | let tests = &[ 98 | (Get, "GET", true), 99 | (Head, "HEAD", true), 100 | (Post, "POST", true), 101 | (Put, "PUT", true), 102 | (Delete, "DELETE", true), 103 | (Connect, "CONNECT", true), 104 | (Options, "OPTIONS", true), 105 | (Trace, "TRACE", true), 106 | (Patch, "PATCH", true), 107 | // Case insensitive. 108 | (Get, "get", true), 109 | (Head, "Head", true), 110 | (Post, "posT", true), 111 | (Put, "put", true), 112 | (Delete, "delete", true), 113 | (Connect, "ConNECT", true), 114 | (Options, "OptionS", true), 115 | (Trace, "Trace", true), 116 | (Patch, "patcH", true), 117 | (Head, "POST", false), 118 | (Post, "head", false), 119 | ]; 120 | for (status_code, str, expected) in tests { 121 | assert_eq!(status_code.eq(*str), *expected); 122 | } 123 | } 124 | 125 | #[test] 126 | fn fmt_display() { 127 | let tests = &[ 128 | (Get, "GET"), 129 | (Head, "HEAD"), 130 | (Post, "POST"), 131 | (Put, "PUT"), 132 | (Delete, "DELETE"), 133 | (Connect, "CONNECT"), 134 | (Options, "OPTIONS"), 135 | (Trace, "TRACE"), 136 | (Patch, "PATCH"), 137 | ]; 138 | for (method, expected) in tests { 139 | assert_eq!(*method.to_string(), **expected); 140 | } 141 | } 142 | 143 | #[test] 144 | fn unknown_method_fmt_display() { 145 | assert_eq!(UnknownMethod.to_string(), "unknown HTTP method"); 146 | } 147 | -------------------------------------------------------------------------------- /http/tests/functional/transform.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the transform module. 2 | 3 | use heph_http::body::OneshotBody; 4 | use heph_http::handler::{Handler, Middleware}; 5 | use heph_http::transform::{Body, Cloned, Path, TransformMiddleware}; 6 | use heph_http::{Header, HeaderName, Headers, Method, Request, Response, StatusCode, Version}; 7 | use heph_rt::test; 8 | 9 | const REQ_BODY: &'static str = "test_body"; 10 | const OK_BODY: &'static str = "good"; 11 | const BAD_BODY: &'static str = "bad"; 12 | const HOST: &'static str = "localhost"; 13 | 14 | type TestBody = OneshotBody<&'static str>; 15 | 16 | struct Error(&'static str); 17 | 18 | impl From for Response { 19 | fn from(err: Error) -> Response { 20 | Response::bad_request().with_body(TestBody::new(err.0)) 21 | } 22 | } 23 | 24 | struct Text(&'static str); 25 | 26 | impl From for Response { 27 | fn from(txt: Text) -> Response { 28 | Response::ok().with_body(TestBody::new(txt.0)) 29 | } 30 | } 31 | 32 | #[test] 33 | fn transform_middleware() { 34 | // Test the type conversion in `TransformMiddleware`. 35 | 36 | async fn handler( 37 | method: Method, 38 | cloned_path: Cloned, 39 | path: Path, 40 | version: Version, 41 | cloned_headers: Cloned, 42 | headers: Headers, 43 | cloned_body: Cloned>, 44 | body: Body, 45 | ) -> Result { 46 | assert_eq!(method, Method::Get); 47 | assert_eq!((cloned_path.0).0, path.0); 48 | assert_eq!(version, Version::Http11); 49 | let cloned_headers = cloned_headers.0; 50 | assert_eq!(cloned_headers.len(), 1); 51 | assert_eq!(headers.len(), 1); 52 | assert_eq!( 53 | cloned_headers 54 | .get_value::<&str>(&HeaderName::HOST) 55 | .unwrap() 56 | .unwrap(), 57 | HOST, 58 | ); 59 | assert_eq!( 60 | headers 61 | .get_value::<&str>(&HeaderName::HOST) 62 | .unwrap() 63 | .unwrap(), 64 | HOST, 65 | ); 66 | assert_eq!((cloned_body.0).0.into_inner(), REQ_BODY); 67 | assert_eq!(body.0.into_inner(), REQ_BODY); 68 | 69 | if path.0 == "/ok" { 70 | Ok(Text(OK_BODY)) 71 | } else { 72 | Err(Error(BAD_BODY)) 73 | } 74 | } 75 | 76 | let middleware = TransformMiddleware::wrap(handler); 77 | 78 | let tests = [ 79 | ("/ok", StatusCode::OK, OK_BODY), 80 | ("/error", StatusCode::BAD_REQUEST, BAD_BODY), 81 | ]; 82 | for (path, expected_status, expected_body) in tests { 83 | let request = Request::new( 84 | Method::Get, 85 | path.into(), 86 | Version::Http11, 87 | { 88 | let mut headers = Headers::EMPTY; 89 | headers.append(Header::new(HeaderName::HOST, HOST.as_bytes())); 90 | headers 91 | }, 92 | TestBody::new(REQ_BODY), 93 | ); 94 | let response: Response = test::block_on_future(middleware.handle(request)); 95 | assert_eq!(response.status(), expected_status); 96 | assert_eq!(response.body().into_inner(), expected_body); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /http/tests/functional/version.rs: -------------------------------------------------------------------------------- 1 | use heph_http::head::version::UnknownVersion; 2 | use heph_http::Version::{self, *}; 3 | 4 | use crate::assert_size; 5 | 6 | #[test] 7 | fn size() { 8 | assert_size::(1); 9 | } 10 | 11 | #[test] 12 | fn major() { 13 | let tests = &[(Http10, 1), (Http11, 1)]; 14 | for (version, expected) in tests { 15 | assert_eq!(version.major(), *expected); 16 | } 17 | } 18 | 19 | #[test] 20 | fn minor() { 21 | let tests = &[(Http10, 0), (Http11, 1)]; 22 | for (version, expected) in tests { 23 | assert_eq!(version.minor(), *expected); 24 | } 25 | } 26 | 27 | #[test] 28 | fn highest_minor() { 29 | let tests = &[(Http10, Http11), (Http11, Http11)]; 30 | for (version, expected) in tests { 31 | assert_eq!(version.highest_minor(), *expected); 32 | } 33 | } 34 | 35 | #[test] 36 | fn from_str() { 37 | let tests = &[(Http10, "HTTP/1.0"), (Http11, "HTTP/1.1")]; 38 | for (expected, input) in tests { 39 | let got: Version = input.parse().unwrap(); 40 | assert_eq!(got, *expected); 41 | // NOTE: version (unlike most other types) is matched case-sensitive. 42 | } 43 | assert!("HTTP/1.2".parse::().is_err()); 44 | } 45 | 46 | #[test] 47 | fn as_str() { 48 | let tests = &[(Http10, "HTTP/1.0"), (Http11, "HTTP/1.1")]; 49 | for (method, expected) in tests { 50 | assert_eq!(method.as_str(), *expected); 51 | } 52 | } 53 | 54 | #[test] 55 | fn fmt_display() { 56 | let tests = &[(Http10, "HTTP/1.0"), (Http11, "HTTP/1.1")]; 57 | for (method, expected) in tests { 58 | assert_eq!(*method.to_string(), **expected); 59 | } 60 | } 61 | 62 | #[test] 63 | fn unknown_version_fmt_display() { 64 | assert_eq!(UnknownVersion.to_string(), "unknown HTTP version"); 65 | } 66 | -------------------------------------------------------------------------------- /inbox/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.2 2 | 3 | ## Changed 4 | 5 | * Compiling with a stable compiler is now supported! MSRV is now 1.63. 6 | (https://github.com/Thomasdezeeuw/inbox/commit/63bf02d1c26e7cc6eeed34cd6a0947e5510986c6, 7 | https://github.com/Thomasdezeeuw/inbox/commit/69ace0dd39f689a154e23390fa517cb947bc81bc, 8 | https://github.com/Thomasdezeeuw/inbox/commit/019bfeaaf3faf82d9bfd700311ae23ca1c587af6). 9 | * Replaced `parking_lot` dependency with the lock found in `std::sync`, which 10 | received a lot of improvements in release Rust releases 11 | (https://github.com/Thomasdezeeuw/inbox/commit/68253068ee127e0a15bcd1022cfc422c28cb4ebb). 12 | * Add Receiver::(try_)peek 13 | (https://github.com/Thomasdezeeuw/inbox/commit/33d3a4d63f89f42012e3e3d24992eabf250b29ce). 14 | 15 | # 0.2.2 16 | 17 | ## Added 18 | 19 | * Add limited arbitary sized channels in the form of `new` and `Manager::new` 20 | (https://github.com/Thomasdezeeuw/inbox/commit/d3056eec8b68456113965fdf20489d1c9b2b3c27, 21 | https://github.com/Thomasdezeeuw/inbox/commit/0489ae7321b697bb5f7fa43cf8359af05b9e29fe, 22 | https://github.com/Thomasdezeeuw/inbox/commit/d597c11cb7f995e49d2d63ad58c8db88ff5c3a13). 23 | * Makes `SendValue` safe to leak 24 | (https://github.com/Thomasdezeeuw/inbox/commit/3306857a54e08eff1681f93ec82af28069f4a8d5). 25 | 26 | # 0.2.1 27 | 28 | ## Fixes 29 | 30 | * Data race when dropping `SendValue` 31 | (https://github.com/Thomasdezeeuw/inbox/commit/2f62c1efbeda079f9eae050d04d42462b9723677). 32 | * Data race when dropping `Join` 33 | (https://github.com/Thomasdezeeuw/inbox/commit/798771781ffbef24bbbd969e699db848a90f50ea). 34 | 35 | # 0.2.0 36 | 37 | ## Changed 38 | 39 | * **BREAKING**: `oneshot::Receiver::try_recv` now only returns the message, not a 40 | reset receiver. 41 | (https://github.com/Thomasdezeeuw/inbox/commit/2dd49a96e55e97e66a6634eab92cb81764c8d0cd). 42 | 43 | ## Fixes 44 | 45 | * `oneshot::Receiver::try_reset` no only resets the if the Sender is fully 46 | dropped, not still in-progress of dropping. 47 | (https://github.com/Thomasdezeeuw/inbox/commit/37dd066cdcfa56599c9fcbd06b83ce39d449aeca). 48 | * Fixes a data-race in the oneshot channel, where a reset receiver (returned by 49 | `oneshot::Receiver::try_reset`) would attempt to free the channel twice. 50 | (https://github.com/Thomasdezeeuw/inbox/commit/2dd49a96e55e97e66a6634eab92cb81764c8d0cd). 51 | 52 | # 0.1.3 53 | 54 | ## Added 55 | 56 | * `Sender::join`, waits until the other side is disconnected 57 | (https://github.com/Thomasdezeeuw/inbox/commit/31db1d9587e307600fd7e075c1c1f0ad27c438ea). 58 | 59 | # 0.1.2 60 | 61 | ## Added 62 | 63 | * `oneshot::Receiver::register_waker`, allows a `task::Waker` to be registered 64 | (https://github.com/Thomasdezeeuw/inbox/commit/3a711032d789e4652f4ee4d193e0ecaebc1226f4). 65 | 66 | # 0.1.1 67 | 68 | ## Added 69 | 70 | * Support of ThreadSanitizer, by using atomic loads instead of fences when the 71 | thread sanitizer is enabled 72 | (https://github.com/Thomasdezeeuw/inbox/commit/dc5202e0d621856403a125dcef5bf33e9477d2c4). 73 | 74 | ## Changed 75 | 76 | * Optimised `oneshot::Receiver::try_reset` by dropping the message in place on 77 | the heap. 78 | (https://github.com/Thomasdezeeuw/inbox/commit/5a5b5421869e152c18436d56470dd4c4619317e8). 79 | 80 | ## Fixes 81 | 82 | * Data race when dropping `oneshot::Sender`/`Receiver` 83 | (https://github.com/Thomasdezeeuw/inbox/commit/96ac5a09e2161aebf3a63ff099272828ba961492). 84 | * Possible data race in oneshot channel 85 | (https://github.com/Thomasdezeeuw/inbox/commit/d40f7db267aabffc446ff6e9860f03f0fc6aa92d). 86 | 87 | To catch these kind of problems we now run the address, leak, memory and thread 88 | sanitizers on the CI, in addition to Miri (which we already ran) 89 | (https://github.com/Thomasdezeeuw/inbox/commit/b45ce7ebac8b2926b07a3670276d5548a0426e8a). 90 | 91 | # 0.1.0 92 | 93 | Initial release. 94 | -------------------------------------------------------------------------------- /inbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heph-inbox" 3 | description = """ 4 | Bounded capacity channel designed to be used as inbox for actors. Also supports 5 | one shot channels. 6 | """ 7 | version = "0.2.3" 8 | authors = ["Thomas de Zeeuw "] 9 | license = "MIT" 10 | documentation = "https://docs.rs/heph-inbox" 11 | repository = "https://github.com/Thomasdezeeuw/heph" 12 | readme = "README.md" 13 | keywords = ["inbox", "channel", "actor", "async"] 14 | categories = ["asynchronous"] 15 | include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"] 16 | edition = "2021" 17 | -------------------------------------------------------------------------------- /inbox/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | -------------------------------------------------------------------------------- /inbox/README.md: -------------------------------------------------------------------------------- 1 | # Inbox 2 | 3 | Bounded capacity channel. 4 | 5 | The channel is a multi-producer, single-consumer (MPSC) bounded queue. It is 6 | designed to be used as inbox for actors, following the [actor model]. 7 | 8 | [actor model]: https://en.wikipedia.org/wiki/Actor_model 9 | 10 | # Examples 11 | 12 | Simple creation of a channel and sending a message over it. 13 | 14 | ```rust 15 | use std::thread; 16 | 17 | use heph_inbox::RecvError; 18 | 19 | // Create a new small channel. 20 | let (mut sender, mut receiver) = heph_inbox::new_small(); 21 | 22 | let sender_handle = thread::spawn(move || { 23 | if let Err(err) = sender.try_send("Hello world!".to_owned()) { 24 | panic!("Failed to send value: {}", err); 25 | } 26 | }); 27 | 28 | let receiver_handle = thread::spawn(move || { 29 | // NOTE: this is just an example don't actually use a loop like this, it 30 | // will waste CPU cycles when the channel is empty! 31 | loop { 32 | match receiver.try_recv() { 33 | Ok(value) => println!("Got a value: {}", value), 34 | Err(RecvError::Empty) => continue, 35 | Err(RecvError::Disconnected) => break, 36 | } 37 | } 38 | }); 39 | 40 | sender_handle.join().unwrap(); 41 | receiver_handle.join().unwrap(); 42 | ``` 43 | 44 | ## License 45 | 46 | Licensed under the MIT license ([LICENSE](LICENSE) or 47 | https://opensource.org/licenses/MIT). 48 | 49 | ### Contribution 50 | 51 | Unless you explicitly state otherwise, any contribution intentionally submitted 52 | for inclusion in the work by you shall be licensed as above, without any 53 | additional terms or conditions. 54 | -------------------------------------------------------------------------------- /inbox/src/waker.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | use std::sync::RwLock; 3 | use std::task; 4 | 5 | /// Registration of a [`task::Waker`]. 6 | pub(crate) struct WakerRegistration { 7 | /// This will be `true` if this waker needs to be awoken, `false` otherwise. 8 | needs_wakeup: AtomicBool, 9 | /// The actual waking mechanism. 10 | waker: RwLock>, 11 | } 12 | 13 | impl WakerRegistration { 14 | /// Create a new empty registration. 15 | pub(crate) const fn new() -> WakerRegistration { 16 | WakerRegistration { 17 | needs_wakeup: AtomicBool::new(false), 18 | waker: RwLock::new(None), 19 | } 20 | } 21 | 22 | /// Register `waker`. 23 | pub(crate) fn register(&self, waker: &task::Waker) -> bool { 24 | let stored_waker = self.waker.read().unwrap(); 25 | if let Some(stored_waker) = &*stored_waker { 26 | if stored_waker.will_wake(waker) { 27 | self.needs_wakeup.store(true, Ordering::Release); 28 | return false; 29 | } 30 | } 31 | drop(stored_waker); // Unlock read lock. 32 | 33 | let mut stored_waker = self.waker.write().unwrap(); 34 | // Since another thread could have changed the waker since we dropped 35 | // the read lock and we got the write lock, we have to check the waker 36 | // again. 37 | if let Some(stored_waker) = &*stored_waker { 38 | if stored_waker.will_wake(waker) { 39 | self.needs_wakeup.store(true, Ordering::Release); 40 | return false; 41 | } 42 | } 43 | *stored_waker = Some(waker.clone()); 44 | drop(stored_waker); 45 | 46 | self.needs_wakeup.store(true, Ordering::Release); 47 | true 48 | } 49 | 50 | /// Wake the waker registered, if required. 51 | pub(crate) fn wake(&self) { 52 | if !self.needs_wakeup.load(Ordering::Acquire) { 53 | // Receiver doesn't need a wake-up. 54 | return; 55 | } 56 | 57 | // Mark that we've woken and after actually do the waking. 58 | if self.needs_wakeup.swap(false, Ordering::AcqRel) { 59 | if let Some(waker) = &*self.waker.read().unwrap() { 60 | waker.wake_by_ref(); 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /inbox/tests/regression.rs: -------------------------------------------------------------------------------- 1 | //! Regression tests. 2 | 3 | use heph_inbox::oneshot::{self, new_oneshot}; 4 | use heph_inbox::{self as inbox, new}; 5 | 6 | #[macro_use] 7 | mod util; 8 | 9 | #[test] 10 | fn cyclic_drop_dependency_with_oneshot_channel() { 11 | with_all_capacities!(|capacity| { 12 | let (sender, receiver) = new(capacity); 13 | let (one_send, mut one_recv) = new_oneshot::(); 14 | 15 | // Put `oneshot::Sender` in the channel. 16 | sender.try_send(one_send).unwrap(); 17 | 18 | // Dropping the receiver should also drop the `oneshot::Sender` we send 19 | // above. 20 | drop(receiver); 21 | // This needs to work in case we would call `oneshot::Receiver::recv` here. 22 | // If we didn't empty the channel on dropping the `Receiver` this would 23 | // return a `NoValue` error. 24 | assert_eq!( 25 | one_recv.try_recv().unwrap_err(), 26 | oneshot::RecvError::Disconnected 27 | ); 28 | 29 | drop(one_recv); 30 | // This needs to live until now. 31 | drop(sender); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /remote/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heph-remote" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | default = ["json"] 8 | # Enables serialisation using JSON. 9 | json = ["serde_json"] 10 | 11 | [dependencies] 12 | heph = { version = "0.5.0", path = "../", default-features = false } 13 | heph-rt = { version = "0.5.0", path = "../rt", default-features = false } 14 | log = { version = "0.4.17", default-features = false } 15 | serde = { version = "1.0.130", default-features = false } 16 | getrandom = { version = "0.2.3", default-features = false } 17 | 18 | # Optional dependencies, enabled by features. 19 | # Required by the `json` feature. 20 | serde_json = { version = "1.0.72", default-features = false, features = ["std"], optional = true } 21 | 22 | [dev-dependencies.heph-rt] 23 | path = "../rt" 24 | features = ["test"] 25 | -------------------------------------------------------------------------------- /remote/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | 3 | test: 4 | cargo hack test --feature-powerset $(TEST_OPTS) 5 | -------------------------------------------------------------------------------- /remote/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Remote messaging for Heph. 2 | 3 | #![feature(never_type, impl_trait_in_assoc_type)] 4 | #![warn( 5 | anonymous_parameters, 6 | bare_trait_objects, 7 | missing_debug_implementations, 8 | missing_docs, 9 | rust_2018_idioms, 10 | trivial_numeric_casts, 11 | unused_extern_crates, 12 | unused_import_braces, 13 | unused_qualifications, 14 | unused_results, 15 | variant_size_differences 16 | )] 17 | 18 | pub mod net_relay; 19 | -------------------------------------------------------------------------------- /remote/src/net_relay/routers.rs: -------------------------------------------------------------------------------- 1 | //! Various [`Route`]rs. 2 | //! 3 | //! The following routes are provided: 4 | //! * [`Relay`] relays all messages to a single actor. 5 | //! * [`RelayGroup`] relays all messages to a group of actors. 6 | //! * [`Drop`] drops all messages. 7 | 8 | use std::fmt; 9 | use std::future::Future; 10 | use std::future::{ready, Ready}; 11 | use std::net::SocketAddr; 12 | 13 | use heph::actor_ref::{ActorGroup, ActorRef, SendError, SendValue}; 14 | 15 | use crate::net_relay::Route; 16 | 17 | #[rustfmt::skip] 18 | impl Route for F 19 | where 20 | F: FnMut(M, SocketAddr) -> Fut, 21 | Fut: Future>, 22 | E: fmt::Display, 23 | { 24 | type Route<'a> = Fut 25 | where Self: 'a; 26 | type Error = E; 27 | 28 | fn route<'a>(&'a mut self, msg: M, source: SocketAddr) -> Self::Route<'a> { 29 | (self)(msg, source) 30 | } 31 | } 32 | 33 | /// [`Route`] implementation that routes all messages to a single 34 | /// actor. 35 | #[derive(Debug)] 36 | pub struct Relay { 37 | actor_ref: ActorRef, 38 | } 39 | 40 | impl Relay { 41 | /// Relay all remote messages to the `actor_ref`. 42 | pub const fn to(actor_ref: ActorRef) -> Relay { 43 | Relay { actor_ref } 44 | } 45 | } 46 | 47 | impl Clone for Relay { 48 | fn clone(&self) -> Relay { 49 | Relay { 50 | actor_ref: self.actor_ref.clone(), 51 | } 52 | } 53 | } 54 | 55 | #[rustfmt::skip] 56 | impl Route for Relay 57 | where 58 | M: 'static + Unpin, 59 | { 60 | type Error = SendError; 61 | type Route<'a> = SendValue<'a, M> 62 | where Self: 'a; 63 | 64 | fn route<'a>(&'a mut self, msg: M, _: SocketAddr) -> Self::Route<'a> { 65 | self.actor_ref.send(msg) 66 | } 67 | } 68 | 69 | /// [`Route`] implementation that routes all messages to a group of 70 | /// actors. 71 | #[derive(Debug)] 72 | pub struct RelayGroup { 73 | actor_group: ActorGroup, 74 | delivery: Delivery, 75 | } 76 | 77 | /// The kind of delivery to use. 78 | #[derive(Copy, Clone, Debug)] 79 | pub enum Delivery { 80 | /// Delivery a copy of the message to all actors in the group. 81 | ToAll, 82 | /// Delivery the message to one of the actors. 83 | ToOne, 84 | } 85 | 86 | impl RelayGroup { 87 | /// Relay all remote messages to the `actor_group`. 88 | pub const fn to(actor_group: ActorGroup, delivery: Delivery) -> RelayGroup { 89 | RelayGroup { 90 | actor_group, 91 | delivery, 92 | } 93 | } 94 | } 95 | 96 | impl Clone for RelayGroup { 97 | fn clone(&self) -> RelayGroup { 98 | RelayGroup { 99 | actor_group: self.actor_group.clone(), 100 | delivery: self.delivery, 101 | } 102 | } 103 | } 104 | 105 | #[rustfmt::skip] 106 | impl Route for RelayGroup 107 | where 108 | M: Clone + Unpin + 'static, 109 | { 110 | type Error = !; 111 | type Route<'a> = Ready> 112 | where Self: 'a; 113 | 114 | fn route<'a>(&'a mut self, msg: M, _: SocketAddr) -> Self::Route<'a> { 115 | _ = match self.delivery { 116 | Delivery::ToAll => self.actor_group.try_send_to_all(msg), 117 | Delivery::ToOne => self.actor_group.try_send_to_one(msg), 118 | }; 119 | ready(Ok(())) 120 | } 121 | } 122 | 123 | /// Router that drops all messages. 124 | #[derive(Copy, Clone, Debug)] 125 | pub struct Drop; 126 | 127 | #[rustfmt::skip] 128 | impl Route for Drop { 129 | type Error = !; 130 | type Route<'a> = Ready> 131 | where Self: 'a; 132 | 133 | fn route<'a>(&'a mut self, _: M, _: SocketAddr) -> Self::Route<'a> { 134 | ready(Ok(())) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /rt/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.4.0 2 | 3 | Initial public release. The changes below are compared to Heph v0.3.1 (last 4 | release to include the runtime). 5 | 6 | ## Added 7 | 8 | * The `access` module, holding the `Access` trait and the `ThreadLocal`, 9 | `ThreadSafe` and `Sync` implementations 10 | (https://github.com/Thomasdezeeuw/heph/commit/e6514071364d4ddb5b1dc9b6f1df19ee4f771eda). 11 | * The `pipe` module was added to support Unix pipes 12 | (https://github.com/Thomasdezeeuw/heph/commit/0c4f1ab3eaf08bea1d65776528bfd6114c9f8374). 13 | * The `systemd` module was added to better interface with systemd 14 | (https://github.com/Thomasdezeeuw/heph/commit/a97398777e9f08b9b5467ecd9aea4311cc885432 and more). 15 | * `test::block_on` 16 | (https://github.com/Thomasdezeeuw/heph/commit/8f8426db6a1ae84d9ee7ea8dd6ae58ef1b6bb89f). 17 | * `test::spawn_local_future` 18 | (https://github.com/Thomasdezeeuw/heph/commit/8f8426db6a1ae84d9ee7ea8dd6ae58ef1b6bb89f). 19 | 20 | ## Changed 21 | 22 | * The `heph::rt` module is now effectively the root of the `heph-rt` crate. 23 | * `Setup`, `ActorOptions`, `SyncActorOptions` and `FutureOptions` are marked as 24 | `must_use` 25 | (https://github.com/Thomasdezeeuw/heph/commit/c4d634f3e90691f099e9d1487721279e0d490f0f). 26 | * `heph_rt::Config::default` was renamed to `new` to now overshadow a possible 27 | `Default` implementation 28 | (https://github.com/Thomasdezeeuw/heph/commit/d617d6018515e732df838252f6f46273f95b0d0a). 29 | * Panics are now caught when running actors 30 | (https://github.com/Thomasdezeeuw/heph/commit/ad5c4150aa4f194e644c2408f2372d1174f3b3b0). 31 | * Panics are now caught when running futures 32 | (https://github.com/Thomasdezeeuw/heph/commit/91774018075e899dda7887754c6f5851f58fb44d). 33 | * Restarted actors are now scheduled instead of ran to prevent an infinite 34 | restart loop 35 | (https://github.com/Thomasdezeeuw/heph/commit/6e182f5b030bef6c3cbd82023873fd9b35656d2f). 36 | 37 | ## Fixes 38 | 39 | * When one worker thread stops all others are awoken to ensure all worker 40 | threads shutdown down 41 | (https://github.com/Thomasdezeeuw/heph/commit/adba587bdfc6386fcda091114c7ca8dc59d7c614). 42 | -------------------------------------------------------------------------------- /rt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heph-rt" 3 | description = "Heph-rt is a speciailised runtime for Heph's actor." 4 | version = "0.5.0" 5 | publish = false # In development. 6 | authors = ["Thomas de Zeeuw "] 7 | license = "MIT" 8 | documentation = "https://docs.rs/heph-rt" 9 | repository = "https://github.com/Thomasdezeeuw/heph" 10 | readme = "README.md" 11 | keywords = ["actor", "runtime", "async", "functions"] 12 | categories = ["asynchronous"] 13 | include = ["/Cargo.toml", "/src/**/*.rs", "/README.md", "/LICENSE"] 14 | edition = "2021" 15 | 16 | [features] 17 | default = [] 18 | 19 | # Feature that enables the `test` module. 20 | test = ["heph/test"] 21 | 22 | [dependencies] 23 | a10 = { version = "0.2.0", default-features = false, features = ["nightly"] } 24 | heph = { version = "0.5.0", path = "../", default-features = false } 25 | heph-inbox = { version = "0.2.3", path = "../inbox", default-features = false } 26 | log = { version = "0.4.21", default-features = false, features = ["kv_std"] } 27 | crossbeam-channel = { version = "0.5.0", default-features = false, features = ["std"] } 28 | libc = { version = "0.2.96", default-features = false } 29 | socket2 = { version = "0.5.2", default-features = false, features = ["all"] } 30 | 31 | [dev-dependencies] 32 | getrandom = { version = "0.2.2", default-features = false, features = ["std"] } 33 | std-logger = { version = "0.5.3", default-features = false, features = ["nightly"] } 34 | 35 | [[test]] 36 | name = "functional" 37 | required-features = ["test"] 38 | 39 | [[test]] 40 | name = "process_signals" 41 | # Require full control over the spawned threads. 42 | harness = false 43 | required-features = ["test"] 44 | 45 | [[test]] 46 | name = "regression" 47 | required-features = ["test"] 48 | -------------------------------------------------------------------------------- /rt/Makefile: -------------------------------------------------------------------------------- 1 | include ../Makefile.include 2 | -------------------------------------------------------------------------------- /rt/README.md: -------------------------------------------------------------------------------- 1 | # Heph-rt 2 | 3 | Specialised runtime for Heph actors. 4 | 5 | See the main [Heph README] for more information. 6 | 7 | [Heph README]: https://github.com/Thomasdezeeuw/heph/blob/main/README.md 8 | -------------------------------------------------------------------------------- /rt/examples/1_hello_world.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use heph::actor::{self, actor_fn}; 4 | use heph::supervisor::NoSupervisor; 5 | use heph_rt::spawn::ActorOptions; 6 | use heph_rt::{self as rt, Runtime, RuntimeRef, ThreadLocal}; 7 | 8 | fn main() -> Result<(), rt::Error> { 9 | // Enable logging. 10 | std_logger::Config::logfmt().init(); 11 | 12 | // We create a new runtime. Add a setup function, which adds our greeter 13 | // actor. And finally we start it. 14 | let mut runtime = Runtime::setup().build()?; 15 | runtime.run_on_workers(add_greeter_actor)?; 16 | runtime.start() 17 | } 18 | 19 | /// The is the setup function used in the runtime. 20 | fn add_greeter_actor(mut runtime_ref: RuntimeRef) -> Result<(), !> { 21 | // Spawn our `greeter_actor` onto the runtime. 22 | // All actors need supervision, however our actor doesn't return an error 23 | // (it uses `!`, the never type, as error), because of this we'll use the 24 | // `NoSupervisor`, which is a supervisor that does nothing and can't be 25 | // called. 26 | // Along with the supervisor we'll also supply the argument to start the 27 | // actor, in our case this is `()` since our actor doesn't accept any 28 | // arguments. 29 | // We'll use the default actor options here, other examples expand on the 30 | // options available. 31 | let actor = actor_fn(greeter_actor); 32 | let actor_ref = runtime_ref.spawn_local(NoSupervisor, actor, (), ActorOptions::default()); 33 | 34 | // Now we can send our actor a message using its `actor_ref`. 35 | actor_ref.try_send("World").unwrap(); 36 | Ok(()) 37 | } 38 | 39 | /// Our greeter actor. 40 | /// 41 | /// We'll receive a single message and print it. 42 | async fn greeter_actor(mut ctx: actor::Context<&'static str, ThreadLocal>) { 43 | // All actors have an actor context, which give the actor access to, among 44 | // other things, its inbox from which it can receive a message. 45 | while let Ok(name) = ctx.receive_next().await { 46 | println!("Hello {name}"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rt/examples/2_my_ip.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use std::io; 4 | 5 | use heph::actor::{self, actor_fn, Actor, NewActor}; 6 | use heph::supervisor::{Supervisor, SupervisorStrategy}; 7 | use heph_rt::net::{tcp, TcpStream}; 8 | use heph_rt::spawn::options::{ActorOptions, Priority}; 9 | use heph_rt::{self as rt, Runtime, ThreadLocal}; 10 | use log::{error, info}; 11 | 12 | fn main() -> Result<(), rt::Error> { 13 | // For this example we'll enable logging, this give us a bit more insight 14 | // into the runtime. By default it only logs informational or more severe 15 | // messages, the environment variable `LOG_LEVEL` can be set to change this. 16 | // For example enabling logging of trace severity message can be done by 17 | // setting `LOG_LEVEL=trace`. 18 | std_logger::Config::logfmt().init(); 19 | 20 | // Create our TCP server. This server will create a new actor for each 21 | // incoming TCP connection. As always, actors needs supervision, this is 22 | // done by `conn_supervisor` in this example. And as each actor will need to 23 | // be added to the runtime it needs the `ActorOptions` to do that, we'll use 24 | // the defaults options here. 25 | let actor = actor_fn(conn_actor); 26 | let address = "127.0.0.1:7890".parse().unwrap(); 27 | let server = tcp::server::setup(address, conn_supervisor, actor, ActorOptions::default()) 28 | .map_err(rt::Error::setup)?; 29 | 30 | // Just like in examples 1 and 2 we'll create our runtime and run our setup 31 | // function. But for this example we'll create a worker thread per available 32 | // CPU core using `use_all_cores`. 33 | let mut runtime = Runtime::setup().use_all_cores().build()?; 34 | runtime.run_on_workers(move |mut runtime_ref| -> io::Result<()> { 35 | // As the TCP server is just another actor we need to spawn it like any 36 | // other actor. And again actors needs supervision, thus we provide 37 | // `ServerSupervisor` as supervisor. 38 | // We'll give our server a low priority to prioritise handling of 39 | // ongoing requests over accepting new requests possibly overloading the 40 | // system. 41 | let options = ActorOptions::default().with_priority(Priority::LOW); 42 | let server_ref = runtime_ref.spawn_local(ServerSupervisor, server, (), options); 43 | 44 | // The server can handle the interrupt, terminate and quit signals, 45 | // so it will perform a clean shutdown for us. 46 | runtime_ref.receive_signals(server_ref.try_map()); 47 | Ok(()) 48 | })?; 49 | info!("listening on {address}"); 50 | runtime.start() 51 | } 52 | 53 | /// Our supervisor for the TCP server. 54 | #[derive(Copy, Clone, Debug)] 55 | struct ServerSupervisor; 56 | 57 | impl Supervisor for ServerSupervisor 58 | where 59 | NA: NewActor, 60 | NA::Actor: Actor>, 61 | { 62 | fn decide(&mut self, err: tcp::server::Error) -> SupervisorStrategy<()> { 63 | match err { 64 | // When we hit an error accepting a connection we'll drop the old 65 | // listener and create a new one. 66 | tcp::server::Error::Accept(err) => { 67 | error!("error accepting new connection: {err}"); 68 | SupervisorStrategy::Restart(()) 69 | } 70 | // Async function never return an error creating a new actor. 71 | tcp::server::Error::NewActor(err) => err, 72 | } 73 | } 74 | 75 | fn decide_on_restart_error(&mut self, err: !) -> SupervisorStrategy<()> { 76 | err 77 | } 78 | 79 | fn second_restart_error(&mut self, err: !) { 80 | err 81 | } 82 | } 83 | 84 | /// Our supervisor for the connection actor. 85 | /// 86 | /// Since we can't create a new TCP connection all this supervisor does is log 87 | /// the error and signal to stop the actor. 88 | fn conn_supervisor(err: io::Error) -> SupervisorStrategy { 89 | error!("error handling connection: {err}"); 90 | SupervisorStrategy::Stop 91 | } 92 | 93 | /// Our connection actor. 94 | /// 95 | /// This actor will not receive any message and thus uses `!` (the never type) 96 | /// as message type. 97 | async fn conn_actor(_: actor::Context, stream: TcpStream) -> io::Result<()> { 98 | let address = stream.peer_addr()?; 99 | info!(address:% = address; "accepted connection"); 100 | 101 | // This will allocate a new string which isn't the most efficient way to do 102 | // this, but it's the easiest so we'll keep this for sake of example. 103 | let ip = address.ip().to_string(); 104 | 105 | // Next we'll write the IP address to the connection. 106 | stream.send_all(ip).await?; 107 | Ok(()) 108 | } 109 | -------------------------------------------------------------------------------- /rt/examples/3_rpc.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use std::fmt; 4 | 5 | use heph::actor::{self, actor_fn}; 6 | use heph::actor_ref::{ActorRef, RpcMessage}; 7 | use heph::supervisor::NoSupervisor; 8 | use heph_rt::spawn::ActorOptions; 9 | use heph_rt::{self as rt, Runtime, RuntimeRef, ThreadLocal}; 10 | 11 | fn main() -> Result<(), rt::Error> { 12 | // Setup is much like example 1, see that example for more information. 13 | std_logger::Config::logfmt().init(); 14 | let mut runtime = Runtime::setup().build()?; 15 | runtime.run_on_workers(add_rpc_actor)?; 16 | runtime.start() 17 | } 18 | 19 | fn add_rpc_actor(mut runtime_ref: RuntimeRef) -> Result<(), !> { 20 | // See example 1 for information on how to spawn actors. 21 | let pong_actor = actor_fn(pong_actor); 22 | let actor_ref = runtime_ref.spawn_local(NoSupervisor, pong_actor, (), ActorOptions::default()); 23 | 24 | let ping_actor = actor_fn(ping_actor); 25 | runtime_ref.spawn_local(NoSupervisor, ping_actor, actor_ref, ActorOptions::default()); 26 | 27 | Ok(()) 28 | } 29 | 30 | async fn ping_actor(_: actor::Context, actor_ref: ActorRef) { 31 | // Make a Remote Procedure Call (RPC) and await the response. 32 | match actor_ref.rpc(Ping).await { 33 | Ok(response) => println!("Got a RPC response: {response}"), 34 | Err(err) => eprintln!("RPC request error: {err}"), 35 | } 36 | } 37 | 38 | // Message type to support the ping-pong RPC. 39 | type PongMessage = RpcMessage; 40 | 41 | async fn pong_actor(mut ctx: actor::Context) { 42 | // Await a message, same as all other messages. 43 | while let Ok(msg) = ctx.receive_next().await { 44 | // Next we respond to the request. 45 | let res = msg 46 | .handle(|request| async move { 47 | println!("Got a RPC request: {request}"); 48 | // Return a response. 49 | Pong 50 | }) 51 | .await; 52 | 53 | if let Err(err) = res { 54 | eprintln!("failed to respond to RPC: {err}"); 55 | } 56 | } 57 | } 58 | 59 | struct Ping; 60 | 61 | impl fmt::Display for Ping { 62 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 63 | f.write_str("Ping") 64 | } 65 | } 66 | 67 | struct Pong; 68 | 69 | impl fmt::Display for Pong { 70 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 71 | f.write_str("Pong") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rt/examples/4_sync_actor.rs: -------------------------------------------------------------------------------- 1 | use heph::actor::actor_fn; 2 | use heph::supervisor::NoSupervisor; 3 | use heph::sync; 4 | use heph_rt::spawn::SyncActorOptions; 5 | use heph_rt::{self as rt, Runtime}; 6 | 7 | fn main() -> Result<(), rt::Error> { 8 | // Enable logging. 9 | std_logger::Config::logfmt().init(); 10 | 11 | // Spawning synchronous actor works slightly differently the spawning 12 | // regular (asynchronous) actors. Mainly, synchronous actors need to be 13 | // spawned before the runtime is started. 14 | let mut runtime = Runtime::new()?; 15 | 16 | // Spawn a new synchronous actor, returning an actor reference to it. 17 | let actor = actor_fn(actor); 18 | // Options used to spawn the synchronous actor. 19 | // Here we'll set the name of 20 | // the thread that runs the actor. 21 | let options = SyncActorOptions::default().with_thread_name("My actor".to_owned()); 22 | let actor_ref = runtime.spawn_sync_actor(NoSupervisor, actor, "Bye", options)?; 23 | 24 | // Just like with any actor reference we can send the actor a message. 25 | actor_ref.try_send("Hello world".to_string()).unwrap(); 26 | // We need to drop the reference here to ensure the actor stops. 27 | // The actor contains a `while` loop receiving messages (see the `actor` 28 | // function), that only stops iterating once all actor reference to it are 29 | // dropped or waits otherwise. If didn't manually dropped the reference here 30 | // it would be dropped only after `runtime.start` returned below, when it 31 | // goes out of scope. However that will never happen as the `actor` will 32 | // wait until its dropped. 33 | drop(actor_ref); 34 | 35 | // And now we start the runtime. 36 | runtime.start() 37 | } 38 | 39 | fn actor(mut ctx: sync::Context, exit_msg: &'static str) { 40 | while let Ok(msg) = ctx.receive_next() { 41 | println!("Got a message: {msg}"); 42 | } 43 | println!("{exit_msg}"); 44 | } 45 | -------------------------------------------------------------------------------- /rt/examples/6_process_signals.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use heph::actor::{self, actor_fn}; 4 | use heph::supervisor::NoSupervisor; 5 | use heph::sync; 6 | use heph_rt::spawn::{ActorOptions, SyncActorOptions}; 7 | use heph_rt::{self as rt, Runtime, RuntimeRef, Signal, ThreadLocal, ThreadSafe}; 8 | 9 | fn main() -> Result<(), rt::Error> { 10 | // Enable logging. 11 | std_logger::Config::logfmt().init(); 12 | 13 | // Signal handling is support for all actor and its as simple as receiving 14 | // `Signal` messages from the runtime and handling them accordingly in the 15 | // actors. 16 | 17 | let mut runtime = Runtime::setup().build()?; 18 | 19 | // Thread-local actors. 20 | runtime.run_on_workers(|mut runtime_ref: RuntimeRef| -> Result<(), !> { 21 | // Spawn our actor. 22 | let local_actor = actor_fn(local_actor); 23 | let actor_ref = 24 | runtime_ref.spawn_local(NoSupervisor, local_actor, (), ActorOptions::default()); 25 | actor_ref.try_send("Hello thread local actor").unwrap(); 26 | // Register our actor reference to receive process signals. 27 | runtime_ref.receive_signals(actor_ref.try_map()); 28 | 29 | Ok(()) 30 | })?; 31 | 32 | // Synchronous actors (following the same structure as thread-local actors). 33 | let sync_actor = actor_fn(sync_actor); 34 | let actor_ref = 35 | runtime.spawn_sync_actor(NoSupervisor, sync_actor, (), SyncActorOptions::default())?; 36 | actor_ref.try_send("Hello sync actor").unwrap(); 37 | runtime.receive_signals(actor_ref.try_map()); 38 | 39 | // Thread-safe actor (following the same structure as thread-local actors). 40 | let thread_safe_actor = actor_fn(thread_safe_actor); 41 | let actor_ref = runtime.spawn(NoSupervisor, thread_safe_actor, (), ActorOptions::default()); 42 | actor_ref.try_send("Hello thread safe actor").unwrap(); 43 | runtime.receive_signals(actor_ref.try_map()); 44 | 45 | runtime.start() 46 | } 47 | 48 | enum Message { 49 | /// Print the string. 50 | Print(String), 51 | /// Shutdown. 52 | Terminate, 53 | } 54 | 55 | impl From<&'static str> for Message { 56 | fn from(msg: &'static str) -> Message { 57 | Message::Print(msg.to_owned()) 58 | } 59 | } 60 | 61 | impl TryFrom for Message { 62 | type Error = (); 63 | 64 | fn try_from(signal: Signal) -> Result { 65 | match signal { 66 | Signal::Interrupt | Signal::Terminate | Signal::Quit => Ok(Message::Terminate), 67 | _ => Err(()), 68 | } 69 | } 70 | } 71 | 72 | fn sync_actor(mut ctx: sync::Context) { 73 | while let Ok(msg) = ctx.receive_next() { 74 | match msg { 75 | Message::Print(msg) => println!("Got a message: {msg}"), 76 | Message::Terminate => break, 77 | } 78 | } 79 | 80 | println!("shutting down the synchronous actor"); 81 | } 82 | 83 | async fn thread_safe_actor(mut ctx: actor::Context) { 84 | while let Ok(msg) = ctx.receive_next().await { 85 | match msg { 86 | Message::Print(msg) => println!("Got a message: {msg}"), 87 | Message::Terminate => break, 88 | } 89 | } 90 | 91 | println!("shutting down the thread safe actor"); 92 | } 93 | 94 | async fn local_actor(mut ctx: actor::Context) { 95 | while let Ok(msg) = ctx.receive_next().await { 96 | match msg { 97 | Message::Print(msg) => println!("Got a message: {msg}"), 98 | Message::Terminate => break, 99 | } 100 | } 101 | 102 | println!("shutting down the thread local actor"); 103 | } 104 | -------------------------------------------------------------------------------- /rt/examples/7_restart_supervisor.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use std::thread::sleep; 4 | use std::time::Duration; 5 | 6 | use heph::actor::{self, actor_fn}; 7 | use heph::{restart_supervisor, sync}; 8 | use heph_rt::spawn::{ActorOptions, SyncActorOptions}; 9 | use heph_rt::{self as rt, Runtime, RuntimeRef, ThreadLocal}; 10 | 11 | fn main() -> Result<(), rt::Error> { 12 | // Enable logging. 13 | std_logger::Config::logfmt().init(); 14 | 15 | let mut runtime = Runtime::setup().build()?; 16 | 17 | let sync_actor = actor_fn(sync_print_actor); 18 | let arg = "Hello world!".to_owned(); 19 | let supervisor = PrintSupervisor::new(arg.clone()); 20 | let options = SyncActorOptions::default(); 21 | runtime.spawn_sync_actor(supervisor, sync_actor, arg, options)?; 22 | 23 | // NOTE: this is only here to make the test pass. 24 | sleep(Duration::from_millis(100)); 25 | 26 | runtime.run_on_workers(|mut runtime_ref: RuntimeRef| -> Result<(), !> { 27 | let print_actor = actor_fn(print_actor); 28 | let options = ActorOptions::default(); 29 | let arg = "Hello world!".to_owned(); 30 | let supervisor = PrintSupervisor::new(arg.clone()); 31 | runtime_ref.spawn_local(supervisor, print_actor, arg, options); 32 | Ok(()) 33 | })?; 34 | 35 | runtime.start() 36 | } 37 | 38 | // Create a restart supervisor for the [`print_actor`]. 39 | restart_supervisor!( 40 | PrintSupervisor, 41 | String, 42 | 5, 43 | Duration::from_secs(5), 44 | ": actor message '{}'", 45 | args 46 | ); 47 | 48 | /// A very bad printing actor. 49 | async fn print_actor(_: actor::Context<(), ThreadLocal>, msg: String) -> Result<(), String> { 50 | Err(format!("can't print message '{msg}'")) 51 | } 52 | 53 | /// A very bad synchronous printing actor. 54 | fn sync_print_actor(_: sync::Context, msg: String) -> Result<(), String> { 55 | Err(format!("can't print message synchronously '{msg}'")) 56 | } 57 | -------------------------------------------------------------------------------- /rt/examples/8_tracing.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use std::thread::sleep; 4 | use std::time::Duration; 5 | 6 | use heph::actor::{self, actor_fn}; 7 | use heph::actor_ref::{ActorRef, SendError}; 8 | use heph::supervisor::{NoSupervisor, SupervisorStrategy}; 9 | use heph::sync; 10 | use heph_rt::spawn::{ActorOptions, SyncActorOptions}; 11 | use heph_rt::trace::Trace; 12 | use heph_rt::{self as rt, Runtime, RuntimeRef}; 13 | use log::warn; 14 | 15 | fn main() -> Result<(), rt::Error> { 16 | // Enable logging. 17 | std_logger::Config::logfmt().init(); 18 | 19 | let mut runtime_setup = Runtime::setup(); 20 | runtime_setup.enable_tracing("heph_tracing_example.bin.log")?; 21 | let mut runtime = runtime_setup.build()?; 22 | 23 | // Spawn our printing actor. 24 | // NOTE: to enable tracing for this sync actor it must be spawned after 25 | // enabling tracing. 26 | let options = SyncActorOptions::default().with_thread_name("Printer".to_owned()); 27 | let print_actor = actor_fn(print_actor); 28 | let actor_ref = runtime.spawn_sync_actor(NoSupervisor, print_actor, (), options)?; 29 | 30 | runtime.run_on_workers(|runtime_ref| setup(runtime_ref, actor_ref))?; 31 | 32 | runtime.start() 33 | } 34 | 35 | const CHAIN_SIZE: usize = 5; 36 | 37 | /// Setup function will start a chain of `relay_actors`, just to create some 38 | /// activity for the trace. 39 | fn setup(mut runtime_ref: RuntimeRef, actor_ref: ActorRef<&'static str>) -> Result<(), !> { 40 | // Create a chain of relay actors that will relay messages to the next 41 | // actor. 42 | let mut next_actor_ref = actor_ref; 43 | for _ in 0..CHAIN_SIZE { 44 | let relay_actor = actor_fn(relay_actor); 45 | next_actor_ref = runtime_ref.spawn_local( 46 | |err| { 47 | warn!("error running actor: {err}"); 48 | SupervisorStrategy::Stop 49 | }, 50 | relay_actor, 51 | next_actor_ref, 52 | ActorOptions::default(), 53 | ); 54 | } 55 | 56 | // The first actor in the chain will be a thread-safe actor. 57 | next_actor_ref = runtime_ref.spawn( 58 | |err| { 59 | warn!("error running actor: {err}"); 60 | SupervisorStrategy::Stop 61 | }, 62 | actor_fn(relay_actor), 63 | next_actor_ref, 64 | ActorOptions::default(), 65 | ); 66 | 67 | // Send the messages down the chain of actors. 68 | let msgs = &[ 69 | "First message: Hello World!", 70 | "Hello Mars!", 71 | "End of transmission.", 72 | ]; 73 | for msg in msgs { 74 | next_actor_ref.try_send(*msg).unwrap(); 75 | } 76 | 77 | Ok(()) 78 | } 79 | 80 | /// Actor that relays all messages it receives to actor behind `relay`. 81 | async fn relay_actor( 82 | mut ctx: actor::Context<&'static str, RT>, 83 | relay: ActorRef<&'static str>, 84 | ) -> Result<(), SendError> 85 | where 86 | RT: rt::Access, 87 | { 88 | let mut receive_timing = ctx.start_trace(); 89 | while let Ok(msg) = ctx.receive_next().await { 90 | ctx.finish_trace(receive_timing, "receiving message", &[]); 91 | 92 | let send_timing = ctx.start_trace(); 93 | // Sleep to extend the duration of the trace. 94 | sleep(Duration::from_millis(5)); 95 | relay.send(msg).await?; 96 | ctx.finish_trace(send_timing, "sending message", &[]); 97 | 98 | receive_timing = ctx.start_trace(); 99 | } 100 | Ok(()) 101 | } 102 | 103 | /// Sync actor that prints all messages it receives. 104 | fn print_actor(mut ctx: sync::Context<&'static str, rt::Sync>) { 105 | loop { 106 | // Start timing of receiving a message. 107 | let timing = ctx.start_trace(); 108 | let msg = if let Ok(msg) = ctx.receive_next() { 109 | msg 110 | } else { 111 | break; 112 | }; 113 | // Finish timing. 114 | ctx.finish_trace(timing, "receiving message", &[]); 115 | 116 | let timing = ctx.start_trace(); 117 | // Sleep to extend the duration of the trace. 118 | sleep(Duration::from_millis(5)); 119 | println!("Received message: {msg}"); 120 | ctx.finish_trace(timing, "printing message", &[("message", &msg)]); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /rt/examples/99_stress_memory.rs: -------------------------------------------------------------------------------- 1 | //! This is just a memory stress test of the runtime. 2 | //! 3 | //! Currently using 10 million "actors" this test uses ~3 GB and takes ~3 4 | //! seconds to spawn the actors. 5 | 6 | #![feature(never_type)] 7 | 8 | use std::future::pending; 9 | 10 | use heph::actor::{self, actor_fn}; 11 | use heph::supervisor::NoSupervisor; 12 | use heph_rt::spawn::ActorOptions; 13 | use heph_rt::{self as rt, Runtime, ThreadLocal}; 14 | use log::info; 15 | 16 | fn main() -> Result<(), rt::Error> { 17 | // Enable logging. 18 | std_logger::Config::logfmt().init(); 19 | 20 | let mut runtime = Runtime::setup().build()?; 21 | runtime.run_on_workers(move |mut runtime_ref| -> Result<(), !> { 22 | const N: usize = 10_000_000; 23 | 24 | info!("Spawning {N} actors, this might take a while"); 25 | let start = std::time::Instant::now(); 26 | let actor = actor_fn(actor); 27 | for _ in 0..N { 28 | runtime_ref.spawn_local(NoSupervisor, actor, (), ActorOptions::default()); 29 | } 30 | info!("Spawning took {:?}", start.elapsed()); 31 | 32 | let control_actor = actor_fn(control_actor); 33 | runtime_ref.spawn_local(NoSupervisor, control_actor, (), ActorOptions::default()); 34 | 35 | Ok(()) 36 | })?; 37 | runtime.start() 38 | } 39 | 40 | /// Our "actor", but it doesn't do much. 41 | async fn actor(_: actor::Context) { 42 | pending().await 43 | } 44 | 45 | async fn control_actor(_: actor::Context) { 46 | info!("Running, check the memory usage!"); 47 | info!("Send a signal (e.g. by pressing Ctrl-C) to stop."); 48 | } 49 | -------------------------------------------------------------------------------- /rt/examples/9_systemd.rs: -------------------------------------------------------------------------------- 1 | #![feature(never_type)] 2 | 3 | use std::net::Ipv4Addr; 4 | use std::{env, io}; 5 | 6 | use heph::actor::{self, actor_fn}; 7 | use heph::restart_supervisor; 8 | use heph::supervisor::StopSupervisor; 9 | use heph_rt::net::{tcp, TcpStream}; 10 | use heph_rt::spawn::options::{ActorOptions, Priority}; 11 | use heph_rt::{self as rt, Runtime, ThreadLocal}; 12 | use log::info; 13 | 14 | fn main() -> Result<(), rt::Error> { 15 | // Enable logging. 16 | std_logger::Config::logfmt().init(); 17 | 18 | // We should get the port from systemd, or use a default. 19 | let port = match env::var("PORT").as_deref().unwrap_or("7890").parse() { 20 | Ok(port) => port, 21 | Err(err) => return Err(rt::Error::setup(format!("failed to parse port: {err}"))), 22 | }; 23 | let address = (Ipv4Addr::LOCALHOST, port).into(); 24 | let actor = actor_fn(conn_actor); 25 | let server = tcp::server::setup(address, StopSupervisor, actor, ActorOptions::default()) 26 | .map_err(rt::Error::setup)?; 27 | 28 | let mut runtime = Runtime::setup() 29 | .use_all_cores() 30 | .auto_cpu_affinity() 31 | .build()?; 32 | 33 | #[cfg(target_os = "linux")] 34 | { 35 | let actor = actor_fn(heph_rt::systemd::watchdog); 36 | // NOTE: this should do a proper health check of you application. 37 | let health_check = || -> Result<(), !> { Ok(()) }; 38 | let options = ActorOptions::default().with_priority(Priority::HIGH); 39 | let systemd_ref = runtime.spawn(StopSupervisor, actor, health_check, options); 40 | runtime.receive_signals(systemd_ref.try_map()); 41 | } 42 | 43 | runtime.run_on_workers(move |mut runtime_ref| -> io::Result<()> { 44 | let supervisor = ServerSupervisor::new(); 45 | let options = ActorOptions::default().with_priority(Priority::LOW); 46 | let server_ref = runtime_ref.spawn_local(supervisor, server, (), options); 47 | runtime_ref.receive_signals(server_ref.try_map()); 48 | Ok(()) 49 | })?; 50 | 51 | info!("listening on {address}"); 52 | runtime.start() 53 | } 54 | 55 | restart_supervisor!(ServerSupervisor, ()); 56 | 57 | async fn conn_actor(_: actor::Context, stream: TcpStream) -> io::Result<()> { 58 | let address = stream.peer_addr()?; 59 | info!("accepted connection: address={address}"); 60 | let ip = address.ip().to_string(); 61 | stream.send_all(ip).await?; 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /rt/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains a number of examples that highlight certain parts of the 4 | system. 5 | 6 | 7 | ## 1. Hello World 8 | 9 | Conforming to the tradition that is "Hello World", a simple program that prints 10 | "Hello World". 11 | 12 | The code can be found in `1_hello_world.rs` and run with `cargo run --example 13 | 1_hello_world`, and it should print "Hello World". 14 | 15 | 16 | ## 2. IP Server 17 | 18 | The second example is a simple TCP server that writes the ip of the connection 19 | to the connection. 20 | 21 | The code can be found in `2_my_ip.rs` and run with `cargo run --example 22 | 2_my_ip`, running something like `nc localhost 7890` should then print your ip 23 | address, e.g. "127.0.0.1". 24 | 25 | 26 | ## 3. RPC 27 | 28 | Example three shows how Heph makes Remote Procedure Calls (RPC) easy. 29 | 30 | 31 | ## 4. Synchronous Actor 32 | 33 | The fourth example how to use synchronous actors. These are actors that have the 34 | thread all to themselves, which means that can do heavy computation and blocking 35 | I/O without stalling other actors. 36 | 37 | 38 | ## 5. Remote Actor References 39 | 40 | TODO: reimplement this. 41 | 42 | 43 | ## 6. Process Signal Handling 44 | 45 | Heph has build-in support for handling process signals. This example shows this 46 | can be used to cleanly shutdown your application. 47 | 48 | The code can be found in `6_process_signals.rs` and run with `cargo run 49 | --example 6_process_signals`, pressing ctrl-c (sending it an interrupt signal 50 | `SIGINT`) should shutdown the example cleanly. 51 | 52 | 53 | ## 7. Restart Supervisor Macro 54 | 55 | Example seven shows how the `restart_supervisor!` macro can be used to easily 56 | create a new `Supervisor` implementation that attempts to restart the actor with 57 | cloned arguments. 58 | 59 | 60 | ## 8. Runtime Tracing 61 | 62 | Heph supports generating trace files in its own custom format, described in the 63 | [Trace Format design document]. This format can be converted into [Chrome's 64 | Trace Event Format] so it can be opened by [Catapult trace view]. 65 | 66 | ```bash 67 | $ cargo run --example 8_tracing # Run the example, to generate the trace. 68 | $ cd ../tools # Got into the tools directory. 69 | # Convert the trace to Chrome's format. 70 | $ cargo run --bin convert_trace ../rt/heph_tracing_example.bin.log 71 | # Make the trace viewable in HTML. 72 | $ $(CATAPULT_REPO)/tracing/bin/trace2html ../heph_tracing_example.json 73 | $ open ../heph_tracing_example.html # Finally open the trace in your browser. 74 | ``` 75 | 76 | [Trace Format design document]: ../doc/Trace%20Format.md 77 | [Chrome's Trace Event Format]: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview 78 | [Catapult trace view]: https://chromium.googlesource.com/catapult/+/refs/heads/master/tracing/README.md 79 | 80 | ## 9. Systemd support 81 | 82 | Heph also has various utilties to support [systemd]. You can use the following 83 | [service] file to start example 9. 84 | 85 | [systemd]: https://systemd.io 86 | [service]: https://www.freedesktop.org/software/systemd/man/systemd.service.html 87 | 88 | ``` 89 | [Unit] 90 | Description=Heph Test service 91 | Requires=network.target 92 | 93 | [Service] 94 | # Required to setup communication between the service manager (systemd) and the 95 | # service itself. 96 | Type=notify 97 | # Restart the service if it fails. 98 | Restart=on-failure 99 | # Require the service to send a keep-alive ping every minute. 100 | WatchdogSec=1min 101 | 102 | # Path to the example. 103 | ExecStart=/path/to/heph/target/debug/examples/9_systemd 104 | # The port the service will listen on. 105 | Environment=PORT=8000 106 | # Enable some debug logging. 107 | Environment=DEBUG=1 108 | ``` 109 | -------------------------------------------------------------------------------- /rt/src/channel.rs: -------------------------------------------------------------------------------- 1 | //! Runtime channel for use in communicating between the coordinator and a 2 | //! worker thread. 3 | 4 | // TODO: remove `rt::channel` entirely and replace it with an `ActorRef` to the 5 | // `worker::comm_actor`. 6 | 7 | use std::future::Future; 8 | use std::io; 9 | use std::pin::Pin; 10 | use std::sync::mpsc; 11 | use std::task::{self, Poll}; 12 | 13 | use a10::msg::{msg_listener, try_send_msg, MsgListener, MsgToken}; 14 | 15 | use crate::wakers::no_ring_ctx; 16 | 17 | const WAKE: u32 = u32::from_ne_bytes([b'W', b'A', b'K', b'E']); // 1162559831. 18 | 19 | /// Create a new communication channel. 20 | /// 21 | /// The `sq` will be used to wake up the receiving end when sending. 22 | pub(crate) fn new(sq: a10::SubmissionQueue) -> io::Result<(Sender, Receiver)> { 23 | let (listener, token) = msg_listener(sq.clone())?; 24 | let (sender, receiver) = mpsc::channel(); 25 | let sender = Sender { sender, sq, token }; 26 | let receiver = Receiver { receiver, listener }; 27 | Ok((sender, receiver)) 28 | } 29 | 30 | /// Sending side of the communication channel. 31 | #[derive(Debug)] 32 | pub(crate) struct Sender { 33 | #[allow(clippy::struct_field_names)] 34 | sender: mpsc::Sender, 35 | /// Receiver's submission queue and token used to wake it up. 36 | sq: a10::SubmissionQueue, 37 | token: MsgToken, 38 | } 39 | 40 | impl Sender { 41 | /// Send a message into the channel. 42 | pub(crate) fn send(&self, msg: T) -> io::Result<()> { 43 | self.sender 44 | .send(msg) 45 | .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "receiver closed channel"))?; 46 | try_send_msg(&self.sq, self.token, WAKE) 47 | } 48 | } 49 | 50 | /// Receiving side of the communication channel. 51 | #[derive(Debug)] 52 | pub(crate) struct Receiver { 53 | receiver: mpsc::Receiver, 54 | listener: MsgListener, 55 | } 56 | 57 | impl Receiver { 58 | /// Receive a message from the channel. 59 | pub(crate) fn recv<'r>(&'r mut self) -> Receive<'r, T> { 60 | Receive { receiver: self } 61 | } 62 | } 63 | 64 | /// [`Future`] behind [`Receiver::recv`]. 65 | pub(crate) struct Receive<'r, T> { 66 | receiver: &'r mut Receiver, 67 | } 68 | 69 | impl<'r, T> Future for Receive<'r, T> { 70 | type Output = Option; 71 | 72 | fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 73 | let receiver = &mut *self.receiver; 74 | loop { 75 | // Check if we have a message first. 76 | if let Ok(msg) = receiver.receiver.try_recv() { 77 | return Poll::Ready(Some(msg)); 78 | } 79 | 80 | // If not wait until we get a signal that another message is 81 | // available. 82 | no_ring_ctx!(ctx); 83 | match Pin::new(&mut receiver.listener).poll_next(ctx) { 84 | Poll::Ready(data) => { 85 | debug_assert_eq!(data, Some(WAKE)); 86 | continue; 87 | } 88 | Poll::Pending => return Poll::Pending, 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /rt/src/io/futures.rs: -------------------------------------------------------------------------------- 1 | //! A10 I/O [`Future`] wrappers. 2 | 3 | #![allow(missing_debug_implementations)] 4 | 5 | use std::future::Future; 6 | use std::io; 7 | use std::pin::Pin; 8 | use std::task::{self, Poll}; 9 | 10 | use a10::extract::Extractor; 11 | 12 | use crate::io::buf::{Buf, BufMut, BufMutSlice, BufSlice, BufWrapper}; 13 | use crate::wakers::no_ring_ctx; 14 | 15 | /// [`Future`] behind write implementations. 16 | pub(crate) struct Write<'a, B>(pub(crate) Extractor>>); 17 | 18 | impl<'a, B: Buf> Future for Write<'a, B> { 19 | type Output = io::Result<(B, usize)>; 20 | 21 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 22 | no_ring_ctx!(ctx); 23 | // SAFETY: not moving the `Future`. 24 | unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) } 25 | .poll(ctx) 26 | .map_ok(|(buf, len)| (buf.0, len)) 27 | } 28 | } 29 | 30 | /// [`Future`] behind write all implementations. 31 | pub(crate) struct WriteAll<'a, B>(pub(crate) Extractor>>); 32 | 33 | impl<'a, B: Buf> Future for WriteAll<'a, B> { 34 | type Output = io::Result; 35 | 36 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 37 | no_ring_ctx!(ctx); 38 | // SAFETY: not moving the `Future`. 39 | unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) } 40 | .poll(ctx) 41 | .map_ok(|buf| buf.0) 42 | } 43 | } 44 | 45 | /// [`Future`] behind write vectored implementations. 46 | pub(crate) struct WriteVectored<'a, B, const N: usize>( 47 | pub(crate) Extractor, N>>, 48 | ); 49 | 50 | impl<'a, B: BufSlice, const N: usize> Future for WriteVectored<'a, B, N> { 51 | type Output = io::Result<(B, usize)>; 52 | 53 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 54 | no_ring_ctx!(ctx); 55 | // SAFETY: not moving the `Future`. 56 | unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) } 57 | .poll(ctx) 58 | .map_ok(|(buf, len)| (buf.0, len)) 59 | } 60 | } 61 | 62 | /// [`Future`] behind write all vectored implementations. 63 | pub(crate) struct WriteAllVectored<'a, B, const N: usize>( 64 | pub(crate) Extractor, N>>, 65 | ); 66 | 67 | impl<'a, B: BufSlice, const N: usize> Future for WriteAllVectored<'a, B, N> { 68 | type Output = io::Result; 69 | 70 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 71 | no_ring_ctx!(ctx); 72 | // SAFETY: not moving the `Future`. 73 | unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) } 74 | .poll(ctx) 75 | .map_ok(|buf| buf.0) 76 | } 77 | } 78 | 79 | /// [`Future`] behind read implementations. 80 | pub(crate) struct Read<'a, B>(pub(crate) a10::io::Read<'a, BufWrapper>); 81 | 82 | impl<'a, B: BufMut> Future for Read<'a, B> { 83 | type Output = io::Result; 84 | 85 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 86 | no_ring_ctx!(ctx); 87 | // SAFETY: not moving the `Future`. 88 | unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) } 89 | .poll(ctx) 90 | .map_ok(|buf| buf.0) 91 | } 92 | } 93 | 94 | /// [`Future`] behind read vectored implementations. 95 | pub(crate) struct ReadVectored<'a, B, const N: usize>( 96 | pub(crate) a10::io::ReadVectored<'a, BufWrapper, N>, 97 | ); 98 | 99 | impl<'a, B: BufMutSlice, const N: usize> Future for ReadVectored<'a, B, N> { 100 | type Output = io::Result; 101 | 102 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 103 | no_ring_ctx!(ctx); 104 | // SAFETY: not moving the `Future`. 105 | unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) } 106 | .poll(ctx) 107 | .map_ok(|buf| buf.0) 108 | } 109 | } 110 | 111 | /// [`Future`] behind read `n` implementations. 112 | pub(crate) struct ReadN<'a, B>(pub(crate) a10::io::ReadN<'a, BufWrapper>); 113 | 114 | impl<'a, B: BufMut> Future for ReadN<'a, B> { 115 | type Output = io::Result; 116 | 117 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 118 | no_ring_ctx!(ctx); 119 | // SAFETY: not moving the `Future`. 120 | unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) } 121 | .poll(ctx) 122 | .map_ok(|buf| buf.0) 123 | } 124 | } 125 | 126 | /// [`Future`] behind read `n` vectored implementations. 127 | pub(crate) struct ReadNVectored<'a, B, const N: usize>( 128 | pub(crate) a10::io::ReadNVectored<'a, BufWrapper, N>, 129 | ); 130 | 131 | impl<'a, B: BufMutSlice, const N: usize> Future for ReadNVectored<'a, B, N> { 132 | type Output = io::Result; 133 | 134 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 135 | no_ring_ctx!(ctx); 136 | // SAFETY: not moving the `Future`. 137 | unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) } 138 | .poll(ctx) 139 | .map_ok(|buf| buf.0) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /rt/src/log.rs: -------------------------------------------------------------------------------- 1 | //! Logging. 2 | //! 3 | //! Logging in Heph is done via the [`log`] crate, much like the entire Rust 4 | //! ecosystem does (or should). However the log crate doesn't provide an actual 5 | //! logging implementation, it only defines macros for logging. 6 | //! 7 | //! Heph doesn't provide a logging implementation, but it recommends the 8 | //! [`std-logger`] crate. 9 | //! 10 | //! [`log`]: https://crates.io/crates/log 11 | //! [`std-logger`]: https://crates.io/crates/std_logger 12 | //! 13 | //! # Examples 14 | //! 15 | //! Enabling logging. 16 | //! 17 | //! ``` 18 | //! #![feature(never_type)] 19 | //! 20 | //! use heph_rt::Runtime; 21 | //! use log::info; 22 | //! 23 | //! fn main() -> Result<(), heph_rt::Error> { 24 | //! // Enable logging. 25 | //! std_logger::Config::logfmt().init(); 26 | //! 27 | //! let runtime = Runtime::new()?; 28 | //! // Runtime setup etc. 29 | //! info!("starting runtime"); 30 | //! runtime.start() 31 | //! } 32 | //! ``` 33 | -------------------------------------------------------------------------------- /rt/src/net/tcp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Transmission Control Protocol (TCP) related types. 2 | //! 3 | //! Three main types are provided: 4 | //! 5 | //! * [`TcpListener`] listens for incoming connections. 6 | //! * [`TcpStream`] represents a single TCP connection. 7 | //! * [TCP server] is an [`Actor`] that listens for incoming connections and 8 | //! starts a new actor for each. 9 | //! 10 | //! [TCP server]: crate::net::tcp::server 11 | //! [`Actor`]: heph::actor::Actor 12 | 13 | pub mod listener; 14 | pub mod server; 15 | pub mod stream; 16 | 17 | #[doc(no_inline)] 18 | pub use listener::TcpListener; 19 | #[doc(no_inline)] 20 | pub use stream::TcpStream; 21 | -------------------------------------------------------------------------------- /rt/src/net/uds/mod.rs: -------------------------------------------------------------------------------- 1 | //! Unix Domain Socket (UDS) or Inter-Process Communication (IPC) related types. 2 | //! 3 | //! Three main types are provided: 4 | //! 5 | //! * [`UnixListener`] listens for incoming Unix connections. 6 | //! * [`UnixStream`] represents a Unix stream socket. 7 | //! * [`UnixDatagram`] represents a Unix datagram socket. 8 | 9 | use std::mem::MaybeUninit; 10 | use std::path::Path; 11 | use std::{io, ptr}; 12 | 13 | use socket2::SockAddr; 14 | 15 | pub mod datagram; 16 | pub mod listener; 17 | pub mod stream; 18 | 19 | #[doc(no_inline)] 20 | pub use datagram::UnixDatagram; 21 | #[doc(no_inline)] 22 | pub use listener::UnixListener; 23 | #[doc(no_inline)] 24 | pub use stream::UnixStream; 25 | 26 | /// Unix socket address. 27 | #[derive(Clone, Debug, Eq, PartialEq)] 28 | pub struct UnixAddr { 29 | /// NOTE: must always be of type `AF_UNIX`. 30 | inner: SockAddr, 31 | } 32 | 33 | impl UnixAddr { 34 | /// Create a `UnixAddr` from `path`. 35 | pub fn from_pathname

(path: P) -> io::Result 36 | where 37 | P: AsRef, 38 | { 39 | SockAddr::unix(path.as_ref()).map(|a| UnixAddr { inner: a }) 40 | } 41 | 42 | /// Returns the contents of this address if it is a pathname address. 43 | pub fn as_pathname(&self) -> Option<&Path> { 44 | self.inner.as_pathname() 45 | } 46 | } 47 | 48 | /// **Not part of the API, do not use**. 49 | #[doc(hidden)] 50 | impl a10::net::SocketAddress for UnixAddr { 51 | unsafe fn as_ptr(&self) -> (*const libc::sockaddr, libc::socklen_t) { 52 | (self.inner.as_ptr(), self.inner.len()) 53 | } 54 | 55 | #[allow(clippy::cast_possible_truncation)] 56 | unsafe fn as_mut_ptr(this: &mut MaybeUninit) -> (*mut libc::sockaddr, libc::socklen_t) { 57 | ( 58 | ptr::addr_of_mut!((*this.as_mut_ptr()).inner).cast(), 59 | size_of::() as _, 60 | ) 61 | } 62 | 63 | unsafe fn init(this: MaybeUninit, length: libc::socklen_t) -> Self { 64 | debug_assert!(length as usize >= size_of::()); 65 | // SAFETY: caller must initialise the address. 66 | let mut this = this.assume_init(); 67 | this.inner.set_length(length); 68 | debug_assert!(this.inner.is_unix()); 69 | this 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rt/src/scheduler/mod.rs: -------------------------------------------------------------------------------- 1 | //! Scheduler implementations. 2 | 3 | use std::cmp::Ordering; 4 | use std::collections::BinaryHeap; 5 | use std::panic::{catch_unwind, AssertUnwindSafe}; 6 | use std::pin::Pin; 7 | use std::time::{Duration, Instant}; 8 | 9 | use log::trace; 10 | 11 | use crate::spawn::options::Priority; 12 | 13 | mod cfs; 14 | mod inactive; 15 | pub(crate) mod process; 16 | pub(crate) mod shared; 17 | #[cfg(test)] 18 | mod tests; 19 | 20 | use cfs::Cfs; 21 | use inactive::Inactive; 22 | pub(crate) use process::ProcessId; 23 | 24 | type Process = process::Process; 25 | 26 | #[derive(Debug)] 27 | pub(crate) struct Scheduler { 28 | /// Processes that are ready to run. 29 | ready: BinaryHeap>>>, 30 | /// Processes that are not ready to run. 31 | inactive: Inactive, 32 | } 33 | 34 | impl Scheduler { 35 | /// Create a new `Scheduler`. 36 | pub(crate) fn new() -> Scheduler { 37 | Scheduler { 38 | ready: BinaryHeap::new(), 39 | inactive: Inactive::empty(), 40 | } 41 | } 42 | 43 | /// Returns the number of processes ready to run. 44 | pub(crate) fn ready(&self) -> usize { 45 | self.ready.len() 46 | } 47 | 48 | /// Returns the number of inactive processes. 49 | pub(crate) const fn inactive(&self) -> usize { 50 | self.inactive.len() 51 | } 52 | 53 | /// Returns `true` if the scheduler has any user processes (in any state), 54 | /// `false` otherwise. This ignore system processes. 55 | pub(crate) fn has_user_process(&self) -> bool { 56 | self.has_ready_process() || self.inactive.has_user_process() 57 | } 58 | 59 | /// Returns `true` if the scheduler has any processes that are ready to run, 60 | /// `false` otherwise. 61 | pub(crate) fn has_ready_process(&self) -> bool { 62 | !self.ready.is_empty() 63 | } 64 | 65 | /// Add a new proces to the scheduler. 66 | pub(crate) fn add_new_process

(&mut self, priority: Priority, process: P) -> ProcessId 67 | where 68 | P: process::Run + 'static, 69 | { 70 | let process = Box::pin(Process::::new(priority, Box::pin(process))); 71 | let pid = process.id(); 72 | self.ready.push(process); 73 | pid 74 | } 75 | 76 | /// Mark the process, with `pid`, as ready to run. 77 | /// 78 | /// # Notes 79 | /// 80 | /// Calling this with an invalid or outdated `pid` will be silently ignored. 81 | pub(crate) fn mark_ready(&mut self, pid: ProcessId) { 82 | trace!(pid = pid.0; "marking process as ready"); 83 | if let Some(process) = self.inactive.remove(pid) { 84 | self.ready.push(process); 85 | } 86 | } 87 | 88 | /// Returns the next ready process. 89 | pub(crate) fn next_process(&mut self) -> Option>>> { 90 | self.ready.pop() 91 | } 92 | 93 | /// Add back a process that was previously removed via 94 | /// [`Scheduler::next_process`]. 95 | pub(crate) fn add_back_process(&mut self, process: Pin>>) { 96 | let pid = process.id(); 97 | trace!(pid = pid.0; "adding back process"); 98 | self.inactive.add(process); 99 | } 100 | 101 | /// Mark `process` as complete, removing it from the scheduler. 102 | #[allow(clippy::unused_self)] 103 | pub(crate) fn complete(&self, process: Pin>>) { 104 | let pid = process.as_ref().id(); 105 | trace!(pid = pid.0; "removing process"); 106 | // Don't want to panic when dropping the process. 107 | drop(catch_unwind(AssertUnwindSafe(move || drop(process)))); 108 | } 109 | } 110 | 111 | /// Scheduling implementation. 112 | /// 113 | /// The type itself holds the per process data needed for scheduling. 114 | pub(crate) trait Schedule { 115 | /// Create new data. 116 | fn new(priority: Priority) -> Self; 117 | 118 | /// Update the process data with the latest run information. 119 | /// 120 | /// Arguments: 121 | /// * `start`: time at which the latest run started. 122 | /// * `end`: time at which the latest run ended. 123 | /// * `elapsed`: `end - start`. 124 | fn update(&mut self, start: Instant, end: Instant, elapsed: Duration); 125 | 126 | /// Determine if the `lhs` or `rhs` should run first. 127 | fn order(lhs: &Self, rhs: &Self) -> Ordering; 128 | } 129 | -------------------------------------------------------------------------------- /rt/src/sync_worker.rs: -------------------------------------------------------------------------------- 1 | //! Synchronous actor thread code. 2 | //! 3 | //! A sync worker manages and runs a single synchronous actor. It simply runs 4 | //! the sync actor and handles any errors it returns (by restarting the actor or 5 | //! stopping the thread). 6 | //! 7 | //! The [`sync_worker::Handle`] type is a handle to the sync worker thread 8 | //! managed by the [coordinator]. The [`start`] function can be used to start a 9 | //! new synchronous actor. 10 | //! 11 | //! [coordinator]: crate::coordinator 12 | //! [`sync_worker::Handle`]: Handle 13 | 14 | use std::sync::Arc; 15 | use std::{io, thread}; 16 | 17 | use heph::actor_ref::ActorRef; 18 | use heph::supervisor::SyncSupervisor; 19 | use heph::sync::{SyncActor, SyncActorRunnerBuilder}; 20 | 21 | use crate::spawn::options::SyncActorOptions; 22 | use crate::trace; 23 | use crate::{self as rt, shared}; 24 | 25 | /// Start a new thread that runs a synchronous actor. 26 | pub(crate) fn start( 27 | id: usize, 28 | supervisor: S, 29 | actor: A, 30 | arg: A::Argument, 31 | options: SyncActorOptions, 32 | shared: Arc, 33 | trace_log: Option, 34 | ) -> io::Result<(Handle, ActorRef)> 35 | where 36 | S: SyncSupervisor + Send + 'static, 37 | A: SyncActor + Send + 'static, 38 | A::Message: Send + 'static, 39 | A::Argument: Send + 'static, 40 | { 41 | let (runner, actor_ref) = SyncActorRunnerBuilder::new() 42 | .with_rt(rt::Sync::new(shared.clone(), trace_log)) 43 | .with_inbox_size(options.inbox_size()) 44 | .build(supervisor, actor); 45 | let thread_name = options 46 | .take_thread_name() 47 | .unwrap_or_else(|| A::name().to_owned()); 48 | let wake_coordinator_on_drop = WakeOnDrop(shared); 49 | let handle = thread::Builder::new().name(thread_name).spawn(move || { 50 | runner.run(arg); 51 | // Wake the coordinator. Note that if it's dropped early it will also 52 | // wake the coordinator, see the `Drop` implementation. 53 | drop(wake_coordinator_on_drop); 54 | })?; 55 | Ok((Handle { id, handle }, actor_ref)) 56 | } 57 | 58 | /// Calls [`shared::RuntimeInternals::wake_coordinator`] when the type is 59 | /// dropped. 60 | struct WakeOnDrop(Arc); 61 | 62 | impl Drop for WakeOnDrop { 63 | fn drop(&mut self) { 64 | // Wake the coordinator forcing it check if the sync workers are still alive. 65 | self.0.wake_coordinator(); 66 | } 67 | } 68 | 69 | /// Handle to a synchronous worker. 70 | #[derive(Debug)] 71 | pub(crate) struct Handle { 72 | /// Unique id among all threads in the `Runtime`. 73 | id: usize, 74 | /// Handle for the actual thread. 75 | handle: thread::JoinHandle<()>, 76 | } 77 | 78 | impl Handle { 79 | /// Return the worker's id. 80 | pub(crate) const fn id(&self) -> usize { 81 | self.id 82 | } 83 | 84 | /// See [`thread::JoinHandle::is_finished`]. 85 | pub(crate) fn is_finished(&self) -> bool { 86 | self.handle.is_finished() 87 | } 88 | 89 | /// See [`thread::JoinHandle::join`]. 90 | pub(crate) fn join(self) -> thread::Result<()> { 91 | self.handle.join() 92 | } 93 | 94 | /// Returns the [`thread::JoinHandle`]. 95 | #[cfg(any(test, feature = "test"))] 96 | pub(crate) fn into_handle(self) -> thread::JoinHandle<()> { 97 | self.handle 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /rt/tests/data/hello_world: -------------------------------------------------------------------------------- 1 | Hello world! 2 | -------------------------------------------------------------------------------- /rt/tests/functional.rs: -------------------------------------------------------------------------------- 1 | //! Functional tests. 2 | 3 | #![feature(async_iterator, never_type, write_all_vectored)] 4 | 5 | #[path = "util/mod.rs"] // rustfmt can't find the file. 6 | #[macro_use] 7 | mod util; 8 | 9 | #[path = "functional"] // rustfmt can't find the files. 10 | mod functional { 11 | mod access; 12 | mod actor; 13 | mod actor_context; 14 | mod actor_group; 15 | mod actor_ref; 16 | mod from_message; 17 | mod fs; 18 | mod fs_watch; 19 | mod future; 20 | mod io; 21 | mod pipe; 22 | mod restart_supervisor; 23 | mod runtime; 24 | mod signal; 25 | mod spawn; 26 | mod sync_actor; 27 | mod tcp; 28 | mod test; 29 | mod timer; 30 | mod udp; 31 | mod uds; 32 | } 33 | -------------------------------------------------------------------------------- /rt/tests/functional/access.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the access module. 2 | 3 | use heph_rt::{Runtime, RuntimeRef, ThreadLocal, ThreadSafe}; 4 | 5 | use crate::util::{assert_send, assert_sync}; 6 | 7 | #[test] 8 | fn thread_safe_is_send_sync() { 9 | assert_send::(); 10 | assert_sync::(); 11 | } 12 | 13 | #[test] 14 | fn thread_local_from_runtime_ref() { 15 | let mut rt = Runtime::new().unwrap(); 16 | rt.run_on_workers(|runtime_ref| { 17 | let thread_local = ThreadLocal::from(runtime_ref); 18 | drop(thread_local); 19 | Ok::<_, !>(()) 20 | }) 21 | .unwrap(); 22 | } 23 | 24 | #[test] 25 | fn thread_local_deref_as_runtime_ref() { 26 | let mut rt = Runtime::new().unwrap(); 27 | rt.run_on_workers(|runtime_ref| { 28 | let mut thread_local = ThreadLocal::from(runtime_ref); 29 | let _runtime_ref: &mut RuntimeRef = &mut *thread_local; 30 | drop(thread_local); 31 | Ok::<_, !>(()) 32 | }) 33 | .unwrap(); 34 | } 35 | 36 | #[test] 37 | fn thread_safe_from_runtime() { 38 | let rt = Runtime::new().unwrap(); 39 | let thread_safe = ThreadSafe::from(&rt); 40 | drop(thread_safe); 41 | drop(rt); 42 | } 43 | 44 | #[test] 45 | fn thread_safe_from_runtime_ref() { 46 | let mut rt = Runtime::new().unwrap(); 47 | rt.run_on_workers(|runtime_ref| { 48 | let thread_safe = ThreadSafe::from(&runtime_ref); 49 | drop(thread_safe); 50 | Ok::<_, !>(()) 51 | }) 52 | .unwrap(); 53 | } 54 | -------------------------------------------------------------------------------- /rt/tests/functional/actor.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the [`Actor`] trait. 2 | 3 | use heph::actor::{self, actor_fn, NewActor}; 4 | 5 | #[test] 6 | fn future_output_result() { 7 | // Actor is implemented for `Future>`. 8 | async fn actor(_: actor::Context<(), ()>) -> Result<(), ()> { 9 | Ok(()) 10 | } 11 | is_new_actor(actor_fn(actor)); 12 | } 13 | 14 | #[test] 15 | fn future_output_tuple() { 16 | // Actor is implemented for `Future`. 17 | async fn actor(_: actor::Context<(), ()>) {} 18 | is_new_actor(actor_fn(actor)); 19 | } 20 | 21 | fn is_new_actor(_: NA) {} 22 | -------------------------------------------------------------------------------- /rt/tests/functional/actor_context.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the `actor::Context`. 2 | 3 | use std::pin::Pin; 4 | use std::task::Poll; 5 | 6 | use heph::actor::{self, actor_fn, NoMessages, RecvError}; 7 | use heph::supervisor::NoSupervisor; 8 | use heph_rt::spawn::{ActorOptions, Spawn}; 9 | use heph_rt::test::{init_local_actor, poll_actor}; 10 | use heph_rt::{Runtime, ThreadLocal, ThreadSafe}; 11 | 12 | use crate::util::{assert_send, assert_sync}; 13 | 14 | #[test] 15 | fn thread_safe_is_send_sync() { 16 | assert_send::>(); 17 | assert_sync::>(); 18 | } 19 | 20 | async fn local_actor_context_actor(mut ctx: actor::Context) { 21 | assert_eq!(ctx.try_receive_next(), Err(RecvError::Empty)); 22 | 23 | let msg = ctx.receive_next().await.unwrap(); 24 | assert_eq!(msg, 123); 25 | 26 | assert_eq!(ctx.receive_next().await, Err(NoMessages)); 27 | assert_eq!(ctx.try_receive_next(), Err(RecvError::Disconnected)); 28 | } 29 | 30 | #[test] 31 | fn local_actor_context() { 32 | let local_actor_context_actor = actor_fn(local_actor_context_actor); 33 | let (actor, actor_ref) = init_local_actor(local_actor_context_actor, ()).unwrap(); 34 | let mut actor = Box::pin(actor); 35 | 36 | // Inbox should be empty initially. 37 | assert_eq!(poll_actor(Pin::as_mut(&mut actor)), Poll::Pending); 38 | 39 | // Any send message should be receivable. 40 | actor_ref.try_send(123usize).unwrap(); 41 | assert_eq!(poll_actor(Pin::as_mut(&mut actor)), Poll::Pending); 42 | 43 | // Once all actor references are dropped 44 | drop(actor_ref); 45 | assert_eq!(poll_actor(Pin::as_mut(&mut actor)), Poll::Ready(Ok(()))); 46 | } 47 | 48 | async fn actor_ref_actor(mut ctx: actor::Context) { 49 | assert_eq!(ctx.receive_next().await, Err(NoMessages)); 50 | 51 | // Send a message to ourselves. 52 | let self_ref = ctx.actor_ref(); 53 | self_ref.send(123usize).await.unwrap(); 54 | let msg = ctx.receive_next().await.unwrap(); 55 | assert_eq!(msg, 123); 56 | } 57 | 58 | #[test] 59 | fn actor_ref() { 60 | let actor_ref_actor = actor_fn(actor_ref_actor); 61 | let (actor, actor_ref) = init_local_actor(actor_ref_actor, ()).unwrap(); 62 | let mut actor = Box::pin(actor); 63 | 64 | drop(actor_ref); 65 | assert_eq!(poll_actor(Pin::as_mut(&mut actor)), Poll::Ready(Ok(()))); 66 | } 67 | 68 | async fn thread_safe_try_spawn_actor(mut ctx: actor::Context) { 69 | let actor_ref1 = ctx 70 | .try_spawn( 71 | NoSupervisor, 72 | actor_fn(spawned_actor1), 73 | (), 74 | ActorOptions::default(), 75 | ) 76 | .unwrap(); 77 | let actor_ref2 = ctx.spawn( 78 | NoSupervisor, 79 | actor_fn(spawned_actor1), 80 | (), 81 | ActorOptions::default(), 82 | ); 83 | 84 | actor_ref1.send(123usize).await.unwrap(); 85 | actor_ref2.send(123usize).await.unwrap(); 86 | } 87 | 88 | async fn spawned_actor1(mut ctx: actor::Context) { 89 | let msg = ctx.receive_next().await.unwrap(); 90 | assert_eq!(msg, 123); 91 | } 92 | 93 | #[test] 94 | fn thread_safe_try_spawn() { 95 | let thread_safe_try_spawn_actor = actor_fn(thread_safe_try_spawn_actor); 96 | let mut runtime = Runtime::new().unwrap(); 97 | let _ = runtime.spawn( 98 | NoSupervisor, 99 | thread_safe_try_spawn_actor, 100 | (), 101 | ActorOptions::default(), 102 | ); 103 | runtime.start().unwrap(); 104 | } 105 | -------------------------------------------------------------------------------- /rt/tests/functional/from_message.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the `from_message!` macro. 2 | 3 | use std::time::Duration; 4 | 5 | use heph::actor::{self, actor_fn}; 6 | use heph::actor_ref::{ActorRef, RpcMessage}; 7 | use heph::from_message; 8 | use heph::supervisor::NoSupervisor; 9 | use heph_rt::spawn::ActorOptions; 10 | use heph_rt::test::{join, try_spawn_local}; 11 | use heph_rt::ThreadLocal; 12 | 13 | #[derive(Debug)] 14 | enum Message { 15 | Msg(String), 16 | Rpc(RpcMessage), 17 | Rpc2(RpcMessage<(String, usize), (usize, usize)>), 18 | } 19 | 20 | from_message!(Message::Msg(String)); 21 | from_message!(Message::Rpc(String) -> usize); 22 | from_message!(Message::Rpc2(String, usize) -> (usize, usize)); 23 | 24 | #[test] 25 | fn from_message() { 26 | let pong_actor = actor_fn(pong_actor); 27 | let pong_ref = try_spawn_local(NoSupervisor, pong_actor, (), ActorOptions::default()).unwrap(); 28 | 29 | let ping_actor = actor_fn(ping_actor); 30 | let ping_ref = try_spawn_local( 31 | NoSupervisor, 32 | ping_actor, 33 | pong_ref.clone(), 34 | ActorOptions::default(), 35 | ) 36 | .unwrap(); 37 | 38 | join(&ping_ref, Duration::from_secs(1)).unwrap(); 39 | join(&pong_ref, Duration::from_secs(1)).unwrap(); 40 | } 41 | 42 | async fn ping_actor(_: actor::Context, actor_ref: ActorRef) { 43 | actor_ref.send("Hello!".to_owned()).await.unwrap(); 44 | 45 | let response = actor_ref.rpc("Rpc".to_owned()).await.unwrap(); 46 | assert_eq!(response, 0); 47 | 48 | let response = actor_ref.rpc(("Rpc2".to_owned(), 2)).await.unwrap(); 49 | assert_eq!(response, (1, 2)); 50 | } 51 | 52 | async fn pong_actor(mut ctx: actor::Context) { 53 | let msg = ctx.receive_next().await.unwrap(); 54 | assert!(matches!(msg, Message::Msg(msg) if msg == "Hello!")); 55 | 56 | let mut count = 0; 57 | if let Ok(Message::Rpc(RpcMessage { request, response })) = ctx.receive_next().await { 58 | assert_eq!(request, "Rpc"); 59 | response.respond(count).unwrap(); 60 | count += 1; 61 | } 62 | 63 | if let Ok(Message::Rpc2(RpcMessage { request, response })) = ctx.receive_next().await { 64 | let (msg, cnt) = request; 65 | assert_eq!(msg, "Rpc2"); 66 | response.respond((count, cnt)).unwrap(); 67 | count += cnt; 68 | } 69 | 70 | assert_eq!(count, 3); 71 | } 72 | -------------------------------------------------------------------------------- /rt/tests/functional/future.rs: -------------------------------------------------------------------------------- 1 | //! Tests for spawning [`Future`]s. 2 | 3 | use std::future::Future; 4 | use std::pin::Pin; 5 | use std::task::{self, Poll}; 6 | 7 | use heph::actor::{self, actor_fn}; 8 | use heph::supervisor::NoSupervisor; 9 | use heph_rt::spawn::{ActorOptions, FutureOptions}; 10 | use heph_rt::test::poll_future; 11 | use heph_rt::{Runtime, ThreadSafe}; 12 | 13 | use crate::util::{expect_pending, expect_ready}; 14 | 15 | struct TestFuture { 16 | wakes: usize, 17 | } 18 | 19 | const fn test_future() -> TestFuture { 20 | TestFuture { wakes: 0 } 21 | } 22 | 23 | impl Future for TestFuture { 24 | type Output = (); 25 | 26 | fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 27 | match self.wakes { 28 | 0 => { 29 | ctx.waker().wake_by_ref(); 30 | self.wakes += 1; 31 | Poll::Pending 32 | } 33 | 1 => { 34 | ctx.waker().clone().wake(); 35 | self.wakes += 1; 36 | Poll::Pending 37 | } 38 | _ => Poll::Ready(()), 39 | } 40 | } 41 | } 42 | 43 | #[test] 44 | fn test_poll_future() { 45 | let mut future = Box::pin(test_future()); 46 | 47 | expect_pending(poll_future(future.as_mut())); 48 | expect_pending(poll_future(future.as_mut())); 49 | expect_ready(poll_future(future.as_mut()), ()); 50 | } 51 | 52 | #[test] 53 | fn thread_local_waking() { 54 | let mut runtime = Runtime::new().unwrap(); 55 | runtime 56 | .run_on_workers::<_, !>(|mut runtime_ref| { 57 | runtime_ref.spawn_local_future(test_future(), FutureOptions::default()); 58 | Ok(()) 59 | }) 60 | .unwrap(); 61 | runtime.start().unwrap(); 62 | } 63 | 64 | #[test] 65 | fn thread_safe_waking() { 66 | async fn spawn_actor(mut ctx: actor::Context) { 67 | // Spawn on `ThreadSafe`. 68 | ctx.runtime() 69 | .spawn_future(test_future(), FutureOptions::default()); 70 | } 71 | 72 | let mut runtime = Runtime::new().unwrap(); 73 | let _ = runtime.spawn( 74 | NoSupervisor, 75 | actor_fn(spawn_actor), 76 | (), 77 | ActorOptions::default(), 78 | ); 79 | // Spawn on `Runtime`. 80 | runtime.spawn_future(test_future(), FutureOptions::default()); 81 | runtime 82 | .run_on_workers::<_, !>(|mut runtime_ref| { 83 | // Spawn on `Runtime_ref`. 84 | runtime_ref.spawn_future(test_future(), FutureOptions::default()); 85 | Ok(()) 86 | }) 87 | .unwrap(); 88 | runtime.start().unwrap(); 89 | } 90 | -------------------------------------------------------------------------------- /rt/tests/functional/signal.rs: -------------------------------------------------------------------------------- 1 | use heph::messages::Terminate; 2 | use heph_rt::Signal; 3 | 4 | #[test] 5 | fn terminate_try_from_signal() { 6 | let tests = [ 7 | (Signal::Interrupt, Ok(Terminate)), 8 | (Signal::Terminate, Ok(Terminate)), 9 | (Signal::Quit, Ok(Terminate)), 10 | (Signal::User1, Err(())), 11 | (Signal::User2, Err(())), 12 | ]; 13 | 14 | for (signal, expected) in tests { 15 | let got = Terminate::try_from(signal); 16 | assert_eq!(expected, got); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /rt/tests/functional/spawn.rs: -------------------------------------------------------------------------------- 1 | //! Tests to check what types implement the Spawn trait. 2 | 3 | use std::future::Pending; 4 | use std::marker::PhantomData; 5 | 6 | use heph::actor::{self, NewActor}; 7 | use heph::supervisor::NoSupervisor; 8 | use heph_rt::spawn::Spawn; 9 | use heph_rt::{Runtime, RuntimeRef, ThreadLocal, ThreadSafe}; 10 | 11 | struct TestNewActor(PhantomData); 12 | 13 | impl NewActor for TestNewActor { 14 | type Message = !; 15 | type Argument = (); 16 | type Actor = Pending>; 17 | type Error = !; 18 | type RuntimeAccess = RT; 19 | 20 | fn new( 21 | &mut self, 22 | _: actor::Context, 23 | _: Self::Argument, 24 | ) -> Result { 25 | todo!() 26 | } 27 | } 28 | 29 | fn can_spawn_thread_local() 30 | where 31 | T: Spawn, ThreadLocal>, 32 | { 33 | } 34 | 35 | fn can_spawn_thread_safe() 36 | where 37 | T: Spawn, ThreadSafe>, 38 | { 39 | } 40 | 41 | #[test] 42 | fn runtime() { 43 | can_spawn_thread_safe::(); 44 | } 45 | 46 | #[test] 47 | fn runtime_ref() { 48 | can_spawn_thread_local::(); 49 | can_spawn_thread_safe::(); 50 | } 51 | 52 | #[test] 53 | fn thread_local_actor_context() { 54 | can_spawn_thread_local::>(); 55 | can_spawn_thread_safe::>(); 56 | } 57 | 58 | #[test] 59 | fn thread_safe_actor_context() { 60 | can_spawn_thread_safe::>(); 61 | } 62 | 63 | #[test] 64 | fn thread_local() { 65 | can_spawn_thread_local::(); 66 | can_spawn_thread_safe::(); 67 | } 68 | 69 | #[test] 70 | fn thread_safe() { 71 | can_spawn_thread_safe::(); 72 | } 73 | -------------------------------------------------------------------------------- /rt/tests/functional/sync_actor.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::pin::Pin; 3 | use std::sync::{Arc, Mutex}; 4 | use std::task::{self, Poll}; 5 | use std::thread::sleep; 6 | use std::time::Duration; 7 | 8 | use heph::actor::{actor_fn, RecvError}; 9 | use heph::supervisor::{NoSupervisor, SupervisorStrategy}; 10 | use heph::sync; 11 | use heph_rt::spawn::SyncActorOptions; 12 | use heph_rt::test::spawn_sync_actor; 13 | 14 | #[derive(Clone, Debug)] 15 | struct BlockFuture { 16 | data: Arc)>>, 17 | } 18 | 19 | impl BlockFuture { 20 | fn new() -> BlockFuture { 21 | BlockFuture { 22 | data: Arc::new(Mutex::new((false, None))), 23 | } 24 | } 25 | 26 | fn unblock(&self) { 27 | let mut data = self.data.lock().unwrap(); 28 | data.0 = true; 29 | data.1.take().unwrap().wake(); 30 | } 31 | 32 | fn wake(&self) { 33 | self.data.lock().unwrap().1.take().unwrap().wake_by_ref(); 34 | } 35 | 36 | fn has_waker(&self) -> bool { 37 | self.data.lock().unwrap().1.is_some() 38 | } 39 | } 40 | 41 | impl Future for BlockFuture { 42 | type Output = (); 43 | 44 | fn poll(self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 45 | let mut data = self.data.lock().unwrap(); 46 | if data.0 { 47 | Poll::Ready(()) 48 | } else { 49 | data.1 = Some(ctx.waker().clone()); 50 | Poll::Pending 51 | } 52 | } 53 | } 54 | 55 | fn block_on_actor(mut ctx: sync::Context, fut: Fut) 56 | where 57 | Fut: Future, 58 | { 59 | let _ = ctx.block_on(fut); 60 | } 61 | 62 | #[test] 63 | fn block_on() { 64 | let future = BlockFuture::new(); 65 | 66 | let (handle, _) = spawn_sync_actor( 67 | NoSupervisor, 68 | actor_fn(block_on_actor), 69 | future.clone(), 70 | SyncActorOptions::default(), 71 | ) 72 | .unwrap(); 73 | 74 | while !future.has_waker() { 75 | sleep(Duration::from_millis(10)); 76 | } 77 | 78 | future.unblock(); 79 | handle.join().unwrap(); 80 | } 81 | 82 | #[test] 83 | fn block_on_spurious_wake_up() { 84 | let future = BlockFuture::new(); 85 | 86 | let (handle, _) = spawn_sync_actor( 87 | NoSupervisor, 88 | actor_fn(block_on_actor), 89 | future.clone(), 90 | SyncActorOptions::default(), 91 | ) 92 | .unwrap(); 93 | 94 | // Wait until the future is polled a first time. 95 | while !future.has_waker() { 96 | sleep(Duration::from_millis(10)); 97 | } 98 | // Wake up the sync actor, but don't yet let it continue. 99 | future.wake(); 100 | 101 | // Wait until the sync actor is run again. 102 | while !future.has_waker() { 103 | sleep(Duration::from_millis(10)); 104 | } 105 | // Now let the sync actor complete. 106 | future.unblock(); 107 | handle.join().unwrap(); 108 | } 109 | 110 | fn try_receive_next_actor(mut ctx: sync::Context) { 111 | loop { 112 | match ctx.try_receive_next() { 113 | Ok(msg) => { 114 | assert_eq!(msg, "Hello world"); 115 | return; 116 | } 117 | Err(RecvError::Empty) => continue, 118 | Err(RecvError::Disconnected) => panic!("unexpected disconnected error"), 119 | } 120 | } 121 | } 122 | 123 | #[test] 124 | fn context_try_receive_next() { 125 | let (handle, actor_ref) = spawn_sync_actor( 126 | NoSupervisor, 127 | actor_fn(try_receive_next_actor), 128 | (), 129 | SyncActorOptions::default(), 130 | ) 131 | .unwrap(); 132 | 133 | actor_ref.try_send("Hello world".to_owned()).unwrap(); 134 | handle.join().unwrap(); 135 | } 136 | 137 | #[test] 138 | fn supervision() { 139 | let (handle, _) = spawn_sync_actor( 140 | bad_actor_supervisor, 141 | actor_fn(bad_actor), 142 | 0usize, 143 | SyncActorOptions::default(), 144 | ) 145 | .unwrap(); 146 | 147 | handle.join().unwrap(); 148 | } 149 | 150 | fn bad_actor_supervisor(err_count: usize) -> SupervisorStrategy { 151 | if err_count == 1 { 152 | SupervisorStrategy::Restart(err_count) 153 | } else { 154 | SupervisorStrategy::Stop 155 | } 156 | } 157 | 158 | fn bad_actor(_: sync::Context, count: usize) -> Result<(), usize> { 159 | Err(count + 1) 160 | } 161 | -------------------------------------------------------------------------------- /rt/tests/functional/tcp/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the TCP types. 2 | 3 | mod listener; 4 | mod server; 5 | mod stream; 6 | -------------------------------------------------------------------------------- /rt/tests/functional/uds/datagram.rs: -------------------------------------------------------------------------------- 1 | //! Tests for `UnixDatagram`. 2 | 3 | use std::io; 4 | use std::net::Shutdown; 5 | use std::time::Duration; 6 | 7 | use heph::actor::{self, actor_fn}; 8 | use heph_rt::net::uds::{UnixAddr, UnixDatagram}; 9 | use heph_rt::net::Unconnected; 10 | use heph_rt::spawn::ActorOptions; 11 | use heph_rt::test::{block_on_local_actor, join, try_spawn_local, PanicSupervisor}; 12 | use heph_rt::ThreadLocal; 13 | use heph_rt::{self as rt}; 14 | 15 | use crate::util::temp_file; 16 | 17 | const DATA: &[u8] = b"Hello world"; 18 | const DATA2: &[u8] = b"Hello mars"; 19 | 20 | #[test] 21 | fn pair() { 22 | async fn actor(ctx: actor::Context) -> io::Result<()> 23 | where 24 | RT: rt::Access, 25 | { 26 | let (s1, s2) = UnixDatagram::pair(ctx.runtime_ref())?; 27 | 28 | // Addresses must point to each other. 29 | let s1_local = s1.local_addr()?; 30 | let s1_peer = s1.peer_addr()?; 31 | let s2_local = s2.local_addr()?; 32 | let s2_peer = s2.peer_addr()?; 33 | assert_eq!(s1_local, s2_peer); 34 | assert_eq!(s1_peer, s2_local); 35 | 36 | // Send to one arrives at the other. 37 | let (_, n) = s1.send(DATA).await?; 38 | assert_eq!(n, DATA.len()); 39 | let mut buf = s2.recv(Vec::with_capacity(DATA.len() + 1)).await?; 40 | assert_eq!(buf.len(), DATA.len()); 41 | assert_eq!(buf, DATA); 42 | buf.clear(); 43 | 44 | // Same as above, but then in the other direction. 45 | let (_, n) = s2.send(DATA2).await?; 46 | assert_eq!(n, DATA2.len()); 47 | let mut buf = s1.recv(buf).await?; 48 | assert_eq!(buf.len(), DATA2.len()); 49 | assert_eq!(buf, DATA2); 50 | buf.clear(); 51 | 52 | // Shutdown. 53 | s1.shutdown(Shutdown::Both).await?; 54 | s2.shutdown(Shutdown::Both).await?; 55 | 56 | // No errors. 57 | assert!(s1.take_error()?.is_none()); 58 | assert!(s2.take_error()?.is_none()); 59 | 60 | Ok(()) 61 | } 62 | 63 | let actor = actor_fn(actor); 64 | let actor_ref = try_spawn_local(PanicSupervisor, actor, (), ActorOptions::default()).unwrap(); 65 | join(&actor_ref, Duration::from_secs(1)).unwrap(); 66 | } 67 | 68 | #[test] 69 | fn bound() { 70 | async fn actor(ctx: actor::Context) -> io::Result<()> 71 | where 72 | RT: rt::Access, 73 | { 74 | let path1 = temp_file("uds.bound1"); 75 | let path2 = temp_file("uds.bound2"); 76 | let address1 = UnixAddr::from_pathname(path1)?; 77 | let address2 = UnixAddr::from_pathname(path2)?; 78 | let listener = UnixDatagram::bind(ctx.runtime_ref(), address1.clone()).await?; 79 | 80 | // Addresses must point to each other. 81 | assert_eq!(listener.local_addr()?, address1); 82 | assert!(listener.peer_addr().is_err()); 83 | 84 | let socket = UnixDatagram::bind(ctx.runtime_ref(), address2.clone()).await?; 85 | let socket = socket.connect(address1.clone()).await?; 86 | assert_eq!(socket.local_addr()?, address2); 87 | assert_eq!(socket.peer_addr()?, address1); 88 | 89 | let (_, n) = listener.send_to(DATA, address2).await?; 90 | assert_eq!(n, DATA.len()); 91 | let buf = socket.recv(Vec::with_capacity(DATA.len() + 1)).await?; 92 | assert_eq!(buf.len(), DATA.len()); 93 | assert_eq!(buf, DATA); 94 | 95 | // No errors. 96 | assert!(listener.take_error()?.is_none()); 97 | assert!(socket.take_error()?.is_none()); 98 | 99 | Ok(()) 100 | } 101 | 102 | let actor = actor_fn(actor); 103 | let actor_ref = try_spawn_local(PanicSupervisor, actor, (), ActorOptions::default()).unwrap(); 104 | join(&actor_ref, Duration::from_secs(1)).unwrap(); 105 | } 106 | 107 | #[test] 108 | fn socket_from_std() { 109 | async fn actor(ctx: actor::Context) -> io::Result<()> { 110 | let path1 = temp_file("uds.socket_from_std1"); 111 | let path2 = temp_file("uds.socket_from_std2"); 112 | let socket = std::os::unix::net::UnixDatagram::bind(path1)?; 113 | let socket = UnixDatagram::::from_std(ctx.runtime_ref(), socket); 114 | let local_address = socket.local_addr()?; 115 | 116 | let peer = std::os::unix::net::UnixDatagram::bind(path2)?; 117 | let peer_address = UnixAddr::from_pathname(peer.local_addr()?.as_pathname().unwrap())?; 118 | 119 | let (_, bytes_written) = socket.send_to(DATA, peer_address).await?; 120 | assert_eq!(bytes_written, DATA.len()); 121 | 122 | let mut buf = vec![0; DATA.len() + 2]; 123 | let (n, address) = peer.recv_from(&mut buf)?; 124 | assert_eq!(n, DATA.len()); 125 | assert_eq!(&buf[..n], DATA); 126 | assert_eq!(address.as_pathname(), local_address.as_pathname()); 127 | 128 | Ok(()) 129 | } 130 | 131 | block_on_local_actor(actor_fn(actor), ()); 132 | } 133 | -------------------------------------------------------------------------------- /rt/tests/functional/uds/listener.rs: -------------------------------------------------------------------------------- 1 | //! Tests for `UnixListener`. 2 | 3 | use std::io; 4 | use std::time::Duration; 5 | 6 | use heph::actor::{self, actor_fn}; 7 | use heph_rt::net::uds::{UnixAddr, UnixListener, UnixStream}; 8 | use heph_rt::spawn::ActorOptions; 9 | use heph_rt::test::{block_on_local_actor, join, try_spawn_local, PanicSupervisor}; 10 | use heph_rt::util::next; 11 | use heph_rt::ThreadLocal; 12 | use heph_rt::{self as rt}; 13 | 14 | use crate::util::temp_file; 15 | 16 | const DATA: &[u8] = b"Hello world"; 17 | 18 | #[test] 19 | fn accept() { 20 | async fn actor(ctx: actor::Context) -> io::Result<()> 21 | where 22 | RT: rt::Access, 23 | { 24 | let path = temp_file("uds_listener_accept"); 25 | let address = UnixAddr::from_pathname(path)?; 26 | let listener = UnixListener::bind(ctx.runtime_ref(), address.clone()).await?; 27 | 28 | let stream = UnixStream::connect(ctx.runtime_ref(), address).await?; 29 | 30 | let (client, _) = listener.accept().await?; 31 | 32 | let (_, n) = stream.send(DATA).await?; 33 | assert_eq!(n, DATA.len()); 34 | let buf = client.recv(Vec::with_capacity(DATA.len() + 1)).await?; 35 | assert_eq!(buf.len(), DATA.len()); 36 | assert_eq!(buf, DATA); 37 | 38 | // No errors. 39 | assert!(listener.take_error()?.is_none()); 40 | assert!(client.take_error()?.is_none()); 41 | assert!(stream.take_error()?.is_none()); 42 | 43 | Ok(()) 44 | } 45 | 46 | let actor = actor_fn(actor); 47 | let actor_ref = try_spawn_local(PanicSupervisor, actor, (), ActorOptions::default()).unwrap(); 48 | join(&actor_ref, Duration::from_secs(1)).unwrap(); 49 | } 50 | 51 | #[test] 52 | fn incoming() { 53 | async fn actor(ctx: actor::Context) -> io::Result<()> 54 | where 55 | RT: rt::Access, 56 | { 57 | let path = temp_file("uds_listener_incoming"); 58 | let address = UnixAddr::from_pathname(path)?; 59 | let listener = UnixListener::bind(ctx.runtime_ref(), address.clone()).await?; 60 | 61 | let mut incoming = listener.incoming(); 62 | 63 | let stream = UnixStream::connect(ctx.runtime_ref(), address).await?; 64 | 65 | let client = next(&mut incoming).await.unwrap()?; 66 | 67 | let (_, n) = stream.send(DATA).await?; 68 | assert_eq!(n, DATA.len()); 69 | let buf = client.recv(Vec::with_capacity(DATA.len() + 1)).await?; 70 | assert_eq!(buf.len(), DATA.len()); 71 | assert_eq!(buf, DATA); 72 | 73 | // No errors. 74 | assert!(listener.take_error()?.is_none()); 75 | assert!(client.take_error()?.is_none()); 76 | assert!(stream.take_error()?.is_none()); 77 | 78 | Ok(()) 79 | } 80 | 81 | let actor = actor_fn(actor); 82 | let actor_ref = try_spawn_local(PanicSupervisor, actor, (), ActorOptions::default()).unwrap(); 83 | join(&actor_ref, Duration::from_secs(1)).unwrap(); 84 | } 85 | 86 | #[test] 87 | fn listener_from_std() { 88 | async fn actor(ctx: actor::Context) -> io::Result<()> { 89 | let path = temp_file("uds.listener_from_std"); 90 | 91 | let listener = std::os::unix::net::UnixListener::bind(path)?; 92 | let listener = UnixListener::from_std(ctx.runtime_ref(), listener); 93 | 94 | let address = listener.local_addr()?; 95 | let stream = UnixStream::connect(ctx.runtime_ref(), address).await?; 96 | 97 | let (client, _) = listener.accept().await?; 98 | 99 | let (_, n) = stream.send(DATA).await?; 100 | assert_eq!(n, DATA.len()); 101 | let buf = client.recv(Vec::with_capacity(DATA.len() + 1)).await?; 102 | assert_eq!(buf.len(), DATA.len()); 103 | assert_eq!(buf, DATA); 104 | 105 | Ok(()) 106 | } 107 | 108 | block_on_local_actor(actor_fn(actor), ()); 109 | } 110 | -------------------------------------------------------------------------------- /rt/tests/functional/uds/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the UDS types. 2 | 3 | mod datagram; 4 | mod listener; 5 | mod stream; 6 | -------------------------------------------------------------------------------- /rt/tests/functional/uds/stream.rs: -------------------------------------------------------------------------------- 1 | //! Tests for `UnixStream`. 2 | 3 | use std::io::{self, Read}; 4 | use std::net::Shutdown; 5 | use std::os::unix::net; 6 | use std::time::Duration; 7 | 8 | use heph::actor::{self, actor_fn}; 9 | use heph_rt::net::uds::{UnixAddr, UnixStream}; 10 | use heph_rt::spawn::ActorOptions; 11 | use heph_rt::test::{block_on_local_actor, join, try_spawn_local, PanicSupervisor}; 12 | use heph_rt::ThreadLocal; 13 | use heph_rt::{self as rt}; 14 | 15 | use crate::util::temp_file; 16 | 17 | const DATA: &[u8] = b"Hello world"; 18 | const DATA2: &[u8] = b"Hello mars"; 19 | 20 | #[test] 21 | fn pair() { 22 | async fn actor(ctx: actor::Context) -> io::Result<()> 23 | where 24 | RT: rt::Access, 25 | { 26 | let (s1, s2) = UnixStream::pair(ctx.runtime_ref())?; 27 | 28 | // Addresses must point to each other. 29 | let s1_local = s1.local_addr()?; 30 | let s1_peer = s1.peer_addr()?; 31 | let s2_local = s2.local_addr()?; 32 | let s2_peer = s2.peer_addr()?; 33 | assert_eq!(s1_local, s2_peer); 34 | assert_eq!(s1_peer, s2_local); 35 | 36 | // Send to one arrives at the other. 37 | let (_, n) = s1.send(DATA).await?; 38 | assert_eq!(n, DATA.len()); 39 | let mut buf = s2.recv(Vec::with_capacity(DATA.len() + 1)).await?; 40 | assert_eq!(buf.len(), DATA.len()); 41 | assert_eq!(buf, DATA); 42 | buf.clear(); 43 | 44 | // Same as above, but then in the other direction. 45 | let (_, n) = s2.send(DATA2).await?; 46 | assert_eq!(n, DATA2.len()); 47 | let mut buf = s1.recv(buf).await?; 48 | assert_eq!(buf.len(), DATA2.len()); 49 | assert_eq!(buf, DATA2); 50 | buf.clear(); 51 | 52 | // Shutdown. 53 | s1.shutdown(Shutdown::Both).await?; 54 | s2.shutdown(Shutdown::Both).await?; 55 | 56 | // No errors. 57 | assert!(s1.take_error()?.is_none()); 58 | assert!(s2.take_error()?.is_none()); 59 | 60 | Ok(()) 61 | } 62 | 63 | let actor = actor_fn(actor); 64 | let actor_ref = try_spawn_local(PanicSupervisor, actor, (), ActorOptions::default()).unwrap(); 65 | join(&actor_ref, Duration::from_secs(1)).unwrap(); 66 | } 67 | 68 | #[test] 69 | fn connect() { 70 | async fn actor(ctx: actor::Context) -> io::Result<()> 71 | where 72 | RT: rt::Access, 73 | { 74 | let path = temp_file("uds_stream_bound"); 75 | let listener = net::UnixListener::bind(&path)?; 76 | 77 | let address = UnixAddr::from_pathname(path)?; 78 | let stream = UnixStream::connect(ctx.runtime_ref(), address).await?; 79 | 80 | let (mut client, _) = listener.accept()?; 81 | 82 | let (_, n) = stream.send(DATA).await?; 83 | assert_eq!(n, DATA.len()); 84 | let mut buf = vec![0; DATA.len() + 1]; 85 | let n = client.read(&mut buf)?; 86 | assert_eq!(n, DATA.len()); 87 | assert_eq!(&buf[..n], DATA); 88 | 89 | // No errors. 90 | assert!(listener.take_error()?.is_none()); 91 | assert!(client.take_error()?.is_none()); 92 | assert!(stream.take_error()?.is_none()); 93 | 94 | Ok(()) 95 | } 96 | 97 | let actor = actor_fn(actor); 98 | let actor_ref = try_spawn_local(PanicSupervisor, actor, (), ActorOptions::default()).unwrap(); 99 | join(&actor_ref, Duration::from_secs(1)).unwrap(); 100 | } 101 | 102 | #[test] 103 | fn stream_from_std() { 104 | async fn actor(ctx: actor::Context) -> io::Result<()> { 105 | let (mut peer, stream) = net::UnixStream::pair()?; 106 | let stream = UnixStream::from_std(ctx.runtime_ref(), stream); 107 | 108 | stream.send_all(DATA).await?; 109 | 110 | let mut buf = vec![0; DATA.len() + 2]; 111 | let n = peer.read(&mut buf)?; 112 | assert_eq!(n, DATA.len()); 113 | assert_eq!(&buf[..n], DATA); 114 | 115 | Ok(()) 116 | } 117 | 118 | block_on_local_actor(actor_fn(actor), ()); 119 | } 120 | -------------------------------------------------------------------------------- /rt/tests/process_signals.rs: -------------------------------------------------------------------------------- 1 | #![feature(async_iterator, never_type, write_all_vectored)] 2 | 3 | use std::future::pending; 4 | use std::process; 5 | use std::sync::atomic::{AtomicUsize, Ordering}; 6 | use std::sync::Arc; 7 | 8 | use heph::actor::{self, actor_fn}; 9 | use heph::supervisor::NoSupervisor; 10 | use heph::sync; 11 | use heph_rt::spawn::options::{ActorOptions, FutureOptions, SyncActorOptions}; 12 | use heph_rt::{Runtime, Signal}; 13 | 14 | #[path = "util/mod.rs"] // rustfmt can't find the file. 15 | #[macro_use] 16 | mod util; 17 | 18 | use util::send_signal; 19 | 20 | fn main() { 21 | no_signal_handlers(); 22 | with_signal_handles(); 23 | } 24 | 25 | /// Runtime without any actor to receive the signal should stop itself. 26 | fn no_signal_handlers() { 27 | let mut runtime = Runtime::setup().build().unwrap(); 28 | // We add a never ending process to make sure that the runtime doesn't 29 | // shutdown because no processes are left to run. 30 | runtime.spawn_future(pending(), FutureOptions::default()); 31 | send_signal(process::id(), libc::SIGINT).expect("failed to send signal"); 32 | let err_str = runtime.start().unwrap_err().to_string(); 33 | assert!( 34 | err_str.contains("received process signal, but no receivers for it: stopping runtime"), 35 | "got error '{err_str}'", 36 | ); 37 | } 38 | 39 | /// Runtime with actors to receive the signal should not stop and relay the 40 | /// signals to the actors instead. 41 | fn with_signal_handles() { 42 | let mut runtime = Runtime::setup().build().unwrap(); 43 | 44 | let thread_local = Arc::new(AtomicUsize::new(0)); 45 | let thread_safe1 = Arc::new(AtomicUsize::new(0)); // Via `RuntimeRef`. 46 | let thread_safe2 = Arc::new(AtomicUsize::new(0)); // Via `Runtime`. 47 | let sync = Arc::new(AtomicUsize::new(0)); 48 | 49 | let tl = thread_local.clone(); 50 | let ts = thread_safe1.clone(); 51 | runtime 52 | .run_on_workers(|mut runtime_ref| -> Result<(), !> { 53 | let tla = actor_fn(actor); 54 | let actor_ref = runtime_ref.spawn_local(NoSupervisor, tla, tl, ActorOptions::default()); 55 | runtime_ref.receive_signals(actor_ref); 56 | 57 | let tsa = actor_fn(actor); 58 | let actor_ref = runtime_ref.spawn(NoSupervisor, tsa, ts, ActorOptions::default()); 59 | runtime_ref.receive_signals(actor_ref); 60 | 61 | Ok(()) 62 | }) 63 | .unwrap(); 64 | 65 | let actor_ref = runtime.spawn( 66 | NoSupervisor, 67 | actor_fn(actor), 68 | thread_safe2.clone(), 69 | ActorOptions::default(), 70 | ); 71 | runtime.receive_signals(actor_ref); 72 | 73 | let actor_ref = runtime 74 | .spawn_sync_actor( 75 | NoSupervisor, 76 | actor_fn(sync_actor), 77 | sync.clone(), 78 | SyncActorOptions::default(), 79 | ) 80 | .unwrap(); 81 | runtime.receive_signals(actor_ref); 82 | 83 | // Sending a signal now shouldn't cause the runtime to return an error (as 84 | // the signal is handled by one or more actors). 85 | send_signal(process::id(), libc::SIGINT).expect("failed to send signal"); 86 | runtime.start().unwrap(); 87 | 88 | // Make sure that all the actor received the signal once. 89 | assert_eq!(thread_local.load(Ordering::Acquire), 1); 90 | assert_eq!(thread_safe1.load(Ordering::Acquire), 1); 91 | assert_eq!(thread_safe2.load(Ordering::Acquire), 1); 92 | assert_eq!(sync.load(Ordering::Acquire), 1); 93 | } 94 | 95 | async fn actor(mut ctx: actor::Context, got_signal: Arc) { 96 | let _msg = ctx.receive_next().await.unwrap(); 97 | got_signal.fetch_add(1, Ordering::AcqRel); 98 | } 99 | 100 | fn sync_actor(mut ctx: sync::Context, got_signal: Arc) { 101 | let _msg = ctx.receive_next().unwrap(); 102 | got_signal.fetch_add(1, Ordering::AcqRel); 103 | } 104 | -------------------------------------------------------------------------------- /rt/tests/regression.rs: -------------------------------------------------------------------------------- 1 | //! Regression tests. 2 | //! 3 | //! Format is `tests/regression/issue_#.rs`, referring to a GitHub issue #. 4 | 5 | #![feature(never_type)] 6 | 7 | #[path = "regression"] // rustfmt can't find the files. 8 | mod regression { 9 | mod issue_145; 10 | mod issue_294; 11 | mod issue_323; 12 | } 13 | -------------------------------------------------------------------------------- /rt/tests/regression/issue_145.rs: -------------------------------------------------------------------------------- 1 | //! The `TcpListener` and TCP server should bind to port 0, using the same port 2 | //! on each worker thread. 3 | 4 | use std::io::Read; 5 | use std::net::SocketAddr; 6 | use std::sync::{Arc, Mutex}; 7 | use std::thread; 8 | use std::time::Duration; 9 | 10 | use heph::actor::{self, actor_fn}; 11 | use heph::messages::Terminate; 12 | use heph::supervisor::{NoSupervisor, Supervisor, SupervisorStrategy}; 13 | use heph::{Actor, ActorRef, NewActor}; 14 | use heph_rt::net::{tcp, TcpListener, TcpStream}; 15 | use heph_rt::spawn::ActorOptions; 16 | use heph_rt::{Runtime, RuntimeRef, ThreadLocal}; 17 | 18 | const N: usize = 4; 19 | 20 | #[test] 21 | fn issue_145_tcp_server() { 22 | let mut runtime = Runtime::setup().num_threads(N).build().unwrap(); 23 | 24 | let addresses = Arc::new(Mutex::new(Vec::::new())); 25 | let servers = Arc::new(Mutex::new(Vec::new())); 26 | let addr2 = addresses.clone(); 27 | let srv2 = servers.clone(); 28 | let conn_actor = 29 | actor_fn(conn_actor).map_arg(move |stream| (stream, addr2.clone(), srv2.clone())); 30 | let address = "127.0.0.1:0".parse().unwrap(); 31 | let server = 32 | tcp::server::setup(address, NoSupervisor, conn_actor, ActorOptions::default()).unwrap(); 33 | let expected_address = server.local_addr(); 34 | 35 | runtime 36 | .run_on_workers::<_, !>(move |mut runtime_ref| { 37 | let srv_ref = 38 | runtime_ref.spawn_local(ServerSupervisor, server, (), ActorOptions::default()); 39 | // NOTE: this is not safe or supported. DO NOT USE THIS. 40 | let r = unsafe { std::mem::transmute_copy::(&runtime_ref) }; 41 | servers.lock().unwrap().push((r, srv_ref)); 42 | Ok(()) 43 | }) 44 | .unwrap(); 45 | 46 | let handle = thread::spawn(move || { 47 | // TODO: replace with a barrier. 48 | thread::sleep(Duration::from_millis(50)); 49 | 50 | for _ in 0..N { 51 | // Create a test connection to check the addresses. 52 | let mut stream = std::net::TcpStream::connect(&expected_address).unwrap(); 53 | let mut buf = [0; 1]; 54 | let n = stream.read(&mut buf).unwrap(); 55 | assert_eq!(n, 0); 56 | 57 | // TODO: replace with a barrier. 58 | thread::sleep(Duration::from_millis(100)); 59 | } 60 | }); 61 | 62 | runtime.start().unwrap(); 63 | 64 | handle.join().unwrap(); 65 | for address in addresses.lock().unwrap().iter() { 66 | assert_eq!(*address, expected_address); 67 | } 68 | } 69 | 70 | struct ServerSupervisor; 71 | 72 | impl Supervisor for ServerSupervisor 73 | where 74 | L: NewActor, 75 | A: Actor>, 76 | { 77 | fn decide(&mut self, _: tcp::server::Error) -> SupervisorStrategy<()> { 78 | SupervisorStrategy::Stop 79 | } 80 | 81 | fn decide_on_restart_error(&mut self, err: !) -> SupervisorStrategy<()> { 82 | err 83 | } 84 | 85 | fn second_restart_error(&mut self, err: !) { 86 | err 87 | } 88 | } 89 | 90 | #[allow(clippy::type_complexity)] // `servers` is too complex. 91 | async fn conn_actor( 92 | mut ctx: actor::Context, 93 | stream: TcpStream, 94 | addresses: Arc>>, 95 | servers: Arc)>>>, 96 | ) -> Result<(), !> { 97 | let mut addresses = addresses.lock().unwrap(); 98 | addresses.push(stream.local_addr().unwrap()); 99 | 100 | // Shutdown the TCP server that started us to ensure the next request goes 101 | // to a different server. 102 | // NOTE: this is not safe or supported. DO NOT USE THIS. 103 | let r = unsafe { std::mem::transmute_copy::(&*ctx.runtime()) }; 104 | let mut servers = servers.lock().unwrap(); 105 | let idx = servers.iter().position(|(rr, _)| r == *rr).unwrap(); 106 | let (_, server_ref) = servers.remove(idx); 107 | server_ref.try_send(Terminate).unwrap(); 108 | 109 | Ok(()) 110 | } 111 | 112 | #[test] 113 | fn issue_145_tcp_listener() { 114 | let mut runtime = Runtime::new().unwrap(); 115 | runtime 116 | .run_on_workers::<_, !>(move |mut runtime_ref| { 117 | let actor = actor_fn(listener_actor); 118 | runtime_ref 119 | .try_spawn_local(NoSupervisor, actor, (), ActorOptions::default()) 120 | .unwrap(); 121 | Ok(()) 122 | }) 123 | .unwrap(); 124 | runtime.start().unwrap(); 125 | } 126 | 127 | async fn listener_actor(ctx: actor::Context) -> Result<(), !> { 128 | let address = "127.0.0.1:0".parse().unwrap(); 129 | // NOTE: this should not fail. 130 | let listener = TcpListener::bind(ctx.runtime_ref(), address).await.unwrap(); 131 | let addr = listener.local_addr().unwrap(); 132 | assert!(addr.port() != 0); 133 | Ok(()) 134 | } 135 | -------------------------------------------------------------------------------- /rt/tests/regression/issue_294.rs: -------------------------------------------------------------------------------- 1 | //! The setup function should be dropped after the runtime is started. 2 | //! 3 | //! In this test the actor reference, to the sync actor, should be dropped allow 4 | //! the sync actor to stop and not prevent the test from returning. 5 | 6 | use heph::actor::actor_fn; 7 | use heph::supervisor::NoSupervisor; 8 | use heph::sync; 9 | use heph_rt::spawn::SyncActorOptions; 10 | use heph_rt::Runtime; 11 | 12 | #[test] 13 | fn issue_294() { 14 | let mut runtime = Runtime::new().unwrap(); 15 | 16 | let actor = actor_fn(actor); 17 | let options = SyncActorOptions::default(); 18 | let actor_ref = runtime 19 | .spawn_sync_actor(NoSupervisor, actor, (), options) 20 | .unwrap(); 21 | 22 | runtime 23 | .run_on_workers::<_, !>(move |_| { 24 | actor_ref.try_send(()).unwrap(); 25 | Ok(()) 26 | }) 27 | .unwrap(); 28 | runtime.start().unwrap(); 29 | } 30 | 31 | fn actor(mut ctx: sync::Context<(), RT>) { 32 | while let Ok(()) = ctx.receive_next() {} 33 | } 34 | -------------------------------------------------------------------------------- /rt/tests/regression/issue_323.rs: -------------------------------------------------------------------------------- 1 | //! Short running synchronous threads that are finished before calling 2 | //! [`Runtime::start`] should be collected, and not run for ever. 3 | 4 | use std::thread::sleep; 5 | use std::time::Duration; 6 | 7 | use heph::actor::actor_fn; 8 | use heph::supervisor::NoSupervisor; 9 | use heph::sync; 10 | use heph_rt::spawn::SyncActorOptions; 11 | use heph_rt::Runtime; 12 | 13 | #[test] 14 | fn issue_323() { 15 | let mut runtime = Runtime::new().unwrap(); 16 | 17 | let actor = actor_fn(actor); 18 | let options = SyncActorOptions::default(); 19 | runtime 20 | .spawn_sync_actor(NoSupervisor, actor, (), options) 21 | .unwrap(); 22 | 23 | // Let the synchronous actor complete first. 24 | sleep(Duration::from_secs(1)); 25 | 26 | // This just needs to return and not hang for ever. 27 | runtime.start().unwrap(); 28 | } 29 | 30 | /// Short running synchronous actor. 31 | fn actor(_: sync::Context<(), RT>) {} 32 | -------------------------------------------------------------------------------- /src/actor/context.rs: -------------------------------------------------------------------------------- 1 | //! Module containing the `Context` and related types. 2 | 3 | use std::fmt; 4 | use std::future::Future; 5 | use std::pin::Pin; 6 | use std::task::{self, Poll}; 7 | 8 | use heph_inbox::{self as inbox, Receiver, RecvValue}; 9 | 10 | use crate::actor_ref::ActorRef; 11 | 12 | /// The context in which an actor is executed. 13 | /// 14 | /// This context can be used for a number of things including receiving messages 15 | /// and getting access to the runtime which is running the actor (`RT`). 16 | #[derive(Debug)] 17 | pub struct Context { 18 | /// Inbox of the actor, shared between this and zero or more actor 19 | /// references. 20 | inbox: Receiver, 21 | /// Runtime access. 22 | rt: RT, 23 | } 24 | 25 | impl Context { 26 | /// Create a new `actor::Context`. 27 | #[doc(hidden)] // Not part of the stable API. 28 | pub const fn new(inbox: Receiver, rt: RT) -> Context { 29 | Context { inbox, rt } 30 | } 31 | 32 | /// Attempt to receive the next message. 33 | /// 34 | /// This will attempt to receive next message if one is available. If the 35 | /// actor wants to wait until a message is received [`receive_next`] can be 36 | /// used, which returns a `Future`. 37 | /// 38 | /// [`receive_next`]: Context::receive_next 39 | /// 40 | /// # Examples 41 | /// 42 | /// An actor that receives a name to greet, or greets the entire world. 43 | /// 44 | /// ``` 45 | /// use heph::actor; 46 | /// 47 | /// async fn greeter_actor(mut ctx: actor::Context) { 48 | /// if let Ok(name) = ctx.try_receive_next() { 49 | /// println!("Hello: {name}"); 50 | /// } else { 51 | /// println!("Hello world"); 52 | /// } 53 | /// } 54 | /// # _ = greeter_actor; // Silence dead code warnings. 55 | /// ``` 56 | pub fn try_receive_next(&mut self) -> Result { 57 | self.inbox.try_recv().map_err(RecvError::from) 58 | } 59 | 60 | /// Receive the next message. 61 | /// 62 | /// This returns a [`Future`] that will complete once a message is ready. 63 | /// 64 | /// # Examples 65 | /// 66 | /// An actor that await a message and prints it. 67 | /// 68 | /// ``` 69 | /// use heph::actor; 70 | /// 71 | /// async fn print_actor(mut ctx: actor::Context) { 72 | /// if let Ok(msg) = ctx.receive_next().await { 73 | /// println!("Got a message: {msg}"); 74 | /// } 75 | /// } 76 | /// # _ = print_actor; // Silence dead code warnings. 77 | /// ``` 78 | pub fn receive_next<'ctx>(&'ctx mut self) -> ReceiveMessage<'ctx, M> { 79 | ReceiveMessage { 80 | recv: self.inbox.recv(), 81 | } 82 | } 83 | 84 | /// Returns a reference to this actor. 85 | pub fn actor_ref(&self) -> ActorRef { 86 | ActorRef::local(self.inbox.new_sender()) 87 | } 88 | 89 | /// Get mutable access to the runtime this actor is running in. 90 | pub fn runtime(&mut self) -> &mut RT { 91 | &mut self.rt 92 | } 93 | 94 | /// Get access to the runtime this actor is running in. 95 | pub const fn runtime_ref(&self) -> &RT { 96 | &self.rt 97 | } 98 | } 99 | 100 | /// Error returned in case receiving a value from an actor's inbox fails. 101 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 102 | pub enum RecvError { 103 | /// Inbox is empty. 104 | Empty, 105 | /// All [`ActorRef`]s are disconnected and the inbox is empty. 106 | Disconnected, 107 | } 108 | 109 | impl RecvError { 110 | pub(crate) const fn from(err: inbox::RecvError) -> RecvError { 111 | match err { 112 | inbox::RecvError::Empty => RecvError::Empty, 113 | inbox::RecvError::Disconnected => RecvError::Disconnected, 114 | } 115 | } 116 | } 117 | 118 | /// Future to receive a single message. 119 | /// 120 | /// The implementation behind and [`actor::Context::receive_next`]. 121 | /// 122 | /// [`actor::Context::receive_next`]: crate::actor::Context::receive_next 123 | #[derive(Debug)] 124 | #[must_use = "futures do nothing unless you `.await` or poll them"] 125 | pub struct ReceiveMessage<'ctx, M> { 126 | recv: RecvValue<'ctx, M>, 127 | } 128 | 129 | impl<'ctx, M> Future for ReceiveMessage<'ctx, M> { 130 | type Output = Result; 131 | 132 | fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> Poll { 133 | Pin::new(&mut self.recv) 134 | .poll(ctx) 135 | .map(|r| r.ok_or(NoMessages)) 136 | } 137 | } 138 | 139 | /// Returned when an actor's inbox has no messages and no references to the 140 | /// actor exists. 141 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 142 | pub struct NoMessages; 143 | 144 | impl fmt::Display for NoMessages { 145 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 146 | f.write_str("no messages in inbox") 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Heph, derived from [Hephaestus], is the Greek god of blacksmiths, 2 | //! metalworking, carpenters, craftsmen, artisans, sculptors, metallurgy, fire, 3 | //! and volcanoes. Well, this crate has very little to do with Greek gods, but I 4 | //! needed a name. 5 | //! 6 | //! [Hephaestus]: https://en.wikipedia.org/wiki/Hephaestus 7 | //! 8 | //! ## About 9 | //! 10 | //! Heph is an [actor] library based on asynchronous functions. Such 11 | //! asynchronous functions look like this: 12 | //! 13 | //! ``` 14 | //! use heph::actor; 15 | //! 16 | //! async fn actor(mut ctx: actor::Context) { 17 | //! // Receive a message. 18 | //! if let Ok(msg) = ctx.receive_next().await { 19 | //! // Print the message. 20 | //! println!("got a message: {msg}"); 21 | //! } 22 | //! } 23 | //! # _ = actor; // Silence dead code warnings. 24 | //! ``` 25 | //! 26 | //! This simple example shows two properties of Heph: 27 | //! * it's asynchronous nature using `async fn`, 28 | //! * communication between actors by sending messages, which follows the [actor model]. 29 | //! 30 | //! [actor]: https://en.wikipedia.org/wiki/Actor_model 31 | //! [actor model]: https://en.wikipedia.org/wiki/Actor_model 32 | //! 33 | //! ## Getting started 34 | //! 35 | //! There are two ways to get starting with Heph. If you like to see examples, 36 | //! take a look at the [examples] in the examples directory of the source code. 37 | //! If you like to learn more about some of the core concepts of Heph start with 38 | //! the [Quick Start Guide]. 39 | //! 40 | //! [examples]: https://github.com/Thomasdezeeuw/heph/blob/main/examples 41 | //! [Quick Start Guide]: crate::quick_start 42 | //! 43 | //! ## Features 44 | //! 45 | //! This crate has one optional feature: `test`. The `test` feature will enable 46 | //! the `test` module which contains testing facilities. 47 | 48 | #![feature(doc_auto_cfg, doc_cfg_hide, never_type, thread_raw)] 49 | #![warn( 50 | anonymous_parameters, 51 | bare_trait_objects, 52 | missing_debug_implementations, 53 | missing_docs, 54 | rust_2018_idioms, 55 | trivial_numeric_casts, 56 | unused_extern_crates, 57 | unused_import_braces, 58 | unused_qualifications, 59 | unused_results, 60 | variant_size_differences 61 | )] 62 | // Disallow warnings when running tests. 63 | #![cfg_attr(test, deny(warnings))] 64 | // Disallow warnings in examples, we want to set a good example after all. 65 | #![doc(test(attr(deny(warnings))))] 66 | // The `cfg(any(test, feature = "test"))` attribute creates a doc element 67 | // staying that it's only supporting "using test or test", that is a bit 68 | // confusing. So we hide those parts and instead manually replace all of them 69 | // with: `doc(cfg(feature = "test"))`. That will stay it's only supported using 70 | // the test feature. 71 | #![doc(cfg_hide(any(test, feature = "test")))] 72 | 73 | pub mod actor; 74 | pub mod actor_ref; 75 | pub mod future; 76 | pub mod messages; 77 | pub mod quick_start; 78 | pub mod supervisor; 79 | pub mod sync; 80 | #[cfg(any(test, feature = "test"))] 81 | pub mod test; 82 | 83 | #[doc(no_inline)] 84 | pub use actor::{actor_fn, Actor, NewActor}; 85 | #[doc(no_inline)] 86 | pub use actor_ref::ActorRef; 87 | #[doc(no_inline)] 88 | pub use future::{ActorFuture, ActorFutureBuilder}; 89 | #[doc(no_inline)] 90 | pub use supervisor::{Supervisor, SupervisorStrategy, SyncSupervisor}; 91 | #[doc(no_inline)] 92 | pub use sync::{SyncActor, SyncActorRunner, SyncActorRunnerBuilder}; 93 | 94 | /// Attempts to extract a message from a panic, defaulting to ``. 95 | /// NOTE: be sure to derefence the `Box`! 96 | #[doc(hidden)] // Not part of the stable API. 97 | pub fn panic_message<'a>(panic: &'a (dyn std::any::Any + Send + 'static)) -> &'a str { 98 | match panic.downcast_ref::<&'static str>() { 99 | Some(s) => s, 100 | None => match panic.downcast_ref::() { 101 | Some(s) => s, 102 | None => "", 103 | }, 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | //! Testing facilities. 2 | //! 3 | //! # Notes 4 | //! 5 | //! *This module is only available when the `test` feature is enabled*. It 6 | //! shouldn't be enabled by default, and shouldn't end up in your production 7 | //! binary. 8 | //! 9 | //! It is possible to only enable the test feature when testing by adding the 10 | //! following to `Cargo.toml`. 11 | //! 12 | //! ```toml 13 | //! [dev-dependencies.heph] 14 | //! features = ["test"] 15 | //! ``` 16 | 17 | use std::any::Any; 18 | use std::sync::atomic::{AtomicU8, Ordering}; 19 | use std::{fmt, panic, slice}; 20 | 21 | use getrandom::getrandom; 22 | use log::warn; 23 | 24 | use crate::supervisor::{Supervisor, SupervisorStrategy, SyncSupervisor}; 25 | use crate::{Actor, NewActor, SyncActor}; 26 | 27 | /// Percentage of messages lost on purpose. 28 | static MSG_LOSS: AtomicU8 = AtomicU8::new(0); 29 | 30 | /// Set the percentage of messages lost on purpose. 31 | /// 32 | /// This is useful to test the resilience of actors with respect to message 33 | /// loss. Any and all messages send, thus including remote and local messages, 34 | /// could be lost on purpose when using this function. 35 | /// 36 | /// Note that the sending of the messages will not return an error if the 37 | /// message is lost using this function. 38 | /// 39 | /// `percent` must be number between `0` and `100`, setting this to `0` (the 40 | /// default) will disable the message loss. 41 | pub fn set_message_loss(mut percent: u8) { 42 | if percent > 100 { 43 | percent = 100; 44 | } 45 | MSG_LOSS.store(percent, Ordering::Release); 46 | } 47 | 48 | /// Returns `true` if the message should be lost. 49 | pub(crate) fn should_lose_msg() -> bool { 50 | // SAFETY: `Relaxed` is fine here as we'll get the update, sending a message 51 | // when we're not supposed to isn't too bad. 52 | let loss = MSG_LOSS.load(Ordering::Relaxed); 53 | loss != 0 || random_percentage() < loss 54 | } 55 | 56 | /// Returns a number between [0, 100]. 57 | fn random_percentage() -> u8 { 58 | let mut p = 0; 59 | if let Err(err) = getrandom(slice::from_mut(&mut p)) { 60 | warn!("error getting random bytes: {err}"); 61 | 100 62 | } else { 63 | p % 100 64 | } 65 | } 66 | 67 | /// Returns the size of the actor. 68 | /// 69 | /// When using asynchronous function for actors see [`size_of_actor_val`]. 70 | pub const fn size_of_actor() -> usize 71 | where 72 | NA: NewActor, 73 | { 74 | size_of::() 75 | } 76 | 77 | /// Returns the size of the point-to actor. 78 | /// 79 | /// # Examples 80 | /// 81 | /// ``` 82 | /// use heph::actor::{self, actor_fn}; 83 | /// use heph::test::size_of_actor_val; 84 | /// 85 | /// async fn actor(mut ctx: actor::Context) { 86 | /// // Receive a message. 87 | /// if let Ok(msg) = ctx.receive_next().await { 88 | /// // Print the message. 89 | /// println!("got a message: {msg}"); 90 | /// } 91 | /// } 92 | /// 93 | /// assert_eq!(size_of_actor_val(&actor_fn(actor)), 56); 94 | /// ``` 95 | pub const fn size_of_actor_val(_: &NA) -> usize 96 | where 97 | NA: NewActor, 98 | { 99 | size_of_actor::() 100 | } 101 | 102 | /// Quick and dirty supervisor that panics whenever it receives an error. 103 | #[derive(Copy, Clone, Debug)] 104 | pub struct PanicSupervisor; 105 | 106 | impl Supervisor for PanicSupervisor 107 | where 108 | NA: NewActor, 109 | NA::Error: fmt::Display, 110 | ::Error: fmt::Display, 111 | { 112 | fn decide(&mut self, err: ::Error) -> SupervisorStrategy { 113 | let name = NA::name(); 114 | panic!("error running '{name}' actor: {err}") 115 | } 116 | 117 | fn decide_on_restart_error(&mut self, err: NA::Error) -> SupervisorStrategy { 118 | // NOTE: should never be called. 119 | let name = NA::name(); 120 | panic!("error restarting '{name}' actor: {err}") 121 | } 122 | 123 | fn second_restart_error(&mut self, err: NA::Error) { 124 | // NOTE: should never be called. 125 | let name = NA::name(); 126 | panic!("error restarting '{name}' actor a second time: {err}") 127 | } 128 | 129 | fn decide_on_panic( 130 | &mut self, 131 | panic: Box, 132 | ) -> SupervisorStrategy { 133 | panic::resume_unwind(panic) 134 | } 135 | } 136 | 137 | impl SyncSupervisor for PanicSupervisor 138 | where 139 | A: SyncActor, 140 | A::Error: fmt::Display, 141 | { 142 | fn decide(&mut self, err: A::Error) -> SupervisorStrategy { 143 | let name = A::name(); 144 | panic!("error running '{name}' actor: {err}") 145 | } 146 | 147 | fn decide_on_panic( 148 | &mut self, 149 | panic: Box, 150 | ) -> SupervisorStrategy { 151 | panic::resume_unwind(panic) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /tests/functional.rs: -------------------------------------------------------------------------------- 1 | //! Functional tests. 2 | 3 | #![feature(never_type)] 4 | 5 | mod util { 6 | use std::future::Future; 7 | use std::pin::{pin, Pin}; 8 | use std::task::{self, Poll}; 9 | 10 | use heph::{actor, sync, Actor, NewActor, SyncActor}; 11 | 12 | pub(crate) fn assert_send() {} 13 | 14 | pub(crate) fn assert_sync() {} 15 | 16 | #[track_caller] 17 | pub(crate) fn assert_size(expected: usize) { 18 | assert_eq!(size_of::(), expected); 19 | } 20 | 21 | pub(crate) fn block_on(fut: Fut) -> Fut::Output { 22 | let mut fut = pin!(fut); 23 | let mut ctx = task::Context::from_waker(task::Waker::noop()); 24 | loop { 25 | match fut.as_mut().poll(&mut ctx) { 26 | Poll::Ready(output) => return output, 27 | Poll::Pending => {} 28 | } 29 | } 30 | } 31 | 32 | pub(crate) fn poll_once(fut: Pin<&mut Fut>) { 33 | let mut ctx = task::Context::from_waker(task::Waker::noop()); 34 | match fut.poll(&mut ctx) { 35 | Poll::Ready(_) => panic!("unexpected output"), 36 | Poll::Pending => {} 37 | } 38 | } 39 | 40 | pub(crate) struct EmptyNewActor; 41 | 42 | impl NewActor for EmptyNewActor { 43 | type Message = !; 44 | type Argument = (); 45 | type Actor = EmptyActor; 46 | type Error = &'static str; 47 | type RuntimeAccess = (); 48 | 49 | fn new( 50 | &mut self, 51 | _: actor::Context, 52 | _: Self::Argument, 53 | ) -> Result { 54 | Ok(EmptyActor) 55 | } 56 | } 57 | 58 | pub(crate) struct EmptyActor; 59 | 60 | impl Actor for EmptyActor { 61 | type Error = &'static str; 62 | fn try_poll( 63 | self: Pin<&mut Self>, 64 | _: &mut task::Context<'_>, 65 | ) -> Poll> { 66 | Poll::Ready(Ok(())) 67 | } 68 | } 69 | 70 | impl SyncActor for EmptyActor { 71 | type Message = !; 72 | type Argument = (); 73 | type Error = &'static str; 74 | type RuntimeAccess = (); 75 | 76 | fn run( 77 | &self, 78 | _: sync::Context, 79 | _: Self::Argument, 80 | ) -> Result<(), Self::Error> { 81 | Ok(()) 82 | } 83 | } 84 | } 85 | 86 | #[path = "functional"] // rustfmt can't find the files. 87 | mod functional { 88 | mod actor; 89 | mod actor_group; 90 | mod actor_ref; 91 | mod restart_supervisor; 92 | mod stop_supervisor; 93 | mod sync_actor; 94 | mod test; 95 | } 96 | -------------------------------------------------------------------------------- /tests/functional/actor.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the [`Actor`] trait. 2 | 3 | use heph::actor::{self, actor_fn, NewActor}; 4 | 5 | #[test] 6 | fn future_output_result() { 7 | // Actor is implemented for `Future>`. 8 | async fn actor(_: actor::Context<(), ()>) -> Result<(), ()> { 9 | Ok(()) 10 | } 11 | is_new_actor(actor_fn(actor)); 12 | } 13 | 14 | #[test] 15 | fn future_output_tuple() { 16 | // Actor is implemented for `Future`. 17 | async fn actor(_: actor::Context<(), ()>) {} 18 | is_new_actor(actor_fn(actor)); 19 | } 20 | 21 | fn is_new_actor(_: NA) {} 22 | -------------------------------------------------------------------------------- /tests/functional/actor_group.rs: -------------------------------------------------------------------------------- 1 | //! Tests related to `ActorGroup`. 2 | 3 | use std::pin::pin; 4 | 5 | use heph::actor_ref::{ActorGroup, SendError}; 6 | use heph::future::{ActorFuture, ActorFutureBuilder, InboxSize}; 7 | use heph::supervisor::NoSupervisor; 8 | use heph::{actor, actor_fn}; 9 | 10 | use crate::util::{assert_send, assert_size, assert_sync, block_on, poll_once}; 11 | 12 | #[test] 13 | fn size() { 14 | assert_size::>(32); 15 | } 16 | 17 | #[test] 18 | fn is_send_sync() { 19 | assert_send::>(); 20 | assert_sync::>(); 21 | 22 | // UnsafeCell is !Sync and Send, our reference should still be Send and 23 | // Sync. 24 | assert_send::>>(); 25 | assert_sync::>>(); 26 | } 27 | 28 | #[test] 29 | fn empty() { 30 | let group = ActorGroup::<()>::empty(); 31 | assert_eq!(group.len(), 0); 32 | assert!(group.is_empty()); 33 | } 34 | 35 | #[test] 36 | fn make_unique_empty() { 37 | let mut group = ActorGroup::<()>::empty(); 38 | assert_eq!(group.len(), 0); 39 | group.make_unique(); 40 | assert_eq!(group.len(), 0); 41 | } 42 | 43 | #[test] 44 | fn try_send_to_one() { 45 | let (future, actor_ref) = ActorFuture::new(NoSupervisor, actor_fn(count_actor), 1).unwrap(); 46 | 47 | let group = ActorGroup::from(actor_ref); 48 | assert_eq!(group.try_send_to_one(()), Ok(())); 49 | drop(group); 50 | 51 | block_on(future); 52 | } 53 | 54 | #[test] 55 | fn try_send_to_one_full_inbox() { 56 | let (future, actor_ref) = ActorFutureBuilder::new() 57 | .with_inbox_size(InboxSize::ONE) 58 | .build(NoSupervisor, actor_fn(count_actor), 1) 59 | .unwrap(); 60 | 61 | let group = ActorGroup::from(actor_ref); 62 | assert_eq!(group.try_send_to_one(()), Ok(())); 63 | assert_eq!(group.try_send_to_one(()), Err(SendError)); 64 | drop(group); 65 | 66 | block_on(future); 67 | } 68 | 69 | #[test] 70 | fn try_send_to_one_empty() { 71 | let group = ActorGroup::<()>::empty(); 72 | assert_eq!(group.try_send_to_one(()), Err(SendError)); 73 | } 74 | 75 | #[test] 76 | fn send_to_one() { 77 | let (future, actor_ref) = ActorFuture::new(NoSupervisor, actor_fn(count_actor), 1).unwrap(); 78 | 79 | let group = ActorGroup::from(actor_ref); 80 | assert_eq!(block_on(group.send_to_one(())), Ok(())); 81 | drop(group); 82 | 83 | block_on(future); 84 | } 85 | 86 | #[test] 87 | fn send_to_one_full_inbox() { 88 | let (future, actor_ref) = ActorFutureBuilder::new() 89 | .with_inbox_size(InboxSize::ONE) 90 | .build(NoSupervisor, actor_fn(count_actor), 1) 91 | .unwrap(); 92 | let mut future = pin!(future); 93 | 94 | let group = ActorGroup::from(actor_ref); 95 | assert_eq!(block_on(group.send_to_one(())), Ok(())); 96 | 97 | { 98 | let mut send_future = pin!(group.send_to_one(())); 99 | poll_once(send_future.as_mut()); // Should return Poll::Pending. 100 | 101 | // Emptying the actor's inbox should allow us to send the message. 102 | poll_once(future.as_mut()); 103 | assert_eq!(block_on(send_future), Ok(())); 104 | } // Drops `send_future`, to allow us to drop `group`. 105 | drop(group); 106 | 107 | block_on(future); 108 | } 109 | 110 | #[test] 111 | fn send_to_one_empty() { 112 | let group = ActorGroup::<()>::empty(); 113 | assert_eq!(block_on(group.send_to_one(())), Err(SendError)); 114 | } 115 | 116 | #[test] 117 | fn try_send_to_all() { 118 | let (future1, actor_ref1) = ActorFuture::new(NoSupervisor, actor_fn(count_actor), 1).unwrap(); 119 | let (future2, actor_ref2) = ActorFuture::new(NoSupervisor, actor_fn(count_actor), 1).unwrap(); 120 | 121 | let group = ActorGroup::new([actor_ref1, actor_ref2]); 122 | assert_eq!(group.try_send_to_all(()), Ok(())); 123 | drop(group); 124 | 125 | block_on(future1); 126 | block_on(future2); 127 | } 128 | 129 | #[test] 130 | fn try_send_to_all_full_inbox() { 131 | let (future1, actor_ref1) = ActorFutureBuilder::new() 132 | .with_inbox_size(InboxSize::ONE) 133 | .build(NoSupervisor, actor_fn(count_actor), 1) 134 | .unwrap(); 135 | let (future2, actor_ref2) = ActorFutureBuilder::new() 136 | .with_inbox_size(InboxSize::ONE) 137 | .build(NoSupervisor, actor_fn(count_actor), 1) 138 | .unwrap(); 139 | 140 | let group = ActorGroup::new([actor_ref1, actor_ref2]); 141 | assert_eq!(group.try_send_to_all(()), Ok(())); 142 | assert_eq!(group.try_send_to_all(()), Ok(())); 143 | drop(group); 144 | 145 | block_on(future1); 146 | block_on(future2); 147 | } 148 | 149 | #[test] 150 | fn try_send_to_all_empty() { 151 | let group = ActorGroup::<()>::empty(); 152 | assert_eq!(group.try_send_to_all(()), Err(SendError)); 153 | } 154 | 155 | async fn count_actor(mut ctx: actor::Context<(), ()>, expected_amount: usize) { 156 | let mut amount = 0; 157 | while let Ok(()) = ctx.receive_next().await { 158 | amount += 1; 159 | } 160 | assert_eq!(amount, expected_amount); 161 | } 162 | -------------------------------------------------------------------------------- /tests/functional/actor_ref.rs: -------------------------------------------------------------------------------- 1 | //! Tests related to `ActorRef`. 2 | 3 | use heph::actor_ref::{ActorRef, Join, RpcError, SendError, SendValue}; 4 | 5 | use crate::util::{assert_send, assert_size, assert_sync}; 6 | 7 | #[test] 8 | fn size() { 9 | assert_size::>(24); 10 | assert_size::>(40); 11 | assert_size::>(32); 12 | } 13 | 14 | #[test] 15 | fn is_send_sync() { 16 | assert_send::>(); 17 | assert_sync::>(); 18 | 19 | // UnsafeCell is !Sync and Send, our reference should still be Send and 20 | // Sync. 21 | assert_send::>>(); 22 | assert_sync::>>(); 23 | } 24 | 25 | #[test] 26 | fn send_value_future_is_send_sync() { 27 | assert_send::>(); 28 | assert_sync::>(); 29 | } 30 | 31 | #[test] 32 | fn send_error_format() { 33 | assert_eq!(format!("{}", SendError), "unable to send message"); 34 | } 35 | 36 | #[test] 37 | fn rpc_error_format() { 38 | assert_eq!(format!("{}", RpcError::SendError), "unable to send message"); 39 | assert_eq!(format!("{}", RpcError::SendError), format!("{}", SendError)); 40 | assert_eq!(format!("{}", RpcError::NoResponse), "no RPC response"); 41 | } 42 | -------------------------------------------------------------------------------- /tests/functional/stop_supervisor.rs: -------------------------------------------------------------------------------- 1 | use std::panic; 2 | 3 | use heph::supervisor::{StopSupervisor, Supervisor, SupervisorStrategy, SyncSupervisor}; 4 | 5 | use crate::util::{EmptyActor, EmptyNewActor}; 6 | 7 | #[test] 8 | fn stop_supervisor_decide() { 9 | assert_eq!( 10 | Supervisor::::decide(&mut StopSupervisor, "first error"), 11 | SupervisorStrategy::Stop 12 | ); 13 | } 14 | 15 | #[test] 16 | fn stop_supervisor_decide_on_restart_error() { 17 | assert_eq!( 18 | Supervisor::::decide_on_restart_error(&mut StopSupervisor, "restart error"), 19 | SupervisorStrategy::Stop 20 | ); 21 | } 22 | 23 | #[test] 24 | fn stop_supervisor_second_restart_error() { 25 | Supervisor::::second_restart_error(&mut StopSupervisor, "second restart error"); 26 | } 27 | 28 | #[test] 29 | fn stop_supervisor_decide_on_panic() { 30 | let panic = panic::catch_unwind(|| panic!("original panic")).unwrap_err(); 31 | assert_eq!( 32 | Supervisor::::decide_on_panic(&mut StopSupervisor, panic), 33 | SupervisorStrategy::Stop 34 | ); 35 | } 36 | 37 | #[test] 38 | fn stop_supervisor_sync_decide() { 39 | assert_eq!( 40 | SyncSupervisor::::decide(&mut StopSupervisor, "first error"), 41 | SupervisorStrategy::Stop 42 | ); 43 | } 44 | 45 | #[test] 46 | fn stop_supervisor_sync_decide_on_panic() { 47 | let panic = panic::catch_unwind(|| panic!("original panic")).unwrap_err(); 48 | assert_eq!( 49 | SyncSupervisor::::decide_on_panic(&mut StopSupervisor, panic), 50 | SupervisorStrategy::Stop 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /tests/functional/test.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the `test` module. 2 | 3 | use std::panic; 4 | 5 | use heph::actor::{self, actor_fn}; 6 | use heph::test::{size_of_actor, size_of_actor_val, PanicSupervisor}; 7 | use heph::{Supervisor, SyncSupervisor}; 8 | 9 | use crate::util::{EmptyActor, EmptyNewActor}; 10 | 11 | #[test] 12 | fn test_size_of_actor() { 13 | async fn actor1(_: actor::Context) { 14 | /* Nothing. */ 15 | } 16 | 17 | assert_eq!(size_of_actor_val(&actor_fn(actor1)), 24); 18 | 19 | assert_eq!(size_of::(), 0); 20 | assert_eq!(size_of_actor::(), 0); 21 | assert_eq!(size_of_actor_val(&EmptyNewActor), 0); 22 | } 23 | 24 | #[test] 25 | #[should_panic = "error running 'EmptyActor' actor: first error"] 26 | fn panic_supervisor_decide() { 27 | Supervisor::::decide(&mut PanicSupervisor, "first error"); 28 | } 29 | 30 | #[test] 31 | #[should_panic = "error restarting 'EmptyActor' actor: restart error"] 32 | fn panic_supervisor_decide_on_restart_error() { 33 | Supervisor::::decide_on_restart_error(&mut PanicSupervisor, "restart error"); 34 | } 35 | 36 | #[test] 37 | #[should_panic = "error restarting 'EmptyActor' actor a second time: second restart error"] 38 | fn panic_supervisor_second_restart_error() { 39 | Supervisor::::second_restart_error(&mut PanicSupervisor, "second restart error"); 40 | } 41 | 42 | #[test] 43 | #[should_panic = "original panic"] 44 | fn panic_supervisor_decide_on_panic() { 45 | let panic = panic::catch_unwind(|| panic!("original panic")).unwrap_err(); 46 | Supervisor::::decide_on_panic(&mut PanicSupervisor, panic); 47 | } 48 | 49 | #[test] 50 | #[should_panic = "error running 'EmptyActor' actor: first error"] 51 | fn panic_supervisor_sync_decide() { 52 | SyncSupervisor::::decide(&mut PanicSupervisor, "first error"); 53 | } 54 | 55 | #[test] 56 | #[should_panic = "original panic"] 57 | fn panic_supervisor_sync_decide_on_panic() { 58 | let panic = panic::catch_unwind(|| panic!("original panic")).unwrap_err(); 59 | SyncSupervisor::::decide_on_panic(&mut PanicSupervisor, panic); 60 | } 61 | -------------------------------------------------------------------------------- /tests/message_loss.rs: -------------------------------------------------------------------------------- 1 | //! Test the [`set_message_loss`] function. 2 | //! 3 | //! # Notes 4 | //! 5 | //! This function needs to be in it's own binary since `set_message_loss` is set 6 | //! globally. 7 | 8 | use std::future::Future; 9 | use std::pin::{pin, Pin}; 10 | use std::task::{self, Poll}; 11 | 12 | use heph::actor::{self, actor_fn, NoMessages}; 13 | use heph::future::ActorFuture; 14 | use heph::supervisor::NoSupervisor; 15 | use heph::test::set_message_loss; 16 | 17 | async fn expect_1_messages(mut ctx: actor::Context) { 18 | let msg = ctx.receive_next().await.expect("missing first message"); 19 | assert_eq!(msg, 123); 20 | 21 | match ctx.receive_next().await { 22 | Ok(msg) => panic!("unexpected message: {msg:?}"), 23 | Err(NoMessages) => return, 24 | } 25 | } 26 | 27 | #[test] 28 | fn actor_ref_message_loss() { 29 | let (future, actor_ref) = 30 | ActorFuture::new(NoSupervisor, actor_fn(expect_1_messages), ()).unwrap(); 31 | let mut future = pin!(future); 32 | assert_eq!(poll(future.as_mut()), None); 33 | 34 | // Should arrive. 35 | actor_ref.try_send(123_usize).unwrap(); 36 | assert_eq!(poll(future.as_mut()), None); 37 | 38 | // After setting the message loss to 100% no messages should arrive. 39 | set_message_loss(100); 40 | actor_ref.try_send(456_usize).unwrap(); 41 | actor_ref.try_send(789_usize).unwrap(); 42 | assert_eq!(poll(future.as_mut()), None); 43 | 44 | drop(actor_ref); 45 | assert_eq!(poll(future.as_mut()), Some(())); 46 | } 47 | 48 | pub fn poll(fut: Pin<&mut Fut>) -> Option { 49 | let mut ctx = task::Context::from_waker(task::Waker::noop()); 50 | match fut.poll(&mut ctx) { 51 | Poll::Ready(output) => Some(output), 52 | Poll::Pending => None, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "heph-tools" 3 | version = "0.1.0" 4 | authors = ["Thomas de Zeeuw "] 5 | edition = "2021" 6 | --------------------------------------------------------------------------------