├── .github ├── dependabot.yml └── workflows │ ├── ci_check.yml │ ├── ci_test.yml │ └── ci_test_illumos.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── compio-buf ├── Cargo.toml └── src │ ├── buf_result.rs │ ├── io_buf.rs │ ├── io_slice.rs │ ├── io_vec_buf.rs │ ├── iter.rs │ ├── lib.rs │ ├── slice.rs │ └── uninit.rs ├── compio-dispatcher ├── Cargo.toml ├── src │ └── lib.rs └── tests │ └── listener.rs ├── compio-driver ├── Cargo.toml ├── build.rs ├── src │ ├── asyncify.rs │ ├── buffer_pool │ │ ├── fallback.rs │ │ ├── fusion.rs │ │ ├── iour.rs │ │ └── mod.rs │ ├── driver_type.rs │ ├── fd.rs │ ├── fusion │ │ ├── mod.rs │ │ └── op.rs │ ├── iocp │ │ ├── cp │ │ │ ├── global.rs │ │ │ ├── mod.rs │ │ │ └── multi.rs │ │ ├── mod.rs │ │ ├── op.rs │ │ └── wait │ │ │ ├── mod.rs │ │ │ ├── packet.rs │ │ │ └── thread_pool.rs │ ├── iour │ │ ├── mod.rs │ │ └── op.rs │ ├── key.rs │ ├── lib.rs │ ├── op.rs │ ├── poll │ │ ├── mod.rs │ │ └── op.rs │ └── unix │ │ ├── mod.rs │ │ └── op.rs └── tests │ └── file.rs ├── compio-fs ├── Cargo.toml ├── build.rs ├── src │ ├── async_fd.rs │ ├── file.rs │ ├── lib.rs │ ├── metadata │ │ ├── mod.rs │ │ ├── unix.rs │ │ └── windows.rs │ ├── named_pipe.rs │ ├── open_options │ │ ├── mod.rs │ │ ├── unix.rs │ │ └── windows.rs │ ├── pipe.rs │ ├── stdio │ │ ├── mod.rs │ │ ├── unix.rs │ │ └── windows.rs │ └── utils │ │ ├── mod.rs │ │ ├── unix.rs │ │ └── windows.rs └── tests │ ├── file.rs │ └── utils.rs ├── compio-io ├── Cargo.toml ├── src │ ├── buffer.rs │ ├── compat.rs │ ├── framed │ │ ├── codec │ │ │ ├── mod.rs │ │ │ └── serde_json.rs │ │ ├── frame.rs │ │ └── mod.rs │ ├── lib.rs │ ├── read │ │ ├── buf.rs │ │ ├── ext.rs │ │ ├── managed.rs │ │ └── mod.rs │ ├── util │ │ ├── internal.rs │ │ ├── mod.rs │ │ ├── null.rs │ │ ├── repeat.rs │ │ ├── split.rs │ │ └── take.rs │ └── write │ │ ├── buf.rs │ │ ├── ext.rs │ │ └── mod.rs └── tests │ ├── compat.rs │ └── io.rs ├── compio-log ├── Cargo.toml ├── src │ ├── dummy.rs │ └── lib.rs └── tests │ └── test.rs ├── compio-macros ├── Cargo.toml └── src │ ├── item_fn.rs │ ├── lib.rs │ ├── main_fn.rs │ └── test_fn.rs ├── compio-net ├── Cargo.toml ├── src │ ├── cmsg │ │ ├── mod.rs │ │ ├── unix.rs │ │ └── windows.rs │ ├── lib.rs │ ├── poll_fd │ │ ├── mod.rs │ │ ├── unix.rs │ │ └── windows.rs │ ├── resolve │ │ ├── mod.rs │ │ ├── unix.rs │ │ └── windows.rs │ ├── socket.rs │ ├── split.rs │ ├── tcp.rs │ ├── udp.rs │ └── unix.rs └── tests │ ├── cmsg.rs │ ├── poll.rs │ ├── split.rs │ ├── tcp_accept.rs │ ├── tcp_connect.rs │ ├── udp.rs │ └── unix_stream.rs ├── compio-process ├── Cargo.toml ├── src │ ├── lib.rs │ ├── linux.rs │ ├── unix.rs │ └── windows.rs └── tests │ └── process.rs ├── compio-quic ├── Cargo.toml ├── benches │ └── quic.rs ├── build.rs ├── examples │ ├── http3-client.rs │ ├── http3-server.rs │ ├── quic-client.rs │ ├── quic-dispatcher.rs │ └── quic-server.rs ├── src │ ├── builder.rs │ ├── connection.rs │ ├── endpoint.rs │ ├── incoming.rs │ ├── lib.rs │ ├── recv_stream.rs │ ├── send_stream.rs │ └── socket.rs └── tests │ ├── basic.rs │ ├── common │ └── mod.rs │ ├── control.rs │ └── echo.rs ├── compio-runtime ├── Cargo.toml ├── src │ ├── attacher.rs │ ├── event.rs │ ├── lib.rs │ ├── runtime │ │ ├── mod.rs │ │ ├── op.rs │ │ ├── send_wrapper.rs │ │ └── time.rs │ └── time.rs └── tests │ ├── custom_loop.rs │ └── event.rs ├── compio-signal ├── Cargo.toml └── src │ ├── lib.rs │ ├── linux.rs │ ├── unix.rs │ └── windows.rs ├── compio-tls ├── Cargo.toml ├── src │ ├── adapter │ │ ├── mod.rs │ │ └── rtls.rs │ ├── lib.rs │ └── stream │ │ ├── mod.rs │ │ └── rtls.rs └── tests │ └── connect.rs ├── compio ├── Cargo.toml ├── benches │ ├── fs.rs │ ├── monoio_wrap │ │ └── mod.rs │ └── net.rs ├── examples │ ├── basic.rs │ ├── dispatcher.rs │ ├── driver.rs │ ├── named_pipe.rs │ ├── net.rs │ ├── resolve.rs │ ├── tick.rs │ └── unix.rs ├── src │ └── lib.rs └── tests │ └── runtime.rs ├── flake.lock ├── flake.nix └── rustfmt.toml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | -------------------------------------------------------------------------------- /.github/workflows/ci_check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | RUST_BACKTRACE: 1 17 | 18 | jobs: 19 | check: 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | os: [ ubuntu-22.04, windows-latest, macos-latest ] 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Setup Rust Toolchain 27 | run: | 28 | rustup default nightly 29 | rustup component add clippy 30 | - uses: actions/setup-go@v4 31 | with: 32 | go-version: '>=1.18' 33 | - name: Install NASM on Windows 34 | if: runner.os == 'Windows' 35 | uses: ilammy/setup-nasm@v1 36 | - name: Install ninja-build tool on Windows 37 | if: runner.os == 'Windows' 38 | uses: seanmiddleditch/gha-setup-ninja@v4 39 | - name: Check clippy 40 | run: | 41 | cargo clippy --all-features --all-targets -- -Dwarnings 42 | - name: Check Docs 43 | run: | 44 | cargo doc --workspace --all-features --no-deps 45 | -------------------------------------------------------------------------------- /.github/workflows/ci_test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | RUST_BACKTRACE: 1 17 | 18 | jobs: 19 | test: 20 | runs-on: ${{ matrix.setup.os }} 21 | strategy: 22 | matrix: 23 | toolchain: ['nightly', 'beta', 'stable'] 24 | setup: 25 | - os: 'ubuntu-22.04' 26 | - os: 'ubuntu-22.04' 27 | features: 'polling' # fusion 28 | - os: 'ubuntu-22.04' 29 | features: 'io-uring-sqe128,io-uring-cqe32' 30 | - os: 'ubuntu-22.04' 31 | features: 'polling,ring' 32 | no_default_features: true 33 | - os: 'ubuntu-22.04' 34 | features: 'polling,native-tls,ring' 35 | no_default_features: true 36 | - os: 'ubuntu-22.04' 37 | features: 'codec-serde-json' 38 | - os: 'windows-latest' 39 | target: 'x86_64-pc-windows-msvc' 40 | - os: 'windows-latest' 41 | target: 'x86_64-pc-windows-gnu' 42 | - os: 'windows-latest' 43 | target: 'i686-pc-windows-msvc' 44 | - os: 'windows-latest' 45 | target: 'x86_64-pc-windows-msvc' 46 | features: 'iocp-global' 47 | - os: 'windows-latest' 48 | target: 'x86_64-pc-windows-msvc' 49 | features: 'iocp-wait-packet' 50 | - os: 'macos-13' 51 | - os: 'macos-14' 52 | - os: 'macos-15' 53 | steps: 54 | - uses: actions/checkout@v4 55 | - name: Setup Rust Toolchain 56 | run: rustup toolchain install ${{ matrix.toolchain }} 57 | - name: Setup target 58 | if: ${{ matrix.setup.target }} 59 | run: rustup +${{ matrix.toolchain }} target install ${{ matrix.setup.target }} 60 | - name: Test on ${{ matrix.setup.os }} ${{ matrix.toolchain }} ${{ matrix.setup.target }} 61 | shell: bash 62 | run: | 63 | set -ex 64 | 65 | ARGS=("--features" "all") 66 | 67 | # Add feature "nightly" if toolchain is nightly 68 | if [[ "${{ matrix.toolchain }}" == "nightly" ]]; then 69 | ARGS+=("--features" "nightly") 70 | fi 71 | # Add features if features is not empty 72 | if [[ -n "${{ matrix.setup.features }}" ]]; then 73 | ARGS+=("--features" "${{ matrix.setup.features }}") 74 | fi 75 | # Add no-default-features if no_default_features is true 76 | if [[ -n "${{ matrix.setup.no_default_features }}" ]]; then 77 | ARGS+=("--no-default-features") 78 | fi 79 | # Add doctest-xcompile if on windows and nightly 80 | if [[ "${{ matrix.setup.os }}" == "windows-latest" && ${{ matrix.toolchain }} == "nightly" ]]; then 81 | ARGS+=("-Z" "doctest-xcompile") 82 | fi 83 | 84 | cargo +${{ matrix.toolchain }} test --workspace "${ARGS[@]}" 85 | -------------------------------------------------------------------------------- /.github/workflows/ci_test_illumos.yml: -------------------------------------------------------------------------------- 1 | name: TestIllumos 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | runs-on: ubuntu-latest 18 | strategy: 19 | matrix: 20 | toolchain: ["nightly", "beta", "stable"] 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Test in Illumos 24 | id: test 25 | uses: vmactions/omnios-vm@v1 26 | with: 27 | usesh: true 28 | sync: rsync 29 | copyback: false 30 | prepare: | 31 | pkg install gcc14 curl pkg-config glib2 32 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf > install.sh 33 | chmod +x install.sh 34 | ./install.sh -y --default-toolchain ${{ matrix.toolchain }} --profile minimal 35 | run: | 36 | . "$HOME/.cargo/env" 37 | set -ex 38 | 39 | ARGS="--features all" 40 | 41 | # Add feature "nightly" if toolchain is nightly 42 | if [ "${{ matrix.toolchain }}" = "nightly" ]; then 43 | ARGS="$ARGS --features nightly" 44 | fi 45 | 46 | RUST_BACKTRACE=1 cargo +${{ matrix.toolchain }} test --workspace $ARGS 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /.vscode 4 | /.cargo 5 | .idea 6 | /.direnv 7 | .envrc 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | We adhere to [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). tl;dr: **be nice**. 4 | 5 | If you have any question about the Code of Conduct, or anything about Compio, feel free to join [our group](https://t.me/compio_rs) in telegram. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Compio 2 | 3 | Thanks for your help improving the project! We are so happy to have you! :tada: 4 | 5 | There are opportunities to contribute to Compio at any level. It doesn't matter if 6 | you are just getting started with Rust or are the most weathered expert, we can 7 | use your help. If you have any question about Compio, feel free to join [our group](https://t.me/compio_rs) in telegram. 8 | 9 | This guide will walk you through the process of contributing to Compio on following topics: 10 | 11 | - [General guidelines](#general-guidelines) 12 | - [Develop Guide](#develop-guide) 13 | - [Style Guide](#style-guide) 14 | - [Contribute with issue](#contribute-with-issue) 15 | - [Contribute with pull request](#contribute-with-pull-request) 16 | 17 | ## General guidelines 18 | 19 | We adhere to [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). tl;dr: **be nice**. Before making any contribution, check existing issue and pull requests to avoid duplication of effort. Also, in case of bug, try updating to the latest version of Compio and/or rust might help. 20 | 21 | ### Develop Guide 22 | 23 | - Use nightly toolchain to develop and run `rustup update` regularly. Compio does use nightly features, behind feature gate; so, when testing with `--all-features` flag, only nightly toolchain would work. 24 | 25 | ### Style Guide 26 | 27 | - Use `cargo fmt --all` with nightly toolchain to format your code (for nightly `rustfmt` features, see detail in [`rustfmt.toml`]). 28 | - Use `cargo clippy --all` to check any style/code problem. 29 | - Use [Angular Convention](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format) when making commits 30 | - When adding new crate, add `#![warn(missing_docs)]` at the top of `lib.rs` 31 | 32 | [`rustfmt.toml`]: https://github.com/compio-rs/compio/blob/master/rustfmt.toml 33 | 34 | ## Contribute with issue 35 | 36 | If you find a bug or have a feature request, please [open an issue](https://github.com/compio-rs/compio/issues/new/choose) with detailed description. Issues that are lack of information or destructive will be requested for more information or closed. 37 | 38 | It's also helpful if you can provide the following information: 39 | 40 | - A minimal reproducible example 41 | - The version of Compio you are using. 42 | - The version of Rust you are using. 43 | - Your environment (OS, Platform, etc). 44 | 45 | ## Contribute with pull request 46 | 47 | We welcome any code contributions. It's always welcome and recommended to open an issue to discuss on major changes before opening a PR. And pull requests should: 48 | 49 | - follow the [Style Guide](#style-guide). 50 | - pass CI tests and style check. You can run `cargo test --all-features` locally to test, but result may differ from CI depend on local environment -- it might be a good chance to contribute! 51 | - be reviewed by at least one maintainer before getting merged. 52 | - have a description of what it does and why it is needed in PR body. 53 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "compio", 4 | "compio-buf", 5 | "compio-dispatcher", 6 | "compio-driver", 7 | "compio-fs", 8 | "compio-io", 9 | "compio-log", 10 | "compio-macros", 11 | "compio-net", 12 | "compio-process", 13 | "compio-quic", 14 | "compio-runtime", 15 | "compio-signal", 16 | "compio-tls", 17 | ] 18 | resolver = "2" 19 | 20 | [workspace.package] 21 | edition = "2021" 22 | authors = ["Berrysoft "] 23 | readme = "README.md" 24 | license = "MIT" 25 | repository = "https://github.com/compio-rs/compio" 26 | 27 | [workspace.dependencies] 28 | compio-buf = { path = "./compio-buf", version = "0.5.1" } 29 | compio-driver = { path = "./compio-driver", version = "0.7.0", default-features = false } 30 | compio-runtime = { path = "./compio-runtime", version = "0.7.0" } 31 | compio-macros = { path = "./compio-macros", version = "0.1.2" } 32 | compio-fs = { path = "./compio-fs", version = "0.7.0" } 33 | compio-io = { path = "./compio-io", version = "0.6.0" } 34 | compio-net = { path = "./compio-net", version = "0.7.0" } 35 | compio-signal = { path = "./compio-signal", version = "0.5.0" } 36 | compio-dispatcher = { path = "./compio-dispatcher", version = "0.6.0" } 37 | compio-log = { path = "./compio-log", version = "0.1.0" } 38 | compio-tls = { path = "./compio-tls", version = "0.5.0", default-features = false } 39 | compio-process = { path = "./compio-process", version = "0.4.0" } 40 | compio-quic = { path = "./compio-quic", version = "0.3.0", default-features = false } 41 | 42 | bytes = "1.7.1" 43 | flume = "0.11.0" 44 | cfg_aliases = "0.2.1" 45 | cfg-if = "1.0.0" 46 | criterion = "0.6.0" 47 | crossbeam-channel = "0.5.8" 48 | crossbeam-queue = "0.3.8" 49 | futures-channel = "0.3.29" 50 | futures-util = "0.3.29" 51 | libc = "0.2.164" 52 | nix = "0.30.1" 53 | once_cell = "1.18.0" 54 | os_pipe = "1.1.4" 55 | paste = "1.0.14" 56 | rand = "0.9.0" 57 | rustls = { version = "0.23.1", default-features = false } 58 | rustls-native-certs = "0.8.0" 59 | slab = "0.4.9" 60 | socket2 = "0.5.6" 61 | tempfile = "3.8.1" 62 | tokio = "1.33.0" 63 | tracing-subscriber = "0.3.18" 64 | widestring = "1.0.2" 65 | windows-sys = "0.52.0" 66 | thiserror = "2.0.3" 67 | 68 | [profile.bench] 69 | debug = true 70 | lto = true 71 | codegen-units = 1 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Berrysoft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | 7 | --- 8 | 9 | # Compio 10 | 11 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/compio-rs/compio/blob/master/LICENSE) 12 | [![crates.io](https://img.shields.io/crates/v/compio)](https://crates.io/crates/compio) 13 | [![docs.rs](https://img.shields.io/badge/docs.rs-compio-latest)](https://docs.rs/compio) 14 | [![Check](https://github.com/compio-rs/compio/actions/workflows/ci_check.yml/badge.svg)](https://github.com/compio-rs/compio/actions/workflows/ci_check.yml) 15 | [![Test](https://github.com/compio-rs/compio/actions/workflows/ci_test.yml/badge.svg)](https://github.com/compio-rs/compio/actions/workflows/ci_test.yml) 16 | [![Telegram](https://img.shields.io/badge/Telegram-compio--rs-blue?logo=telegram)](https://t.me/compio_rs) 17 | 18 | A thread-per-core Rust runtime with IOCP/io_uring/polling. 19 | The name comes from "completion-based IO". 20 | This crate is inspired by [monoio](https://github.com/bytedance/monoio/). 21 | 22 | ## Why not Tokio? 23 | 24 | Tokio is a great generic-purpose async runtime. 25 | However, it is poll-based, and even uses [undocumented APIs](https://notgull.net/device-afd/) on Windows. 26 | We would like some new high-level APIs to perform IOCP/io_uring. 27 | 28 | Unlike `tokio-uring`, this runtime isn't Tokio-based. 29 | This is mainly because that no public APIs to control IOCP in `mio`, 30 | and `tokio` won't expose APIs to control `mio` before `mio` reaches 1.0. 31 | 32 | ## Why not monoio/tokio-uring/glommio? 33 | 34 | They don't support Windows. 35 | 36 | ## Quick start 37 | 38 | Add `compio` as dependency: 39 | 40 | ``` 41 | compio = { version = "0.13.1", features = ["macros"] } 42 | ``` 43 | 44 | Then we can use high level APIs to perform filesystem & net IO. 45 | 46 | ```rust 47 | use compio::{fs::File, io::AsyncReadAtExt}; 48 | 49 | #[compio::main] 50 | async fn main() { 51 | let file = File::open("Cargo.toml").await.unwrap(); 52 | let (read, buffer) = file.read_to_end_at(Vec::with_capacity(1024), 0).await.unwrap(); 53 | assert_eq!(read, buffer.len()); 54 | let buffer = String::from_utf8(buffer).unwrap(); 55 | println!("{}", buffer); 56 | } 57 | ``` 58 | 59 | You can also control the low-level driver manually. See `driver` example of the repo. 60 | 61 | ## Contributing 62 | 63 | There are opportunities to contribute to Compio at any level. It doesn't matter if 64 | you are just getting started with Rust or are the most weathered expert, we can 65 | use your help. If you have any question about Compio, feel free to join our [telegram group](https://t.me/compio_rs). Before contributing, please checkout our [contributing guide](https://github.com/compio-rs/compio/blob/master/CONTRIBUTING.md). 66 | -------------------------------------------------------------------------------- /compio-buf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-buf" 3 | version = "0.5.1" 4 | description = "Buffer trait for completion based async IO" 5 | categories = ["asynchronous"] 6 | keywords = ["async"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | bumpalo = { version = "3.14.0", optional = true } 19 | arrayvec = { version = "0.7.4", optional = true } 20 | bytes = { workspace = true, optional = true } 21 | smallvec = { version = "1.13.2", optional = true } 22 | 23 | [target.'cfg(unix)'.dependencies] 24 | libc = { workspace = true } 25 | 26 | [features] 27 | # Dependencies 28 | arrayvec = ["dep:arrayvec"] 29 | smallvec = ["dep:smallvec"] 30 | bumpalo = ["dep:bumpalo"] 31 | bytes = ["dep:bytes"] 32 | 33 | # Nightly features 34 | allocator_api = ["bumpalo?/allocator_api"] 35 | read_buf = [] 36 | try_trait_v2 = [] 37 | nightly = ["allocator_api", "read_buf", "try_trait_v2"] 38 | -------------------------------------------------------------------------------- /compio-buf/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for working with buffers. 2 | //! 3 | //! Completion APIs require passing ownership of buffers to the runtime. The 4 | //! crate defines [`IoBuf`] and [`IoBufMut`] traits which are implemented by 5 | //! buffer types that respect the safety contract. 6 | 7 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 8 | #![cfg_attr(feature = "allocator_api", feature(allocator_api))] 9 | #![cfg_attr(feature = "read_buf", feature(read_buf, core_io_borrowed_buf))] 10 | #![cfg_attr(feature = "try_trait_v2", feature(try_trait_v2, try_trait_v2_residual))] 11 | #![warn(missing_docs)] 12 | 13 | #[cfg(feature = "arrayvec")] 14 | pub use arrayvec; 15 | #[cfg(feature = "bumpalo")] 16 | pub use bumpalo; 17 | #[cfg(feature = "bytes")] 18 | pub use bytes; 19 | 20 | mod io_slice; 21 | pub use io_slice::*; 22 | 23 | mod buf_result; 24 | pub use buf_result::*; 25 | 26 | mod io_buf; 27 | pub use io_buf::*; 28 | 29 | mod io_vec_buf; 30 | pub use io_vec_buf::*; 31 | 32 | mod slice; 33 | pub use slice::*; 34 | 35 | mod uninit; 36 | pub use uninit::*; 37 | 38 | mod iter; 39 | pub use iter::*; 40 | 41 | /// Trait to get the inner buffer of an operation or a result. 42 | pub trait IntoInner { 43 | /// The inner type. 44 | type Inner; 45 | 46 | /// Get the inner buffer. 47 | fn into_inner(self) -> Self::Inner; 48 | } 49 | 50 | #[cfg(not(feature = "allocator_api"))] 51 | #[macro_export] 52 | #[doc(hidden)] 53 | macro_rules! t_alloc { 54 | ($b:tt, $t:ty, $a:ident) => { 55 | $b<$t> 56 | }; 57 | } 58 | 59 | #[cfg(feature = "allocator_api")] 60 | #[macro_export] 61 | #[doc(hidden)] 62 | macro_rules! t_alloc { 63 | ($b:tt, $t:ty, $a:ident) => { 64 | $b<$t, $a> 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /compio-buf/src/slice.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use crate::*; 4 | 5 | /// An owned view into a contiguous sequence of bytes. 6 | /// 7 | /// This is similar to Rust slices (`&buf[..]`) but owns the underlying buffer. 8 | /// This type is useful for performing io-uring read and write operations using 9 | /// a subset of a buffer. 10 | /// 11 | /// Slices are created using [`IoBuf::slice`]. 12 | /// 13 | /// # Examples 14 | /// 15 | /// Creating a slice 16 | /// 17 | /// ``` 18 | /// use compio_buf::IoBuf; 19 | /// 20 | /// let buf = b"hello world"; 21 | /// let slice = buf.slice(..5); 22 | /// 23 | /// assert_eq!(&slice[..], b"hello"); 24 | /// ``` 25 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 26 | pub struct Slice { 27 | buffer: T, 28 | begin: usize, 29 | end: usize, 30 | } 31 | 32 | impl Slice { 33 | pub(crate) fn new(buffer: T, begin: usize, end: usize) -> Self { 34 | Self { buffer, begin, end } 35 | } 36 | 37 | /// Offset in the underlying buffer at which this slice starts. 38 | pub fn begin(&self) -> usize { 39 | self.begin 40 | } 41 | 42 | /// Offset in the underlying buffer at which this slice ends. 43 | pub fn end(&self) -> usize { 44 | self.end 45 | } 46 | 47 | pub(crate) fn set_range(&mut self, begin: usize, end: usize) { 48 | self.begin = begin; 49 | self.end = end; 50 | } 51 | 52 | /// Gets a reference to the underlying buffer. 53 | /// 54 | /// This method escapes the slice's view. 55 | pub fn as_inner(&self) -> &T { 56 | &self.buffer 57 | } 58 | 59 | /// Gets a mutable reference to the underlying buffer. 60 | /// 61 | /// This method escapes the slice's view. 62 | pub fn as_inner_mut(&mut self) -> &mut T { 63 | &mut self.buffer 64 | } 65 | } 66 | 67 | fn slice_mut(buffer: &mut T) -> &mut [u8] { 68 | unsafe { std::slice::from_raw_parts_mut(buffer.as_buf_mut_ptr(), (*buffer).buf_len()) } 69 | } 70 | 71 | impl Deref for Slice { 72 | type Target = [u8]; 73 | 74 | fn deref(&self) -> &Self::Target { 75 | let bytes = self.buffer.as_slice(); 76 | let end = self.end.min(bytes.len()); 77 | &bytes[self.begin..end] 78 | } 79 | } 80 | 81 | impl DerefMut for Slice { 82 | fn deref_mut(&mut self) -> &mut Self::Target { 83 | let bytes = slice_mut(&mut self.buffer); 84 | let end = self.end.min(bytes.len()); 85 | &mut bytes[self.begin..end] 86 | } 87 | } 88 | 89 | unsafe impl IoBuf for Slice { 90 | fn as_buf_ptr(&self) -> *const u8 { 91 | self.buffer.as_slice()[self.begin..].as_ptr() 92 | } 93 | 94 | fn buf_len(&self) -> usize { 95 | self.deref().len() 96 | } 97 | 98 | fn buf_capacity(&self) -> usize { 99 | self.end - self.begin 100 | } 101 | } 102 | 103 | unsafe impl IoBufMut for Slice { 104 | fn as_buf_mut_ptr(&mut self) -> *mut u8 { 105 | slice_mut(&mut self.buffer)[self.begin..].as_mut_ptr() 106 | } 107 | } 108 | 109 | impl SetBufInit for Slice { 110 | unsafe fn set_buf_init(&mut self, len: usize) { 111 | self.buffer.set_buf_init(self.begin + len) 112 | } 113 | } 114 | 115 | impl IntoInner for Slice { 116 | type Inner = T; 117 | 118 | fn into_inner(self) -> Self::Inner { 119 | self.buffer 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /compio-buf/src/uninit.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | /// A [`Slice`] that only exposes uninitialized bytes. 4 | /// 5 | /// [`Uninit`] can be created with [`IoBuf::uninit`]. 6 | /// 7 | /// # Examples 8 | /// 9 | /// Creating an uninit slice 10 | /// 11 | /// ``` 12 | /// use compio_buf::IoBuf; 13 | /// 14 | /// let mut buf = Vec::from(b"hello world"); 15 | /// buf.reserve_exact(10); 16 | /// let slice = buf.uninit(); 17 | /// 18 | /// assert_eq!(slice.as_slice(), b""); 19 | /// assert_eq!(slice.buf_capacity(), 10); 20 | /// ``` 21 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 22 | pub struct Uninit(Slice); 23 | 24 | impl Uninit { 25 | pub(crate) fn new(buffer: T) -> Self { 26 | let len = buffer.buf_len(); 27 | Self(buffer.slice(len..)) 28 | } 29 | } 30 | 31 | impl Uninit { 32 | /// Offset in the underlying buffer at which uninitialized bytes starts. 33 | pub fn begin(&self) -> usize { 34 | self.0.begin() 35 | } 36 | 37 | /// Gets a reference to the underlying buffer. 38 | /// 39 | /// This method escapes the slice's view. 40 | pub fn as_inner(&self) -> &T { 41 | self.0.as_inner() 42 | } 43 | 44 | /// Gets a mutable reference to the underlying buffer. 45 | /// 46 | /// This method escapes the slice's view. 47 | pub fn as_inner_mut(&mut self) -> &mut T { 48 | self.0.as_inner_mut() 49 | } 50 | } 51 | 52 | unsafe impl IoBuf for Uninit { 53 | fn as_buf_ptr(&self) -> *const u8 { 54 | self.0.as_buf_ptr() 55 | } 56 | 57 | fn buf_len(&self) -> usize { 58 | 0 59 | } 60 | 61 | fn buf_capacity(&self) -> usize { 62 | self.0.buf_capacity() 63 | } 64 | } 65 | 66 | unsafe impl IoBufMut for Uninit { 67 | fn as_buf_mut_ptr(&mut self) -> *mut u8 { 68 | self.0.as_buf_mut_ptr() 69 | } 70 | } 71 | 72 | impl SetBufInit for Uninit { 73 | unsafe fn set_buf_init(&mut self, len: usize) { 74 | self.0.set_buf_init(len); 75 | let inner = self.0.as_inner(); 76 | self.0.set_range(inner.buf_len(), inner.buf_capacity()); 77 | } 78 | } 79 | 80 | impl IntoInner for Uninit { 81 | type Inner = T; 82 | 83 | fn into_inner(self) -> Self::Inner { 84 | self.0.into_inner() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /compio-dispatcher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-dispatcher" 3 | version = "0.6.0" 4 | description = "Multithreading dispatcher for compio" 5 | categories = ["asynchronous"] 6 | keywords = ["async", "runtime"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | # Workspace dependencies 19 | compio-driver = { workspace = true } 20 | compio-runtime = { workspace = true } 21 | 22 | flume = { workspace = true } 23 | futures-channel = { workspace = true } 24 | 25 | [dev-dependencies] 26 | compio-buf = { workspace = true } 27 | compio-io = { workspace = true } 28 | compio-net = { workspace = true } 29 | compio-macros = { workspace = true } 30 | 31 | futures-util = { workspace = true } 32 | 33 | [features] 34 | io-uring = ["compio-runtime/io-uring"] 35 | polling = ["compio-runtime/polling"] 36 | -------------------------------------------------------------------------------- /compio-dispatcher/tests/listener.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroUsize; 2 | 3 | use compio_buf::arrayvec::ArrayVec; 4 | use compio_dispatcher::Dispatcher; 5 | use compio_io::{AsyncReadExt, AsyncWriteExt}; 6 | use compio_net::{TcpListener, TcpStream}; 7 | use compio_runtime::spawn; 8 | use futures_util::{StreamExt, stream::FuturesUnordered}; 9 | 10 | #[compio_macros::test] 11 | async fn listener_dispatch() { 12 | const THREAD_NUM: usize = 5; 13 | const CLIENT_NUM: usize = 10; 14 | 15 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 16 | let addr = listener.local_addr().unwrap(); 17 | let dispatcher = Dispatcher::builder() 18 | .worker_threads(NonZeroUsize::new(THREAD_NUM).unwrap()) 19 | .build() 20 | .unwrap(); 21 | let task = spawn(async move { 22 | let mut futures = FuturesUnordered::from_iter((0..CLIENT_NUM).map(|_| async { 23 | let mut cli = TcpStream::connect(&addr).await.unwrap(); 24 | cli.write_all("Hello world!").await.unwrap(); 25 | })); 26 | while let Some(()) = futures.next().await {} 27 | }); 28 | let mut handles = FuturesUnordered::new(); 29 | for _i in 0..CLIENT_NUM { 30 | let (mut srv, _) = listener.accept().await.unwrap(); 31 | let handle = dispatcher 32 | .dispatch(move || async move { 33 | let (_, buf) = srv.read_exact(ArrayVec::::new()).await.unwrap(); 34 | assert_eq!(buf.as_slice(), b"Hello world!"); 35 | }) 36 | .unwrap(); 37 | handles.push(handle); 38 | } 39 | while handles.next().await.is_some() {} 40 | let (_, results) = futures_util::join!(task, dispatcher.join()); 41 | results.unwrap(); 42 | } 43 | -------------------------------------------------------------------------------- /compio-driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-driver" 3 | version = "0.7.0" 4 | description = "Low-level driver for compio" 5 | categories = ["asynchronous"] 6 | keywords = ["async", "iocp", "io-uring"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | default-target = "x86_64-unknown-linux-gnu" 17 | targets = [ 18 | "x86_64-pc-windows-gnu", 19 | "x86_64-unknown-linux-gnu", 20 | "x86_64-apple-darwin", 21 | "aarch64-apple-ios", 22 | "aarch64-linux-android", 23 | "x86_64-unknown-dragonfly", 24 | "x86_64-unknown-freebsd", 25 | "x86_64-unknown-illumos", 26 | "x86_64-unknown-netbsd", 27 | "x86_64-unknown-openbsd", 28 | ] 29 | 30 | [dependencies] 31 | # Workspace dependencies 32 | compio-buf = { workspace = true } 33 | compio-log = { workspace = true } 34 | 35 | # Utils 36 | cfg-if = { workspace = true } 37 | crossbeam-channel = { workspace = true } 38 | futures-util = { workspace = true } 39 | socket2 = { workspace = true, features = ["all"] } 40 | 41 | # Windows specific dependencies 42 | [target.'cfg(windows)'.dependencies] 43 | aligned-array = "1.0.1" 44 | once_cell = { workspace = true } 45 | windows-sys = { workspace = true, features = [ 46 | "Win32_Foundation", 47 | "Win32_Networking_WinSock", 48 | "Win32_Security", 49 | "Win32_Storage_FileSystem", 50 | "Win32_System_Console", 51 | "Win32_System_IO", 52 | "Win32_System_Pipes", 53 | "Win32_System_SystemServices", 54 | "Win32_System_Threading", 55 | "Win32_System_WindowsProgramming", 56 | ] } 57 | 58 | # Linux specific dependencies 59 | [target.'cfg(target_os = "linux")'.dependencies] 60 | io-uring = { version = "0.7.0", optional = true } 61 | io_uring_buf_ring = { version = "0.2.0", optional = true } 62 | polling = { version = "3.3.0", optional = true } 63 | paste = { workspace = true } 64 | slab = { workspace = true, optional = true } 65 | 66 | # Other platform dependencies 67 | [target.'cfg(all(not(target_os = "linux"), unix))'.dependencies] 68 | polling = "3.3.0" 69 | 70 | [target.'cfg(unix)'.dependencies] 71 | crossbeam-channel = { workspace = true } 72 | crossbeam-queue = { workspace = true } 73 | libc = { workspace = true } 74 | 75 | [build-dependencies] 76 | cfg_aliases = { workspace = true } 77 | 78 | [features] 79 | default = ["io-uring"] 80 | io-uring = ["dep:io-uring", "dep:io_uring_buf_ring", "dep:slab"] 81 | polling = ["dep:polling"] 82 | 83 | io-uring-sqe128 = [] 84 | io-uring-cqe32 = [] 85 | 86 | iocp-global = [] 87 | iocp-wait-packet = [] 88 | 89 | # Nightly features 90 | once_cell_try = [] 91 | nightly = ["once_cell_try"] 92 | -------------------------------------------------------------------------------- /compio-driver/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() { 4 | cfg_aliases! { 5 | datasync: { any( 6 | target_os = "android", 7 | target_os = "freebsd", 8 | target_os = "fuchsia", 9 | target_os = "illumos", 10 | target_os = "linux", 11 | target_os = "netbsd" 12 | ) }, 13 | gnulinux: { all(target_os = "linux", target_env = "gnu") }, 14 | freebsd: { target_os = "freebsd" }, 15 | solarish: { any(target_os = "illumos", target_os = "solaris") }, 16 | aio: { any(freebsd, solarish) }, 17 | io_uring: { all(target_os = "linux", feature = "io-uring") }, 18 | fusion: { all(target_os = "linux", feature = "io-uring", feature = "polling") } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /compio-driver/src/buffer_pool/fusion.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::{Borrow, BorrowMut}, 3 | fmt::{Debug, Formatter}, 4 | ops::{Deref, DerefMut}, 5 | }; 6 | 7 | #[path = "fallback.rs"] 8 | mod fallback; 9 | 10 | #[path = "iour.rs"] 11 | mod iour; 12 | 13 | pub use fallback::BufferPool as FallbackBufferPool; 14 | pub(crate) use fallback::OwnedBuffer; 15 | pub use iour::BufferPool as IoUringBufferPool; 16 | 17 | /// Buffer pool 18 | /// 19 | /// A buffer pool to allow user no need to specify a specific buffer to do the 20 | /// IO operation 21 | pub struct BufferPool { 22 | inner: BufferPollInner, 23 | } 24 | 25 | impl Debug for BufferPool { 26 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 27 | f.debug_struct("BufferPool").finish_non_exhaustive() 28 | } 29 | } 30 | 31 | impl BufferPool { 32 | pub(crate) fn new_io_uring(buffer_pool: iour::BufferPool) -> Self { 33 | Self { 34 | inner: BufferPollInner::IoUring(buffer_pool), 35 | } 36 | } 37 | 38 | pub(crate) fn as_io_uring(&self) -> &iour::BufferPool { 39 | match &self.inner { 40 | BufferPollInner::IoUring(inner) => inner, 41 | BufferPollInner::Poll(_) => panic!("BufferPool type is not poll type"), 42 | } 43 | } 44 | 45 | pub(crate) fn as_poll(&self) -> &fallback::BufferPool { 46 | match &self.inner { 47 | BufferPollInner::Poll(inner) => inner, 48 | BufferPollInner::IoUring(_) => panic!("BufferPool type is not io-uring type"), 49 | } 50 | } 51 | 52 | pub(crate) fn new_poll(buffer_pool: fallback::BufferPool) -> Self { 53 | Self { 54 | inner: BufferPollInner::Poll(buffer_pool), 55 | } 56 | } 57 | 58 | pub(crate) fn into_io_uring(self) -> iour::BufferPool { 59 | match self.inner { 60 | BufferPollInner::IoUring(inner) => inner, 61 | BufferPollInner::Poll(_) => panic!("BufferPool type is not poll type"), 62 | } 63 | } 64 | } 65 | 66 | enum BufferPollInner { 67 | IoUring(iour::BufferPool), 68 | Poll(fallback::BufferPool), 69 | } 70 | 71 | /// Buffer borrowed from buffer pool 72 | /// 73 | /// When IO operation finish, user will obtain a `BorrowedBuffer` to access the 74 | /// filled data 75 | pub struct BorrowedBuffer<'a> { 76 | inner: BorrowedBufferInner<'a>, 77 | } 78 | 79 | impl<'a> BorrowedBuffer<'a> { 80 | pub(crate) fn new_io_uring(buffer: iour::BorrowedBuffer<'a>) -> Self { 81 | Self { 82 | inner: BorrowedBufferInner::IoUring(buffer), 83 | } 84 | } 85 | 86 | pub(crate) fn new_poll(buffer: fallback::BorrowedBuffer<'a>) -> Self { 87 | Self { 88 | inner: BorrowedBufferInner::Poll(buffer), 89 | } 90 | } 91 | } 92 | 93 | enum BorrowedBufferInner<'a> { 94 | IoUring(iour::BorrowedBuffer<'a>), 95 | Poll(fallback::BorrowedBuffer<'a>), 96 | } 97 | 98 | impl Debug for BorrowedBuffer<'_> { 99 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 100 | f.debug_struct("BorrowedBuffer").finish_non_exhaustive() 101 | } 102 | } 103 | 104 | impl Deref for BorrowedBuffer<'_> { 105 | type Target = [u8]; 106 | 107 | fn deref(&self) -> &Self::Target { 108 | match &self.inner { 109 | BorrowedBufferInner::IoUring(inner) => inner.deref(), 110 | BorrowedBufferInner::Poll(inner) => inner.deref(), 111 | } 112 | } 113 | } 114 | 115 | impl DerefMut for BorrowedBuffer<'_> { 116 | fn deref_mut(&mut self) -> &mut Self::Target { 117 | match &mut self.inner { 118 | BorrowedBufferInner::IoUring(inner) => inner.deref_mut(), 119 | BorrowedBufferInner::Poll(inner) => inner.deref_mut(), 120 | } 121 | } 122 | } 123 | 124 | impl AsRef<[u8]> for BorrowedBuffer<'_> { 125 | fn as_ref(&self) -> &[u8] { 126 | self.deref() 127 | } 128 | } 129 | 130 | impl AsMut<[u8]> for BorrowedBuffer<'_> { 131 | fn as_mut(&mut self) -> &mut [u8] { 132 | self.deref_mut() 133 | } 134 | } 135 | 136 | impl Borrow<[u8]> for BorrowedBuffer<'_> { 137 | fn borrow(&self) -> &[u8] { 138 | self.deref() 139 | } 140 | } 141 | 142 | impl BorrowMut<[u8]> for BorrowedBuffer<'_> { 143 | fn borrow_mut(&mut self) -> &mut [u8] { 144 | self.deref_mut() 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /compio-driver/src/buffer_pool/iour.rs: -------------------------------------------------------------------------------- 1 | //! The io-uring buffer pool. It is backed by a [`Vec`] of [`Vec`]. 2 | //! The kernel selects the buffer and returns `flags`. The crate 3 | //! [`io_uring_buf_ring`] handles the returning of buffer on drop. 4 | 5 | use std::{ 6 | borrow::{Borrow, BorrowMut}, 7 | fmt::{Debug, Formatter}, 8 | io, 9 | ops::{Deref, DerefMut}, 10 | }; 11 | 12 | use io_uring::cqueue::buffer_select; 13 | use io_uring_buf_ring::IoUringBufRing; 14 | 15 | /// Buffer pool 16 | /// 17 | /// A buffer pool to allow user no need to specify a specific buffer to do the 18 | /// IO operation 19 | pub struct BufferPool { 20 | buf_ring: IoUringBufRing>, 21 | } 22 | 23 | impl Debug for BufferPool { 24 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 25 | f.debug_struct("BufferPool").finish_non_exhaustive() 26 | } 27 | } 28 | 29 | impl BufferPool { 30 | pub(crate) fn new(buf_ring: IoUringBufRing>) -> Self { 31 | Self { buf_ring } 32 | } 33 | 34 | pub(crate) fn buffer_group(&self) -> u16 { 35 | self.buf_ring.buffer_group() 36 | } 37 | 38 | pub(crate) fn into_inner(self) -> IoUringBufRing> { 39 | self.buf_ring 40 | } 41 | 42 | /// ## Safety 43 | /// * `available_len` should be the returned value from the op. 44 | pub(crate) unsafe fn get_buffer( 45 | &self, 46 | flags: u32, 47 | available_len: usize, 48 | ) -> io::Result { 49 | let buffer_id = buffer_select(flags).ok_or_else(|| { 50 | io::Error::new( 51 | io::ErrorKind::InvalidInput, 52 | format!("flags {flags} is invalid"), 53 | ) 54 | })?; 55 | 56 | self.buf_ring 57 | .get_buf(buffer_id, available_len) 58 | .map(BorrowedBuffer) 59 | .ok_or_else(|| io::Error::other(format!("cannot find buffer {buffer_id}"))) 60 | } 61 | 62 | pub(crate) fn reuse_buffer(&self, flags: u32) { 63 | // It ignores invalid flags. 64 | if let Some(buffer_id) = buffer_select(flags) { 65 | // Safety: 0 is always valid length. We just want to get the buffer once and 66 | // return it immediately. 67 | unsafe { self.buf_ring.get_buf(buffer_id, 0) }; 68 | } 69 | } 70 | } 71 | 72 | /// Buffer borrowed from buffer pool 73 | /// 74 | /// When IO operation finish, user will obtain a `BorrowedBuffer` to access the 75 | /// filled data 76 | pub struct BorrowedBuffer<'a>(io_uring_buf_ring::BorrowedBuffer<'a, Vec>); 77 | 78 | impl Debug for BorrowedBuffer<'_> { 79 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 80 | f.debug_struct("BorrowedBuffer").finish_non_exhaustive() 81 | } 82 | } 83 | 84 | impl Deref for BorrowedBuffer<'_> { 85 | type Target = [u8]; 86 | 87 | fn deref(&self) -> &Self::Target { 88 | self.0.deref() 89 | } 90 | } 91 | 92 | impl DerefMut for BorrowedBuffer<'_> { 93 | fn deref_mut(&mut self) -> &mut Self::Target { 94 | self.0.deref_mut() 95 | } 96 | } 97 | 98 | impl AsRef<[u8]> for BorrowedBuffer<'_> { 99 | fn as_ref(&self) -> &[u8] { 100 | self.deref() 101 | } 102 | } 103 | 104 | impl AsMut<[u8]> for BorrowedBuffer<'_> { 105 | fn as_mut(&mut self) -> &mut [u8] { 106 | self.deref_mut() 107 | } 108 | } 109 | 110 | impl Borrow<[u8]> for BorrowedBuffer<'_> { 111 | fn borrow(&self) -> &[u8] { 112 | self.deref() 113 | } 114 | } 115 | 116 | impl BorrowMut<[u8]> for BorrowedBuffer<'_> { 117 | fn borrow_mut(&mut self) -> &mut [u8] { 118 | self.deref_mut() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /compio-driver/src/buffer_pool/mod.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(io_uring)] { 3 | cfg_if::cfg_if! { 4 | if #[cfg(fusion)] { 5 | mod fusion; 6 | pub use fusion::*; 7 | } else { 8 | mod iour; 9 | pub use iour::*; 10 | } 11 | } 12 | } else { 13 | mod fallback; 14 | pub use fallback::*; 15 | } 16 | } 17 | 18 | /// Trait to get the selected buffer of an io operation. 19 | pub trait TakeBuffer { 20 | /// Selected buffer type. It keeps the reference to the buffer pool and 21 | /// returns the buffer back on drop. 22 | type Buffer<'a>; 23 | 24 | /// Buffer pool type. 25 | type BufferPool; 26 | 27 | /// Take the selected buffer with `buffer_pool`, io `result` and `flags`, if 28 | /// io operation is success. 29 | fn take_buffer( 30 | self, 31 | buffer_pool: &Self::BufferPool, 32 | result: std::io::Result, 33 | flags: u32, 34 | ) -> std::io::Result>; 35 | } 36 | -------------------------------------------------------------------------------- /compio-driver/src/driver_type.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicU8, Ordering}; 2 | 3 | const UNINIT: u8 = u8::MAX; 4 | const IO_URING: u8 = 0; 5 | const POLLING: u8 = 1; 6 | const IOCP: u8 = 2; 7 | 8 | static DRIVER_TYPE: AtomicU8 = AtomicU8::new(UNINIT); 9 | 10 | /// Representing underlying driver type the fusion driver is using 11 | #[repr(u8)] 12 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 13 | pub enum DriverType { 14 | /// Using `polling` driver 15 | Poll = POLLING, 16 | 17 | /// Using `io-uring` driver 18 | IoUring = IO_URING, 19 | 20 | /// Using `iocp` driver 21 | IOCP = IOCP, 22 | } 23 | 24 | impl DriverType { 25 | fn from_num(n: u8) -> Self { 26 | match n { 27 | IO_URING => Self::IoUring, 28 | POLLING => Self::Poll, 29 | IOCP => Self::IOCP, 30 | _ => unreachable!("invalid driver type"), 31 | } 32 | } 33 | 34 | /// Get the underlying driver type 35 | fn get() -> DriverType { 36 | cfg_if::cfg_if! { 37 | if #[cfg(windows)] { 38 | DriverType::IOCP 39 | } else if #[cfg(fusion)] { 40 | use io_uring::opcode::*; 41 | 42 | // Add more opcodes here if used 43 | const USED_OP: &[u8] = &[ 44 | Read::CODE, 45 | Readv::CODE, 46 | Write::CODE, 47 | Writev::CODE, 48 | Fsync::CODE, 49 | Accept::CODE, 50 | Connect::CODE, 51 | RecvMsg::CODE, 52 | SendMsg::CODE, 53 | AsyncCancel::CODE, 54 | OpenAt::CODE, 55 | Close::CODE, 56 | Shutdown::CODE, 57 | Socket::CODE, 58 | ]; 59 | 60 | (|| { 61 | let uring = io_uring::IoUring::new(2)?; 62 | let mut probe = io_uring::Probe::new(); 63 | uring.submitter().register_probe(&mut probe)?; 64 | if USED_OP.iter().all(|op| probe.is_supported(*op)) { 65 | std::io::Result::Ok(DriverType::IoUring) 66 | } else { 67 | Ok(DriverType::Poll) 68 | } 69 | })() 70 | .unwrap_or(DriverType::Poll) // Should we fail here? 71 | } else if #[cfg(io_uring)] { 72 | DriverType::IoUring 73 | } else if #[cfg(unix)] { 74 | DriverType::Poll 75 | } else { 76 | compile_error!("unsupported platform"); 77 | } 78 | } 79 | } 80 | 81 | /// Get the underlying driver type and cache it. Following calls will return 82 | /// the cached value. 83 | pub fn current() -> DriverType { 84 | match DRIVER_TYPE.load(Ordering::Acquire) { 85 | UNINIT => {} 86 | x => return DriverType::from_num(x), 87 | } 88 | let dev_ty = Self::get(); 89 | 90 | DRIVER_TYPE.store(dev_ty as u8, Ordering::Release); 91 | 92 | dev_ty 93 | } 94 | 95 | /// Check if the current driver is `polling` 96 | pub fn is_polling() -> bool { 97 | Self::current() == DriverType::Poll 98 | } 99 | 100 | /// Check if the current driver is `io-uring` 101 | pub fn is_iouring() -> bool { 102 | Self::current() == DriverType::IoUring 103 | } 104 | 105 | /// Check if the current driver is `iocp` 106 | pub fn is_iocp() -> bool { 107 | Self::current() == DriverType::IOCP 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /compio-driver/src/iocp/cp/multi.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | os::windows::io::{AsRawHandle, RawHandle}, 4 | sync::Arc, 5 | time::Duration, 6 | }; 7 | 8 | use super::CompletionPort; 9 | use crate::{Entry, Overlapped, RawFd}; 10 | 11 | pub struct Port { 12 | port: Arc, 13 | } 14 | 15 | impl Port { 16 | pub fn new() -> io::Result { 17 | Ok(Self { 18 | port: Arc::new(CompletionPort::new()?), 19 | }) 20 | } 21 | 22 | pub fn attach(&mut self, fd: RawFd) -> io::Result<()> { 23 | self.port.attach(fd) 24 | } 25 | 26 | pub fn handle(&self) -> PortHandle { 27 | PortHandle::new(self.port.clone()) 28 | } 29 | 30 | pub fn post_raw(&self, optr: *const Overlapped) -> io::Result<()> { 31 | self.port.post_raw(optr) 32 | } 33 | 34 | pub fn poll(&self, timeout: Option) -> io::Result + '_> { 35 | let current_id = self.as_raw_handle() as _; 36 | self.port.poll(timeout, Some(current_id)) 37 | } 38 | } 39 | 40 | impl AsRawHandle for Port { 41 | fn as_raw_handle(&self) -> RawHandle { 42 | self.port.as_raw_handle() 43 | } 44 | } 45 | 46 | pub struct PortHandle { 47 | port: Arc, 48 | } 49 | 50 | impl PortHandle { 51 | fn new(port: Arc) -> Self { 52 | Self { port } 53 | } 54 | 55 | pub fn post(&self, res: io::Result, optr: *mut Overlapped) -> io::Result<()> { 56 | self.port.post(res, optr) 57 | } 58 | 59 | pub fn post_raw(&self, optr: *const Overlapped) -> io::Result<()> { 60 | self.port.post_raw(optr) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /compio-driver/src/iocp/wait/mod.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(feature = "iocp-wait-packet")] { 3 | #[path = "packet.rs"] 4 | mod sys; 5 | } else { 6 | #[path = "thread_pool.rs"] 7 | mod sys; 8 | } 9 | } 10 | 11 | pub use sys::*; 12 | -------------------------------------------------------------------------------- /compio-driver/src/iocp/wait/packet.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::c_void, 3 | io, 4 | os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}, 5 | ptr::null_mut, 6 | }; 7 | 8 | use windows_sys::Win32::Foundation::{ 9 | BOOLEAN, GENERIC_READ, GENERIC_WRITE, HANDLE, NTSTATUS, RtlNtStatusToDosError, STATUS_PENDING, 10 | STATUS_SUCCESS, 11 | }; 12 | 13 | use crate::{Key, OpCode, RawFd, sys::cp}; 14 | 15 | extern "system" { 16 | fn NtCreateWaitCompletionPacket( 17 | WaitCompletionPacketHandle: *mut HANDLE, 18 | DesiredAccess: u32, 19 | ObjectAttributes: *mut c_void, 20 | ) -> NTSTATUS; 21 | 22 | fn NtAssociateWaitCompletionPacket( 23 | WaitCompletionPacketHandle: HANDLE, 24 | IoCompletionHandle: HANDLE, 25 | TargetObjectHandle: HANDLE, 26 | KeyContext: *mut c_void, 27 | ApcContext: *mut c_void, 28 | IoStatus: NTSTATUS, 29 | IoStatusInformation: usize, 30 | AlreadySignaled: *mut BOOLEAN, 31 | ) -> NTSTATUS; 32 | 33 | fn NtCancelWaitCompletionPacket( 34 | WaitCompletionPacketHandle: HANDLE, 35 | RemoveSignaledPacket: BOOLEAN, 36 | ) -> NTSTATUS; 37 | } 38 | 39 | pub struct Wait { 40 | handle: OwnedHandle, 41 | cancelled: bool, 42 | } 43 | 44 | fn check_status(status: NTSTATUS) -> io::Result<()> { 45 | if status >= 0 { 46 | Ok(()) 47 | } else { 48 | Err(io::Error::from_raw_os_error(unsafe { 49 | RtlNtStatusToDosError(status) as _ 50 | })) 51 | } 52 | } 53 | 54 | impl Wait { 55 | pub fn new(port: &cp::Port, event: RawFd, op: &mut Key) -> io::Result { 56 | let mut handle = 0; 57 | check_status(unsafe { 58 | NtCreateWaitCompletionPacket(&mut handle, GENERIC_READ | GENERIC_WRITE, null_mut()) 59 | })?; 60 | let handle = unsafe { OwnedHandle::from_raw_handle(handle as _) }; 61 | check_status(unsafe { 62 | NtAssociateWaitCompletionPacket( 63 | handle.as_raw_handle() as _, 64 | port.as_raw_handle() as _, 65 | event, 66 | null_mut(), 67 | op.as_mut_ptr().cast(), 68 | STATUS_SUCCESS, 69 | 0, 70 | null_mut(), 71 | ) 72 | })?; 73 | Ok(Self { 74 | handle, 75 | cancelled: false, 76 | }) 77 | } 78 | 79 | pub fn cancel(&mut self) -> io::Result<()> { 80 | let res = unsafe { NtCancelWaitCompletionPacket(self.handle.as_raw_handle() as _, 0) }; 81 | self.cancelled = res != STATUS_PENDING; 82 | check_status(res) 83 | } 84 | 85 | pub fn is_cancelled(&self) -> bool { 86 | self.cancelled 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /compio-driver/src/iocp/wait/thread_pool.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, io, ptr::null}; 2 | 3 | use windows_sys::Win32::{ 4 | Foundation::{ERROR_IO_PENDING, ERROR_TIMEOUT, WAIT_OBJECT_0, WAIT_TIMEOUT}, 5 | System::Threading::{ 6 | CloseThreadpoolWait, CreateThreadpoolWait, PTP_CALLBACK_INSTANCE, PTP_WAIT, 7 | SetThreadpoolWait, WaitForThreadpoolWaitCallbacks, 8 | }, 9 | }; 10 | 11 | use crate::{Key, OpCode, RawFd, sys::cp, syscall}; 12 | 13 | pub struct Wait { 14 | wait: PTP_WAIT, 15 | // For memory safety. 16 | #[allow(dead_code)] 17 | context: Box, 18 | } 19 | 20 | impl Wait { 21 | pub fn new(port: &cp::Port, event: RawFd, op: &mut Key) -> io::Result { 22 | let port = port.handle(); 23 | let mut context = Box::new(WinThreadpoolWaitContext { 24 | port, 25 | user_data: op.user_data(), 26 | }); 27 | let wait = syscall!( 28 | BOOL, 29 | CreateThreadpoolWait( 30 | Some(Self::wait_callback), 31 | (&mut *context) as *mut WinThreadpoolWaitContext as _, 32 | null() 33 | ) 34 | )?; 35 | unsafe { 36 | SetThreadpoolWait(wait, event as _, null()); 37 | } 38 | Ok(Self { wait, context }) 39 | } 40 | 41 | unsafe extern "system" fn wait_callback( 42 | _instance: PTP_CALLBACK_INSTANCE, 43 | context: *mut c_void, 44 | _wait: PTP_WAIT, 45 | result: u32, 46 | ) { 47 | let context = &*(context as *mut WinThreadpoolWaitContext); 48 | let res = match result { 49 | WAIT_OBJECT_0 => Ok(0), 50 | WAIT_TIMEOUT => Err(io::Error::from_raw_os_error(ERROR_TIMEOUT as _)), 51 | _ => Err(io::Error::from_raw_os_error(result as _)), 52 | }; 53 | let mut op = unsafe { Key::::new_unchecked(context.user_data) }; 54 | context.port.post(res, op.as_mut_ptr()).ok(); 55 | } 56 | 57 | pub fn cancel(&mut self) -> io::Result<()> { 58 | // Try to cancel it, but don't know whether it is successfully cancelled. 59 | unsafe { 60 | SetThreadpoolWait(self.wait, 0, null()); 61 | } 62 | Err(io::Error::from_raw_os_error(ERROR_IO_PENDING as _)) 63 | } 64 | 65 | pub fn is_cancelled(&self) -> bool { 66 | false 67 | } 68 | } 69 | 70 | impl Drop for Wait { 71 | fn drop(&mut self) { 72 | unsafe { 73 | SetThreadpoolWait(self.wait, 0, null()); 74 | WaitForThreadpoolWaitCallbacks(self.wait, 1); 75 | CloseThreadpoolWait(self.wait); 76 | } 77 | } 78 | } 79 | 80 | struct WinThreadpoolWaitContext { 81 | port: cp::PortHandle, 82 | user_data: usize, 83 | } 84 | -------------------------------------------------------------------------------- /compio-driver/src/unix/mod.rs: -------------------------------------------------------------------------------- 1 | //! This mod doesn't actually contain any driver, but meant to provide some 2 | //! common op type and utilities for unix platform (for iour and polling). 3 | 4 | pub(crate) mod op; 5 | 6 | use crate::RawFd; 7 | 8 | /// The overlapped struct for unix needn't contain extra fields. 9 | pub(crate) struct Overlapped; 10 | 11 | impl Overlapped { 12 | pub fn new(_driver: RawFd) -> Self { 13 | Self 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /compio-fs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-fs" 3 | version = "0.7.0" 4 | description = "Filesystem IO for compio" 5 | categories = ["asynchronous", "filesystem"] 6 | keywords = ["async", "fs"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | # Workspace dependencies 19 | compio-buf = { workspace = true } 20 | compio-driver = { workspace = true } 21 | compio-io = { workspace = true } 22 | compio-runtime = { workspace = true } 23 | 24 | cfg-if = { workspace = true } 25 | 26 | # Windows specific dependencies 27 | [target.'cfg(windows)'.dependencies] 28 | widestring = { workspace = true } 29 | windows-sys = { workspace = true, features = [ 30 | "Win32_Foundation", 31 | "Win32_Security", 32 | "Win32_Storage_FileSystem", 33 | "Win32_System_Pipes", 34 | "Win32_System_SystemServices", 35 | ] } 36 | 37 | # Windows specific dev dependencies 38 | [target.'cfg(windows)'.dev-dependencies] 39 | windows-sys = { workspace = true, features = ["Win32_Security_Authorization"] } 40 | 41 | # Unix specific dependencies 42 | [target.'cfg(unix)'.dependencies] 43 | libc = { workspace = true } 44 | os_pipe = { workspace = true } 45 | 46 | [build-dependencies] 47 | cfg_aliases = { workspace = true } 48 | 49 | # Shared dev dependencies for all platforms 50 | [dev-dependencies] 51 | compio-runtime = { workspace = true, features = ["time"] } 52 | compio-macros = { workspace = true } 53 | futures-util = { workspace = true } 54 | tempfile = { workspace = true } 55 | 56 | # Windows specific dev dependencies 57 | [target.'cfg(target_os = "windows")'.dev-dependencies] 58 | windows-sys = { workspace = true, features = ["Win32_Security_Authorization"] } 59 | 60 | # Unix specific dev dependencies 61 | [target.'cfg(unix)'.dev-dependencies] 62 | nix = { workspace = true, features = ["fs"] } 63 | 64 | [features] 65 | io-uring = ["compio-runtime/io-uring"] 66 | polling = ["compio-runtime/polling"] 67 | 68 | read_buf = ["compio-buf/read_buf", "compio-io/read_buf"] 69 | windows_by_handle = [] 70 | nightly = ["read_buf", "windows_by_handle"] 71 | -------------------------------------------------------------------------------- /compio-fs/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() { 4 | cfg_aliases! { 5 | noctime: { any( 6 | target_os = "freebsd", 7 | target_os = "openbsd", 8 | target_vendor = "apple" 9 | ) }, 10 | solarish: { any(target_os = "illumos", target_os = "solaris") }, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /compio-fs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Filesystem manipulation operations. 2 | 3 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 4 | #![warn(missing_docs)] 5 | #![cfg_attr(feature = "read_buf", feature(read_buf, core_io_borrowed_buf))] 6 | #![cfg_attr( 7 | all(windows, feature = "windows_by_handle"), 8 | feature(windows_by_handle) 9 | )] 10 | 11 | mod file; 12 | pub use file::*; 13 | 14 | mod open_options; 15 | pub use open_options::*; 16 | 17 | mod metadata; 18 | pub use metadata::*; 19 | 20 | mod stdio; 21 | pub use stdio::*; 22 | 23 | mod utils; 24 | pub use utils::*; 25 | 26 | mod async_fd; 27 | pub use async_fd::*; 28 | 29 | #[cfg(windows)] 30 | pub mod named_pipe; 31 | 32 | #[cfg(unix)] 33 | pub mod pipe; 34 | 35 | #[cfg(unix)] 36 | pub(crate) fn path_string(path: impl AsRef) -> std::io::Result { 37 | use std::os::unix::ffi::OsStrExt; 38 | 39 | std::ffi::CString::new(path.as_ref().as_os_str().as_bytes().to_vec()).map_err(|_| { 40 | std::io::Error::new( 41 | std::io::ErrorKind::InvalidInput, 42 | "file name contained an unexpected NUL byte", 43 | ) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /compio-fs/src/metadata/windows.rs: -------------------------------------------------------------------------------- 1 | pub use std::fs::{FileType, Metadata, Permissions}; 2 | use std::{io, panic::resume_unwind, path::Path}; 3 | 4 | pub async fn metadata(path: impl AsRef) -> io::Result { 5 | let path = path.as_ref().to_path_buf(); 6 | compio_runtime::spawn_blocking(move || std::fs::metadata(path)) 7 | .await 8 | .unwrap_or_else(|e| resume_unwind(e)) 9 | } 10 | 11 | pub async fn symlink_metadata(path: impl AsRef) -> io::Result { 12 | let path = path.as_ref().to_path_buf(); 13 | compio_runtime::spawn_blocking(move || std::fs::symlink_metadata(path)) 14 | .await 15 | .unwrap_or_else(|e| resume_unwind(e)) 16 | } 17 | 18 | pub async fn set_permissions(path: impl AsRef, perm: Permissions) -> io::Result<()> { 19 | let path = path.as_ref().to_path_buf(); 20 | compio_runtime::spawn_blocking(move || std::fs::set_permissions(path, perm)) 21 | .await 22 | .unwrap_or_else(|e| resume_unwind(e)) 23 | } 24 | -------------------------------------------------------------------------------- /compio-fs/src/open_options/unix.rs: -------------------------------------------------------------------------------- 1 | use std::{io, os::fd::FromRawFd, path::Path}; 2 | 3 | use compio_driver::{RawFd, op::OpenFile}; 4 | 5 | use crate::{File, path_string}; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct OpenOptions { 9 | read: bool, 10 | write: bool, 11 | truncate: bool, 12 | create: bool, 13 | create_new: bool, 14 | custom_flags: i32, 15 | mode: libc::mode_t, 16 | } 17 | 18 | impl OpenOptions { 19 | pub fn new() -> OpenOptions { 20 | OpenOptions { 21 | read: false, 22 | write: false, 23 | truncate: false, 24 | create: false, 25 | create_new: false, 26 | custom_flags: 0, 27 | mode: 0o666, 28 | } 29 | } 30 | 31 | pub fn read(&mut self, read: bool) { 32 | self.read = read; 33 | } 34 | 35 | pub fn write(&mut self, write: bool) { 36 | self.write = write; 37 | } 38 | 39 | pub fn truncate(&mut self, truncate: bool) { 40 | self.truncate = truncate; 41 | } 42 | 43 | pub fn create(&mut self, create: bool) { 44 | self.create = create; 45 | } 46 | 47 | pub fn create_new(&mut self, create_new: bool) { 48 | self.create_new = create_new; 49 | } 50 | 51 | pub fn custom_flags(&mut self, flags: i32) { 52 | self.custom_flags = flags; 53 | } 54 | 55 | pub fn mode(&mut self, mode: u32) { 56 | self.mode = mode as libc::mode_t; 57 | } 58 | 59 | fn get_access_mode(&self) -> io::Result { 60 | match (self.read, self.write) { 61 | (true, false) => Ok(libc::O_RDONLY), 62 | (false, true) => Ok(libc::O_WRONLY), 63 | (true, true) => Ok(libc::O_RDWR), 64 | (false, false) => Err(io::Error::from_raw_os_error(libc::EINVAL)), 65 | } 66 | } 67 | 68 | fn get_creation_mode(&self) -> io::Result { 69 | if !self.write && (self.truncate || self.create || self.create_new) { 70 | return Err(io::Error::from_raw_os_error(libc::EINVAL)); 71 | } 72 | 73 | Ok(match (self.create, self.truncate, self.create_new) { 74 | (false, false, false) => 0, 75 | (true, false, false) => libc::O_CREAT, 76 | (false, true, false) => libc::O_TRUNC, 77 | (true, true, false) => libc::O_CREAT | libc::O_TRUNC, 78 | (_, _, true) => libc::O_CREAT | libc::O_EXCL, 79 | }) 80 | } 81 | 82 | pub async fn open(&self, p: impl AsRef) -> io::Result { 83 | let flags = libc::O_CLOEXEC 84 | | self.get_access_mode()? 85 | | self.get_creation_mode()? 86 | | (self.custom_flags as libc::c_int & !libc::O_ACCMODE); 87 | let p = path_string(p)?; 88 | let op = OpenFile::new(p, flags, self.mode); 89 | let fd = compio_runtime::submit(op).await.0? as RawFd; 90 | File::from_std(unsafe { std::fs::File::from_raw_fd(fd) }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /compio-fs/src/open_options/windows.rs: -------------------------------------------------------------------------------- 1 | use std::{io, os::windows::fs::OpenOptionsExt, panic::resume_unwind, path::Path}; 2 | 3 | use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OVERLAPPED; 4 | 5 | use crate::File; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct OpenOptions { 9 | opt: std::fs::OpenOptions, 10 | } 11 | 12 | impl OpenOptions { 13 | pub fn new() -> OpenOptions { 14 | OpenOptions { 15 | opt: std::fs::OpenOptions::new(), 16 | } 17 | } 18 | 19 | pub fn read(&mut self, read: bool) { 20 | self.opt.read(read); 21 | } 22 | 23 | pub fn write(&mut self, write: bool) { 24 | self.opt.write(write); 25 | } 26 | 27 | pub fn truncate(&mut self, truncate: bool) { 28 | self.opt.truncate(truncate); 29 | } 30 | 31 | pub fn create(&mut self, create: bool) { 32 | self.opt.create(create); 33 | } 34 | 35 | pub fn create_new(&mut self, create_new: bool) { 36 | self.opt.create_new(create_new); 37 | } 38 | 39 | pub fn custom_flags(&mut self, flags: u32) { 40 | self.opt.custom_flags(flags); 41 | } 42 | 43 | pub fn access_mode(&mut self, access_mode: u32) { 44 | self.opt.access_mode(access_mode); 45 | } 46 | 47 | pub fn share_mode(&mut self, share_mode: u32) { 48 | self.opt.share_mode(share_mode); 49 | } 50 | 51 | pub fn attributes(&mut self, attrs: u32) { 52 | self.opt.attributes(attrs); 53 | } 54 | 55 | pub fn security_qos_flags(&mut self, flags: u32) { 56 | self.opt.security_qos_flags(flags); 57 | } 58 | 59 | pub async fn open(&self, p: impl AsRef) -> io::Result { 60 | let mut opt = self.opt.clone(); 61 | opt.attributes(FILE_FLAG_OVERLAPPED); 62 | let p = p.as_ref().to_path_buf(); 63 | let file = compio_runtime::spawn_blocking(move || opt.open(p)) 64 | .await 65 | .unwrap_or_else(|e| resume_unwind(e))?; 66 | File::from_std(file) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /compio-fs/src/stdio/mod.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(windows)] { 3 | mod windows; 4 | pub use windows::*; 5 | } else if #[cfg(unix)] { 6 | mod unix; 7 | pub use unix::*; 8 | } 9 | } 10 | 11 | /// Constructs a handle to the standard input of the current process. 12 | /// 13 | /// ## Platform specific 14 | /// * Windows: This handle is best used for non-interactive uses, such as when a 15 | /// file is piped into the application. For technical reasons, if `stdin` is a 16 | /// console handle, the read method is implemented by using an ordinary 17 | /// blocking read on a separate thread, and it is impossible to cancel that 18 | /// read. This can make shutdown of the runtime hang until the user presses 19 | /// enter. 20 | /// 21 | /// [`AsyncRead`]: compio_io::AsyncRead 22 | pub fn stdin() -> Stdin { 23 | Stdin::new() 24 | } 25 | 26 | /// Constructs a handle to the standard output of the current process. 27 | /// 28 | /// Concurrent writes to stdout must be executed with care: Only individual 29 | /// writes to this [`AsyncWrite`] are guaranteed to be intact. In particular 30 | /// you should be aware that writes using [`write_all`] are not guaranteed 31 | /// to occur as a single write, so multiple threads writing data with 32 | /// [`write_all`] may result in interleaved output. 33 | /// 34 | /// [`AsyncWrite`]: compio_io::AsyncWrite 35 | /// [`write_all`]: compio_io::AsyncWriteExt::write_all 36 | pub fn stdout() -> Stdout { 37 | Stdout::new() 38 | } 39 | 40 | /// Constructs a handle to the standard error of the current process. 41 | /// 42 | /// Concurrent writes to stderr must be executed with care: Only individual 43 | /// writes to this [`AsyncWrite`] are guaranteed to be intact. In particular 44 | /// you should be aware that writes using [`write_all`] are not guaranteed 45 | /// to occur as a single write, so multiple threads writing data with 46 | /// [`write_all`] may result in interleaved output. 47 | /// 48 | /// [`AsyncWrite`]: compio_io::AsyncWrite 49 | /// [`write_all`]: compio_io::AsyncWriteExt::write_all 50 | pub fn stderr() -> Stderr { 51 | Stderr::new() 52 | } 53 | -------------------------------------------------------------------------------- /compio-fs/src/stdio/unix.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use compio_buf::{BufResult, IoBuf, IoBufMut, IoVectoredBuf, IoVectoredBufMut}; 4 | use compio_driver::{AsRawFd, RawFd}; 5 | use compio_io::{AsyncRead, AsyncWrite}; 6 | 7 | #[cfg(doc)] 8 | use super::{stderr, stdin, stdout}; 9 | use crate::AsyncFd; 10 | 11 | /// A handle to the standard input stream of a process. 12 | /// 13 | /// See [`stdin`]. 14 | #[derive(Debug, Clone)] 15 | pub struct Stdin(AsyncFd); 16 | 17 | impl Stdin { 18 | pub(crate) fn new() -> Self { 19 | // SAFETY: no need to attach on unix 20 | Self(unsafe { AsyncFd::new_unchecked(libc::STDIN_FILENO) }) 21 | } 22 | } 23 | 24 | impl AsyncRead for Stdin { 25 | async fn read(&mut self, buf: B) -> BufResult { 26 | (&*self).read(buf).await 27 | } 28 | 29 | async fn read_vectored(&mut self, buf: V) -> BufResult { 30 | (&*self).read_vectored(buf).await 31 | } 32 | } 33 | 34 | impl AsyncRead for &Stdin { 35 | async fn read(&mut self, buf: B) -> BufResult { 36 | (&self.0).read(buf).await 37 | } 38 | 39 | async fn read_vectored(&mut self, buf: V) -> BufResult { 40 | (&self.0).read_vectored(buf).await 41 | } 42 | } 43 | 44 | impl AsRawFd for Stdin { 45 | fn as_raw_fd(&self) -> RawFd { 46 | self.0.as_raw_fd() 47 | } 48 | } 49 | 50 | /// A handle to the standard output stream of a process. 51 | /// 52 | /// See [`stdout`]. 53 | #[derive(Debug, Clone)] 54 | pub struct Stdout(AsyncFd); 55 | 56 | impl Stdout { 57 | pub(crate) fn new() -> Self { 58 | // SAFETY: no need to attach on unix 59 | Self(unsafe { AsyncFd::new_unchecked(libc::STDOUT_FILENO) }) 60 | } 61 | } 62 | 63 | impl AsyncWrite for Stdout { 64 | async fn write(&mut self, buf: T) -> BufResult { 65 | self.0.write(buf).await 66 | } 67 | 68 | async fn write_vectored(&mut self, buf: T) -> BufResult { 69 | self.0.write_vectored(buf).await 70 | } 71 | 72 | async fn flush(&mut self) -> io::Result<()> { 73 | self.0.flush().await 74 | } 75 | 76 | async fn shutdown(&mut self) -> io::Result<()> { 77 | self.0.shutdown().await 78 | } 79 | } 80 | 81 | impl AsRawFd for Stdout { 82 | fn as_raw_fd(&self) -> RawFd { 83 | self.0.as_raw_fd() 84 | } 85 | } 86 | 87 | /// A handle to the standard output stream of a process. 88 | /// 89 | /// See [`stderr`]. 90 | #[derive(Debug, Clone)] 91 | pub struct Stderr(AsyncFd); 92 | 93 | impl Stderr { 94 | pub(crate) fn new() -> Self { 95 | // SAFETY: no need to attach on unix 96 | Self(unsafe { AsyncFd::new_unchecked(libc::STDERR_FILENO) }) 97 | } 98 | } 99 | 100 | impl AsyncWrite for Stderr { 101 | async fn write(&mut self, buf: T) -> BufResult { 102 | self.0.write(buf).await 103 | } 104 | 105 | async fn write_vectored(&mut self, buf: T) -> BufResult { 106 | self.0.write_vectored(buf).await 107 | } 108 | 109 | async fn flush(&mut self) -> io::Result<()> { 110 | self.0.flush().await 111 | } 112 | 113 | async fn shutdown(&mut self) -> io::Result<()> { 114 | self.0.shutdown().await 115 | } 116 | } 117 | 118 | impl AsRawFd for Stderr { 119 | fn as_raw_fd(&self) -> RawFd { 120 | self.0.as_raw_fd() 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /compio-fs/src/utils/unix.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path::Path}; 2 | 3 | use compio_driver::op::{CreateDir, HardLink, Rename, Symlink, Unlink}; 4 | 5 | use crate::path_string; 6 | 7 | async fn unlink(path: impl AsRef, dir: bool) -> io::Result<()> { 8 | let path = path_string(path)?; 9 | let op = Unlink::new(path, dir); 10 | compio_runtime::submit(op).await.0?; 11 | Ok(()) 12 | } 13 | 14 | pub async fn remove_file(path: impl AsRef) -> io::Result<()> { 15 | unlink(path, false).await 16 | } 17 | 18 | pub async fn remove_dir(path: impl AsRef) -> io::Result<()> { 19 | unlink(path, true).await 20 | } 21 | 22 | pub async fn rename(from: impl AsRef, to: impl AsRef) -> io::Result<()> { 23 | let from = path_string(from)?; 24 | let to = path_string(to)?; 25 | let op = Rename::new(from, to); 26 | compio_runtime::submit(op).await.0?; 27 | Ok(()) 28 | } 29 | 30 | pub async fn symlink(original: impl AsRef, link: impl AsRef) -> io::Result<()> { 31 | let original = path_string(original)?; 32 | let link = path_string(link)?; 33 | let op = Symlink::new(original, link); 34 | compio_runtime::submit(op).await.0?; 35 | Ok(()) 36 | } 37 | 38 | pub async fn hard_link(original: impl AsRef, link: impl AsRef) -> io::Result<()> { 39 | let original = path_string(original)?; 40 | let link = path_string(link)?; 41 | let op = HardLink::new(original, link); 42 | compio_runtime::submit(op).await.0?; 43 | Ok(()) 44 | } 45 | 46 | pub struct DirBuilder { 47 | mode: u32, 48 | } 49 | 50 | impl DirBuilder { 51 | pub fn new() -> Self { 52 | Self { mode: 0o777 } 53 | } 54 | 55 | pub fn mode(&mut self, mode: u32) { 56 | self.mode = mode; 57 | } 58 | 59 | pub async fn create(&self, path: &Path) -> io::Result<()> { 60 | let path = path_string(path)?; 61 | let op = CreateDir::new(path, self.mode as _); 62 | compio_runtime::submit(op).await.0?; 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /compio-fs/src/utils/windows.rs: -------------------------------------------------------------------------------- 1 | use std::{io, panic::resume_unwind, path::Path}; 2 | 3 | pub async fn remove_file(path: impl AsRef) -> io::Result<()> { 4 | let path = path.as_ref().to_path_buf(); 5 | compio_runtime::spawn_blocking(move || std::fs::remove_file(path)) 6 | .await 7 | .unwrap_or_else(|e| resume_unwind(e)) 8 | } 9 | 10 | pub async fn remove_dir(path: impl AsRef) -> io::Result<()> { 11 | let path = path.as_ref().to_path_buf(); 12 | compio_runtime::spawn_blocking(move || std::fs::remove_dir(path)) 13 | .await 14 | .unwrap_or_else(|e| resume_unwind(e)) 15 | } 16 | 17 | pub async fn rename(from: impl AsRef, to: impl AsRef) -> io::Result<()> { 18 | let from = from.as_ref().to_path_buf(); 19 | let to = to.as_ref().to_path_buf(); 20 | compio_runtime::spawn_blocking(move || std::fs::rename(from, to)) 21 | .await 22 | .unwrap_or_else(|e| resume_unwind(e)) 23 | } 24 | 25 | pub async fn symlink_file(original: impl AsRef, link: impl AsRef) -> io::Result<()> { 26 | let original = original.as_ref().to_path_buf(); 27 | let link = link.as_ref().to_path_buf(); 28 | compio_runtime::spawn_blocking(move || std::os::windows::fs::symlink_file(original, link)) 29 | .await 30 | .unwrap_or_else(|e| resume_unwind(e)) 31 | } 32 | 33 | pub async fn symlink_dir(original: impl AsRef, link: impl AsRef) -> io::Result<()> { 34 | let original = original.as_ref().to_path_buf(); 35 | let link = link.as_ref().to_path_buf(); 36 | compio_runtime::spawn_blocking(move || std::os::windows::fs::symlink_dir(original, link)) 37 | .await 38 | .unwrap_or_else(|e| resume_unwind(e)) 39 | } 40 | 41 | pub async fn hard_link(original: impl AsRef, link: impl AsRef) -> io::Result<()> { 42 | let original = original.as_ref().to_path_buf(); 43 | let link = link.as_ref().to_path_buf(); 44 | compio_runtime::spawn_blocking(move || std::fs::hard_link(original, link)) 45 | .await 46 | .unwrap_or_else(|e| resume_unwind(e)) 47 | } 48 | 49 | pub struct DirBuilder; 50 | 51 | impl DirBuilder { 52 | pub fn new() -> Self { 53 | Self 54 | } 55 | 56 | pub async fn create(&self, path: &Path) -> io::Result<()> { 57 | let path = path.to_path_buf(); 58 | compio_runtime::spawn_blocking(move || std::fs::create_dir(path)) 59 | .await 60 | .unwrap_or_else(|e| resume_unwind(e)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /compio-io/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-io" 3 | version = "0.6.0" 4 | description = "IO traits for completion based async IO" 5 | categories = ["asynchronous"] 6 | keywords = ["async", "io"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [dependencies] 14 | compio-buf = { workspace = true, features = ["arrayvec", "bytes"] } 15 | futures-util = { workspace = true, features = ["sink"] } 16 | paste = { workspace = true } 17 | thiserror = { workspace = true, optional = true } 18 | pin-project-lite = { version = "0.2.14", optional = true } 19 | serde = { version = "1.0.219", optional = true } 20 | serde_json = { version = "1.0.140", optional = true } 21 | 22 | [dev-dependencies] 23 | compio-runtime = { workspace = true } 24 | compio-macros = { workspace = true } 25 | tokio = { workspace = true, features = ["macros", "rt"] } 26 | serde = { version = "1.0.219", features = ["derive"] } 27 | futures-executor = "0.3.30" 28 | 29 | [features] 30 | default = [] 31 | compat = ["dep:pin-project-lite", "futures-util/io"] 32 | 33 | # Codecs 34 | # Serde json codec 35 | codec-serde-json = ["dep:serde", "dep:serde_json", "dep:thiserror"] 36 | 37 | # Nightly features 38 | allocator_api = ["compio-buf/allocator_api"] 39 | read_buf = ["compio-buf/read_buf"] 40 | nightly = ["allocator_api", "read_buf"] 41 | -------------------------------------------------------------------------------- /compio-io/src/framed/codec/mod.rs: -------------------------------------------------------------------------------- 1 | //! Traits and implementations for encoding/decoding structured types to/from 2 | //! bytes. 3 | 4 | use std::io; 5 | 6 | #[cfg(feature = "codec-serde-json")] 7 | pub mod serde_json; 8 | 9 | /// Trait for types that encode values into bytes. 10 | pub trait Encoder { 11 | /// The error type that can be returned during encoding operations. 12 | type Error: From; 13 | 14 | /// Encodes an item into bytes. 15 | /// 16 | /// Returns the number of bytes written to the buffer. 17 | fn encode(&mut self, item: Item, buf: &mut Vec) -> Result<(), Self::Error>; 18 | } 19 | 20 | /// Trait for decoding byte sequences back into structured items. 21 | pub trait Decoder { 22 | /// Errors happened during the decoding process 23 | type Error: From; 24 | 25 | /// Decodes a byte sequence into an item. 26 | fn decode(&mut self, buf: &[u8]) -> Result; 27 | } 28 | -------------------------------------------------------------------------------- /compio-io/src/read/buf.rs: -------------------------------------------------------------------------------- 1 | use compio_buf::{BufResult, IntoInner, IoBuf, IoBufMut, IoVectoredBufMut, buf_try}; 2 | 3 | use crate::{AsyncRead, IoResult, buffer::Buffer, util::DEFAULT_BUF_SIZE}; 4 | /// # AsyncBufRead 5 | /// 6 | /// Async read with buffered content. 7 | pub trait AsyncBufRead: AsyncRead { 8 | /// Try fill the internal buffer with data 9 | async fn fill_buf(&mut self) -> IoResult<&'_ [u8]>; 10 | 11 | /// Mark how much data is read 12 | fn consume(&mut self, amount: usize); 13 | } 14 | 15 | impl AsyncBufRead for &mut A { 16 | async fn fill_buf(&mut self) -> IoResult<&'_ [u8]> { 17 | (**self).fill_buf().await 18 | } 19 | 20 | fn consume(&mut self, amount: usize) { 21 | (**self).consume(amount) 22 | } 23 | } 24 | 25 | /// Wraps a reader and buffers input from [`AsyncRead`] 26 | /// 27 | /// It can be excessively inefficient to work directly with a [`AsyncRead`] 28 | /// instance. A `BufReader` performs large, infrequent reads on the 29 | /// underlying [`AsyncRead`] and maintains an in-memory buffer of the results. 30 | /// 31 | /// `BufReader` can improve the speed of programs that make *small* and 32 | /// *repeated* read calls to the same file or network socket. It does not 33 | /// help when reading very large amounts at once, or reading just one or a few 34 | /// times. It also provides no advantage when reading from a source that is 35 | /// already in memory, like a `Vec`. 36 | /// 37 | /// When the `BufReader` is dropped, the contents of its buffer will be 38 | /// discarded. Reading from the underlying reader after unwrapping the 39 | /// `BufReader` with [`BufReader::into_inner`] can cause data loss. 40 | /// 41 | /// # Caution 42 | /// 43 | /// Due to the pass-by-ownership nature of completion-based IO, the buffer is 44 | /// passed to the inner reader when [`fill_buf`] is called. If the future 45 | /// returned by [`fill_buf`] is dropped before inner `read` is completed, 46 | /// `BufReader` will not be able to retrieve the buffer, causing panic on next 47 | /// [`fill_buf`] call. 48 | /// 49 | /// [`fill_buf`]: #method.fill_buf 50 | #[derive(Debug)] 51 | pub struct BufReader { 52 | reader: R, 53 | buf: Buffer, 54 | } 55 | 56 | impl BufReader { 57 | /// Creates a new `BufReader` with a default buffer capacity. The default is 58 | /// currently 8 KB, but may change in the future. 59 | pub fn new(reader: R) -> Self { 60 | Self::with_capacity(DEFAULT_BUF_SIZE, reader) 61 | } 62 | 63 | /// Creates a new `BufReader` with the specified buffer capacity. 64 | pub fn with_capacity(cap: usize, reader: R) -> Self { 65 | Self { 66 | reader, 67 | buf: Buffer::with_capacity(cap), 68 | } 69 | } 70 | } 71 | 72 | impl AsyncRead for BufReader { 73 | async fn read(&mut self, buf: B) -> BufResult { 74 | let (mut slice, buf) = buf_try!(self.fill_buf().await, buf); 75 | slice.read(buf).await.map_res(|res| { 76 | self.consume(res); 77 | res 78 | }) 79 | } 80 | 81 | async fn read_vectored(&mut self, buf: V) -> BufResult { 82 | let (mut slice, buf) = buf_try!(self.fill_buf().await, buf); 83 | slice.read_vectored(buf).await.map_res(|res| { 84 | self.consume(res); 85 | res 86 | }) 87 | } 88 | } 89 | 90 | impl AsyncBufRead for BufReader { 91 | async fn fill_buf(&mut self) -> IoResult<&'_ [u8]> { 92 | let Self { reader, buf } = self; 93 | 94 | if buf.all_done() { 95 | buf.reset() 96 | } 97 | 98 | if buf.need_fill() { 99 | buf.with(|b| async move { 100 | let len = b.buf_len(); 101 | let b = b.slice(len..); 102 | reader.read(b).await.into_inner() 103 | }) 104 | .await?; 105 | } 106 | 107 | Ok(buf.slice()) 108 | } 109 | 110 | fn consume(&mut self, amount: usize) { 111 | self.buf.advance(amount); 112 | } 113 | } 114 | 115 | impl IntoInner for BufReader { 116 | type Inner = R; 117 | 118 | fn into_inner(self) -> Self::Inner { 119 | self.reader 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /compio-io/src/read/managed.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use compio_buf::IoBuf; 4 | 5 | use crate::IoResult; 6 | 7 | /// # AsyncReadManaged 8 | /// 9 | /// Async read with buffer pool 10 | pub trait AsyncReadManaged { 11 | /// Buffer pool type 12 | type BufferPool; 13 | /// Filled buffer type 14 | type Buffer: IoBuf; 15 | 16 | /// Read some bytes from this source with [`Self::BufferPool`] and return 17 | /// a [`Self::Buffer`]. 18 | /// 19 | /// If `len` == 0, will use [`Self::BufferPool`] inner buffer size as the 20 | /// max len, if `len` > 0, `min(len, inner buffer size)` will be the 21 | /// read max len 22 | async fn read_managed( 23 | &mut self, 24 | buffer_pool: &Self::BufferPool, 25 | len: usize, 26 | ) -> IoResult; 27 | } 28 | 29 | /// # AsyncReadAtManaged 30 | /// 31 | /// Async read with buffer pool and position 32 | pub trait AsyncReadManagedAt { 33 | /// Buffer pool type 34 | type BufferPool; 35 | /// Filled buffer type 36 | type Buffer: IoBuf; 37 | 38 | /// Read some bytes from this source at position with [`Self::BufferPool`] 39 | /// and return a [`Self::Buffer`]. 40 | /// 41 | /// If `len` == 0, will use [`Self::BufferPool`] inner buffer size as the 42 | /// max len, if `len` > 0, `min(len, inner buffer size)` will be the 43 | /// read max len 44 | async fn read_managed_at( 45 | &self, 46 | pos: u64, 47 | buffer_pool: &Self::BufferPool, 48 | len: usize, 49 | ) -> IoResult; 50 | } 51 | 52 | impl AsyncReadManaged for Cursor { 53 | type Buffer = A::Buffer; 54 | type BufferPool = A::BufferPool; 55 | 56 | async fn read_managed( 57 | &mut self, 58 | buffer_pool: &Self::BufferPool, 59 | len: usize, 60 | ) -> IoResult { 61 | let pos = self.position(); 62 | let buf = self 63 | .get_ref() 64 | .read_managed_at(pos, buffer_pool, len) 65 | .await?; 66 | self.set_position(pos + buf.buf_len() as u64); 67 | Ok(buf) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /compio-io/src/util/internal.rs: -------------------------------------------------------------------------------- 1 | use std::mem::MaybeUninit; 2 | 3 | use compio_buf::IoBufMut; 4 | 5 | #[inline] 6 | pub(crate) fn slice_to_uninit(src: &[u8], dst: &mut [MaybeUninit]) -> usize { 7 | let len = src.len().min(dst.len()); 8 | unsafe { 9 | std::ptr::copy_nonoverlapping(src.as_ptr() as _, dst.as_mut_ptr(), len); 10 | } 11 | len 12 | } 13 | 14 | /// Copy the contents of a slice into a buffer implementing [`IoBufMut`]. 15 | #[inline] 16 | pub(crate) fn slice_to_buf(src: &[u8], buf: &mut B) -> usize { 17 | let len = slice_to_uninit(src, buf.as_mut_slice()); 18 | unsafe { buf.set_buf_init(len) }; 19 | 20 | len 21 | } 22 | 23 | pub(crate) const DEFAULT_BUF_SIZE: usize = 8 * 1024; 24 | pub(crate) const MISSING_BUF: &str = "The buffer was submitted for io and never returned"; 25 | -------------------------------------------------------------------------------- /compio-io/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | //! IO related utilities functions for ease of use. 2 | 3 | mod take; 4 | pub use take::Take; 5 | 6 | mod null; 7 | pub use null::{Null, null}; 8 | 9 | mod repeat; 10 | pub use repeat::{Repeat, repeat}; 11 | 12 | mod internal; 13 | pub(crate) use internal::*; 14 | 15 | mod split; 16 | pub use split::{ReadHalf, WriteHalf, split}; 17 | 18 | use crate::{AsyncRead, AsyncWrite, AsyncWriteExt, IoResult}; 19 | 20 | /// Asynchronously copies the entire contents of a reader into a writer. 21 | /// 22 | /// This function returns a future that will continuously read data from 23 | /// `reader` and then write it into `writer` in a streaming fashion until 24 | /// `reader` returns EOF or fails. 25 | /// 26 | /// On success, the total number of bytes that were copied from `reader` to 27 | /// `writer` is returned. 28 | /// 29 | /// This is an asynchronous version of [`std::io::copy`][std]. 30 | /// 31 | /// A heap-allocated copy buffer with 8 KB is created to take data from the 32 | /// reader to the writer. 33 | pub async fn copy(reader: &mut R, writer: &mut W) -> IoResult { 34 | let mut buf = Vec::with_capacity(DEFAULT_BUF_SIZE); 35 | let mut total = 0u64; 36 | 37 | loop { 38 | let res; 39 | (res, buf) = reader.read(buf).await.into(); 40 | match res { 41 | Ok(0) => break, 42 | Ok(read) => { 43 | total += read as u64; 44 | } 45 | Err(e) if e.kind() == std::io::ErrorKind::Interrupted => { 46 | continue; 47 | } 48 | Err(e) => return Err(e), 49 | } 50 | let res; 51 | (res, buf) = writer.write_all(buf).await.into(); 52 | res?; 53 | buf.clear(); 54 | } 55 | 56 | Ok(total) 57 | } 58 | -------------------------------------------------------------------------------- /compio-io/src/util/null.rs: -------------------------------------------------------------------------------- 1 | use compio_buf::{BufResult, IoBufMut}; 2 | 3 | use crate::{AsyncBufRead, AsyncRead, AsyncWrite, IoResult}; 4 | 5 | /// An empty reader and writer constructed via [`null`]. 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 7 | pub struct Null { 8 | _p: (), 9 | } 10 | 11 | impl AsyncRead for Null { 12 | async fn read(&mut self, buf: B) -> compio_buf::BufResult { 13 | BufResult(Ok(0), buf) 14 | } 15 | } 16 | 17 | impl AsyncBufRead for Null { 18 | async fn fill_buf(&mut self) -> IoResult<&'_ [u8]> { 19 | Ok(&[]) 20 | } 21 | 22 | fn consume(&mut self, _: usize) {} 23 | } 24 | 25 | impl AsyncWrite for Null { 26 | async fn write(&mut self, buf: T) -> BufResult { 27 | BufResult(Ok(0), buf) 28 | } 29 | 30 | async fn write_vectored( 31 | &mut self, 32 | buf: T, 33 | ) -> BufResult { 34 | BufResult(Ok(0), buf) 35 | } 36 | 37 | async fn flush(&mut self) -> IoResult<()> { 38 | Ok(()) 39 | } 40 | 41 | async fn shutdown(&mut self) -> IoResult<()> { 42 | Ok(()) 43 | } 44 | } 45 | 46 | /// Create a new [`Null`] reader and writer which acts like a black hole. 47 | /// 48 | /// All reads from and writes to this reader will return 49 | /// [`BufResult(Ok(0), buf)`] and leave the buffer unchanged. 50 | /// 51 | /// # Examples 52 | /// 53 | /// ``` 54 | /// use compio_io::{AsyncRead, AsyncWrite, null}; 55 | /// 56 | /// # compio_runtime::Runtime::new().unwrap().block_on(async { 57 | /// let mut buf = Vec::with_capacity(10); 58 | /// let mut null = null(); 59 | /// 60 | /// let (num_read, buf) = null.read(buf).await.unwrap(); 61 | /// 62 | /// assert_eq!(num_read, 0); 63 | /// assert!(buf.is_empty()); 64 | /// 65 | /// let (num_written, buf) = null.write(buf).await.unwrap(); 66 | /// assert_eq!(num_written, 0); 67 | /// # }) 68 | /// ``` 69 | /// 70 | /// [`BufResult(Ok(0), buf)`]: compio_buf::BufResult 71 | #[inline(always)] 72 | pub fn null() -> Null { 73 | Null { _p: () } 74 | } 75 | -------------------------------------------------------------------------------- /compio-io/src/util/repeat.rs: -------------------------------------------------------------------------------- 1 | use std::mem::MaybeUninit; 2 | 3 | use compio_buf::BufResult; 4 | 5 | use crate::{AsyncBufRead, AsyncRead, IoResult}; 6 | 7 | /// A reader that infinitely repeats one byte constructed via [`repeat`]. 8 | /// 9 | /// All reads from this reader will succeed by filling the specified buffer with 10 | /// the given byte. 11 | /// 12 | /// # Examples 13 | /// 14 | /// ```rust 15 | /// # compio_runtime::Runtime::new().unwrap().block_on(async { 16 | /// use compio_io::{self, AsyncRead, AsyncReadExt}; 17 | /// 18 | /// let (len, buffer) = compio_io::repeat(42) 19 | /// .read(Vec::with_capacity(3)) 20 | /// .await 21 | /// .unwrap(); 22 | /// 23 | /// assert_eq!(buffer.as_slice(), [42, 42, 42]); 24 | /// assert_eq!(len, 3); 25 | /// # }) 26 | /// ``` 27 | pub struct Repeat(u8); 28 | 29 | impl AsyncRead for Repeat { 30 | async fn read( 31 | &mut self, 32 | mut buf: B, 33 | ) -> compio_buf::BufResult { 34 | let slice = buf.as_mut_slice(); 35 | 36 | let len = slice.len(); 37 | slice.fill(MaybeUninit::new(self.0)); 38 | unsafe { buf.set_buf_init(len) }; 39 | 40 | BufResult(Ok(len), buf) 41 | } 42 | } 43 | 44 | impl AsyncBufRead for Repeat { 45 | async fn fill_buf(&mut self) -> IoResult<&'_ [u8]> { 46 | Ok(std::slice::from_ref(&self.0)) 47 | } 48 | 49 | fn consume(&mut self, _: usize) {} 50 | } 51 | 52 | /// Creates a reader that infinitely repeats one byte. 53 | /// 54 | /// All reads from this reader will succeed by filling the specified buffer with 55 | /// the given byte. 56 | /// 57 | /// # Examples 58 | /// 59 | /// ```rust 60 | /// # compio_runtime::Runtime::new().unwrap().block_on(async { 61 | /// use compio_io::{self, AsyncRead, AsyncReadExt}; 62 | /// 63 | /// let ((), buffer) = compio_io::repeat(42) 64 | /// .read_exact(Vec::with_capacity(3)) 65 | /// .await 66 | /// .unwrap(); 67 | /// 68 | /// assert_eq!(buffer.as_slice(), [42, 42, 42]); 69 | /// # }) 70 | /// ``` 71 | pub fn repeat(byte: u8) -> Repeat { 72 | Repeat(byte) 73 | } 74 | -------------------------------------------------------------------------------- /compio-io/src/util/split.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use compio_buf::{BufResult, IoBuf, IoBufMut, IoVectoredBuf, IoVectoredBufMut}; 4 | use futures_util::lock::Mutex; 5 | 6 | use crate::{AsyncRead, AsyncWrite, IoResult}; 7 | 8 | /// Splits a single value implementing `AsyncRead + AsyncWrite` into separate 9 | /// [`AsyncRead`] and [`AsyncWrite`] handles. 10 | pub fn split(stream: T) -> (ReadHalf, WriteHalf) { 11 | let stream = Arc::new(Mutex::new(stream)); 12 | (ReadHalf(stream.clone()), WriteHalf(stream)) 13 | } 14 | 15 | /// The readable half of a value returned from [`split`]. 16 | #[derive(Debug)] 17 | pub struct ReadHalf(Arc>); 18 | 19 | impl ReadHalf { 20 | /// Reunites with a previously split [`WriteHalf`]. 21 | /// 22 | /// # Panics 23 | /// 24 | /// If this [`ReadHalf`] and the given [`WriteHalf`] do not originate from 25 | /// the same [`split`] operation this method will panic. 26 | /// This can be checked ahead of time by comparing the stored pointer 27 | /// of the two halves. 28 | #[track_caller] 29 | pub fn unsplit(self, w: WriteHalf) -> T { 30 | if Arc::ptr_eq(&self.0, &w.0) { 31 | drop(w); 32 | let inner = Arc::try_unwrap(self.0).expect("`Arc::try_unwrap` failed"); 33 | inner.into_inner() 34 | } else { 35 | #[cold] 36 | fn panic_unrelated() -> ! { 37 | panic!("Unrelated `WriteHalf` passed to `ReadHalf::unsplit`.") 38 | } 39 | 40 | panic_unrelated() 41 | } 42 | } 43 | } 44 | 45 | impl AsyncRead for ReadHalf { 46 | async fn read(&mut self, buf: B) -> BufResult { 47 | self.0.lock().await.read(buf).await 48 | } 49 | 50 | async fn read_vectored(&mut self, buf: V) -> BufResult { 51 | self.0.lock().await.read_vectored(buf).await 52 | } 53 | } 54 | 55 | /// The writable half of a value returned from [`split`]. 56 | #[derive(Debug)] 57 | pub struct WriteHalf(Arc>); 58 | 59 | impl AsyncWrite for WriteHalf { 60 | async fn write(&mut self, buf: B) -> BufResult { 61 | self.0.lock().await.write(buf).await 62 | } 63 | 64 | async fn write_vectored(&mut self, buf: B) -> BufResult { 65 | self.0.lock().await.write_vectored(buf).await 66 | } 67 | 68 | async fn flush(&mut self) -> IoResult<()> { 69 | self.0.lock().await.flush().await 70 | } 71 | 72 | async fn shutdown(&mut self) -> IoResult<()> { 73 | self.0.lock().await.shutdown().await 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /compio-io/src/util/take.rs: -------------------------------------------------------------------------------- 1 | use compio_buf::{BufResult, IntoInner, IoBufMut, buf_try}; 2 | 3 | use crate::{AsyncBufRead, AsyncRead, IoResult}; 4 | 5 | /// Read up to a limit number of bytes from reader. 6 | #[derive(Debug)] 7 | pub struct Take { 8 | reader: R, 9 | limit: u64, 10 | } 11 | 12 | impl Take { 13 | pub(crate) fn new(reader: T, limit: u64) -> Self { 14 | Self { reader, limit } 15 | } 16 | 17 | /// Returns the number of bytes that can be read before this instance will 18 | /// return EOF. 19 | /// 20 | /// # Note 21 | /// 22 | /// This instance may reach `EOF` after reading fewer bytes than indicated 23 | /// by this method if the underlying [`AsyncRead`] instance reaches EOF. 24 | pub fn limit(&self) -> u64 { 25 | self.limit 26 | } 27 | 28 | /// Sets the number of bytes that can be read before this instance will 29 | /// return EOF. This is the same as constructing a new `Take` instance, so 30 | /// the amount of bytes read and the previous limit value don't matter when 31 | /// calling this method. 32 | pub fn set_limit(&mut self, limit: u64) { 33 | self.limit = limit; 34 | } 35 | 36 | /// Consumes the `Take`, returning the wrapped reader. 37 | pub fn into_inner(self) -> T { 38 | self.reader 39 | } 40 | 41 | /// Gets a reference to the underlying reader. 42 | pub fn get_ref(&self) -> &T { 43 | &self.reader 44 | } 45 | 46 | /// Gets a mutable reference to the underlying reader. 47 | /// 48 | /// Care should be taken to avoid modifying the internal I/O state of the 49 | /// underlying reader as doing so may corrupt the internal limit of this 50 | /// `Take`. 51 | pub fn get_mut(&mut self) -> &mut T { 52 | &mut self.reader 53 | } 54 | } 55 | 56 | impl AsyncRead for Take { 57 | async fn read(&mut self, buf: B) -> BufResult { 58 | if self.limit == 0 { 59 | return BufResult(Ok(0), buf); 60 | } 61 | 62 | let max = self.limit.min(buf.buf_capacity() as u64) as usize; 63 | let buf = buf.slice(..max); 64 | 65 | let (n, buf) = buf_try!(self.reader.read(buf).await.into_inner()); 66 | assert!(n as u64 <= self.limit, "number of read bytes exceeds limit"); 67 | self.limit -= n as u64; 68 | 69 | BufResult(Ok(n), buf) 70 | } 71 | } 72 | 73 | impl AsyncBufRead for Take { 74 | async fn fill_buf(&mut self) -> IoResult<&'_ [u8]> { 75 | if self.limit == 0 { 76 | return Ok(&[]); 77 | } 78 | 79 | let buf = self.reader.fill_buf().await?; 80 | let cap = self.limit.min(buf.len() as u64) as usize; 81 | Ok(&buf[..cap]) 82 | } 83 | 84 | fn consume(&mut self, amount: usize) { 85 | // Don't let callers reset the limit by passing an overlarge value 86 | let amount = self.limit.min(amount as u64) as usize; 87 | self.limit -= amount as u64; 88 | self.reader.consume(amount); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /compio-io/tests/compat.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use compio_io::compat::AsyncStream; 4 | use futures_executor::block_on; 5 | use futures_util::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}; 6 | 7 | #[test] 8 | fn async_compat_read() { 9 | block_on(async { 10 | let src = &[1u8, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0][..]; 11 | let mut stream = AsyncStream::new(src); 12 | 13 | let mut buf = [0; 6]; 14 | let len = stream.read(&mut buf).await.unwrap(); 15 | 16 | assert_eq!(len, 6); 17 | assert_eq!(buf, [1, 1, 4, 5, 1, 4]); 18 | 19 | let mut buf = [0; 20]; 20 | let len = stream.read(&mut buf).await.unwrap(); 21 | assert_eq!(len, 7); 22 | assert_eq!(&buf[..7], [1, 9, 1, 9, 8, 1, 0]); 23 | }) 24 | } 25 | 26 | #[test] 27 | fn async_compat_bufread() { 28 | block_on(async { 29 | let src = &[1u8, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0][..]; 30 | let mut stream = AsyncStream::new(src); 31 | 32 | let slice = stream.fill_buf().await.unwrap(); 33 | assert_eq!(slice, [1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0]); 34 | stream.consume_unpin(6); 35 | 36 | let mut buf = [0; 7]; 37 | let len = stream.read(&mut buf).await.unwrap(); 38 | 39 | assert_eq!(len, 7); 40 | assert_eq!(buf, [1, 9, 1, 9, 8, 1, 0]); 41 | }) 42 | } 43 | 44 | #[test] 45 | fn async_compat_write() { 46 | block_on(async { 47 | let dst = Cursor::new([0u8; 10]); 48 | let mut stream = AsyncStream::new(dst); 49 | 50 | let len = stream.write(&[1, 1, 4, 5, 1, 4]).await.unwrap(); 51 | stream.flush().await.unwrap(); 52 | 53 | assert_eq!(len, 6); 54 | assert_eq!(stream.get_ref().position(), 6); 55 | assert_eq!(stream.get_ref().get_ref(), &[1, 1, 4, 5, 1, 4, 0, 0, 0, 0]); 56 | 57 | let dst = Cursor::new([0u8; 10]); 58 | let mut stream = AsyncStream::with_capacity(10, dst); 59 | let len = stream 60 | .write(&[1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0]) 61 | .await 62 | .unwrap(); 63 | assert_eq!(len, 10); 64 | 65 | stream.flush().await.unwrap(); 66 | assert_eq!(stream.get_ref().get_ref(), &[1, 1, 4, 5, 1, 4, 1, 9, 1, 9]); 67 | }) 68 | } 69 | 70 | #[test] 71 | fn async_compat_flush_fail() { 72 | block_on(async { 73 | let dst = Cursor::new([0u8; 10]); 74 | let mut stream = AsyncStream::new(dst); 75 | let len = stream 76 | .write(&[1, 1, 4, 5, 1, 4, 1, 9, 1, 9, 8, 1, 0]) 77 | .await 78 | .unwrap(); 79 | assert_eq!(len, 13); 80 | let err = stream.flush().await.unwrap_err(); 81 | assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof); 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /compio-log/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-log" 3 | version = "0.1.0" 4 | description = "Log of compio" 5 | categories = ["asynchronous"] 6 | edition = { workspace = true } 7 | authors = { workspace = true } 8 | readme = { workspace = true } 9 | license = { workspace = true } 10 | repository = { workspace = true } 11 | 12 | [dependencies] 13 | tracing = { version = "0.1", default-features = false } 14 | 15 | [dev-dependencies] 16 | tracing-subscriber = { workspace = true } 17 | 18 | [features] 19 | enable_log = [] 20 | -------------------------------------------------------------------------------- /compio-log/src/dummy.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! debug { 3 | ($($args:tt)*) => {}; 4 | } 5 | 6 | #[macro_export] 7 | macro_rules! debug_span { 8 | ($($args:tt)*) => { 9 | $crate::Span::none() 10 | }; 11 | } 12 | 13 | #[macro_export] 14 | macro_rules! error { 15 | ($($args:tt)*) => {}; 16 | } 17 | 18 | #[macro_export] 19 | macro_rules! error_span { 20 | ($($args:tt)*) => { 21 | $crate::Span::none() 22 | }; 23 | } 24 | 25 | #[macro_export] 26 | macro_rules! event { 27 | ($($args:tt)*) => {}; 28 | } 29 | 30 | #[macro_export] 31 | macro_rules! info { 32 | ($($args:tt)*) => {}; 33 | } 34 | 35 | #[macro_export] 36 | macro_rules! info_span { 37 | ($($args:tt)*) => { 38 | $crate::Span::none() 39 | }; 40 | } 41 | 42 | #[macro_export] 43 | macro_rules! span { 44 | ($($args:tt)*) => { 45 | $crate::Span::none() 46 | }; 47 | } 48 | 49 | #[macro_export] 50 | macro_rules! trace { 51 | ($($args:tt)*) => {}; 52 | } 53 | 54 | #[macro_export] 55 | macro_rules! trace_span { 56 | ($($args:tt)*) => { 57 | $crate::Span::none() 58 | }; 59 | } 60 | 61 | #[macro_export] 62 | macro_rules! warn { 63 | ($($args:tt)*) => {}; 64 | } 65 | 66 | #[macro_export] 67 | macro_rules! warn_span { 68 | ($($args:tt)*) => { 69 | $crate::Span::none() 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /compio-log/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg_attr(not(feature = "enable_log"), doc(hidden))] 2 | pub use tracing::*; 3 | 4 | #[cfg(not(feature = "enable_log"))] 5 | pub mod dummy; 6 | 7 | #[cfg(feature = "enable_log")] 8 | #[macro_export] 9 | macro_rules! instrument { 10 | ($lvl:expr, $name:expr, $($fields:tt)*) => { 11 | let _guard = $crate::span!(target:module_path!(), $lvl, $name, $($fields)*).entered(); 12 | }; 13 | ($lvl:expr, $name:expr) => { 14 | let _guard = $crate::span!(target:module_path!(), $lvl, $name).entered(); 15 | }; 16 | } 17 | 18 | #[cfg(not(feature = "enable_log"))] 19 | #[macro_export] 20 | macro_rules! instrument { 21 | ($lvl:expr, $name:expr, $($fields:tt)*) => {}; 22 | ($lvl:expr, $name:expr) => {}; 23 | } 24 | -------------------------------------------------------------------------------- /compio-log/tests/test.rs: -------------------------------------------------------------------------------- 1 | use compio_log::Level; 2 | 3 | #[test] 4 | fn test_log() { 5 | tracing_subscriber::fmt() 6 | .with_max_level(Level::TRACE) 7 | .init(); 8 | 9 | compio_log::debug!("debug"); 10 | compio_log::error!("error"); 11 | compio_log::event!(Level::DEBUG, "event"); 12 | compio_log::info!("info"); 13 | compio_log::warn!("warn"); 14 | compio_log::trace!("trace"); 15 | } 16 | -------------------------------------------------------------------------------- /compio-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-macros" 3 | version = "0.1.2" 4 | description = "Proc macro of compio" 5 | categories = ["asynchronous"] 6 | edition = { workspace = true } 7 | authors = { workspace = true } 8 | readme = { workspace = true } 9 | license = { workspace = true } 10 | repository = { workspace = true } 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | proc-macro2 = "1.0.69" 17 | quote = "1.0.33" 18 | syn = { version = "2.0.38", features = ["full"] } 19 | proc-macro-crate = "3.0.0" 20 | -------------------------------------------------------------------------------- /compio-macros/src/item_fn.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{ 4 | Attribute, Expr, Lit, Meta, Signature, Visibility, parse::Parse, punctuated::Punctuated, 5 | }; 6 | 7 | type AttributeArgs = Punctuated; 8 | 9 | pub(crate) struct RawAttr { 10 | pub inner_attrs: AttributeArgs, 11 | } 12 | 13 | impl Parse for RawAttr { 14 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 15 | let inner_attrs = AttributeArgs::parse_terminated(input)?; 16 | Ok(Self { inner_attrs }) 17 | } 18 | } 19 | 20 | pub(crate) struct RawBodyItemFn { 21 | pub attrs: Vec, 22 | pub args: AttributeArgs, 23 | pub vis: Visibility, 24 | pub sig: Signature, 25 | pub body: TokenStream, 26 | } 27 | 28 | impl RawBodyItemFn { 29 | pub fn new(attrs: Vec, vis: Visibility, sig: Signature, body: TokenStream) -> Self { 30 | Self { 31 | attrs, 32 | args: AttributeArgs::new(), 33 | vis, 34 | sig, 35 | body, 36 | } 37 | } 38 | 39 | pub fn set_args(&mut self, args: AttributeArgs) { 40 | self.args = args; 41 | } 42 | 43 | pub fn crate_name(&self) -> Option { 44 | for attr in &self.args { 45 | if let Meta::NameValue(name) = &attr { 46 | let ident = name 47 | .path 48 | .get_ident() 49 | .map(|ident| ident.to_string().to_lowercase()) 50 | .unwrap_or_default(); 51 | if ident == "crate" { 52 | if let Expr::Lit(lit) = &name.value { 53 | if let Lit::Str(s) = &lit.lit { 54 | let crate_name = s.parse::().unwrap(); 55 | return Some(quote!(#crate_name::runtime)); 56 | } 57 | } 58 | } else { 59 | panic!("Unsupported property {ident}"); 60 | } 61 | } 62 | } 63 | None 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /compio-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod item_fn; 2 | 3 | mod main_fn; 4 | 5 | mod test_fn; 6 | 7 | use proc_macro::TokenStream; 8 | use proc_macro_crate::{FoundCrate, crate_name}; 9 | use proc_macro2::{Ident, Span}; 10 | use quote::{ToTokens, quote}; 11 | use syn::parse_macro_input; 12 | 13 | #[proc_macro_attribute] 14 | pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { 15 | parse_macro_input!(item as main_fn::CompioMain) 16 | .with_args(parse_macro_input!(args as item_fn::RawAttr)) 17 | .into_token_stream() 18 | .into() 19 | } 20 | 21 | #[proc_macro_attribute] 22 | pub fn test(args: TokenStream, item: TokenStream) -> TokenStream { 23 | parse_macro_input!(item as test_fn::CompioTest) 24 | .with_args(parse_macro_input!(args as item_fn::RawAttr)) 25 | .into_token_stream() 26 | .into() 27 | } 28 | 29 | fn retrieve_runtime_mod() -> proc_macro2::TokenStream { 30 | match crate_name("compio-runtime") { 31 | Ok(FoundCrate::Itself) => quote!(crate), 32 | Ok(FoundCrate::Name(name)) => { 33 | let ident = Ident::new(&name, Span::call_site()); 34 | quote!(::#ident) 35 | } 36 | Err(_) => match crate_name("compio") { 37 | Ok(FoundCrate::Itself) => quote!(crate::runtime), 38 | Ok(FoundCrate::Name(name)) => { 39 | let ident = Ident::new(&name, Span::call_site()); 40 | quote!(::#ident::runtime) 41 | } 42 | Err(_) => panic!("Cannot find compio or compio_runtime."), 43 | }, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /compio-macros/src/main_fn.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{ToTokens, TokenStreamExt, quote}; 3 | use syn::{AttrStyle, Attribute, Signature, Visibility, parse::Parse}; 4 | 5 | use crate::{ 6 | item_fn::{RawAttr, RawBodyItemFn}, 7 | retrieve_runtime_mod, 8 | }; 9 | 10 | pub(crate) struct CompioMain(pub RawBodyItemFn); 11 | 12 | impl CompioMain { 13 | pub fn with_args(mut self, args: RawAttr) -> Self { 14 | self.0.set_args(args.inner_attrs); 15 | self 16 | } 17 | } 18 | 19 | impl Parse for CompioMain { 20 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 21 | let attrs = input.call(Attribute::parse_outer)?; 22 | let vis: Visibility = input.parse()?; 23 | let mut sig: Signature = input.parse()?; 24 | let body: TokenStream = input.parse()?; 25 | 26 | if sig.asyncness.is_none() { 27 | return Err(syn::Error::new_spanned( 28 | sig.ident, 29 | "the `async` keyword is missing from the function declaration", 30 | )); 31 | }; 32 | 33 | sig.asyncness.take(); 34 | Ok(Self(RawBodyItemFn::new(attrs, vis, sig, body))) 35 | } 36 | } 37 | 38 | impl ToTokens for CompioMain { 39 | fn to_tokens(&self, tokens: &mut TokenStream) { 40 | tokens.append_all( 41 | self.0 42 | .attrs 43 | .iter() 44 | .filter(|a| matches!(a.style, AttrStyle::Outer)), 45 | ); 46 | self.0.vis.to_tokens(tokens); 47 | self.0.sig.to_tokens(tokens); 48 | let block = &self.0.body; 49 | let runtime_mod = self.0.crate_name().unwrap_or_else(retrieve_runtime_mod); 50 | tokens.append_all(quote!({ 51 | #runtime_mod::Runtime::new().expect("cannot create runtime").block_on(async move #block) 52 | })); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /compio-macros/src/test_fn.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{ToTokens, TokenStreamExt, quote}; 3 | use syn::{AttrStyle, Attribute, Signature, Visibility, parse::Parse}; 4 | 5 | use crate::{ 6 | item_fn::{RawAttr, RawBodyItemFn}, 7 | retrieve_runtime_mod, 8 | }; 9 | 10 | pub(crate) struct CompioTest(pub RawBodyItemFn); 11 | 12 | impl CompioTest { 13 | pub fn with_args(mut self, args: RawAttr) -> Self { 14 | self.0.set_args(args.inner_attrs); 15 | self 16 | } 17 | } 18 | 19 | impl Parse for CompioTest { 20 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 21 | let attrs = input.call(Attribute::parse_outer)?; 22 | let vis: Visibility = input.parse()?; 23 | let mut sig: Signature = input.parse()?; 24 | let body: TokenStream = input.parse()?; 25 | 26 | if sig.asyncness.is_none() { 27 | return Err(syn::Error::new_spanned( 28 | sig.ident, 29 | "the `async` keyword is missing from the function declaration", 30 | )); 31 | }; 32 | sig.asyncness.take(); 33 | Ok(Self(RawBodyItemFn::new(attrs, vis, sig, body))) 34 | } 35 | } 36 | 37 | impl ToTokens for CompioTest { 38 | fn to_tokens(&self, tokens: &mut TokenStream) { 39 | tokens.append_all(quote!(#[test])); 40 | tokens.append_all( 41 | self.0 42 | .attrs 43 | .iter() 44 | .filter(|a| matches!(a.style, AttrStyle::Outer)), 45 | ); 46 | self.0.vis.to_tokens(tokens); 47 | self.0.sig.to_tokens(tokens); 48 | let block = &self.0.body; 49 | let runtime_mod = self.0.crate_name().unwrap_or_else(retrieve_runtime_mod); 50 | tokens.append_all(quote!({ 51 | #runtime_mod::Runtime::new().expect("cannot create runtime").block_on(async move #block) 52 | })); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /compio-net/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-net" 3 | version = "0.7.0" 4 | description = "Networking IO for compio" 5 | categories = ["asynchronous", "network-programming"] 6 | keywords = ["async", "net"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | # Workspace dependencies 19 | compio-buf = { workspace = true } 20 | compio-driver = { workspace = true } 21 | compio-io = { workspace = true } 22 | compio-runtime = { workspace = true, features = ["event"] } 23 | 24 | cfg-if = { workspace = true } 25 | either = "1.9.0" 26 | once_cell = { workspace = true } 27 | socket2 = { workspace = true } 28 | 29 | [target.'cfg(windows)'.dependencies] 30 | widestring = { workspace = true } 31 | windows-sys = { workspace = true, features = [ 32 | "Win32_Foundation", 33 | "Win32_Networking_WinSock", 34 | "Win32_System_IO", 35 | ] } 36 | 37 | [target.'cfg(unix)'.dependencies] 38 | libc = { workspace = true } 39 | 40 | # Shared dev dependencies for all platforms 41 | [dev-dependencies] 42 | compio-macros = { workspace = true } 43 | futures-util = { workspace = true } 44 | tempfile = { workspace = true } 45 | 46 | [features] 47 | io-uring = ["compio-runtime/io-uring"] 48 | polling = ["compio-runtime/polling"] 49 | 50 | # Nightly features 51 | once_cell_try = [] 52 | nightly = ["once_cell_try"] 53 | -------------------------------------------------------------------------------- /compio-net/src/cmsg/mod.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | cfg_if::cfg_if! { 4 | if #[cfg(windows)] { 5 | #[path = "windows.rs"] 6 | mod sys; 7 | } else if #[cfg(unix)] { 8 | #[path = "unix.rs"] 9 | mod sys; 10 | } 11 | } 12 | 13 | /// Reference to a control message. 14 | pub struct CMsgRef<'a>(sys::CMsgRef<'a>); 15 | 16 | impl CMsgRef<'_> { 17 | /// Returns the level of the control message. 18 | pub fn level(&self) -> i32 { 19 | self.0.level() 20 | } 21 | 22 | /// Returns the type of the control message. 23 | pub fn ty(&self) -> i32 { 24 | self.0.ty() 25 | } 26 | 27 | /// Returns the length of the control message. 28 | #[allow(clippy::len_without_is_empty)] 29 | pub fn len(&self) -> usize { 30 | self.0.len() as _ 31 | } 32 | 33 | /// Returns a reference to the data of the control message. 34 | /// 35 | /// # Safety 36 | /// 37 | /// The data part must be properly aligned and contains an initialized 38 | /// instance of `T`. 39 | pub unsafe fn data(&self) -> &T { 40 | self.0.data() 41 | } 42 | } 43 | 44 | /// An iterator for control messages. 45 | pub struct CMsgIter<'a> { 46 | inner: sys::CMsgIter, 47 | _p: PhantomData<&'a ()>, 48 | } 49 | 50 | impl<'a> CMsgIter<'a> { 51 | /// Create [`CMsgIter`] with the given buffer. 52 | /// 53 | /// # Panics 54 | /// 55 | /// This function will panic if the buffer is too short or not properly 56 | /// aligned. 57 | /// 58 | /// # Safety 59 | /// 60 | /// The buffer should contain valid control messages. 61 | pub unsafe fn new(buffer: &'a [u8]) -> Self { 62 | Self { 63 | inner: sys::CMsgIter::new(buffer.as_ptr(), buffer.len()), 64 | _p: PhantomData, 65 | } 66 | } 67 | } 68 | 69 | impl<'a> Iterator for CMsgIter<'a> { 70 | type Item = CMsgRef<'a>; 71 | 72 | fn next(&mut self) -> Option { 73 | unsafe { 74 | let cmsg = self.inner.current(); 75 | self.inner.next(); 76 | cmsg.map(CMsgRef) 77 | } 78 | } 79 | } 80 | 81 | /// Helper to construct control message. 82 | pub struct CMsgBuilder<'a> { 83 | inner: sys::CMsgIter, 84 | len: usize, 85 | _p: PhantomData<&'a mut ()>, 86 | } 87 | 88 | impl<'a> CMsgBuilder<'a> { 89 | /// Create [`CMsgBuilder`] with the given buffer. The buffer will be zeroed 90 | /// on creation. 91 | /// 92 | /// # Panics 93 | /// 94 | /// This function will panic if the buffer is too short or not properly 95 | /// aligned. 96 | pub fn new(buffer: &'a mut [u8]) -> Self { 97 | buffer.fill(0); 98 | Self { 99 | inner: sys::CMsgIter::new(buffer.as_ptr(), buffer.len()), 100 | len: 0, 101 | _p: PhantomData, 102 | } 103 | } 104 | 105 | /// Finishes building, returns length of the control message. 106 | pub fn finish(self) -> usize { 107 | self.len 108 | } 109 | 110 | /// Try to append a control message entry into the buffer. If the buffer 111 | /// does not have enough space or is not properly aligned with the value 112 | /// type, returns `None`. 113 | pub fn try_push(&mut self, level: i32, ty: i32, value: T) -> Option<()> { 114 | if !self.inner.is_aligned::() || !self.inner.is_space_enough::() { 115 | return None; 116 | } 117 | 118 | // SAFETY: the buffer is zeroed and the pointer is valid and aligned 119 | unsafe { 120 | let mut cmsg = self.inner.current_mut()?; 121 | cmsg.set_level(level); 122 | cmsg.set_ty(ty); 123 | self.len += cmsg.set_data(value); 124 | 125 | self.inner.next(); 126 | } 127 | 128 | Some(()) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /compio-net/src/cmsg/unix.rs: -------------------------------------------------------------------------------- 1 | use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_NXTHDR, CMSG_SPACE, c_int, cmsghdr, msghdr}; 2 | 3 | pub(crate) struct CMsgRef<'a>(&'a cmsghdr); 4 | 5 | impl CMsgRef<'_> { 6 | pub(crate) fn level(&self) -> c_int { 7 | self.0.cmsg_level 8 | } 9 | 10 | pub(crate) fn ty(&self) -> c_int { 11 | self.0.cmsg_type 12 | } 13 | 14 | pub(crate) fn len(&self) -> usize { 15 | self.0.cmsg_len as _ 16 | } 17 | 18 | pub(crate) unsafe fn data(&self) -> &T { 19 | let data_ptr = CMSG_DATA(self.0); 20 | data_ptr.cast::().as_ref().unwrap() 21 | } 22 | } 23 | 24 | pub(crate) struct CMsgMut<'a>(&'a mut cmsghdr); 25 | 26 | impl CMsgMut<'_> { 27 | pub(crate) fn set_level(&mut self, level: c_int) { 28 | self.0.cmsg_level = level; 29 | } 30 | 31 | pub(crate) fn set_ty(&mut self, ty: c_int) { 32 | self.0.cmsg_type = ty; 33 | } 34 | 35 | pub(crate) unsafe fn set_data(&mut self, data: T) -> usize { 36 | self.0.cmsg_len = CMSG_LEN(std::mem::size_of::() as _) as _; 37 | let data_ptr = CMSG_DATA(self.0); 38 | std::ptr::write(data_ptr.cast::(), data); 39 | CMSG_SPACE(std::mem::size_of::() as _) as _ 40 | } 41 | } 42 | 43 | pub(crate) struct CMsgIter { 44 | msg: msghdr, 45 | cmsg: *mut cmsghdr, 46 | } 47 | 48 | impl CMsgIter { 49 | pub(crate) fn new(ptr: *const u8, len: usize) -> Self { 50 | assert!(len >= unsafe { CMSG_SPACE(0) as _ }, "buffer too short"); 51 | assert!(ptr.cast::().is_aligned(), "misaligned buffer"); 52 | 53 | let mut msg: msghdr = unsafe { std::mem::zeroed() }; 54 | msg.msg_control = ptr as _; 55 | msg.msg_controllen = len as _; 56 | // SAFETY: msg is initialized and valid 57 | let cmsg = unsafe { CMSG_FIRSTHDR(&msg) }; 58 | Self { msg, cmsg } 59 | } 60 | 61 | pub(crate) unsafe fn current<'a>(&self) -> Option> { 62 | self.cmsg.as_ref().map(CMsgRef) 63 | } 64 | 65 | pub(crate) unsafe fn next(&mut self) { 66 | if !self.cmsg.is_null() { 67 | self.cmsg = CMSG_NXTHDR(&self.msg, self.cmsg); 68 | } 69 | } 70 | 71 | pub(crate) unsafe fn current_mut<'a>(&self) -> Option> { 72 | self.cmsg.as_mut().map(CMsgMut) 73 | } 74 | 75 | pub(crate) fn is_aligned(&self) -> bool { 76 | self.msg.msg_control.cast::().is_aligned() 77 | } 78 | 79 | pub(crate) fn is_space_enough(&self) -> bool { 80 | if !self.cmsg.is_null() { 81 | let space = unsafe { CMSG_SPACE(std::mem::size_of::() as _) as usize }; 82 | #[allow(clippy::unnecessary_cast)] 83 | let max = self.msg.msg_control as usize + self.msg.msg_controllen as usize; 84 | self.cmsg as usize + space <= max 85 | } else { 86 | false 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /compio-net/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Network related. 2 | //! 3 | //! Currently, TCP/UDP/Unix socket are implemented. 4 | 5 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 6 | #![cfg_attr(feature = "once_cell_try", feature(once_cell_try))] 7 | #![warn(missing_docs)] 8 | 9 | mod cmsg; 10 | mod poll_fd; 11 | mod resolve; 12 | mod socket; 13 | pub(crate) mod split; 14 | mod tcp; 15 | mod udp; 16 | mod unix; 17 | 18 | pub use cmsg::*; 19 | pub use poll_fd::*; 20 | pub use resolve::ToSocketAddrsAsync; 21 | pub(crate) use resolve::{each_addr, first_addr_buf}; 22 | pub(crate) use socket::*; 23 | pub use split::*; 24 | pub use tcp::*; 25 | pub use udp::*; 26 | pub use unix::*; 27 | -------------------------------------------------------------------------------- /compio-net/src/poll_fd/mod.rs: -------------------------------------------------------------------------------- 1 | cfg_if::cfg_if! { 2 | if #[cfg(windows)] { 3 | #[path = "windows.rs"] 4 | mod sys; 5 | } else if #[cfg(unix)] { 6 | #[path = "unix.rs"] 7 | mod sys; 8 | } 9 | } 10 | 11 | #[cfg(windows)] 12 | use std::os::windows::io::{AsRawSocket, RawSocket}; 13 | use std::{io, ops::Deref}; 14 | 15 | use compio_buf::IntoInner; 16 | use compio_driver::{AsRawFd, RawFd, SharedFd, ToSharedFd}; 17 | 18 | /// A wrapper for socket, providing functionalities to wait for readiness. 19 | #[derive(Debug)] 20 | pub struct PollFd(sys::PollFd); 21 | 22 | impl PollFd { 23 | /// Create [`PollFd`] without attaching the source. Ready-based sources need 24 | /// not to be attached. 25 | pub fn new(source: T) -> io::Result { 26 | Self::from_shared_fd(SharedFd::new(source)) 27 | } 28 | 29 | pub(crate) fn from_shared_fd(inner: SharedFd) -> io::Result { 30 | Ok(Self(sys::PollFd::new(inner)?)) 31 | } 32 | } 33 | 34 | impl PollFd { 35 | /// Wait for accept readiness, before calling `accept`, or after `accept` 36 | /// returns `WouldBlock`. 37 | pub async fn accept_ready(&self) -> io::Result<()> { 38 | self.0.accept_ready().await 39 | } 40 | 41 | /// Wait for connect readiness. 42 | pub async fn connect_ready(&self) -> io::Result<()> { 43 | self.0.connect_ready().await 44 | } 45 | 46 | /// Wait for read readiness. 47 | pub async fn read_ready(&self) -> io::Result<()> { 48 | self.0.read_ready().await 49 | } 50 | 51 | /// Wait for write readiness. 52 | pub async fn write_ready(&self) -> io::Result<()> { 53 | self.0.write_ready().await 54 | } 55 | } 56 | 57 | impl IntoInner for PollFd { 58 | type Inner = SharedFd; 59 | 60 | fn into_inner(self) -> Self::Inner { 61 | self.0.into_inner() 62 | } 63 | } 64 | 65 | impl ToSharedFd for PollFd { 66 | fn to_shared_fd(&self) -> SharedFd { 67 | self.0.to_shared_fd() 68 | } 69 | } 70 | 71 | impl AsRawFd for PollFd { 72 | fn as_raw_fd(&self) -> RawFd { 73 | self.0.as_raw_fd() 74 | } 75 | } 76 | 77 | #[cfg(windows)] 78 | impl AsRawSocket for PollFd { 79 | fn as_raw_socket(&self) -> RawSocket { 80 | self.0.as_raw_socket() 81 | } 82 | } 83 | 84 | impl Deref for PollFd { 85 | type Target = T; 86 | 87 | fn deref(&self) -> &Self::Target { 88 | &self.0 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /compio-net/src/poll_fd/unix.rs: -------------------------------------------------------------------------------- 1 | use std::{io, ops::Deref}; 2 | 3 | use compio_buf::{BufResult, IntoInner}; 4 | use compio_driver::{ 5 | AsRawFd, RawFd, SharedFd, ToSharedFd, 6 | op::{Interest, PollOnce}, 7 | }; 8 | 9 | #[derive(Debug)] 10 | pub struct PollFd { 11 | inner: SharedFd, 12 | } 13 | 14 | impl PollFd { 15 | pub fn new(inner: SharedFd) -> io::Result { 16 | Ok(Self { inner }) 17 | } 18 | } 19 | 20 | impl PollFd { 21 | pub async fn accept_ready(&self) -> io::Result<()> { 22 | self.read_ready().await 23 | } 24 | 25 | pub async fn connect_ready(&self) -> io::Result<()> { 26 | self.write_ready().await 27 | } 28 | 29 | pub async fn read_ready(&self) -> io::Result<()> { 30 | let op = PollOnce::new(self.to_shared_fd(), Interest::Readable); 31 | let BufResult(res, _) = compio_runtime::submit(op).await; 32 | res?; 33 | Ok(()) 34 | } 35 | 36 | pub async fn write_ready(&self) -> io::Result<()> { 37 | let op = PollOnce::new(self.to_shared_fd(), Interest::Writable); 38 | let BufResult(res, _) = compio_runtime::submit(op).await; 39 | res?; 40 | Ok(()) 41 | } 42 | } 43 | 44 | impl IntoInner for PollFd { 45 | type Inner = SharedFd; 46 | 47 | fn into_inner(self) -> Self::Inner { 48 | self.inner 49 | } 50 | } 51 | 52 | impl ToSharedFd for PollFd { 53 | fn to_shared_fd(&self) -> SharedFd { 54 | self.inner.clone() 55 | } 56 | } 57 | 58 | impl AsRawFd for PollFd { 59 | fn as_raw_fd(&self) -> RawFd { 60 | self.inner.as_raw_fd() 61 | } 62 | } 63 | 64 | impl Deref for PollFd { 65 | type Target = T; 66 | 67 | fn deref(&self) -> &Self::Target { 68 | &self.inner 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /compio-net/src/resolve/unix.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | net::{SocketAddr, ToSocketAddrs}, 4 | panic::resume_unwind, 5 | }; 6 | 7 | pub async fn resolve_sock_addrs( 8 | host: &str, 9 | port: u16, 10 | ) -> io::Result> { 11 | let host = host.to_string(); 12 | compio_runtime::spawn_blocking(move || (host, port).to_socket_addrs()) 13 | .await 14 | .unwrap_or_else(|e| resume_unwind(e)) 15 | } 16 | -------------------------------------------------------------------------------- /compio-net/src/split.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt, io}; 2 | 3 | use compio_buf::{BufResult, IoBuf, IoBufMut, IoVectoredBuf, IoVectoredBufMut}; 4 | use compio_driver::AsRawFd; 5 | use compio_io::{AsyncRead, AsyncWrite}; 6 | 7 | pub(crate) fn split(stream: &T) -> (ReadHalf, WriteHalf) 8 | where 9 | for<'a> &'a T: AsyncRead + AsyncWrite, 10 | { 11 | (ReadHalf(stream), WriteHalf(stream)) 12 | } 13 | 14 | /// Borrowed read half. 15 | #[derive(Debug)] 16 | pub struct ReadHalf<'a, T>(&'a T); 17 | 18 | impl AsyncRead for ReadHalf<'_, T> 19 | where 20 | for<'a> &'a T: AsyncRead, 21 | { 22 | async fn read(&mut self, buf: B) -> BufResult { 23 | self.0.read(buf).await 24 | } 25 | 26 | async fn read_vectored(&mut self, buf: V) -> BufResult { 27 | self.0.read_vectored(buf).await 28 | } 29 | } 30 | 31 | /// Borrowed write half. 32 | #[derive(Debug)] 33 | pub struct WriteHalf<'a, T>(&'a T); 34 | 35 | impl AsyncWrite for WriteHalf<'_, T> 36 | where 37 | for<'a> &'a T: AsyncWrite, 38 | { 39 | async fn write(&mut self, buf: B) -> BufResult { 40 | self.0.write(buf).await 41 | } 42 | 43 | async fn write_vectored(&mut self, buf: B) -> BufResult { 44 | self.0.write_vectored(buf).await 45 | } 46 | 47 | async fn flush(&mut self) -> io::Result<()> { 48 | self.0.flush().await 49 | } 50 | 51 | async fn shutdown(&mut self) -> io::Result<()> { 52 | self.0.shutdown().await 53 | } 54 | } 55 | 56 | pub(crate) fn into_split(stream: T) -> (OwnedReadHalf, OwnedWriteHalf) 57 | where 58 | for<'a> &'a T: AsyncRead + AsyncWrite, 59 | T: Clone, 60 | { 61 | (OwnedReadHalf(stream.clone()), OwnedWriteHalf(stream)) 62 | } 63 | 64 | /// Owned read half. 65 | #[derive(Debug)] 66 | pub struct OwnedReadHalf(T); 67 | 68 | impl OwnedReadHalf { 69 | /// Attempts to put the two halves of a `TcpStream` back together and 70 | /// recover the original socket. Succeeds only if the two halves 71 | /// originated from the same call to `into_split`. 72 | pub fn reunite(self, w: OwnedWriteHalf) -> Result> { 73 | if self.0.as_raw_fd() == w.0.as_raw_fd() { 74 | drop(w); 75 | Ok(self.0) 76 | } else { 77 | Err(ReuniteError(self, w)) 78 | } 79 | } 80 | } 81 | 82 | impl AsyncRead for OwnedReadHalf 83 | where 84 | for<'a> &'a T: AsyncRead, 85 | { 86 | async fn read(&mut self, buf: B) -> BufResult { 87 | (&self.0).read(buf).await 88 | } 89 | 90 | async fn read_vectored(&mut self, buf: V) -> BufResult { 91 | (&self.0).read_vectored(buf).await 92 | } 93 | } 94 | 95 | /// Owned write half. 96 | #[derive(Debug)] 97 | pub struct OwnedWriteHalf(T); 98 | 99 | impl AsyncWrite for OwnedWriteHalf 100 | where 101 | for<'a> &'a T: AsyncWrite, 102 | { 103 | async fn write(&mut self, buf: B) -> BufResult { 104 | (&self.0).write(buf).await 105 | } 106 | 107 | async fn write_vectored(&mut self, buf: B) -> BufResult { 108 | (&self.0).write_vectored(buf).await 109 | } 110 | 111 | async fn flush(&mut self) -> io::Result<()> { 112 | (&self.0).flush().await 113 | } 114 | 115 | async fn shutdown(&mut self) -> io::Result<()> { 116 | (&self.0).shutdown().await 117 | } 118 | } 119 | 120 | /// Error indicating that two halves were not from the same socket, and thus 121 | /// could not be reunited. 122 | #[derive(Debug)] 123 | pub struct ReuniteError(pub OwnedReadHalf, pub OwnedWriteHalf); 124 | 125 | impl fmt::Display for ReuniteError { 126 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 127 | write!( 128 | f, 129 | "tried to reunite halves that are not from the same socket" 130 | ) 131 | } 132 | } 133 | 134 | impl Error for ReuniteError {} 135 | -------------------------------------------------------------------------------- /compio-net/tests/cmsg.rs: -------------------------------------------------------------------------------- 1 | use compio_buf::IoBuf; 2 | use compio_net::{CMsgBuilder, CMsgIter}; 3 | 4 | #[test] 5 | fn test_cmsg() { 6 | let mut buf = [0u8; 64]; 7 | let mut builder = CMsgBuilder::new(&mut buf); 8 | 9 | builder.try_push(0, 0, ()).unwrap(); // 16 / 12 10 | builder.try_push(1, 1, u32::MAX).unwrap(); // 16 + 4 + 4 / 12 + 4 11 | builder.try_push(2, 2, i64::MIN).unwrap(); // 16 + 8 / 12 + 8 12 | let len = builder.finish(); 13 | assert!(len == 64 || len == 48); 14 | 15 | unsafe { 16 | let buf = buf.slice(..len); 17 | let mut iter = CMsgIter::new(&buf); 18 | 19 | let cmsg = iter.next().unwrap(); 20 | assert_eq!((cmsg.level(), cmsg.ty(), cmsg.data::<()>()), (0, 0, &())); 21 | let cmsg = iter.next().unwrap(); 22 | assert_eq!( 23 | (cmsg.level(), cmsg.ty(), cmsg.data::()), 24 | (1, 1, &u32::MAX) 25 | ); 26 | let cmsg = iter.next().unwrap(); 27 | assert_eq!( 28 | (cmsg.level(), cmsg.ty(), cmsg.data::()), 29 | (2, 2, &i64::MIN) 30 | ); 31 | assert!(iter.next().is_none()); 32 | } 33 | } 34 | 35 | #[test] 36 | #[should_panic] 37 | fn invalid_buffer_length() { 38 | let mut buf = [0u8; 1]; 39 | CMsgBuilder::new(&mut buf); 40 | } 41 | 42 | #[test] 43 | #[should_panic] 44 | fn invalid_buffer_alignment() { 45 | let mut buf = [0u8; 64]; 46 | CMsgBuilder::new(&mut buf[1..]); 47 | } 48 | -------------------------------------------------------------------------------- /compio-net/tests/poll.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | net::{Ipv4Addr, SocketAddrV4}, 4 | }; 5 | 6 | use compio_net::PollFd; 7 | use socket2::{Domain, Protocol, SockAddr, Socket, Type}; 8 | 9 | fn is_would_block(e: &io::Error) -> bool { 10 | #[cfg(unix)] 11 | { 12 | e.kind() == io::ErrorKind::WouldBlock || e.raw_os_error() == Some(libc::EINPROGRESS) 13 | } 14 | #[cfg(not(unix))] 15 | { 16 | e.kind() == io::ErrorKind::WouldBlock 17 | } 18 | } 19 | 20 | #[compio_macros::test] 21 | async fn poll_connect() { 22 | let listener = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); 23 | listener.set_nonblocking(true).unwrap(); 24 | listener 25 | .bind(&SockAddr::from(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))) 26 | .unwrap(); 27 | listener.listen(4).unwrap(); 28 | let addr = listener.local_addr().unwrap(); 29 | let listener = PollFd::new(listener).unwrap(); 30 | let accept_task = async { 31 | loop { 32 | listener.accept_ready().await.unwrap(); 33 | match listener.accept() { 34 | Ok(res) => break res, 35 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => continue, 36 | Err(e) => panic!("{e:?}"), 37 | } 38 | } 39 | }; 40 | 41 | let client = Socket::new(Domain::IPV4, Type::STREAM, Some(Protocol::TCP)).unwrap(); 42 | client.set_nonblocking(true).unwrap(); 43 | let client = PollFd::new(client).unwrap(); 44 | let res = client.connect(&addr); 45 | let tx = if let Err(e) = res { 46 | assert!(is_would_block(&e)); 47 | let (tx, _) = accept_task.await; 48 | tx 49 | } else { 50 | let ((tx, _), res) = futures_util::join!(accept_task, client.connect_ready()); 51 | res.unwrap(); 52 | tx 53 | }; 54 | 55 | tx.set_nonblocking(true).unwrap(); 56 | let tx = PollFd::new(tx).unwrap(); 57 | 58 | let send_task = async { 59 | loop { 60 | match tx.send(b"Hello world!") { 61 | Ok(res) => break res, 62 | Err(e) if is_would_block(&e) => {} 63 | Err(e) => panic!("{e:?}"), 64 | } 65 | tx.write_ready().await.unwrap(); 66 | } 67 | }; 68 | 69 | let mut buffer = Vec::with_capacity(12); 70 | let recv_task = async { 71 | loop { 72 | match client.recv(buffer.spare_capacity_mut()) { 73 | Ok(res) => { 74 | unsafe { buffer.set_len(res) }; 75 | break res; 76 | } 77 | Err(e) if is_would_block(&e) => {} 78 | Err(e) => panic!("{e:?}"), 79 | } 80 | client.read_ready().await.unwrap(); 81 | } 82 | }; 83 | 84 | let (write, read) = futures_util::join!(send_task, recv_task); 85 | assert_eq!(write, 12); 86 | assert_eq!(read, 12); 87 | assert_eq!(buffer, b"Hello world!"); 88 | } 89 | -------------------------------------------------------------------------------- /compio-net/tests/split.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{Read, Write}, 3 | panic::resume_unwind, 4 | }; 5 | 6 | use compio_buf::BufResult; 7 | use compio_io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 8 | use compio_net::{TcpStream, UnixListener, UnixStream}; 9 | 10 | #[compio_macros::test] 11 | async fn tcp_split() { 12 | const MSG: &[u8] = b"split"; 13 | 14 | let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); 15 | let addr = listener.local_addr().unwrap(); 16 | 17 | let handle = compio_runtime::spawn_blocking(move || { 18 | let (mut stream, _) = listener.accept().unwrap(); 19 | stream.write_all(MSG).unwrap(); 20 | 21 | let mut read_buf = [0u8; 32]; 22 | let read_len = stream.read(&mut read_buf).unwrap(); 23 | assert_eq!(&read_buf[..read_len], MSG); 24 | }); 25 | 26 | let stream = TcpStream::connect(&addr).await.unwrap(); 27 | let (mut read_half, mut write_half) = stream.into_split(); 28 | 29 | let read_buf = [0u8; 32]; 30 | let (read_res, buf) = read_half.read(read_buf).await.unwrap(); 31 | assert_eq!(read_res, MSG.len()); 32 | assert_eq!(&buf[..MSG.len()], MSG); 33 | 34 | write_half.write_all(MSG).await.unwrap(); 35 | handle.await.unwrap_or_else(|e| resume_unwind(e)); 36 | } 37 | 38 | #[compio_macros::test] 39 | async fn tcp_unsplit() { 40 | let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); 41 | let addr = listener.local_addr().unwrap(); 42 | 43 | let handle = compio_runtime::spawn_blocking(move || { 44 | drop(listener.accept().unwrap()); 45 | drop(listener.accept().unwrap()); 46 | }); 47 | 48 | let stream1 = TcpStream::connect(&addr).await.unwrap(); 49 | let (read1, write1) = stream1.into_split(); 50 | 51 | let stream2 = TcpStream::connect(&addr).await.unwrap(); 52 | let (_, write2) = stream2.into_split(); 53 | 54 | let read1 = match read1.reunite(write2) { 55 | Ok(_) => panic!("Reunite should not succeed"), 56 | Err(err) => err.0, 57 | }; 58 | 59 | read1.reunite(write1).expect("Reunite should succeed"); 60 | 61 | handle.await.unwrap_or_else(|e| resume_unwind(e)); 62 | } 63 | 64 | #[compio_macros::test] 65 | async fn unix_split() { 66 | let dir = tempfile::Builder::new() 67 | .prefix("compio-uds-split-tests") 68 | .tempdir() 69 | .unwrap(); 70 | let sock_path = dir.path().join("connect.sock"); 71 | 72 | let listener = UnixListener::bind(&sock_path).await.unwrap(); 73 | 74 | let (client, (server, _)) = 75 | futures_util::try_join!(UnixStream::connect(&sock_path), listener.accept()).unwrap(); 76 | 77 | let (mut a_read, mut a_write) = server.into_split(); 78 | let (mut b_read, mut b_write) = client.into_split(); 79 | 80 | let (a_response, b_response) = futures_util::future::try_join( 81 | send_recv_all(&mut a_read, &mut a_write, b"A"), 82 | send_recv_all(&mut b_read, &mut b_write, b"B"), 83 | ) 84 | .await 85 | .unwrap(); 86 | 87 | assert_eq!(a_response, b"B"); 88 | assert_eq!(b_response, b"A"); 89 | } 90 | 91 | async fn send_recv_all( 92 | read: &mut R, 93 | write: &mut W, 94 | input: &'static [u8], 95 | ) -> std::io::Result> { 96 | write.write_all(input).await.0?; 97 | write.shutdown().await?; 98 | 99 | let output = Vec::with_capacity(2); 100 | let BufResult(res, buf) = read.read_exact(output).await; 101 | assert_eq!(res.unwrap_err().kind(), std::io::ErrorKind::UnexpectedEof); 102 | Ok(buf) 103 | } 104 | -------------------------------------------------------------------------------- /compio-net/tests/tcp_accept.rs: -------------------------------------------------------------------------------- 1 | use std::panic::resume_unwind; 2 | 3 | use compio_net::{TcpListener, TcpStream, ToSocketAddrsAsync}; 4 | 5 | async fn test_impl(addr: impl ToSocketAddrsAsync) { 6 | let listener = TcpListener::bind(addr).await.unwrap(); 7 | let addr = listener.local_addr().unwrap(); 8 | let task = compio_runtime::spawn(async move { 9 | let (socket, _) = listener.accept().await.unwrap(); 10 | socket 11 | }); 12 | let cli = TcpStream::connect(&addr).await.unwrap(); 13 | let srv = task.await.unwrap_or_else(|e| resume_unwind(e)); 14 | assert_eq!(cli.local_addr().unwrap(), srv.peer_addr().unwrap()); 15 | } 16 | 17 | macro_rules! test_accept { 18 | ($(($ident:ident, $target:expr),)*) => { 19 | $( 20 | #[compio_macros::test] 21 | async fn $ident() { 22 | println!("Testing {}...", stringify!($ident)); 23 | test_impl($target).await; 24 | } 25 | )* 26 | }; 27 | } 28 | 29 | test_accept! { 30 | (ip_str, "127.0.0.1:0"), 31 | (host_str, "localhost:0"), 32 | (socket_addr, "127.0.0.1:0".parse::().unwrap()), 33 | (str_port_tuple, ("127.0.0.1", 0)), 34 | (ip_port_tuple, ("127.0.0.1".parse::().unwrap(), 0)), 35 | } 36 | -------------------------------------------------------------------------------- /compio-net/tests/udp.rs: -------------------------------------------------------------------------------- 1 | use compio_net::UdpSocket; 2 | 3 | #[compio_macros::test] 4 | async fn connect() { 5 | const MSG: &str = "foo bar baz"; 6 | 7 | let passive = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 8 | let passive_addr = passive.local_addr().unwrap(); 9 | 10 | let active = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 11 | let active_addr = active.local_addr().unwrap(); 12 | 13 | active.connect(passive_addr).await.unwrap(); 14 | active.send(MSG).await.0.unwrap(); 15 | 16 | let (_, buffer) = passive.recv(Vec::with_capacity(20)).await.unwrap(); 17 | assert_eq!(MSG.as_bytes(), &buffer); 18 | assert_eq!(active.local_addr().unwrap(), active_addr); 19 | assert_eq!(active.peer_addr().unwrap(), passive_addr); 20 | } 21 | 22 | #[compio_macros::test] 23 | async fn send_to() { 24 | const MSG: &str = "foo bar baz"; 25 | 26 | macro_rules! must_success { 27 | ($r:expr, $expect_addr:expr) => { 28 | let res = $r; 29 | assert_eq!(res.0.unwrap().1, $expect_addr); 30 | assert_eq!(res.1, MSG.as_bytes()); 31 | }; 32 | } 33 | 34 | let passive1 = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 35 | let passive1_addr = passive1.local_addr().unwrap(); 36 | 37 | let passive01 = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); 38 | let passive01_addr = passive01.local_addr().unwrap(); 39 | 40 | let passive2 = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 41 | let passive2_addr = passive2.local_addr().unwrap(); 42 | 43 | let passive3 = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 44 | let passive3_addr = passive3.local_addr().unwrap(); 45 | 46 | let active = UdpSocket::bind("127.0.0.1:0").await.unwrap(); 47 | let active_addr = active.local_addr().unwrap(); 48 | 49 | active.send_to(MSG, &passive01_addr).await.0.unwrap(); 50 | active.send_to(MSG, &passive1_addr).await.0.unwrap(); 51 | active.send_to(MSG, &passive2_addr).await.0.unwrap(); 52 | active.send_to(MSG, &passive3_addr).await.0.unwrap(); 53 | 54 | must_success!( 55 | passive1.recv_from(Vec::with_capacity(20)).await, 56 | active_addr 57 | ); 58 | must_success!( 59 | passive2.recv_from(Vec::with_capacity(20)).await, 60 | active_addr 61 | ); 62 | must_success!( 63 | passive3.recv_from(Vec::with_capacity(20)).await, 64 | active_addr 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /compio-net/tests/unix_stream.rs: -------------------------------------------------------------------------------- 1 | use compio_io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 2 | use compio_net::{UnixListener, UnixStream}; 3 | 4 | #[compio_macros::test] 5 | async fn accept_read_write() -> std::io::Result<()> { 6 | let dir = tempfile::Builder::new() 7 | .prefix("compio-uds-tests") 8 | .tempdir() 9 | .unwrap(); 10 | let sock_path = dir.path().join("connect.sock"); 11 | 12 | let listener = UnixListener::bind(&sock_path).await?; 13 | 14 | let (mut client, (mut server, _)) = 15 | futures_util::try_join!(UnixStream::connect(&sock_path), listener.accept()).unwrap(); 16 | 17 | client.write_all("hello").await.0?; 18 | drop(client); 19 | 20 | let buf = Vec::with_capacity(5); 21 | let ((), buf) = server.read_exact(buf).await.unwrap(); 22 | assert_eq!(&buf[..], b"hello"); 23 | let len = server.read(buf).await.0?; 24 | assert_eq!(len, 0); 25 | Ok(()) 26 | } 27 | 28 | #[compio_macros::test] 29 | async fn shutdown() -> std::io::Result<()> { 30 | let dir = tempfile::Builder::new() 31 | .prefix("compio-uds-tests") 32 | .tempdir() 33 | .unwrap(); 34 | let sock_path = dir.path().join("connect.sock"); 35 | 36 | let listener = UnixListener::bind(&sock_path).await?; 37 | 38 | let (mut client, (mut server, _)) = 39 | futures_util::try_join!(UnixStream::connect(&sock_path), listener.accept()).unwrap(); 40 | 41 | // Shut down the client 42 | client.shutdown().await?; 43 | // Read from the server should return 0 to indicate the channel has been closed. 44 | let n = server.read(Vec::with_capacity(1)).await.0?; 45 | assert_eq!(n, 0); 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /compio-process/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-process" 3 | version = "0.4.0" 4 | description = "Processes for compio" 5 | categories = ["asynchronous"] 6 | keywords = ["async", "process"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | compio-buf = { workspace = true } 19 | compio-driver = { workspace = true } 20 | compio-io = { workspace = true } 21 | compio-runtime = { workspace = true } 22 | 23 | cfg-if = { workspace = true } 24 | futures-util = { workspace = true } 25 | 26 | [target.'cfg(windows)'.dependencies] 27 | windows-sys = { workspace = true } 28 | 29 | [dev-dependencies] 30 | compio-macros = { workspace = true } 31 | 32 | [features] 33 | io-uring = ["compio-runtime/io-uring"] 34 | polling = ["compio-runtime/polling"] 35 | 36 | linux_pidfd = [] 37 | nightly = ["linux_pidfd"] 38 | -------------------------------------------------------------------------------- /compio-process/src/linux.rs: -------------------------------------------------------------------------------- 1 | use std::{io, process}; 2 | 3 | use compio_driver::{ 4 | SharedFd, 5 | op::{Interest, PollOnce}, 6 | }; 7 | 8 | pub async fn child_wait(mut child: process::Child) -> io::Result { 9 | #[cfg(feature = "linux_pidfd")] 10 | let fd = { 11 | use std::os::linux::process::ChildExt; 12 | 13 | use compio_driver::AsRawFd; 14 | 15 | child.pidfd().ok().map(|fd| fd.as_raw_fd()) 16 | }; 17 | #[cfg(not(feature = "linux_pidfd"))] 18 | let fd = None::; 19 | if let Some(fd) = fd { 20 | // pidfd won't be closed, and the child has not been reaped. 21 | let fd = SharedFd::new(fd); 22 | let op = PollOnce::new(fd, Interest::Readable); 23 | compio_runtime::submit(op).await.0?; 24 | child.wait() 25 | } else { 26 | unix::child_wait(child).await 27 | } 28 | } 29 | 30 | // For trait impls and fallback. 31 | #[path = "unix.rs"] 32 | mod unix; 33 | -------------------------------------------------------------------------------- /compio-process/src/unix.rs: -------------------------------------------------------------------------------- 1 | use std::{io, panic::resume_unwind, process}; 2 | 3 | use compio_buf::{BufResult, IntoInner, IoBuf, IoBufMut}; 4 | use compio_driver::{ 5 | AsRawFd, RawFd, SharedFd, ToSharedFd, 6 | op::{BufResultExt, Recv, Send}, 7 | }; 8 | use compio_io::{AsyncRead, AsyncWrite}; 9 | 10 | use crate::{ChildStderr, ChildStdin, ChildStdout}; 11 | 12 | pub async fn child_wait(mut child: process::Child) -> io::Result { 13 | compio_runtime::spawn_blocking(move || child.wait()) 14 | .await 15 | .unwrap_or_else(|e| resume_unwind(e)) 16 | } 17 | 18 | impl AsRawFd for ChildStdout { 19 | fn as_raw_fd(&self) -> RawFd { 20 | self.0.as_raw_fd() 21 | } 22 | } 23 | 24 | impl ToSharedFd for ChildStdout { 25 | fn to_shared_fd(&self) -> SharedFd { 26 | self.0.to_shared_fd() 27 | } 28 | } 29 | 30 | impl AsyncRead for ChildStdout { 31 | async fn read(&mut self, buffer: B) -> BufResult { 32 | let fd = self.to_shared_fd(); 33 | let op = Recv::new(fd, buffer); 34 | compio_runtime::submit(op).await.into_inner().map_advanced() 35 | } 36 | } 37 | 38 | impl AsRawFd for ChildStderr { 39 | fn as_raw_fd(&self) -> RawFd { 40 | self.0.as_raw_fd() 41 | } 42 | } 43 | 44 | impl ToSharedFd for ChildStderr { 45 | fn to_shared_fd(&self) -> SharedFd { 46 | self.0.to_shared_fd() 47 | } 48 | } 49 | 50 | impl AsyncRead for ChildStderr { 51 | async fn read(&mut self, buffer: B) -> BufResult { 52 | let fd = self.to_shared_fd(); 53 | let op = Recv::new(fd, buffer); 54 | compio_runtime::submit(op).await.into_inner().map_advanced() 55 | } 56 | } 57 | 58 | impl AsRawFd for ChildStdin { 59 | fn as_raw_fd(&self) -> RawFd { 60 | self.0.as_raw_fd() 61 | } 62 | } 63 | 64 | impl ToSharedFd for ChildStdin { 65 | fn to_shared_fd(&self) -> SharedFd { 66 | self.0.to_shared_fd() 67 | } 68 | } 69 | 70 | impl AsyncWrite for ChildStdin { 71 | async fn write(&mut self, buffer: T) -> BufResult { 72 | let fd = self.to_shared_fd(); 73 | let op = Send::new(fd, buffer); 74 | compio_runtime::submit(op).await.into_inner() 75 | } 76 | 77 | async fn flush(&mut self) -> io::Result<()> { 78 | Ok(()) 79 | } 80 | 81 | async fn shutdown(&mut self) -> io::Result<()> { 82 | Ok(()) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /compio-process/src/windows.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | os::windows::{io::AsRawHandle, process::ExitStatusExt}, 4 | pin::Pin, 5 | process, 6 | task::Poll, 7 | }; 8 | 9 | use compio_buf::{BufResult, IntoInner, IoBuf, IoBufMut}; 10 | use compio_driver::{ 11 | AsRawFd, OpCode, OpType, RawFd, SharedFd, ToSharedFd, 12 | op::{BufResultExt, Recv, Send}, 13 | syscall, 14 | }; 15 | use compio_io::{AsyncRead, AsyncWrite}; 16 | use windows_sys::Win32::System::{IO::OVERLAPPED, Threading::GetExitCodeProcess}; 17 | 18 | use crate::{ChildStderr, ChildStdin, ChildStdout}; 19 | 20 | struct WaitProcess { 21 | child: process::Child, 22 | } 23 | 24 | impl WaitProcess { 25 | pub fn new(child: process::Child) -> Self { 26 | Self { child } 27 | } 28 | } 29 | 30 | impl OpCode for WaitProcess { 31 | fn op_type(&self) -> OpType { 32 | OpType::Event(self.child.as_raw_handle() as _) 33 | } 34 | 35 | unsafe fn operate(self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> Poll> { 36 | let mut code = 0; 37 | syscall!( 38 | BOOL, 39 | GetExitCodeProcess(self.child.as_raw_handle() as _, &mut code) 40 | )?; 41 | Poll::Ready(Ok(code as _)) 42 | } 43 | } 44 | 45 | pub async fn child_wait(child: process::Child) -> io::Result { 46 | let op = WaitProcess::new(child); 47 | let code = compio_runtime::submit(op).await.0?; 48 | Ok(process::ExitStatus::from_raw(code as _)) 49 | } 50 | 51 | impl AsRawFd for ChildStdout { 52 | fn as_raw_fd(&self) -> RawFd { 53 | self.0.as_raw_fd() 54 | } 55 | } 56 | 57 | impl ToSharedFd for ChildStdout { 58 | fn to_shared_fd(&self) -> SharedFd { 59 | self.0.to_shared_fd() 60 | } 61 | } 62 | 63 | impl AsyncRead for ChildStdout { 64 | async fn read(&mut self, buffer: B) -> BufResult { 65 | let fd = self.to_shared_fd(); 66 | let op = Recv::new(fd, buffer); 67 | compio_runtime::submit(op).await.into_inner().map_advanced() 68 | } 69 | } 70 | 71 | impl AsRawFd for ChildStderr { 72 | fn as_raw_fd(&self) -> RawFd { 73 | self.0.as_raw_fd() 74 | } 75 | } 76 | 77 | impl ToSharedFd for ChildStderr { 78 | fn to_shared_fd(&self) -> SharedFd { 79 | self.0.to_shared_fd() 80 | } 81 | } 82 | 83 | impl AsyncRead for ChildStderr { 84 | async fn read(&mut self, buffer: B) -> BufResult { 85 | let fd = self.to_shared_fd(); 86 | let op = Recv::new(fd, buffer); 87 | compio_runtime::submit(op).await.into_inner().map_advanced() 88 | } 89 | } 90 | 91 | impl AsRawFd for ChildStdin { 92 | fn as_raw_fd(&self) -> RawFd { 93 | self.0.as_raw_fd() 94 | } 95 | } 96 | 97 | impl ToSharedFd for ChildStdin { 98 | fn to_shared_fd(&self) -> SharedFd { 99 | self.0.to_shared_fd() 100 | } 101 | } 102 | 103 | impl AsyncWrite for ChildStdin { 104 | async fn write(&mut self, buffer: T) -> BufResult { 105 | let fd = self.to_shared_fd(); 106 | let op = Send::new(fd, buffer); 107 | compio_runtime::submit(op).await.into_inner() 108 | } 109 | 110 | async fn flush(&mut self) -> io::Result<()> { 111 | Ok(()) 112 | } 113 | 114 | async fn shutdown(&mut self) -> io::Result<()> { 115 | Ok(()) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /compio-process/tests/process.rs: -------------------------------------------------------------------------------- 1 | use std::process::Stdio; 2 | 3 | use compio_process::Command; 4 | 5 | #[compio_macros::test] 6 | async fn exit_code() { 7 | let mut cmd; 8 | 9 | if cfg!(windows) { 10 | cmd = Command::new("cmd"); 11 | cmd.arg("/c"); 12 | } else { 13 | cmd = Command::new("sh"); 14 | cmd.arg("-c"); 15 | } 16 | 17 | let child = cmd.arg("exit 2").spawn().unwrap(); 18 | 19 | let id = child.id(); 20 | assert!(id > 0); 21 | 22 | let status = child.wait().await.unwrap(); 23 | assert_eq!(status.code(), Some(2)); 24 | } 25 | 26 | #[compio_macros::test] 27 | async fn echo() { 28 | let mut cmd; 29 | 30 | if cfg!(windows) { 31 | cmd = Command::new("cmd"); 32 | cmd.arg("/c"); 33 | } else { 34 | cmd = Command::new("sh"); 35 | cmd.arg("-c"); 36 | } 37 | 38 | let child = cmd 39 | .arg("echo hello world") 40 | .stdout(Stdio::piped()) 41 | .unwrap() 42 | .spawn() 43 | .unwrap(); 44 | 45 | let id = child.id(); 46 | assert!(id > 0); 47 | 48 | let output = child.wait_with_output().await.unwrap(); 49 | assert!(output.status.success()); 50 | 51 | let out = String::from_utf8(output.stdout).unwrap(); 52 | assert_eq!(out.trim(), "hello world"); 53 | } 54 | 55 | #[cfg(unix)] 56 | #[compio_macros::test] 57 | async fn arg0() { 58 | let mut cmd = Command::new("sh"); 59 | cmd.arg0("test_string") 60 | .arg("-c") 61 | .arg("echo $0") 62 | .stdout(Stdio::piped()) 63 | .unwrap(); 64 | 65 | let output = cmd.output().await.unwrap(); 66 | assert_eq!(output.stdout, b"test_string\n"); 67 | } 68 | -------------------------------------------------------------------------------- /compio-quic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-quic" 3 | version = "0.3.1" 4 | description = "QUIC for compio" 5 | categories = ["asynchronous", "network-programming"] 6 | keywords = ["async", "net", "quic"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | # Workspace dependencies 19 | compio-io = { workspace = true } 20 | compio-buf = { workspace = true, features = ["bytes"] } 21 | compio-log = { workspace = true } 22 | compio-net = { workspace = true } 23 | compio-runtime = { workspace = true, features = ["time"] } 24 | 25 | quinn-proto = { version = "0.11.10", default-features = false } 26 | rustls = { workspace = true } 27 | rustls-platform-verifier = { version = "0.6.0", optional = true } 28 | rustls-native-certs = { workspace = true, optional = true } 29 | webpki-roots = { version = "1.0.0", optional = true } 30 | h3 = { version = "0.0.8", optional = true } 31 | h3-datagram = { version = "0.0.2", optional = true } 32 | 33 | # Utils 34 | flume = { workspace = true } 35 | futures-util = { workspace = true } 36 | thiserror = { workspace = true } 37 | rustc-hash = "2.0.0" 38 | 39 | # Windows specific dependencies 40 | [target.'cfg(windows)'.dependencies] 41 | windows-sys = { workspace = true, features = ["Win32_Networking_WinSock"] } 42 | 43 | [target.'cfg(unix)'.dependencies] 44 | libc = { workspace = true } 45 | 46 | [build-dependencies] 47 | cfg_aliases = { workspace = true } 48 | 49 | [dev-dependencies] 50 | compio-dispatcher = { workspace = true } 51 | compio-driver = { workspace = true } 52 | compio-fs = { workspace = true } 53 | compio-macros = { workspace = true } 54 | compio-runtime = { workspace = true, features = ["criterion"] } 55 | 56 | criterion = { workspace = true, features = ["async_tokio"] } 57 | http = "1.1.0" 58 | quinn = "0.11.6" 59 | rand = { workspace = true } 60 | rcgen = "0.13.1" 61 | socket2 = { workspace = true, features = ["all"] } 62 | tokio = { workspace = true, features = ["macros", "rt"] } 63 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 64 | 65 | [features] 66 | default = ["ring"] 67 | io-uring = ["compio-runtime/io-uring"] 68 | polling = ["compio-runtime/polling"] 69 | io-compat = ["futures-util/io"] 70 | platform-verifier = ["dep:rustls-platform-verifier"] 71 | native-certs = ["dep:rustls-native-certs"] 72 | webpki-roots = ["dep:webpki-roots"] 73 | h3 = ["dep:h3", "dep:h3-datagram"] 74 | ring = ["quinn-proto/rustls-ring"] 75 | aws-lc-rs = ["quinn-proto/rustls-aws-lc-rs"] 76 | aws-lc-rs-fips = ["aws-lc-rs", "quinn-proto/rustls-aws-lc-rs-fips"] 77 | 78 | [[example]] 79 | name = "http3-client" 80 | required-features = ["h3"] 81 | 82 | [[example]] 83 | name = "http3-server" 84 | required-features = ["h3"] 85 | 86 | [[bench]] 87 | name = "quic" 88 | harness = false 89 | -------------------------------------------------------------------------------- /compio-quic/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() { 4 | cfg_aliases! { 5 | aix: { target_os = "aix" }, 6 | linux: { target_os = "linux" }, 7 | linux_all: { any(target_os = "linux", target_os = "android") }, 8 | freebsd: { target_os = "freebsd" }, 9 | netbsd: { target_os = "netbsd" }, 10 | non_freebsd: { any(target_os = "openbsd", target_os = "netbsd") }, 11 | bsd: { any(freebsd, non_freebsd) }, 12 | solarish: { any(target_os = "illumos", target_os = "solaris") }, 13 | apple: { target_vendor = "apple" }, 14 | rustls: { any(feature = "aws-lc-rs", feature = "ring") } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /compio-quic/examples/http3-client.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, 3 | path::PathBuf, 4 | str::FromStr, 5 | }; 6 | 7 | use compio_buf::bytes::Buf; 8 | use compio_io::AsyncWriteAtExt; 9 | use compio_net::ToSocketAddrsAsync; 10 | use compio_quic::ClientBuilder; 11 | use http::{Request, Uri}; 12 | use tracing_subscriber::EnvFilter; 13 | 14 | #[compio_macros::main] 15 | async fn main() { 16 | tracing_subscriber::fmt() 17 | .with_env_filter(EnvFilter::from_default_env()) 18 | .init(); 19 | 20 | let args = std::env::args().collect::>(); 21 | if args.len() != 3 { 22 | eprintln!("Usage: {} ", args[0]); 23 | std::process::exit(1); 24 | } 25 | 26 | let uri = Uri::from_str(&args[1]).unwrap(); 27 | let outpath = PathBuf::from(&args[2]); 28 | 29 | let host = uri.host().unwrap(); 30 | let remote = (host, uri.port_u16().unwrap_or(443)) 31 | .to_socket_addrs_async() 32 | .await 33 | .unwrap() 34 | .next() 35 | .unwrap(); 36 | 37 | let endpoint = ClientBuilder::new_with_no_server_verification() 38 | .with_key_log() 39 | .with_alpn_protocols(&["h3"]) 40 | .bind(SocketAddr::new( 41 | if remote.is_ipv6() { 42 | IpAddr::V6(Ipv6Addr::UNSPECIFIED) 43 | } else { 44 | IpAddr::V4(Ipv4Addr::UNSPECIFIED) 45 | }, 46 | 0, 47 | )) 48 | .await 49 | .unwrap(); 50 | 51 | { 52 | println!("Connecting to {host} at {remote}"); 53 | let conn = endpoint.connect(remote, host, None).unwrap().await.unwrap(); 54 | 55 | let (mut conn, mut send_req) = compio_quic::h3::client::new(conn).await.unwrap(); 56 | let handle = compio_runtime::spawn(async move { conn.wait_idle().await }); 57 | 58 | let req = Request::get(uri).body(()).unwrap(); 59 | let mut stream = send_req.send_request(req).await.unwrap(); 60 | stream.finish().await.unwrap(); 61 | 62 | let resp = stream.recv_response().await.unwrap(); 63 | println!("{resp:?}"); 64 | 65 | let mut out = compio_fs::File::create(outpath).await.unwrap(); 66 | let mut pos = 0; 67 | while let Some(mut chunk) = stream.recv_data().await.unwrap() { 68 | let len = chunk.remaining(); 69 | out.write_all_at(chunk.copy_to_bytes(len), pos) 70 | .await 71 | .unwrap(); 72 | pos += len as u64; 73 | } 74 | if let Some(headers) = stream.recv_trailers().await.unwrap() { 75 | println!("{headers:?}"); 76 | } 77 | 78 | drop(send_req); 79 | 80 | handle.await.unwrap(); 81 | } 82 | 83 | endpoint.shutdown().await.unwrap(); 84 | } 85 | -------------------------------------------------------------------------------- /compio-quic/examples/http3-server.rs: -------------------------------------------------------------------------------- 1 | use compio_buf::bytes::Bytes; 2 | use compio_quic::ServerBuilder; 3 | use http::{HeaderMap, Response}; 4 | use tracing_subscriber::EnvFilter; 5 | 6 | #[compio_macros::main] 7 | async fn main() { 8 | tracing_subscriber::fmt() 9 | .with_env_filter(EnvFilter::from_default_env()) 10 | .init(); 11 | 12 | let rcgen::CertifiedKey { cert, key_pair } = 13 | rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); 14 | let cert = cert.der().clone(); 15 | let key_der = key_pair.serialize_der().try_into().unwrap(); 16 | 17 | let endpoint = ServerBuilder::new_with_single_cert(vec![cert], key_der) 18 | .unwrap() 19 | .with_key_log() 20 | .with_alpn_protocols(&["h3"]) 21 | .bind("[::1]:4433") 22 | .await 23 | .unwrap(); 24 | 25 | while let Some(incoming) = endpoint.wait_incoming().await { 26 | compio_runtime::spawn(async move { 27 | let conn = incoming.await.unwrap(); 28 | println!("Accepted connection from {}", conn.remote_address()); 29 | 30 | let mut conn = compio_quic::h3::server::builder() 31 | .build::<_, Bytes>(conn) 32 | .await 33 | .unwrap(); 34 | 35 | while let Ok(Some(resolver)) = conn.accept().await { 36 | let (req, mut stream) = resolver.resolve_request().await.unwrap(); 37 | println!("Received request: {req:?}"); 38 | stream 39 | .send_response( 40 | Response::builder() 41 | .header("server", "compio-quic") 42 | .body(()) 43 | .unwrap(), 44 | ) 45 | .await 46 | .unwrap(); 47 | stream 48 | .send_data("hello from compio-quic".into()) 49 | .await 50 | .unwrap(); 51 | let mut headers = HeaderMap::new(); 52 | headers.insert("msg", "byebye".parse().unwrap()); 53 | stream.send_trailers(headers).await.unwrap(); 54 | } 55 | }) 56 | .detach(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /compio-quic/examples/quic-client.rs: -------------------------------------------------------------------------------- 1 | use std::net::{IpAddr, Ipv6Addr, SocketAddr}; 2 | 3 | use compio_quic::ClientBuilder; 4 | use tracing_subscriber::EnvFilter; 5 | 6 | #[compio_macros::main] 7 | async fn main() { 8 | tracing_subscriber::fmt() 9 | .with_env_filter(EnvFilter::from_default_env()) 10 | .init(); 11 | 12 | let endpoint = ClientBuilder::new_with_no_server_verification() 13 | .with_key_log() 14 | .bind("[::1]:0") 15 | .await 16 | .unwrap(); 17 | 18 | { 19 | let conn = endpoint 20 | .connect( 21 | SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 4433), 22 | "localhost", 23 | None, 24 | ) 25 | .unwrap() 26 | .await 27 | .unwrap(); 28 | 29 | let (mut send, mut recv) = conn.open_bi().unwrap(); 30 | send.write(&[1, 2, 3]).await.unwrap(); 31 | send.finish().unwrap(); 32 | 33 | let mut buf = vec![]; 34 | recv.read_to_end(&mut buf).await.unwrap(); 35 | println!("{buf:?}"); 36 | 37 | conn.close(1u32.into(), b"bye"); 38 | } 39 | 40 | endpoint.shutdown().await.unwrap(); 41 | } 42 | -------------------------------------------------------------------------------- /compio-quic/examples/quic-dispatcher.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroUsize; 2 | 3 | use compio_dispatcher::Dispatcher; 4 | use compio_quic::{ClientBuilder, Endpoint, ServerBuilder}; 5 | use compio_runtime::spawn; 6 | use futures_util::{StreamExt, stream::FuturesUnordered}; 7 | 8 | #[compio_macros::main] 9 | async fn main() { 10 | const THREAD_NUM: usize = 5; 11 | const CLIENT_NUM: usize = 10; 12 | 13 | let rcgen::CertifiedKey { cert, key_pair } = 14 | rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); 15 | let cert = cert.der().clone(); 16 | let key_der = key_pair.serialize_der().try_into().unwrap(); 17 | 18 | let server_config = ServerBuilder::new_with_single_cert(vec![cert.clone()], key_der) 19 | .unwrap() 20 | .build(); 21 | let client_config = ClientBuilder::new_with_empty_roots() 22 | .with_custom_certificate(cert) 23 | .unwrap() 24 | .with_no_crls() 25 | .build(); 26 | let mut endpoint = Endpoint::server("127.0.0.1:0", server_config) 27 | .await 28 | .unwrap(); 29 | endpoint.default_client_config = Some(client_config); 30 | 31 | spawn({ 32 | let endpoint = endpoint.clone(); 33 | async move { 34 | let mut futures = FuturesUnordered::from_iter((0..CLIENT_NUM).map(|i| { 35 | let endpoint = &endpoint; 36 | async move { 37 | let conn = endpoint 38 | .connect(endpoint.local_addr().unwrap(), "localhost", None) 39 | .unwrap() 40 | .await 41 | .unwrap(); 42 | let mut send = conn.open_uni().unwrap(); 43 | send.write_all(format!("Hello world {i}!").as_bytes()) 44 | .await 45 | .unwrap(); 46 | send.finish().unwrap(); 47 | send.stopped().await.unwrap(); 48 | } 49 | })); 50 | while let Some(()) = futures.next().await {} 51 | } 52 | }) 53 | .detach(); 54 | 55 | let dispatcher = Dispatcher::builder() 56 | .worker_threads(NonZeroUsize::new(THREAD_NUM).unwrap()) 57 | .build() 58 | .unwrap(); 59 | let mut handles = FuturesUnordered::new(); 60 | for _i in 0..CLIENT_NUM { 61 | let incoming = endpoint.wait_incoming().await.unwrap(); 62 | let handle = dispatcher 63 | .dispatch(move || async move { 64 | let conn = incoming.await.unwrap(); 65 | let mut recv = conn.accept_uni().await.unwrap(); 66 | let mut buf = vec![]; 67 | recv.read_to_end(&mut buf).await.unwrap(); 68 | println!("{}", std::str::from_utf8(&buf).unwrap()); 69 | }) 70 | .unwrap(); 71 | handles.push(handle); 72 | } 73 | while handles.next().await.is_some() {} 74 | dispatcher.join().await.unwrap(); 75 | } 76 | -------------------------------------------------------------------------------- /compio-quic/examples/quic-server.rs: -------------------------------------------------------------------------------- 1 | use compio_quic::ServerBuilder; 2 | use tracing_subscriber::EnvFilter; 3 | 4 | #[compio_macros::main] 5 | async fn main() { 6 | tracing_subscriber::fmt() 7 | .with_env_filter(EnvFilter::from_default_env()) 8 | .init(); 9 | 10 | let rcgen::CertifiedKey { cert, key_pair } = 11 | rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); 12 | let cert = cert.der().clone(); 13 | let key_der = key_pair.serialize_der().try_into().unwrap(); 14 | 15 | let endpoint = ServerBuilder::new_with_single_cert(vec![cert], key_der) 16 | .unwrap() 17 | .with_key_log() 18 | .bind("[::1]:4433") 19 | .await 20 | .unwrap(); 21 | 22 | if let Some(incoming) = endpoint.wait_incoming().await { 23 | let conn = incoming.await.unwrap(); 24 | 25 | let (mut send, mut recv) = conn.accept_bi().await.unwrap(); 26 | 27 | let mut buf = vec![]; 28 | recv.read_to_end(&mut buf).await.unwrap(); 29 | println!("{buf:?}"); 30 | 31 | send.write(&[4, 5, 6]).await.unwrap(); 32 | send.finish().unwrap(); 33 | 34 | conn.closed().await; 35 | } 36 | 37 | endpoint.close(0u32.into(), b""); 38 | endpoint.shutdown().await.unwrap(); 39 | } 40 | -------------------------------------------------------------------------------- /compio-quic/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! QUIC implementation for compio 2 | //! 3 | //! Ported from [`quinn`]. 4 | //! 5 | //! [`quinn`]: https://docs.rs/quinn 6 | 7 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 8 | #![warn(missing_docs)] 9 | 10 | pub use quinn_proto::{ 11 | AckFrequencyConfig, ApplicationClose, Chunk, ClientConfig, ClosedStream, ConfigError, 12 | ConnectError, ConnectionClose, ConnectionStats, EndpointConfig, IdleTimeout, 13 | MtuDiscoveryConfig, ServerConfig, StreamId, Transmit, TransportConfig, VarInt, congestion, 14 | crypto, 15 | }; 16 | 17 | #[cfg(rustls)] 18 | mod builder; 19 | mod connection; 20 | mod endpoint; 21 | mod incoming; 22 | mod recv_stream; 23 | mod send_stream; 24 | mod socket; 25 | 26 | #[cfg(rustls)] 27 | pub use builder::{ClientBuilder, ServerBuilder}; 28 | pub use connection::{Connecting, Connection, ConnectionError}; 29 | pub use endpoint::Endpoint; 30 | pub use incoming::{Incoming, IncomingFuture}; 31 | pub use recv_stream::{ReadError, ReadExactError, RecvStream}; 32 | pub use send_stream::{SendStream, WriteError}; 33 | 34 | pub(crate) use crate::{ 35 | connection::{ConnectionEvent, ConnectionInner}, 36 | endpoint::EndpointInner, 37 | socket::*, 38 | }; 39 | 40 | /// Errors from [`SendStream::stopped`] and [`RecvStream::stopped`]. 41 | #[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] 42 | pub enum StoppedError { 43 | /// The connection was lost 44 | #[error("connection lost")] 45 | ConnectionLost(#[from] ConnectionError), 46 | /// This was a 0-RTT stream and the server rejected it 47 | /// 48 | /// Can only occur on clients for 0-RTT streams, which can be opened using 49 | /// [`Connecting::into_0rtt()`]. 50 | /// 51 | /// [`Connecting::into_0rtt()`]: crate::Connecting::into_0rtt() 52 | #[error("0-RTT rejected")] 53 | ZeroRttRejected, 54 | } 55 | 56 | impl From for std::io::Error { 57 | fn from(x: StoppedError) -> Self { 58 | use StoppedError::*; 59 | let kind = match x { 60 | ZeroRttRejected => std::io::ErrorKind::ConnectionReset, 61 | ConnectionLost(_) => std::io::ErrorKind::NotConnected, 62 | }; 63 | Self::new(kind, x) 64 | } 65 | } 66 | 67 | /// HTTP/3 support via [`h3`]. 68 | #[cfg(feature = "h3")] 69 | pub mod h3 { 70 | pub use h3::*; 71 | 72 | pub use crate::{ 73 | connection::h3_impl::{BidiStream, OpenStreams}, 74 | send_stream::h3_impl::SendStream, 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /compio-quic/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use compio_log::subscriber::DefaultGuard; 4 | use compio_quic::{ClientBuilder, ClientConfig, ServerBuilder, ServerConfig, TransportConfig}; 5 | use tracing_subscriber::{EnvFilter, util::SubscriberInitExt}; 6 | 7 | pub fn subscribe() -> DefaultGuard { 8 | tracing_subscriber::fmt() 9 | .with_env_filter(EnvFilter::from_default_env()) 10 | .finish() 11 | .set_default() 12 | } 13 | 14 | pub fn config_pair(transport: Option) -> (ServerConfig, ClientConfig) { 15 | let rcgen::CertifiedKey { cert, key_pair } = 16 | rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); 17 | let cert = cert.der().clone(); 18 | let key_der = key_pair.serialize_der().try_into().unwrap(); 19 | 20 | let mut server_config = ServerBuilder::new_with_single_cert(vec![cert.clone()], key_der) 21 | .unwrap() 22 | .build(); 23 | let mut client_config = ClientBuilder::new_with_empty_roots() 24 | .with_custom_certificate(cert) 25 | .unwrap() 26 | .with_no_crls() 27 | .build(); 28 | if let Some(transport) = transport { 29 | let transport = Arc::new(transport); 30 | server_config.transport_config(transport.clone()); 31 | client_config.transport_config(transport); 32 | } 33 | (server_config, client_config) 34 | } 35 | -------------------------------------------------------------------------------- /compio-quic/tests/control.rs: -------------------------------------------------------------------------------- 1 | use compio_quic::{ConnectionError, Endpoint, TransportConfig}; 2 | 3 | mod common; 4 | use common::{config_pair, subscribe}; 5 | use futures_util::join; 6 | 7 | #[compio_macros::test] 8 | async fn ip_blocking() { 9 | let _guard = subscribe(); 10 | 11 | let (server_config, client_config) = config_pair(None); 12 | 13 | let server = Endpoint::server("127.0.0.1:0", server_config) 14 | .await 15 | .unwrap(); 16 | let server_addr = server.local_addr().unwrap(); 17 | 18 | let client1 = Endpoint::client("127.0.0.1:0").await.unwrap(); 19 | let client1_addr = client1.local_addr().unwrap(); 20 | let client2 = Endpoint::client("127.0.0.1:0").await.unwrap(); 21 | 22 | let srv = compio_runtime::spawn(async move { 23 | loop { 24 | let incoming = server.wait_incoming().await.unwrap(); 25 | if incoming.remote_address() == client1_addr { 26 | incoming.refuse(); 27 | } else if incoming.remote_address_validated() { 28 | incoming.await.unwrap(); 29 | } else { 30 | incoming.retry().unwrap(); 31 | } 32 | } 33 | }); 34 | 35 | let e = client1 36 | .connect(server_addr, "localhost", Some(client_config.clone())) 37 | .unwrap() 38 | .await 39 | .unwrap_err(); 40 | assert!(matches!(e, ConnectionError::ConnectionClosed(_))); 41 | client2 42 | .connect(server_addr, "localhost", Some(client_config)) 43 | .unwrap() 44 | .await 45 | .unwrap(); 46 | 47 | let _ = srv.cancel().await; 48 | } 49 | 50 | #[compio_macros::test] 51 | async fn stream_id_flow_control() { 52 | let _guard = subscribe(); 53 | 54 | let mut cfg = TransportConfig::default(); 55 | cfg.max_concurrent_uni_streams(1u32.into()); 56 | 57 | let (server_config, client_config) = config_pair(Some(cfg)); 58 | let mut endpoint = Endpoint::server("127.0.0.1:0", server_config) 59 | .await 60 | .unwrap(); 61 | endpoint.default_client_config = Some(client_config); 62 | 63 | let (conn1, conn2) = join!( 64 | async { 65 | endpoint 66 | .connect(endpoint.local_addr().unwrap(), "localhost", None) 67 | .unwrap() 68 | .await 69 | .unwrap() 70 | }, 71 | async { endpoint.wait_incoming().await.unwrap().await.unwrap() }, 72 | ); 73 | 74 | // If `open_uni_wait` doesn't get unblocked when the previous stream is dropped, 75 | // this will time out. 76 | join!( 77 | async { 78 | conn1.open_uni_wait().await.unwrap(); 79 | }, 80 | async { 81 | conn1.open_uni_wait().await.unwrap(); 82 | }, 83 | async { 84 | conn1.open_uni_wait().await.unwrap(); 85 | }, 86 | async { 87 | conn2.accept_uni().await.unwrap(); 88 | conn2.accept_uni().await.unwrap(); 89 | } 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /compio-runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-runtime" 3 | version = "0.7.0" 4 | description = "High-level runtime for compio" 5 | categories = ["asynchronous"] 6 | keywords = ["async", "runtime"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | default-target = "x86_64-unknown-linux-gnu" 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | targets = [ 18 | "x86_64-pc-windows-gnu", 19 | "x86_64-unknown-linux-gnu", 20 | "x86_64-apple-darwin", 21 | "aarch64-apple-ios", 22 | "aarch64-linux-android", 23 | "x86_64-unknown-dragonfly", 24 | "x86_64-unknown-freebsd", 25 | "x86_64-unknown-illumos", 26 | "x86_64-unknown-netbsd", 27 | "x86_64-unknown-openbsd", 28 | ] 29 | 30 | [dependencies] 31 | # Workspace dependencies 32 | compio-driver = { workspace = true } 33 | compio-buf = { workspace = true } 34 | compio-log = { workspace = true } 35 | 36 | async-task = "4.5.0" 37 | cfg-if = { workspace = true, optional = true } 38 | criterion = { workspace = true, optional = true } 39 | crossbeam-queue = { workspace = true } 40 | futures-util = { workspace = true } 41 | once_cell = { workspace = true } 42 | scoped-tls = "1.0.1" 43 | slab = { workspace = true, optional = true } 44 | socket2 = { workspace = true } 45 | 46 | # Windows specific dependencies 47 | [target.'cfg(windows)'.dependencies] 48 | windows-sys = { workspace = true, features = ["Win32_System_IO"] } 49 | 50 | # Unix specific dependencies 51 | [target.'cfg(unix)'.dependencies] 52 | libc = { workspace = true } 53 | 54 | [target.'cfg(windows)'.dev-dependencies] 55 | windows-sys = { workspace = true, features = ["Win32_UI_WindowsAndMessaging"] } 56 | 57 | [target.'cfg(target_os = "macos")'.dev-dependencies] 58 | core-foundation = "0.10.0" 59 | block2 = "0.6.0" 60 | 61 | [target.'cfg(not(any(windows, target_os = "macos")))'.dev-dependencies] 62 | glib = "0.20" 63 | 64 | [features] 65 | event = ["dep:cfg-if", "compio-buf/arrayvec"] 66 | time = ["dep:slab"] 67 | 68 | io-uring = ["compio-driver/io-uring"] 69 | polling = ["compio-driver/polling"] 70 | 71 | [[test]] 72 | name = "event" 73 | required-features = ["event"] 74 | -------------------------------------------------------------------------------- /compio-runtime/src/attacher.rs: -------------------------------------------------------------------------------- 1 | #[cfg(unix)] 2 | use std::os::fd::{FromRawFd, RawFd}; 3 | #[cfg(windows)] 4 | use std::os::windows::io::{FromRawHandle, FromRawSocket, RawHandle, RawSocket}; 5 | use std::{io, ops::Deref}; 6 | 7 | use compio_buf::IntoInner; 8 | use compio_driver::{AsRawFd, SharedFd, ToSharedFd}; 9 | 10 | use crate::Runtime; 11 | 12 | /// Attach a handle to the driver of current thread. 13 | /// 14 | /// A handle can and only can attach once to one driver. The attacher will try 15 | /// to attach the handle. 16 | #[derive(Debug)] 17 | pub struct Attacher { 18 | source: SharedFd, 19 | } 20 | 21 | impl Attacher { 22 | /// Create [`Attacher`] without trying to attach the source. 23 | /// 24 | /// # Safety 25 | /// 26 | /// The user should ensure that the source is attached to the current 27 | /// driver. 28 | pub unsafe fn new_unchecked(source: S) -> Self { 29 | Self { 30 | source: SharedFd::new(source), 31 | } 32 | } 33 | 34 | /// Create [`Attacher`] without trying to attach the source. 35 | /// 36 | /// # Safety 37 | /// 38 | /// See [`Attacher::new_unchecked`]. 39 | pub unsafe fn from_shared_fd_unchecked(source: SharedFd) -> Self { 40 | Self { source } 41 | } 42 | } 43 | 44 | impl Attacher { 45 | /// Create [`Attacher`]. It tries to attach the source, and will return 46 | /// [`Err`] if it fails. 47 | /// 48 | /// ## Platform specific 49 | /// * IOCP: a handle could not be attached more than once. If you want to 50 | /// clone the handle, create the [`Attacher`] before cloning. 51 | pub fn new(source: S) -> io::Result { 52 | Runtime::with_current(|r| r.attach(source.as_raw_fd()))?; 53 | Ok(unsafe { Self::new_unchecked(source) }) 54 | } 55 | } 56 | 57 | impl IntoInner for Attacher { 58 | type Inner = SharedFd; 59 | 60 | fn into_inner(self) -> Self::Inner { 61 | self.source 62 | } 63 | } 64 | 65 | impl Clone for Attacher { 66 | fn clone(&self) -> Self { 67 | Self { 68 | source: self.source.clone(), 69 | } 70 | } 71 | } 72 | 73 | #[cfg(windows)] 74 | impl FromRawHandle for Attacher { 75 | unsafe fn from_raw_handle(handle: RawHandle) -> Self { 76 | Self::new_unchecked(S::from_raw_handle(handle)) 77 | } 78 | } 79 | 80 | #[cfg(windows)] 81 | impl FromRawSocket for Attacher { 82 | unsafe fn from_raw_socket(sock: RawSocket) -> Self { 83 | Self::new_unchecked(S::from_raw_socket(sock)) 84 | } 85 | } 86 | 87 | #[cfg(unix)] 88 | impl FromRawFd for Attacher { 89 | unsafe fn from_raw_fd(fd: RawFd) -> Self { 90 | Self::new_unchecked(S::from_raw_fd(fd)) 91 | } 92 | } 93 | 94 | impl Deref for Attacher { 95 | type Target = S; 96 | 97 | fn deref(&self) -> &Self::Target { 98 | self.source.deref() 99 | } 100 | } 101 | 102 | impl ToSharedFd for Attacher { 103 | fn to_shared_fd(&self) -> SharedFd { 104 | self.source.to_shared_fd() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /compio-runtime/src/event.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous events. 2 | 3 | use std::{ 4 | pin::Pin, 5 | sync::{ 6 | Arc, 7 | atomic::{AtomicBool, Ordering}, 8 | }, 9 | task::{Context, Poll}, 10 | }; 11 | 12 | use futures_util::{Future, task::AtomicWaker}; 13 | 14 | #[derive(Debug)] 15 | struct Inner { 16 | waker: AtomicWaker, 17 | set: AtomicBool, 18 | } 19 | 20 | #[derive(Debug, Clone)] 21 | struct Flag(Arc); 22 | 23 | impl Flag { 24 | pub fn new() -> Self { 25 | Self(Arc::new(Inner { 26 | waker: AtomicWaker::new(), 27 | set: AtomicBool::new(false), 28 | })) 29 | } 30 | 31 | pub fn notify(&self) { 32 | self.0.set.store(true, Ordering::Relaxed); 33 | self.0.waker.wake(); 34 | } 35 | 36 | pub fn notified(&self) -> bool { 37 | self.0.set.load(Ordering::Relaxed) 38 | } 39 | } 40 | 41 | impl Future for Flag { 42 | type Output = (); 43 | 44 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 45 | // quick check to avoid registration if already done. 46 | if self.0.set.load(Ordering::Relaxed) { 47 | return Poll::Ready(()); 48 | } 49 | 50 | self.0.waker.register(cx.waker()); 51 | 52 | // Need to check condition **after** `register` to avoid a race 53 | // condition that would result in lost notifications. 54 | if self.0.set.load(Ordering::Relaxed) { 55 | Poll::Ready(()) 56 | } else { 57 | Poll::Pending 58 | } 59 | } 60 | } 61 | 62 | /// An event that won't wake until [`EventHandle::notify`] is called 63 | /// successfully. 64 | #[derive(Debug)] 65 | pub struct Event { 66 | flag: Flag, 67 | } 68 | 69 | impl Default for Event { 70 | fn default() -> Self { 71 | Self::new() 72 | } 73 | } 74 | 75 | impl Event { 76 | /// Create [`Event`]. 77 | pub fn new() -> Self { 78 | Self { flag: Flag::new() } 79 | } 80 | 81 | /// Get a notify handle. 82 | pub fn handle(&self) -> EventHandle { 83 | EventHandle::new(self.flag.clone()) 84 | } 85 | 86 | /// Get if the event has been notified. 87 | pub fn notified(&self) -> bool { 88 | self.flag.notified() 89 | } 90 | 91 | /// Wait for [`EventHandle::notify`] called. 92 | pub async fn wait(self) { 93 | self.flag.await 94 | } 95 | } 96 | 97 | /// A wake up handle to [`Event`]. 98 | pub struct EventHandle { 99 | flag: Flag, 100 | } 101 | 102 | impl EventHandle { 103 | fn new(flag: Flag) -> Self { 104 | Self { flag } 105 | } 106 | 107 | /// Notify the event. 108 | pub fn notify(self) { 109 | self.flag.notify() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /compio-runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The runtime of compio. 2 | //! 3 | //! ``` 4 | //! let ans = compio_runtime::Runtime::new().unwrap().block_on(async { 5 | //! println!("Hello world!"); 6 | //! 42 7 | //! }); 8 | //! assert_eq!(ans, 42); 9 | //! ``` 10 | 11 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 12 | #![warn(missing_docs)] 13 | 14 | mod attacher; 15 | mod runtime; 16 | 17 | #[cfg(feature = "event")] 18 | pub mod event; 19 | #[cfg(feature = "time")] 20 | pub mod time; 21 | 22 | pub use async_task::Task; 23 | pub use attacher::*; 24 | use compio_buf::BufResult; 25 | pub use runtime::{ 26 | JoinHandle, Runtime, RuntimeBuilder, spawn, spawn_blocking, submit, submit_with_flags, 27 | }; 28 | -------------------------------------------------------------------------------- /compio-runtime/src/runtime/op.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use compio_buf::BufResult; 8 | use compio_driver::{Key, OpCode, PushEntry}; 9 | 10 | use crate::runtime::Runtime; 11 | 12 | #[derive(Debug)] 13 | pub struct OpFuture { 14 | key: Option>, 15 | } 16 | 17 | impl OpFuture { 18 | pub fn new(key: Key) -> Self { 19 | Self { key: Some(key) } 20 | } 21 | } 22 | 23 | impl Future for OpFuture { 24 | type Output = (BufResult, u32); 25 | 26 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 27 | let res = Runtime::with_current(|r| r.poll_task(cx, self.key.take().unwrap())); 28 | match res { 29 | PushEntry::Pending(key) => { 30 | self.key = Some(key); 31 | Poll::Pending 32 | } 33 | PushEntry::Ready(res) => Poll::Ready(res), 34 | } 35 | } 36 | } 37 | 38 | impl Drop for OpFuture { 39 | fn drop(&mut self) { 40 | if let Some(key) = self.key.take() { 41 | Runtime::with_current(|r| r.cancel_op(key)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /compio-runtime/src/runtime/send_wrapper.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Thomas Keh. 2 | // Copyright 2024 compio-rs 3 | // 4 | // Licensed under the Apache License, Version 2.0 or the MIT license 6 | // , at your 7 | // option. This file may not be copied, modified, or distributed 8 | // except according to those terms. 9 | 10 | use std::{ 11 | cell::Cell, 12 | mem::{self, ManuallyDrop}, 13 | thread::{self, ThreadId}, 14 | }; 15 | 16 | thread_local! { 17 | static THREAD_ID: Cell = Cell::new(thread::current().id()); 18 | } 19 | 20 | /// A wrapper that copied from `send_wrapper` crate, with our own optimizations. 21 | pub struct SendWrapper { 22 | data: ManuallyDrop, 23 | thread_id: ThreadId, 24 | } 25 | 26 | impl SendWrapper { 27 | /// Create a `SendWrapper` wrapper around a value of type `T`. 28 | /// The wrapper takes ownership of the value. 29 | #[inline] 30 | pub fn new(data: T) -> SendWrapper { 31 | SendWrapper { 32 | data: ManuallyDrop::new(data), 33 | thread_id: THREAD_ID.get(), 34 | } 35 | } 36 | 37 | /// Returns `true` if the value can be safely accessed from within the 38 | /// current thread. 39 | #[inline] 40 | pub fn valid(&self) -> bool { 41 | self.thread_id == THREAD_ID.get() 42 | } 43 | 44 | /// Returns a reference to the contained value. 45 | /// 46 | /// # Safety 47 | /// 48 | /// The caller should be in the same thread as the creator. 49 | #[inline] 50 | pub unsafe fn get_unchecked(&self) -> &T { 51 | &self.data 52 | } 53 | 54 | /// Returns a reference to the contained value, if valid. 55 | #[inline] 56 | pub fn get(&self) -> Option<&T> { 57 | if self.valid() { Some(&self.data) } else { None } 58 | } 59 | } 60 | 61 | unsafe impl Send for SendWrapper {} 62 | unsafe impl Sync for SendWrapper {} 63 | 64 | impl Drop for SendWrapper { 65 | /// Drops the contained value. 66 | /// 67 | /// # Panics 68 | /// 69 | /// Dropping panics if it is done from a different thread than the one the 70 | /// `SendWrapper` instance has been created with. 71 | /// 72 | /// Exceptions: 73 | /// - There is no extra panic if the thread is already panicking/unwinding. 74 | /// This is because otherwise there would be double panics (usually 75 | /// resulting in an abort) when dereferencing from a wrong thread. 76 | /// - If `T` has a trivial drop ([`needs_drop::()`] is false) then this 77 | /// method never panics. 78 | /// 79 | /// [`needs_drop::()`]: std::mem::needs_drop 80 | #[track_caller] 81 | fn drop(&mut self) { 82 | // If the drop is trivial (`needs_drop` = false), then dropping `T` can't access 83 | // it and so it can be safely dropped on any thread. 84 | if !mem::needs_drop::() || self.valid() { 85 | unsafe { 86 | // Drop the inner value 87 | // 88 | // Safety: 89 | // - We've just checked that it's valid to drop `T` on this thread 90 | // - We only move out from `self.data` here and in drop, so `self.data` is 91 | // present 92 | ManuallyDrop::drop(&mut self.data); 93 | } 94 | } else { 95 | invalid_drop() 96 | } 97 | } 98 | } 99 | 100 | #[cold] 101 | #[inline(never)] 102 | #[track_caller] 103 | fn invalid_drop() { 104 | const DROP_ERROR: &str = "Dropped SendWrapper variable from a thread different to the one \ 105 | it has been created with."; 106 | 107 | if !thread::panicking() { 108 | // panic because of dropping from wrong thread 109 | // only do this while not unwinding (could be caused by deref from wrong thread) 110 | panic!("{}", DROP_ERROR) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /compio-runtime/tests/event.rs: -------------------------------------------------------------------------------- 1 | use std::panic::resume_unwind; 2 | 3 | use compio_runtime::event::Event; 4 | 5 | #[test] 6 | fn event_handle() { 7 | compio_runtime::Runtime::new().unwrap().block_on(async { 8 | let event = Event::new(); 9 | let handle = event.handle(); 10 | let task = compio_runtime::spawn_blocking(move || { 11 | handle.notify(); 12 | }); 13 | event.wait().await; 14 | task.await.unwrap_or_else(|e| resume_unwind(e)); 15 | }) 16 | } 17 | 18 | #[test] 19 | #[cfg(windows)] 20 | fn win32_event() { 21 | use std::{ 22 | os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle}, 23 | pin::Pin, 24 | ptr::null, 25 | task::Poll, 26 | }; 27 | 28 | use compio_driver::{OpCode, OpType, syscall}; 29 | use windows_sys::Win32::System::{ 30 | IO::OVERLAPPED, 31 | Threading::{CreateEventW, SetEvent}, 32 | }; 33 | 34 | struct WaitEvent { 35 | event: OwnedHandle, 36 | } 37 | 38 | impl OpCode for WaitEvent { 39 | fn op_type(&self) -> OpType { 40 | OpType::Event(self.event.as_raw_handle() as _) 41 | } 42 | 43 | unsafe fn operate( 44 | self: Pin<&mut Self>, 45 | _optr: *mut OVERLAPPED, 46 | ) -> Poll> { 47 | Poll::Ready(Ok(0)) 48 | } 49 | } 50 | 51 | compio_runtime::Runtime::new().unwrap().block_on(async { 52 | let event = syscall!(BOOL, CreateEventW(null(), 0, 0, null())).unwrap(); 53 | let event = unsafe { OwnedHandle::from_raw_handle(event as _) }; 54 | 55 | let event_raw = event.as_raw_handle() as _; 56 | 57 | let wait = compio_runtime::submit(WaitEvent { event }); 58 | 59 | let task = compio_runtime::spawn_blocking(move || { 60 | unsafe { SetEvent(event_raw) }; 61 | }); 62 | 63 | wait.await.0.unwrap(); 64 | task.await.unwrap_or_else(|e| resume_unwind(e)); 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /compio-signal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-signal" 3 | version = "0.5.0" 4 | description = "Signal handling for compio" 5 | categories = ["asynchronous"] 6 | keywords = ["async", "signal"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | # Workspace dependencies 19 | compio-runtime = { workspace = true, features = ["event"] } 20 | 21 | # Windows specific dependencies 22 | [target.'cfg(windows)'.dependencies] 23 | compio-driver = { workspace = true } 24 | once_cell = { workspace = true } 25 | slab = { workspace = true } 26 | windows-sys = { workspace = true, features = [ 27 | "Win32_Foundation", 28 | "Win32_System_Console", 29 | ] } 30 | 31 | # Linux specific dependencies 32 | [target.'cfg(target_os = "linux")'.dependencies] 33 | compio-buf = { workspace = true } 34 | compio-driver = { workspace = true } 35 | 36 | # Unix specific dependencies 37 | [target.'cfg(unix)'.dependencies] 38 | libc = { workspace = true } 39 | 40 | [target.'cfg(all(unix, not(target_os = "linux")))'.dependencies] 41 | once_cell = { workspace = true } 42 | os_pipe = { workspace = true } 43 | slab = { workspace = true } 44 | 45 | [features] 46 | io-uring = ["compio-runtime/io-uring"] 47 | polling = ["compio-runtime/polling"] 48 | # Nightly features 49 | lazy_cell = [] 50 | once_cell_try = [] 51 | nightly = ["lazy_cell", "once_cell_try"] 52 | -------------------------------------------------------------------------------- /compio-signal/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous signal handling. 2 | //! 3 | //! # Examples 4 | //! 5 | //! Print on "ctrl-c" notification. 6 | //! 7 | //! ```rust,no_run 8 | //! use compio_signal::ctrl_c; 9 | //! 10 | //! # compio_runtime::Runtime::new().unwrap().block_on(async { 11 | //! ctrl_c().await.unwrap(); 12 | //! println!("ctrl-c received!"); 13 | //! # }) 14 | //! ``` 15 | 16 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 17 | #![cfg_attr(feature = "once_cell_try", feature(once_cell_try))] 18 | #![cfg_attr(feature = "lazy_cell", feature(lazy_cell))] 19 | #![warn(missing_docs)] 20 | #![allow(stable_features)] 21 | 22 | #[cfg(windows)] 23 | pub mod windows; 24 | 25 | #[cfg(unix)] 26 | #[cfg_attr(target_os = "linux", path = "linux.rs")] 27 | pub mod unix; 28 | 29 | /// Completes when a "ctrl-c" notification is sent to the process. 30 | pub async fn ctrl_c() -> std::io::Result<()> { 31 | #[cfg(windows)] 32 | { 33 | windows::ctrl_c().await 34 | } 35 | #[cfg(unix)] 36 | { 37 | unix::signal(libc::SIGINT).await 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /compio-signal/src/linux.rs: -------------------------------------------------------------------------------- 1 | //! Linux-specific types for signal handling. 2 | 3 | use std::{ 4 | cell::RefCell, collections::HashMap, io, mem::MaybeUninit, os::fd::FromRawFd, ptr::null_mut, 5 | thread_local, 6 | }; 7 | 8 | use compio_buf::{BufResult, IntoInner, IoBuf, IoBufMut, SetBufInit}; 9 | use compio_driver::{OwnedFd, SharedFd, op::Recv, syscall}; 10 | 11 | thread_local! { 12 | static REG_MAP: RefCell> = RefCell::new(HashMap::new()); 13 | } 14 | 15 | fn sigset(sig: i32) -> io::Result { 16 | let mut set: MaybeUninit = MaybeUninit::uninit(); 17 | syscall!(libc::sigemptyset(set.as_mut_ptr()))?; 18 | syscall!(libc::sigaddset(set.as_mut_ptr(), sig))?; 19 | // SAFETY: sigemptyset initializes the set. 20 | Ok(unsafe { set.assume_init() }) 21 | } 22 | 23 | fn register_signal(sig: i32) -> io::Result { 24 | REG_MAP.with_borrow_mut(|map| { 25 | let count = map.entry(sig).or_default(); 26 | let set = sigset(sig)?; 27 | if *count == 0 { 28 | syscall!(libc::pthread_sigmask(libc::SIG_BLOCK, &set, null_mut()))?; 29 | } 30 | *count += 1; 31 | Ok(set) 32 | }) 33 | } 34 | 35 | fn unregister_signal(sig: i32) -> io::Result { 36 | REG_MAP.with_borrow_mut(|map| { 37 | let count = map.entry(sig).or_default(); 38 | if *count > 0 { 39 | *count -= 1; 40 | } 41 | let set = sigset(sig)?; 42 | if *count == 0 { 43 | syscall!(libc::pthread_sigmask(libc::SIG_UNBLOCK, &set, null_mut()))?; 44 | } 45 | Ok(set) 46 | }) 47 | } 48 | 49 | /// Represents a listener to unix signal event. 50 | #[derive(Debug)] 51 | struct SignalFd { 52 | fd: SharedFd, 53 | sig: i32, 54 | } 55 | 56 | impl SignalFd { 57 | fn new(sig: i32) -> io::Result { 58 | let set = register_signal(sig)?; 59 | let mut flag = libc::SFD_CLOEXEC; 60 | if cfg!(not(feature = "io-uring")) || compio_driver::DriverType::is_polling() { 61 | flag |= libc::SFD_NONBLOCK; 62 | } 63 | let fd = syscall!(libc::signalfd(-1, &set, flag))?; 64 | let fd = unsafe { OwnedFd::from_raw_fd(fd) }; 65 | Ok(Self { 66 | fd: SharedFd::new(fd), 67 | sig, 68 | }) 69 | } 70 | 71 | async fn wait(self) -> io::Result<()> { 72 | const INFO_SIZE: usize = std::mem::size_of::(); 73 | 74 | struct SignalInfo(MaybeUninit); 75 | 76 | unsafe impl IoBuf for SignalInfo { 77 | fn as_buf_ptr(&self) -> *const u8 { 78 | self.0.as_ptr().cast() 79 | } 80 | 81 | fn buf_len(&self) -> usize { 82 | 0 83 | } 84 | 85 | fn buf_capacity(&self) -> usize { 86 | INFO_SIZE 87 | } 88 | } 89 | 90 | unsafe impl IoBufMut for SignalInfo { 91 | fn as_buf_mut_ptr(&mut self) -> *mut u8 { 92 | self.0.as_mut_ptr().cast() 93 | } 94 | } 95 | 96 | impl SetBufInit for SignalInfo { 97 | unsafe fn set_buf_init(&mut self, len: usize) { 98 | debug_assert!(len <= INFO_SIZE) 99 | } 100 | } 101 | 102 | let info = SignalInfo(MaybeUninit::::uninit()); 103 | let op = Recv::new(self.fd.clone(), info); 104 | let BufResult(res, op) = compio_runtime::submit(op).await; 105 | let len = res?; 106 | debug_assert_eq!(len, INFO_SIZE); 107 | let info = op.into_inner(); 108 | let info = unsafe { info.0.assume_init() }; 109 | debug_assert_eq!(info.ssi_signo, self.sig as u32); 110 | Ok(()) 111 | } 112 | } 113 | 114 | impl Drop for SignalFd { 115 | fn drop(&mut self) { 116 | unregister_signal(self.sig).ok(); 117 | } 118 | } 119 | 120 | /// Creates a new listener which will receive notifications when the current 121 | /// process receives the specified signal. 122 | /// 123 | /// It sets the signal mask of the current thread. 124 | pub async fn signal(sig: i32) -> io::Result<()> { 125 | let fd = SignalFd::new(sig)?; 126 | fd.wait().await?; 127 | Ok(()) 128 | } 129 | -------------------------------------------------------------------------------- /compio-signal/src/windows.rs: -------------------------------------------------------------------------------- 1 | //! Windows-specific types for signal handling. 2 | 3 | #[cfg(feature = "lazy_cell")] 4 | use std::sync::LazyLock; 5 | #[cfg(feature = "once_cell_try")] 6 | use std::sync::OnceLock; 7 | use std::{collections::HashMap, io, sync::Mutex}; 8 | 9 | use compio_driver::syscall; 10 | use compio_runtime::event::{Event, EventHandle}; 11 | #[cfg(not(feature = "lazy_cell"))] 12 | use once_cell::sync::Lazy as LazyLock; 13 | #[cfg(not(feature = "once_cell_try"))] 14 | use once_cell::sync::OnceCell as OnceLock; 15 | use slab::Slab; 16 | use windows_sys::Win32::{ 17 | Foundation::BOOL, 18 | System::Console::{ 19 | CTRL_BREAK_EVENT, CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT, 20 | SetConsoleCtrlHandler, 21 | }, 22 | }; 23 | 24 | static HANDLER: LazyLock>>> = 25 | LazyLock::new(|| Mutex::new(HashMap::new())); 26 | 27 | unsafe extern "system" fn ctrl_event_handler(ctrltype: u32) -> BOOL { 28 | let mut handler = HANDLER.lock().unwrap(); 29 | if let Some(handlers) = handler.get_mut(&ctrltype) { 30 | if !handlers.is_empty() { 31 | let handlers = std::mem::replace(handlers, Slab::new()); 32 | for (_, handler) in handlers { 33 | handler.notify(); 34 | } 35 | return 1; 36 | } 37 | } 38 | 0 39 | } 40 | 41 | static INIT: OnceLock<()> = OnceLock::new(); 42 | 43 | fn init() -> io::Result<()> { 44 | syscall!(BOOL, SetConsoleCtrlHandler(Some(ctrl_event_handler), 1))?; 45 | Ok(()) 46 | } 47 | 48 | fn register(ctrltype: u32, e: &Event) -> usize { 49 | let mut handler = HANDLER.lock().unwrap(); 50 | let handle = e.handle(); 51 | handler.entry(ctrltype).or_default().insert(handle) 52 | } 53 | 54 | fn unregister(ctrltype: u32, key: usize) { 55 | let mut handler = HANDLER.lock().unwrap(); 56 | if let Some(handlers) = handler.get_mut(&ctrltype) { 57 | if handlers.contains(key) { 58 | let _ = handlers.remove(key); 59 | } 60 | } 61 | } 62 | 63 | /// Represents a listener to console CTRL event. 64 | #[derive(Debug)] 65 | struct CtrlEvent { 66 | ctrltype: u32, 67 | event: Option, 68 | handler_key: usize, 69 | } 70 | 71 | impl CtrlEvent { 72 | pub(crate) fn new(ctrltype: u32) -> io::Result { 73 | INIT.get_or_try_init(init)?; 74 | 75 | let event = Event::new(); 76 | let handler_key = register(ctrltype, &event); 77 | Ok(Self { 78 | ctrltype, 79 | event: Some(event), 80 | handler_key, 81 | }) 82 | } 83 | 84 | pub async fn wait(mut self) { 85 | self.event 86 | .take() 87 | .expect("event could not be None") 88 | .wait() 89 | .await 90 | } 91 | } 92 | 93 | impl Drop for CtrlEvent { 94 | fn drop(&mut self) { 95 | unregister(self.ctrltype, self.handler_key); 96 | } 97 | } 98 | 99 | async fn ctrl_event(ctrltype: u32) -> io::Result<()> { 100 | let event = CtrlEvent::new(ctrltype)?; 101 | event.wait().await; 102 | Ok(()) 103 | } 104 | 105 | /// Creates a new listener which receives "ctrl-break" notifications sent to the 106 | /// process. 107 | pub async fn ctrl_break() -> io::Result<()> { 108 | ctrl_event(CTRL_BREAK_EVENT).await 109 | } 110 | 111 | /// Creates a new listener which receives "ctrl-close" notifications sent to the 112 | /// process. 113 | pub async fn ctrl_close() -> io::Result<()> { 114 | ctrl_event(CTRL_CLOSE_EVENT).await 115 | } 116 | 117 | /// Creates a new listener which receives "ctrl-c" notifications sent to the 118 | /// process. 119 | pub async fn ctrl_c() -> io::Result<()> { 120 | ctrl_event(CTRL_C_EVENT).await 121 | } 122 | 123 | /// Creates a new listener which receives "ctrl-logoff" notifications sent to 124 | /// the process. 125 | pub async fn ctrl_logoff() -> io::Result<()> { 126 | ctrl_event(CTRL_LOGOFF_EVENT).await 127 | } 128 | 129 | /// Creates a new listener which receives "ctrl-shutdown" notifications sent to 130 | /// the process. 131 | pub async fn ctrl_shutdown() -> io::Result<()> { 132 | ctrl_event(CTRL_SHUTDOWN_EVENT).await 133 | } 134 | -------------------------------------------------------------------------------- /compio-tls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "compio-tls" 3 | version = "0.5.0" 4 | description = "TLS adaptor with compio" 5 | categories = ["asynchronous", "network-programming"] 6 | keywords = ["async", "net", "tls"] 7 | edition = { workspace = true } 8 | authors = { workspace = true } 9 | readme = { workspace = true } 10 | license = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | [package.metadata.docs.rs] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [dependencies] 18 | compio-buf = { workspace = true } 19 | compio-io = { workspace = true, features = ["compat"] } 20 | 21 | native-tls = { version = "0.2.11", optional = true, features = ["alpn"] } 22 | rustls = { workspace = true, default-features = false, optional = true, features = [ 23 | "logging", 24 | "std", 25 | "tls12", 26 | ] } 27 | 28 | [dev-dependencies] 29 | compio-net = { workspace = true } 30 | compio-runtime = { workspace = true } 31 | compio-macros = { workspace = true } 32 | 33 | rustls = { workspace = true, default-features = false, features = ["ring"] } 34 | rustls-native-certs = { workspace = true } 35 | 36 | [features] 37 | default = ["native-tls"] 38 | all = ["native-tls", "rustls"] 39 | rustls = ["dep:rustls"] 40 | 41 | ring = ["rustls", "rustls/ring"] 42 | aws-lc-rs = ["rustls", "rustls/aws-lc-rs"] 43 | aws-lc-rs-fips = ["aws-lc-rs", "rustls/fips"] 44 | 45 | read_buf = ["compio-buf/read_buf", "compio-io/read_buf", "rustls?/read_buf"] 46 | nightly = ["read_buf"] 47 | -------------------------------------------------------------------------------- /compio-tls/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Async TLS streams. 2 | 3 | #![warn(missing_docs)] 4 | #![cfg_attr(feature = "read_buf", feature(read_buf, core_io_borrowed_buf))] 5 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 6 | 7 | #[cfg(all(not(feature = "native-tls"), not(feature = "rustls")))] 8 | compile_error!("You must choose at least one of these features: [\"native-tls\", \"rustls\"]"); 9 | 10 | #[cfg(feature = "native-tls")] 11 | pub use native_tls; 12 | #[cfg(feature = "rustls")] 13 | pub use rustls; 14 | 15 | mod adapter; 16 | mod stream; 17 | 18 | pub use adapter::*; 19 | pub use stream::*; 20 | -------------------------------------------------------------------------------- /compio-tls/src/stream/rtls.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use rustls::{ClientConnection, Error, IoState, Reader, ServerConnection, Writer}; 4 | 5 | #[derive(Debug)] 6 | enum TlsConnection { 7 | Client(ClientConnection), 8 | Server(ServerConnection), 9 | } 10 | 11 | impl TlsConnection { 12 | pub fn reader(&mut self) -> Reader<'_> { 13 | match self { 14 | Self::Client(c) => c.reader(), 15 | Self::Server(c) => c.reader(), 16 | } 17 | } 18 | 19 | pub fn writer(&mut self) -> Writer<'_> { 20 | match self { 21 | Self::Client(c) => c.writer(), 22 | Self::Server(c) => c.writer(), 23 | } 24 | } 25 | 26 | pub fn process_new_packets(&mut self) -> Result { 27 | match self { 28 | Self::Client(c) => c.process_new_packets(), 29 | Self::Server(c) => c.process_new_packets(), 30 | } 31 | } 32 | 33 | pub fn read_tls(&mut self, rd: &mut dyn io::Read) -> io::Result { 34 | match self { 35 | Self::Client(c) => c.read_tls(rd), 36 | Self::Server(c) => c.read_tls(rd), 37 | } 38 | } 39 | 40 | pub fn wants_read(&self) -> bool { 41 | match self { 42 | Self::Client(c) => c.wants_read(), 43 | Self::Server(c) => c.wants_read(), 44 | } 45 | } 46 | 47 | pub fn write_tls(&mut self, wr: &mut dyn io::Write) -> io::Result { 48 | match self { 49 | Self::Client(c) => c.write_tls(wr), 50 | Self::Server(c) => c.write_tls(wr), 51 | } 52 | } 53 | 54 | pub fn wants_write(&self) -> bool { 55 | match self { 56 | Self::Client(c) => c.wants_write(), 57 | Self::Server(c) => c.wants_write(), 58 | } 59 | } 60 | } 61 | 62 | #[derive(Debug)] 63 | pub struct TlsStream { 64 | inner: S, 65 | conn: TlsConnection, 66 | } 67 | 68 | impl TlsStream { 69 | pub fn new_client(inner: S, conn: ClientConnection) -> Self { 70 | Self { 71 | inner, 72 | conn: TlsConnection::Client(conn), 73 | } 74 | } 75 | 76 | pub fn new_server(inner: S, conn: ServerConnection) -> Self { 77 | Self { 78 | inner, 79 | conn: TlsConnection::Server(conn), 80 | } 81 | } 82 | 83 | pub fn get_mut(&mut self) -> &mut S { 84 | &mut self.inner 85 | } 86 | 87 | pub fn negotiated_alpn(&self) -> Option<&[u8]> { 88 | match &self.conn { 89 | TlsConnection::Client(client) => client.alpn_protocol(), 90 | TlsConnection::Server(server) => server.alpn_protocol(), 91 | } 92 | } 93 | } 94 | 95 | impl TlsStream { 96 | fn read_impl(&mut self, mut f: impl FnMut(Reader) -> io::Result) -> io::Result { 97 | loop { 98 | while self.conn.wants_read() { 99 | self.conn.read_tls(&mut self.inner)?; 100 | self.conn.process_new_packets().map_err(io::Error::other)?; 101 | } 102 | 103 | match f(self.conn.reader()) { 104 | Ok(len) => { 105 | return Ok(len); 106 | } 107 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => continue, 108 | Err(e) => return Err(e), 109 | } 110 | } 111 | } 112 | } 113 | 114 | impl io::Read for TlsStream { 115 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 116 | self.read_impl(|mut reader| reader.read(buf)) 117 | } 118 | 119 | #[cfg(feature = "read_buf")] 120 | fn read_buf(&mut self, mut buf: io::BorrowedCursor<'_>) -> io::Result<()> { 121 | self.read_impl(|mut reader| reader.read_buf(buf.reborrow())) 122 | } 123 | } 124 | 125 | impl io::Write for TlsStream { 126 | fn write(&mut self, buf: &[u8]) -> io::Result { 127 | self.flush()?; 128 | self.conn.writer().write(buf) 129 | } 130 | 131 | fn flush(&mut self) -> io::Result<()> { 132 | while self.conn.wants_write() { 133 | self.conn.write_tls(&mut self.inner)?; 134 | } 135 | self.inner.flush()?; 136 | Ok(()) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /compio-tls/tests/connect.rs: -------------------------------------------------------------------------------- 1 | use compio_io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; 2 | use compio_net::TcpStream; 3 | use compio_tls::TlsConnector; 4 | 5 | async fn connect(connector: TlsConnector) { 6 | let stream = TcpStream::connect("www.example.com:443").await.unwrap(); 7 | let mut stream = connector.connect("www.example.com", stream).await.unwrap(); 8 | 9 | stream 10 | .write_all("GET / HTTP/1.1\r\nHost:www.example.com\r\nConnection: close\r\n\r\n") 11 | .await 12 | .unwrap(); 13 | stream.flush().await.unwrap(); 14 | let (_, res) = stream.read_to_end(vec![]).await.unwrap(); 15 | println!("{}", String::from_utf8_lossy(&res)); 16 | } 17 | 18 | #[cfg(feature = "native-tls")] 19 | #[compio_macros::test] 20 | async fn native() { 21 | let connector = TlsConnector::from(native_tls::TlsConnector::new().unwrap()); 22 | 23 | connect(connector).await; 24 | } 25 | 26 | #[cfg(feature = "rustls")] 27 | #[compio_macros::test] 28 | async fn rtls() { 29 | let mut store = rustls::RootCertStore::empty(); 30 | for cert in rustls_native_certs::load_native_certs().unwrap() { 31 | store.add(cert).unwrap(); 32 | } 33 | 34 | let connector = TlsConnector::from(std::sync::Arc::new( 35 | rustls::ClientConfig::builder() 36 | .with_root_certificates(store) 37 | .with_no_client_auth(), 38 | )); 39 | 40 | connect(connector).await; 41 | } 42 | -------------------------------------------------------------------------------- /compio/benches/monoio_wrap/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, future::Future}; 2 | 3 | use criterion::async_executor::AsyncExecutor; 4 | 5 | pub struct MonoioRuntime(RefCell>); 6 | 7 | impl Default for MonoioRuntime { 8 | fn default() -> Self { 9 | Self::new() 10 | } 11 | } 12 | 13 | impl MonoioRuntime { 14 | pub fn new() -> Self { 15 | Self(RefCell::new( 16 | monoio::RuntimeBuilder::::new() 17 | .build() 18 | .unwrap(), 19 | )) 20 | } 21 | } 22 | 23 | impl AsyncExecutor for MonoioRuntime { 24 | fn block_on(&self, future: impl Future) -> T { 25 | self.0.borrow_mut().block_on(future) 26 | } 27 | } 28 | 29 | impl AsyncExecutor for &MonoioRuntime { 30 | fn block_on(&self, future: impl Future) -> T { 31 | self.0.borrow_mut().block_on(future) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /compio/examples/basic.rs: -------------------------------------------------------------------------------- 1 | use compio::{fs::OpenOptions, io::AsyncReadAtExt}; 2 | 3 | #[compio::main] 4 | async fn main() { 5 | let file = OpenOptions::new() 6 | .read(true) 7 | .open("Cargo.toml") 8 | .await 9 | .unwrap(); 10 | let (read, buffer) = file 11 | .read_to_end_at(Vec::with_capacity(4096), 0) 12 | .await 13 | .unwrap(); 14 | assert_eq!(read, buffer.len()); 15 | let buffer = String::from_utf8(buffer).unwrap(); 16 | println!("{buffer}"); 17 | file.close().await.unwrap(); 18 | } 19 | -------------------------------------------------------------------------------- /compio/examples/dispatcher.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroUsize; 2 | 3 | use compio::{ 4 | BufResult, 5 | dispatcher::Dispatcher, 6 | io::{AsyncRead, AsyncWriteExt}, 7 | net::{TcpListener, TcpStream}, 8 | runtime::spawn, 9 | }; 10 | use futures_util::{StreamExt, stream::FuturesUnordered}; 11 | 12 | #[compio::main] 13 | async fn main() { 14 | const THREAD_NUM: usize = 5; 15 | const CLIENT_NUM: usize = 10; 16 | 17 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 18 | let addr = listener.local_addr().unwrap(); 19 | let dispatcher = Dispatcher::builder() 20 | .worker_threads(NonZeroUsize::new(THREAD_NUM).unwrap()) 21 | .build() 22 | .unwrap(); 23 | spawn(async move { 24 | let mut futures = FuturesUnordered::from_iter((0..CLIENT_NUM).map(|i| { 25 | let addr = &addr; 26 | async move { 27 | let mut cli = TcpStream::connect(addr).await.unwrap(); 28 | cli.write_all(format!("Hello world {i}!")).await.unwrap(); 29 | } 30 | })); 31 | while let Some(()) = futures.next().await {} 32 | }) 33 | .detach(); 34 | let mut handles = FuturesUnordered::new(); 35 | for _i in 0..CLIENT_NUM { 36 | let (mut srv, _) = listener.accept().await.unwrap(); 37 | let handle = dispatcher 38 | .dispatch(move || async move { 39 | let BufResult(res, buf) = srv.read(Vec::with_capacity(20)).await; 40 | res.unwrap(); 41 | println!("{}", std::str::from_utf8(&buf).unwrap()); 42 | }) 43 | .unwrap(); 44 | handles.push(handle); 45 | } 46 | while handles.next().await.is_some() {} 47 | dispatcher.join().await.unwrap(); 48 | } 49 | -------------------------------------------------------------------------------- /compio/examples/driver.rs: -------------------------------------------------------------------------------- 1 | use compio::{ 2 | buf::IntoInner, 3 | driver::{ 4 | AsRawFd, OpCode, OwnedFd, Proactor, PushEntry, SharedFd, 5 | op::{CloseFile, ReadAt}, 6 | }, 7 | }; 8 | 9 | #[cfg(windows)] 10 | fn open_file(driver: &mut Proactor) -> OwnedFd { 11 | use std::os::windows::{ 12 | fs::OpenOptionsExt, 13 | io::{FromRawHandle, IntoRawHandle, OwnedHandle}, 14 | }; 15 | 16 | use compio::{BufResult, driver::op::Asyncify}; 17 | use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OVERLAPPED; 18 | 19 | let op = Asyncify::new(|| { 20 | BufResult( 21 | std::fs::OpenOptions::new() 22 | .read(true) 23 | .attributes(FILE_FLAG_OVERLAPPED) 24 | .open("Cargo.toml") 25 | .map(|f| f.into_raw_handle() as usize), 26 | (), 27 | ) 28 | }); 29 | let (fd, _) = push_and_wait(driver, op); 30 | OwnedFd::File(unsafe { OwnedHandle::from_raw_handle(fd as _) }) 31 | } 32 | 33 | #[cfg(unix)] 34 | fn open_file(driver: &mut Proactor) -> OwnedFd { 35 | use std::{ffi::CString, os::fd::FromRawFd}; 36 | 37 | use compio_driver::op::OpenFile; 38 | 39 | let op = OpenFile::new( 40 | CString::new("Cargo.toml").unwrap(), 41 | libc::O_CLOEXEC | libc::O_RDONLY, 42 | 0o666, 43 | ); 44 | let (fd, _) = push_and_wait(driver, op); 45 | unsafe { OwnedFd::from_raw_fd(fd as _) } 46 | } 47 | 48 | fn push_and_wait(driver: &mut Proactor, op: O) -> (usize, O) { 49 | match driver.push(op) { 50 | PushEntry::Ready(res) => res.unwrap(), 51 | PushEntry::Pending(mut user_data) => loop { 52 | driver.poll(None).unwrap(); 53 | match driver.pop(user_data) { 54 | PushEntry::Pending(k) => user_data = k, 55 | PushEntry::Ready((res, _)) => break res.unwrap(), 56 | } 57 | }, 58 | } 59 | } 60 | 61 | fn main() { 62 | let mut driver = Proactor::new().unwrap(); 63 | 64 | let fd = open_file(&mut driver); 65 | let fd = SharedFd::new(fd); 66 | driver.attach(fd.as_raw_fd()).unwrap(); 67 | 68 | let op = ReadAt::new(fd.clone(), 0, Vec::with_capacity(4096)); 69 | let (n, op) = push_and_wait(&mut driver, op); 70 | 71 | let mut buffer = op.into_inner(); 72 | unsafe { 73 | buffer.set_len(n); 74 | } 75 | println!("{}", String::from_utf8(buffer).unwrap()); 76 | 77 | let op = CloseFile::new(fd.try_unwrap().unwrap()); 78 | push_and_wait(&mut driver, op); 79 | } 80 | -------------------------------------------------------------------------------- /compio/examples/named_pipe.rs: -------------------------------------------------------------------------------- 1 | use compio::{ 2 | BufResult, 3 | io::{AsyncReadExt, AsyncWriteExt}, 4 | }; 5 | 6 | #[compio::main] 7 | async fn main() { 8 | #[cfg(windows)] 9 | { 10 | use compio::fs::named_pipe::{ClientOptions, ServerOptions}; 11 | 12 | const PIPE_NAME: &str = r"\\.\pipe\compio-named-pipe"; 13 | 14 | let mut server = ServerOptions::new() 15 | .access_inbound(false) 16 | .create(PIPE_NAME) 17 | .unwrap(); 18 | let mut client = ClientOptions::new() 19 | .write(false) 20 | .open(PIPE_NAME) 21 | .await 22 | .unwrap(); 23 | 24 | server.connect().await.unwrap(); 25 | 26 | let write = server.write_all("Hello world!"); 27 | let buffer = Vec::with_capacity(12); 28 | let read = client.read_exact(buffer); 29 | 30 | let (BufResult(write, _), BufResult(read, buffer)) = futures_util::join!(write, read); 31 | write.unwrap(); 32 | read.unwrap(); 33 | println!("{}", String::from_utf8(buffer).unwrap()); 34 | } 35 | #[cfg(unix)] 36 | { 37 | use compio::fs::pipe::OpenOptions; 38 | use nix::{sys::stat::Mode, unistd::mkfifo}; 39 | use tempfile::tempdir; 40 | 41 | let dir = tempdir().unwrap(); 42 | let file = dir.path().join("compio-named-pipe"); 43 | 44 | mkfifo(&file, Mode::S_IRWXU).unwrap(); 45 | let options = OpenOptions::new(); 46 | let (mut rx, mut tx) = 47 | futures_util::try_join!(options.open_receiver(&file), options.open_sender(&file)) 48 | .unwrap(); 49 | 50 | let write = tx.write_all("Hello world!"); 51 | let buffer = Vec::with_capacity(12); 52 | let read = rx.read_exact(buffer); 53 | 54 | let (BufResult(write, _), BufResult(read, buffer)) = futures_util::join!(write, read); 55 | write.unwrap(); 56 | read.unwrap(); 57 | println!("{}", String::from_utf8(buffer).unwrap()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /compio/examples/net.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | use compio::{ 4 | io::{AsyncReadExt, AsyncWriteExt}, 5 | net::{TcpListener, TcpStream}, 6 | }; 7 | 8 | #[compio::main] 9 | async fn main() { 10 | let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap(); 11 | let addr = listener.local_addr().unwrap(); 12 | 13 | let (mut tx, (mut rx, _)) = 14 | futures_util::try_join!(TcpStream::connect(&addr), listener.accept()).unwrap(); 15 | 16 | tx.write_all("Hello world!").await.0.unwrap(); 17 | 18 | let buffer = Vec::with_capacity(12); 19 | let ((), buffer) = rx.read_exact(buffer).await.unwrap(); 20 | println!("{}", String::from_utf8(buffer).unwrap()); 21 | } 22 | -------------------------------------------------------------------------------- /compio/examples/resolve.rs: -------------------------------------------------------------------------------- 1 | use compio::net::ToSocketAddrsAsync; 2 | use futures_util::{StreamExt, stream::FuturesUnordered}; 3 | 4 | #[compio::main] 5 | async fn main() { 6 | let mut tasks = std::env::args() 7 | .skip(1) 8 | .map(|name| async move { 9 | ( 10 | (name.as_str(), 0).to_socket_addrs_async().await.unwrap(), 11 | name, 12 | ) 13 | }) 14 | .collect::>(); 15 | while let Some((addrs, name)) = tasks.next().await { 16 | println!("Address of {name}"); 17 | for addr in addrs { 18 | println!(" {}", addr.ip()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /compio/examples/tick.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use compio::{signal::ctrl_c, time::interval}; 4 | use futures_util::{FutureExt, select}; 5 | 6 | #[compio::main] 7 | async fn main() { 8 | let mut interval = interval(Duration::from_secs(2)); 9 | loop { 10 | let ctrlc = ctrl_c(); 11 | let ctrlc = std::pin::pin!(ctrlc); 12 | select! { 13 | res = ctrlc.fuse() => { 14 | res.unwrap(); 15 | println!("break"); 16 | break; 17 | }, 18 | _ = interval.tick().fuse() => println!("ping"), 19 | } 20 | } 21 | println!("exit first loop"); 22 | loop { 23 | interval.tick().await; 24 | println!("ping"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /compio/examples/unix.rs: -------------------------------------------------------------------------------- 1 | use compio::{ 2 | io::{AsyncReadExt, AsyncWriteExt}, 3 | net::{UnixListener, UnixStream}, 4 | }; 5 | use tempfile::tempdir; 6 | 7 | #[compio::main] 8 | async fn main() { 9 | let dir = tempdir().unwrap(); 10 | let path = dir.path().join("unix-example.sock"); 11 | let listener = UnixListener::bind(&path).await.unwrap(); 12 | 13 | let addr = listener.local_addr().unwrap(); 14 | 15 | let (mut tx, (mut rx, _)) = 16 | futures_util::try_join!(UnixStream::connect_addr(&addr), listener.accept()).unwrap(); 17 | 18 | assert_eq!(addr, tx.peer_addr().unwrap()); 19 | 20 | tx.write_all("Hello world!").await.0.unwrap(); 21 | 22 | let buffer = Vec::with_capacity(12); 23 | let (_, buffer) = rx.read_exact(buffer).await.unwrap(); 24 | println!("{}", String::from_utf8(buffer).unwrap()); 25 | } 26 | -------------------------------------------------------------------------------- /compio/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Compio 2 | //! A thread-per-core Rust runtime with IOCP/io_uring/polling. 3 | //! The name comes from "completion-based IO". 4 | //! This crate is inspired by [monoio](https://github.com/bytedance/monoio/). 5 | //! 6 | //! ## Quick start 7 | //! ```rust 8 | //! # compio::runtime::Runtime::new().unwrap().block_on(async { 9 | //! use compio::{fs::File, io::AsyncReadAtExt}; 10 | //! 11 | //! let file = File::open("Cargo.toml").await.unwrap(); 12 | //! let (read, buffer) = file 13 | //! .read_to_end_at(Vec::with_capacity(1024), 0) 14 | //! .await 15 | //! .unwrap(); 16 | //! assert_eq!(read, buffer.len()); 17 | //! let buffer = String::from_utf8(buffer).unwrap(); 18 | //! println!("{}", buffer); 19 | //! # }) 20 | //! ``` 21 | 22 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 23 | #![warn(missing_docs)] 24 | 25 | #[doc(no_inline)] 26 | pub use buf::BufResult; 27 | #[cfg(feature = "arrayvec")] 28 | pub use buf::arrayvec; 29 | #[cfg(feature = "bumpalo")] 30 | pub use buf::bumpalo; 31 | #[cfg(feature = "bytes")] 32 | pub use buf::bytes; 33 | #[cfg(feature = "dispatcher")] 34 | #[doc(inline)] 35 | pub use compio_dispatcher as dispatcher; 36 | #[cfg(feature = "io")] 37 | #[doc(inline)] 38 | pub use compio_io as io; 39 | #[cfg(feature = "macros")] 40 | pub use compio_macros::*; 41 | #[cfg(feature = "process")] 42 | #[doc(inline)] 43 | pub use compio_process as process; 44 | #[cfg(feature = "quic")] 45 | #[doc(inline)] 46 | pub use compio_quic as quic; 47 | #[cfg(feature = "signal")] 48 | #[doc(inline)] 49 | pub use compio_signal as signal; 50 | #[cfg(feature = "tls")] 51 | #[doc(inline)] 52 | pub use compio_tls as tls; 53 | #[cfg(feature = "event")] 54 | #[doc(no_inline)] 55 | pub use runtime::event; 56 | #[cfg(feature = "time")] 57 | #[doc(no_inline)] 58 | pub use runtime::time; 59 | #[cfg(feature = "native-tls")] 60 | pub use tls::native_tls; 61 | #[cfg(feature = "rustls")] 62 | pub use tls::rustls; 63 | #[doc(inline)] 64 | pub use {compio_buf as buf, compio_driver as driver}; 65 | #[cfg(feature = "runtime")] 66 | #[doc(inline)] 67 | pub use {compio_fs as fs, compio_net as net, compio_runtime as runtime}; 68 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1748190013, 24 | "narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "62b852f6c6742134ade1abdd2a21685fd617a291", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs", 41 | "rust-overlay": "rust-overlay" 42 | } 43 | }, 44 | "rust-overlay": { 45 | "inputs": { 46 | "nixpkgs": [ 47 | "nixpkgs" 48 | ] 49 | }, 50 | "locked": { 51 | "lastModified": 1748399823, 52 | "narHash": "sha256-kahD8D5hOXOsGbNdoLLnqCL887cjHkx98Izc37nDjlA=", 53 | "owner": "oxalica", 54 | "repo": "rust-overlay", 55 | "rev": "d68a69dc71bc19beb3479800392112c2f6218159", 56 | "type": "github" 57 | }, 58 | "original": { 59 | "owner": "oxalica", 60 | "repo": "rust-overlay", 61 | "type": "github" 62 | } 63 | }, 64 | "systems": { 65 | "locked": { 66 | "lastModified": 1681028828, 67 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 68 | "owner": "nix-systems", 69 | "repo": "default", 70 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "nix-systems", 75 | "repo": "default", 76 | "type": "github" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Compio dev shell"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | rust-overlay.url = "github:oxalica/rust-overlay"; 7 | flake-utils.url = "github:numtide/flake-utils"; 8 | rust-overlay.inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | 11 | outputs = { 12 | nixpkgs, 13 | rust-overlay, 14 | flake-utils, 15 | ... 16 | }: 17 | flake-utils.lib.eachDefaultSystem ( 18 | system: let 19 | overlays = [(import rust-overlay)]; 20 | pkgs = import nixpkgs { 21 | inherit system overlays; 22 | }; 23 | in 24 | with pkgs; { 25 | devShells.default = mkShell { 26 | buildInputs = [ 27 | go 28 | cmake 29 | glib 30 | openssl 31 | pkg-config 32 | (rust-bin.selectLatestNightlyWith (toolchain: 33 | toolchain.default.override { 34 | extensions = ["rust-src"]; 35 | })) 36 | ]; 37 | }; 38 | } 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | 3 | style_edition = "2024" 4 | 5 | group_imports = "StdExternalCrate" 6 | imports_granularity = "Crate" 7 | reorder_imports = true 8 | 9 | wrap_comments = true 10 | normalize_comments = true 11 | 12 | reorder_impl_items = true 13 | condense_wildcard_suffixes = true 14 | enum_discrim_align_threshold = 20 15 | use_field_init_shorthand = true 16 | 17 | format_strings = true 18 | format_code_in_doc_comments = true 19 | format_macro_matchers = true 20 | --------------------------------------------------------------------------------