├── .cargo └── config.toml ├── .github └── workflows │ ├── rust-clippy.yml │ └── rust.yml ├── .gitignore ├── .misc ├── ENQT.png └── Remoc.png ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTORS ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── SECURITY.md ├── check_version.sh ├── codecov.yml ├── examples └── rtc │ ├── README.md │ ├── counter-client │ ├── Cargo.toml │ └── src │ │ └── main.rs │ ├── counter-server │ ├── Cargo.toml │ └── src │ │ └── main.rs │ └── counter │ ├── Cargo.toml │ └── src │ └── lib.rs ├── publish.sh ├── remoc ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── src │ ├── chmux │ │ ├── any_storage.rs │ │ ├── cfg.rs │ │ ├── client.rs │ │ ├── credit.rs │ │ ├── forward.rs │ │ ├── listener.rs │ │ ├── mod.rs │ │ ├── msg.rs │ │ ├── mux.rs │ │ ├── port_allocator.rs │ │ ├── receiver.rs │ │ └── sender.rs │ ├── codec │ │ ├── bincode.rs │ │ ├── ciborium.rs │ │ ├── json.rs │ │ ├── map │ │ │ ├── btreemap.rs │ │ │ ├── hashmap.rs │ │ │ └── mod.rs │ │ ├── message_pack.rs │ │ ├── mod.rs │ │ └── postcard.rs │ ├── connect.rs │ ├── connect_ext.rs │ ├── doctest.rs │ ├── exec │ │ ├── js │ │ │ ├── mod.rs │ │ │ ├── runtime.rs │ │ │ ├── sync_wrapper.rs │ │ │ ├── task.rs │ │ │ ├── thread_pool.rs │ │ │ └── time.rs │ │ ├── mod.rs │ │ └── native │ │ │ └── mod.rs │ ├── lib.rs │ ├── prelude.rs │ ├── provider.rs │ ├── rch │ │ ├── base │ │ │ ├── io.rs │ │ │ ├── mod.rs │ │ │ ├── receiver.rs │ │ │ └── sender.rs │ │ ├── bin │ │ │ ├── mod.rs │ │ │ ├── receiver.rs │ │ │ └── sender.rs │ │ ├── broadcast │ │ │ ├── mod.rs │ │ │ ├── receiver.rs │ │ │ └── sender.rs │ │ ├── interlock.rs │ │ ├── lr │ │ │ ├── mod.rs │ │ │ ├── receiver.rs │ │ │ └── sender.rs │ │ ├── mod.rs │ │ ├── mpsc │ │ │ ├── distributor.rs │ │ │ ├── mod.rs │ │ │ ├── receiver.rs │ │ │ └── sender.rs │ │ ├── oneshot │ │ │ ├── mod.rs │ │ │ ├── receiver.rs │ │ │ └── sender.rs │ │ └── watch │ │ │ ├── mod.rs │ │ │ ├── receiver.rs │ │ │ └── sender.rs │ ├── remote_send.rs │ ├── rfn │ │ ├── mod.rs │ │ ├── msg.rs │ │ ├── rfn_const.rs │ │ ├── rfn_mut.rs │ │ └── rfn_once.rs │ ├── robj │ │ ├── handle.rs │ │ ├── lazy.rs │ │ ├── lazy_blob │ │ │ ├── fw_bin.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── rw_lock │ │ │ ├── mod.rs │ │ │ ├── msg.rs │ │ │ ├── owner.rs │ │ │ └── rw_lock.rs │ ├── robs │ │ ├── hash_map.rs │ │ ├── hash_set.rs │ │ ├── list.rs │ │ ├── mod.rs │ │ └── vec.rs │ └── rtc │ │ └── mod.rs └── tests │ ├── chmux │ ├── channel.rs │ ├── mod.rs │ ├── tcp.rs │ └── unix.rs │ ├── codec │ └── mod.rs │ ├── rch │ ├── bin.rs │ ├── broadcast.rs │ ├── lr.rs │ ├── mod.rs │ ├── mpsc.rs │ ├── oneshot.rs │ ├── remote.rs │ └── watch.rs │ ├── rfn │ ├── mod.rs │ ├── rfn_const.rs │ ├── rfn_mut.rs │ └── rfn_once.rs │ ├── robj │ ├── handle.rs │ ├── lazy.rs │ ├── lazy_blob.rs │ ├── mod.rs │ └── rw_lock.rs │ ├── robs │ ├── hash_map.rs │ ├── hash_set.rs │ ├── list.rs │ ├── mod.rs │ └── vec.rs │ ├── rtc │ ├── default.rs │ ├── errors.rs │ ├── generics.rs │ ├── lifetime.rs │ ├── mod.rs │ ├── readonly.rs │ ├── simple.rs │ ├── simple_clone.rs │ ├── simple_req.rs │ └── value.rs │ └── tests.rs ├── remoc_macro ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md └── src │ ├── lib.rs │ ├── method.rs │ ├── trait_def.rs │ └── util.rs └── rustfmt.toml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-unknown] 2 | runner = "wasm-bindgen-test-runner" 3 | rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""] 4 | 5 | [target.wasm32-wasip1] 6 | # for pure WASI environment: 7 | runner = "wasmtime -S inherit-env" 8 | 9 | # for WASI on the web (`js` feature enabled): 10 | #runner = "wasm-bindgen-test-runner" 11 | #rustflags = ["-Z", "wasi-exec-model=reactor", "--cfg", "getrandom_backend=\"wasm_js\""] 12 | 13 | 14 | [target.wasm32-wasip1-threads] 15 | # for pure WASI environment: 16 | runner = "wasmtime -S threads -S inherit-env" 17 | 18 | # for WASI on the web (`js` feature enabled): 19 | #runner = "wasm-bindgen-test-runner" 20 | #rustflags = ["-Z", "wasi-exec-model=reactor", "--cfg", "getrandom_backend=\"wasm_js\""] 21 | -------------------------------------------------------------------------------- /.github/workflows/rust-clippy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # rust-clippy is a tool that runs a bunch of lints to catch common 6 | # mistakes in your Rust code and help improve your Rust code. 7 | # More details at https://github.com/rust-lang/rust-clippy 8 | # and https://rust-lang.github.io/rust-clippy/ 9 | 10 | name: rust-clippy analyze 11 | 12 | on: 13 | push: 14 | branches: [ "master" ] 15 | pull_request: 16 | # The branches below must be a subset of the branches above 17 | branches: [ "master" ] 18 | schedule: 19 | - cron: '35 16 * * 6' 20 | 21 | jobs: 22 | rust-clippy-analyze: 23 | name: Run rust-clippy analyzing 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | security-events: write 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v2 31 | 32 | - name: Install Rust toolchain 33 | uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 34 | with: 35 | profile: minimal 36 | toolchain: nightly 37 | components: clippy 38 | override: true 39 | 40 | - name: Install required cargo 41 | run: cargo install clippy-sarif sarif-fmt 42 | 43 | - name: Run rust-clippy 44 | run: 45 | cargo clippy 46 | --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 47 | continue-on-error: true 48 | 49 | - name: Upload analysis results to GitHub 50 | uses: github/codeql-action/upload-sarif@v1 51 | with: 52 | sarif_file: rust-clippy-results.sarif 53 | wait-for-processing: true 54 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | rustfmt: 12 | name: Check formatting and version dependencies 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout sources 17 | uses: actions/checkout@v2 18 | 19 | - name: Check version dependencies 20 | run: ./check_version.sh 21 | 22 | - name: Run cargo fmt 23 | run: cargo fmt --all -- --check 24 | 25 | msrv: 26 | name: Check with MSRV 27 | runs-on: ubuntu-latest 28 | env: 29 | minrust: 1.81 30 | 31 | steps: 32 | - name: Checkout sources 33 | uses: actions/checkout@v2 34 | 35 | - name: Install Rust ${{ env.minrust }} 36 | run: rustup default ${{ env.minrust }} 37 | 38 | - name: Cache dependencies 39 | uses: Swatinem/rust-cache@v1 40 | 41 | - name: Run cargo check 42 | run: cargo check 43 | 44 | # Lints 45 | build-docs: 46 | name: Build docs 47 | needs: [msrv] 48 | runs-on: ubuntu-latest 49 | 50 | steps: 51 | - name: Checkout sources 52 | uses: actions/checkout@v2 53 | 54 | - name: Cache dependencies 55 | uses: Swatinem/rust-cache@v1 56 | 57 | - name: Build docs 58 | env: 59 | RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links 60 | run: cargo doc --no-deps 61 | 62 | check-examples: 63 | name: Check examples 64 | needs: [msrv] 65 | runs-on: ubuntu-latest 66 | 67 | steps: 68 | - name: Checkout sources 69 | uses: actions/checkout@v2 70 | 71 | - name: Cache dependencies 72 | uses: Swatinem/rust-cache@v1 73 | 74 | - name: Check examples 75 | run: | 76 | cargo check --manifest-path examples/rtc/counter-client/Cargo.toml 77 | cargo check --manifest-path examples/rtc/counter-server/Cargo.toml 78 | 79 | clippy: 80 | name: Clippy 81 | needs: [msrv] 82 | runs-on: ubuntu-latest 83 | 84 | steps: 85 | - name: Checkout sources 86 | uses: actions/checkout@v2 87 | 88 | - name: Cache dependencies 89 | uses: Swatinem/rust-cache@v1 90 | 91 | - name: Install nightly Rust 92 | run: | 93 | rustup install nightly 94 | rustup component add --toolchain nightly clippy 95 | 96 | - name: Run clippy 97 | run: cargo +nightly clippy --tests 98 | 99 | # Tests 100 | test: 101 | name: Test with default features 102 | needs: [msrv] 103 | runs-on: ubuntu-latest 104 | 105 | steps: 106 | - name: Checkout sources 107 | uses: actions/checkout@v2 108 | 109 | - name: Cache dependencies 110 | uses: Swatinem/rust-cache@v1 111 | 112 | - name: Run cargo test 113 | run: cargo test 114 | 115 | test-codecs: 116 | name: Test with codec ${{ matrix.codec }} 117 | needs: [test] 118 | runs-on: ubuntu-latest 119 | 120 | strategy: 121 | fail-fast: false 122 | matrix: 123 | codec: 124 | - bincode 125 | - bincode2 126 | - ciborium 127 | - message-pack 128 | - postcard 129 | 130 | steps: 131 | - name: Checkout sources 132 | uses: actions/checkout@v2 133 | 134 | - name: Cache dependencies 135 | uses: Swatinem/rust-cache@v1 136 | 137 | - name: Run cargo test 138 | env: 139 | RUSTFLAGS: -A deprecated 140 | run: cargo test --no-default-features --features full --features default-codec-${{ matrix.codec }} 141 | 142 | test-features: 143 | name: Test ${{ matrix.feature }} feature 144 | needs: [test] 145 | runs-on: ubuntu-latest 146 | 147 | strategy: 148 | fail-fast: false 149 | matrix: 150 | feature: 151 | - rch 152 | - rfn 153 | - robj 154 | - robs 155 | - rtc 156 | 157 | steps: 158 | - name: Checkout sources 159 | uses: actions/checkout@v2 160 | 161 | - name: Cache dependencies 162 | uses: Swatinem/rust-cache@v1 163 | 164 | - name: Run cargo test 165 | run: cargo test --no-default-features --features ${{ matrix.feature }} --features default-codec-json 166 | 167 | check-features-without-codec: 168 | name: Check ${{ matrix.feature }} feature without codec 169 | needs: [test] 170 | runs-on: ubuntu-latest 171 | 172 | strategy: 173 | fail-fast: false 174 | matrix: 175 | feature: 176 | - none 177 | - rch 178 | - rfn 179 | - robj 180 | - robs 181 | - rtc 182 | 183 | steps: 184 | - name: Checkout sources 185 | uses: actions/checkout@v2 186 | 187 | - name: Cache dependencies 188 | uses: Swatinem/rust-cache@v1 189 | 190 | - name: Run cargo check 191 | run: | 192 | if [ "${{ matrix.feature }}" = "none" ] ; then cargo check --no-default-features ; else \ 193 | cargo check --no-default-features --features ${{ matrix.feature }} ; fi 194 | 195 | # Coverage 196 | coverage: 197 | name: Code coverage 198 | needs: [test-codecs, test-features, check-features-without-codec, rustfmt] 199 | runs-on: ubuntu-latest 200 | continue-on-error: true 201 | 202 | steps: 203 | - name: Checkout sources 204 | uses: actions/checkout@v2 205 | 206 | - name: Install cargo tarpaulin 207 | uses: baptiste0928/cargo-install@v1 208 | with: 209 | crate: cargo-tarpaulin 210 | 211 | - name: Cache dependencies 212 | uses: Swatinem/rust-cache@v1 213 | 214 | - name: Run cargo tarpaulin 215 | shell: bash 216 | env: 217 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 218 | run: | 219 | cargo tarpaulin --out Xml 220 | bash <(curl -s https://codecov.io/bash) 221 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | TODO 3 | -------------------------------------------------------------------------------- /.misc/ENQT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ENQT-GmbH/remoc/a2b52827a169cec4e7ef05e0bb4e935f54fc5898/.misc/ENQT.png -------------------------------------------------------------------------------- /.misc/Remoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ENQT-GmbH/remoc/a2b52827a169cec4e7ef05e0bb4e935f54fc5898/.misc/Remoc.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "target": true, 4 | }, 5 | "cSpell.languageSettings": [ 6 | { 7 | "languageId": "rust", 8 | "includeRegExpList": [ 9 | "CStyleComment", 10 | "string", 11 | "^//!" 12 | ], 13 | } 14 | ], 15 | "cSpell.words": [ 16 | "bidir", 17 | "Bincode", 18 | "BTreeMap", 19 | "CBOR", 20 | "chmux", 21 | "clippy", 22 | "clonable", 23 | "codecov", 24 | "deps", 25 | "deserializable", 26 | "deserialization", 27 | "deserializers", 28 | "doctest", 29 | "ENQT", 30 | "forwardable", 31 | "MPSC", 32 | "MSRV", 33 | "oneshot", 34 | "RAII", 35 | "remoc", 36 | "remotable", 37 | "robj", 38 | "rustup", 39 | "Serde", 40 | "serializers", 41 | "struct", 42 | "structs", 43 | "unbuffered", 44 | "untrusted" 45 | ], 46 | "rust-analyzer.cargo.extraEnv": { 47 | "RUSTUP_TOOLCHAIN": "nightly" 48 | }, 49 | "rust-analyzer.cargo.features": [ 50 | "full", 51 | "default-codec-json", 52 | "full-codecs", 53 | // "js", 54 | ], 55 | //"rust-analyzer.cargo.target": "wasm32-unknown-unknown", 56 | //"rust-analyzer.cargo.target": "wasm32-wasip1", 57 | //"rust-analyzer.cargo.target": "wasm32-wasip1-threads", 58 | } -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | This file lists all contributors to remoc in alphabetical order. 2 | 3 | baptiste0928 4 | ENQT GmbH 5 | Firaenix 6 | Sebastian Urban 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | version = "0.15.3" 3 | 4 | edition = "2021" 5 | rust-version = "1.80" 6 | license = "Apache-2.0" 7 | 8 | repository = "https://github.com/ENQT-GmbH/remoc/" 9 | authors = [ 10 | "Sebastian Urban ", 11 | "ENQT GmbH ", 12 | "Remoc contributors", 13 | ] 14 | 15 | [workspace] 16 | resolver = "2" 17 | members = ["remoc", "remoc_macro"] 18 | exclude = ["examples"] 19 | 20 | 21 | # for WASI on the web (wasm32-wasip1-threads target with `js` feature): 22 | # [patch.crates-io] 23 | # js-sys = { git = "https://github.com/rust-wasi-web/wasm-bindgen.git" } 24 | # web-sys = { git = "https://github.com/rust-wasi-web/wasm-bindgen.git" } 25 | # wasm-bindgen = { git = "https://github.com/rust-wasi-web/wasm-bindgen.git" } 26 | # wasm-bindgen-futures = { git = "https://github.com/rust-wasi-web/wasm-bindgen.git" } 27 | # wasm-bindgen-test = { git = "https://github.com/rust-wasi-web/wasm-bindgen.git" } 28 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Remoc — remote multiplexed objects and channels 2 | Copyright 2021-2025 Sebastian Urban 3 | Copyright 2021-2023 Remoc contributors (see CONTRIBUTORS file) 4 | Copyright 2020-2021 ENQT GmbH 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | remoc/README.md -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The latest major release is being supported with security updates. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Use the GitHub private vulnerability reporting function to report a security vulnerability. 10 | -------------------------------------------------------------------------------- /check_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Checks that the remoc and remoc_macro crates have the same version and 4 | # remoc depends on the exact same remoc_macro version. 5 | # 6 | 7 | VERSION=$(grep "^version = " Cargo.toml | cut -d ' ' -f 3 | tr -d \") 8 | MACRO_DEP=$(grep 'remoc_macro = ' remoc/Cargo.toml | cut -d ' ' -f 6 | tr -d \",=) 9 | 10 | if [ "$VERSION" != "$MACRO_DEP" ] ; then 11 | echo "Version mismatch!" 12 | echo "remoc version is $VERSION" 13 | echo "remoc_macro dependency version is $MACRO_DEP" 14 | exit 1 15 | fi 16 | 17 | exit 0 18 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: true 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "70...100" 8 | 9 | parsers: 10 | gcov: 11 | branch_detection: 12 | conditional: yes 13 | loop: yes 14 | method: no 15 | macro: no 16 | 17 | comment: 18 | layout: "reach,diff,flags,files,footer" 19 | behavior: default 20 | require_changes: false 21 | 22 | ignore: 23 | - "remoc/tests/**/*" 24 | - "remoc_macro/**/*" 25 | - "*.sh" 26 | - "*.md" 27 | -------------------------------------------------------------------------------- /examples/rtc/README.md: -------------------------------------------------------------------------------- 1 | # Remote trait calling (RTC) counter example 2 | 3 | This example implements a simple counter over RTC. 4 | The server keeps a counter variable that can be remotely queried and increased 5 | by a client. 6 | Furthermore, it provides notifications to all subscribed clients when the 7 | value is changed. 8 | 9 | It is split into three crates: 10 | 11 | * `counter` provides the remote trait definition and error types shared 12 | between client and server. 13 | * `counter-server` implements the counter server and accepts connections 14 | over TCP. 15 | * `counter-client` implements a simple counter client that exercises the server. 16 | 17 | ## Running 18 | 19 | Start the server using the following command: 20 | 21 | cargo run --manifest-path examples/rtc/counter-server/Cargo.toml 22 | 23 | Then, in another terminal, start the client using the following command: 24 | 25 | cargo run --manifest-path examples/rtc/counter-client/Cargo.toml 26 | 27 | All commands assume that you are in the top-level repository directory. 28 | -------------------------------------------------------------------------------- /examples/rtc/counter-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "counter-client" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | counter = { path = "../counter" } 8 | remoc = { path = "../../../remoc" } 9 | tokio = { version = "1", features = ["rt-multi-thread", "net", "sync", "time"] } 10 | tracing-subscriber = "0.3.7" 11 | -------------------------------------------------------------------------------- /examples/rtc/counter-client/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This crate implements the client of the remote counting service. 2 | 3 | use remoc::prelude::*; 4 | use std::{net::Ipv4Addr, time::Duration}; 5 | use tokio::net::TcpStream; 6 | 7 | use counter::{Counter, CounterClient, TCP_PORT}; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | // Initialize logging. 12 | tracing_subscriber::FmtSubscriber::builder().init(); 13 | 14 | // Establish TCP connection to server. 15 | let socket = TcpStream::connect((Ipv4Addr::LOCALHOST, TCP_PORT)).await.unwrap(); 16 | let (socket_rx, socket_tx) = socket.into_split(); 17 | 18 | // Establish a Remoc connection with default configuration over the TCP connection and 19 | // consume (i.e. receive) the counter client from the server. 20 | let mut client: CounterClient = 21 | remoc::Connect::io(remoc::Cfg::default(), socket_rx, socket_tx).consume().await.unwrap(); 22 | 23 | // Subscribe to the counter watch and print each value change. 24 | println!("Subscribing to counter change notifications"); 25 | let mut watch_rx = client.watch().await.unwrap(); 26 | let watch_task = tokio::spawn(async move { 27 | loop { 28 | while let Ok(()) = watch_rx.changed().await { 29 | let value = watch_rx.borrow_and_update().unwrap(); 30 | println!("Counter change notification: {}", *value); 31 | } 32 | } 33 | }); 34 | println!("Done!"); 35 | 36 | // Print current value. 37 | let value = client.value().await.unwrap(); 38 | println!("Current counter value is {}\n", value); 39 | 40 | // Increase counter value. 41 | println!("Increasing counter value by 5"); 42 | client.increase(5).await.unwrap(); 43 | println!("Done!\n"); 44 | 45 | // Print new value. 46 | let value = client.value().await.unwrap(); 47 | println!("New counter value is {}\n", value); 48 | 49 | // Let the server count to the current value. 50 | println!("Asking the server to count to the current value with a step delay of 300ms..."); 51 | let mut rx = client.count_to_value(1, Duration::from_millis(300)).await.unwrap(); 52 | while let Ok(Some(i)) = rx.recv().await { 53 | println!("Server counts {}", i); 54 | } 55 | println!("Server is done counting.\n"); 56 | 57 | // Wait for watch task. 58 | println!("Server exercise is done."); 59 | println!("You can now press Ctrl+C to exit or continue watching for value "); 60 | println!("change notification caused by other clients."); 61 | watch_task.await.unwrap(); 62 | } 63 | -------------------------------------------------------------------------------- /examples/rtc/counter-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "counter-server" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | counter = { path = "../counter" } 8 | remoc = { path = "../../../remoc" } 9 | tokio = { version = "1", features = ["rt-multi-thread", "net", "sync", "time"] } 10 | tracing-subscriber = "0.3.7" 11 | 12 | -------------------------------------------------------------------------------- /examples/rtc/counter-server/src/main.rs: -------------------------------------------------------------------------------- 1 | //! This crate implements the server of the remote counting service. 2 | 3 | use remoc::{codec, prelude::*}; 4 | use std::{net::Ipv4Addr, sync::Arc, time::Duration}; 5 | use tokio::{net::TcpListener, sync::RwLock, time::sleep}; 6 | 7 | use counter::{Counter, CounterServerSharedMut, IncreaseError, TCP_PORT}; 8 | 9 | /// Server object for the counting service, keeping the state. 10 | #[derive(Default)] 11 | pub struct CounterObj { 12 | /// The current value. 13 | value: u32, 14 | /// The subscribed watchers. 15 | watchers: Vec>, 16 | } 17 | 18 | /// Implementation of remote counting service. 19 | #[rtc::async_trait] 20 | impl Counter for CounterObj { 21 | async fn value(&self) -> Result { 22 | Ok(self.value) 23 | } 24 | 25 | async fn watch(&mut self) -> Result, rtc::CallError> { 26 | // Create watch channel. 27 | let (tx, rx) = rch::watch::channel(self.value); 28 | // Keep the sender half in the watchers vector. 29 | self.watchers.push(tx); 30 | // And return the receiver half. 31 | Ok(rx) 32 | } 33 | 34 | async fn increase(&mut self, by: u32) -> Result<(), IncreaseError> { 35 | // Perform the addition if it does not overflow the counter. 36 | match self.value.checked_add(by) { 37 | Some(new_value) => self.value = new_value, 38 | None => return Err(IncreaseError::Overflow { current_value: self.value }), 39 | } 40 | 41 | // Notify all watchers and keep only the ones that are not disconnected. 42 | let value = self.value; 43 | self.watchers.retain(|watch| !watch.send(value).into_disconnected().unwrap()); 44 | 45 | Ok(()) 46 | } 47 | 48 | async fn count_to_value( 49 | &self, step: u32, delay: Duration, 50 | ) -> Result, rtc::CallError> { 51 | // Create mpsc channel for counting. 52 | let (tx, rx) = rch::mpsc::channel(1); 53 | 54 | // Spawn a task to perform the counting. 55 | let value = self.value; 56 | tokio::spawn(async move { 57 | // Counting loop. 58 | for i in (0..value).step_by(step as usize) { 59 | // Send the value. 60 | if tx.send(i).await.into_disconnected().unwrap() { 61 | // Abort the counting if the client dropped the 62 | // receive half or disconnected. 63 | break; 64 | } 65 | 66 | // Wait the specified delay. 67 | sleep(delay).await; 68 | } 69 | }); 70 | 71 | // Return the receive half of the counting channel. 72 | Ok(rx) 73 | } 74 | } 75 | 76 | #[tokio::main] 77 | async fn main() { 78 | // Initialize logging. 79 | tracing_subscriber::FmtSubscriber::builder().init(); 80 | 81 | // Create a counter object that will be shared between all clients. 82 | // You could also create one counter object per connection. 83 | let counter_obj = Arc::new(RwLock::new(CounterObj::default())); 84 | 85 | // Listen to TCP connections using Tokio. 86 | // In reality you would probably use TLS or WebSockets over HTTPS. 87 | println!("Listening on port {}. Press Ctrl+C to exit.", TCP_PORT); 88 | let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, TCP_PORT)).await.unwrap(); 89 | 90 | loop { 91 | // Accept an incoming TCP connection. 92 | let (socket, addr) = listener.accept().await.unwrap(); 93 | let (socket_rx, socket_tx) = socket.into_split(); 94 | println!("Accepted connection from {}", addr); 95 | 96 | // Create a new shared reference to the counter object. 97 | let counter_obj = counter_obj.clone(); 98 | 99 | // Spawn a task for each incoming connection. 100 | tokio::spawn(async move { 101 | // Create a server proxy and client for the accepted connection. 102 | // 103 | // The server proxy executes all incoming method calls on the shared counter_obj 104 | // with a request queue length of 1. 105 | // 106 | // Current limitations of the Rust compiler require that we explicitly 107 | // specify the codec. 108 | let (server, client) = CounterServerSharedMut::<_, codec::Default>::new(counter_obj, 1); 109 | 110 | // Establish a Remoc connection with default configuration over the TCP connection and 111 | // provide (i.e. send) the counter client to the client. 112 | remoc::Connect::io(remoc::Cfg::default(), socket_rx, socket_tx).provide(client).await.unwrap(); 113 | 114 | // Serve incoming requests from the client on this task. 115 | // `true` indicates that requests are handled in parallel. 116 | server.serve(true).await.unwrap(); 117 | }); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /examples/rtc/counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "counter" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | remoc = { path = "../../../remoc" } 8 | serde = { version = "1.0", features = ["derive"] } 9 | -------------------------------------------------------------------------------- /examples/rtc/counter/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This library crate defines the remote counting service. 2 | //! 3 | //! The client and server depend on it. 4 | 5 | use remoc::prelude::*; 6 | use std::time::Duration; 7 | 8 | /// TCP port the server is listening on. 9 | pub const TCP_PORT: u16 = 9871; 10 | 11 | /// Increasing the counter failed. 12 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 13 | pub enum IncreaseError { 14 | /// An overflow would occur. 15 | Overflow { 16 | /// The current value of the counter. 17 | current_value: u32, 18 | }, 19 | /// The RTC call failed. 20 | Call(rtc::CallError), 21 | } 22 | 23 | impl From for IncreaseError { 24 | fn from(err: rtc::CallError) -> Self { 25 | Self::Call(err) 26 | } 27 | } 28 | 29 | /// Remote counting service. 30 | #[rtc::remote] 31 | pub trait Counter { 32 | /// Obtain the current value of the counter. 33 | async fn value(&self) -> Result; 34 | 35 | /// Watch the current value of the counter for immediate notification 36 | /// when it changes. 37 | async fn watch(&mut self) -> Result, rtc::CallError>; 38 | 39 | /// Increase the counter's value by the provided number. 40 | async fn increase(&mut self, by: u32) -> Result<(), IncreaseError>; 41 | 42 | /// Counts to the current value of the counter with the specified 43 | /// delay between each step. 44 | async fn count_to_value( 45 | &self, step: u32, delay: Duration, 46 | ) -> Result, rtc::CallError>; 47 | } 48 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Publishes all crates to crates.io and tags the version in git. 4 | # Also pushes the tags. 5 | # 6 | 7 | set -e 8 | 9 | echo "Checking crate versions" 10 | ./check_version.sh 11 | 12 | VERSION=$(grep "^version = " Cargo.toml | cut -d ' ' -f 3 | tr -d \") 13 | 14 | echo "Publishing remoc_macro $VERSION" 15 | cargo publish --manifest-path remoc_macro/Cargo.toml 16 | 17 | echo "Publishing remoc $VERSION" 18 | cargo publish --manifest-path remoc/Cargo.toml 19 | 20 | echo "Tagging version in git" 21 | git tag "v$VERSION" 22 | git push 23 | git push --tags 24 | -------------------------------------------------------------------------------- /remoc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "remoc" 3 | description = "🦑 Remote multiplexed objects, channels, observable collections and RPC making remote interactions seamless. Provides multiple remote channels and RPC over TCP, TLS or any other transport." 4 | keywords = ["remote", "channel", "rpc", "stream", "multiplexer"] 5 | categories = ["asynchronous", "network-programming"] 6 | 7 | version = { workspace = true } 8 | repository = { workspace = true } 9 | edition = { workspace = true } 10 | rust-version = { workspace = true } 11 | license = { workspace = true } 12 | authors = { workspace = true } 13 | 14 | 15 | [features] 16 | default = ["full", "default-codec-json"] 17 | full = ["serde", "rch", "rfn", "robj", "robs", "rtc"] 18 | rch = ["async-trait", "serde", "tokio-util/codec", "tokio/io-util"] 19 | rfn = ["rch"] 20 | robj = ["rch"] 21 | robs = ["rch"] 22 | rtc = ["rch", "remoc_macro", "async-trait"] 23 | js = [ 24 | "dep:getrandom", 25 | "dep:js-sys", 26 | "dep:web-sys", 27 | "dep:wasm-bindgen", 28 | "dep:wasm-bindgen-futures", 29 | "uuid/rng-rand" 30 | ] 31 | 32 | # Codecs 33 | default-codec-set = [] 34 | codec-bincode = ["bincode"] 35 | default-codec-bincode = ["codec-bincode", "default-codec-set"] 36 | default-codec-bincode2 = ["codec-bincode", "default-codec-set"] 37 | codec-ciborium = ["ciborium"] 38 | default-codec-ciborium = ["codec-ciborium", "default-codec-set"] 39 | codec-json = ["serde_json"] 40 | default-codec-json = ["codec-json", "default-codec-set"] 41 | codec-message-pack = ["rmp-serde"] 42 | default-codec-message-pack = ["codec-message-pack", "default-codec-set"] 43 | codec-postcard = ["postcard"] 44 | default-codec-postcard = ["codec-postcard", "default-codec-set"] 45 | full-codecs = [ 46 | "codec-bincode", 47 | "codec-ciborium", 48 | "codec-json", 49 | "codec-message-pack", 50 | "codec-postcard", 51 | ] 52 | 53 | 54 | [dependencies] 55 | remoc_macro = { version = "=0.15.3", path = "../remoc_macro", optional = true } 56 | 57 | futures = { version = "0.3.31", default-features = false, features = ["std"] } 58 | tokio = { version = "1.43", features = ["macros", "rt", "sync", "time"] } 59 | tokio-util = { version = "0.7", features = ["codec"] } 60 | rand = "0.9" 61 | tracing = "0.1.29" 62 | bytes = "1" 63 | byteorder = "1.4" 64 | uuid = { version = "1.15", features = ["serde", "v4"] } 65 | async-trait = { version = "0.1", optional = true } 66 | serde = { version = "1.0", features = ["derive"], optional = true } 67 | 68 | # Codecs 69 | serde_json = { version = "1.0", optional = true } 70 | bincode = { version = "2.0", default-features = false, features = ["std", "serde"], optional = true } 71 | ciborium = { version = "0.2", optional = true } 72 | rmp-serde = { version = "1.0", optional = true } 73 | postcard = { version = "1.0", features = ["use-std"], optional = true } 74 | 75 | # Web support 76 | getrandom = { version = "0.3", features = ["wasm_js"], optional = true } 77 | js-sys = { version = "0.3.72", optional = true } 78 | web-sys = { version = "0.3.72", features = ["Window", "WorkerGlobalScope"], optional = true } 79 | wasm-bindgen = { version = "0.2.95", optional = true } 80 | wasm-bindgen-futures = { version = "0.4.45", optional = true } 81 | 82 | tracing-subscriber = { version = "0.3.7", features = ["env-filter"] } 83 | 84 | [dev-dependencies] 85 | tracing-subscriber = { version = "0.3.7", features = ["env-filter"] } 86 | tokio = { version = "1.43", features = ["io-util", "rt"] } 87 | wasm-bindgen-test = "0.3.45" 88 | 89 | [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] 90 | tokio = { version = "1.43", features = ["net", "rt-multi-thread"] } 91 | tokio-test = "0.4" 92 | 93 | 94 | [package.metadata.docs.rs] 95 | features = ["full", "full-codecs", "default-codec-json"] 96 | rustdoc-args = ["--cfg", "docsrs"] 97 | 98 | 99 | [lints.rust] 100 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] } 101 | -------------------------------------------------------------------------------- /remoc/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /remoc/NOTICE: -------------------------------------------------------------------------------- 1 | ../NOTICE -------------------------------------------------------------------------------- /remoc/src/chmux/any_storage.rs: -------------------------------------------------------------------------------- 1 | //! Arbitrary data storage. 2 | 3 | use std::{ 4 | any::Any, 5 | collections::{hash_map::Entry, HashMap}, 6 | fmt, 7 | sync::Arc, 8 | }; 9 | use uuid::Uuid; 10 | 11 | /// Box containing any value that is Send, Sync and static. 12 | pub type AnyBox = Box; 13 | 14 | /// An entry in [AnyStorage]. 15 | pub type AnyEntry = Arc>>; 16 | 17 | type AnyMap = HashMap; 18 | 19 | /// Stores arbitrary data indexed by automatically generated keys. 20 | /// 21 | /// Clones share the underlying storage. 22 | #[derive(Clone)] 23 | pub struct AnyStorage { 24 | entries: Arc>, 25 | } 26 | 27 | impl fmt::Debug for AnyStorage { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | let entries = self.entries.lock().unwrap(); 30 | write!(f, "{:?}", *entries) 31 | } 32 | } 33 | 34 | impl AnyStorage { 35 | /// Creates a new storage. 36 | pub(crate) fn new() -> Self { 37 | Self { entries: Arc::new(std::sync::Mutex::new(AnyMap::new())) } 38 | } 39 | 40 | /// Insert a new entry into the storage and return its key. 41 | pub fn insert(&self, entry: AnyEntry) -> Uuid { 42 | let mut entries = self.entries.lock().unwrap(); 43 | loop { 44 | let key = Uuid::new_v4(); 45 | if let Entry::Vacant(e) = entries.entry(key) { 46 | e.insert(entry); 47 | return key; 48 | } 49 | } 50 | } 51 | 52 | /// Returns the value from the storage for the specified key. 53 | pub fn get(&self, key: Uuid) -> Option { 54 | let entries = self.entries.lock().unwrap(); 55 | entries.get(&key).cloned() 56 | } 57 | 58 | /// Removes the value for the specified key from the storage and returns it. 59 | pub fn remove(&self, key: Uuid) -> Option { 60 | let mut entries = self.entries.lock().unwrap(); 61 | entries.remove(&key) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /remoc/src/chmux/forward.rs: -------------------------------------------------------------------------------- 1 | //! Channel data forwarding. 2 | 3 | use bytes::Buf; 4 | use std::{fmt, num::Wrapping}; 5 | 6 | use super::{ConnectError, PortReq, Received, RecvChunkError, RecvError, SendError}; 7 | use crate::exec; 8 | 9 | /// An error occurred during forwarding of a message. 10 | #[derive(Debug, Clone)] 11 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 12 | pub enum ForwardError { 13 | /// Sending failed. 14 | Send(SendError), 15 | /// Receiving failed. 16 | Recv(RecvError), 17 | } 18 | 19 | impl From for ForwardError { 20 | fn from(err: SendError) -> Self { 21 | Self::Send(err) 22 | } 23 | } 24 | 25 | impl From for ForwardError { 26 | fn from(err: RecvError) -> Self { 27 | Self::Recv(err) 28 | } 29 | } 30 | 31 | impl fmt::Display for ForwardError { 32 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 | match self { 34 | ForwardError::Send(err) => write!(f, "forward send failed: {err}"), 35 | ForwardError::Recv(err) => write!(f, "forward receive failed: {err}"), 36 | } 37 | } 38 | } 39 | 40 | impl From for std::io::Error { 41 | fn from(err: ForwardError) -> Self { 42 | match err { 43 | ForwardError::Send(err) => err.into(), 44 | ForwardError::Recv(err) => err.into(), 45 | } 46 | } 47 | } 48 | 49 | impl std::error::Error for ForwardError {} 50 | 51 | /// Forwards all data received from a receiver to a sender. 52 | pub(crate) async fn forward(rx: &mut super::Receiver, tx: &mut super::Sender) -> Result { 53 | // Required to avoid borrow checking loop limitation. 54 | fn spawn_forward(id: u32, mut rx: super::Receiver, mut tx: super::Sender) { 55 | exec::spawn(async move { 56 | if let Err(err) = forward(&mut rx, &mut tx).await { 57 | tracing::debug!("port forwarding for id {id} failed: {err}"); 58 | } 59 | }); 60 | } 61 | 62 | let override_graceful_close = tx.is_graceful_close_overridden(); 63 | tx.set_override_graceful_close(true); 64 | 65 | let mut total = Wrapping(0); 66 | let mut closed = false; 67 | 68 | enum Event { 69 | Received(Option), 70 | Closed, 71 | } 72 | 73 | loop { 74 | let event = tokio::select! { 75 | res = rx.recv_any() => Event::Received(res?), 76 | () = tx.closed(), if !closed => Event::Closed, 77 | }; 78 | 79 | match event { 80 | // Data received. 81 | Event::Received(Some(Received::Data(data))) => { 82 | total += data.remaining(); 83 | tx.send(data.into()).await?; 84 | } 85 | 86 | // Data chunks received. 87 | Event::Received(Some(Received::Chunks)) => { 88 | let mut chunk_tx = tx.send_chunks(); 89 | loop { 90 | match rx.recv_chunk().await { 91 | Ok(Some(chunk)) => { 92 | total += chunk.remaining(); 93 | chunk_tx = chunk_tx.send(chunk).await?; 94 | } 95 | Ok(None) => { 96 | chunk_tx.finish().await?; 97 | break; 98 | } 99 | Err(RecvChunkError::Cancelled) => break, 100 | Err(RecvChunkError::ChMux) => return Err(ForwardError::Recv(RecvError::ChMux)), 101 | } 102 | } 103 | } 104 | 105 | // Ports received. 106 | Event::Received(Some(Received::Requests(reqs))) => { 107 | let allocator = tx.port_allocator(); 108 | 109 | // Allocate local outgoing ports for forwarding. 110 | let mut ports = Vec::new(); 111 | let mut wait = false; 112 | for req in &reqs { 113 | let port = allocator.allocate().await; 114 | ports.push(PortReq::new(port).with_id(req.id())); 115 | wait |= req.is_wait(); 116 | } 117 | 118 | // Connect them. 119 | let connects = tx.connect(ports, wait).await?; 120 | for (req, connect) in reqs.into_iter().zip(connects) { 121 | exec::spawn(async move { 122 | let id = req.id(); 123 | match connect.await { 124 | Ok((out_tx, out_rx)) => { 125 | let in_port = out_tx.port_allocator().allocate().await; 126 | match req.accept_from(in_port).await { 127 | Ok((in_tx, in_rx)) => { 128 | spawn_forward(id, out_rx, in_tx); 129 | spawn_forward(id, in_rx, out_tx); 130 | } 131 | Err(err) => { 132 | tracing::debug!("port forwarding for id {id} failed to accept: {err}"); 133 | } 134 | } 135 | } 136 | Err(err) => { 137 | tracing::debug!("port forwarding for id {id} failed to connect: {err}"); 138 | req.reject(matches!( 139 | err, 140 | ConnectError::LocalPortsExhausted | ConnectError::RemotePortsExhausted 141 | )) 142 | .await; 143 | } 144 | } 145 | }); 146 | } 147 | } 148 | 149 | // End received. 150 | Event::Received(None) => break, 151 | 152 | // Forwarding sender closed. 153 | Event::Closed => { 154 | rx.close().await; 155 | closed = true; 156 | } 157 | } 158 | } 159 | 160 | tx.set_override_graceful_close(override_graceful_close); 161 | 162 | Ok(total.0) 163 | } 164 | -------------------------------------------------------------------------------- /remoc/src/chmux/mod.rs: -------------------------------------------------------------------------------- 1 | //! Low-level channel multiplexer. 2 | //! 3 | //! Multiplexes multiple binary channels over a single binary channel 4 | //! (anything that implements [Sink](futures::Sink) and [Stream](futures::Stream)). 5 | //! A connection is established by calling [ChMux::new]. 6 | //! 7 | //! **You probably do not want to use this module directly.** 8 | //! Instead use methods from [Connect](crate::Connect) to establish a connection over 9 | //! a physical transport and work with high-level [remote channels](crate::rch). 10 | //! 11 | //! # Protocol version compatibility 12 | //! Two endpoints can only communicate if they have the same [protocol version](PROTOCOL_VERSION). 13 | //! A change in protocol version will be accompanied by an increase of the 14 | //! major version number of the Remoc crate. 15 | 16 | use std::{error::Error, fmt}; 17 | 18 | mod any_storage; 19 | mod cfg; 20 | mod client; 21 | mod credit; 22 | mod forward; 23 | mod listener; 24 | mod msg; 25 | mod mux; 26 | mod port_allocator; 27 | mod receiver; 28 | mod sender; 29 | 30 | pub use any_storage::{AnyBox, AnyEntry, AnyStorage}; 31 | pub use cfg::{Cfg, PortsExhausted}; 32 | pub use client::{Client, Connect, ConnectError}; 33 | pub use forward::ForwardError; 34 | pub use listener::{Listener, ListenerError, ListenerStream, Request}; 35 | pub use mux::ChMux; 36 | pub use port_allocator::{PortAllocator, PortNumber, PortReq}; 37 | pub use receiver::{DataBuf, Received, Receiver, ReceiverStream, RecvAnyError, RecvChunkError, RecvError}; 38 | pub use sender::{ChunkSender, Closed, SendError, Sender, SenderSink, TrySendError}; 39 | 40 | /// Channel multiplexer protocol version. 41 | pub const PROTOCOL_VERSION: u8 = 3; 42 | 43 | /// Lowest protocol version that supports port ids. 44 | const PROTOCOL_VERSION_PORT_ID: u8 = 3; 45 | 46 | /// Channel multiplexer error. 47 | #[derive(Debug, Clone)] 48 | pub enum ChMuxError { 49 | /// An error was encountered while sending data to the transport sink. 50 | SinkError(SinkError), 51 | /// An error was encountered while receiving data from the transport stream. 52 | StreamError(StreamError), 53 | /// The transport stream was closed while multiplex channels were active or the 54 | /// multiplex client was not dropped. 55 | StreamClosed, 56 | /// The connection was reset by the remote endpoint. 57 | Reset, 58 | /// No messages where received over the configured connection timeout. 59 | Timeout, 60 | /// A multiplex protocol error occurred. 61 | Protocol(String), 62 | } 63 | 64 | impl fmt::Display for ChMuxError 65 | where 66 | SinkError: fmt::Display, 67 | StreamError: fmt::Display, 68 | { 69 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 70 | match self { 71 | Self::SinkError(err) => write!(f, "send error: {err}"), 72 | Self::StreamError(err) => write!(f, "receive error: {err}"), 73 | Self::StreamClosed => write!(f, "end of receive stream"), 74 | Self::Reset => write!(f, "connection reset"), 75 | Self::Timeout => write!(f, "connection timeout"), 76 | Self::Protocol(err) => write!(f, "protocol error: {err}"), 77 | } 78 | } 79 | } 80 | 81 | impl Error for ChMuxError 82 | where 83 | SinkError: Error, 84 | StreamError: Error, 85 | { 86 | } 87 | 88 | impl From> for std::io::Error { 89 | fn from(err: ChMuxError) -> Self { 90 | use std::io::ErrorKind; 91 | match err { 92 | ChMuxError::SinkError(err) => err, 93 | ChMuxError::StreamError(err) => err, 94 | ChMuxError::StreamClosed => std::io::Error::new(ErrorKind::ConnectionReset, err.to_string()), 95 | ChMuxError::Reset => std::io::Error::new(ErrorKind::ConnectionReset, err.to_string()), 96 | ChMuxError::Timeout => std::io::Error::new(ErrorKind::TimedOut, err.to_string()), 97 | ChMuxError::Protocol(_) => std::io::Error::new(ErrorKind::InvalidData, err.to_string()), 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /remoc/src/chmux/port_allocator.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Borrow, 3 | collections::HashSet, 4 | fmt, 5 | hash::Hash, 6 | mem, 7 | ops::Deref, 8 | sync::{Arc, Mutex}, 9 | }; 10 | use tokio::sync::oneshot; 11 | 12 | struct PortAllocatorInner { 13 | used: HashSet, 14 | limit: u32, 15 | notify_tx: Vec>, 16 | } 17 | 18 | impl PortAllocatorInner { 19 | fn is_available(&self) -> bool { 20 | self.used.len() <= self.limit as usize 21 | } 22 | 23 | fn try_allocate(&mut self, this: Arc>) -> Option { 24 | if self.is_available() { 25 | let number = loop { 26 | let cand = rand::random(); 27 | if !self.used.contains(&cand) { 28 | break cand; 29 | } 30 | }; 31 | 32 | self.used.insert(number); 33 | Some(PortNumber { number, allocator: this }) 34 | } else { 35 | None 36 | } 37 | } 38 | } 39 | 40 | /// Local port number allocator. 41 | /// 42 | /// State is shared between clones of this type. 43 | #[derive(Clone)] 44 | pub struct PortAllocator(Arc>); 45 | 46 | impl fmt::Debug for PortAllocator { 47 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 | let inner = self.0.lock().unwrap(); 49 | f.debug_struct("PortAllocator").field("used", &inner.used.len()).field("limit", &inner.limit).finish() 50 | } 51 | } 52 | 53 | impl PortAllocator { 54 | /// Creates a new port number allocator. 55 | pub(crate) fn new(limit: u32) -> PortAllocator { 56 | let inner = PortAllocatorInner { used: HashSet::new(), limit, notify_tx: Vec::new() }; 57 | PortAllocator(Arc::new(Mutex::new(inner))) 58 | } 59 | 60 | /// Allocates a local port number. 61 | /// 62 | /// Port numbers are allocated randomly. 63 | /// If all ports are currently in use, this waits for a port number to become available. 64 | pub async fn allocate(&self) -> PortNumber { 65 | loop { 66 | let rx = { 67 | let mut inner = self.0.lock().unwrap(); 68 | match inner.try_allocate(self.0.clone()) { 69 | Some(number) => return number, 70 | None => { 71 | let (tx, rx) = oneshot::channel(); 72 | inner.notify_tx.push(tx); 73 | rx 74 | } 75 | } 76 | }; 77 | 78 | let _ = rx.await; 79 | } 80 | } 81 | 82 | /// Tries to allocate a local port number. 83 | /// 84 | /// If all port are currently in use, this returns [None]. 85 | pub fn try_allocate(&self) -> Option { 86 | let mut inner = self.0.lock().unwrap(); 87 | inner.try_allocate(self.0.clone()) 88 | } 89 | } 90 | 91 | /// An allocated local port number. 92 | /// 93 | /// When this is dropped, the allocated is automatically released. 94 | pub struct PortNumber { 95 | number: u32, 96 | allocator: Arc>, 97 | } 98 | 99 | impl fmt::Debug for PortNumber { 100 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 101 | write!(f, "{:?}", self.number) 102 | } 103 | } 104 | 105 | impl fmt::Display for PortNumber { 106 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 107 | write!(f, "{}", self.number) 108 | } 109 | } 110 | 111 | impl Deref for PortNumber { 112 | type Target = u32; 113 | 114 | fn deref(&self) -> &Self::Target { 115 | &self.number 116 | } 117 | } 118 | 119 | impl PartialEq for PortNumber { 120 | fn eq(&self, other: &Self) -> bool { 121 | **self == **other 122 | } 123 | } 124 | 125 | impl Eq for PortNumber {} 126 | 127 | impl PartialOrd for PortNumber { 128 | fn partial_cmp(&self, other: &Self) -> Option { 129 | Some(self.cmp(other)) 130 | } 131 | } 132 | 133 | impl Ord for PortNumber { 134 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 135 | self.number.cmp(&other.number) 136 | } 137 | } 138 | 139 | impl Hash for PortNumber { 140 | fn hash(&self, state: &mut H) { 141 | (**self).hash(state) 142 | } 143 | } 144 | 145 | impl Borrow for PortNumber { 146 | fn borrow(&self) -> &u32 { 147 | &self.number 148 | } 149 | } 150 | 151 | impl Drop for PortNumber { 152 | fn drop(&mut self) { 153 | let notify_tx = { 154 | let mut inner = self.allocator.lock().unwrap(); 155 | inner.used.remove(&self.number); 156 | mem::take(&mut inner.notify_tx) 157 | }; 158 | 159 | for tx in notify_tx { 160 | let _ = tx.send(()); 161 | } 162 | } 163 | } 164 | 165 | /// A port connection request by the local endpoint. 166 | /// 167 | /// The id can be set freely by the user. 168 | /// It is initialized to the [port number](Self::port). 169 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 170 | pub struct PortReq { 171 | /// The allocated, local port number. 172 | pub port: PortNumber, 173 | /// A user-specified id. 174 | pub id: u32, 175 | } 176 | 177 | impl From for PortReq { 178 | /// Create a new port connection request with [`id`](Self::id) set to 179 | /// the [port number](Self::port). 180 | fn from(port: PortNumber) -> Self { 181 | Self { id: port.number, port } 182 | } 183 | } 184 | 185 | impl From for PortNumber { 186 | fn from(req: PortReq) -> Self { 187 | req.port 188 | } 189 | } 190 | 191 | impl PortReq { 192 | /// Create a new port connection request with [`id`](Self::id) set to 193 | /// the [port number](Self::port). 194 | pub fn new(port: PortNumber) -> Self { 195 | Self::from(port) 196 | } 197 | 198 | /// Sets the id to the specified value. 199 | pub fn with_id(mut self, id: u32) -> Self { 200 | self.id = id; 201 | self 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /remoc/src/codec/bincode.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::{Codec, DeserializationError, SerializationError}; 4 | 5 | /// Bincode 1 codec. 6 | /// 7 | /// See [bincode] for details. 8 | /// This uses the [`bincode::config::legacy`] configuration. 9 | #[cfg_attr(docsrs, doc(cfg(feature = "codec-bincode")))] 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct Bincode; 12 | 13 | const LEGACY: bincode::config::Configuration< 14 | bincode::config::LittleEndian, 15 | bincode::config::Fixint, 16 | bincode::config::NoLimit, 17 | > = bincode::config::legacy(); 18 | 19 | impl Codec for Bincode { 20 | #[inline] 21 | fn serialize(mut writer: Writer, item: &Item) -> Result<(), super::SerializationError> 22 | where 23 | Writer: std::io::Write, 24 | Item: serde::Serialize, 25 | { 26 | bincode::serde::encode_into_std_write(item, &mut writer, LEGACY) 27 | .map(|_| ()) 28 | .map_err(SerializationError::new) 29 | } 30 | 31 | #[inline] 32 | fn deserialize(mut reader: Reader) -> Result 33 | where 34 | Reader: std::io::Read, 35 | Item: serde::de::DeserializeOwned, 36 | { 37 | bincode::serde::decode_from_std_read(&mut reader, LEGACY).map_err(DeserializationError::new) 38 | } 39 | } 40 | 41 | /// Bincode 2 codec. 42 | /// 43 | /// See [bincode] for details. 44 | /// This uses the [`bincode::config::standard`] configuration and is not compatible with Bincode 1. 45 | #[cfg_attr(docsrs, doc(cfg(feature = "codec-bincode")))] 46 | #[derive(Clone, Serialize, Deserialize)] 47 | pub struct Bincode2; 48 | 49 | const STANDARD: bincode::config::Configuration = bincode::config::standard(); 50 | 51 | impl Codec for Bincode2 { 52 | #[inline] 53 | fn serialize(mut writer: Writer, item: &Item) -> Result<(), super::SerializationError> 54 | where 55 | Writer: std::io::Write, 56 | Item: serde::Serialize, 57 | { 58 | bincode::serde::encode_into_std_write(item, &mut writer, STANDARD) 59 | .map(|_| ()) 60 | .map_err(SerializationError::new) 61 | } 62 | 63 | #[inline] 64 | fn deserialize(mut reader: Reader) -> Result 65 | where 66 | Reader: std::io::Read, 67 | Item: serde::de::DeserializeOwned, 68 | { 69 | bincode::serde::decode_from_std_read(&mut reader, STANDARD).map_err(DeserializationError::new) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /remoc/src/codec/ciborium.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::{Codec, DeserializationError, SerializationError}; 4 | 5 | /// CBOR codec using [ciborium]. 6 | /// 7 | /// ## Compatibility 8 | /// This codec is able to decode values encoded with Cbor 9 | /// but the opposite is not true. 10 | /// Make sure you are not mixing this codec with the legacy Cbor 11 | /// codec across your remote endpoints to avoid deserialization errors. 12 | /// More information is provided in the [`ciborium` README]. 13 | /// 14 | /// [`ciborium` README]: https://github.com/enarx/ciborium#compatibility-with-other-implementations 15 | #[cfg_attr(docsrs, doc(cfg(feature = "codec-ciborium")))] 16 | #[derive(Clone, Serialize, Deserialize)] 17 | pub struct Ciborium; 18 | 19 | impl Codec for Ciborium { 20 | #[inline] 21 | fn serialize(writer: Writer, item: &Item) -> Result<(), super::SerializationError> 22 | where 23 | Writer: std::io::Write, 24 | Item: serde::Serialize, 25 | { 26 | ciborium::ser::into_writer(item, writer).map_err(SerializationError::new) 27 | } 28 | 29 | #[inline] 30 | fn deserialize(reader: Reader) -> Result 31 | where 32 | Reader: std::io::Read, 33 | Item: serde::de::DeserializeOwned, 34 | { 35 | ciborium::de::from_reader(reader).map_err(DeserializationError::new) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /remoc/src/codec/json.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::{Codec, DeserializationError, SerializationError}; 4 | 5 | /// JSON codec. 6 | /// 7 | /// See [serde_json] for details. 8 | /// 9 | /// When using this with data the contains maps with non-string keys you will 10 | /// need to apply attributes from [map](super::map) to work correctly. 11 | #[cfg_attr(docsrs, doc(cfg(feature = "codec-json")))] 12 | #[derive(Clone, Serialize, Deserialize)] 13 | pub struct Json; 14 | 15 | impl Codec for Json { 16 | #[inline] 17 | fn serialize(writer: Writer, item: &Item) -> Result<(), super::SerializationError> 18 | where 19 | Writer: std::io::Write, 20 | Item: serde::Serialize, 21 | { 22 | serde_json::to_writer(writer, item).map_err(SerializationError::new) 23 | } 24 | 25 | #[inline] 26 | fn deserialize(reader: Reader) -> Result 27 | where 28 | Reader: std::io::Read, 29 | Item: serde::de::DeserializeOwned, 30 | { 31 | serde_json::from_reader(reader).map_err(DeserializationError::new) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /remoc/src/codec/map/btreemap.rs: -------------------------------------------------------------------------------- 1 | //! Serializes and deserializes a [BTreeMap]`` as a [Vec]`<(K, V)>`. 2 | //! 3 | //! This is necessary for the [JSON codec](crate::codec::Json) since it does not 4 | //! support non-string keys on dictionaries. 5 | //! 6 | //! Use by applying the attribute `#[serde(with="remoc::codec::map::btreemap")]` on a field. 7 | //! 8 | //! # Example 9 | //! 10 | //! The following example shows how to apply the attribute to a field in a struct. 11 | //! Since the keys are of non-string type `Vec` [serde_json] would not be able to 12 | //! serialize this without the attribute. 13 | //! 14 | #![cfg_attr( 15 | feature = "codec-json", 16 | doc = r##" 17 | ``` 18 | use std::collections::BTreeMap; 19 | use serde::{Serialize, Deserialize}; 20 | 21 | #[derive(Serialize, Deserialize, Default)] 22 | pub struct TestStruct { 23 | #[serde(with = "remoc::codec::map::btreemap")] 24 | btreemap: BTreeMap, String>, 25 | } 26 | 27 | serde_json::to_string(&TestStruct::default()).unwrap(); 28 | ``` 29 | "## 30 | )] 31 | 32 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 33 | use std::collections::BTreeMap; 34 | 35 | /// Serialization function. 36 | pub fn serialize(h: &BTreeMap, serializer: S) -> Result 37 | where 38 | S: Serializer, 39 | K: Serialize + Clone, 40 | V: Serialize + Clone, 41 | { 42 | let mut v: Vec<(K, V)> = Vec::new(); 43 | for (key, value) in h { 44 | v.push((key.clone(), value.clone())); 45 | } 46 | v.serialize(serializer) 47 | } 48 | 49 | /// Deserialization function. 50 | pub fn deserialize<'de, D, K, V>(deserializer: D) -> Result, D::Error> 51 | where 52 | D: Deserializer<'de>, 53 | K: Serialize + Deserialize<'de> + Ord, 54 | V: Serialize + Deserialize<'de>, 55 | { 56 | let v: Vec<(K, V)> = Vec::deserialize(deserializer)?; 57 | let mut h = BTreeMap::new(); 58 | for (key, value) in v { 59 | h.insert(key, value); 60 | } 61 | Ok(h) 62 | } 63 | -------------------------------------------------------------------------------- /remoc/src/codec/map/hashmap.rs: -------------------------------------------------------------------------------- 1 | //! Serializes and deserializes a [HashMap]`` as a [Vec]`<(K, V)>`. 2 | //! 3 | //! This is necessary for the [JSON codec](crate::codec::Json) since it does not 4 | //! support non-string keys on dictionaries. 5 | //! 6 | //! Use by applying the attribute `#[serde(with="remoc::codec::map::hashmap")]` on a field. 7 | //! 8 | //! # Example 9 | //! 10 | //! The following example shows how to apply the attribute to a field in a struct. 11 | //! Since the keys are of non-string type `Vec` [serde_json] would not be able to 12 | //! serialize this without the attribute. 13 | //! 14 | #![cfg_attr( 15 | feature = "codec-json", 16 | doc = r##" 17 | ``` 18 | use std::collections::HashMap; 19 | use serde::{Serialize, Deserialize}; 20 | 21 | #[derive(Serialize, Deserialize, Default)] 22 | pub struct TestStruct { 23 | #[serde(with = "remoc::codec::map::hashmap")] 24 | hashmap: HashMap, String>, 25 | } 26 | 27 | serde_json::to_string(&TestStruct::default()).unwrap(); 28 | ``` 29 | "## 30 | )] 31 | 32 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 33 | use std::{collections::HashMap, hash::Hash}; 34 | 35 | /// Serialization function. 36 | pub fn serialize(h: &HashMap, serializer: S) -> Result 37 | where 38 | S: Serializer, 39 | K: Serialize + Clone, 40 | V: Serialize + Clone, 41 | { 42 | let mut v: Vec<(K, V)> = Vec::new(); 43 | for (key, value) in h { 44 | v.push((key.clone(), value.clone())); 45 | } 46 | v.serialize(serializer) 47 | } 48 | 49 | /// Deserialization function. 50 | pub fn deserialize<'de, D, K, V>(deserializer: D) -> Result, D::Error> 51 | where 52 | D: Deserializer<'de>, 53 | K: Serialize + Deserialize<'de> + Eq + Hash, 54 | V: Serialize + Deserialize<'de>, 55 | { 56 | let v: Vec<(K, V)> = Vec::deserialize(deserializer)?; 57 | let mut h = HashMap::new(); 58 | for (key, value) in v { 59 | h.insert(key, value); 60 | } 61 | Ok(h) 62 | } 63 | -------------------------------------------------------------------------------- /remoc/src/codec/map/mod.rs: -------------------------------------------------------------------------------- 1 | //! Tools for using [HashMap]s and [BTreeMap]s with non-string keys with 2 | //! the JSON codec. 3 | //! 4 | //! [HashMap]: std::collections::HashMap 5 | //! [BTreeMap]: std::collections::BTreeMap 6 | 7 | pub mod btreemap; 8 | pub mod hashmap; 9 | -------------------------------------------------------------------------------- /remoc/src/codec/message_pack.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::{Codec, DeserializationError, SerializationError}; 4 | 5 | /// MessagePack codec. 6 | /// 7 | /// See [rmp_serde] for details. 8 | /// This serializes structures as maps. 9 | #[cfg_attr(docsrs, doc(cfg(feature = "codec-message-pack")))] 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct MessagePack; 12 | 13 | impl Codec for MessagePack { 14 | #[inline] 15 | fn serialize(mut writer: Writer, item: &Item) -> Result<(), super::SerializationError> 16 | where 17 | Writer: std::io::Write, 18 | Item: serde::Serialize, 19 | { 20 | rmp_serde::encode::write_named(&mut writer, item).map_err(SerializationError::new) 21 | } 22 | 23 | #[inline] 24 | fn deserialize(reader: Reader) -> Result 25 | where 26 | Reader: std::io::Read, 27 | Item: serde::de::DeserializeOwned, 28 | { 29 | rmp_serde::decode::from_read(reader).map_err(DeserializationError::new) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /remoc/src/codec/postcard.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::{Codec, DeserializationError, SerializationError}; 4 | 5 | /// Postcard codec. 6 | /// 7 | /// See [postcard] for details. 8 | /// This uses the default function configuration. 9 | #[cfg_attr(docsrs, doc(cfg(feature = "codec-postcard")))] 10 | #[derive(Clone, Serialize, Deserialize)] 11 | pub struct Postcard; 12 | 13 | impl Codec for Postcard { 14 | #[inline] 15 | fn serialize(writer: Writer, item: &Item) -> Result<(), super::SerializationError> 16 | where 17 | Writer: std::io::Write, 18 | Item: serde::Serialize, 19 | { 20 | postcard::to_io(item, writer).map_err(SerializationError::new)?; 21 | Ok(()) 22 | } 23 | 24 | #[inline] 25 | fn deserialize(mut reader: Reader) -> Result 26 | where 27 | Reader: std::io::Read, 28 | Item: serde::de::DeserializeOwned, 29 | { 30 | let mut bytes = vec![]; 31 | reader.read_to_end(&mut bytes).map_err(DeserializationError::new)?; 32 | 33 | let item = postcard::from_bytes(&bytes).map_err(DeserializationError::new)?; 34 | Ok(item) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /remoc/src/doctest.rs: -------------------------------------------------------------------------------- 1 | use futures::Future; 2 | 3 | #[cfg(feature = "rch")] 4 | pub async fn loop_channel() -> ( 5 | (crate::rch::base::Sender, crate::rch::base::Receiver), 6 | (crate::rch::base::Sender, crate::rch::base::Receiver), 7 | ) 8 | where 9 | T1: crate::RemoteSend, 10 | T2: crate::RemoteSend, 11 | { 12 | use futures::StreamExt; 13 | 14 | use crate::exec; 15 | 16 | let (transport_a_tx, transport_b_rx) = futures::channel::mpsc::channel::(0); 17 | let (transport_b_tx, transport_a_rx) = futures::channel::mpsc::channel::(0); 18 | 19 | let transport_a_rx = transport_a_rx.map(Ok::<_, std::io::Error>); 20 | let transport_b_rx = transport_b_rx.map(Ok::<_, std::io::Error>); 21 | 22 | let a = async move { 23 | let (conn, tx, rx) = 24 | crate::Connect::framed(Default::default(), transport_a_tx, transport_a_rx).await.unwrap(); 25 | exec::spawn(conn); 26 | (tx, rx) 27 | }; 28 | 29 | let b = async move { 30 | let (conn, tx, rx) = 31 | crate::Connect::framed(Default::default(), transport_b_tx, transport_b_rx).await.unwrap(); 32 | exec::spawn(conn); 33 | (tx, rx) 34 | }; 35 | 36 | tokio::join!(a, b) 37 | } 38 | 39 | #[cfg(feature = "rch")] 40 | pub async fn client_server( 41 | client: impl FnOnce(crate::rch::base::Sender) -> ClientFut, 42 | server: impl FnOnce(crate::rch::base::Receiver) -> ServerFut, 43 | ) where 44 | T: crate::RemoteSend, 45 | ClientFut: Future + Send + 'static, 46 | ServerFut: Future + Send + 'static, 47 | { 48 | let ((a_tx, _a_rx), (_b_tx, b_rx)) = loop_channel::<_, ()>().await; 49 | tokio::join!(client(a_tx), server(b_rx)); 50 | } 51 | 52 | #[cfg(feature = "rch")] 53 | pub async fn client_server_bidir( 54 | client: impl FnOnce(crate::rch::base::Sender, crate::rch::base::Receiver) -> ClientFut, 55 | server: impl FnOnce(crate::rch::base::Sender, crate::rch::base::Receiver) -> ServerFut, 56 | ) where 57 | T1: crate::RemoteSend, 58 | T2: crate::RemoteSend, 59 | 60 | ClientFut: Future + Send + 'static, 61 | ServerFut: Future + Send + 'static, 62 | { 63 | let ((a_tx, a_rx), (b_tx, b_rx)) = loop_channel().await; 64 | tokio::join!(client(a_tx, a_rx), server(b_tx, b_rx)); 65 | } 66 | -------------------------------------------------------------------------------- /remoc/src/exec/js/mod.rs: -------------------------------------------------------------------------------- 1 | //! JavaScript async executive. 2 | 3 | pub mod runtime; 4 | pub mod task; 5 | pub mod time; 6 | 7 | mod sync_wrapper; 8 | mod thread_pool; 9 | -------------------------------------------------------------------------------- /remoc/src/exec/js/runtime.rs: -------------------------------------------------------------------------------- 1 | //! The runtime. 2 | 3 | use futures::FutureExt; 4 | use std::{future::Future, panic}; 5 | use tokio::sync::oneshot; 6 | 7 | use super::{ 8 | sync_wrapper::SyncWrapper, 9 | task::{JoinError, JoinErrorRepr, JoinHandle}, 10 | thread_pool, 11 | }; 12 | 13 | /// Handle to the virtual runtime. 14 | #[derive(Debug, Clone)] 15 | pub struct Handle; 16 | 17 | impl Handle { 18 | /// Returns a Handle view over the currently running Runtime. 19 | pub fn current() -> Self { 20 | Self 21 | } 22 | 23 | /// Spawns a future onto the browser. 24 | #[track_caller] 25 | pub fn spawn(&self, future: F) -> JoinHandle 26 | where 27 | F: Future + 'static, 28 | F::Output: 'static, 29 | { 30 | let (result_tx, result_rx) = oneshot::channel(); 31 | let (abort_tx, abort_rx) = oneshot::channel(); 32 | 33 | wasm_bindgen_futures::spawn_local(async move { 34 | let res = tokio::select! { 35 | biased; 36 | res = panic::AssertUnwindSafe(future).catch_unwind() => 37 | res.map_err(|payload| JoinError(JoinErrorRepr::Panicked(SyncWrapper::new(payload)))), 38 | Ok(()) = abort_rx => Err(JoinError(JoinErrorRepr::Aborted)), 39 | }; 40 | let _ = result_tx.send(res); 41 | }); 42 | 43 | JoinHandle { result_rx: Box::pin(result_rx), abort_tx: Some(abort_tx) } 44 | } 45 | 46 | /// Runs the provided function on an thread pool dedicated to blocking operations. 47 | #[track_caller] 48 | pub fn spawn_blocking(&self, f: F) -> JoinHandle 49 | where 50 | F: FnOnce() -> R + Send + 'static, 51 | R: Send + 'static, 52 | { 53 | let (result_tx, result_rx) = oneshot::channel(); 54 | 55 | if let Err(err) = thread_pool::default().exec(move || { 56 | let res = panic::catch_unwind(panic::AssertUnwindSafe(f)) 57 | .map_err(|payload| JoinError(JoinErrorRepr::Panicked(SyncWrapper::new(payload)))); 58 | let _ = result_tx.send(res); 59 | }) { 60 | let (result_tx, result_rx) = oneshot::channel(); 61 | result_tx.send(Err(JoinError(JoinErrorRepr::Spawn(err)))).unwrap_or_else(|_| unreachable!()); 62 | return JoinHandle { result_rx: Box::pin(result_rx), abort_tx: None }; 63 | } 64 | 65 | JoinHandle { result_rx: Box::pin(result_rx), abort_tx: None } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /remoc/src/exec/js/sync_wrapper.rs: -------------------------------------------------------------------------------- 1 | // Copied from Tokio. 2 | 3 | //! This module contains a type that can make `Send + !Sync` types `Sync` by 4 | //! disallowing all immutable access to the value. 5 | //! 6 | //! A similar primitive is provided in the `sync_wrapper` crate. 7 | //! 8 | 9 | use std::any::Any; 10 | 11 | pub(crate) struct SyncWrapper { 12 | value: T, 13 | } 14 | 15 | // safety: The SyncWrapper being send allows you to send the inner value across 16 | // thread boundaries. 17 | #[allow(unsafe_code)] 18 | unsafe impl Send for SyncWrapper {} 19 | 20 | // safety: An immutable reference to a SyncWrapper is useless, so moving such an 21 | // immutable reference across threads is safe. 22 | #[allow(unsafe_code)] 23 | unsafe impl Sync for SyncWrapper {} 24 | 25 | impl SyncWrapper { 26 | pub(crate) fn new(value: T) -> Self { 27 | Self { value } 28 | } 29 | 30 | pub(crate) fn into_inner(self) -> T { 31 | self.value 32 | } 33 | } 34 | 35 | impl SyncWrapper> { 36 | /// Attempt to downcast using `Any::downcast_ref()` to a type that is known to be `Sync`. 37 | pub(crate) fn downcast_ref_sync(&self) -> Option<&T> { 38 | // SAFETY: if the downcast fails, the inner value is not touched, 39 | // so no thread-safety violation can occur. 40 | self.value.downcast_ref() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /remoc/src/exec/js/task.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous green-threads 2 | 3 | use futures::future::FutureExt; 4 | use std::{ 5 | any::Any, 6 | error::Error, 7 | fmt, 8 | future::Future, 9 | pin::Pin, 10 | task::{ready, Context, Poll}, 11 | }; 12 | use tokio::sync::oneshot; 13 | 14 | use super::{runtime::Handle, sync_wrapper::SyncWrapper}; 15 | 16 | /// Task failed to execute to completion. 17 | pub struct JoinError(pub(super) JoinErrorRepr); 18 | 19 | pub(super) enum JoinErrorRepr { 20 | /// Aborted. 21 | Aborted, 22 | /// Panicked. 23 | Panicked(SyncWrapper>), 24 | /// Thread failed. 25 | Failed, 26 | /// Spawning worker thread failed. 27 | Spawn(std::io::Error), 28 | } 29 | 30 | fn panic_payload_as_str(payload: &SyncWrapper>) -> Option<&str> { 31 | if let Some(s) = payload.downcast_ref_sync::() { 32 | return Some(s); 33 | } 34 | 35 | if let Some(s) = payload.downcast_ref_sync::<&'static str>() { 36 | return Some(s); 37 | } 38 | 39 | None 40 | } 41 | 42 | impl fmt::Debug for JoinError { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | match &self.0 { 45 | JoinErrorRepr::Aborted => f.debug_tuple("Aborted").finish(), 46 | JoinErrorRepr::Panicked(p) => { 47 | f.debug_tuple("Panicked").field(&panic_payload_as_str(p).unwrap_or("...")).finish() 48 | } 49 | JoinErrorRepr::Failed => f.debug_tuple("Failed").finish(), 50 | JoinErrorRepr::Spawn(err) => f.debug_tuple("Spawn").field(&err).finish(), 51 | } 52 | } 53 | } 54 | 55 | impl fmt::Display for JoinError { 56 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 57 | match &self.0 { 58 | JoinErrorRepr::Aborted => write!(f, "task was cancelled"), 59 | JoinErrorRepr::Panicked(p) => match panic_payload_as_str(p) { 60 | Some(msg) => write!(f, "task panicked with message {msg}"), 61 | None => write!(f, "task panicked"), 62 | }, 63 | JoinErrorRepr::Failed => write!(f, "task failed"), 64 | JoinErrorRepr::Spawn(err) => write!(f, "spawning worker failed: {err}"), 65 | } 66 | } 67 | } 68 | 69 | impl From for std::io::Error { 70 | fn from(err: JoinError) -> Self { 71 | std::io::Error::new(std::io::ErrorKind::Other, err.to_string()) 72 | } 73 | } 74 | 75 | impl Error for JoinError {} 76 | 77 | impl JoinError { 78 | /// Returns true if the error was caused by the task being cancelled. 79 | pub fn is_cancelled(&self) -> bool { 80 | matches!(&self.0, JoinErrorRepr::Aborted) 81 | } 82 | 83 | /// Returns true if the error was caused by thread failure. 84 | pub fn is_failed(&self) -> bool { 85 | matches!(&self.0, JoinErrorRepr::Failed) 86 | } 87 | 88 | /// Returns true if the error was caused by the task panicking. 89 | pub fn is_panic(&self) -> bool { 90 | matches!(&self.0, JoinErrorRepr::Panicked(_)) 91 | } 92 | 93 | /// Returns true if the error was caused by worker thread spawning failure. 94 | pub fn is_spawn(&self) -> bool { 95 | matches!(&self.0, JoinErrorRepr::Spawn(_)) 96 | } 97 | 98 | /// Consumes the join error, returning the object with which the task panicked. 99 | #[track_caller] 100 | pub fn into_panic(self) -> Box { 101 | self.try_into_panic().expect("`JoinError` reason is not a panic.") 102 | } 103 | 104 | /// Consumes the join error, returning the object with which the task 105 | /// panicked if the task terminated due to a panic. Otherwise, `self` is 106 | /// returned. 107 | pub fn try_into_panic(self) -> Result, JoinError> { 108 | match self.0 { 109 | JoinErrorRepr::Panicked(p) => Ok(p.into_inner()), 110 | _ => Err(self), 111 | } 112 | } 113 | } 114 | 115 | /// An owned permission to join on a task. 116 | pub struct JoinHandle { 117 | pub(super) result_rx: Pin>>>, 118 | pub(super) abort_tx: Option>, 119 | } 120 | 121 | impl fmt::Debug for JoinHandle { 122 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 123 | f.debug_tuple("JoinHandle").finish() 124 | } 125 | } 126 | 127 | impl Future for JoinHandle { 128 | type Output = Result; 129 | 130 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { 131 | let mut result_rx = self.result_rx.as_mut(); 132 | let result = ready!(result_rx.poll_unpin(cx)).unwrap_or(Err(JoinError(JoinErrorRepr::Failed))); 133 | Poll::Ready(result) 134 | } 135 | } 136 | 137 | impl JoinHandle { 138 | /// Abort the task associated with the handle. 139 | pub fn abort(&mut self) { 140 | if let Some(abort_tx) = self.abort_tx.take() { 141 | let _ = abort_tx.send(()); 142 | } 143 | } 144 | } 145 | 146 | /// Spawns a future onto the browser. 147 | #[track_caller] 148 | pub fn spawn(future: F) -> JoinHandle 149 | where 150 | F: Future + 'static, 151 | F::Output: 'static, 152 | { 153 | Handle::current().spawn(future) 154 | } 155 | 156 | /// Spawns a task providing a name for diagnostic purposes. 157 | #[track_caller] 158 | pub fn spawn_named(name: &str, future: Fut) -> JoinHandle 159 | where 160 | Fut: Future + 'static, 161 | Fut::Output: 'static, 162 | { 163 | let _ = name; 164 | spawn(future) 165 | } 166 | 167 | /// Runs the provided function on an thread pool dedicated to blocking operations. 168 | #[track_caller] 169 | pub fn spawn_blocking(f: F) -> JoinHandle 170 | where 171 | F: FnOnce() -> R + Send + 'static, 172 | R: Send + 'static, 173 | { 174 | Handle::current().spawn_blocking(f) 175 | } 176 | 177 | /// Runs a future to completion. 178 | #[track_caller] 179 | pub fn block_on(future: F) -> F::Output { 180 | let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); 181 | rt.block_on(future) 182 | } 183 | -------------------------------------------------------------------------------- /remoc/src/exec/js/thread_pool.rs: -------------------------------------------------------------------------------- 1 | //! Worker thread pool. 2 | 3 | use std::{ 4 | collections::VecDeque, 5 | fmt, 6 | num::NonZero, 7 | sync::{Arc, LazyLock, Mutex}, 8 | thread, 9 | thread::Thread, 10 | }; 11 | 12 | /// Thread pool. 13 | pub struct ThreadPool { 14 | max_workers: usize, 15 | inner: Arc>, 16 | } 17 | 18 | impl fmt::Debug for ThreadPool { 19 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 20 | f.debug_struct("ThreadPool").field("max_workers", &self.max_workers).finish_non_exhaustive() 21 | } 22 | } 23 | 24 | type Task = Box; 25 | 26 | #[derive(Default)] 27 | struct Inner { 28 | workers: Vec, 29 | tasks: VecDeque, 30 | exit: bool, 31 | idle: usize, 32 | } 33 | 34 | impl ThreadPool { 35 | /// Creates a new thread pool of the specified maximum size. 36 | pub fn new(max_workers: NonZero) -> Self { 37 | Self { max_workers: max_workers.get(), inner: Arc::new(Mutex::new(Inner::default())) } 38 | } 39 | 40 | /// Enqueues a task on the thread pool. 41 | /// 42 | /// Returns an error if worker thread spawning failed. 43 | pub fn exec(&self, f: impl FnOnce() + Send + 'static) -> Result<(), std::io::Error> { 44 | let mut inner = self.inner.lock().unwrap(); 45 | inner.tasks.push_back(Box::new(f)); 46 | 47 | // Check if we need to spawn a new worker. 48 | if inner.idle == 0 && inner.workers.len() < self.max_workers { 49 | let id = inner.workers.len() + 1; 50 | tracing::debug!("starting worker {id} / {}", self.max_workers); 51 | let hnd = { 52 | let inner = self.inner.clone(); 53 | thread::Builder::new().name(format!("remoc worker {id}")).spawn(move || Self::worker(inner)) 54 | }?; 55 | inner.workers.push(hnd.thread().clone()); 56 | } else { 57 | for worker in &inner.workers { 58 | worker.unpark(); 59 | } 60 | } 61 | 62 | Ok(()) 63 | } 64 | 65 | /// Worker function. 66 | fn worker(inner: Arc>) { 67 | let mut idle = false; 68 | 69 | loop { 70 | let mut inner = inner.lock().unwrap(); 71 | if let Some(task) = inner.tasks.pop_front() { 72 | if idle { 73 | inner.idle -= 1; 74 | idle = false; 75 | } 76 | 77 | drop(inner); 78 | task(); 79 | } else if inner.exit { 80 | break; 81 | } else { 82 | if !idle { 83 | inner.idle += 1; 84 | idle = true; 85 | } 86 | 87 | drop(inner); 88 | thread::park(); 89 | } 90 | } 91 | } 92 | } 93 | 94 | impl Drop for ThreadPool { 95 | fn drop(&mut self) { 96 | let mut inner = self.inner.lock().unwrap(); 97 | 98 | inner.exit = true; 99 | 100 | for worker in &inner.workers { 101 | worker.unpark(); 102 | } 103 | } 104 | } 105 | 106 | /// Default thread pool. 107 | /// 108 | /// Uses at most one worker per CPU. 109 | pub fn default() -> &'static ThreadPool { 110 | static INSTANCE: LazyLock = LazyLock::new(|| { 111 | let parallelism = thread::available_parallelism().unwrap_or_else(|err| { 112 | tracing::warn!("available parallelism is unknown: {err}"); 113 | NonZero::new(1).unwrap() 114 | }); 115 | ThreadPool::new(parallelism) 116 | }); 117 | 118 | &INSTANCE 119 | } 120 | -------------------------------------------------------------------------------- /remoc/src/exec/mod.rs: -------------------------------------------------------------------------------- 1 | //! Async executive for futures. 2 | //! 3 | //! On native platforms this uses Tokio. 4 | //! On JavaScript this executes Futures as Promises. 5 | 6 | #[cfg(not(feature = "js"))] 7 | mod native; 8 | 9 | #[cfg(not(feature = "js"))] 10 | pub use native::*; 11 | 12 | #[cfg(feature = "js")] 13 | mod js; 14 | 15 | #[cfg(feature = "js")] 16 | pub use js::*; 17 | 18 | pub use task::spawn; 19 | 20 | /// Whether threads are available and working on this platform. 21 | #[inline] 22 | pub async fn are_threads_available() -> bool { 23 | use tokio::sync::{oneshot, OnceCell}; 24 | 25 | static AVAILABLE: OnceCell = OnceCell::const_new(); 26 | *AVAILABLE 27 | .get_or_init(|| async move { 28 | tracing::trace!("spawning test thread"); 29 | 30 | let (tx, rx) = oneshot::channel(); 31 | let res = std::thread::Builder::new().name("remoc thread test".into()).spawn(move || { 32 | tracing::trace!("test thread started"); 33 | let _ = tx.send(()); 34 | }); 35 | 36 | match res { 37 | Ok(_) => { 38 | tracing::trace!("waiting for test thread"); 39 | match rx.await { 40 | Ok(()) => { 41 | tracing::trace!("threads are available"); 42 | true 43 | } 44 | Err(_) => { 45 | tracing::warn!("test thread failed, streaming (de)serialization disabled"); 46 | false 47 | } 48 | } 49 | } 50 | Err(os_error) => { 51 | tracing::warn!(%os_error, "threads not available, streaming (de)serialization disabled"); 52 | false 53 | } 54 | } 55 | }) 56 | .await 57 | } 58 | -------------------------------------------------------------------------------- /remoc/src/exec/native/mod.rs: -------------------------------------------------------------------------------- 1 | //! Native async executive using Tokio. 2 | 3 | pub mod runtime { 4 | pub use tokio::runtime::Handle; 5 | } 6 | 7 | pub mod task { 8 | use std::future::Future; 9 | 10 | pub use tokio::task::{spawn, spawn_blocking, JoinError, JoinHandle}; 11 | 12 | /// Runs a future to completion. 13 | #[track_caller] 14 | pub fn block_on(future: F) -> F::Output { 15 | let rt = tokio::runtime::Builder::new_current_thread().build().unwrap(); 16 | rt.block_on(future) 17 | } 18 | } 19 | 20 | pub mod time { 21 | pub use tokio::time::{sleep, timeout, Sleep, Timeout}; 22 | 23 | pub mod error { 24 | pub use tokio::time::error::Elapsed; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /remoc/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Convenience re-export of common members. 2 | //! 3 | //! Like the standard library's prelude, this module simplifies importing of common items. 4 | //! Unlike the standard prelude, the contents of this module must be imported manually. 5 | //! 6 | //! ``` 7 | //! use remoc::prelude::*; 8 | //! ``` 9 | //! 10 | 11 | pub use crate::chmux; 12 | 13 | #[cfg(feature = "rch")] 14 | pub use crate::rch; 15 | 16 | #[cfg(feature = "rch")] 17 | #[doc(no_inline)] 18 | pub use crate::ConnectExt; 19 | 20 | #[cfg(feature = "rch")] 21 | #[doc(no_inline)] 22 | pub use crate::RemoteSend; 23 | 24 | #[cfg(feature = "rch")] 25 | #[doc(no_inline)] 26 | pub use crate::rch::SendResultExt; 27 | 28 | #[cfg(feature = "rch")] 29 | #[doc(no_inline)] 30 | pub use crate::rch::base::BaseExt; 31 | 32 | #[cfg(feature = "rch")] 33 | #[doc(no_inline)] 34 | pub use crate::rch::mpsc::MpscExt; 35 | 36 | #[cfg(feature = "rch")] 37 | #[doc(no_inline)] 38 | pub use crate::rch::oneshot::OneshotExt; 39 | 40 | #[cfg(feature = "rch")] 41 | #[doc(no_inline)] 42 | pub use crate::rch::watch::WatchExt; 43 | 44 | #[cfg(feature = "rfn")] 45 | pub use crate::rfn; 46 | 47 | #[cfg(feature = "robj")] 48 | pub use crate::robj; 49 | 50 | #[cfg(feature = "robs")] 51 | pub use crate::robs; 52 | 53 | #[cfg(feature = "rtc")] 54 | pub use crate::rtc; 55 | 56 | #[cfg(feature = "rtc")] 57 | #[doc(no_inline)] 58 | pub use crate::rtc::{Client, ReqReceiver, Server, ServerRef, ServerRefMut, ServerShared, ServerSharedMut}; 59 | -------------------------------------------------------------------------------- /remoc/src/provider.rs: -------------------------------------------------------------------------------- 1 | //! Provider for any remote object. 2 | 3 | #[cfg(feature = "rfn")] 4 | use crate::rfn; 5 | 6 | #[cfg(feature = "robj")] 7 | use crate::robj; 8 | 9 | /// A provider for any remote object. 10 | /// 11 | /// This helps to store providers of different remote object types together. 12 | /// For example, you can keep providers of different types in a single vector. 13 | /// 14 | /// Dropping the provider will stop making the object available remotely. 15 | #[cfg_attr(docsrs, doc(cfg(any(feature = "rfn", feature = "robj"))))] 16 | #[derive(Debug)] 17 | #[non_exhaustive] 18 | pub enum Provider { 19 | /// A provider for [RFn](rfn::RFn). 20 | #[cfg(feature = "rfn")] 21 | #[cfg_attr(docsrs, doc(cfg(feature = "rfn")))] 22 | RFn(rfn::RFnProvider), 23 | /// A provider for [RFnMut](rfn::RFnMut). 24 | #[cfg(feature = "rfn")] 25 | #[cfg_attr(docsrs, doc(cfg(feature = "rfn")))] 26 | RFnMut(rfn::RFnMutProvider), 27 | /// A provider for [RFnOnce](rfn::RFnOnce). 28 | #[cfg(feature = "rfn")] 29 | #[cfg_attr(docsrs, doc(cfg(feature = "rfn")))] 30 | RFnOnce(rfn::RFnOnceProvider), 31 | /// A provider for [Handle](robj::handle::Handle). 32 | #[cfg(feature = "robj")] 33 | #[cfg_attr(docsrs, doc(cfg(feature = "robj")))] 34 | Handle(robj::handle::Provider), 35 | /// A provider for [Lazy](robj::lazy::Lazy). 36 | #[cfg(feature = "robj")] 37 | #[cfg_attr(docsrs, doc(cfg(feature = "robj")))] 38 | Lazy(robj::lazy::Provider), 39 | /// A provider for [LazyBlob](robj::lazy_blob::LazyBlob). 40 | #[cfg(feature = "robj")] 41 | #[cfg_attr(docsrs, doc(cfg(feature = "robj")))] 42 | LazyBlob(robj::lazy_blob::Provider), 43 | } 44 | 45 | #[cfg(feature = "rfn")] 46 | impl From for Provider { 47 | fn from(provider: rfn::RFnProvider) -> Self { 48 | Self::RFn(provider) 49 | } 50 | } 51 | 52 | #[cfg(feature = "rfn")] 53 | impl From for Provider { 54 | fn from(provider: rfn::RFnMutProvider) -> Self { 55 | Self::RFnMut(provider) 56 | } 57 | } 58 | 59 | #[cfg(feature = "rfn")] 60 | impl From for Provider { 61 | fn from(provider: rfn::RFnOnceProvider) -> Self { 62 | Self::RFnOnce(provider) 63 | } 64 | } 65 | 66 | #[cfg(feature = "robj")] 67 | impl From for Provider { 68 | fn from(provider: robj::handle::Provider) -> Self { 69 | Self::Handle(provider) 70 | } 71 | } 72 | 73 | #[cfg(feature = "robj")] 74 | impl From for Provider { 75 | fn from(provider: robj::lazy::Provider) -> Self { 76 | Self::Lazy(provider) 77 | } 78 | } 79 | 80 | #[cfg(feature = "robj")] 81 | impl From for Provider { 82 | fn from(provider: robj::lazy_blob::Provider) -> Self { 83 | Self::LazyBlob(provider) 84 | } 85 | } 86 | 87 | impl Provider { 88 | /// Releases the provider while keeping the remote object alive 89 | /// until it is dropped. 90 | pub fn keep(self) { 91 | match self { 92 | #[cfg(feature = "rfn")] 93 | Self::RFn(provider) => provider.keep(), 94 | #[cfg(feature = "rfn")] 95 | Self::RFnMut(provider) => provider.keep(), 96 | #[cfg(feature = "rfn")] 97 | Self::RFnOnce(provider) => provider.keep(), 98 | #[cfg(feature = "robj")] 99 | Self::Handle(provider) => provider.keep(), 100 | #[cfg(feature = "robj")] 101 | Self::Lazy(provider) => provider.keep(), 102 | #[cfg(feature = "robj")] 103 | Self::LazyBlob(provider) => provider.keep(), 104 | } 105 | } 106 | 107 | /// Waits until the provider can be safely dropped because it is not 108 | /// needed anymore. 109 | pub async fn done(&mut self) { 110 | match self { 111 | #[cfg(feature = "rfn")] 112 | Self::RFn(provider) => provider.done().await, 113 | #[cfg(feature = "rfn")] 114 | Self::RFnMut(provider) => provider.done().await, 115 | #[cfg(feature = "rfn")] 116 | Self::RFnOnce(provider) => provider.done().await, 117 | #[cfg(feature = "robj")] 118 | Self::Handle(provider) => provider.done().await, 119 | #[cfg(feature = "robj")] 120 | Self::Lazy(provider) => provider.done().await, 121 | #[cfg(feature = "robj")] 122 | Self::LazyBlob(provider) => provider.done().await, 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /remoc/src/rch/base/io.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes, BytesMut}; 2 | use std::io; 3 | 4 | /// Writes to an internal memory buffer with a limited maximum size. 5 | pub struct LimitedBytesWriter { 6 | limit: usize, 7 | buf: BytesMut, 8 | overflown: bool, 9 | } 10 | 11 | impl LimitedBytesWriter { 12 | /// Creates a new limited writer. 13 | pub fn new(limit: usize) -> Self { 14 | Self { limit, buf: BytesMut::new(), overflown: false } 15 | } 16 | 17 | /// Returns the write buffer, if no overflow has occurred. 18 | /// Otherwise None is returned. 19 | pub fn into_inner(self) -> Option { 20 | if self.overflown { 21 | None 22 | } else { 23 | Some(self.buf) 24 | } 25 | } 26 | 27 | /// True if limit has been reached. 28 | pub fn overflow(&self) -> bool { 29 | self.overflown 30 | } 31 | } 32 | 33 | impl io::Write for LimitedBytesWriter { 34 | #[inline] 35 | fn write(&mut self, buf: &[u8]) -> io::Result { 36 | if self.buf.len() + buf.len() <= self.limit && !self.overflown { 37 | self.buf.extend_from_slice(buf); 38 | Ok(buf.len()) 39 | } else { 40 | self.overflown = true; 41 | Err(io::Error::new(io::ErrorKind::BrokenPipe, "limit reached")) 42 | } 43 | } 44 | 45 | #[inline] 46 | fn flush(&mut self) -> std::io::Result<()> { 47 | Ok(()) 48 | } 49 | } 50 | 51 | /// Forwards data over a mpsc channel. 52 | /// 53 | /// This must not be used in an async thread. 54 | pub struct ChannelBytesWriter { 55 | tx: tokio::sync::mpsc::Sender, 56 | written: usize, 57 | } 58 | 59 | impl ChannelBytesWriter { 60 | /// Creates a new forwarding writer. 61 | pub fn new(tx: tokio::sync::mpsc::Sender) -> Self { 62 | Self { tx, written: 0 } 63 | } 64 | 65 | /// Written bytes. 66 | /// 67 | /// Saturates at usize::MAX; 68 | pub fn written(&self) -> usize { 69 | self.written 70 | } 71 | } 72 | 73 | impl io::Write for ChannelBytesWriter { 74 | #[inline] 75 | fn write(&mut self, buf: &[u8]) -> io::Result { 76 | match self.tx.blocking_send(buf.into()) { 77 | Ok(()) => { 78 | self.written = self.written.saturating_add(buf.len()); 79 | Ok(buf.len()) 80 | } 81 | Err(_) => Err(io::Error::new(io::ErrorKind::BrokenPipe, "channel closed")), 82 | } 83 | } 84 | 85 | #[inline] 86 | fn flush(&mut self) -> io::Result<()> { 87 | Ok(()) 88 | } 89 | } 90 | 91 | /// Reads data from an mpsc channel. 92 | /// 93 | /// This must not be used in an async thread. 94 | pub struct ChannelBytesReader { 95 | rx: tokio::sync::mpsc::Receiver>, 96 | buf: Bytes, 97 | failed: bool, 98 | } 99 | 100 | impl ChannelBytesReader { 101 | /// Creates a new reader. 102 | pub fn new(rx: tokio::sync::mpsc::Receiver>) -> Self { 103 | Self { rx, buf: Bytes::new(), failed: false } 104 | } 105 | } 106 | 107 | impl io::Read for ChannelBytesReader { 108 | #[inline] 109 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 110 | while self.buf.is_empty() { 111 | if self.failed { 112 | return Err(io::Error::new(io::ErrorKind::BrokenPipe, "channel closed")); 113 | } 114 | 115 | match self.rx.blocking_recv() { 116 | Some(Ok(buf)) => self.buf = buf, 117 | Some(Err(())) => self.failed = true, 118 | None => return Ok(0), 119 | } 120 | } 121 | 122 | let len = buf.len().min(self.buf.len()); 123 | buf[..len].copy_from_slice(&self.buf[..len]); 124 | self.buf.advance(len); 125 | Ok(len) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /remoc/src/rch/base/mod.rs: -------------------------------------------------------------------------------- 1 | //! A channel that exchanges values of arbitrary type with a remote endpoint 2 | //! and is primarily used as the initial channel after [establishing a connection](crate::Connect) 3 | //! with a remote endpoint. 4 | //! 5 | //! Each value is serialized into binary format before sending and deserialized 6 | //! after it has been received. 7 | //! 8 | //! The sender and receiver of this channel cannot be sent to a remote endpoint. 9 | //! However, you can send (objects containing) senders and receivers of other 10 | //! channel types (for example [mpsc](super::mpsc), [oneshot](super::oneshot) or 11 | //! [watch](super::watch)) via this channel to a remote endpoint. 12 | //! 13 | //! # Example 14 | //! 15 | //! In the following example the client sends a number over a base channel to the server. 16 | //! The server converts it into a string and sends it back over another base channel. 17 | //! The base channels have been obtained using a [connect function](crate::Connect). 18 | //! 19 | //! ``` 20 | //! use remoc::prelude::*; 21 | //! 22 | //! // This would be run on the client. 23 | //! async fn client(mut tx: rch::base::Sender, mut rx: rch::base::Receiver) { 24 | //! tx.send(1).await.unwrap(); 25 | //! assert_eq!(rx.recv().await.unwrap(), Some("1".to_string())); 26 | //! 27 | //! tx.send(2).await.unwrap(); 28 | //! assert_eq!(rx.recv().await.unwrap(), Some("2".to_string())); 29 | //! 30 | //! tx.send(3).await.unwrap(); 31 | //! assert_eq!(rx.recv().await.unwrap(), Some("3".to_string())); 32 | //! } 33 | //! 34 | //! // This would be run on the server. 35 | //! async fn server(mut tx: rch::base::Sender, mut rx: rch::base::Receiver) { 36 | //! while let Some(number) = rx.recv().await.unwrap() { 37 | //! tx.send(number.to_string()).await.unwrap(); 38 | //! } 39 | //! } 40 | //! # tokio_test::block_on(remoc::doctest::client_server_bidir(client, server)); 41 | //! ``` 42 | //! 43 | 44 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 45 | use std::{error::Error, fmt}; 46 | 47 | mod io; 48 | mod receiver; 49 | mod sender; 50 | 51 | pub use receiver::{PortDeserializer, Receiver, RecvError}; 52 | pub use sender::{Closed, PortSerializer, SendError, SendErrorKind, Sender}; 53 | 54 | use crate::{chmux, codec, RemoteSend}; 55 | 56 | /// Chunk queue length for big data (de-)serialization. 57 | const BIG_DATA_CHUNK_QUEUE: usize = 32; 58 | 59 | /// Limit for counting big data instances. 60 | const BIG_DATA_LIMIT: i8 = 16; 61 | 62 | /// Creating the remote channel failed. 63 | #[derive(Debug, Clone, Serialize, Deserialize)] 64 | pub enum ConnectError { 65 | /// The connect request failed. 66 | Connect(chmux::ConnectError), 67 | /// Listening for the remote connect request failed. 68 | Listen(chmux::ListenerError), 69 | /// The remote endpoint did not send a connect request. 70 | NoConnectRequest, 71 | } 72 | 73 | impl fmt::Display for ConnectError { 74 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 75 | match self { 76 | ConnectError::Connect(err) => write!(f, "connect error: {err}"), 77 | ConnectError::Listen(err) => write!(f, "listen error: {err}"), 78 | ConnectError::NoConnectRequest => write!(f, "no connect request received"), 79 | } 80 | } 81 | } 82 | 83 | impl Error for ConnectError {} 84 | 85 | impl From for ConnectError { 86 | fn from(err: chmux::ConnectError) -> Self { 87 | Self::Connect(err) 88 | } 89 | } 90 | 91 | impl From for ConnectError { 92 | fn from(err: chmux::ListenerError) -> Self { 93 | Self::Listen(err) 94 | } 95 | } 96 | 97 | /// Create a remote channel over an existing [chmux] connection. 98 | /// 99 | /// This will send a connect request over the client and accept 100 | /// one connection request from the listener. 101 | /// 102 | /// Other connections may coexist on the chmux connection. 103 | pub async fn connect( 104 | client: &chmux::Client, listener: &mut chmux::Listener, 105 | ) -> Result<(Sender, Receiver), ConnectError> 106 | where 107 | Tx: RemoteSend, 108 | Rx: RemoteSend, 109 | Codec: codec::Codec, 110 | { 111 | let (client_sr, listener_sr) = tokio::join!(client.connect(), listener.accept()); 112 | let (raw_sender, _) = client_sr?; 113 | let (_, raw_receiver) = listener_sr?.ok_or(ConnectError::NoConnectRequest)?; 114 | Ok((Sender::new(raw_sender), Receiver::new(raw_receiver))) 115 | } 116 | 117 | /// Extensions for base channels. 118 | pub trait BaseExt { 119 | /// Sets the maximum item size for the channel. 120 | fn with_max_item_size(self, max_item_size: usize) -> (Sender, Receiver); 121 | } 122 | 123 | impl BaseExt for (Sender, Receiver) 124 | where 125 | T: Serialize + DeserializeOwned + Send + 'static, 126 | Codec: codec::Codec, 127 | { 128 | fn with_max_item_size(self, max_item_size: usize) -> (Sender, Receiver) { 129 | let (mut tx, mut rx) = self; 130 | tx.set_max_item_size(max_item_size); 131 | rx.set_max_item_size(max_item_size); 132 | (tx, rx) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /remoc/src/rch/bin/mod.rs: -------------------------------------------------------------------------------- 1 | //! A channel that exchanges binary data with a remote endpoint. 2 | //! 3 | //! Allows low-overhead exchange of binary data. 4 | //! 5 | //! At least one end of the channel must be remote. 6 | //! Forwarding, i.e. both channel ends on remote endpoints, is supported. 7 | //! 8 | //! If the sole use is to transfer a large binary object into one direction, 9 | //! consider using a [lazy blob](crate::robj::lazy_blob) instead. 10 | //! 11 | //! This is a wrapper around a [chmux](crate::chmux) channel that allows to 12 | //! establish a connection by sending the sender or receiver to a remote endpoint. 13 | 14 | use std::sync::{Arc, Mutex}; 15 | 16 | mod receiver; 17 | mod sender; 18 | 19 | pub use receiver::Receiver; 20 | pub use sender::Sender; 21 | 22 | use super::interlock::{Interlock, Location}; 23 | 24 | /// Creates a new binary channel that is established by sending either the sender or receiver 25 | /// over a remote channel. 26 | pub fn channel() -> (Sender, Receiver) { 27 | let (sender_tx, sender_rx) = tokio::sync::mpsc::unbounded_channel(); 28 | let (receiver_tx, receiver_rx) = tokio::sync::mpsc::unbounded_channel(); 29 | let interlock = Arc::new(Mutex::new(Interlock::new())); 30 | 31 | let sender = Sender { 32 | sender: None, 33 | sender_rx, 34 | receiver_tx: Some(receiver_tx), 35 | interlock: interlock.clone(), 36 | successor_tx: std::sync::Mutex::new(None), 37 | }; 38 | let receiver = Receiver { 39 | receiver: None, 40 | sender_tx: Some(sender_tx), 41 | receiver_rx, 42 | interlock, 43 | successor_tx: std::sync::Mutex::new(None), 44 | }; 45 | (sender, receiver) 46 | } 47 | -------------------------------------------------------------------------------- /remoc/src/rch/bin/receiver.rs: -------------------------------------------------------------------------------- 1 | use futures::FutureExt; 2 | use serde::{Deserialize, Serialize}; 3 | use std::{ 4 | fmt, mem, 5 | sync::{Arc, Mutex}, 6 | }; 7 | 8 | use super::{ 9 | super::{ 10 | base::{PortDeserializer, PortSerializer}, 11 | ConnectError, 12 | }, 13 | Interlock, Location, 14 | }; 15 | use crate::chmux; 16 | 17 | /// A binary channel receiver. 18 | pub struct Receiver { 19 | pub(super) receiver: Option>, 20 | pub(super) sender_tx: Option>>, 21 | pub(super) receiver_rx: tokio::sync::mpsc::UnboundedReceiver>, 22 | pub(super) interlock: Arc>, 23 | pub(super) successor_tx: std::sync::Mutex>>, 24 | } 25 | 26 | impl fmt::Debug for Receiver { 27 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 28 | f.debug_struct("Receiver").finish() 29 | } 30 | } 31 | 32 | /// A chmux channel receiver in transport. 33 | #[derive(Debug, Serialize, Deserialize)] 34 | pub(crate) struct TransportedReceiver { 35 | /// chmux port number. 36 | pub port: u32, 37 | } 38 | 39 | impl Receiver { 40 | async fn connect(&mut self) { 41 | if self.receiver.is_none() { 42 | self.receiver = Some(self.receiver_rx.recv().await.unwrap_or(Err(ConnectError::Dropped))); 43 | } 44 | } 45 | 46 | /// Establishes the connection and returns a reference to the chmux receiver channel 47 | /// to the remote endpoint. 48 | pub async fn get(&mut self) -> Result<&mut chmux::Receiver, ConnectError> { 49 | self.connect().await; 50 | self.receiver.as_mut().unwrap().as_mut().map_err(|err| err.clone()) 51 | } 52 | 53 | /// Establishes the connection and returns the chmux receiver channel 54 | /// to the remote endpoint. 55 | pub async fn into_inner(mut self) -> Result { 56 | self.connect().await; 57 | self.receiver.take().unwrap() 58 | } 59 | 60 | /// Forward data. 61 | async fn forward(successor_rx: tokio::sync::oneshot::Receiver, tx: super::Sender) { 62 | let Ok(rx) = successor_rx.await else { return }; 63 | let Ok(mut rx) = rx.into_inner().await else { return }; 64 | let Ok(mut tx) = tx.into_inner().await else { return }; 65 | if let Err(err) = rx.forward(&mut tx).await { 66 | tracing::debug!("forwarding binary channel failed: {err}"); 67 | } 68 | } 69 | } 70 | 71 | impl Serialize for Receiver { 72 | /// Serializes this receiver for sending over a chmux channel. 73 | fn serialize(&self, serializer: S) -> Result 74 | where 75 | S: serde::Serializer, 76 | { 77 | let sender_tx = self.sender_tx.clone(); 78 | let interlock_confirm = { 79 | let mut interlock = self.interlock.lock().unwrap(); 80 | if interlock.sender.check_local() { 81 | Some(interlock.sender.start_send()) 82 | } else { 83 | None 84 | } 85 | }; 86 | 87 | match (sender_tx, interlock_confirm) { 88 | // Local-remote connection. 89 | (Some(sender_tx), Some(interlock_confirm)) => { 90 | let port = PortSerializer::connect(|connect| { 91 | async move { 92 | let _ = interlock_confirm.send(()); 93 | 94 | match connect.await { 95 | Ok((raw_tx, _)) => { 96 | let _ = sender_tx.send(Ok(raw_tx)); 97 | } 98 | Err(err) => { 99 | let _ = sender_tx.send(Err(ConnectError::Connect(err))); 100 | } 101 | } 102 | } 103 | .boxed() 104 | })?; 105 | 106 | TransportedReceiver { port }.serialize(serializer) 107 | } 108 | 109 | // Forwarding. 110 | _ => { 111 | let (successor_tx, successor_rx) = tokio::sync::oneshot::channel(); 112 | *self.successor_tx.lock().unwrap() = Some(successor_tx); 113 | let (tx, rx) = super::channel(); 114 | PortSerializer::spawn(Self::forward(successor_rx, tx))?; 115 | 116 | rx.serialize(serializer) 117 | } 118 | } 119 | } 120 | } 121 | 122 | impl<'de> Deserialize<'de> for Receiver { 123 | /// Deserializes this receiver after it has been received over a chmux channel. 124 | fn deserialize(deserializer: D) -> Result 125 | where 126 | D: serde::Deserializer<'de>, 127 | { 128 | let TransportedReceiver { port } = TransportedReceiver::deserialize(deserializer)?; 129 | 130 | let (receiver_tx, receiver_rx) = tokio::sync::mpsc::unbounded_channel(); 131 | PortDeserializer::accept(port, |local_port, request| { 132 | async move { 133 | match request.accept_from(local_port).await { 134 | Ok((_, raw_rx)) => { 135 | let _ = receiver_tx.send(Ok(raw_rx)); 136 | } 137 | Err(err) => { 138 | let _ = receiver_tx.send(Err(ConnectError::Listen(err))); 139 | } 140 | } 141 | } 142 | .boxed() 143 | })?; 144 | 145 | Ok(Self { 146 | receiver: None, 147 | sender_tx: None, 148 | receiver_rx, 149 | interlock: Arc::new(Mutex::new(Interlock { sender: Location::Remote, receiver: Location::Local })), 150 | successor_tx: std::sync::Mutex::new(None), 151 | }) 152 | } 153 | } 154 | 155 | impl Drop for Receiver { 156 | fn drop(&mut self) { 157 | let successor_tx = self.successor_tx.lock().unwrap().take(); 158 | if let Some(successor_tx) = successor_tx { 159 | let dummy = Self { 160 | receiver: None, 161 | sender_tx: None, 162 | receiver_rx: tokio::sync::mpsc::unbounded_channel().1, 163 | interlock: Arc::new(Mutex::new(Interlock::new())), 164 | successor_tx: std::sync::Mutex::new(None), 165 | }; 166 | let _ = successor_tx.send(mem::replace(self, dummy)); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /remoc/src/rch/bin/sender.rs: -------------------------------------------------------------------------------- 1 | use futures::FutureExt; 2 | use serde::{Deserialize, Serialize}; 3 | use std::{ 4 | fmt, mem, 5 | sync::{Arc, Mutex}, 6 | }; 7 | 8 | use super::{ 9 | super::{ 10 | base::{PortDeserializer, PortSerializer}, 11 | ConnectError, 12 | }, 13 | Interlock, Location, 14 | }; 15 | use crate::chmux; 16 | 17 | /// A binary channel sender. 18 | pub struct Sender { 19 | pub(super) sender: Option>, 20 | pub(super) sender_rx: tokio::sync::mpsc::UnboundedReceiver>, 21 | pub(super) receiver_tx: Option>>, 22 | pub(super) interlock: Arc>, 23 | pub(super) successor_tx: std::sync::Mutex>>, 24 | } 25 | 26 | impl fmt::Debug for Sender { 27 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 28 | f.debug_struct("Sender").finish() 29 | } 30 | } 31 | 32 | /// A binary channel sender in transport. 33 | #[derive(Debug, Serialize, Deserialize)] 34 | pub(crate) struct TransportedSender { 35 | /// chmux port number. 36 | pub port: u32, 37 | } 38 | 39 | impl Sender { 40 | async fn connect(&mut self) { 41 | if self.sender.is_none() { 42 | self.sender = Some(self.sender_rx.recv().await.unwrap_or(Err(ConnectError::Dropped))); 43 | } 44 | } 45 | 46 | /// Establishes the connection and returns a reference to the chmux sender channel 47 | /// to the remote endpoint. 48 | pub async fn get(&mut self) -> Result<&mut chmux::Sender, ConnectError> { 49 | self.connect().await; 50 | self.sender.as_mut().unwrap().as_mut().map_err(|err| err.clone()) 51 | } 52 | 53 | /// Establishes the connection and returns the chmux sender channel 54 | /// to the remote endpoint. 55 | pub async fn into_inner(mut self) -> Result { 56 | self.connect().await; 57 | self.sender.take().unwrap() 58 | } 59 | 60 | /// Forward data. 61 | async fn forward(successor_rx: tokio::sync::oneshot::Receiver, rx: super::Receiver) { 62 | let Ok(tx) = successor_rx.await else { return }; 63 | let Ok(mut tx) = tx.into_inner().await else { return }; 64 | let Ok(mut rx) = rx.into_inner().await else { return }; 65 | if let Err(err) = rx.forward(&mut tx).await { 66 | tracing::debug!("forwarding binary channel failed: {err}"); 67 | } 68 | } 69 | } 70 | 71 | impl Serialize for Sender { 72 | /// Serializes this sender for sending over a chmux channel. 73 | fn serialize(&self, serializer: S) -> Result 74 | where 75 | S: serde::Serializer, 76 | { 77 | let receiver_tx = self.receiver_tx.clone(); 78 | let interlock_confirm = { 79 | let mut interlock = self.interlock.lock().unwrap(); 80 | if interlock.receiver.check_local() { 81 | Some(interlock.receiver.start_send()) 82 | } else { 83 | None 84 | } 85 | }; 86 | 87 | match (receiver_tx, interlock_confirm) { 88 | // Local-remote connection. 89 | (Some(receiver_tx), Some(interlock_confirm)) => { 90 | let port = PortSerializer::connect(|connect| { 91 | async move { 92 | let _ = interlock_confirm.send(()); 93 | 94 | match connect.await { 95 | Ok((_, raw_rx)) => { 96 | let _ = receiver_tx.send(Ok(raw_rx)); 97 | } 98 | Err(err) => { 99 | let _ = receiver_tx.send(Err(ConnectError::Connect(err))); 100 | } 101 | } 102 | } 103 | .boxed() 104 | })?; 105 | 106 | TransportedSender { port }.serialize(serializer) 107 | } 108 | 109 | // Forwarding. 110 | _ => { 111 | let (successor_tx, successor_rx) = tokio::sync::oneshot::channel(); 112 | *self.successor_tx.lock().unwrap() = Some(successor_tx); 113 | let (tx, rx) = super::channel(); 114 | PortSerializer::spawn(Self::forward(successor_rx, rx))?; 115 | 116 | tx.serialize(serializer) 117 | } 118 | } 119 | } 120 | } 121 | 122 | impl<'de> Deserialize<'de> for Sender { 123 | /// Deserializes this sender after it has been received over a chmux channel. 124 | fn deserialize(deserializer: D) -> Result 125 | where 126 | D: serde::Deserializer<'de>, 127 | { 128 | let TransportedSender { port } = TransportedSender::deserialize(deserializer)?; 129 | 130 | let (sender_tx, sender_rx) = tokio::sync::mpsc::unbounded_channel(); 131 | PortDeserializer::accept(port, |local_port, request| { 132 | async move { 133 | match request.accept_from(local_port).await { 134 | Ok((raw_tx, _)) => { 135 | let _ = sender_tx.send(Ok(raw_tx)); 136 | } 137 | Err(err) => { 138 | let _ = sender_tx.send(Err(ConnectError::Listen(err))); 139 | } 140 | } 141 | } 142 | .boxed() 143 | })?; 144 | 145 | Ok(Self { 146 | sender: None, 147 | sender_rx, 148 | receiver_tx: None, 149 | interlock: Arc::new(Mutex::new(Interlock { sender: Location::Local, receiver: Location::Remote })), 150 | successor_tx: std::sync::Mutex::new(None), 151 | }) 152 | } 153 | } 154 | 155 | impl Drop for Sender { 156 | fn drop(&mut self) { 157 | let successor_tx = self.successor_tx.lock().unwrap().take(); 158 | if let Some(successor_tx) = successor_tx { 159 | let dummy = Self { 160 | sender: None, 161 | sender_rx: tokio::sync::mpsc::unbounded_channel().1, 162 | receiver_tx: None, 163 | interlock: Arc::new(Mutex::new(Interlock::new())), 164 | successor_tx: std::sync::Mutex::new(None), 165 | }; 166 | let _ = successor_tx.send(mem::replace(self, dummy)); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /remoc/src/rch/broadcast/mod.rs: -------------------------------------------------------------------------------- 1 | //! A multi-producer, multi-consumer broadcast queue with receivers that may be located on remote endpoints. 2 | //! 3 | //! Each sent value is seen by all consumers. 4 | //! The senders must be local, while the receivers can be sent to 5 | //! remote endpoints. 6 | //! Forwarding is supported. 7 | //! 8 | //! This has similar functionality as [tokio::sync::broadcast] with the additional 9 | //! ability to work over remote connections. 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | use crate::{codec, RemoteSend}; 14 | 15 | mod receiver; 16 | mod sender; 17 | 18 | pub use receiver::{Receiver, ReceiverStream, RecvError, StreamError, TryRecvError}; 19 | pub use sender::{Broadcasting, SendError, Sender, Sending}; 20 | 21 | /// Broadcast transport message. 22 | #[derive(Clone, Debug, Serialize, Deserialize)] 23 | pub(crate) enum BroadcastMsg { 24 | /// Value. 25 | Value(T), 26 | /// Lagged notification. 27 | Lagged, 28 | } 29 | 30 | /// Create a bounded, multi-producer, multi-consumer channel where each sent value is broadcasted to all active receivers. 31 | pub fn channel( 32 | send_buffer: usize, 33 | ) -> (Sender, Receiver) 34 | where 35 | T: RemoteSend + Clone, 36 | Codec: codec::Codec, 37 | { 38 | let sender = Sender::new(); 39 | let receiver = sender.subscribe(send_buffer); 40 | (sender, receiver) 41 | } 42 | -------------------------------------------------------------------------------- /remoc/src/rch/interlock.rs: -------------------------------------------------------------------------------- 1 | /// Interlocks sender and receiver against both being sent. 2 | pub(crate) struct Interlock { 3 | pub sender: Location, 4 | pub receiver: Location, 5 | } 6 | 7 | impl Interlock { 8 | /// Creates a new interlock with local sender and receiver locations. 9 | pub fn new() -> Self { 10 | Self { sender: Location::Local, receiver: Location::Local } 11 | } 12 | } 13 | 14 | /// Location of a sender or receiver. 15 | pub(crate) enum Location { 16 | Local, 17 | Sending(tokio::sync::oneshot::Receiver<()>), 18 | Remote, 19 | } 20 | 21 | impl Location { 22 | /// True if location is local. 23 | pub fn check_local(&mut self) -> bool { 24 | match self { 25 | Self::Local => true, 26 | Self::Sending(rx) => match rx.try_recv() { 27 | Ok(()) => { 28 | *self = Self::Remote; 29 | false 30 | } 31 | Err(tokio::sync::oneshot::error::TryRecvError::Empty) => false, 32 | Err(tokio::sync::oneshot::error::TryRecvError::Closed) => { 33 | *self = Self::Local; 34 | true 35 | } 36 | }, 37 | Self::Remote => false, 38 | } 39 | } 40 | 41 | // Start sending and return confirmation channel. 42 | pub fn start_send(&mut self) -> tokio::sync::oneshot::Sender<()> { 43 | let (tx, rx) = tokio::sync::oneshot::channel(); 44 | *self = Self::Sending(rx); 45 | tx 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /remoc/src/rch/lr/mod.rs: -------------------------------------------------------------------------------- 1 | //! A channel that exchanges values of arbitrary type with a remote endpoint and 2 | //! is established by sending exactly one half of it over an existing channel. 3 | //! 4 | //! This is a lighter and more restricted version of an [MPSC channel](super::mpsc). 5 | //! It does not spawn a task per connected channel, but forwarding is not supported 6 | //! and exactly one half of the channel must be sent to a remote endpoint. 7 | 8 | use std::sync::{Arc, Mutex}; 9 | 10 | mod receiver; 11 | mod sender; 12 | 13 | use super::{ 14 | interlock::{Interlock, Location}, 15 | DEFAULT_MAX_ITEM_SIZE, 16 | }; 17 | pub use receiver::{Receiver, RecvError}; 18 | pub use sender::{SendError, SendErrorKind, Sender}; 19 | 20 | /// Creates a new local/remote channel that is established by sending either the sender or receiver 21 | /// over a remote channel. 22 | pub fn channel() -> (Sender, Receiver) { 23 | let (sender_tx, sender_rx) = tokio::sync::mpsc::unbounded_channel(); 24 | let (receiver_tx, receiver_rx) = tokio::sync::mpsc::unbounded_channel(); 25 | let interlock = Arc::new(Mutex::new(Interlock { sender: Location::Local, receiver: Location::Local })); 26 | 27 | let sender = Sender { 28 | sender: None, 29 | sender_rx, 30 | receiver_tx: Some(receiver_tx), 31 | interlock: interlock.clone(), 32 | max_item_size: DEFAULT_MAX_ITEM_SIZE, 33 | }; 34 | let receiver = Receiver { 35 | receiver: None, 36 | sender_tx: Some(sender_tx), 37 | receiver_rx, 38 | interlock, 39 | max_item_size: DEFAULT_MAX_ITEM_SIZE, 40 | }; 41 | (sender, receiver) 42 | } 43 | -------------------------------------------------------------------------------- /remoc/src/rch/mpsc/distributor.rs: -------------------------------------------------------------------------------- 1 | use futures::future; 2 | 3 | use super::{ 4 | super::{DEFAULT_BUFFER, DEFAULT_MAX_ITEM_SIZE}, 5 | channel, Permit, Receiver, Sender, 6 | }; 7 | use crate::{codec, exec, RemoteSend}; 8 | 9 | struct DistributedReceiver { 10 | tx: Sender, 11 | remove_rx: Option>, 12 | } 13 | 14 | impl DistributedReceiver 15 | where 16 | T: RemoteSend + Clone, 17 | Codec: codec::Codec, 18 | { 19 | async fn reserve(&mut self) -> Option> { 20 | let tx = self.tx.clone(); 21 | 22 | loop { 23 | let remove = async { 24 | match &mut self.remove_rx { 25 | Some(remove_rx) => remove_rx.recv().await, 26 | None => future::pending().await, 27 | } 28 | }; 29 | 30 | tokio::select! { 31 | res = tx.reserve() => return res.ok(), 32 | res = remove => { 33 | match res { 34 | Some(()) => return None, 35 | None => self.remove_rx = None, 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | /// A handle to a receiver that receives its values from a distributor. 44 | pub struct DistributedReceiverHandle(tokio::sync::mpsc::UnboundedSender<()>); 45 | 46 | impl DistributedReceiverHandle { 47 | /// Removes the associated receiver from the distributor. 48 | pub fn remove(self) { 49 | let _ = self.0.send(()); 50 | } 51 | 52 | /// Waits for the associated receiver to be closed or fail due to an error. 53 | pub async fn closed(&mut self) { 54 | self.0.closed().await 55 | } 56 | } 57 | 58 | /// Distributes items of an mpsc channel over multiple receivers. 59 | /// 60 | /// Distribution is stopped and all subscribers are closed when the distributor 61 | /// is dropped. 62 | pub struct Distributor< 63 | T, 64 | Codec = codec::Default, 65 | const BUFFER: usize = DEFAULT_BUFFER, 66 | const MAX_ITEM_SIZE: usize = DEFAULT_MAX_ITEM_SIZE, 67 | > { 68 | #[allow(clippy::type_complexity)] 69 | sub_tx: tokio::sync::mpsc::Sender< 70 | tokio::sync::oneshot::Sender<(Receiver, DistributedReceiverHandle)>, 71 | >, 72 | } 73 | 74 | impl Distributor 75 | where 76 | T: RemoteSend + Clone, 77 | Codec: codec::Codec, 78 | { 79 | pub(crate) fn new(rx: Receiver, wait_on_empty: bool) -> Self { 80 | let (sub_tx, sub_rx) = tokio::sync::mpsc::channel(1); 81 | exec::spawn(Self::distribute(rx, sub_rx, wait_on_empty)); 82 | Self { sub_tx } 83 | } 84 | 85 | #[allow(clippy::type_complexity)] 86 | async fn distribute( 87 | mut rx: Receiver, 88 | mut sub_rx: tokio::sync::mpsc::Receiver< 89 | tokio::sync::oneshot::Sender<(Receiver, DistributedReceiverHandle)>, 90 | >, 91 | wait_on_empty: bool, 92 | ) { 93 | let mut txs: Vec> = Vec::new(); 94 | let mut first = true; 95 | 96 | loop { 97 | if txs.is_empty() && !(wait_on_empty || first) { 98 | return; 99 | } 100 | first = false; 101 | 102 | let send_task = async { 103 | if txs.is_empty() { 104 | future::pending().await 105 | } else { 106 | let permits = txs.iter_mut().map(|dr| Box::pin(dr.reserve())); 107 | let (permit_opt, pos, _) = future::select_all(permits).await; 108 | 109 | match permit_opt { 110 | None => { 111 | txs.swap_remove(pos); 112 | } 113 | Some(permit) => { 114 | let value = match rx.recv().await { 115 | Ok(Some(value)) => value, 116 | _ => return false, 117 | }; 118 | permit.send(value); 119 | } 120 | } 121 | 122 | true 123 | } 124 | }; 125 | 126 | tokio::select! { 127 | cont = send_task => { 128 | if !cont { 129 | return; 130 | } 131 | } 132 | 133 | sub_opt = sub_rx.recv() => { 134 | match sub_opt { 135 | Some(sub_tx) => { 136 | let (tx, rx) = channel(1); 137 | let mut tx = tx.set_buffer(); 138 | tx.set_max_item_size(MAX_ITEM_SIZE); 139 | let rx = rx.set_buffer().set_max_item_size(); 140 | let (remove_tx, remove_rx) = tokio::sync::mpsc::unbounded_channel(); 141 | let dr = DistributedReceiver { 142 | tx, remove_rx: Some(remove_rx) 143 | }; 144 | let drh = DistributedReceiverHandle(remove_tx); 145 | txs.push(dr); 146 | let _ = sub_tx.send((rx, drh)); 147 | } 148 | None => return, 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | /// Creates a new subscribed receiver and returns it along with its handle. 156 | pub async fn subscribe( 157 | &self, 158 | ) -> Option<(Receiver, DistributedReceiverHandle)> { 159 | let (sub_tx, sub_rx) = tokio::sync::oneshot::channel(); 160 | let _ = self.sub_tx.send(sub_tx).await; 161 | sub_rx.await.ok() 162 | } 163 | 164 | /// Waits until the distributor is closed. 165 | /// 166 | /// The distributor closes when all subscribers are closed and `wait_on_empty` is false, 167 | /// or when the upstream sender is dropped or fails. 168 | pub async fn closed(&self) { 169 | self.sub_tx.closed().await 170 | } 171 | } 172 | 173 | impl Drop 174 | for Distributor 175 | { 176 | fn drop(&mut self) { 177 | // empty 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /remoc/src/rch/oneshot/mod.rs: -------------------------------------------------------------------------------- 1 | //! A one-shot channel is used for sending a single message between asynchronous, remote tasks. 2 | //! 3 | //! The sender and receiver can both be sent to remote endpoints. 4 | //! The channel also works if both halves are local. 5 | //! Forwarding over multiple connections is supported. 6 | //! 7 | //! This has similar functionality as [tokio::sync::oneshot] with the additional 8 | //! ability to work over remote connections. 9 | //! 10 | //! # Example 11 | //! 12 | //! In the following example the client sends a number and a oneshot channel sender to the server. 13 | //! The server squares the received number and sends the result back over the oneshot channel. 14 | //! 15 | //! ``` 16 | //! use remoc::prelude::*; 17 | //! 18 | //! #[derive(Debug, serde::Serialize, serde::Deserialize)] 19 | //! struct SquareReq { 20 | //! number: u32, 21 | //! result_tx: rch::oneshot::Sender, 22 | //! } 23 | //! 24 | //! // This would be run on the client. 25 | //! async fn client(mut tx: rch::base::Sender) { 26 | //! let (result_tx, result_rx) = rch::oneshot::channel(); 27 | //! tx.send(SquareReq { number: 4, result_tx }).await.unwrap(); 28 | //! let result = result_rx.await.unwrap(); 29 | //! assert_eq!(result, 16); 30 | //! } 31 | //! 32 | //! // This would be run on the server. 33 | //! async fn server(mut rx: rch::base::Receiver) { 34 | //! while let Some(req) = rx.recv().await.unwrap() { 35 | //! req.result_tx.send(req.number * req.number).unwrap(); 36 | //! } 37 | //! } 38 | //! # tokio_test::block_on(remoc::doctest::client_server(client, server)); 39 | //! ``` 40 | //! 41 | 42 | use serde::{de::DeserializeOwned, Serialize}; 43 | 44 | use super::mpsc; 45 | use crate::{codec, RemoteSend}; 46 | 47 | mod receiver; 48 | mod sender; 49 | 50 | pub use receiver::{Receiver, RecvError, TryRecvError}; 51 | pub use sender::{SendError, Sender}; 52 | 53 | /// Create a new one-shot channel for sending single values across asynchronous tasks. 54 | /// 55 | /// The sender and receiver may be sent to remote endpoints via channels. 56 | pub fn channel() -> (Sender, Receiver) 57 | where 58 | T: Serialize + DeserializeOwned + Send + 'static, 59 | Codec: codec::Codec, 60 | { 61 | let (tx, rx) = mpsc::channel(1); 62 | let tx = tx.set_buffer(); 63 | let rx = rx.set_buffer(); 64 | (Sender(tx), Receiver(rx)) 65 | } 66 | 67 | /// Extensions for oneshot channels. 68 | pub trait OneshotExt { 69 | /// Sets the maximum item size for the channel. 70 | fn with_max_item_size( 71 | self, 72 | ) -> (Sender, Receiver); 73 | } 74 | 75 | impl OneshotExt 76 | for (Sender, Receiver) 77 | where 78 | T: RemoteSend, 79 | Codec: codec::Codec, 80 | { 81 | fn with_max_item_size( 82 | self, 83 | ) -> (Sender, Receiver) { 84 | let (mut tx, rx) = self; 85 | tx.set_max_item_size(NEW_MAX_ITEM_SIZE); 86 | let rx = rx.set_max_item_size(); 87 | (tx, rx) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /remoc/src/rch/oneshot/sender.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::{error::Error, fmt}; 3 | 4 | use super::super::{mpsc, ClosedReason, SendErrorExt, Sending}; 5 | use crate::{codec, RemoteSend}; 6 | 7 | /// An error occurred during sending over an mpsc channel. 8 | #[derive(Clone, Debug, Serialize, Deserialize)] 9 | pub enum SendError { 10 | /// The remote end closed the channel. 11 | Closed(T), 12 | /// Communication with the remote endpoint failed. 13 | Failed, 14 | } 15 | 16 | impl SendError { 17 | /// True, if the remote endpoint closed the channel. 18 | pub fn is_closed(&self) -> bool { 19 | matches!(self, Self::Closed(_)) 20 | } 21 | 22 | /// True, if the remote endpoint closed the channel, was dropped or the connection failed. 23 | /// 24 | /// This is always true since a oneshot channel has no method of reporting other errors 25 | /// (such as serialization errors) because the send operation is performed asynchronously. 26 | #[deprecated = "a remoc::rch::oneshot::SendError is always due to disconnection"] 27 | pub fn is_disconnected(&self) -> bool { 28 | true 29 | } 30 | 31 | /// Returns whether the error is final, i.e. no further send operation can succeed. 32 | #[deprecated = "a remoc::rch::oneshot::SendError is always final"] 33 | pub fn is_final(&self) -> bool { 34 | true 35 | } 36 | 37 | /// Returns the error without the contained item. 38 | pub fn without_item(self) -> SendError<()> { 39 | match self { 40 | Self::Closed(_) => SendError::Closed(()), 41 | Self::Failed => SendError::Failed, 42 | } 43 | } 44 | } 45 | 46 | impl SendErrorExt for SendError { 47 | fn is_closed(&self) -> bool { 48 | self.is_closed() 49 | } 50 | 51 | fn is_disconnected(&self) -> bool { 52 | #[allow(deprecated)] 53 | self.is_disconnected() 54 | } 55 | 56 | fn is_final(&self) -> bool { 57 | #[allow(deprecated)] 58 | self.is_final() 59 | } 60 | 61 | fn is_item_specific(&self) -> bool { 62 | false 63 | } 64 | } 65 | 66 | impl fmt::Display for SendError { 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 | match self { 69 | Self::Closed(_) => write!(f, "channel is closed"), 70 | Self::Failed => write!(f, "send error"), 71 | } 72 | } 73 | } 74 | 75 | impl From> for SendError { 76 | fn from(err: mpsc::TrySendError) -> Self { 77 | match err { 78 | mpsc::TrySendError::Closed(err) => Self::Closed(err), 79 | _ => Self::Failed, 80 | } 81 | } 82 | } 83 | 84 | impl Error for SendError where T: fmt::Debug {} 85 | 86 | /// Sends a value to the associated receiver. 87 | #[derive(Serialize, Deserialize)] 88 | #[serde(bound(serialize = "T: RemoteSend, Codec: codec::Codec"))] 89 | #[serde(bound(deserialize = "T: RemoteSend, Codec: codec::Codec"))] 90 | pub struct Sender(pub(crate) mpsc::Sender); 91 | 92 | impl fmt::Debug for Sender { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 94 | f.debug_struct("Sender").finish() 95 | } 96 | } 97 | 98 | impl Sender 99 | where 100 | T: RemoteSend, 101 | Codec: codec::Codec, 102 | { 103 | /// Sends a value over this channel. 104 | #[inline] 105 | pub fn send(self, value: T) -> Result, SendError> { 106 | self.0.try_send(value).map_err(|err| err.into()) 107 | } 108 | 109 | /// Completes when the receiver has been closed, dropped or the connection failed. 110 | /// 111 | /// Use [closed_reason](Self::closed_reason) to obtain the cause for closure. 112 | #[inline] 113 | pub async fn closed(&self) { 114 | self.0.closed().await 115 | } 116 | 117 | /// Returns the reason for why the channel has been closed. 118 | /// 119 | /// Returns [None] if the channel is not closed. 120 | #[inline] 121 | pub fn closed_reason(&self) -> Option { 122 | self.0.closed_reason() 123 | } 124 | 125 | /// Returns whether the receiver has been closed, dropped or the connection failed. 126 | /// 127 | /// Use [closed_reason](Self::closed_reason) to obtain the cause for closure. 128 | #[inline] 129 | pub fn is_closed(&self) -> bool { 130 | self.0.is_closed() 131 | } 132 | 133 | /// The maximum allowed item size in bytes. 134 | pub fn max_item_size(&self) -> usize { 135 | self.0.max_item_size() 136 | } 137 | 138 | /// Sets the maximum allowed item size in bytes. 139 | pub fn set_max_item_size(&mut self, max_item_size: usize) { 140 | self.0.set_max_item_size(max_item_size) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /remoc/src/remote_send.rs: -------------------------------------------------------------------------------- 1 | use serde::{de::DeserializeOwned, Serialize}; 2 | 3 | /// An object that is sendable to a remote endpoint. 4 | /// 5 | /// This trait is automatically implemented for objects that are 6 | /// serializable, deserializable and sendable between threads. 7 | #[cfg_attr(docsrs, doc(cfg(feature = "rch")))] 8 | pub trait RemoteSend: Send + Serialize + DeserializeOwned + 'static {} 9 | 10 | impl RemoteSend for T where T: Send + Serialize + DeserializeOwned + 'static {} 11 | -------------------------------------------------------------------------------- /remoc/src/rfn/msg.rs: -------------------------------------------------------------------------------- 1 | //! Messages exchanged between remote functions and their providers. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::{codec, rch::oneshot, RemoteSend}; 6 | 7 | /// Remote function call request. 8 | #[derive(Serialize, Deserialize)] 9 | #[serde(bound(serialize = "A: RemoteSend, R: RemoteSend, Codec: codec::Codec"))] 10 | #[serde(bound(deserialize = "A: RemoteSend, R: RemoteSend, Codec: codec::Codec"))] 11 | pub struct RFnRequest { 12 | /// Function argument. 13 | pub argument: A, 14 | /// Channel for result transmission. 15 | pub result_tx: oneshot::Sender, 16 | } 17 | -------------------------------------------------------------------------------- /remoc/src/robj/lazy_blob/fw_bin.rs: -------------------------------------------------------------------------------- 1 | //! Binary channel with remotely sendable and forwardable sender. 2 | 3 | use serde::{ser, Deserialize, Serialize}; 4 | use std::sync::Mutex; 5 | 6 | use crate::{exec, rch::bin}; 7 | 8 | /// A chmux sender that can be remotely sent and forwarded. 9 | pub(crate) struct Sender { 10 | bin_tx: Mutex>, 11 | bin_rx_tx: Mutex>>, 12 | } 13 | 14 | impl Sender { 15 | pub fn into_inner(self) -> Option { 16 | let mut bin_tx = self.bin_tx.lock().unwrap(); 17 | bin_tx.take() 18 | } 19 | } 20 | 21 | /// A chmux sender that can be remotely sent and forwarded in transport form. 22 | #[derive(Serialize, Deserialize)] 23 | pub(crate) struct TransportedSender { 24 | bin_tx: bin::Sender, 25 | } 26 | 27 | impl Serialize for Sender { 28 | fn serialize(&self, serializer: S) -> Result 29 | where 30 | S: serde::Serializer, 31 | { 32 | let mut bin_tx = self.bin_tx.lock().unwrap(); 33 | let mut bin_rx_tx = self.bin_rx_tx.lock().unwrap(); 34 | 35 | match (bin_tx.take(), bin_rx_tx.take()) { 36 | // Initial send. 37 | (None, Some(bin_rx_tx)) => { 38 | let (bin_tx, bin_rx) = bin::channel(); 39 | let _ = bin_rx_tx.send(bin_rx); 40 | TransportedSender { bin_tx }.serialize(serializer) 41 | } 42 | 43 | // Forwarded send. 44 | (Some(bin_tx), None) => { 45 | let (bin_fw_tx, bin_fw_rx) = bin::channel(); 46 | exec::spawn(async move { 47 | let Ok(mut bin_tx) = bin_tx.into_inner().await else { return }; 48 | let Ok(mut bin_fw_rx) = bin_fw_rx.into_inner().await else { return }; 49 | 50 | // No error handling is performed, because complete transmission of 51 | // data is verified by size. 52 | let _ = bin_fw_rx.forward(&mut bin_tx).await; 53 | }); 54 | TransportedSender { bin_tx: bin_fw_tx }.serialize(serializer) 55 | } 56 | 57 | _ => Err(ser::Error::custom("invalid state")), 58 | } 59 | } 60 | } 61 | 62 | impl<'de> Deserialize<'de> for Sender { 63 | fn deserialize(deserializer: D) -> Result 64 | where 65 | D: serde::Deserializer<'de>, 66 | { 67 | let TransportedSender { bin_tx } = TransportedSender::deserialize(deserializer)?; 68 | 69 | Ok(Self { bin_tx: Mutex::new(Some(bin_tx)), bin_rx_tx: Mutex::new(None) }) 70 | } 71 | } 72 | 73 | /// A receiver for the corresponding [Sender]. 74 | /// 75 | /// Cannot be remotely sent. 76 | pub(crate) struct Receiver { 77 | bin_rx_rx: tokio::sync::oneshot::Receiver, 78 | } 79 | 80 | impl Receiver { 81 | pub async fn into_inner(self) -> Option { 82 | self.bin_rx_rx.await.ok() 83 | } 84 | } 85 | 86 | /// Create a binary channel with a sender that is remotely sendable and forwardable. 87 | pub(crate) fn channel() -> (Sender, Receiver) { 88 | let (bin_rx_tx, bin_rx_rx) = tokio::sync::oneshot::channel(); 89 | let sender = Sender { bin_tx: Mutex::new(None), bin_rx_tx: Mutex::new(Some(bin_rx_tx)) }; 90 | let receiver = Receiver { bin_rx_rx }; 91 | (sender, receiver) 92 | } 93 | -------------------------------------------------------------------------------- /remoc/src/robj/mod.rs: -------------------------------------------------------------------------------- 1 | //! Remote objects. 2 | //! 3 | //! This module provides functionality to access and transmit data remotely. 4 | //! All types presented here can be sent over [remote channels](crate::rch) 5 | //! or used as arguments or return types of [remote functions](crate::rfn) or in 6 | //! [remote traits](crate::rtc). 7 | //! 8 | 9 | pub mod handle; 10 | pub mod lazy; 11 | pub mod lazy_blob; 12 | pub mod rw_lock; 13 | -------------------------------------------------------------------------------- /remoc/src/robj/rw_lock/mod.rs: -------------------------------------------------------------------------------- 1 | //! A read/write lock that shares a consistent view of a value over multiple remote endpoints. 2 | //! 3 | //! A remote read/write lock allows multiple remote endpoints to share a common value 4 | //! that can be read and written by each endpoint. 5 | //! Multiple endpoints can acquire simultaneous read access to the value. 6 | //! Write access is exclusive and can only be held by one endpoint at a time. 7 | //! This ensures that all endpoints always have a consist view of the shared value. 8 | //! The shared value is always stored on the endpoint that created the 9 | //! [RwLock owner](Owner). 10 | //! 11 | //! Each [RwLock] caches the current value locally until it is invalidated. 12 | //! Thus, subsequent read operations are cheap. 13 | //! However, a write operation requires several round trips between the owner 14 | //! and the remote endpoints, thus its performance is limited by the physical connection 15 | //! latency. 16 | //! 17 | //! # Usage 18 | //! 19 | //! [Create an RwLock owner](Owner::new) and use [Owner::rw_lock] to acquire 20 | //! read/write locks that can be send to remote endpoints, for example over a 21 | //! [remote channel](crate::rch). 22 | //! The remote endpoints can then use [RwLock::read] and [RwLock::write] to obtain 23 | //! read or write access respectively. 24 | //! When the [owner](Owner) is dropped, all locks become invalid and the value 25 | //! is dropped. 26 | //! 27 | //! # Alternatives 28 | //! 29 | //! If you require to broadcast value to multiple endpoints that just require read 30 | //! access, a [watch channel](crate::rch::watch) might be a simpler and better option. 31 | //! 32 | //! # Example 33 | //! 34 | //! In the following example the server creates a lock owner and obtains a 35 | //! read/write lock from it that is sent to the client. 36 | //! The client obtains a read guard and verifies the data. 37 | //! Then it obtains a write guard and changes the data. 38 | //! Afterwards it obtains a new read guard and verifies that the changes have 39 | //! been performed. 40 | //! 41 | //! ``` 42 | //! use remoc::prelude::*; 43 | //! use remoc::robj::rw_lock::{Owner, RwLock}; 44 | //! 45 | //! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] 46 | //! struct Data { 47 | //! field1: u32, 48 | //! field2: String, 49 | //! } 50 | //! 51 | //! // This would be run on the client. 52 | //! async fn client(mut rx: rch::base::Receiver>) { 53 | //! let mut rw_lock = rx.recv().await.unwrap().unwrap(); 54 | //! 55 | //! let read = rw_lock.read().await.unwrap(); 56 | //! assert_eq!(read.field1, 123); 57 | //! assert_eq!(read.field2, "data"); 58 | //! drop(read); 59 | //! 60 | //! let mut write = rw_lock.write().await.unwrap(); 61 | //! write.field1 = 222; 62 | //! write.commit().await.unwrap(); 63 | //! 64 | //! let read = rw_lock.read().await.unwrap(); 65 | //! assert_eq!(read.field1, 222); 66 | //! } 67 | //! 68 | //! // This would be run on the server. 69 | //! async fn server(mut tx: rch::base::Sender>) { 70 | //! let data = Data { field1: 123, field2: "data".to_string() }; 71 | //! let owner = Owner::new(data); 72 | //! tx.send(owner.rw_lock()).await.unwrap(); 73 | //! 74 | //! // The owner must be kept alive until the client is done with the lock. 75 | //! tx.closed().await; 76 | //! } 77 | //! # tokio_test::block_on(remoc::doctest::client_server(server, client)); 78 | //! ``` 79 | //! 80 | 81 | mod msg; 82 | mod owner; 83 | #[allow(clippy::module_inception)] 84 | mod rw_lock; 85 | 86 | pub use owner::Owner; 87 | pub use rw_lock::{CommitError, LockError, ReadGuard, ReadLock, RwLock, WriteGuard}; 88 | -------------------------------------------------------------------------------- /remoc/src/robj/rw_lock/msg.rs: -------------------------------------------------------------------------------- 1 | //! Messages exchanged between read/write locks and the owner. 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::{ 6 | codec, 7 | rch::{mpsc, oneshot, watch}, 8 | RemoteSend, 9 | }; 10 | 11 | /// A read request from a lock to the owner. 12 | #[derive(Debug, Serialize, Deserialize)] 13 | #[serde(bound(serialize = "T: RemoteSend, Codec: codec::Codec"))] 14 | #[serde(bound(deserialize = "T: RemoteSend, Codec: codec::Codec"))] 15 | pub struct ReadRequest { 16 | /// Channel for sending the value. 17 | pub(crate) value_tx: oneshot::Sender, Codec>, 18 | } 19 | 20 | /// A write request from a lock to the owner. 21 | #[derive(Debug, Serialize, Deserialize)] 22 | #[serde(bound(serialize = "T: RemoteSend, Codec: codec::Codec"))] 23 | #[serde(bound(deserialize = "T: RemoteSend, Codec: codec::Codec"))] 24 | pub struct WriteRequest { 25 | /// Channel for sending current value. 26 | pub(crate) value_tx: oneshot::Sender, 27 | /// Channel for receiving modified value. 28 | pub(crate) new_value_rx: oneshot::Receiver, 29 | /// Channel for confirming that modified value has been stored. 30 | pub(crate) confirm_tx: oneshot::Sender<(), Codec>, 31 | } 32 | 33 | /// A value together with invalidation channels. 34 | #[derive(Clone, Serialize, Deserialize)] 35 | #[serde(bound(serialize = "T: RemoteSend, Codec: codec::Codec"))] 36 | #[serde(bound(deserialize = "T: RemoteSend, Codec: codec::Codec"))] 37 | pub struct Value { 38 | /// The shared value. 39 | pub(crate) value: T, 40 | /// Notification channel that all instances of this value have been dropped. 41 | pub(crate) dropped_tx: mpsc::Sender<(), Codec, 1>, 42 | /// Notification channel that value has been invalidated by the owner. 43 | pub(crate) invalid_rx: watch::Receiver, 44 | } 45 | 46 | impl Value 47 | where 48 | T: RemoteSend, 49 | Codec: codec::Codec, 50 | { 51 | /// True, if value is valid. 52 | pub(crate) fn is_valid(&self) -> bool { 53 | if self.dropped_tx.is_closed() { 54 | return false; 55 | } 56 | 57 | match self.invalid_rx.borrow() { 58 | Ok(invalid) if !*invalid => (), 59 | _ => return false, 60 | } 61 | 62 | true 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /remoc/src/robj/rw_lock/owner.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use super::{ 4 | msg::{ReadRequest, Value, WriteRequest}, 5 | ReadLock, RwLock, 6 | }; 7 | use crate::{ 8 | codec, exec, 9 | exec::task::JoinHandle, 10 | rch::{mpsc, watch}, 11 | RemoteSend, 12 | }; 13 | 14 | /// The owner of [RwLock]s holding a shared value. 15 | /// 16 | /// All acquired locks become invalid when this is dropped. 17 | /// 18 | /// See [module-level documentation](super) for details. 19 | pub struct Owner { 20 | task: Option>, 21 | rw_lock: RwLock, 22 | term_tx: Option>, 23 | } 24 | 25 | impl fmt::Debug for Owner { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | f.debug_struct("Owner").finish() 28 | } 29 | } 30 | 31 | impl Owner 32 | where 33 | T: RemoteSend + Clone + Sync, 34 | Codec: codec::Codec, 35 | { 36 | /// Creates a new [RwLock] owner with the specified shared value. 37 | pub fn new(mut value: T) -> Self { 38 | let (read_req_tx, read_req_rx) = mpsc::channel(1); 39 | let read_req_tx = read_req_tx.set_buffer(); 40 | let read_req_rx = read_req_rx.set_buffer(); 41 | let (write_req_tx, write_req_rx) = mpsc::channel(1); 42 | let write_req_tx = write_req_tx.set_buffer(); 43 | let write_req_rx = write_req_rx.set_buffer(); 44 | let (term_tx, term_rx) = tokio::sync::oneshot::channel(); 45 | 46 | let task = exec::spawn(async move { 47 | tokio::select! { 48 | _ = Self::owner_task(&mut value, read_req_rx, write_req_rx) => (), 49 | _ = term_rx => (), 50 | } 51 | value 52 | }); 53 | 54 | let read_lock = ReadLock::new(read_req_tx); 55 | let rw_lock = RwLock::new(read_lock, write_req_tx); 56 | 57 | Self { task: Some(task), rw_lock, term_tx: Some(term_tx) } 58 | } 59 | 60 | /// Message handler for lock owner. 61 | async fn owner_task( 62 | value: &mut T, mut read_req_rx: mpsc::Receiver, Codec, 1>, 63 | mut write_req_rx: mpsc::Receiver, Codec, 1>, 64 | ) { 65 | let (dropped_tx, dropped_rx) = mpsc::channel(1); 66 | let mut dropped_tx = dropped_tx.set_buffer(); 67 | let mut dropped_rx = dropped_rx.set_buffer::<1>(); 68 | let (mut invalid_tx, mut invalid_rx) = watch::channel(false); 69 | 70 | loop { 71 | tokio::select! { 72 | biased; 73 | 74 | // Write value request. 75 | res = write_req_rx.recv() => { 76 | let WriteRequest {value_tx, new_value_rx, confirm_tx} = match res { 77 | Ok(Some(req)) => req, 78 | Ok(None) => break, 79 | Err(err) if err.is_final() => break, 80 | Err(_) => continue, 81 | }; 82 | 83 | // Invalidate current value. 84 | let _ = invalid_tx.send(true); 85 | 86 | // Wait for drop confirmation from all lock holders. 87 | drop(dropped_tx); 88 | loop { 89 | if let Ok(None) = dropped_rx.recv().await { 90 | break; 91 | } 92 | } 93 | 94 | // Create new dropped notification channel. 95 | let (new_dropped_tx, new_dropped_rx) = mpsc::channel(1); 96 | let new_dropped_tx = new_dropped_tx.set_buffer(); 97 | let new_dropped_rx = new_dropped_rx.set_buffer(); 98 | dropped_tx = new_dropped_tx; 99 | dropped_rx = new_dropped_rx; 100 | 101 | // Create new invalidation channel. 102 | let (new_invalid_tx, new_invalid_rx) = watch::channel(false); 103 | invalid_tx = new_invalid_tx; 104 | invalid_rx = new_invalid_rx; 105 | 106 | // Send current value for writing. 107 | let _ = value_tx.send(value.clone()); 108 | 109 | // Wait for modified value and store it. 110 | if let Ok(nv) = new_value_rx.await { 111 | *value = nv; 112 | 113 | // Send confirmation. 114 | let _ = confirm_tx.send(()); 115 | } 116 | }, 117 | 118 | // Read value request. 119 | res = read_req_rx.recv() => { 120 | let ReadRequest {value_tx} = match res { 121 | Ok(Some(req)) => req, 122 | Ok(None) => break, 123 | Err(err) if err.is_final() => break, 124 | Err(_) => continue, 125 | }; 126 | 127 | // Send current value together with invalidation channels. 128 | let v = Value { 129 | value: value.clone(), 130 | dropped_tx: dropped_tx.clone(), 131 | invalid_rx: invalid_rx.clone(), 132 | }; 133 | let _ = value_tx.send(v); 134 | }, 135 | } 136 | } 137 | } 138 | 139 | /// Makes all acquired locks invalid and returns the shared value. 140 | pub async fn into_inner(mut self) -> T { 141 | let _ = self.term_tx.take().unwrap().send(()); 142 | self.task.take().unwrap().await.unwrap() 143 | } 144 | 145 | /// Returns a new read/write lock for the shared value. 146 | pub fn rw_lock(&self) -> RwLock { 147 | self.rw_lock.clone() 148 | } 149 | 150 | /// Returns a new read lock for the shared value. 151 | pub fn read_lock(&self) -> ReadLock { 152 | self.rw_lock.read_lock() 153 | } 154 | } 155 | 156 | impl Drop for Owner { 157 | fn drop(&mut self) { 158 | // empty 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /remoc/tests/chmux/mod.rs: -------------------------------------------------------------------------------- 1 | mod channel; 2 | 3 | #[cfg(not(target_family = "wasm"))] 4 | mod tcp; 5 | 6 | #[cfg(unix)] 7 | mod unix; 8 | -------------------------------------------------------------------------------- /remoc/tests/chmux/tcp.rs: -------------------------------------------------------------------------------- 1 | use futures::stream::StreamExt; 2 | use std::{net::Ipv4Addr, time::Duration}; 3 | use tokio::{ 4 | io::split, 5 | net::{TcpListener, TcpStream}, 6 | time::sleep, 7 | }; 8 | use tokio_util::codec::{length_delimited::LengthDelimitedCodec, FramedRead, FramedWrite}; 9 | 10 | use remoc::{chmux, exec}; 11 | 12 | async fn tcp_server() { 13 | let listener = TcpListener::bind((Ipv4Addr::new(127, 0, 0, 1), 9876)).await.unwrap(); 14 | 15 | let (socket, _) = listener.accept().await.unwrap(); 16 | let (socket_rx, socket_tx) = split(socket); 17 | let framed_tx = FramedWrite::new(socket_tx, LengthDelimitedCodec::new()); 18 | let framed_rx = FramedRead::new(socket_rx, LengthDelimitedCodec::new()); 19 | let framed_rx = framed_rx.map(|data| data.map(|b| b.freeze())); 20 | 21 | let mux_cfg = chmux::Cfg::default(); 22 | let (mux, _, mut server) = chmux::ChMux::new(mux_cfg, framed_tx, framed_rx).await.unwrap(); 23 | 24 | let mux_run = exec::spawn(async move { mux.run().await.unwrap() }); 25 | 26 | while let Some((mut tx, mut rx)) = server.accept().await.unwrap() { 27 | println!("Server accepted request."); 28 | 29 | tx.send("Hi from server".into()).await.unwrap(); 30 | println!("Server sent Hi message"); 31 | 32 | println!("Server dropping sender"); 33 | drop(tx); 34 | println!("Server dropped sender"); 35 | 36 | while let Some(msg) = rx.recv().await.unwrap() { 37 | println!("Server received: {}", String::from_utf8_lossy(&Vec::from(msg))); 38 | } 39 | } 40 | 41 | println!("Waiting for server mux to terminate..."); 42 | mux_run.await.unwrap(); 43 | } 44 | 45 | async fn tcp_client() { 46 | let socket = TcpStream::connect((Ipv4Addr::new(127, 0, 0, 1), 9876)).await.unwrap(); 47 | 48 | let (socket_rx, socket_tx) = split(socket); 49 | let framed_tx = FramedWrite::new(socket_tx, LengthDelimitedCodec::new()); 50 | let framed_rx = FramedRead::new(socket_rx, LengthDelimitedCodec::new()); 51 | let framed_rx = framed_rx.map(|data| data.map(|b| b.freeze())); 52 | 53 | let mux_cfg = chmux::Cfg::default(); 54 | let (mux, client, _) = chmux::ChMux::new(mux_cfg, framed_tx, framed_rx).await.unwrap(); 55 | let mux_run = exec::spawn(async move { mux.run().await.unwrap() }); 56 | 57 | { 58 | let client = client; 59 | 60 | println!("Client connecting..."); 61 | let (mut tx, mut rx) = client.connect().await.unwrap(); 62 | println!("Client connected"); 63 | 64 | tx.send("Hi from client".into()).await.unwrap(); 65 | println!("Client sent Hi message"); 66 | 67 | println!("Client dropping sender"); 68 | drop(tx); 69 | println!("Client dropped sender"); 70 | 71 | while let Some(msg) = rx.recv().await.unwrap() { 72 | println!("Client received: {}", String::from_utf8_lossy(&Vec::from(msg))); 73 | } 74 | 75 | println!("Client closing connection..."); 76 | } 77 | 78 | println!("Waiting for client mux to terminate..."); 79 | mux_run.await.unwrap(); 80 | } 81 | 82 | #[tokio::test] 83 | async fn tcp_test() { 84 | crate::init(); 85 | 86 | println!("Starting server task..."); 87 | let server_task = exec::spawn(tcp_server()); 88 | sleep(Duration::from_millis(100)).await; 89 | 90 | println!("String client thread..."); 91 | let client_task = exec::spawn(tcp_client()); 92 | 93 | println!("Waiting for server task..."); 94 | server_task.await.unwrap(); 95 | println!("Waiting for client thread..."); 96 | client_task.await.unwrap(); 97 | } 98 | -------------------------------------------------------------------------------- /remoc/tests/chmux/unix.rs: -------------------------------------------------------------------------------- 1 | use futures::stream::StreamExt; 2 | use std::{fs, time::Duration}; 3 | use tokio::{ 4 | io::split, 5 | net::{UnixListener, UnixStream}, 6 | time::sleep, 7 | }; 8 | use tokio_util::codec::{length_delimited::LengthDelimitedCodec, FramedRead, FramedWrite}; 9 | 10 | use remoc::{chmux, exec}; 11 | 12 | async fn uds_server() { 13 | let _ = fs::remove_file("/tmp/chmux_test"); 14 | let listener = UnixListener::bind("/tmp/chmux_test").unwrap(); 15 | 16 | let (socket, _) = listener.accept().await.unwrap(); 17 | let (socket_rx, socket_tx) = split(socket); 18 | let framed_tx = FramedWrite::new(socket_tx, LengthDelimitedCodec::new()); 19 | let framed_rx = FramedRead::new(socket_rx, LengthDelimitedCodec::new()); 20 | let framed_rx = framed_rx.map(|data| data.map(|b| b.freeze())); 21 | 22 | let mux_cfg = chmux::Cfg::default(); 23 | let (mux, _, mut server) = chmux::ChMux::new(mux_cfg, framed_tx, framed_rx).await.unwrap(); 24 | 25 | let mux_run = exec::spawn(async move { mux.run().await.unwrap() }); 26 | 27 | while let Some((mut tx, mut rx)) = server.accept().await.unwrap() { 28 | println!("Server accepting request"); 29 | 30 | tx.send("Hi from server".into()).await.unwrap(); 31 | println!("Server sent Hi message"); 32 | 33 | println!("Server dropping sender"); 34 | drop(tx); 35 | println!("Server dropped sender"); 36 | 37 | while let Some(msg) = rx.recv().await.unwrap() { 38 | println!("Server received: {}", String::from_utf8_lossy(&Vec::from(msg))); 39 | } 40 | } 41 | 42 | println!("Waiting for server mux to terminate..."); 43 | mux_run.await.unwrap(); 44 | } 45 | 46 | async fn uds_client() { 47 | let socket = UnixStream::connect("/tmp/chmux_test").await.unwrap(); 48 | 49 | let (socket_rx, socket_tx) = split(socket); 50 | let framed_tx = FramedWrite::new(socket_tx, LengthDelimitedCodec::new()); 51 | let framed_rx = FramedRead::new(socket_rx, LengthDelimitedCodec::new()); 52 | let framed_rx = framed_rx.map(|data| data.map(|b| b.freeze())); 53 | 54 | let mux_cfg = chmux::Cfg::default(); 55 | let (mux, client, _) = chmux::ChMux::new(mux_cfg, framed_tx, framed_rx).await.unwrap(); 56 | let mux_run = exec::spawn(async move { mux.run().await.unwrap() }); 57 | 58 | { 59 | let client = client; 60 | 61 | println!("Client connecting..."); 62 | let (mut tx, mut rx) = client.connect().await.unwrap(); 63 | println!("Client connected"); 64 | 65 | tx.send("Hi from client".into()).await.unwrap(); 66 | println!("Client sent Hi message"); 67 | 68 | println!("Client dropping sender"); 69 | drop(tx); 70 | println!("Client dropped sender"); 71 | 72 | while let Some(msg) = rx.recv().await.unwrap() { 73 | println!("Client received: {}", String::from_utf8_lossy(&Vec::from(msg))); 74 | } 75 | 76 | println!("Client closing connection..."); 77 | } 78 | 79 | println!("Waiting for client mux to terminate..."); 80 | mux_run.await.unwrap(); 81 | } 82 | 83 | #[tokio::test] 84 | async fn uds_test() { 85 | crate::init(); 86 | 87 | println!("Starting server task..."); 88 | let server_task = exec::spawn(uds_server()); 89 | sleep(Duration::from_millis(100)).await; 90 | 91 | println!("String client thread..."); 92 | let client_task = exec::spawn(uds_client()); 93 | 94 | println!("Waiting for server task..."); 95 | server_task.await.unwrap(); 96 | println!("Waiting for client thread..."); 97 | client_task.await.unwrap(); 98 | } 99 | -------------------------------------------------------------------------------- /remoc/tests/codec/mod.rs: -------------------------------------------------------------------------------- 1 | use remoc::codec; 2 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 3 | use std::{ 4 | collections::{BTreeMap, HashMap}, 5 | fmt, 6 | }; 7 | 8 | #[cfg(feature = "js")] 9 | use wasm_bindgen_test::wasm_bindgen_test; 10 | 11 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 12 | pub enum TestEnum { 13 | One(u16), 14 | Two { field1: String, field2: u32 }, 15 | } 16 | 17 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 18 | pub struct TestStruct { 19 | simple: String, 20 | btree: BTreeMap, String>, 21 | hash: HashMap<(u16, String), u8>, 22 | enu: Vec, 23 | } 24 | 25 | impl Default for TestStruct { 26 | fn default() -> Self { 27 | let mut data = Self { 28 | simple: "test_string".to_string(), 29 | btree: BTreeMap::new(), 30 | hash: HashMap::new(), 31 | enu: vec![TestEnum::One(11), TestEnum::Two { field1: "value1".to_string(), field2: 2 }], 32 | }; 33 | data.btree.insert(vec![1, 2, 3], "first value".to_string()); 34 | data.btree.insert(vec![4, 5, 6, 7], "second value".to_string()); 35 | data.hash.insert((1, "one".to_string()), 10); 36 | data.hash.insert((2, "two".to_string()), 20); 37 | data.hash.insert((3, "three".to_string()), 30); 38 | data 39 | } 40 | } 41 | 42 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 43 | pub struct TestStructWithAttr { 44 | simple: String, 45 | #[serde(with = "remoc::codec::map::btreemap")] 46 | btree: BTreeMap, String>, 47 | #[serde(with = "remoc::codec::map::hashmap")] 48 | hash: HashMap<(u16, String), u8>, 49 | enu: Vec, 50 | } 51 | 52 | impl Default for TestStructWithAttr { 53 | fn default() -> Self { 54 | let mut data = Self { 55 | simple: "test_string".to_string(), 56 | btree: BTreeMap::new(), 57 | hash: HashMap::new(), 58 | enu: vec![TestEnum::One(11), TestEnum::Two { field1: "value1".to_string(), field2: 2 }], 59 | }; 60 | data.btree.insert(vec![1, 2, 3], "first value".to_string()); 61 | data.btree.insert(vec![4, 5, 6, 7], "second value".to_string()); 62 | data.hash.insert((1, "one".to_string()), 10); 63 | data.hash.insert((2, "two".to_string()), 20); 64 | data.hash.insert((3, "three".to_string()), 30); 65 | data 66 | } 67 | } 68 | 69 | #[allow(dead_code)] 70 | fn roundtrip() 71 | where 72 | T: Default + Serialize + DeserializeOwned + fmt::Debug + Eq, 73 | Codec: codec::Codec, 74 | { 75 | let data: T = Default::default(); 76 | println!("data:\n{:?}", &data); 77 | 78 | let mut buffer = Vec::new(); 79 | ::serialize(&mut buffer, &data).unwrap(); 80 | println!("serialized ({} bytes):\n{}", buffer.len(), String::from_utf8_lossy(&buffer)); 81 | 82 | let deser: T = ::deserialize(buffer.as_slice()).unwrap(); 83 | assert_eq!(deser, data); 84 | } 85 | 86 | #[cfg(feature = "codec-bincode")] 87 | #[cfg_attr(not(feature = "js"), test)] 88 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 89 | fn bincode() { 90 | roundtrip::() 91 | } 92 | 93 | #[cfg(feature = "codec-ciborium")] 94 | #[cfg_attr(not(feature = "js"), test)] 95 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 96 | fn ciborium() { 97 | roundtrip::() 98 | } 99 | 100 | #[cfg(feature = "codec-json")] 101 | #[cfg_attr(not(feature = "js"), test)] 102 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 103 | #[should_panic] 104 | fn json_without_attr() { 105 | roundtrip::() 106 | } 107 | 108 | #[cfg(feature = "codec-json")] 109 | #[cfg_attr(not(feature = "js"), test)] 110 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 111 | fn json_with_attr() { 112 | roundtrip::() 113 | } 114 | 115 | #[cfg(feature = "codec-message-pack")] 116 | #[cfg_attr(not(feature = "js"), test)] 117 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 118 | fn message_pack() { 119 | roundtrip::() 120 | } 121 | 122 | #[cfg(feature = "codec-postcard")] 123 | #[cfg_attr(not(feature = "js"), test)] 124 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 125 | fn postcard() { 126 | roundtrip::() 127 | } 128 | -------------------------------------------------------------------------------- /remoc/tests/rch/lr.rs: -------------------------------------------------------------------------------- 1 | use remoc::rch::lr; 2 | 3 | #[cfg(feature = "js")] 4 | use wasm_bindgen_test::wasm_bindgen_test; 5 | 6 | use crate::loop_channel; 7 | 8 | #[cfg_attr(not(feature = "js"), tokio::test)] 9 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 10 | async fn send_sender() { 11 | crate::init(); 12 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>().await; 13 | 14 | println!("Sending remote lr channel sender"); 15 | let (tx, mut rx) = lr::channel(); 16 | a_tx.send(tx).await.unwrap(); 17 | println!("Receiving remote lr channel sender"); 18 | let mut tx = b_rx.recv().await.unwrap().unwrap(); 19 | 20 | for i in 1..1024 { 21 | println!("Sending {i}"); 22 | tx.send(i).await.unwrap(); 23 | let r = rx.recv().await.unwrap().unwrap(); 24 | println!("Received {r}"); 25 | assert_eq!(i, r, "send/receive mismatch"); 26 | } 27 | 28 | println!("Verifying that channel is open"); 29 | assert!(!tx.is_closed().await.unwrap()); 30 | 31 | println!("Closing channel"); 32 | rx.close().await; 33 | tx.closed().await.unwrap().await; 34 | assert!(tx.is_closed().await.unwrap()); 35 | 36 | println!("Trying send after close"); 37 | match tx.send(0).await { 38 | Ok(_) => panic!("send succeeded after close"), 39 | Err(err) if err.is_closed() => (), 40 | Err(_) => panic!("wrong error after close"), 41 | } 42 | } 43 | 44 | #[cfg_attr(not(feature = "js"), tokio::test)] 45 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 46 | async fn send_receiver() { 47 | crate::init(); 48 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>().await; 49 | 50 | println!("Sending remote lr channel receiver"); 51 | let (mut tx, rx) = lr::channel(); 52 | a_tx.send(rx).await.unwrap(); 53 | println!("Receiving remote lr channel receiver"); 54 | let mut rx = b_rx.recv().await.unwrap().unwrap(); 55 | 56 | for i in 1..1024 { 57 | println!("Sending {i}"); 58 | tx.send(i).await.unwrap(); 59 | let r = rx.recv().await.unwrap().unwrap(); 60 | println!("Received {r}"); 61 | assert_eq!(i, r, "send/receive mismatch"); 62 | } 63 | 64 | println!("Verifying that channel is open"); 65 | assert!(!tx.is_closed().await.unwrap()); 66 | 67 | println!("Dropping receiver"); 68 | drop(rx); 69 | tx.closed().await.unwrap().await; 70 | assert!(tx.is_closed().await.unwrap()); 71 | 72 | println!("Trying send after receiver drop"); 73 | match tx.send(0).await { 74 | Ok(_) => panic!("send succeeded after close"), 75 | Err(err) if err.is_disconnected() => (), 76 | Err(_) => panic!("wrong error after close"), 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /remoc/tests/rch/mod.rs: -------------------------------------------------------------------------------- 1 | mod bin; 2 | mod broadcast; 3 | mod lr; 4 | mod mpsc; 5 | mod oneshot; 6 | mod remote; 7 | mod watch; 8 | -------------------------------------------------------------------------------- /remoc/tests/rch/oneshot.rs: -------------------------------------------------------------------------------- 1 | use remoc::rch::oneshot; 2 | 3 | #[cfg(feature = "js")] 4 | use wasm_bindgen_test::wasm_bindgen_test; 5 | 6 | use crate::loop_channel; 7 | 8 | #[cfg_attr(not(feature = "js"), tokio::test)] 9 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 10 | async fn simple() { 11 | crate::init(); 12 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::<(oneshot::Sender, oneshot::Receiver)>().await; 13 | 14 | println!("Sending remote oneshot channel sender and receiver"); 15 | let (tx, rx) = oneshot::channel(); 16 | a_tx.send((tx, rx)).await.unwrap(); 17 | println!("Receiving remote oneshot channel sender and receiver"); 18 | let (tx, rx) = b_rx.recv().await.unwrap().unwrap(); 19 | 20 | let i = 512; 21 | println!("Sending {i}"); 22 | let mut sending = tx.send(i).unwrap(); 23 | 24 | let r = rx.await.unwrap(); 25 | println!("Received {r}"); 26 | assert_eq!(i, r, "send/receive mismatch"); 27 | 28 | sending.try_result().unwrap().unwrap(); 29 | } 30 | 31 | #[cfg_attr(not(feature = "js"), tokio::test)] 32 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 33 | async fn close() { 34 | crate::init(); 35 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>().await; 36 | 37 | println!("Sending remote oneshot channel sender"); 38 | let (tx, mut rx) = oneshot::channel(); 39 | a_tx.send(tx).await.unwrap(); 40 | println!("Receiving remote oneshot channel sender"); 41 | let tx = b_rx.recv().await.unwrap().unwrap(); 42 | 43 | assert!(!tx.is_closed()); 44 | 45 | println!("Closing receiver"); 46 | rx.close(); 47 | 48 | println!("Waiting for close notification"); 49 | tx.closed().await; 50 | 51 | match tx.send(0) { 52 | Ok(_) => panic!("send after close succeeded"), 53 | #[allow(deprecated)] 54 | Err(err) if err.is_closed() => (), 55 | Err(err) => panic!("wrong error after close: {err}"), 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /remoc/tests/rfn/mod.rs: -------------------------------------------------------------------------------- 1 | mod rfn_const; 2 | mod rfn_mut; 3 | mod rfn_once; 4 | -------------------------------------------------------------------------------- /remoc/tests/rfn/rfn_const.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | use remoc::rfn::{CallError, RFn}; 6 | 7 | #[cfg_attr(not(feature = "js"), tokio::test)] 8 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 9 | async fn simple() { 10 | crate::init(); 11 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>().await; 12 | 13 | let rfn = RFn::new_1(|arg: i16| async move { Ok::<_, CallError>(-arg) }); 14 | 15 | println!("Sending remote function"); 16 | a_tx.send(rfn).await.unwrap(); 17 | println!("Receiving remote function"); 18 | let rfn = b_rx.recv().await.unwrap().unwrap(); 19 | 20 | println!("calling function"); 21 | let value = 123; 22 | let result = rfn.call(value).await.unwrap(); 23 | println!("rfn({value}) = {result}"); 24 | assert_eq!(result, -value); 25 | 26 | println!("calling function"); 27 | let value = 331; 28 | let result = rfn.call(value).await.unwrap(); 29 | println!("rfn({value}) = {result}"); 30 | assert_eq!(result, -value); 31 | } 32 | -------------------------------------------------------------------------------- /remoc/tests/rfn/rfn_mut.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | use remoc::rfn::{CallError, RFnMut}; 6 | 7 | #[cfg_attr(not(feature = "js"), tokio::test)] 8 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 9 | async fn simple() { 10 | crate::init(); 11 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>().await; 12 | 13 | let mut counter = 0; 14 | let rfn = RFnMut::new_2(move |arg1: i16, arg2: i16| { 15 | counter += arg1 + arg2; 16 | async move { Ok::<_, CallError>(counter) } 17 | }); 18 | 19 | println!("Sending remote function"); 20 | a_tx.send(rfn).await.unwrap(); 21 | println!("Receiving remote function"); 22 | let mut rfn = b_rx.recv().await.unwrap().unwrap(); 23 | 24 | println!("calling function"); 25 | let result = rfn.call(12, 13).await.unwrap(); 26 | println!("rfn(12, 13) = {result}"); 27 | assert_eq!(result, 25); 28 | 29 | println!("calling function"); 30 | let result = rfn.call(33, 0).await.unwrap(); 31 | println!("rfn(33, 0) = {result}"); 32 | assert_eq!(result, 58); 33 | } 34 | -------------------------------------------------------------------------------- /remoc/tests/rfn/rfn_once.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | use remoc::rfn::{CallError, RFnOnce}; 6 | 7 | #[cfg_attr(not(feature = "js"), tokio::test)] 8 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 9 | async fn simple() { 10 | crate::init(); 11 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>>().await; 12 | 13 | let reply_value = "reply".to_string(); 14 | let fn_value = reply_value.clone(); 15 | let rfn = RFnOnce::new_1(|arg: i16| async move { 16 | assert_eq!(arg, 123); 17 | Ok(fn_value) 18 | }); 19 | 20 | println!("Sending remote function"); 21 | a_tx.send(rfn).await.unwrap(); 22 | println!("Receiving remote function"); 23 | let rfn = b_rx.recv().await.unwrap().unwrap(); 24 | 25 | println!("calling function"); 26 | let result = rfn.call(123).await.unwrap(); 27 | println!("result: {result}"); 28 | assert_eq!(result, reply_value); 29 | } 30 | -------------------------------------------------------------------------------- /remoc/tests/robj/handle.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | use remoc::{ 6 | codec, 7 | robj::handle::{Handle, HandleError}, 8 | }; 9 | 10 | #[cfg_attr(not(feature = "js"), tokio::test)] 11 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 12 | async fn simple() { 13 | crate::init(); 14 | let ((mut a_tx, mut a_rx), (mut b_tx, mut b_rx)) = loop_channel::>().await; 15 | 16 | let value = "test string".to_string(); 17 | 18 | let local_handle = Handle::new(value.clone()); 19 | println!("Created handle: {:?}", &local_handle); 20 | 21 | let _other_handle: Handle<_, codec::Default> = Handle::new(123); 22 | 23 | println!("Sending handle to remote"); 24 | a_tx.send(local_handle).await.unwrap(); 25 | println!("Receiving handle"); 26 | let remote_handle = b_rx.recv().await.unwrap().unwrap(); 27 | println!("{:?}", &remote_handle); 28 | 29 | match remote_handle.as_ref().await { 30 | Ok(_) => panic!("remote deref succeeded"), 31 | Err(HandleError::Unknown) => (), 32 | Err(err) => panic!("wrong remote deref error: {err}"), 33 | } 34 | 35 | println!("Sending handle back"); 36 | b_tx.send(remote_handle).await.unwrap(); 37 | println!("Receiving handle"); 38 | let local_handle = a_rx.recv().await.unwrap().unwrap(); 39 | println!("{:?}", &local_handle); 40 | 41 | println!("Changing handle type"); 42 | let other_type_handle = local_handle.cast::(); 43 | println!("{:?}", &other_type_handle); 44 | 45 | match other_type_handle.as_ref().await { 46 | Ok(_) => panic!("mismatched type deref succeeded"), 47 | Err(HandleError::MismatchedType(t)) => println!("wrong type: {t}"), 48 | Err(err) => panic!("wrong mismatched type deref error: {err}"), 49 | } 50 | 51 | println!("Changing handle type back to original"); 52 | let mut local_handle = other_type_handle.cast::(); 53 | 54 | assert_eq!(*local_handle.as_ref().await.unwrap(), value); 55 | assert_eq!(*local_handle.as_mut().await.unwrap(), value); 56 | 57 | println!("handle value ref: {}", *local_handle.as_ref().await.unwrap()); 58 | println!("handle value mut: {}", *local_handle.as_mut().await.unwrap()); 59 | println!("handle value: {}", local_handle.into_inner().await.unwrap()); 60 | } 61 | -------------------------------------------------------------------------------- /remoc/tests/robj/lazy.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | use remoc::robj::lazy::Lazy; 6 | 7 | #[cfg_attr(not(feature = "js"), tokio::test)] 8 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 9 | async fn simple() { 10 | crate::init(); 11 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>().await; 12 | 13 | let value = "test string data".to_string(); 14 | 15 | let lazy = Lazy::new(value.clone()); 16 | 17 | println!("Sending lazy"); 18 | a_tx.send(lazy).await.unwrap(); 19 | println!("Receiving lazy"); 20 | let lazy = b_rx.recv().await.unwrap().unwrap(); 21 | 22 | println!("Fetching lazy"); 23 | println!("reference: {}", *lazy.get().await.unwrap()); 24 | println!("value: {}", lazy.into_inner().await.unwrap()); 25 | } 26 | -------------------------------------------------------------------------------- /remoc/tests/robj/lazy_blob.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rng, RngCore}; 2 | 3 | #[cfg(feature = "js")] 4 | use wasm_bindgen_test::wasm_bindgen_test; 5 | 6 | use crate::loop_channel; 7 | use remoc::robj::lazy_blob::LazyBlob; 8 | 9 | #[cfg_attr(not(feature = "js"), tokio::test)] 10 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 11 | async fn simple() { 12 | crate::init(); 13 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 14 | 15 | let mut rng = rand::rng(); 16 | let size = rng.random_range(10_000_000..15_000_000); 17 | let mut data = vec![0; size]; 18 | rng.fill_bytes(&mut data); 19 | 20 | println!("Creating lazy blob of size {} bytes", data.len()); 21 | let lazy: LazyBlob = LazyBlob::new(data.clone().into()); 22 | 23 | println!("Sending lazy blob"); 24 | a_tx.send(lazy).await.unwrap(); 25 | println!("Receiving lazy blob"); 26 | let lazy = b_rx.recv().await.unwrap().unwrap(); 27 | 28 | println!("Length is {} bytes", lazy.len().unwrap()); 29 | assert_eq!(lazy.len().unwrap(), size); 30 | 31 | println!("Fetching reference"); 32 | let fetched = lazy.get().await.unwrap(); 33 | assert_eq!(Vec::from(fetched), data); 34 | 35 | println!("Fetching value"); 36 | let fetched = lazy.into_inner().await.unwrap(); 37 | assert_eq!(Vec::from(fetched), data); 38 | } 39 | -------------------------------------------------------------------------------- /remoc/tests/robj/mod.rs: -------------------------------------------------------------------------------- 1 | mod handle; 2 | mod lazy; 3 | mod lazy_blob; 4 | mod rw_lock; 5 | -------------------------------------------------------------------------------- /remoc/tests/robj/rw_lock.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | use remoc::{ 6 | exec, 7 | robj::rw_lock::{Owner, RwLock}, 8 | }; 9 | 10 | #[cfg_attr(not(feature = "js"), tokio::test)] 11 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 12 | async fn simple() { 13 | crate::init(); 14 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>().await; 15 | 16 | let value = "test string".to_string(); 17 | let new_value = "new value".to_string(); 18 | 19 | println!("Creating owner"); 20 | let owner = Owner::new(value.clone()); 21 | 22 | println!("Sending RwLocks"); 23 | a_tx.send(owner.rw_lock()).await.unwrap(); 24 | a_tx.send(owner.rw_lock()).await.unwrap(); 25 | 26 | println!("Receiving RwLocks"); 27 | let rw_lock1 = b_rx.recv().await.unwrap().unwrap(); 28 | let rw_lock2 = b_rx.recv().await.unwrap().unwrap(); 29 | 30 | { 31 | let read1 = rw_lock1.read().await.unwrap(); 32 | let read2 = rw_lock2.read().await.unwrap(); 33 | println!("Read value 1: {}", *read1); 34 | println!("Read value 2: {}", *read1); 35 | assert_eq!(*read1, value); 36 | assert_eq!(*read2, value); 37 | 38 | assert!(!read1.is_invalidated()); 39 | assert!(!read2.is_invalidated()); 40 | 41 | let rw_lock3 = rw_lock1.clone(); 42 | let read3 = rw_lock3.read().await.unwrap(); 43 | println!("Read value 3: {}", *read3); 44 | assert_eq!(*read3, value); 45 | assert!(!read3.is_invalidated()); 46 | drop(read3); 47 | 48 | println!("Making write request"); 49 | let write_req = exec::spawn(async move { rw_lock3.write().await.unwrap() }); 50 | 51 | println!("Waiting for invalidation"); 52 | read1.invalidated().await; 53 | assert!(read1.is_invalidated()); 54 | println!("read1 invalidated"); 55 | read2.invalidated().await; 56 | assert!(read2.is_invalidated()); 57 | println!("read2 invalidated"); 58 | 59 | drop(read1); 60 | drop(read2); 61 | 62 | write_req.await.unwrap(); 63 | } 64 | 65 | println!("Making write request"); 66 | let mut write1 = rw_lock1.write().await.unwrap(); 67 | *write1 = new_value.clone(); 68 | println!("Committing"); 69 | write1.commit().await.unwrap(); 70 | 71 | let read1 = rw_lock1.read().await.unwrap(); 72 | let read2 = rw_lock2.read().await.unwrap(); 73 | println!("Read value: {}", *read1); 74 | assert_eq!(*read1, new_value); 75 | assert_eq!(*read2, new_value); 76 | 77 | assert!(!read1.is_invalidated()); 78 | assert!(!read2.is_invalidated()); 79 | } 80 | -------------------------------------------------------------------------------- /remoc/tests/robs/list.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | #[cfg(feature = "js")] 4 | use wasm_bindgen_test::wasm_bindgen_test; 5 | 6 | use remoc::{ 7 | exec::time::sleep, 8 | robs::{ 9 | list::{ListEvent, ObservableList}, 10 | RecvError, 11 | }, 12 | }; 13 | 14 | #[cfg_attr(not(feature = "js"), tokio::test)] 15 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 16 | async fn standalone() { 17 | let mut obs: ObservableList<_, remoc::codec::Default> = ObservableList::new(); 18 | 19 | obs.push(0); 20 | obs.push(10); 21 | obs.push(20); 22 | assert_eq!(obs.len(), 3); 23 | } 24 | 25 | #[cfg_attr(not(feature = "js"), tokio::test)] 26 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 27 | async fn events() { 28 | let mut obs: ObservableList<_, remoc::codec::Default> = ObservableList::new(); 29 | 30 | let mut sub = obs.subscribe(); 31 | assert!(!sub.is_complete()); 32 | assert!(!sub.is_done()); 33 | 34 | assert_eq!(sub.recv().await.unwrap(), Some(ListEvent::InitialComplete)); 35 | assert!(sub.is_complete()); 36 | assert!(!sub.is_done()); 37 | 38 | obs.push(0u32); 39 | assert_eq!(sub.recv().await.unwrap(), Some(ListEvent::Push(0))); 40 | 41 | obs.push(10); 42 | assert_eq!(sub.recv().await.unwrap(), Some(ListEvent::Push(10))); 43 | 44 | obs.push(20); 45 | assert_eq!(sub.recv().await.unwrap(), Some(ListEvent::Push(20))); 46 | 47 | obs.done(); 48 | assert_eq!(sub.recv().await.unwrap(), Some(ListEvent::Done)); 49 | assert!(sub.is_done()); 50 | } 51 | 52 | #[cfg_attr(not(feature = "js"), tokio::test)] 53 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 54 | async fn events_incremental() { 55 | let hs = vec![0u32, 1, 2]; 56 | let mut obs: ObservableList<_, remoc::codec::Default> = ObservableList::from(hs.clone()); 57 | 58 | let mut sub = obs.subscribe(); 59 | assert!(!sub.is_complete()); 60 | assert!(!sub.is_done()); 61 | 62 | let mut hs2 = Vec::new(); 63 | for _ in 0..3 { 64 | match sub.recv().await.unwrap() { 65 | Some(ListEvent::Push(k)) => { 66 | hs2.push(k); 67 | } 68 | other => panic!("unexpected event {other:?}"), 69 | } 70 | } 71 | assert_eq!(hs, hs2); 72 | 73 | assert_eq!(sub.recv().await.unwrap(), Some(ListEvent::InitialComplete)); 74 | assert!(sub.is_complete()); 75 | assert!(!sub.is_done()); 76 | 77 | obs.done(); 78 | assert_eq!(sub.recv().await.unwrap(), Some(ListEvent::Done)); 79 | assert!(sub.is_done()); 80 | } 81 | 82 | #[cfg_attr(not(feature = "js"), tokio::test)] 83 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 84 | async fn mirrored() { 85 | let mut pre = Vec::new(); 86 | for i in 1000..1500i32 { 87 | pre.push(i); 88 | } 89 | let len = pre.len(); 90 | 91 | let mut obs: ObservableList<_, remoc::codec::Default> = ObservableList::from(pre); 92 | assert_eq!(obs.len(), len); 93 | 94 | let sub = obs.subscribe(); 95 | let mut mirror = sub.mirror(1000); 96 | 97 | for i in 1..500 { 98 | obs.push(i); 99 | } 100 | 101 | loop { 102 | let mb = mirror.borrow_and_update().await.unwrap(); 103 | assert!(!mb.is_done()); 104 | 105 | println!("original: {obs:?}"); 106 | println!("mirrored: {mb:?}"); 107 | 108 | if *mb == *obs.borrow().await { 109 | assert!(mb.is_complete()); 110 | break; 111 | } 112 | 113 | drop(mb); 114 | sleep(Duration::from_millis(100)).await; 115 | } 116 | 117 | assert!(!obs.is_done()); 118 | println!("done"); 119 | obs.done(); 120 | assert!(obs.is_done()); 121 | mirror.changed().await; 122 | 123 | { 124 | let mb = mirror.borrow().await.unwrap(); 125 | assert!(mb.is_done()); 126 | } 127 | } 128 | 129 | #[cfg_attr(not(feature = "js"), tokio::test)] 130 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 131 | async fn mirrored_disconnect() { 132 | let mut obs: ObservableList<_, remoc::codec::Default> = ObservableList::new(); 133 | 134 | let sub = obs.subscribe(); 135 | let mut mirror = sub.mirror(1000); 136 | 137 | for i in 1..500 { 138 | obs.push(i); 139 | } 140 | 141 | println!("drop"); 142 | drop(obs); 143 | 144 | loop { 145 | mirror.changed().await; 146 | if let Err(RecvError::Closed) = mirror.borrow().await { 147 | break; 148 | } 149 | } 150 | } 151 | 152 | #[cfg_attr(not(feature = "js"), tokio::test)] 153 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 154 | async fn mirrored_disconnect_after_done() { 155 | let mut obs: ObservableList<_, remoc::codec::Default> = ObservableList::new(); 156 | 157 | let sub = obs.subscribe(); 158 | let mut mirror = sub.mirror(1000); 159 | 160 | for i in 1..500 { 161 | obs.push(i); 162 | } 163 | 164 | println!("done and drop"); 165 | obs.done(); 166 | 167 | loop { 168 | let mb = mirror.borrow_and_update().await.unwrap(); 169 | 170 | println!("mirrored: {mb:?}"); 171 | if *mb == *obs.borrow().await { 172 | break; 173 | } 174 | 175 | drop(mb); 176 | sleep(Duration::from_millis(100)).await; 177 | } 178 | 179 | let mb = mirror.borrow_and_update().await.unwrap(); 180 | assert!(mb.is_complete()); 181 | assert!(mb.is_done()); 182 | } 183 | -------------------------------------------------------------------------------- /remoc/tests/robs/mod.rs: -------------------------------------------------------------------------------- 1 | mod hash_map; 2 | mod hash_set; 3 | mod list; 4 | mod vec; 5 | -------------------------------------------------------------------------------- /remoc/tests/rtc/default.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | 6 | // Avoid imports here to test if proc macro works without imports. 7 | 8 | #[remoc::rtc::remote] 9 | pub trait DefaultTrait: Sync { 10 | async fn value(&self) -> Result; 11 | 12 | async fn default_method(&self) -> Result { 13 | let a = 1; 14 | let b = 2; 15 | Ok(a + b) 16 | } 17 | } 18 | 19 | pub struct CounterObj { 20 | value: u32, 21 | } 22 | 23 | impl CounterObj { 24 | pub fn new() -> Self { 25 | Self { value: 0 } 26 | } 27 | } 28 | 29 | #[remoc::rtc::async_trait] 30 | impl DefaultTrait for CounterObj { 31 | async fn value(&self) -> Result { 32 | Ok(self.value) 33 | } 34 | } 35 | 36 | #[cfg_attr(not(feature = "js"), tokio::test)] 37 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 38 | async fn simple() { 39 | use remoc::rtc::ServerRefMut; 40 | 41 | crate::init(); 42 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 43 | 44 | println!("Creating default server"); 45 | let mut counter_obj = CounterObj::new(); 46 | let (server, client) = DefaultTraitServerRefMut::new(&mut counter_obj, 1); 47 | 48 | println!("Sending default client"); 49 | a_tx.send(client).await.unwrap(); 50 | 51 | let client_task = async move { 52 | println!("Receiving default client"); 53 | let client = b_rx.recv().await.unwrap().unwrap(); 54 | 55 | println!("value: {}", client.value().await.unwrap()); 56 | assert_eq!(client.value().await.unwrap(), 0); 57 | 58 | println!("default_method: {}", client.default_method().await.unwrap()); 59 | assert_eq!(client.default_method().await.unwrap(), 3); 60 | }; 61 | 62 | let ((), res) = tokio::join!(client_task, server.serve()); 63 | res.unwrap(); 64 | } 65 | -------------------------------------------------------------------------------- /remoc/tests/rtc/errors.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | 6 | // Avoid imports here to test if proc macro works without imports. 7 | 8 | #[remoc::rtc::remote] 9 | pub trait DataGenerator { 10 | async fn data(&self, size: usize) -> Result, remoc::rtc::CallError>; 11 | } 12 | 13 | pub struct DataGeneratorObj {} 14 | 15 | impl DataGeneratorObj { 16 | pub fn new() -> Self { 17 | Self {} 18 | } 19 | } 20 | 21 | #[remoc::rtc::async_trait] 22 | impl DataGenerator for DataGeneratorObj { 23 | async fn data(&self, size: usize) -> Result, remoc::rtc::CallError> { 24 | Ok(vec![1; size]) 25 | } 26 | } 27 | 28 | #[cfg_attr(not(feature = "js"), tokio::test)] 29 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 30 | async fn max_item_size_exceeded() { 31 | use remoc::rtc::{Client, ServerRefMut}; 32 | 33 | crate::init(); 34 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 35 | 36 | let mut gen_obj = DataGeneratorObj::new(); 37 | let (server, client) = DataGeneratorServerRefMut::new(&mut gen_obj, 1); 38 | 39 | a_tx.send(client).await.unwrap(); 40 | 41 | let client_task = async move { 42 | println!("Receiving client"); 43 | let mut client = b_rx.recv().await.unwrap().unwrap(); 44 | 45 | client.set_max_reply_size(16777); 46 | let max_item_size = client.max_reply_size(); 47 | 48 | let elems = max_item_size / 10; 49 | println!("Getting {elems} elements, which is under limit"); 50 | let rxed = client.data(elems).await.unwrap(); 51 | println!("Received {} elements", rxed.len()); 52 | assert_eq!(rxed.len(), elems); 53 | 54 | let elems = max_item_size * 10; 55 | println!("Getting {elems} elements, which is over limit"); 56 | let rxed = client.data(elems).await; 57 | assert!(matches!(rxed, Err(remoc::rtc::CallError::Dropped))); 58 | }; 59 | 60 | let ((), res) = tokio::join!(client_task, server.serve()); 61 | assert!(matches!( 62 | res, 63 | Err(remoc::rtc::ServeError::ReplySend(remoc::rch::SendingErrorKind::Send( 64 | remoc::rch::base::SendErrorKind::MaxItemSizeExceeded 65 | ))) 66 | )); 67 | } 68 | -------------------------------------------------------------------------------- /remoc/tests/rtc/generics.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | 6 | // Avoid imports here to test if proc macro works without imports. 7 | 8 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 9 | pub enum IncreaseError { 10 | Overflow, 11 | Call(remoc::rtc::CallError), 12 | } 13 | 14 | impl From for IncreaseError { 15 | fn from(err: remoc::rtc::CallError) -> Self { 16 | Self::Call(err) 17 | } 18 | } 19 | 20 | pub trait CheckedAddable 21 | where 22 | Self: Sized, 23 | { 24 | fn checked_add(self, rhs: Self) -> Option; 25 | } 26 | 27 | impl CheckedAddable for u8 { 28 | fn checked_add(self, rhs: Self) -> Option { 29 | self.checked_add(rhs) 30 | } 31 | } 32 | 33 | #[remoc::rtc::remote] 34 | pub trait GenericCounter 35 | where 36 | T: remoc::RemoteSend + CheckedAddable + Clone + Default + Sync, 37 | { 38 | async fn value(&self) -> Result; 39 | async fn watch(&mut self) -> Result, remoc::rtc::CallError>; 40 | #[no_cancel] 41 | async fn increase(&mut self, #[serde(default)] by: T) -> Result<(), IncreaseError>; 42 | } 43 | 44 | pub struct GenericCounterObj { 45 | value: T, 46 | watchers: Vec>, 47 | } 48 | 49 | impl GenericCounterObj 50 | where 51 | T: Default, 52 | { 53 | pub fn new() -> Self { 54 | Self { value: T::default(), watchers: Vec::new() } 55 | } 56 | } 57 | 58 | #[remoc::rtc::async_trait] 59 | impl GenericCounter for GenericCounterObj 60 | where 61 | T: remoc::RemoteSend + CheckedAddable + Clone + Default + Sync, 62 | { 63 | async fn value(&self) -> Result { 64 | Ok(self.value.clone()) 65 | } 66 | 67 | async fn watch(&mut self) -> Result, remoc::rtc::CallError> { 68 | let (tx, rx) = remoc::rch::watch::channel(self.value.clone()); 69 | self.watchers.push(tx); 70 | Ok(rx) 71 | } 72 | 73 | async fn increase(&mut self, by: T) -> Result<(), IncreaseError> { 74 | match self.value.clone().checked_add(by) { 75 | Some(new_value) => self.value = new_value, 76 | None => return Err(IncreaseError::Overflow), 77 | } 78 | 79 | for watch in &self.watchers { 80 | let _ = watch.send(self.value.clone()); 81 | } 82 | 83 | Ok(()) 84 | } 85 | } 86 | 87 | #[cfg_attr(not(feature = "js"), tokio::test)] 88 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 89 | async fn simple() { 90 | use remoc::rtc::Server; 91 | 92 | crate::init(); 93 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::>().await; 94 | 95 | println!("Creating generic counter server"); 96 | let counter_obj = GenericCounterObj::new(); 97 | let (server, client) = GenericCounterServer::new(counter_obj, 1); 98 | 99 | println!("Sending generic counter client"); 100 | a_tx.send(client).await.unwrap(); 101 | 102 | let client_task = async move { 103 | println!("Receiving counter client"); 104 | let mut client = b_rx.recv().await.unwrap().unwrap(); 105 | 106 | println!("Spawning watch..."); 107 | let mut watch_rx = client.watch().await.unwrap(); 108 | remoc::exec::spawn(async move { 109 | while watch_rx.changed().await.is_ok() { 110 | println!("Watch value: {}", *watch_rx.borrow_and_update().unwrap()); 111 | } 112 | }); 113 | 114 | println!("value: {}", client.value().await.unwrap()); 115 | assert_eq!(client.value().await.unwrap(), 0); 116 | 117 | println!("add 20"); 118 | client.increase(20).await.unwrap(); 119 | println!("value: {}", client.value().await.unwrap()); 120 | assert_eq!(client.value().await.unwrap(), 20); 121 | 122 | println!("add 45"); 123 | client.increase(45).await.unwrap(); 124 | println!("value: {}", client.value().await.unwrap()); 125 | assert_eq!(client.value().await.unwrap(), 65); 126 | }; 127 | 128 | let ((), res) = tokio::join!(client_task, server.serve()); 129 | res.unwrap(); 130 | } 131 | -------------------------------------------------------------------------------- /remoc/tests/rtc/lifetime.rs: -------------------------------------------------------------------------------- 1 | // This should fail. 2 | 3 | #[remoc::rtc::remote] 4 | pub trait MethodLifetime 5 | { 6 | async fn hello<'a>(&self) -> Result, remoc::rtc::CallError>; 7 | } 8 | 9 | #[remoc::rtc::remote] 10 | pub trait TraitLifetime<'a> 11 | { 12 | async fn hello(&self) -> Result, remoc::rtc::CallError>; 13 | } 14 | -------------------------------------------------------------------------------- /remoc/tests/rtc/mod.rs: -------------------------------------------------------------------------------- 1 | mod default; 2 | mod errors; 3 | mod generics; 4 | mod readonly; 5 | mod simple; 6 | mod simple_clone; 7 | mod simple_req; 8 | mod value; 9 | 10 | // Must result in compile error: 11 | // mod lifetime; 12 | -------------------------------------------------------------------------------- /remoc/tests/rtc/readonly.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | 6 | // Avoid imports here to test if proc macro works without imports. 7 | 8 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 9 | pub enum IncreaseError { 10 | Overflow, 11 | Call(remoc::rtc::CallError), 12 | } 13 | 14 | impl From for IncreaseError { 15 | fn from(err: remoc::rtc::CallError) -> Self { 16 | Self::Call(err) 17 | } 18 | } 19 | 20 | #[remoc::rtc::remote] 21 | pub trait ReadValue { 22 | async fn value(&self) -> Result; 23 | } 24 | 25 | pub struct ReadValueObj { 26 | value: u32, 27 | } 28 | 29 | impl ReadValueObj { 30 | pub fn new(value: u32) -> Self { 31 | Self { value } 32 | } 33 | } 34 | 35 | #[remoc::rtc::async_trait] 36 | impl ReadValue for ReadValueObj { 37 | async fn value(&self) -> Result { 38 | Ok(self.value) 39 | } 40 | } 41 | 42 | #[cfg_attr(not(feature = "js"), tokio::test)] 43 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 44 | async fn simple() { 45 | use remoc::rtc::ServerRef; 46 | 47 | crate::init(); 48 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 49 | 50 | println!("Creating server"); 51 | let obj = ReadValueObj::new(123); 52 | let (server, client) = ReadValueServerRef::new(&obj, 1); 53 | 54 | println!("Sending client"); 55 | a_tx.send(client).await.unwrap(); 56 | 57 | let client_task = async move { 58 | println!("Receiving client"); 59 | let client = b_rx.recv().await.unwrap().unwrap(); 60 | 61 | println!("value: {}", client.value().await.unwrap()); 62 | assert_eq!(client.value().await.unwrap(), 123); 63 | }; 64 | 65 | let ((), res) = tokio::join!(client_task, server.serve()); 66 | res.unwrap(); 67 | } 68 | 69 | #[cfg_attr(not(feature = "js"), tokio::test)] 70 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 71 | async fn closed() { 72 | use remoc::rtc::{Client, ServerRef}; 73 | 74 | crate::init(); 75 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 76 | 77 | println!("Creating server"); 78 | let obj = ReadValueObj::new(123); 79 | let (server, client) = ReadValueServerRef::new(&obj, 16); 80 | 81 | println!("Sending client"); 82 | a_tx.send(client).await.unwrap(); 83 | 84 | let (drop_tx, drop_rx) = tokio::sync::oneshot::channel(); 85 | 86 | let client_task = async move { 87 | println!("Receiving client"); 88 | let client = b_rx.recv().await.unwrap().unwrap(); 89 | 90 | println!("value: {}", client.value().await.unwrap()); 91 | assert_eq!(client.value().await.unwrap(), 123); 92 | 93 | assert!(!client.is_closed()); 94 | println!("Client capacity: {}", client.capacity()); 95 | 96 | remoc::exec::spawn(async move { 97 | remoc::exec::time::sleep(std::time::Duration::from_millis(500)).await; 98 | drop_tx.send(()).unwrap(); 99 | }); 100 | 101 | println!("Waiting for client close"); 102 | client.closed().await; 103 | println!("Client closed"); 104 | 105 | assert!(client.is_closed()); 106 | }; 107 | 108 | let server_task = async move { 109 | tokio::select! { 110 | res = server.serve() => res.unwrap(), 111 | res = drop_rx => res.unwrap(), 112 | } 113 | println!("Dropping server"); 114 | }; 115 | 116 | tokio::join!(client_task, server_task); 117 | } 118 | -------------------------------------------------------------------------------- /remoc/tests/rtc/simple.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | 6 | // Avoid imports here to test if proc macro works without imports. 7 | 8 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 9 | pub enum IncreaseError { 10 | Overflow, 11 | Call(remoc::rtc::CallError), 12 | } 13 | 14 | impl From for IncreaseError { 15 | fn from(err: remoc::rtc::CallError) -> Self { 16 | Self::Call(err) 17 | } 18 | } 19 | 20 | #[remoc::rtc::remote] 21 | pub trait Counter { 22 | async fn value(&self) -> Result; 23 | async fn watch(&mut self) -> Result, remoc::rtc::CallError>; 24 | #[no_cancel] 25 | async fn increase(&mut self, #[serde(default)] by: u32) -> Result<(), IncreaseError>; 26 | } 27 | 28 | pub struct CounterObj { 29 | value: u32, 30 | watchers: Vec>, 31 | } 32 | 33 | impl CounterObj { 34 | pub fn new() -> Self { 35 | Self { value: 0, watchers: Vec::new() } 36 | } 37 | } 38 | 39 | #[remoc::rtc::async_trait] 40 | impl Counter for CounterObj { 41 | async fn value(&self) -> Result { 42 | Ok(self.value) 43 | } 44 | 45 | async fn watch(&mut self) -> Result, remoc::rtc::CallError> { 46 | let (tx, rx) = remoc::rch::watch::channel(self.value); 47 | self.watchers.push(tx); 48 | Ok(rx) 49 | } 50 | 51 | async fn increase(&mut self, by: u32) -> Result<(), IncreaseError> { 52 | match self.value.checked_add(by) { 53 | Some(new_value) => self.value = new_value, 54 | None => return Err(IncreaseError::Overflow), 55 | } 56 | 57 | for watch in &self.watchers { 58 | let _ = watch.send(self.value); 59 | } 60 | 61 | Ok(()) 62 | } 63 | } 64 | 65 | #[cfg_attr(not(feature = "js"), tokio::test)] 66 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 67 | async fn simple() { 68 | use remoc::rtc::ServerRefMut; 69 | 70 | crate::init(); 71 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 72 | 73 | println!("Creating counter server"); 74 | let mut counter_obj = CounterObj::new(); 75 | let (server, client) = CounterServerRefMut::new(&mut counter_obj, 1); 76 | 77 | println!("Sending counter client"); 78 | a_tx.send(client).await.unwrap(); 79 | 80 | let client_task = async move { 81 | println!("Receiving counter client"); 82 | let mut client = b_rx.recv().await.unwrap().unwrap(); 83 | 84 | println!("Spawning watch..."); 85 | let mut watch_rx = client.watch().await.unwrap(); 86 | remoc::exec::spawn(async move { 87 | while watch_rx.changed().await.is_ok() { 88 | println!("Watch value: {}", *watch_rx.borrow_and_update().unwrap()); 89 | } 90 | }); 91 | 92 | println!("value: {}", client.value().await.unwrap()); 93 | assert_eq!(client.value().await.unwrap(), 0); 94 | 95 | println!("add 20"); 96 | client.increase(20).await.unwrap(); 97 | println!("value: {}", client.value().await.unwrap()); 98 | assert_eq!(client.value().await.unwrap(), 20); 99 | 100 | println!("add 45"); 101 | client.increase(45).await.unwrap(); 102 | println!("value: {}", client.value().await.unwrap()); 103 | assert_eq!(client.value().await.unwrap(), 65); 104 | }; 105 | 106 | let ((), res) = tokio::join!(client_task, server.serve()); 107 | res.unwrap(); 108 | 109 | println!("Counter obj value: {}", counter_obj.value); 110 | assert_eq!(counter_obj.value, 65); 111 | } 112 | 113 | #[cfg_attr(not(feature = "js"), tokio::test)] 114 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 115 | async fn simple_spawn() { 116 | use remoc::rtc::ServerSharedMut; 117 | 118 | crate::init(); 119 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 120 | 121 | println!("Spawning counter server"); 122 | let counter_obj = std::sync::Arc::new(tokio::sync::RwLock::new(CounterObj::new())); 123 | let (server, client) = CounterServerSharedMut::new(counter_obj.clone(), 16); 124 | let server_task = remoc::exec::spawn(async move { 125 | server.serve(true).await.unwrap(); 126 | println!("Server done"); 127 | 128 | let value = counter_obj.read().await.value; 129 | println!("Counter obj value: {}", value); 130 | assert_eq!(value, 65); 131 | }); 132 | 133 | println!("Sending counter client"); 134 | a_tx.send(client).await.unwrap(); 135 | 136 | println!("Receiving counter client"); 137 | let mut client = b_rx.recv().await.unwrap().unwrap(); 138 | 139 | println!("Spawning watch..."); 140 | let mut watch_rx = client.watch().await.unwrap(); 141 | remoc::exec::spawn(async move { 142 | while watch_rx.changed().await.is_ok() { 143 | println!("Watch value: {}", *watch_rx.borrow_and_update().unwrap()); 144 | } 145 | }); 146 | 147 | println!("value: {}", client.value().await.unwrap()); 148 | assert_eq!(client.value().await.unwrap(), 0); 149 | 150 | println!("add 20"); 151 | client.increase(20).await.unwrap(); 152 | println!("value: {}", client.value().await.unwrap()); 153 | assert_eq!(client.value().await.unwrap(), 20); 154 | 155 | println!("add 45"); 156 | client.increase(45).await.unwrap(); 157 | println!("value: {}", client.value().await.unwrap()); 158 | assert_eq!(client.value().await.unwrap(), 65); 159 | 160 | drop(client); 161 | println!("waiting for server to terminate"); 162 | server_task.await.unwrap(); 163 | } 164 | -------------------------------------------------------------------------------- /remoc/tests/rtc/simple_clone.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | 6 | // Avoid imports here to test if proc macro works without imports. 7 | 8 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 9 | pub enum IncreaseError { 10 | Overflow, 11 | Call(remoc::rtc::CallError), 12 | } 13 | 14 | impl From for IncreaseError { 15 | fn from(err: remoc::rtc::CallError) -> Self { 16 | Self::Call(err) 17 | } 18 | } 19 | 20 | #[remoc::rtc::remote(clone)] 21 | pub trait Counter { 22 | async fn value(&self) -> Result; 23 | async fn watch(&mut self) -> Result, remoc::rtc::CallError>; 24 | #[no_cancel] 25 | async fn increase(&mut self, #[serde(default)] by: u32) -> Result<(), IncreaseError>; 26 | } 27 | 28 | pub struct CounterObj { 29 | value: u32, 30 | watchers: Vec>, 31 | } 32 | 33 | impl CounterObj { 34 | pub fn new() -> Self { 35 | Self { value: 0, watchers: Vec::new() } 36 | } 37 | } 38 | 39 | #[remoc::rtc::async_trait] 40 | impl Counter for CounterObj { 41 | async fn value(&self) -> Result { 42 | Ok(self.value) 43 | } 44 | 45 | async fn watch(&mut self) -> Result, remoc::rtc::CallError> { 46 | let (tx, rx) = remoc::rch::watch::channel(self.value); 47 | self.watchers.push(tx); 48 | Ok(rx) 49 | } 50 | 51 | async fn increase(&mut self, by: u32) -> Result<(), IncreaseError> { 52 | match self.value.checked_add(by) { 53 | Some(new_value) => self.value = new_value, 54 | None => return Err(IncreaseError::Overflow), 55 | } 56 | 57 | for watch in &self.watchers { 58 | let _ = watch.send(self.value); 59 | } 60 | 61 | Ok(()) 62 | } 63 | } 64 | 65 | #[cfg_attr(not(feature = "js"), tokio::test)] 66 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 67 | async fn simple_clone() { 68 | use remoc::rtc::ServerRefMut; 69 | 70 | crate::init(); 71 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 72 | 73 | println!("Creating counter server"); 74 | let mut counter_obj = CounterObj::new(); 75 | let (server, client) = CounterServerRefMut::new(&mut counter_obj, 1); 76 | 77 | println!("Sending counter client"); 78 | a_tx.send(client).await.unwrap(); 79 | 80 | let client_task = async move { 81 | println!("Receiving counter client"); 82 | let mut client = b_rx.recv().await.unwrap().unwrap(); 83 | 84 | println!("Spawning watch..."); 85 | let mut watch_rx = client.watch().await.unwrap(); 86 | remoc::exec::spawn(async move { 87 | while watch_rx.changed().await.is_ok() { 88 | println!("Watch value: {}", *watch_rx.borrow_and_update().unwrap()); 89 | } 90 | }); 91 | 92 | println!("value: {}", client.value().await.unwrap()); 93 | assert_eq!(client.value().await.unwrap(), 0); 94 | 95 | let mut client = client.clone(); 96 | 97 | println!("add 20"); 98 | client.increase(20).await.unwrap(); 99 | println!("value: {}", client.value().await.unwrap()); 100 | assert_eq!(client.value().await.unwrap(), 20); 101 | 102 | let mut client = client.clone(); 103 | 104 | println!("add 45"); 105 | client.increase(45).await.unwrap(); 106 | println!("value: {}", client.value().await.unwrap()); 107 | assert_eq!(client.value().await.unwrap(), 65); 108 | }; 109 | 110 | let ((), res) = tokio::join!(client_task, server.serve()); 111 | res.unwrap(); 112 | 113 | println!("Counter obj value: {}", counter_obj.value); 114 | assert_eq!(counter_obj.value, 65); 115 | } 116 | -------------------------------------------------------------------------------- /remoc/tests/rtc/simple_req.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | 6 | // Avoid imports here to test if proc macro works without imports. 7 | 8 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 9 | pub enum IncreaseError { 10 | Overflow, 11 | Call(remoc::rtc::CallError), 12 | } 13 | 14 | impl From for IncreaseError { 15 | fn from(err: remoc::rtc::CallError) -> Self { 16 | Self::Call(err) 17 | } 18 | } 19 | 20 | #[remoc::rtc::remote] 21 | pub trait Counter { 22 | async fn value(&self) -> Result; 23 | async fn watch(&mut self) -> Result, remoc::rtc::CallError>; 24 | #[no_cancel] 25 | async fn increase(&mut self, #[serde(default)] by: u32) -> Result<(), IncreaseError>; 26 | } 27 | 28 | pub struct CounterObj { 29 | value: u32, 30 | watchers: Vec>, 31 | } 32 | 33 | impl CounterObj { 34 | pub fn new() -> Self { 35 | Self { value: 0, watchers: Vec::new() } 36 | } 37 | 38 | pub fn handle_req(&mut self, req: CounterReq) 39 | where 40 | Codec: remoc::codec::Codec, 41 | { 42 | match req { 43 | CounterReq::Value { __reply_tx } => { 44 | let _ = __reply_tx.send(Ok(self.value)); 45 | } 46 | CounterReq::Watch { __reply_tx } => { 47 | let (tx, rx) = remoc::rch::watch::channel(self.value); 48 | self.watchers.push(tx); 49 | let _ = __reply_tx.send(Ok(rx)); 50 | } 51 | CounterReq::Increase { __reply_tx, by } => { 52 | match self.value.checked_add(by) { 53 | Some(new_value) => self.value = new_value, 54 | None => { 55 | let _ = __reply_tx.send(Err(IncreaseError::Overflow)); 56 | return; 57 | } 58 | } 59 | 60 | for watch in &self.watchers { 61 | let _ = watch.send(self.value); 62 | } 63 | 64 | let _ = __reply_tx.send(Ok(())); 65 | } 66 | _ => (), 67 | } 68 | } 69 | } 70 | 71 | #[remoc::rtc::async_trait] 72 | impl Counter for CounterObj { 73 | async fn value(&self) -> Result { 74 | Ok(self.value) 75 | } 76 | 77 | async fn watch(&mut self) -> Result, remoc::rtc::CallError> { 78 | let (tx, rx) = remoc::rch::watch::channel(self.value); 79 | self.watchers.push(tx); 80 | Ok(rx) 81 | } 82 | 83 | async fn increase(&mut self, by: u32) -> Result<(), IncreaseError> { 84 | match self.value.checked_add(by) { 85 | Some(new_value) => self.value = new_value, 86 | None => return Err(IncreaseError::Overflow), 87 | } 88 | 89 | for watch in &self.watchers { 90 | let _ = watch.send(self.value); 91 | } 92 | 93 | Ok(()) 94 | } 95 | } 96 | 97 | #[cfg_attr(not(feature = "js"), tokio::test)] 98 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 99 | async fn simple_req() { 100 | use remoc::rtc::ReqReceiver; 101 | 102 | crate::init(); 103 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 104 | 105 | println!("Creating counter server"); 106 | let mut counter_obj = CounterObj::new(); 107 | let (mut req_rx, client) = CounterReqReceiver::new(1); 108 | 109 | println!("Sending counter request receiver"); 110 | a_tx.send(client).await.unwrap(); 111 | 112 | let client_task = remoc::exec::spawn(async move { 113 | println!("Receiving counter client"); 114 | let mut client = b_rx.recv().await.unwrap().unwrap(); 115 | 116 | println!("Spawning watch..."); 117 | let mut watch_rx = client.watch().await.unwrap(); 118 | remoc::exec::spawn(async move { 119 | while watch_rx.changed().await.is_ok() { 120 | println!("Watch value: {}", *watch_rx.borrow_and_update().unwrap()); 121 | } 122 | }); 123 | 124 | println!("value: {}", client.value().await.unwrap()); 125 | assert_eq!(client.value().await.unwrap(), 0); 126 | 127 | println!("add 20"); 128 | client.increase(20).await.unwrap(); 129 | println!("value: {}", client.value().await.unwrap()); 130 | assert_eq!(client.value().await.unwrap(), 20); 131 | 132 | println!("add 45"); 133 | client.increase(45).await.unwrap(); 134 | println!("value: {}", client.value().await.unwrap()); 135 | assert_eq!(client.value().await.unwrap(), 65); 136 | }); 137 | 138 | while let Some(req) = req_rx.recv().await.unwrap() { 139 | counter_obj.handle_req(req); 140 | } 141 | 142 | client_task.await.unwrap(); 143 | 144 | println!("Counter obj value: {}", counter_obj.value); 145 | assert_eq!(counter_obj.value, 65); 146 | } 147 | -------------------------------------------------------------------------------- /remoc/tests/rtc/value.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "js")] 2 | use wasm_bindgen_test::wasm_bindgen_test; 3 | 4 | use crate::loop_channel; 5 | 6 | // Avoid imports here to test if proc macro works without imports. 7 | 8 | #[derive(Debug, serde::Serialize, serde::Deserialize)] 9 | pub enum IncreaseError { 10 | Overflow, 11 | Call(remoc::rtc::CallError), 12 | } 13 | 14 | impl From for IncreaseError { 15 | fn from(err: remoc::rtc::CallError) -> Self { 16 | Self::Call(err) 17 | } 18 | } 19 | 20 | #[remoc::rtc::remote] 21 | pub trait Counter { 22 | async fn value(self) -> Result; 23 | async fn value_plus(self, add: u32) -> Result; 24 | async fn value_ref(&self) -> Result; 25 | #[no_cancel] 26 | async fn increase(&mut self, #[serde(default)] by: u32) -> Result<(), IncreaseError>; 27 | } 28 | 29 | pub struct CounterObj { 30 | value: u32, 31 | watchers: Vec>, 32 | } 33 | 34 | impl CounterObj { 35 | pub fn new() -> Self { 36 | Self { value: 0, watchers: Vec::new() } 37 | } 38 | } 39 | 40 | #[remoc::rtc::async_trait] 41 | impl Counter for CounterObj { 42 | async fn value(self) -> Result { 43 | Ok(self.value) 44 | } 45 | 46 | async fn value_plus(self, add: u32) -> Result { 47 | Ok(self.value + add) 48 | } 49 | 50 | async fn value_ref(&self) -> Result { 51 | Ok(self.value) 52 | } 53 | 54 | async fn increase(&mut self, by: u32) -> Result<(), IncreaseError> { 55 | match self.value.checked_add(by) { 56 | Some(new_value) => self.value = new_value, 57 | None => return Err(IncreaseError::Overflow), 58 | } 59 | 60 | for watch in &self.watchers { 61 | let _ = watch.send(self.value); 62 | } 63 | 64 | Ok(()) 65 | } 66 | } 67 | 68 | #[cfg_attr(not(feature = "js"), tokio::test)] 69 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 70 | async fn simple() { 71 | use remoc::rtc::Server; 72 | 73 | crate::init(); 74 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 75 | 76 | println!("Creating counter server"); 77 | let counter_obj = CounterObj::new(); 78 | let (server, client) = CounterServer::new(counter_obj, 1); 79 | 80 | println!("Sending counter client"); 81 | a_tx.send(client).await.unwrap(); 82 | 83 | let client_task = async move { 84 | println!("Receiving counter client"); 85 | let mut client = b_rx.recv().await.unwrap().unwrap(); 86 | 87 | println!("add 20"); 88 | client.increase(20).await.unwrap(); 89 | 90 | println!("add 45"); 91 | client.increase(45).await.unwrap(); 92 | 93 | let value = client.value().await.unwrap(); 94 | println!("value: {}", value); 95 | assert_eq!(value, 65); 96 | }; 97 | 98 | let (_, counter_obj) = tokio::join!(client_task, server.serve()); 99 | assert!(counter_obj.unwrap().is_none()); 100 | } 101 | 102 | #[cfg_attr(not(feature = "js"), tokio::test)] 103 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 104 | async fn simple_plus() { 105 | use remoc::rtc::Server; 106 | 107 | crate::init(); 108 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 109 | 110 | println!("Creating counter server"); 111 | let counter_obj = CounterObj::new(); 112 | let (server, client) = CounterServer::new(counter_obj, 1); 113 | 114 | println!("Sending counter client"); 115 | a_tx.send(client).await.unwrap(); 116 | 117 | let client_task = async move { 118 | println!("Receiving counter client"); 119 | let mut client = b_rx.recv().await.unwrap().unwrap(); 120 | 121 | println!("add 20"); 122 | client.increase(20).await.unwrap(); 123 | 124 | println!("add 45"); 125 | client.increase(45).await.unwrap(); 126 | 127 | let value = client.value_plus(10).await.unwrap(); 128 | println!("value: {}", value); 129 | assert_eq!(value, 75); 130 | }; 131 | 132 | let (_, counter_obj) = tokio::join!(client_task, server.serve()); 133 | assert!(counter_obj.unwrap().is_none()); 134 | } 135 | 136 | #[cfg_attr(not(feature = "js"), tokio::test)] 137 | #[cfg_attr(feature = "js", wasm_bindgen_test)] 138 | async fn simple_spawn() { 139 | use remoc::rtc::Server; 140 | 141 | crate::init(); 142 | let ((mut a_tx, _), (_, mut b_rx)) = loop_channel::().await; 143 | 144 | println!("Spawning counter server"); 145 | let counter_obj = CounterObj::new(); 146 | let (server, client) = CounterServer::new(counter_obj, 1); 147 | let server_task = remoc::exec::spawn(async move { 148 | let counter_obj = server.serve().await.unwrap().unwrap(); 149 | println!("Server done"); 150 | 151 | let value = counter_obj.value; 152 | println!("Counter obj value: {}", value); 153 | assert_eq!(value, 65); 154 | }); 155 | 156 | println!("Sending counter client"); 157 | a_tx.send(client).await.unwrap(); 158 | 159 | println!("Receiving counter client"); 160 | let mut client = b_rx.recv().await.unwrap().unwrap(); 161 | 162 | println!("value: {}", client.value_ref().await.unwrap()); 163 | assert_eq!(client.value_ref().await.unwrap(), 0); 164 | 165 | println!("add 20"); 166 | client.increase(20).await.unwrap(); 167 | println!("value: {}", client.value_ref().await.unwrap()); 168 | assert_eq!(client.value_ref().await.unwrap(), 20); 169 | 170 | println!("add 45"); 171 | client.increase(45).await.unwrap(); 172 | println!("value: {}", client.value_ref().await.unwrap()); 173 | assert_eq!(client.value_ref().await.unwrap(), 65); 174 | 175 | drop(client); 176 | println!("waiting for server to terminate"); 177 | server_task.await.unwrap(); 178 | } 179 | -------------------------------------------------------------------------------- /remoc/tests/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "rch")] 2 | use futures::StreamExt; 3 | 4 | #[allow(unused_imports)] 5 | use std::{net::Ipv4Addr, sync::Once}; 6 | 7 | #[cfg(feature = "rch")] 8 | #[cfg(not(target_family = "wasm"))] 9 | use tokio::net::{TcpListener, TcpStream}; 10 | 11 | #[cfg(feature = "rch")] 12 | use remoc::{rch::base, RemoteSend}; 13 | 14 | use remoc::exec; 15 | 16 | mod chmux; 17 | 18 | #[cfg(feature = "serde")] 19 | mod codec; 20 | 21 | #[cfg(feature = "rch")] 22 | mod rch; 23 | 24 | #[cfg(feature = "rfn")] 25 | mod rfn; 26 | 27 | #[cfg(feature = "robj")] 28 | mod robj; 29 | 30 | #[cfg(feature = "robs")] 31 | mod robs; 32 | 33 | #[cfg(feature = "rtc")] 34 | mod rtc; 35 | 36 | static INIT: Once = Once::new(); 37 | 38 | pub fn init() { 39 | INIT.call_once(|| { 40 | use tracing_subscriber::{filter::EnvFilter, fmt::format::FmtSpan}; 41 | tracing_subscriber::fmt::fmt() 42 | .with_env_filter(EnvFilter::from_default_env()) 43 | .with_span_events(FmtSpan::NEW) 44 | .init(); 45 | }); 46 | } 47 | 48 | #[macro_export] 49 | macro_rules! loop_transport { 50 | ($queue_length:expr, $a_tx:ident, $a_rx:ident, $b_tx:ident, $b_rx:ident) => { 51 | let ($a_tx, $b_rx) = futures::channel::mpsc::channel::($queue_length); 52 | let ($b_tx, $a_rx) = futures::channel::mpsc::channel::($queue_length); 53 | 54 | let $a_rx = $a_rx.map(Ok::<_, std::io::Error>); 55 | let $b_rx = $b_rx.map(Ok::<_, std::io::Error>); 56 | }; 57 | } 58 | 59 | #[cfg(feature = "rch")] 60 | pub async fn loop_channel() -> ((base::Sender, base::Receiver), (base::Sender, base::Receiver)) 61 | where 62 | T: RemoteSend, 63 | { 64 | let cfg = remoc::chmux::Cfg::default(); 65 | loop_channel_with_cfg(cfg).await 66 | } 67 | 68 | #[cfg(feature = "rch")] 69 | pub async fn droppable_loop_channel( 70 | ) -> ((base::Sender, base::Receiver), (base::Sender, base::Receiver), tokio::sync::mpsc::Receiver<()>) 71 | where 72 | T: RemoteSend, 73 | { 74 | let cfg = remoc::chmux::Cfg::default(); 75 | droppable_loop_channel_with_cfg(cfg).await 76 | } 77 | 78 | #[cfg(feature = "rch")] 79 | pub async fn loop_channel_with_cfg( 80 | cfg: remoc::chmux::Cfg, 81 | ) -> ((base::Sender, base::Receiver), (base::Sender, base::Receiver)) 82 | where 83 | T: RemoteSend, 84 | { 85 | let (a, b, mut drop_rx) = droppable_loop_channel_with_cfg(cfg).await; 86 | exec::spawn(async move { 87 | let _ = drop_rx.recv().await; 88 | }); 89 | (a, b) 90 | } 91 | 92 | #[cfg(feature = "rch")] 93 | pub async fn droppable_loop_channel_with_cfg( 94 | cfg: remoc::chmux::Cfg, 95 | ) -> ((base::Sender, base::Receiver), (base::Sender, base::Receiver), tokio::sync::mpsc::Receiver<()>) 96 | where 97 | T: RemoteSend, 98 | { 99 | let (drop_tx, drop_rx) = tokio::sync::mpsc::channel(1); 100 | loop_transport!(0, transport_a_tx, transport_a_rx, transport_b_tx, transport_b_rx); 101 | 102 | let a_cfg = cfg.clone(); 103 | let a_drop_tx = drop_tx.clone(); 104 | let a = async move { 105 | let (conn, tx, rx) = remoc::Connect::framed(a_cfg, transport_a_tx, transport_a_rx).await.unwrap(); 106 | exec::spawn(async move { 107 | tokio::select! { 108 | _ = conn => (), 109 | _ = a_drop_tx.closed() => (), 110 | } 111 | }); 112 | (tx, rx) 113 | }; 114 | 115 | let b_cfg = cfg.clone(); 116 | let b = async move { 117 | let (conn, tx, rx) = remoc::Connect::framed(b_cfg, transport_b_tx, transport_b_rx).await.unwrap(); 118 | exec::spawn(async move { 119 | tokio::select! { 120 | _ = conn => (), 121 | _ = drop_tx.closed() => (), 122 | } 123 | }); 124 | (tx, rx) 125 | }; 126 | 127 | let (a, b) = tokio::join!(a, b); 128 | (a, b, drop_rx) 129 | } 130 | 131 | #[cfg(feature = "rch")] 132 | #[cfg(not(target_family = "wasm"))] 133 | pub async fn tcp_loop_channel( 134 | tcp_port: u16, 135 | ) -> ((base::Sender, base::Receiver), (base::Sender, base::Receiver)) 136 | where 137 | T: RemoteSend, 138 | { 139 | let server = async move { 140 | let listener = TcpListener::bind((Ipv4Addr::new(127, 0, 0, 1), tcp_port)).await.unwrap(); 141 | let (socket, _) = listener.accept().await.unwrap(); 142 | let (socket_rx, socket_tx) = socket.into_split(); 143 | let (conn, tx, rx) = 144 | remoc::Connect::io_buffered(Default::default(), socket_rx, socket_tx, 100_000).await.unwrap(); 145 | exec::spawn(conn); 146 | (tx, rx) 147 | }; 148 | 149 | let client = async move { 150 | let socket = TcpStream::connect((Ipv4Addr::new(127, 0, 0, 1), tcp_port)).await.unwrap(); 151 | let (socket_rx, socket_tx) = socket.into_split(); 152 | let (conn, tx, rx) = 153 | remoc::Connect::io_buffered(Default::default(), socket_rx, socket_tx, 8721).await.unwrap(); 154 | exec::spawn(conn); 155 | (tx, rx) 156 | }; 157 | 158 | tokio::join!(server, client) 159 | } 160 | -------------------------------------------------------------------------------- /remoc_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "remoc_macro" 3 | description = "Procedural macros for Remoc" 4 | 5 | version = { workspace = true } 6 | repository = { workspace = true } 7 | edition = { workspace = true } 8 | rust-version = { workspace = true } 9 | license = { workspace = true } 10 | authors = { workspace = true } 11 | 12 | 13 | [dependencies] 14 | proc-macro2 = "1" 15 | syn = { version = "2", features = ["extra-traits", "full", "visit"] } 16 | quote = "1" 17 | 18 | 19 | [lib] 20 | proc-macro = true 21 | -------------------------------------------------------------------------------- /remoc_macro/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /remoc_macro/NOTICE: -------------------------------------------------------------------------------- 1 | ../NOTICE -------------------------------------------------------------------------------- /remoc_macro/README.md: -------------------------------------------------------------------------------- 1 | # Remoc macros 2 | 3 | This crate contains procedural macros for use with [Remoc]. 4 | 5 | It is pulled in automatically when needed and should not be used directly. 6 | 7 | [Remoc]: https://crates.io/crates/remoc 8 | 9 | ## License 10 | 11 | Remoc is licensed under the [Apache 2.0 license]. 12 | 13 | [Apache 2.0 license]: https://github.com/ENQT-GmbH/remoc/blob/master/LICENSE 14 | 15 | ### Contribution 16 | 17 | Unless you explicitly state otherwise, any contribution intentionally submitted 18 | for inclusion in Remoc by you, shall be licensed as Apache 2.0, without any 19 | additional terms or conditions. 20 | -------------------------------------------------------------------------------- /remoc_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | //! Procedural macros for Remoc. 4 | 5 | use quote::quote; 6 | use syn::{meta, parse_macro_input}; 7 | 8 | mod method; 9 | mod trait_def; 10 | mod util; 11 | 12 | use crate::trait_def::TraitDef; 13 | 14 | #[proc_macro_attribute] 15 | pub fn remote(args: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream { 16 | let mut trait_def = parse_macro_input!(input as TraitDef); 17 | let meta_parser = meta::parser(|meta| trait_def.parse_meta(meta)); 18 | parse_macro_input!(args with meta_parser); 19 | 20 | let vanilla_trait = trait_def.vanilla_trait(); 21 | let request_enums = trait_def.request_enums(); 22 | let servers = trait_def.servers(); 23 | let client = trait_def.client(); 24 | 25 | #[allow(clippy::let_and_return)] 26 | let output = proc_macro::TokenStream::from(quote! { 27 | #vanilla_trait 28 | #request_enums 29 | #servers 30 | #client 31 | }); 32 | 33 | // println!("{}", &output); 34 | 35 | output 36 | } 37 | -------------------------------------------------------------------------------- /remoc_macro/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions. 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::{format_ident, quote, ToTokens}; 5 | use syn::{Attribute, Ident}; 6 | 7 | /// Converts the identifier to pascal case. 8 | pub fn to_pascal_case(ident: &Ident) -> Ident { 9 | let s = ident.to_string(); 10 | let mut capital = true; 11 | let mut out = String::new(); 12 | for c in s.chars() { 13 | if c == '_' { 14 | capital = true; 15 | } else if capital { 16 | out.push(c.to_uppercase().next().unwrap()); 17 | capital = false; 18 | } else { 19 | out.push(c); 20 | } 21 | } 22 | format_ident!("{}", out) 23 | } 24 | 25 | /// TokenStream for list of attributes. 26 | pub fn attribute_tokens(attrs: &[Attribute]) -> TokenStream { 27 | let mut tokens = quote! {}; 28 | for attr in attrs { 29 | attr.to_tokens(&mut tokens); 30 | } 31 | tokens 32 | } 33 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 114 2 | use_small_heuristics = "Max" 3 | 4 | fn_params_layout = "Compressed" 5 | 6 | #wrap_comments = true 7 | #comment_width = 100 8 | imports_granularity = "Crate" 9 | --------------------------------------------------------------------------------