tokio-uring-0.4.0/.cargo/config.toml000064400000000000000000000000521046102023000154040ustar 00000000000000[env] RUST_TEST_THREADS = { value = "1" } tokio-uring-0.4.0/.cargo_vcs_info.json0000644000000001360000000000100133040ustar { "git": { "sha1": "a98b29237e0f43aae6f0abbaf5e8771ce99b0e5c" }, "path_in_vcs": "" }tokio-uring-0.4.0/.github/workflows/ci.yml000064400000000000000000000034001046102023000166040ustar 00000000000000name: CI on: pull_request: branches: - master push: branches: - master env: RUSTFLAGS: -Dwarnings RUST_BACKTRACE: 1 jobs: # Depends on all actions that are required for a "successful" CI run. # Based on the ci here: https://github.com/tokio-rs/tokio/blob/master/.github/workflows/ci.yml all-systems-go: runs-on: ubuntu-latest needs: - check - clippy - fmt - test - test-docs - docs steps: - run: exit 0 bench: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo bench --no-run check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo check clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo clippy test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo test test-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo test --doc fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update stable - run: cargo fmt -- --check docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust run: rustup update nightly && rustup default nightly - run: cargo doc --no-deps --all-features env: RUSTDOCFLAGS: -Dwarnings tokio-uring-0.4.0/.gitignore000064400000000000000000000000231046102023000140570ustar 00000000000000/target Cargo.lock tokio-uring-0.4.0/CHANGELOG.md000064400000000000000000000114211046102023000137040ustar 00000000000000# 0.4.0 (November 5th, 2022) ### Fixed - Fix panic in Deref/DerefMut for Slice extending into uninitialized part of the buffer ([#52]) - docs: all-features = true ([#84]) - fix fs unit tests to avoid parallelism ([#121]) - Box the socket address to allow moving the Connect future ([#126]) - rt: Fix data race ([#146]) ### Added - Implement fs::File::readv_at()/writev_at() ([#87]) - fs: implement FromRawFd for File ([#89]) - Implement `AsRawFd` for `TcpStream` ([#94]) - net: add TcpListener.local_addr method ([#107]) - net: add TcpStream.write_all ([#111]) - driver: add Builder API as an option to start ([#113]) - Socket and TcpStream shutdown ([#124]) - fs: implement fs::File::from_std ([#131]) - net: implement FromRawFd for TcpStream ([#132]) - fs: implement OpenOptionsExt for OpenOptions ([#133]) - Add NoOp support ([#134]) - Add writev to TcpStream ([#136]) - sync TcpStream, UnixStream and UdpSocket functionality ([#141]) - Add benchmarks for no-op submission ([#144]) - Expose runtime structure ([#148]) ### Changed - driver: batch submit requests and add benchmark ([#78]) - Depend on io-uring version ^0.5.8 ([#153]) ### Internal Improvements - chore: fix clippy lints ([#99]) - io: refactor post-op logic in ops into Completable ([#116]) - Support multi completion events: v2 ([#130]) - simplify driver operation futures ([#139]) - rt: refactor runtime to avoid Rc\> ([#142]) - Remove unused dev-dependencies ([#143]) - chore: types and fields explicitly named ([#149]) - Ignore errors from uring while cleaning up ([#154]) - rt: drop runtime before driver during shutdown ([#155]) - rt: refactor drop logic ([#157]) - rt: fix error when calling block_on twice ([#162]) ### CI changes - chore: update actions/checkout action to v3 ([#90]) - chore: add all-systems-go ci check ([#98]) - chore: add clippy to ci ([#100]) - ci: run cargo test --doc ([#135]) [#52]: https://github.com/tokio-rs/tokio-uring/pull/52 [#78]: https://github.com/tokio-rs/tokio-uring/pull/78 [#84]: https://github.com/tokio-rs/tokio-uring/pull/84 [#87]: https://github.com/tokio-rs/tokio-uring/pull/87 [#89]: https://github.com/tokio-rs/tokio-uring/pull/89 [#90]: https://github.com/tokio-rs/tokio-uring/pull/90 [#94]: https://github.com/tokio-rs/tokio-uring/pull/94 [#98]: https://github.com/tokio-rs/tokio-uring/pull/98 [#99]: https://github.com/tokio-rs/tokio-uring/pull/99 [#100]: https://github.com/tokio-rs/tokio-uring/pull/100 [#107]: https://github.com/tokio-rs/tokio-uring/pull/107 [#111]: https://github.com/tokio-rs/tokio-uring/pull/111 [#113]: https://github.com/tokio-rs/tokio-uring/pull/113 [#116]: https://github.com/tokio-rs/tokio-uring/pull/116 [#121]: https://github.com/tokio-rs/tokio-uring/pull/121 [#124]: https://github.com/tokio-rs/tokio-uring/pull/124 [#126]: https://github.com/tokio-rs/tokio-uring/pull/126 [#130]: https://github.com/tokio-rs/tokio-uring/pull/130 [#131]: https://github.com/tokio-rs/tokio-uring/pull/131 [#132]: https://github.com/tokio-rs/tokio-uring/pull/132 [#133]: https://github.com/tokio-rs/tokio-uring/pull/133 [#134]: https://github.com/tokio-rs/tokio-uring/pull/134 [#135]: https://github.com/tokio-rs/tokio-uring/pull/135 [#136]: https://github.com/tokio-rs/tokio-uring/pull/136 [#139]: https://github.com/tokio-rs/tokio-uring/pull/139 [#141]: https://github.com/tokio-rs/tokio-uring/pull/141 [#142]: https://github.com/tokio-rs/tokio-uring/pull/142 [#143]: https://github.com/tokio-rs/tokio-uring/pull/143 [#144]: https://github.com/tokio-rs/tokio-uring/pull/144 [#146]: https://github.com/tokio-rs/tokio-uring/pull/146 [#148]: https://github.com/tokio-rs/tokio-uring/pull/148 [#149]: https://github.com/tokio-rs/tokio-uring/pull/149 [#153]: https://github.com/tokio-rs/tokio-uring/pull/153 [#154]: https://github.com/tokio-rs/tokio-uring/pull/154 [#155]: https://github.com/tokio-rs/tokio-uring/pull/155 [#157]: https://github.com/tokio-rs/tokio-uring/pull/157 [#162]: https://github.com/tokio-rs/tokio-uring/pull/162 # 0.3.0 (March 2nd, 2022) ### Added - net: add unix stream & listener ([#74]) - net: add tcp and udp support ([#40]) [#74]: https://github.com/tokio-rs/tokio-uring/pull/74 [#40]: https://github.com/tokio-rs/tokio-uring/pull/40 # 0.2.0 (January 9th, 2022) ### Fixed - fs: fix error handling related to changes in rustc ([#69]) - op: fix 'already borrowed' panic ([#39]) ### Added - fs: add fs::remove_file ([#66]) - fs: implement Debug for File ([#65]) - fs: add remove_dir and unlink ([#63]) - buf: impl IoBuf/IoBufMut for bytes::Bytes/BytesMut ([#43]) [#69]: https://github.com/tokio-rs/tokio-uring/pull/69 [#66]: https://github.com/tokio-rs/tokio-uring/pull/66 [#65]: https://github.com/tokio-rs/tokio-uring/pull/65 [#63]: https://github.com/tokio-rs/tokio-uring/pull/63 [#39]: https://github.com/tokio-rs/tokio-uring/pull/39 [#43]: https://github.com/tokio-rs/tokio-uring/pull/43 tokio-uring-0.4.0/Cargo.lock0000644000000571200000000000100112640ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "async-stream" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" dependencies = [ "async-stream-impl", "futures-core", ] [[package]] name = "async-stream-impl" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytes" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" [[package]] name = "ciborium-ll" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "bitflags", "clap_lex", "indexmap", "textwrap", ] [[package]] name = "clap_lex" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "criterion" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" dependencies = [ "anes", "atty", "cast", "ciborium", "clap", "criterion-plot", "itertools", "lazy_static", "num-traits", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-channel" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", "scopeguard", ] [[package]] name = "crossbeam-utils" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if", ] [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "fastrand" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" dependencies = [ "instant", ] [[package]] name = "futures" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-macro" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-util" version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "iai" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678" [[package]] name = "indexmap" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "io-uring" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00d78c9f2db2a9800dfd15c69543896dae2135112dde0d1944442e83da8ce23a" dependencies = [ "bitflags", "libc", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] [[package]] name = "mio" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi", "windows-sys", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "once_cell" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "oorandom" version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "os_str_bytes" version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" [[package]] name = "pin-project-lite" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "plotters" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" dependencies = [ "plotters-backend", ] [[package]] name = "proc-macro2" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", "num_cpus", ] [[package]] name = "redox_syscall" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scoped-tls" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "slab" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "socket2" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", ] [[package]] name = "syn" version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "tempfile" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", "fastrand", "libc", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "tokio" version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg", "libc", "mio", "pin-project-lite", "socket2", "winapi", ] [[package]] name = "tokio-stream" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-test" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", "bytes", "futures-core", "tokio", "tokio-stream", ] [[package]] name = "tokio-uring" version = "0.4.0" dependencies = [ "bytes", "criterion", "futures", "iai", "io-uring", "libc", "scoped-tls", "slab", "socket2", "tempfile", "tokio", "tokio-test", ] [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "walkdir" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" tokio-uring-0.4.0/Cargo.toml0000644000000034470000000000100113120ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "tokio-uring" version = "0.4.0" authors = ["Tokio Contributors "] description = """ io-uring support for the Tokio asynchronous runtime. """ homepage = "https://tokio.rs" documentation = "https://docs.rs/tokio-uring/0.4.0/tokio-uring" readme = "README.md" keywords = [ "async", "fs", "io-uring", ] categories = [ "asynchronous", "network-programming", ] license = "MIT" repository = "https://github.com/tokio-rs/tokio-uring" [package.metadata.docs.rs] all-features = true [[bench]] name = "lai_no_op" path = "benches/lai/no_op.rs" harness = false [[bench]] name = "criterion_no_op" path = "benches/criterion/no_op.rs" harness = false [dependencies.bytes] version = "1.0" optional = true [dependencies.io-uring] version = "0.5.8" features = ["unstable"] [dependencies.libc] version = "0.2.80" [dependencies.scoped-tls] version = "1.0.0" [dependencies.slab] version = "0.4.2" [dependencies.socket2] version = "0.4.4" features = ["all"] [dependencies.tokio] version = "1.2" features = [ "net", "rt", ] [dev-dependencies.criterion] version = "0.4.0" [dev-dependencies.futures] version = "0.3.25" [dev-dependencies.iai] version = "0.1.1" [dev-dependencies.tempfile] version = "3.2.0" [dev-dependencies.tokio] version = "1.21.0" [dev-dependencies.tokio-test] version = "0.4.2" tokio-uring-0.4.0/Cargo.toml.orig000064400000000000000000000022771046102023000147730ustar 00000000000000[package] name = "tokio-uring" version = "0.4.0" authors = ["Tokio Contributors "] edition = "2018" readme = "README.md" license = "MIT" documentation = "https://docs.rs/tokio-uring/0.4.0/tokio-uring" repository = "https://github.com/tokio-rs/tokio-uring" homepage = "https://tokio.rs" description = """ io-uring support for the Tokio asynchronous runtime. """ categories = ["asynchronous", "network-programming"] keywords = ["async", "fs", "io-uring"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tokio = { version = "1.2", features = ["net", "rt"] } scoped-tls = "1.0.0" slab = "0.4.2" libc = "0.2.80" io-uring = { version = "0.5.8", features = ["unstable"] } socket2 = { version = "0.4.4", features = ["all"] } bytes = { version = "1.0", optional = true } [dev-dependencies] tempfile = "3.2.0" tokio-test = "0.4.2" iai = "0.1.1" futures = "0.3.25" criterion = "0.4.0" # we use joinset in our tests tokio = "1.21.0" [package.metadata.docs.rs] all-features = true [[bench]] name = "lai_no_op" path = "benches/lai/no_op.rs" harness = false [[bench]] name = "criterion_no_op" path = "benches/criterion/no_op.rs" harness = false tokio-uring-0.4.0/DESIGN.md000064400000000000000000000763231046102023000134020ustar 00000000000000# Summary The RFC proposes a new asynchronous Rust runtime backed by [io-uring] as a new crate: tokio-uring. The API aims to be as close to idiomatic Tokio, but will deviate when necessary to provide full access to io-uring's capabilities. It also will be compatible with existing Tokio libraries. The runtime will use an isolated thread-per-core model, and many types will be `!Send`. [io-uring]: https://kernel.dk/io_uring.pdf?source=techstories.org # Motivation Tokio's current Linux implementation uses non-blocking system calls and epoll for event notification. With epoll, a tuned TCP proxy will spend [70% to 80%][overhead] of CPU cycles outside of userspace, including cycles spent performing syscalls and copying data between the kernel and userspace. In 2019, Linux added a new API, io-uring, which reduces overhead by eliminating most syscalls and mapping memory regions used for byte buffers ahead of time. Early benchmarks comparing io-uring against epoll are promising; a TCP echo client and server implemented in C show up to [60% improvement][bench]. Though not yet measured, using io-uring instead of Tokio's thread-pool strategy will likely provide significant gains for file system operations. Because io-uring differs significantly from epoll, Tokio must provide a new set of APIs to take full advantage of the reduced overhead. However, Tokio's [stability guarantee][stability] means Tokio APIs cannot change until 2024 at the earliest. Additionally, the io-uring API is still evolving with [new functionality][tweet] planned for the near future. Instead of waiting for io-uring to mature and a Tokio 2.0 release, we will release a standalone crate dedicated to exposing an io-uring optimal API. This new crate will be able to iterate rapidly with breaking changes without violating Tokio's stability guarantee. Applications deployed exclusively on Linux kernels 5.10 or later may choose to use this crate when taking full advantage of io-uring's benefits provides measurable benefits. Examples of intended use-cases include TCP proxies, HTTP file servers, and databases. [overhead]: https://www.usenix.org/system/files/conference/nsdi14/nsdi14-paper-jeong.pdf [bench]: https://github.com/frevib/io_uring-echo-server/blob/master/benchmarks/benchmarks.md [stability]: https://tokio.rs/blog/2020-12-tokio-1-0#a-stability-guarantee [tweet]: https://twitter.com/axboe/status/1371978266806919168 # Guide-level explanation The tokio-uring crate provides a module layout similar to the Tokio crate, including modules such as net (TCP, UDP, and Unix domain sockets), fs (file system access), io (standard in, out, and error streams). It also provides a runtime module containing an io-uring specific runtime. Modules such as sync and any other containing async Rust utilities are not included by tokio-uring, in favor of the ones provided by the main Tokio crate. ```toml [dependencies] tokio-uring = "0.1" ``` ```rust fn main() { let rt = tokio_uring::runtime::Runtime::new().unwrap(); rt.block_on(async { // The rest of the application comes here. }); } ``` The application's `main` function starts a tokio-uring runtime and launches its asynchronous logic within that. The tokio-uring runtime can drive both io-uring specific resources (e.g., `TcpStream` and `TcpListener`) and Tokio resources, enabling any library written for Tokio to run within the io-uring specific runtime. ## Submit-based operations Operations on io-uring backed resources return futures, representing the operation completion. The caller awaits the future to get the operation result. ```rust let socket = my_listener.accept().await?; ``` The runtime communicates with the kernel using two single-producer, single-consumer queues. It submits operation requests, such as accepting a TCP socket, to the kernel using the submission queue. The kernel then performs the operation. On completion, the kernel returns the operation results via the completion queue and notifies the process. The `io_uring_enter` syscall flushes the submission queue and acquires any pending completion events. Upon request, this syscall may block the thread waiting for a minimum number of completion events. Both queues use memory shared between the process and the kernel and synchronize with atomic operations. Operation futures provide an asynchronous cancel function, enabling the caller to await on a clean cancellation. ```rust let accept = tcp_listener.accept(); tokio::select! { (tcp_stream, addr) = &mut accept => { ... } _ = tokio::time::sleep(Duration::from_millis(100)) => { // Accept timed out, cancel the in-flight accept. match accept.cancel().await { Ok(_) => { ... } // operation canceled gracefully Err(Completed(tcp_stream, addr)) => { // The operation completed between the timeout // and cancellation request. } Err(_) => { ... } } } } ``` In practice, operation timeouts will deserve a dedicated API to handle the boilerplate. ```rust let (tcp_stream, addr) = tcp_listener .accept() .timeout(Duration::from_millis(100)) .await? ``` The cancel and timeout function are inherent methods on operation future types. If the operation's future drops before the operation completes, the runtime will submit a cancellation request for the operation. However, cancellation is asynchronous and best-effort, the operation may still complete. In that case, the runtime discards the operation result. The queue's single-producer characteristic optimizes for a single thread to own a given submission queue. Supporting a multi-threaded runtime requires either synchronizing pushing onto the submission queue or creating a queue pair per thread. The tokio-uring runtime uses an isolated thread-per-core model, differing from Tokio's other runtimes. Unlike Tokio's primary multi-threaded runtime, there is no work-stealing. Tasks remain on the thread that spawned them for the duration of their lifecycle. Each runtime thread will own a dedicated submission and completion queue pair, and operations are submitted using the submission queue associated with the current thread. Operation completion futures will not implement `Send`, guaranteeing that they remain on the thread to receive the operation result. Interestingly, the resources, e.g., `TcpListener`, can be `Send` as long as they do not hold operation futures internally. It is possible to have two operations in-flight from a single resource associated with different queues and threads. Because operations are not `Send`, tasks awaiting these operations are also not `Send`, making them unable to be spawned using the spawn function from the Tokio crate. The tokio-uring crate will provide a spawn function that accepts not `Send` tasks. When using multiple isolated runtime threads, balancing load between them becomes challenging. Applications must take care to ensure load remains balanced across threads, and strategies tend to vary. For example, a TCP server can distribute accepted connections across multiple threads, ideally while maintaining equal load across threads. One approach is to submit "accept" operations for the same listener concurrently across all the runtime threads while ensuring overloaded workers back-off. Defining runtime load is out of this crate's scope and left to the application, though pseudocode follows. ```rust let listener = Arc::new(TcpListener::bind(...)); spawn_on_each_thread(async { loop { // Wait for this worker to have capacity. This // ensures there are a minimum number of workers // in the runtime that are flagged as with capacity // to avoid total starvation. current_worker::wait_for_capacity().await; let socket = listener.accept().await; spawn(async move { ... }); } }) ``` ## Reading and writing Read and write operations require passing ownership of buffers to the kernel. When the operation completes, the kernel returns ownership of the buffer to the caller. The caller is responsible for allocating the buffer's memory and ensuring it remains alive until the operation completes. Additionally, while the kernel owns the memory, the process may not read from or write to the buffer. By designing the Rust APIs using ownership passing, Rust enforces the requirements at compile time. The following example demonstrates reading and writing with a file resource. ```rust use tokio_uring::buf; /// The result of an operation that includes a buffer. The buffer must /// be returned to the caller when the operation completes successfully /// or fails. /// /// This is implemented as a new type to implement std::ops::Try once /// the trait is stabilized. type BufResult = (std::io::Result, buf::Slice); // Positional read and write function definitions impl File { async fn read_at(&self, buf: buf::Slice, pos: u64) -> BufResult; async fn write_at(&self, buf: buf::Slice, pos: u64) -> BufResult; } /// The caller allocates a buffer let buf = buf::IoBuf::with_capacity(4096); // Read the first 1024 bytes of the file, when `std::ops::Try` is // stable, the `?` can be applied directly on the `BufResult` type. let BufResult(res, buf) = file.read_at(0, buf.slice(0..1024)).await; // Check the result. res?; // Write some data back to the file. let BufResult(res, _) = file.write_at(1024, buf.slice(0..512)).await; res?; ``` When reading from a TCP stream, read operations remain in flight until the socket receives data, an exchange that can take an arbitrary amount of time. Suppose each read operation requires pre-allocating memory for the in-flight operation. In that case, the amount of memory consumed by the process grows linearly with the number of in-flight read operations. For applications with a large number of open connections, this can be problematic. The io-uring API supports registering buffer pools with the ring and configuring read operations to use the buffer pool instead of a dedicated per-operation buffer. When the socket receives data, the kernel checks out a buffer from the pool and returns it to the caller. After reading the data, the caller returns the buffer to the kernel. If no buffers are available, the operation pauses until the process returns a buffer to the kernel, at which time the operation completes. ```rust // ... let buf = file.read_at_prepared(0, 1024, DefaultPool).await?; // Write some data back to the file. let BufResult(res, buf) = file.write_at(1024, buf.slice(0..512)).await; res?; // Dropping the buffer lets the kernel know the buffer may // be reused. drop(buf); ``` The runtime supports initializing multiple pools containing buffers of different sizes. When submitting a read operation, the caller specifies the pool from which to draw a buffer. ```rust // Allocate a buffer pool let my_pool = BufferPool::builder() .buffer_size(16_384) .num_buffers(256) .build(); // Create the runtime let mut rt = tokio_uring::runtime::Runtime::new()?; // Provide the buffer pool to the kernel. This passes // ownership of the pool to the kernel. let pool_token = rt.provide_buffers(my_pool)?; rt.block_on(async { // ... let buf = file.read_at_prepared(0, 1024, pool_token).await?; }); ``` ## Buffer management Buffers passed to read and write operations must remain alive and pinned to a memory location while operations are in-flight, ruling out `&mut [u8]` as an option. Additionally, read and write operations may reference buffers using a pointer or, when the buffer is part of a pre-registered buffer pool, using a numeric buffer identifier. The tokio-uring crate will provide its buffer type, `IoBuf`, to use when reading and writing. ```rust pub struct IoBuf { kind: Kind, } enum Kind { /// A vector-backed buffer Vec(Vec), /// Buffer pool backed buffer. The pool is managed by io-uring. Provided(ProvidedBuf), } ``` Internally, the IoBuf type is either backed by individually heap-allocated memory or a buffer pool entry. Acquiring an `IoBuf` is done via `IoBuf::with_capacity` or checking-out an entry from a pool. ```rust // Individually heap-allocated let my_buf = IoBuf::with_capacity(4096); // Checked-out from a pool match my_buffer_pool.checkout() { Ok(io_buf) => ..., Err(e) => panic!("buffer pool empty"), } ``` On drop, if the `IoBuf` is a buffer pool member, it is checked back in. If the kernel initially checked out the buffer as part of a read operation, an io-uring operation is issued to return it. Submitting the io-uring operation requires the buffer to remain on the same thread that checked it out and is enforced by making the `IoBuf` type `!Send`. Buffer pools will also be `!Send` as they contain `IoBuf` values. `IoBuf` provides an owned slice API allowing the caller to read to and write from a buffer's sub-ranges. ```rust pub struct Slice { buf: IoBuf, begin: usize, end: usize, } impl IoBuf { fn slice(self, range: impl ops::RangeBounds) -> Slice { .. } } // Write a sub-slice of a buffer my_file.write(my_io_buf.slice(10..20)).await ``` A slice end may go past the buffer's length but not past the capacity, enabling reads to uninitialized memory. ```rust // The buffer's memory is uninitialized let mut buf = IoBuf::with_capacity(4096); let slice = buf.slice(0..100); assert_eq!(slice.len(), 0); assert_eq!(slice.capacity(), 100); // Read data from a file into the buffer let BufResult(res, slice) = my_file.read_at(slice, 0); assert_eq!(slice.len(), 100); assert_eq!(slice.capacity(), 100); ``` A trait argument for reading and writing may be possible as a future improvement. Consider a read API that takes `T: AsMut<[u8]>` for the buffer argument. ```rust async fn read>(&self, dst: T) { ... } struct MyUnstableBuf { mem: [u8; 10], } impl AsMut<[u8]> for MyUnstableBuf { fn as_mut(&mut self) -> &mut [u8] { &mut self.mem[..] } } ``` This read function takes ownership of the buffer; however, any pointer to the buffer obtained from a value becomes invalid when the value moves. Storing the buffer value at a stable location while the operation is in-flight should be sufficient to satisfy safety. ## Closing resources Idiomatically with Rust, closing a resource is performed in the drop handler, and within an asynchronous context, the drop handler should be non-blocking. Closing an io-uring resource requires canceling any in-flight operations, which is an asynchronous process. Consider an open TcpStream associated with file-descriptor (FD) 10. A task submits a read operation to the kernel, but the `TcpStream` is dropped and closed before the kernel sees it. A new `TcpStream` is accepted, and the kernel reuses FD 10. At this point, the kernel sees the original read operation request with FD 10 and completes it on the new `TcpStream`, not the intended `TcpStream`. The runtime receives the completion of the read operation. It discards the result because the associated operation future is gone, resulting in the caller losing data from the second TcpStream. This problem occurs even issuing a cancellation request for the read operation. There is no guarantee the kernel will see the cancellation request before completing the operation. There are two options for respecting both requirements, neither ideal: closing the resource in the background or blocking the thread in the drop handler. If the resource is closed in the background, the process may encounter unexpected errors, such as "too many open files." Blocking the thread to cancel in-flight operations and close the resource prevents the runtime from processing other tasks and adds latency across the system. Instead, tokio-uring will provide an explicit asynchronous close function on resource types. ```rust impl TcpStream { async fn close(self) { ... } } my_tcp_stream.close().await; ``` The resource must still tolerate the caller dropping it without being explicitly closed. In this case, tokio-uring will close the resource in the background, avoiding blocking the runtime. The drop handler will move ownership of the resource handle to the runtime and submit cancellation requests for any in-flight operation. Once all existing in-flight operations complete, the runtime will submit a close operation. If the drop handler must process closing a resource in the background, it will notify the developer by emitting a warning message using [`tracing`]. In the future, it may be possible for Rust to provide a `#[must_not_drop]` attribute. This attribute will result in compilation warnings if the developer drops a resource without using the explicit close method. [`tracing`]: https://github.com/tokio-rs/tracing ## Byte streams Byte stream types, such as `TcpStream`, will not provide read and write methods (see "Alternatives" below for reasoning). Instead, byte streams will manage their buffers internally, as described in ["Notes on io-uring"][notes], and implement buffered I/O traits, such as AsyncBufRead. The caller starts by waiting for the byte stream to fill its internal buffer, reads the data, and marks the data as consumed. [notes]: https://without.boats/blog/io-uring/ ```rust // `fill_buf()` is provided by `AsyncBufRead` let data: &[u8] = my_stream.fill_buf().await?; println!("Got {} bytes", data.len()); // Consume the data my_stream.consume(data.len()); ``` Internally, byte streams submit read operations using the default buffer pool. Additional methods exist to take and place buffers, supporting zero-copy piping between two byte streams. ```rust my_tcp_stream.fill_buf().await?; let buf: IoBuf = my_tcp_stream.take_read_buf(); // Mutate `buf` if needed here. my_other_stream.place_write_buf(buf); my_other_stream.flush().await?; ``` Implementing buffer management on the `TcpStream` type requires tracking the in-flight read and write operations, making the `TcpStream` type `!Send`. Sending a `TcpStream` across threads is doable by first converting the io-uring `TcpStream` to a standard library `TcpStream`, sending that value to a new thread, and converting it back to an io-uring `TcpStream`. Unlike the standard library, the `File` type does not expose a byte stream directly. Instead, the caller requests a read or write stream, making it possible to support multiple concurrent streams. Each file stream maintains its cursor and issues positional read and write operations based on the cursor. ```rust let read_stream = my_file.read_stream(); let write_stream = my_file.write_stream(); read_stream.fill_buf().await? let buf: IoBuf = read_stream.take_read_buf(); write_stream.place_write_buf(buf); write_stream.flush().await?; // Because `read_stream` and `write_stream` maintain separate // cursors, `my_file` is unchanged at the end of this example. ``` Byte streams may have a configurable number of concurrent in-flight operations. Achieving maximum throughput [requires configuring][modern-storage] this value to take advantage of the underlying hardware's characteristics. [modern-storage]: https://itnext.io/modern-storage-is-plenty-fast-it-is-the-apis-that-are-bad-6a68319fbc1a ## Traits The tokio-uring crate will not expose any traits. The crate does not aim to be a point of abstraction for submission-based I/O models. Instead, to provide compatibility with the existing Rust asynchronous I/O ecosystem, byte stream types, such as `TcpStream`, will implement Tokio's [AsyncRead], [AsyncWrite], and [AsyncBufRead] traits. Using these traits requires an additional copy between the caller's buffer and the byte stream's internal buffer compared to taking and placing buffers. [AsyncRead]: https://docs.rs/tokio/1/tokio/io/trait.AsyncRead.html [AsyncWrite]: https://docs.rs/tokio/1/tokio/io/trait.AsyncWrite.html [AsyncBufRead]: https://docs.rs/tokio/1/tokio/io/trait.AsyncBufRead.html # Implementation details Creating the tokio-uring runtime initializes the io-uring submission and completion queues and a Tokio current-thread epoll-based runtime. Instead of waiting on completion events by blocking the thread on the io-uring completion queue, the tokio-uring runtime registers the completion queue with the epoll handle. By building tokio-uring on top of Tokio's runtime, existing Tokio ecosystem crates can work with the tokio-uring runtime. When the kernel pushes a completion event onto the completion queue, "epoll_wait" unblocks and returns a readiness event. The Tokio current-thread runtime then polls the io-uring driver task, draining the completion queue and notifying completion futures. Like Tokio, using an I/O type does not require explicitly referencing the runtime. Operations access the current runtime via a thread-local variable. Requiring a handle would be intrusive as the application must pass the handle throughout the code. Additionally, an explicit handle would require a pointer-sized struct, causing binary bloat. The disadvantage with such an approach is that there is no way to guarantee operation submission happens within the runtime context at compile time. Attempting to use a tokio-uring resource from outside of the runtime will result in a panic. ```rust use tokio_uring::runtime::Runtime; use tokio_uring::net::TcpListener; fn main() { // Binding a TcpListener does not require access to the runtime. let listener = TcpListener::bind("0.0.0.0:1234".parse().unwrap()); let rt = Runtime::new().unwrap(); rt.block_on(async { // This works, as `block_on` sets the thread-local variable. let _ = listener.accept().await; }); // BOOM: panics because called outside of the runtime futures::future::block_on(async { let _ = listener.accept().await; }); } ``` ## Operation state Most io-uring operations reference resources, such as buffers and file descriptors, for the kernel to use. These resources must remain available while the operation is in-flight. Any memory referenced by pointers must remain allocated, and the process must not access the memory. Because asynchronous Rust allows dropping futures at any time, the operation futures may not own data referenced by the in-flight operation. The tokio-uring runtime will take ownership and store resources referenced by operations while they are in-flight. ```rust struct IoUringDriver { // Storage for state referenced by in-flight operations in_flight_operations: Slab, // The io-uring submission and completion queues. queues: IoUringQueues, } struct Operation { // Resources referenced by the kernel, this must stay // available until the operation completes. state: State, lifecycle: Lifecycle, } enum State { // An in-flight read operation, `None` when reading into // a kernel-owned buffer pool Read { buffer: Option }, Write { buffer: buf::Slice }, // Accept a TCP socket Accept { ... } Close { ... } // ... other operations } enum Lifecycle { /// The operation has been submitted to uring and is currently in-flight Submitted, /// The submitter is waiting for the completion of the operation Waiting(Waker), /// The submitter no longer has interest in the operation result. Ignored, /// The operation has completed. The completion result is stored. Completed(Completion), } // Completion result returned from the kernel via the completion queue struct Completion { result: io::Result, flags: u32, } ``` The `Operation` struct holds any data referenced by the operation submitted to the kernel, preventing the data from being dropped early. The lifecycle field tracks if the operation is in-flight, has completed, or if the associated future has dropped. The lifecycle field also passes operation results from the driver to the future via the `Completed` variant. When a task starts a read operation, the runtime allocates an entry in the in-flight operation store, storing the buffer and initializing the lifecycle to `Submitted`. The runtime then pushes the operation to the submission queue but does not synchronize it with the kernel. Synchronization happens once the task yields to the runtime, enabling the task to submit multiple operations without synchronizing each one. Delaying synchronization can add a small amount of latency. While not enforced, tasks should execute for no more than 500 microseconds before yielding. When the runtime receives completion results, it must complete the associated operation future. The runtime loads the in-flight operation state, stores the result, transitions the lifecycle to `Completed`, and notifies the waker. The next time the caller's task executes, it polls the operation's future which completes and returns the stored result. If the operation's future drops before the operation completes and the operation request is still in the submission queue, the drop function removes the request. Otherwise, it sets the lifecycle to `Ignored` and submits a cancellation request to the kernel. The cancellation request will attempt to terminate the operation, causing it to complete immediately with an error. Cancellation is best-effort; the operation may or may not terminate early. If the operation does complete, the runtime discards the result. The runtime maintains the internal operation state until the completion as this state owns data the kernel may be referencing. ## Read operations Depending on the flavor, read operation can have multiple io-uring operation representations. The io-uring API provides two different read operation codes: `IORING_OP_READ` and `IORING_OP_READ_FIXED`. The first accepts a buffer as a pointer, and the second takes a buffer as an identifier referencing a buffer pool and entry. Additionally, the `IORING_OP_READ` operation can accept a null buffer pointer, indicating that io-uring should pick a buffer from a provided buffer pool. The tokio-uring runtime will determine which opcode to use based on the kind of `IoBuf` kind it receives. The `Vec` kind maps to the `IORING_OP_READ` opcode, and the `Provided` maps to `IORING_OP_READ_FIXED`. ## Prior art [Glommio] is an existing asynchronous Rust runtime built on top of io-uring. This proposal draws heavily from ideas presented there, tweaking concepts to line up with Tokio's idioms. [`@withoutboats`][boats] has explored the space with [ringbahn]. The tokio-uring crate is built on the pure-rust [io-uring] crate, authored by [@quininer]. This crate provides a low-level interface to the io-uring syscalls. [Glommio]: https://github.com/DataDog/glommio [boats]: https://github.com/withoutboats [ringbahn]: https://github.com/ringbahn/ringbahn [@quininer]: https://github.com/quininer/ [io-uring]: https://github.com/tokio-rs/io-uring/ ## Future work The main Tokio crate will likely adopts concepts from tokio-uring in the future. The most obvious area is Tokio's file system API, currently implemented using a thread-pool. The current API would remain, and the implementation would use io-uring when supported by the operating system. The tokio-uring APIs may form the basis for a Tokio 2.0 release, though this cannot happen until 2024 at the earliest. As an intermittent step, the tokio-uring crate could explore supporting alternate backends, such as epoll, kqueue, and iocp. The focus will always remain on io-uring. The current design does not cover registering file descriptors with the kernel, which improves file system access performance. After registering a file descriptor with io-uring, it must not move to a different thread, implying `!Send`. Because many resource types are Send, a likely path for supporting the feature is adding new types to represent the registered state. For example, `File` could have a `RegisteredFile` analog. Making this change would be forwards compatible and not impact the current design. ## Alternatives ### Use a work-stealing scheduler An alternative approach could use a work-stealing scheduler, allowing underutilized worker threads to steal work from overloaded workers. While work-stealing is an efficient strategy for balancing load across worker threads, required synchronization adds overhead. [An earlier article] on the Tokio blog includes an overview of various scheduling strategies. The tokio-uring crate targets use-cases that can benefit from taking advantage of io-uring at the expense of discarding Tokio's portable API. These use cases will also benefit from reduced synchronization overhead and fine-grained control over thread load balancing strategies. [An earlier article]: https://tokio.rs/blog/2019-10-scheduler ### Read and write methods on TcpStream Read and write operations on byte stream types, such as `TcpStream`, are stateful. Each operation operates on the next chunk of the stream, advancing a logical cursor. The combination of submit-based operations with drop-to-cancel semantics presents a challenge. Consider the following. ```rust let buf = IoBuf::with_capacity(4096); // This read will be canceled. select! { _ = my_tcp_stream.read(buf.slice(..)) => unreachable!(), _ = future::ready(()) => {} } let buf = IoBuf::with_capacity(4096); let (res, buf) = my_tcp_stream.read(buf.slice(..)).await; ``` Dropping the first read operation cancels it, and because it never completes, the second read operation should read the first packet of data from the TCP stream. However, the kernel may have already completed the first read operation before seeing the cancellation request. A naive runtime implementation would drop the completion result of any canceled operation, which would result in losing data. Instead, the runtime could preserve the data from the first read operation and return it as part of the second read. The process would not lose data, but the runtime would need to perform an extra copy or return the caller a different buffer than the one it submitted. The proposed API is not vulnerable to this issue as resources track their operations, preventing them from being dropped as long as the resource is open. ### Expose a raw io-uring operation submission API The proposed tokio-uring API does not include a strategy for the user to submit custom io-uring operations. Any raw API would be unsafe or would require the runtime to support taking opaque data as a trait object. Given that io-uring has a well-defined set of supported options, tokio-uring opts to support each operation explicitly. The application also can create its own set of io-uring queues using the io-uring crate directly. ### Load-balancing spawn function The tokio-uring crate omits a spawn function that balances tasks across threads, leaving this to future work. Instead, the application should manage its load balancing. While there are a few common balancing strategies, such as round-robin, randomized, and power of two choices, there is no one-size-fits-all strategy. Additionally, some load-balancing methods require application-specific metrics such as worker load. Additionally, consider pseudocode representing a typical accept loop pattern. ```rust loop { let (socket, _) = listener.accept().await?; spawn(async move { let request = read_request(&socket).await; let response = process(request).await; write_response(response, &socket).await; }); } ``` Often, the request data is already buffered when the socket is accepted. If the spawn call results in the task moving to a different worker thread, it will delay reading the request due to cross-thread synchronization. Additionally, it may be possible to batch an accept operation with an operation that reads from the accepted socket in the future, complicating moving the accepted socket to a new thread. The details of such a strategy are still not finalized but may impact a load-balancing spawn function. tokio-uring-0.4.0/LICENSE000064400000000000000000000020371046102023000131030ustar 00000000000000Copyright (c) 2021 Carl Lerche Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tokio-uring-0.4.0/README.md000064400000000000000000000041401046102023000133520ustar 00000000000000# tokio-uring This crate provides [`io-uring`] for [Tokio] by exposing a new Runtime that is compatible with Tokio but also can drive [`io-uring`]-backed resources. Any library that works with [Tokio] also works with `tokio-uring`. The crate provides new resource types that work with [`io-uring`]. [`io-uring`]: https://unixism.net/loti/ [Tokio]: https://github.com/tokio-rs/tokio [`fs::File`]: https://docs.rs/tokio-uring/latest/tokio_uring/fs/struct.File.html [API Docs](https://docs.rs/tokio-uring/latest/tokio_uring) | [Chat](https://discord.gg/tokio) # Getting started Using `tokio-uring` requires starting a [`tokio-uring`] runtime. This runtime internally manages the main Tokio runtime and a `io-uring` driver. In your Cargo.toml: ```toml [dependencies] tokio = { version = "0.4.0" } ``` In your main.rs: ```rust use tokio_uring::fs::File; fn main() -> Result<(), Box> { tokio_uring::start(async { // Open a file let file = File::open("hello.txt").await?; let buf = vec![0; 4096]; // Read some data, the buffer is passed by ownership and // submitted to the kernel. When the operation completes, // we get the buffer back. let (res, buf) = file.read_at(buf, 0).await; let n = res?; // Display the contents println!("{:?}", &buf[..n]); Ok(()) }) } ``` ## Requirements `tokio-uring` requires a very recent linux kernel. (Not even all kernels with io_uring support will work) In particular `5.4.0` does not work (This is standard on Ubuntu 20.4). However `5.11.0` (the ubuntu hwe image) does work. ## Project status The `tokio-uring` project is still very young. Currently, we are focusing on supporting filesystem and network operations. Eventually, we will add safe APIs for all io-uring compatible operations. ## License This project is licensed under the [MIT license]. [MIT license]: LICENSE ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in tokio-uring by you, shall be licensed as MIT, without any additional terms or conditions. tokio-uring-0.4.0/benches/criterion/no_op.rs000064400000000000000000000042461046102023000171670ustar 00000000000000use criterion::{ criterion_group, criterion_main, BenchmarkId, Criterion, SamplingMode, Throughput, }; use std::time::{Duration, Instant}; use futures::stream::{self, StreamExt}; #[derive(Clone)] struct Options { iterations: usize, concurrency: usize, sq_size: usize, cq_size: usize, } impl Default for Options { fn default() -> Self { Self { iterations: 100000, concurrency: 1, sq_size: 128, cq_size: 256, } } } fn run_no_ops(opts: &Options, count: u64) -> Duration { let mut ring_opts = tokio_uring::uring_builder(); ring_opts .setup_cqsize(opts.cq_size as _) // .setup_sqpoll(10) // .setup_sqpoll_cpu(1) ; let mut m = Duration::ZERO; // Run the required number of iterations for _ in 0..count { m += tokio_uring::builder() .entries(opts.sq_size as _) .uring_builder(&ring_opts) .start(async move { let start = Instant::now(); stream::iter(0..opts.iterations) .for_each_concurrent(Some(opts.concurrency), |_| async move { tokio_uring::no_op().await.unwrap(); }) .await; start.elapsed() }) } m } fn bench(c: &mut Criterion) { let mut group = c.benchmark_group("no_op"); let mut opts = Options::default(); for concurrency in [1, 32, 64, 256].iter() { opts.concurrency = *concurrency; // We perform long running benchmarks: this is the best mode group.sampling_mode(SamplingMode::Flat); group.throughput(Throughput::Elements(opts.iterations as u64)); group.bench_with_input( BenchmarkId::from_parameter(concurrency), &opts, |b, opts| { // Custom iterator used because we don't expose access to runtime, // which is required to do async benchmarking with criterion b.iter_custom(move |iter| run_no_ops(opts, iter)); }, ); } group.finish(); } criterion_group!(benches, bench); criterion_main!(benches); tokio-uring-0.4.0/benches/lai/no_op.rs000064400000000000000000000042261046102023000157340ustar 00000000000000use futures::stream::{self, StreamExt}; use iai::black_box; #[derive(Clone)] struct Options { iterations: usize, concurrency: usize, sq_size: usize, cq_size: usize, } impl Default for Options { fn default() -> Self { Self { iterations: 100000, concurrency: 1, sq_size: 64, cq_size: 256, } } } fn runtime_only() -> Result<(), Box> { let opts = Options::default(); let mut ring_opts = tokio_uring::uring_builder(); ring_opts .setup_cqsize(opts.cq_size as _) // .setup_sqpoll(10) // .setup_sqpoll_cpu(1) ; tokio_uring::builder() .entries(opts.sq_size as _) .uring_builder(&ring_opts) .start(async move { black_box(Ok(())) }) } fn run_no_ops(opts: Options) -> Result<(), Box> { let mut ring_opts = tokio_uring::uring_builder(); ring_opts .setup_cqsize(opts.cq_size as _) // .setup_sqpoll(10) // .setup_sqpoll_cpu(1) ; tokio_uring::builder() .entries(opts.sq_size as _) .uring_builder(&ring_opts) .start(async move { stream::iter(0..opts.iterations) .for_each_concurrent(Some(opts.concurrency), |_| async move { tokio_uring::no_op().await.unwrap(); }) .await; Ok(()) }) } // This provides a baseline for estimating op overhead on top of this fn no_op_x1() -> Result<(), Box> { let opts = Options::default(); run_no_ops(black_box(opts)) } fn no_op_x32() -> Result<(), Box> { let mut opts = Options::default(); opts.concurrency = 32; run_no_ops(black_box(opts)) } fn no_op_x64() -> Result<(), Box> { let mut opts = Options::default(); opts.concurrency = 64; run_no_ops(black_box(opts)) } fn no_op_x256() -> Result<(), Box> { let mut opts = Options::default(); opts.concurrency = 256; run_no_ops(black_box(opts)) } iai::main!(runtime_only, no_op_x1, no_op_x32, no_op_x64, no_op_x256); tokio-uring-0.4.0/examples/cat.rs000064400000000000000000000017121046102023000150300ustar 00000000000000use std::{ io::Write, {env, io}, }; use tokio_uring::fs::File; fn main() { // The file to `cat` is passed as a CLI argument let args: Vec<_> = env::args().collect(); if args.len() <= 1 { panic!("no path specified"); } let path = &args[1]; // Lock stdout let out = io::stdout(); let mut out = out.lock(); tokio_uring::start(async { // Open the file without blocking let file = File::open(path).await.unwrap(); let mut buf = vec![0; 16 * 1_024]; // Track the current position in the file; let mut pos = 0; loop { // Read a chunk let (res, b) = file.read_at(buf, pos).await; let n = res.unwrap(); if n == 0 { break; } out.write_all(&b[..n]).unwrap(); pos += n as u64; buf = b; } // Include a new line println!(""); }); } tokio-uring-0.4.0/examples/mix.rs000064400000000000000000000026611046102023000150620ustar 00000000000000//! Shows how use Tokio types from the `tokio-uring` runtime. //! //! Serve a single file over TCP use std::env; use tokio_uring::{fs::File, net::TcpListener}; fn main() { // The file to serve over TCP is passed as a CLI argument let args: Vec<_> = env::args().collect(); if args.len() <= 1 { panic!("no path specified"); } tokio_uring::start(async { // Start a TCP listener let listener = TcpListener::bind("0.0.0.0:8080".parse().unwrap()).unwrap(); // Accept new sockets loop { let (socket, _) = listener.accept().await.unwrap(); let path = args[1].clone(); // Spawn a task to send the file back to the socket tokio_uring::spawn(async move { // Open the file without blocking let file = File::open(path).await.unwrap(); let mut buf = vec![0; 16 * 1_024]; // Track the current position in the file; let mut pos = 0; loop { // Read a chunk let (res, b) = file.read_at(buf, pos).await; let n = res.unwrap(); if n == 0 { break; } let (res, b) = socket.write(b).await; pos += res.unwrap() as u64; buf = b; } }); } }); } tokio-uring-0.4.0/examples/tcp_listener.rs000064400000000000000000000027601046102023000167600ustar 00000000000000use std::{env, net::SocketAddr}; use tokio_uring::net::TcpListener; fn main() { let args: Vec<_> = env::args().collect(); let socket_addr = if args.len() <= 1 { "127.0.0.1:0" } else { args[1].as_ref() }; let socket_addr: SocketAddr = socket_addr.parse().unwrap(); tokio_uring::start(async { let listener = TcpListener::bind(socket_addr).unwrap(); println!("Listening on {}", listener.local_addr().unwrap()); loop { let (stream, socket_addr) = listener.accept().await.unwrap(); tokio_uring::spawn(async move { // implement ping-pong loop use tokio_uring::buf::IoBuf; // for slice() println!("{} connected", socket_addr); let mut n = 0; let mut buf = vec![0u8; 4096]; loop { let (result, nbuf) = stream.read(buf).await; buf = nbuf; let read = result.unwrap(); if read == 0 { println!("{} closed, {} total ping-ponged", socket_addr, n); break; } let (res, slice) = stream.write_all(buf.slice(..read)).await; let _ = res.unwrap(); buf = slice.into_inner(); println!("{} all {} bytes ping-ponged", socket_addr, read); n += read; } }); } }); } tokio-uring-0.4.0/examples/tcp_stream.rs000064400000000000000000000012001046102023000164120ustar 00000000000000use std::{env, net::SocketAddr}; use tokio_uring::net::TcpStream; fn main() { let args: Vec<_> = env::args().collect(); if args.len() <= 1 { panic!("no addr specified"); } let socket_addr: SocketAddr = args[1].parse().unwrap(); tokio_uring::start(async { let stream = TcpStream::connect(socket_addr).await.unwrap(); let buf = vec![1u8; 128]; let (result, buf) = stream.write(buf).await; println!("written: {}", result.unwrap()); let (result, buf) = stream.read(buf).await; let read = result.unwrap(); println!("read: {:?}", &buf[..read]); }); } tokio-uring-0.4.0/examples/udp_socket.rs000064400000000000000000000013471046102023000164250ustar 00000000000000use std::{env, net::SocketAddr}; use tokio_uring::net::UdpSocket; fn main() { let args: Vec<_> = env::args().collect(); if args.len() <= 1 { panic!("no addr specified"); } let socket_addr: SocketAddr = args[1].parse().unwrap(); tokio_uring::start(async { let socket = UdpSocket::bind(socket_addr).await.unwrap(); let buf = vec![0u8; 128]; let (result, mut buf) = socket.recv_from(buf).await; let (read, socket_addr) = result.unwrap(); buf.resize(read, 0); println!("received from {}: {:?}", socket_addr, &buf[..]); let (result, _buf) = socket.send_to(buf, socket_addr).await; println!("sent to {}: {}", socket_addr, result.unwrap()); }); } tokio-uring-0.4.0/examples/unix_listener.rs000064400000000000000000000015761046102023000171610ustar 00000000000000use std::env; use tokio_uring::net::UnixListener; fn main() { let args: Vec<_> = env::args().collect(); if args.len() <= 1 { panic!("no addr specified"); } let socket_addr: String = args[1].clone(); tokio_uring::start(async { let listener = UnixListener::bind(&socket_addr).unwrap(); loop { let stream = listener.accept().await.unwrap(); let socket_addr = socket_addr.clone(); tokio_uring::spawn(async move { let buf = vec![1u8; 128]; let (result, buf) = stream.write(buf).await; println!("written to {}: {}", &socket_addr, result.unwrap()); let (result, buf) = stream.read(buf).await; let read = result.unwrap(); println!("read from {}: {:?}", &socket_addr, &buf[..read]); }); } }); } tokio-uring-0.4.0/examples/unix_stream.rs000064400000000000000000000011341046102023000166150ustar 00000000000000use std::env; use tokio_uring::net::UnixStream; fn main() { let args: Vec<_> = env::args().collect(); if args.len() <= 1 { panic!("no addr specified"); } let socket_addr: &String = &args[1]; tokio_uring::start(async { let stream = UnixStream::connect(socket_addr).await.unwrap(); let buf = vec![1u8; 128]; let (result, buf) = stream.write(buf).await; println!("written: {}", result.unwrap()); let (result, buf) = stream.read(buf).await; let read = result.unwrap(); println!("read: {:?}", &buf[..read]); }); } tokio-uring-0.4.0/examples/wrk-bench.rs000064400000000000000000000022341046102023000161410ustar 00000000000000use std::io; use std::rc::Rc; use tokio::task::JoinHandle; pub const RESPONSE: &'static [u8] = b"HTTP/1.1 200 OK\nContent-Type: text/plain\nContent-Length: 12\n\nHello world!"; pub const ADDRESS: &'static str = "127.0.0.1:8080"; fn main() -> io::Result<()> { tokio_uring::start(async { let mut tasks = Vec::with_capacity(16); let listener = Rc::new(tokio_uring::net::TcpListener::bind( ADDRESS.parse().unwrap(), )?); for _ in 0..16 { let listener = listener.clone(); let task: JoinHandle> = tokio::task::spawn_local(async move { loop { let (stream, _) = listener.accept().await?; tokio_uring::spawn(async move { let (result, _) = stream.write(RESPONSE).await; if let Err(err) = result { eprintln!("Client connection failed: {}", err); } }); } }); tasks.push(task); } for t in tasks { t.await.unwrap()?; } Ok(()) }) } tokio-uring-0.4.0/src/buf/io_buf.rs000064400000000000000000000101451046102023000152510ustar 00000000000000use crate::buf::Slice; use std::ops; /// An `io-uring` compatible buffer. /// /// The `IoBuf` trait is implemented by buffer types that can be passed to /// io-uring operations. Users will not need to use this trait directly, except /// for the [`slice`] method. /// /// # Slicing /// /// Because buffers are passed by ownership to the runtime, Rust's slice API /// (`&buf[..]`) cannot be used. Instead, `tokio-uring` provides an owned slice /// API: [`slice()`]. The method takes ownership fo the buffer and returns a /// `Slice` type that tracks the requested offset. /// /// # Safety /// /// Buffers passed to `io-uring` operations must reference a stable memory /// region. While the runtime holds ownership to a buffer, the pointer returned /// by `stable_ptr` must remain valid even if the `IoBuf` value is moved. /// /// [`slice()`]: IoBuf::slice pub unsafe trait IoBuf: Unpin + 'static { /// Returns a raw pointer to the vector’s buffer. /// /// This method is to be used by the `tokio-uring` runtime and it is not /// expected for users to call it directly. /// /// The implementation must ensure that, while the `tokio-uring` runtime /// owns the value, the pointer returned by `stable_ptr` **does not** /// change. fn stable_ptr(&self) -> *const u8; /// Number of initialized bytes. /// /// This method is to be used by the `tokio-uring` runtime and it is not /// expected for users to call it directly. /// /// For `Vec`, this is identical to `len()`. fn bytes_init(&self) -> usize; /// Total size of the buffer, including uninitialized memory, if any. /// /// This method is to be used by the `tokio-uring` runtime and it is not /// expected for users to call it directly. /// /// For `Vec`, this is identical to `capacity()`. fn bytes_total(&self) -> usize; /// Returns a view of the buffer with the specified range. /// /// This method is similar to Rust's slicing (`&buf[..]`), but takes /// ownership of the buffer. /// /// # Examples /// /// ``` /// use tokio_uring::buf::IoBuf; /// /// let buf = b"hello world".to_vec(); /// buf.slice(5..10); /// ``` fn slice(self, range: impl ops::RangeBounds) -> Slice where Self: Sized, { use core::ops::Bound; let begin = match range.start_bound() { Bound::Included(&n) => n, Bound::Excluded(&n) => n + 1, Bound::Unbounded => 0, }; assert!(begin < self.bytes_total()); let end = match range.end_bound() { Bound::Included(&n) => n.checked_add(1).expect("out of range"), Bound::Excluded(&n) => n, Bound::Unbounded => self.bytes_total(), }; assert!(end <= self.bytes_total()); assert!(begin <= self.bytes_init()); Slice::new(self, begin, end) } } unsafe impl IoBuf for Vec { fn stable_ptr(&self) -> *const u8 { self.as_ptr() } fn bytes_init(&self) -> usize { self.len() } fn bytes_total(&self) -> usize { self.capacity() } } unsafe impl IoBuf for &'static [u8] { fn stable_ptr(&self) -> *const u8 { self.as_ptr() } fn bytes_init(&self) -> usize { <[u8]>::len(self) } fn bytes_total(&self) -> usize { self.bytes_init() } } unsafe impl IoBuf for &'static str { fn stable_ptr(&self) -> *const u8 { self.as_ptr() } fn bytes_init(&self) -> usize { ::len(self) } fn bytes_total(&self) -> usize { self.bytes_init() } } #[cfg(feature = "bytes")] unsafe impl IoBuf for bytes::Bytes { fn stable_ptr(&self) -> *const u8 { self.as_ptr() } fn bytes_init(&self) -> usize { self.len() } fn bytes_total(&self) -> usize { self.len() } } #[cfg(feature = "bytes")] unsafe impl IoBuf for bytes::BytesMut { fn stable_ptr(&self) -> *const u8 { self.as_ptr() } fn bytes_init(&self) -> usize { self.len() } fn bytes_total(&self) -> usize { self.capacity() } } tokio-uring-0.4.0/src/buf/io_buf_mut.rs000064400000000000000000000034741046102023000161450ustar 00000000000000use crate::buf::IoBuf; /// A mutable`io-uring` compatible buffer. /// /// The `IoBufMut` trait is implemented by buffer types that can be passed to /// io-uring operations. Users will not need to use this trait directly. /// /// # Safety /// /// Buffers passed to `io-uring` operations must reference a stable memory /// region. While the runtime holds ownership to a buffer, the pointer returned /// by `stable_mut_ptr` must remain valid even if the `IoBufMut` value is moved. pub unsafe trait IoBufMut: IoBuf { /// Returns a raw mutable pointer to the vector’s buffer. /// /// This method is to be used by the `tokio-uring` runtime and it is not /// expected for users to call it directly. /// /// The implementation must ensure that, while the `tokio-uring` runtime /// owns the value, the pointer returned by `stable_mut_ptr` **does not** /// change. fn stable_mut_ptr(&mut self) -> *mut u8; /// Updates the number of initialized bytes. /// /// The specified `pos` becomes the new value returned by /// `IoBuf::bytes_init`. /// /// # Safety /// /// The caller must ensure that all bytes starting at `stable_mut_ptr()` up /// to `pos` are initialized and owned by the buffer. unsafe fn set_init(&mut self, pos: usize); } unsafe impl IoBufMut for Vec { fn stable_mut_ptr(&mut self) -> *mut u8 { self.as_mut_ptr() } unsafe fn set_init(&mut self, init_len: usize) { if self.len() < init_len { self.set_len(init_len); } } } #[cfg(feature = "bytes")] unsafe impl IoBufMut for bytes::BytesMut { fn stable_mut_ptr(&mut self) -> *mut u8 { self.as_mut_ptr() } unsafe fn set_init(&mut self, init_len: usize) { if self.len() < init_len { self.set_len(init_len); } } } tokio-uring-0.4.0/src/buf/mod.rs000064400000000000000000000015401046102023000145640ustar 00000000000000//! Utilities for working with buffers. //! //! `io-uring` APIs require passing ownership of buffers to the runtime. The //! crate defines [`IoBuf`] and [`IoBufMut`] traits which are implemented by buffer //! types that respect the `io-uring` contract. mod io_buf; pub use io_buf::IoBuf; mod io_buf_mut; pub use io_buf_mut::IoBufMut; mod slice; pub use slice::Slice; pub(crate) fn deref(buf: &impl IoBuf) -> &[u8] { // Safety: the `IoBuf` trait is marked as unsafe and is expected to be // implemented correctly. unsafe { std::slice::from_raw_parts(buf.stable_ptr(), buf.bytes_init()) } } pub(crate) fn deref_mut(buf: &mut impl IoBufMut) -> &mut [u8] { // Safety: the `IoBufMut` trait is marked as unsafe and is expected to be // implemented correct. unsafe { std::slice::from_raw_parts_mut(buf.stable_mut_ptr(), buf.bytes_init()) } } tokio-uring-0.4.0/src/buf/slice.rs000064400000000000000000000075131046102023000151120ustar 00000000000000use crate::buf::{IoBuf, IoBufMut}; use std::cmp; use std::ops; /// An owned view into a contiguous sequence of bytes. /// /// This is similar to Rust slices (`&buf[..]`) but owns the underlying buffer. /// This type is useful for performing io-uring read and write operations using /// a subset of a buffer. /// /// Slices are created using [`IoBuf::slice`]. /// /// # Examples /// /// Creating a slice /// /// ``` /// use tokio_uring::buf::IoBuf; /// /// let buf = b"hello world".to_vec(); /// let slice = buf.slice(..5); /// /// assert_eq!(&slice[..], b"hello"); /// ``` pub struct Slice { buf: T, begin: usize, end: usize, } impl Slice { pub(crate) fn new(buf: T, begin: usize, end: usize) -> Slice { Slice { buf, begin, end } } /// Offset in the underlying buffer at which this slice starts. /// /// # Examples /// /// ``` /// use tokio_uring::buf::IoBuf; /// /// let buf = b"hello world".to_vec(); /// let slice = buf.slice(1..5); /// /// assert_eq!(1, slice.begin()); /// ``` pub fn begin(&self) -> usize { self.begin } /// Ofset in the underlying buffer at which this slice ends. /// /// # Examples /// /// ``` /// use tokio_uring::buf::IoBuf; /// /// let buf = b"hello world".to_vec(); /// let slice = buf.slice(1..5); /// /// assert_eq!(5, slice.end()); /// ``` pub fn end(&self) -> usize { self.end } /// Gets a reference to the underlying buffer. /// /// This method escapes the slice's view. /// /// # Examples /// /// ``` /// use tokio_uring::buf::IoBuf; /// /// let buf = b"hello world".to_vec(); /// let slice = buf.slice(..5); /// /// assert_eq!(slice.get_ref(), b"hello world"); /// assert_eq!(&slice[..], b"hello"); /// ``` pub fn get_ref(&self) -> &T { &self.buf } /// Gets a mutable reference to the underlying buffer. /// /// This method escapes the slice's view. /// /// # Examples /// /// ``` /// use tokio_uring::buf::IoBuf; /// /// let buf = b"hello world".to_vec(); /// let mut slice = buf.slice(..5); /// /// slice.get_mut()[0] = b'b'; /// /// assert_eq!(slice.get_mut(), b"bello world"); /// assert_eq!(&slice[..], b"bello"); /// ``` pub fn get_mut(&mut self) -> &mut T { &mut self.buf } /// Unwraps this `Slice`, returning the underlying buffer. /// /// # Examples /// /// ``` /// use tokio_uring::buf::IoBuf; /// /// let buf = b"hello world".to_vec(); /// let slice = buf.slice(..5); /// /// let buf = slice.into_inner(); /// assert_eq!(buf, b"hello world"); /// ``` pub fn into_inner(self) -> T { self.buf } } impl ops::Deref for Slice { type Target = [u8]; fn deref(&self) -> &[u8] { let buf_bytes = super::deref(&self.buf); let end = cmp::min(self.end, buf_bytes.len()); &buf_bytes[self.begin..end] } } impl ops::DerefMut for Slice { fn deref_mut(&mut self) -> &mut [u8] { let buf_bytes = super::deref_mut(&mut self.buf); let end = cmp::min(self.end, buf_bytes.len()); &mut buf_bytes[self.begin..end] } } unsafe impl IoBuf for Slice { fn stable_ptr(&self) -> *const u8 { super::deref(&self.buf)[self.begin..].as_ptr() } fn bytes_init(&self) -> usize { ops::Deref::deref(self).len() } fn bytes_total(&self) -> usize { self.end - self.begin } } unsafe impl IoBufMut for Slice { fn stable_mut_ptr(&mut self) -> *mut u8 { super::deref_mut(&mut self.buf)[self.begin..].as_mut_ptr() } unsafe fn set_init(&mut self, pos: usize) { self.buf.set_init(self.begin + pos); } } tokio-uring-0.4.0/src/buf/slice_mut.rs000064400000000000000000000030161046102023000157710ustar 00000000000000use crate::buf::{IoBuf, IoBufMut}; use std::ops; pub struct SliceMut { buf: T, begin: usize, end: usize, } impl SliceMut { pub(crate) fn new(buf: T, begin: usize, end: usize) -> SliceMut { SliceMut { buf, begin, end, } } /// Offset in the underlying buffer at which this slice starts. pub fn begin(&self) -> usize { self.begin } pub fn end(&self) -> usize { self.end } pub fn get_ref(&self) -> &T { &self.buf } pub fn get_mut(&mut self) -> &mut T { &mut self.buf } pub fn into_inner(self) -> T { self.buf } } impl ops::Deref for SliceMut { type Target = [u8]; fn deref(&self) -> &[u8] { &super::deref(&self.buf)[self.begin..self.end] } } impl ops::DerefMut for SliceMut { fn deref_mut(&mut self) -> &mut [u8] { &mut super::deref_mut(&mut self.buf)[self.begin..self.end] } } unsafe impl IoBuf for SliceMut { fn stable_ptr(&self) -> *const u8 { ops::Deref::deref(self).as_ptr() } fn len(&self) -> usize { self.end - self.begin } } unsafe impl IoBufMut for SliceMut { fn stable_mut_ptr(&mut self) -> *mut u8 { ops::DerefMut::deref_mut(self).as_mut_ptr() } fn capacity(&self) -> usize { self.end - self.begin } unsafe fn set_init(&mut self, pos: usize) { self.buf.set_init(self.begin + pos); } } tokio-uring-0.4.0/src/driver/accept.rs000064400000000000000000000030511046102023000157620ustar 00000000000000use crate::driver::op::{self, Completable}; use crate::driver::{Op, SharedFd, Socket}; use std::net::SocketAddr; use std::{boxed::Box, io}; pub(crate) struct Accept { fd: SharedFd, pub(crate) socketaddr: Box<(libc::sockaddr_storage, libc::socklen_t)>, } impl Op { pub(crate) fn accept(fd: &SharedFd) -> io::Result> { use io_uring::{opcode, types}; let socketaddr = Box::new(( unsafe { std::mem::zeroed() }, std::mem::size_of::() as libc::socklen_t, )); Op::submit_with( Accept { fd: fd.clone(), socketaddr, }, |accept| { opcode::Accept::new( types::Fd(accept.fd.raw_fd()), &mut accept.socketaddr.0 as *mut _ as *mut _, &mut accept.socketaddr.1, ) .flags(libc::O_CLOEXEC) .build() }, ) } } impl Completable for Accept { type Output = io::Result<(Socket, Option)>; fn complete(self, cqe: op::CqeResult) -> Self::Output { let fd = cqe.result?; let fd = SharedFd::new(fd as i32); let socket = Socket { fd }; let (_, addr) = unsafe { socket2::SockAddr::init(move |addr_storage, len| { *addr_storage = self.socketaddr.0.to_owned(); *len = self.socketaddr.1; Ok(()) })? }; Ok((socket, addr.as_socket())) } } tokio-uring-0.4.0/src/driver/bind.rs000064400000000000000000000000001046102023000154260ustar 00000000000000tokio-uring-0.4.0/src/driver/close.rs000064400000000000000000000011101046102023000156220ustar 00000000000000use crate::driver::Op; use crate::driver::op::{self, Completable}; use std::io; use std::os::unix::io::RawFd; pub(crate) struct Close { fd: RawFd, } impl Op { pub(crate) fn close(fd: RawFd) -> io::Result> { use io_uring::{opcode, types}; Op::try_submit_with(Close { fd }, |close| { opcode::Close::new(types::Fd(close.fd)).build() }) } } impl Completable for Close { type Output = io::Result<()>; fn complete(self, cqe: op::CqeResult) -> Self::Output { let _ = cqe.result?; Ok(()) } } tokio-uring-0.4.0/src/driver/connect.rs000064400000000000000000000022201046102023000161510ustar 00000000000000use crate::driver::op::{self, Completable}; use crate::driver::{Op, SharedFd}; use socket2::SockAddr; use std::io; /// Open a file pub(crate) struct Connect { fd: SharedFd, // this avoids a UAF (UAM?) if the future is moved, but not if the future is // dropped. no Op can be dropped before completion in tokio-uring land right now. socket_addr: Box, } impl Op { /// Submit a request to connect. pub(crate) fn connect(fd: &SharedFd, socket_addr: SockAddr) -> io::Result> { use io_uring::{opcode, types}; Op::submit_with( Connect { fd: fd.clone(), socket_addr: Box::new(socket_addr), }, |connect| { opcode::Connect::new( types::Fd(connect.fd.raw_fd()), connect.socket_addr.as_ptr(), connect.socket_addr.len(), ) .build() }, ) } } impl Completable for Connect { type Output = io::Result<()>; fn complete(self, cqe: op::CqeResult) -> Self::Output { cqe.result.map(|_| ()) } } tokio-uring-0.4.0/src/driver/fsync.rs000064400000000000000000000015251046102023000156510ustar 00000000000000use crate::driver::{Op, SharedFd}; use std::io; use crate::driver::op::{self, Completable}; use io_uring::{opcode, types}; pub(crate) struct Fsync { fd: SharedFd, } impl Op { pub(crate) fn fsync(fd: &SharedFd) -> io::Result> { Op::submit_with(Fsync { fd: fd.clone() }, |fsync| { opcode::Fsync::new(types::Fd(fsync.fd.raw_fd())).build() }) } pub(crate) fn datasync(fd: &SharedFd) -> io::Result> { Op::submit_with(Fsync { fd: fd.clone() }, |fsync| { opcode::Fsync::new(types::Fd(fsync.fd.raw_fd())) .flags(types::FsyncFlags::DATASYNC) .build() }) } } impl Completable for Fsync { type Output = io::Result<()>; fn complete(self, cqe: op::CqeResult) -> Self::Output { cqe.result.map(|_| ()) } } tokio-uring-0.4.0/src/driver/mod.rs000064400000000000000000000131531046102023000153060ustar 00000000000000mod accept; mod close; pub(crate) use close::Close; mod connect; mod fsync; mod noop; pub(crate) use noop::NoOp; mod op; pub(crate) use op::Op; mod open; mod read; mod readv; mod recv_from; mod rename_at; mod send_to; mod shared_fd; pub(crate) use shared_fd::SharedFd; mod socket; pub(crate) use socket::Socket; mod unlink_at; mod util; mod write; mod writev; use crate::driver::op::Lifecycle; use io_uring::opcode::AsyncCancel; use io_uring::IoUring; use slab::Slab; use std::io; use std::os::unix::io::{AsRawFd, RawFd}; pub(crate) struct Driver { /// In-flight operations ops: Ops, /// IoUring bindings pub(crate) uring: IoUring, } struct Ops { // When dropping the driver, all in-flight operations must have completed. This // type wraps the slab and ensures that, on drop, the slab is empty. lifecycle: Slab, /// Received but unserviced Op completions completions: Slab, } impl Driver { pub(crate) fn new(b: &crate::Builder) -> io::Result { let uring = b.urb.build(b.entries)?; Ok(Driver { ops: Ops::new(), uring, }) } fn wait(&self) -> io::Result { self.uring.submit_and_wait(1) } // only used in tests rn #[allow(unused)] fn num_operations(&self) -> usize { self.ops.lifecycle.len() } pub(crate) fn tick(&mut self) { let mut cq = self.uring.completion(); cq.sync(); for cqe in cq { if cqe.user_data() == u64::MAX { // Result of the cancellation action. There isn't anything we // need to do here. We must wait for the CQE for the operation // that was canceled. continue; } let index = cqe.user_data() as _; self.ops.complete(index, cqe.into()); } } pub(crate) fn submit(&mut self) -> io::Result<()> { loop { match self.uring.submit() { Ok(_) => { self.uring.submission().sync(); return Ok(()); } Err(ref e) if e.raw_os_error() == Some(libc::EBUSY) => { self.tick(); } Err(e) if e.raw_os_error() != Some(libc::EINTR) => { return Err(e); } _ => continue, } } } } impl AsRawFd for Driver { fn as_raw_fd(&self) -> RawFd { self.uring.as_raw_fd() } } /// Drop the driver, cancelling any in-progress ops and waiting for them to terminate. /// /// This first cancels all ops and then waits for them to be moved to the completed lifecycle phase. /// /// It is possible for this to be run without previously dropping the runtime, but this should only /// be possible in the case of [`std::process::exit`]. /// /// This depends on us knowing when ops are completed and done firing. /// When multishot ops are added (support exists but none are implemented), a way to know if such /// an op is finished MUST be added, otherwise our shutdown process is unsound. impl Drop for Driver { fn drop(&mut self) { // get all ops in flight for cancellation while !self.uring.submission().is_empty() { self.submit().expect("Internal error when dropping driver"); } // pre-determine what to cancel let mut cancellable_ops = Vec::new(); for (id, cycle) in self.ops.lifecycle.iter() { // don't cancel completed items if !matches!(cycle, Lifecycle::Completed(_)) { cancellable_ops.push(id); } } // cancel all ops for id in cancellable_ops { unsafe { while self .uring .submission() .push(&AsyncCancel::new(id as u64).build().user_data(u64::MAX)) .is_err() { self.submit().expect("Internal error when dropping driver"); } } } // TODO: add a way to know if a multishot op is done sending completions // SAFETY: this is currently unsound for multishot ops while !self .ops .lifecycle .iter() .all(|(_, cycle)| matches!(cycle, Lifecycle::Completed(_))) { // If waiting fails, ignore the error. The wait will be attempted // again on the next loop. let _ = self.wait(); self.tick(); } } } impl Ops { fn new() -> Ops { Ops { lifecycle: Slab::with_capacity(64), completions: Slab::with_capacity(64), } } fn get_mut(&mut self, index: usize) -> Option<(&mut op::Lifecycle, &mut Slab)> { let completions = &mut self.completions; self.lifecycle .get_mut(index) .map(|lifecycle| (lifecycle, completions)) } // Insert a new operation fn insert(&mut self) -> usize { self.lifecycle.insert(op::Lifecycle::Submitted) } // Remove an operation fn remove(&mut self, index: usize) { self.lifecycle.remove(index); } fn complete(&mut self, index: usize, cqe: op::CqeResult) { let completions = &mut self.completions; if self.lifecycle[index].complete(completions, cqe) { self.lifecycle.remove(index); } } } impl Drop for Ops { fn drop(&mut self) { assert!(self .lifecycle .iter() .all(|(_, cycle)| matches!(cycle, Lifecycle::Completed(_)))) } } tokio-uring-0.4.0/src/driver/noop.rs000064400000000000000000000013251046102023000155000ustar 00000000000000use crate::driver::{ op::{self, Completable}, Op, }; use std::io; /// No operation. Just posts a completion event, nothing else. /// /// Has a place in benchmarking. pub struct NoOp {} impl Op { pub fn no_op() -> io::Result> { use io_uring::opcode; Op::submit_with(NoOp {}, |_| opcode::Nop::new().build()) } } impl Completable for NoOp { type Output = io::Result<()>; fn complete(self, cqe: op::CqeResult) -> Self::Output { cqe.result.map(|_| ()) } } #[cfg(test)] mod test { use crate as tokio_uring; #[test] fn perform_no_op() -> () { tokio_uring::start(async { tokio_uring::no_op().await.unwrap(); }) } } tokio-uring-0.4.0/src/driver/op/slab_list.rs000064400000000000000000000110561046102023000171210ustar 00000000000000//! An indexed linked list, with entries held in slab storage. //! The slab may hold multiple independent lists concurrently. //! //! Each list is uniquely identified by a SlabListIndices, //! which holds the index of the first element of the list. //! It also holds the index of the last element, to support //! push operations without list traversal. use slab::Slab; use std::ops::{Deref, DerefMut}; /// A linked list backed by slab storage pub(crate) struct SlabList<'a, T> { index: SlabListIndices, slab: &'a mut Slab>, } // Indices to the head and tail of a single list held within a SlabList pub(crate) struct SlabListIndices { start: usize, end: usize, } /// Multi cycle operations may return an unbounded number of CQE's /// for a single cycle SQE. /// /// These are held in an indexed linked list pub(crate) struct SlabListEntry { entry: T, next: usize, } impl Deref for SlabListEntry { type Target = T; fn deref(&self) -> &Self::Target { &self.entry } } impl DerefMut for SlabListEntry { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.entry } } impl SlabListIndices { pub(crate) fn new() -> Self { let start = usize::MAX; SlabListIndices { start, end: start } } pub(crate) fn into_list(self, slab: &mut Slab>) -> SlabList<'_, T> { SlabList::from_indices(self, slab) } } impl<'a, T> SlabList<'a, T> { pub(crate) fn from_indices( index: SlabListIndices, slab: &'a mut Slab>, ) -> Self { SlabList { slab, index } } pub(crate) fn is_empty(&self) -> bool { self.index.start == usize::MAX } /// Peek at the end of the list (most recently pushed) /// This leaves the list unchanged pub(crate) fn peek_end(&mut self) -> Option<&T> { if self.index.end == usize::MAX { None } else { Some(&self.slab[self.index.end].entry) } } /// Pop from front of list #[allow(dead_code)] pub(crate) fn pop(&mut self) -> Option { self.slab .try_remove(self.index.start) .map(|SlabListEntry { next, entry, .. }| { if next == usize::MAX { self.index.end = usize::MAX; } self.index.start = next; entry }) } /// Push to the end of the list pub(crate) fn push(&mut self, entry: T) { let prev = self.index.end; let entry = SlabListEntry { entry, next: usize::MAX, }; self.index.end = self.slab.insert(entry); if prev != usize::MAX { self.slab[prev].next = self.index.end; } else { self.index.start = self.index.end; } } /// Consume the list, without dropping entries, returning just the start and end indices pub(crate) fn into_indices(mut self) -> SlabListIndices { std::mem::replace(&mut self.index, SlabListIndices::new()) } } impl<'a, T> Drop for SlabList<'a, T> { fn drop(&mut self) { while !self.is_empty() { let removed = self.slab.remove(self.index.start); self.index.start = removed.next; } } } #[cfg(test)] mod test { use super::*; #[test] fn push_pop() { let mut slab = Slab::with_capacity(8); let mut list = SlabListIndices::new().into_list(&mut slab); assert!(list.is_empty()); assert_eq!(list.pop(), None); for i in 0..5 { list.push(i); assert_eq!(list.peek_end(), Some(&i)); assert!(!list.is_empty()); assert!(!list.slab.is_empty()); } for i in 0..5 { assert_eq!(list.pop(), Some(i)) } assert!(list.is_empty()); assert!(list.slab.is_empty()); assert_eq!(list.pop(), None); } #[test] fn entries_freed_on_drop() { let mut slab = Slab::with_capacity(8); { let mut list = SlabListIndices::new().into_list(&mut slab); list.push(42); assert!(!list.is_empty()); } assert!(slab.is_empty()); } #[test] fn entries_kept_on_converion_to_index() { let mut slab = Slab::with_capacity(8); { let mut list = SlabListIndices::new().into_list(&mut slab); list.push(42); assert!(!list.is_empty()); // This forgets the entries let _ = list.into_indices(); } assert!(!slab.is_empty()); } } tokio-uring-0.4.0/src/driver/op.rs000064400000000000000000000335301046102023000151460ustar 00000000000000use std::future::Future; use std::io; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll, Waker}; use io_uring::{cqueue, squeue}; mod slab_list; use slab::Slab; use slab_list::{SlabListEntry, SlabListIndices}; use crate::driver; use crate::runtime::CONTEXT; use crate::util::PhantomUnsendUnsync; /// A SlabList is used to hold unserved completions. /// /// This is relevant to multi-completion Operations, /// which require an unknown number of CQE events to be /// captured before completion. pub(crate) type Completion = SlabListEntry; /// In-flight operation pub(crate) struct Op { // Operation index in the slab pub(super) index: usize, // Per-operation data data: Option, // CqeType marker _cqe_type: PhantomData, // Make !Send + !Sync _phantom: PhantomUnsendUnsync, } /// A Marker for Ops which expect only a single completion event pub(crate) struct SingleCQE; pub(crate) trait Completable { type Output; fn complete(self, cqe: CqeResult) -> Self::Output; } pub(crate) enum Lifecycle { /// The operation has been submitted to uring and is currently in-flight Submitted, /// The submitter is waiting for the completion of the operation Waiting(Waker), /// The submitter no longer has interest in the operation result. The state /// must be passed to the driver and held until the operation completes. Ignored(Box), /// The operation has completed with a single cqe result Completed(CqeResult), /// One or more completion results have been recieved /// This holds the indices uniquely identifying the list within the slab CompletionList(SlabListIndices), } /// A single CQE entry pub(crate) struct CqeResult { pub(crate) result: io::Result, pub(crate) flags: u32, } impl From for CqeResult { fn from(cqe: cqueue::Entry) -> Self { let res = cqe.result(); let flags = cqe.flags(); let result = if res >= 0 { Ok(res as u32) } else { Err(io::Error::from_raw_os_error(-res)) }; CqeResult { result, flags } } } impl Op where T: Completable, { /// Create a new operation fn new(data: T, inner: &mut driver::Driver) -> Self { Op { index: inner.ops.insert(), data: Some(data), _cqe_type: PhantomData, _phantom: PhantomData, } } /// Submit an operation to uring. /// /// `state` is stored during the operation tracking any state submitted to /// the kernel. pub(super) fn submit_with(data: T, f: F) -> io::Result where F: FnOnce(&mut T) -> squeue::Entry, { CONTEXT.with(|cx| { cx.with_driver_mut(|driver| { // Create the operation let mut op = Op::new(data, driver); // Configure the SQE let sqe = f(op.data.as_mut().unwrap()).user_data(op.index as _); // Push the new operation while unsafe { driver.uring.submission().push(&sqe).is_err() } { // If the submission queue is full, flush it to the kernel driver.submit()?; } Ok(op) }) }) } /// Try submitting an operation to uring pub(super) fn try_submit_with(data: T, f: F) -> io::Result where F: FnOnce(&mut T) -> squeue::Entry, { if CONTEXT.with(|cx| cx.is_set()) { Op::submit_with(data, f) } else { Err(io::ErrorKind::Other.into()) } } } impl Future for Op where T: Unpin + 'static + Completable, { type Output = T::Output; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { use std::mem; let me = &mut *self; CONTEXT.with(|runtime_context| { runtime_context.with_driver_mut(|driver| { let (lifecycle, _) = driver .ops .get_mut(me.index) .expect("invalid internal state"); match mem::replace(lifecycle, Lifecycle::Submitted) { Lifecycle::Submitted => { *lifecycle = Lifecycle::Waiting(cx.waker().clone()); Poll::Pending } Lifecycle::Waiting(waker) if !waker.will_wake(cx.waker()) => { *lifecycle = Lifecycle::Waiting(cx.waker().clone()); Poll::Pending } Lifecycle::Waiting(waker) => { *lifecycle = Lifecycle::Waiting(waker); Poll::Pending } Lifecycle::Ignored(..) => unreachable!(), Lifecycle::Completed(cqe) => { driver.ops.remove(me.index); me.index = usize::MAX; Poll::Ready(me.data.take().unwrap().complete(cqe)) } Lifecycle::CompletionList(..) => { unreachable!("No `more` flag set for SingleCQE") } } }) }) } } /// The operation may have pending cqe's not yet processed. /// To manage this, the lifecycle associated with the Op may if required /// be placed in LifeCycle::Ignored state to handle cqe's which arrive after /// the Op has been dropped. impl Drop for Op { fn drop(&mut self) { use std::mem; CONTEXT.with(|runtime_context| { runtime_context.with_driver_mut(|driver| { // Get the Op Lifecycle state from the driver let (lifecycle, completions) = match driver.ops.get_mut(self.index) { Some(val) => val, None => { // Op dropped after the driver return; } }; match mem::replace(lifecycle, Lifecycle::Submitted) { Lifecycle::Submitted | Lifecycle::Waiting(_) => { *lifecycle = Lifecycle::Ignored(Box::new(self.data.take())); } Lifecycle::Completed(..) => { driver.ops.remove(self.index); } Lifecycle::CompletionList(indices) => { // Deallocate list entries, recording if more CQE's are expected let more = { let mut list = indices.into_list(completions); io_uring::cqueue::more(list.peek_end().unwrap().flags) // Dropping list deallocates the list entries }; if more { // If more are expected, we have to keep the op around *lifecycle = Lifecycle::Ignored(Box::new(self.data.take())); } else { driver.ops.remove(self.index); } } Lifecycle::Ignored(..) => unreachable!(), } }) }) } } impl Lifecycle { pub(super) fn complete(&mut self, completions: &mut Slab, cqe: CqeResult) -> bool { use std::mem; match mem::replace(self, Lifecycle::Submitted) { x @ Lifecycle::Submitted | x @ Lifecycle::Waiting(..) => { if io_uring::cqueue::more(cqe.flags) { let mut list = SlabListIndices::new().into_list(completions); list.push(cqe); *self = Lifecycle::CompletionList(list.into_indices()); } else { *self = Lifecycle::Completed(cqe); } if let Lifecycle::Waiting(waker) = x { // waker is woken to notify cqe has arrived // Note: Maybe defer calling until cqe with !`more` flag set? waker.wake(); } false } lifecycle @ Lifecycle::Ignored(..) => { if io_uring::cqueue::more(cqe.flags) { // Not yet complete. The Op has been dropped, so we can drop the CQE // but we must keep the lifecycle alive until no more CQE's expected *self = lifecycle; false } else { // This Op has completed, we can drop true } } Lifecycle::Completed(..) => { // Completions with more flag set go straight onto the slab, // and are handled in Lifecycle::CompletionList. // To construct Lifecycle::Completed, a CQE with `more` flag unset was received // we shouldn't be receiving another. unreachable!("invalid operation state") } Lifecycle::CompletionList(indices) => { // A completion list may contain CQE's with and without `more` flag set. // Only the final one may have `more` unset, although we don't check. let mut list = indices.into_list(completions); list.push(cqe); *self = Lifecycle::CompletionList(list.into_indices()); false } } } } #[cfg(test)] mod test { use std::rc::Rc; use tokio_test::{assert_pending, assert_ready, task}; use super::*; #[derive(Debug)] pub(crate) struct Completion { result: io::Result, flags: u32, data: Rc<()>, } impl Completable for Rc<()> { type Output = Completion; fn complete(self, cqe: CqeResult) -> Self::Output { Completion { result: cqe.result, flags: cqe.flags, data: self.clone(), } } } #[test] fn op_stays_in_slab_on_drop() { let (op, data) = init(); drop(op); assert_eq!(2, Rc::strong_count(&data)); assert_eq!(1, num_operations()); release(); } #[test] fn poll_op_once() { let (op, data) = init(); let mut op = task::spawn(op); assert_pending!(op.poll()); assert_eq!(2, Rc::strong_count(&data)); complete(&op, Ok(1)); assert_eq!(1, num_operations()); assert_eq!(2, Rc::strong_count(&data)); assert!(op.is_woken()); let Completion { result, flags, data: d, } = assert_ready!(op.poll()); assert_eq!(2, Rc::strong_count(&data)); assert_eq!(1, result.unwrap()); assert_eq!(0, flags); drop(d); assert_eq!(1, Rc::strong_count(&data)); drop(op); assert_eq!(0, num_operations()); release(); } #[test] fn poll_op_twice() { { let (op, ..) = init(); let mut op = task::spawn(op); assert_pending!(op.poll()); assert_pending!(op.poll()); complete(&op, Ok(1)); assert!(op.is_woken()); let Completion { result, flags, .. } = assert_ready!(op.poll()); assert_eq!(1, result.unwrap()); assert_eq!(0, flags); } release(); } #[test] fn poll_change_task() { { let (op, ..) = init(); let mut op = task::spawn(op); assert_pending!(op.poll()); let op = op.into_inner(); let mut op = task::spawn(op); assert_pending!(op.poll()); complete(&op, Ok(1)); assert!(op.is_woken()); let Completion { result, flags, .. } = assert_ready!(op.poll()); assert_eq!(1, result.unwrap()); assert_eq!(0, flags); } release(); } #[test] fn complete_before_poll() { let (op, data) = init(); let mut op = task::spawn(op); complete(&op, Ok(1)); assert_eq!(1, num_operations()); assert_eq!(2, Rc::strong_count(&data)); let Completion { result, flags, .. } = assert_ready!(op.poll()); assert_eq!(1, result.unwrap()); assert_eq!(0, flags); drop(op); assert_eq!(0, num_operations()); release(); } #[test] fn complete_after_drop() { let (op, data) = init(); let index = op.index; drop(op); assert_eq!(2, Rc::strong_count(&data)); assert_eq!(1, num_operations()); let cqe = CqeResult { result: Ok(1), flags: 0, }; CONTEXT.with(|cx| cx.with_driver_mut(|driver| driver.ops.complete(index, cqe))); assert_eq!(1, Rc::strong_count(&data)); assert_eq!(0, num_operations()); release(); } fn init() -> (Op>, Rc<()>) { use crate::driver::Driver; let driver = Driver::new(&crate::builder()).unwrap(); let data = Rc::new(()); let op = CONTEXT.with(|cx| { cx.set_driver(driver); cx.with_driver_mut(|driver| Op::new(data.clone(), driver)) }); (op, data) } fn num_operations() -> usize { CONTEXT.with(|cx| cx.with_driver_mut(|driver| driver.num_operations())) } fn complete(op: &Op>, result: io::Result) { let cqe = CqeResult { result, flags: 0 }; CONTEXT.with(|cx| cx.with_driver_mut(|driver| driver.ops.complete(op.index, cqe))); } fn release() { CONTEXT.with(|cx| { cx.with_driver_mut(|driver| { driver.ops.lifecycle.clear(); driver.ops.completions.clear(); }); cx.unset_driver(); }); } } tokio-uring-0.4.0/src/driver/open.rs000064400000000000000000000025421046102023000154700ustar 00000000000000use crate::driver::{self, Op, SharedFd}; use crate::fs::{File, OpenOptions}; use crate::driver::op::{self, Completable}; use std::ffi::CString; use std::io; use std::path::Path; /// Open a file #[allow(dead_code)] pub(crate) struct Open { pub(crate) path: CString, pub(crate) flags: libc::c_int, } impl Op { /// Submit a request to open a file. pub(crate) fn open(path: &Path, options: &OpenOptions) -> io::Result> { use io_uring::{opcode, types}; let path = driver::util::cstr(path)?; let flags = libc::O_CLOEXEC | options.access_mode()? | options.creation_mode()? | (options.custom_flags & !libc::O_ACCMODE); Op::submit_with(Open { path, flags }, |open| { // Get a reference to the memory. The string will be held by the // operation state and will not be accessed again until the operation // completes. let p_ref = open.path.as_c_str().as_ptr(); opcode::OpenAt::new(types::Fd(libc::AT_FDCWD), p_ref) .flags(flags) .mode(options.mode) .build() }) } } impl Completable for Open { type Output = io::Result; fn complete(self, cqe: op::CqeResult) -> Self::Output { Ok(File::from_shared_fd(SharedFd::new(cqe.result? as _))) } } tokio-uring-0.4.0/src/driver/pool.rs000064400000000000000000000034451046102023000155030ustar 00000000000000use crate::driver; use io_uring::{opcode, IoUring}; use std::io; use std::mem::ManuallyDrop; /// Buffer pool shared with kernel pub(crate) struct Pool { mem: *mut u8, num: usize, size: usize, } pub(crate) struct ProvidedBuf { buf: ManuallyDrop>, driver: driver::Handle, } impl Pool { pub(super) fn new(num: usize, size: usize) -> Pool { let total = num * size; let mut mem = ManuallyDrop::new(Vec::::with_capacity(total)); assert_eq!(mem.capacity(), total); Pool { mem: mem.as_mut_ptr(), num, size, } } pub(super) fn provide_buffers(&self, uring: &mut IoUring) -> io::Result<()> { let op = opcode::ProvideBuffers::new(self.mem, self.size as _, self.num as _, 0, 0) .build() .user_data(0); // Scoped to ensure `sq` drops before trying to submit { let mut sq = uring.submission(); if unsafe { sq.push(&op) }.is_err() { unimplemented!("when is this hit?"); } } uring.submit_and_wait(1)?; let mut cq = uring.completion(); for cqe in &mut cq { assert_eq!(cqe.user_data(), 0); } Ok(()) } } impl ProvidedBuf {} impl Drop for ProvidedBuf { fn drop(&mut self) { let mut driver = self.driver.borrow_mut(); let pool = &driver.pool; let ptr = self.buf.as_mut_ptr(); let bid = (ptr as usize - pool.mem as usize) / pool.size; let op = opcode::ProvideBuffers::new(ptr, pool.size as _, 1, 0, bid as _) .build() .user_data(u64::MAX); let mut sq = driver.uring.submission(); if unsafe { sq.push(&op) }.is_err() { unimplemented!(); } } } tokio-uring-0.4.0/src/driver/read.rs000064400000000000000000000031141046102023000154360ustar 00000000000000use crate::buf::IoBufMut; use crate::driver::{Op, SharedFd}; use crate::BufResult; use crate::driver::op::{self, Completable}; use std::io; pub(crate) struct Read { /// Holds a strong ref to the FD, preventing the file from being closed /// while the operation is in-flight. #[allow(dead_code)] fd: SharedFd, /// Reference to the in-flight buffer. pub(crate) buf: T, } impl Op> { pub(crate) fn read_at(fd: &SharedFd, buf: T, offset: u64) -> io::Result>> { use io_uring::{opcode, types}; Op::submit_with( Read { fd: fd.clone(), buf, }, |read| { // Get raw buffer info let ptr = read.buf.stable_mut_ptr(); let len = read.buf.bytes_total(); opcode::Read::new(types::Fd(fd.raw_fd()), ptr, len as _) .offset(offset as _) .build() }, ) } } impl Completable for Read where T: IoBufMut, { type Output = BufResult; fn complete(self, cqe: op::CqeResult) -> Self::Output { // Convert the operation result to `usize` let res = cqe.result.map(|v| v as usize); // Recover the buffer let mut buf = self.buf; // If the operation was successful, advance the initialized cursor. if let Ok(n) = res { // Safety: the kernel wrote `n` bytes to the buffer. unsafe { buf.set_init(n); } } (res, buf) } } tokio-uring-0.4.0/src/driver/readv.rs000064400000000000000000000050401046102023000156240ustar 00000000000000use crate::buf::IoBufMut; use crate::driver::{Op, SharedFd}; use crate::BufResult; use crate::driver::op::{self, Completable}; use libc::iovec; use std::io; pub(crate) struct Readv { /// Holds a strong ref to the FD, preventing the file from being closed /// while the operation is in-flight. #[allow(dead_code)] fd: SharedFd, /// Reference to the in-flight buffer. pub(crate) bufs: Vec, /// Parameter for `io_uring::op::readv`, referring `bufs`. iovs: Vec, } impl Op> { pub(crate) fn readv_at( fd: &SharedFd, mut bufs: Vec, offset: u64, ) -> io::Result>> { use io_uring::{opcode, types}; // Build `iovec` objects referring the provided `bufs` for `io_uring::opcode::Readv`. let iovs: Vec = bufs .iter_mut() .map(|b| iovec { // Safety guaranteed by `IoBufMut`. iov_base: unsafe { b.stable_mut_ptr().add(b.bytes_init()) as *mut libc::c_void }, iov_len: b.bytes_total() - b.bytes_init(), }) .collect(); Op::submit_with( Readv { fd: fd.clone(), bufs, iovs, }, |read| { opcode::Readv::new( types::Fd(fd.raw_fd()), read.iovs.as_ptr(), read.iovs.len() as u32, ) .offset(offset as _) .build() }, ) } } impl Completable for Readv where T: IoBufMut, { type Output = BufResult>; fn complete(self, cqe: op::CqeResult) -> Self::Output { // Convert the operation result to `usize` let res = cqe.result.map(|v| v as usize); // Recover the buffer let mut bufs = self.bufs; // If the operation was successful, advance the initialized cursor. if let Ok(n) = res { let mut count = n; for b in bufs.iter_mut() { let sz = std::cmp::min(count, b.bytes_total() - b.bytes_init()); let pos = b.bytes_init() + sz; // Safety: the kernel returns bytes written, and we have ensured that `pos` is // valid for current buffer. unsafe { b.set_init(pos) }; count -= sz; if count == 0 { break; } } assert_eq!(count, 0); } (res, bufs) } } tokio-uring-0.4.0/src/driver/recv_from.rs000064400000000000000000000043731046102023000165150ustar 00000000000000use crate::driver::op::{self, Completable}; use crate::{ buf::IoBufMut, driver::{Op, SharedFd}, BufResult, }; use socket2::SockAddr; use std::{ io::IoSliceMut, {boxed::Box, io, net::SocketAddr}, }; #[allow(dead_code)] pub(crate) struct RecvFrom { fd: SharedFd, pub(crate) buf: T, io_slices: Vec>, pub(crate) socket_addr: Box, pub(crate) msghdr: Box, } impl Op> { pub(crate) fn recv_from(fd: &SharedFd, mut buf: T) -> io::Result>> { use io_uring::{opcode, types}; let mut io_slices = vec![IoSliceMut::new(unsafe { std::slice::from_raw_parts_mut(buf.stable_mut_ptr(), buf.bytes_total()) })]; let socket_addr = Box::new(unsafe { SockAddr::init(|_, _| Ok(()))?.1 }); let mut msghdr: Box = Box::new(unsafe { std::mem::zeroed() }); msghdr.msg_iov = io_slices.as_mut_ptr().cast(); msghdr.msg_iovlen = io_slices.len() as _; msghdr.msg_name = socket_addr.as_ptr() as *mut libc::c_void; msghdr.msg_namelen = socket_addr.len(); Op::submit_with( RecvFrom { fd: fd.clone(), buf, io_slices, socket_addr, msghdr, }, |recv_from| { opcode::RecvMsg::new( types::Fd(recv_from.fd.raw_fd()), recv_from.msghdr.as_mut() as *mut _, ) .build() }, ) } } impl Completable for RecvFrom where T: IoBufMut, { type Output = BufResult<(usize, SocketAddr), T>; fn complete(self, cqe: op::CqeResult) -> Self::Output { // Convert the operation result to `usize` let res = cqe.result.map(|v| v as usize); // Recover the buffer let mut buf = self.buf; let socket_addr = (*self.socket_addr).as_socket(); let res = res.map(|n| { let socket_addr: SocketAddr = socket_addr.unwrap(); // Safety: the kernel wrote `n` bytes to the buffer. unsafe { buf.set_init(n); } (n, socket_addr) }); (res, buf) } } tokio-uring-0.4.0/src/driver/rename_at.rs000064400000000000000000000027541046102023000164670ustar 00000000000000use crate::driver::{self, Op}; use crate::driver::op::{self, Completable}; use std::ffi::CString; use std::io; use std::path::Path; /// Renames a file, moving it between directories if required. /// /// The given paths are interpreted relative to the current working directory /// of the calling process. pub(crate) struct RenameAt { pub(crate) from: CString, pub(crate) to: CString, } impl Op { /// Submit a request to rename a specified path to a new name with /// the provided flags. pub(crate) fn rename_at(from: &Path, to: &Path, flags: u32) -> io::Result> { use io_uring::{opcode, types}; let from = driver::util::cstr(from)?; let to = driver::util::cstr(to)?; Op::submit_with(RenameAt { from, to }, |rename| { // Get a reference to the memory. The string will be held by the // operation state and will not be accessed again until the operation // completes. let from_ref = rename.from.as_c_str().as_ptr(); let to_ref = rename.to.as_c_str().as_ptr(); opcode::RenameAt::new( types::Fd(libc::AT_FDCWD), from_ref, types::Fd(libc::AT_FDCWD), to_ref, ) .flags(flags) .build() }) } } impl Completable for RenameAt { type Output = io::Result<()>; fn complete(self, cqe: op::CqeResult) -> Self::Output { cqe.result.map(|_| ()) } } tokio-uring-0.4.0/src/driver/send_to.rs000064400000000000000000000036721046102023000161670ustar 00000000000000use crate::buf::IoBuf; use crate::driver::op::{self, Completable}; use crate::driver::{Op, SharedFd}; use crate::BufResult; use socket2::SockAddr; use std::io::IoSlice; use std::{boxed::Box, io, net::SocketAddr}; pub(crate) struct SendTo { #[allow(dead_code)] fd: SharedFd, pub(crate) buf: T, #[allow(dead_code)] io_slices: Vec>, #[allow(dead_code)] socket_addr: Box, pub(crate) msghdr: Box, } impl Op> { pub(crate) fn send_to( fd: &SharedFd, buf: T, socket_addr: SocketAddr, ) -> io::Result>> { use io_uring::{opcode, types}; let io_slices = vec![IoSlice::new(unsafe { std::slice::from_raw_parts(buf.stable_ptr(), buf.bytes_init()) })]; let socket_addr = Box::new(SockAddr::from(socket_addr)); let mut msghdr: Box = Box::new(unsafe { std::mem::zeroed() }); msghdr.msg_iov = io_slices.as_ptr() as *mut _; msghdr.msg_iovlen = io_slices.len() as _; msghdr.msg_name = socket_addr.as_ptr() as *mut libc::c_void; msghdr.msg_namelen = socket_addr.len(); Op::submit_with( SendTo { fd: fd.clone(), buf, io_slices, socket_addr, msghdr, }, |send_to| { opcode::SendMsg::new( types::Fd(send_to.fd.raw_fd()), send_to.msghdr.as_ref() as *const _, ) .build() }, ) } } impl Completable for SendTo where T: IoBuf, { type Output = BufResult; fn complete(self, cqe: op::CqeResult) -> Self::Output { // Convert the operation result to `usize` let res = cqe.result.map(|v| v as usize); // Recover the buffer let buf = self.buf; (res, buf) } } tokio-uring-0.4.0/src/driver/shared_fd.rs000064400000000000000000000100571046102023000164460ustar 00000000000000use crate::driver::{Close, Op}; use crate::future::poll_fn; use std::cell::RefCell; use std::os::unix::io::{FromRawFd, RawFd}; use std::rc::Rc; use std::task::Waker; // Tracks in-flight operations on a file descriptor. Ensures all in-flight // operations complete before submitting the close. #[derive(Clone)] pub(crate) struct SharedFd { inner: Rc, } struct Inner { // Open file descriptor fd: RawFd, // Waker to notify when the close operation completes. state: RefCell, } enum State { /// Initial state Init, /// Waiting for all in-flight operation to complete. Waiting(Option), /// The FD is closing Closing(Op), /// The FD is fully closed Closed, } impl SharedFd { pub(crate) fn new(fd: RawFd) -> SharedFd { SharedFd { inner: Rc::new(Inner { fd, state: RefCell::new(State::Init), }), } } /// Returns the RawFd pub(crate) fn raw_fd(&self) -> RawFd { self.inner.fd } /// An FD cannot be closed until all in-flight operation have completed. /// This prevents bugs where in-flight reads could operate on the incorrect /// file descriptor. /// /// TO model this, if there are no in-flight operations, then pub(crate) async fn close(mut self) { // Get a mutable reference to Inner, indicating there are no // in-flight operations on the FD. if let Some(inner) = Rc::get_mut(&mut self.inner) { // Submit the close operation inner.submit_close_op(); } self.inner.closed().await; } } impl Inner { /// If there are no in-flight operations, submit the operation. fn submit_close_op(&mut self) { // Close the FD let state = RefCell::get_mut(&mut self.state); // Submit a close operation *state = match Op::close(self.fd) { Ok(op) => State::Closing(op), Err(_) => { // Submitting the operation failed, we fall back on a // synchronous `close`. This is safe as, at this point, we // guarantee all in-flight operations have completed. The most // common cause for an error is attempting to close the FD while // off runtime. // // This is done by initializing a `File` with the FD and // dropping it. // // TODO: Should we warn? let _ = unsafe { std::fs::File::from_raw_fd(self.fd) }; State::Closed } }; } /// Completes when the FD has been closed. async fn closed(&self) { use std::future::Future; use std::pin::Pin; use std::task::Poll; poll_fn(|cx| { let mut state = self.state.borrow_mut(); match &mut *state { State::Init => { *state = State::Waiting(Some(cx.waker().clone())); Poll::Pending } State::Waiting(Some(waker)) => { if !waker.will_wake(cx.waker()) { *waker = cx.waker().clone(); } Poll::Pending } State::Waiting(None) => { *state = State::Waiting(Some(cx.waker().clone())); Poll::Pending } State::Closing(op) => { // Nothing to do if the close opeation failed. let _ = ready!(Pin::new(op).poll(cx)); *state = State::Closed; Poll::Ready(()) } State::Closed => Poll::Ready(()), } }) .await; } } impl Drop for Inner { fn drop(&mut self) { // Submit the close operation, if needed match RefCell::get_mut(&mut self.state) { State::Init | State::Waiting(..) => { self.submit_close_op(); } _ => {} } } } tokio-uring-0.4.0/src/driver/socket.rs000064400000000000000000000115231046102023000160160ustar 00000000000000use crate::{ buf::{IoBuf, IoBufMut}, driver::{Op, SharedFd}, }; use std::{ io, net::SocketAddr, os::unix::io::{AsRawFd, IntoRawFd, RawFd}, path::Path, }; #[derive(Clone)] pub(crate) struct Socket { /// Open file descriptor pub(crate) fd: SharedFd, } pub(crate) fn get_domain(socket_addr: SocketAddr) -> libc::c_int { match socket_addr { SocketAddr::V4(_) => libc::AF_INET, SocketAddr::V6(_) => libc::AF_INET6, } } impl Socket { pub(crate) fn new(socket_addr: SocketAddr, socket_type: libc::c_int) -> io::Result { let socket_type = socket_type | libc::SOCK_CLOEXEC; let domain = get_domain(socket_addr); let fd = socket2::Socket::new(domain.into(), socket_type.into(), None)?.into_raw_fd(); let fd = SharedFd::new(fd); Ok(Socket { fd }) } pub(crate) fn new_unix(socket_type: libc::c_int) -> io::Result { let socket_type = socket_type | libc::SOCK_CLOEXEC; let domain = libc::AF_UNIX; let fd = socket2::Socket::new(domain.into(), socket_type.into(), None)?.into_raw_fd(); let fd = SharedFd::new(fd); Ok(Socket { fd }) } pub(crate) async fn write(&self, buf: T) -> crate::BufResult { let op = Op::write_at(&self.fd, buf, 0).unwrap(); op.await } pub async fn writev(&self, buf: Vec) -> crate::BufResult> { let op = Op::writev_at(&self.fd, buf, 0).unwrap(); op.await } pub(crate) async fn send_to( &self, buf: T, socket_addr: SocketAddr, ) -> crate::BufResult { let op = Op::send_to(&self.fd, buf, socket_addr).unwrap(); op.await } pub(crate) async fn read(&self, buf: T) -> crate::BufResult { let op = Op::read_at(&self.fd, buf, 0).unwrap(); op.await } pub(crate) async fn recv_from( &self, buf: T, ) -> crate::BufResult<(usize, SocketAddr), T> { let op = Op::recv_from(&self.fd, buf).unwrap(); op.await } pub(crate) async fn accept(&self) -> io::Result<(Socket, Option)> { let op = Op::accept(&self.fd)?; op.await } pub(crate) async fn connect(&self, socket_addr: socket2::SockAddr) -> io::Result<()> { let op = Op::connect(&self.fd, socket_addr)?; op.await } pub(crate) fn bind(socket_addr: SocketAddr, socket_type: libc::c_int) -> io::Result { Self::bind_internal( socket_addr.into(), get_domain(socket_addr).into(), socket_type.into(), ) } pub(crate) fn bind_unix>( path: P, socket_type: libc::c_int, ) -> io::Result { let addr = socket2::SockAddr::unix(path.as_ref())?; Self::bind_internal(addr, libc::AF_UNIX.into(), socket_type.into()) } pub(crate) fn from_std(socket: T) -> Socket { let fd = SharedFd::new(socket.into_raw_fd()); Self::from_shared_fd(fd) } pub(crate) fn from_shared_fd(fd: SharedFd) -> Socket { Self { fd } } fn bind_internal( socket_addr: socket2::SockAddr, domain: socket2::Domain, socket_type: socket2::Type, ) -> io::Result { let sys_listener = socket2::Socket::new(domain, socket_type, None)?; sys_listener.set_reuse_port(true)?; sys_listener.set_reuse_address(true)?; // TODO: config for buffer sizes // sys_listener.set_send_buffer_size(send_buf_size)?; // sys_listener.set_recv_buffer_size(recv_buf_size)?; sys_listener.bind(&socket_addr)?; let fd = SharedFd::new(sys_listener.into_raw_fd()); Ok(Self { fd }) } pub(crate) fn listen(&self, backlog: libc::c_int) -> io::Result<()> { syscall!(listen(self.as_raw_fd(), backlog))?; Ok(()) } /// Shuts down the read, write, or both halves of this connection. /// /// This function will cause all pending and future I/O on the specified portions to return /// immediately with an appropriate value. pub fn shutdown(&self, how: std::net::Shutdown) -> io::Result<()> { use std::os::unix::io::FromRawFd; let fd = self.as_raw_fd(); // SAFETY: Our fd is the handle the kernel has given us for a socket, // TCP or Unix, Listener or Stream, so it is a valid file descriptor/socket. // Create a socket2::Socket long enough to call its shutdown method // and then forget it so the socket is not otherwise dropped here. let s = unsafe { socket2::Socket::from_raw_fd(fd) }; let result = s.shutdown(how); std::mem::forget(s); result } } impl AsRawFd for Socket { fn as_raw_fd(&self) -> RawFd { self.fd.raw_fd() } } tokio-uring-0.4.0/src/driver/unlink_at.rs000064400000000000000000000027321046102023000165140ustar 00000000000000use crate::driver::{self, Op}; use crate::driver::op::{self, Completable}; use std::ffi::CString; use std::io; use std::path::Path; /// Unlink a path relative to the current working directory of the caller's process. pub(crate) struct Unlink { pub(crate) path: CString, } impl Op { /// Submit a request to unlink a directory with provided flags. pub(crate) fn unlink_dir(path: &Path) -> io::Result> { Self::unlink(path, libc::AT_REMOVEDIR) } /// Submit a request to unlink a file with provided flags. pub(crate) fn unlink_file(path: &Path) -> io::Result> { Self::unlink(path, 0) } /// Submit a request to unlink a specifed path with provided flags. pub(crate) fn unlink(path: &Path, flags: i32) -> io::Result> { use io_uring::{opcode, types}; let path = driver::util::cstr(path)?; Op::submit_with(Unlink { path }, |unlink| { // Get a reference to the memory. The string will be held by the // operation state and will not be accessed again until the operation // completes. let p_ref = unlink.path.as_c_str().as_ptr(); opcode::UnlinkAt::new(types::Fd(libc::AT_FDCWD), p_ref) .flags(flags) .build() }) } } impl Completable for Unlink { type Output = io::Result<()>; fn complete(self, cqe: op::CqeResult) -> Self::Output { cqe.result.map(|_| ()) } } tokio-uring-0.4.0/src/driver/util.rs000064400000000000000000000003101046102023000154730ustar 00000000000000use std::ffi::CString; use std::io; use std::path::Path; pub(super) fn cstr(p: &Path) -> io::Result { use std::os::unix::ffi::OsStrExt; Ok(CString::new(p.as_os_str().as_bytes())?) } tokio-uring-0.4.0/src/driver/write.rs000064400000000000000000000024331046102023000156600ustar 00000000000000use crate::driver::op::{self, Completable}; use crate::{ buf::IoBuf, driver::{Op, SharedFd}, BufResult, }; use std::io; pub(crate) struct Write { /// Holds a strong ref to the FD, preventing the file from being closed /// while the operation is in-flight. #[allow(dead_code)] fd: SharedFd, pub(crate) buf: T, } impl Op> { pub(crate) fn write_at(fd: &SharedFd, buf: T, offset: u64) -> io::Result>> { use io_uring::{opcode, types}; Op::submit_with( Write { fd: fd.clone(), buf, }, |write| { // Get raw buffer info let ptr = write.buf.stable_ptr(); let len = write.buf.bytes_init(); opcode::Write::new(types::Fd(fd.raw_fd()), ptr, len as _) .offset(offset as _) .build() }, ) } } impl Completable for Write where T: IoBuf, { type Output = BufResult; fn complete(self, cqe: op::CqeResult) -> Self::Output { // Convert the operation result to `usize` let res = cqe.result.map(|v| v as usize); // Recover the buffer let buf = self.buf; (res, buf) } } tokio-uring-0.4.0/src/driver/writev.rs000064400000000000000000000033661046102023000160540ustar 00000000000000use crate::driver::op::{self, Completable}; use crate::{ buf::IoBuf, driver::{Op, SharedFd}, BufResult, }; use libc::iovec; use std::io; pub(crate) struct Writev { /// Holds a strong ref to the FD, preventing the file from being closed /// while the operation is in-flight. #[allow(dead_code)] fd: SharedFd, pub(crate) bufs: Vec, /// Parameter for `io_uring::op::readv`, referring `bufs`. iovs: Vec, } impl Op> { pub(crate) fn writev_at( fd: &SharedFd, mut bufs: Vec, offset: u64, ) -> io::Result>> { use io_uring::{opcode, types}; // Build `iovec` objects referring the provided `bufs` for `io_uring::opcode::Readv`. let iovs: Vec = bufs .iter_mut() .map(|b| iovec { iov_base: b.stable_ptr() as *mut libc::c_void, iov_len: b.bytes_init(), }) .collect(); Op::submit_with( Writev { fd: fd.clone(), bufs, iovs, }, |write| { opcode::Writev::new( types::Fd(fd.raw_fd()), write.iovs.as_ptr(), write.iovs.len() as u32, ) .offset(offset as _) .build() }, ) } } impl Completable for Writev where T: IoBuf, { type Output = BufResult>; fn complete(self, cqe: op::CqeResult) -> Self::Output { // Convert the operation result to `usize` let res = cqe.result.map(|v| v as usize); // Recover the buffer let buf = self.bufs; (res, buf) } } tokio-uring-0.4.0/src/fs/directory.rs000064400000000000000000000007651046102023000156550ustar 00000000000000use crate::driver::Op; use std::io; use std::path::Path; /// Removes an empty directory. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::remove_dir; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// remove_dir("/some/dir").await?; /// Ok::<(), std::io::Error>(()) /// })?; /// Ok(()) /// } /// ``` pub async fn remove_dir>(path: P) -> io::Result<()> { Op::unlink_dir(path.as_ref())?.await } tokio-uring-0.4.0/src/fs/file.rs000064400000000000000000000373041046102023000145670ustar 00000000000000use crate::buf::{IoBuf, IoBufMut}; use crate::driver::{Op, SharedFd}; use crate::fs::OpenOptions; use std::fmt; use std::io; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use std::path::Path; /// A reference to an open file on the filesystem. /// /// An instance of a `File` can be read and/or written depending on what options /// it was opened with. The `File` type provides **positional** read and write /// operations. The file does not maintain an internal cursor. The caller is /// required to specify an offset when issuing an operation. /// /// While files are automatically closed when they go out of scope, the /// operation happens asynchronously in the background. It is recommended to /// call the `close()` function in order to guarantee that the file successfully /// closed before exiting the scope. Closing a file does not guarantee writes /// have persisted to disk. Use [`sync_all`] to ensure all writes have reached /// the filesystem. /// /// [`sync_all`]: File::sync_all /// /// # Examples /// /// Creates a new file and write data to it: /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// // Open a file /// let file = File::create("hello.txt").await?; /// /// // Write some data /// let (res, buf) = file.write_at(&b"hello world"[..], 0).await; /// let n = res?; /// /// println!("wrote {} bytes", n); /// /// // Sync data to the file system. /// file.sync_all().await?; /// /// // Close the file /// file.close().await?; /// /// Ok(()) /// }) /// } /// ``` pub struct File { /// Open file descriptor fd: SharedFd, } impl File { /// Attempts to open a file in read-only mode. /// /// See the [`OpenOptions::open`] method for more details. /// /// # Errors /// /// This function will return an error if `path` does not already exist. /// Other errors may also be returned according to [`OpenOptions::open`]. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let f = File::open("foo.txt").await?; /// /// // Close the file /// f.close().await?; /// Ok(()) /// }) /// } /// ``` pub async fn open(path: impl AsRef) -> io::Result { OpenOptions::new().read(true).open(path).await } /// Opens a file in write-only mode. /// /// This function will create a file if it does not exist, /// and will truncate it if it does. /// /// See the [`OpenOptions::open`] function for more details. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let f = File::create("foo.txt").await?; /// /// // Close the file /// f.close().await?; /// Ok(()) /// }) /// } /// ``` pub async fn create(path: impl AsRef) -> io::Result { OpenOptions::new() .write(true) .create(true) .truncate(true) .open(path) .await } pub(crate) fn from_shared_fd(fd: SharedFd) -> File { File { fd } } /// Converts a [`std::fs::File`][std] to a [`tokio_uring::fs::File`][file]. /// /// [std]: std::fs::File /// [file]: File pub fn from_std(file: std::fs::File) -> File { File::from_shared_fd(SharedFd::new(file.into_raw_fd())) } /// Read some bytes at the specified offset from the file into the specified /// buffer, returning how many bytes were read. /// /// # Return /// /// The method returns the operation result and the same buffer value passed /// as an argument. /// /// If the method returns [`Ok(n)`], then the read was successful. A nonzero /// `n` value indicates that the buffer has been filled with `n` bytes of /// data from the file. If `n` is `0`, then one of the following happened: /// /// 1. The specified offset is the end of the file. /// 2. The buffer specified was 0 bytes in length. /// /// It is not an error if the returned value `n` is smaller than the buffer /// size, even when the file contains enough data to fill the buffer. /// /// # Errors /// /// If this function encounters any form of I/O or other error, an error /// variant will be returned. The buffer is returned on error. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let f = File::open("foo.txt").await?; /// let buffer = vec![0; 10]; /// /// // Read up to 10 bytes /// let (res, buffer) = f.read_at(buffer, 0).await; /// let n = res?; /// /// println!("The bytes: {:?}", &buffer[..n]); /// /// // Close the file /// f.close().await?; /// Ok(()) /// }) /// } /// ``` pub async fn read_at(&self, buf: T, pos: u64) -> crate::BufResult { // Submit the read operation let op = Op::read_at(&self.fd, buf, pos).unwrap(); op.await } /// Read some bytes at the specified offset from the file into the specified /// array of buffers, returning how many bytes were read. /// /// # Return /// /// The method returns the operation result and the same array of buffers /// passed as an argument. /// /// If the method returns [`Ok(n)`], then the read was successful. A nonzero /// `n` value indicates that the buffers have been filled with `n` bytes of /// data from the file. If `n` is `0`, then one of the following happened: /// /// 1. The specified offset is the end of the file. /// 2. The buffers specified were 0 bytes in length. /// /// It is not an error if the returned value `n` is smaller than the buffer /// size, even when the file contains enough data to fill the buffer. /// /// # Errors /// /// If this function encounters any form of I/O or other error, an error /// variant will be returned. The buffer is returned on error. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let f = File::open("foo.txt").await?; /// let buffers = vec![Vec::::with_capacity(10), Vec::::with_capacity(10)]; /// /// // Read up to 20 bytes /// let (res, buffer) = f.readv_at(buffers, 0).await; /// let n = res?; /// /// println!("Read {} bytes", n); /// /// // Close the file /// f.close().await?; /// Ok(()) /// }) /// } /// ``` pub async fn readv_at( &self, bufs: Vec, pos: u64, ) -> crate::BufResult> { // Submit the read operation let op = Op::readv_at(&self.fd, bufs, pos).unwrap(); op.await } /// Write data from buffers into this file at the specified offset, /// returning how many bytes were written. /// /// This function will attempt to write the entire contents of `bufs`, but /// the entire write may not succeed, or the write may also generate an /// error. The bytes will be written starting at the specified offset. /// /// # Return /// /// The method returns the operation result and the same array of buffers passed /// in as an argument. A return value of `0` typically means that the /// underlying file is no longer able to accept bytes and will likely not be /// able to in the future as well, or that the buffer provided is empty. /// /// # Errors /// /// Each call to `write` may generate an I/O error indicating that the /// operation could not be completed. If an error is returned then no bytes /// in the buffer were written to this writer. /// /// It is **not** considered an error if the entire buffer could not be /// written to this writer. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = File::create("foo.txt").await?; /// /// // Writes some prefix of the byte string, not necessarily all of it. /// let bufs = vec!["some".to_owned().into_bytes(), " bytes".to_owned().into_bytes()]; /// let (res, _) = file.writev_at(bufs, 0).await; /// let n = res?; /// /// println!("wrote {} bytes", n); /// /// // Close the file /// file.close().await?; /// Ok(()) /// }) /// } /// ``` /// /// [`Ok(n)`]: Ok pub async fn writev_at( &self, buf: Vec, pos: u64, ) -> crate::BufResult> { let op = Op::writev_at(&self.fd, buf, pos).unwrap(); op.await } /// Write a buffer into this file at the specified offset, returning how /// many bytes were written. /// /// This function will attempt to write the entire contents of `buf`, but /// the entire write may not succeed, or the write may also generate an /// error. The bytes will be written starting at the specified offset. /// /// # Return /// /// The method returns the operation result and the same buffer value passed /// in as an argument. A return value of `0` typically means that the /// underlying file is no longer able to accept bytes and will likely not be /// able to in the future as well, or that the buffer provided is empty. /// /// # Errors /// /// Each call to `write` may generate an I/O error indicating that the /// operation could not be completed. If an error is returned then no bytes /// in the buffer were written to this writer. /// /// It is **not** considered an error if the entire buffer could not be /// written to this writer. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = File::create("foo.txt").await?; /// /// // Writes some prefix of the byte string, not necessarily all of it. /// let (res, _) = file.write_at(&b"some bytes"[..], 0).await; /// let n = res?; /// /// println!("wrote {} bytes", n); /// /// // Close the file /// file.close().await?; /// Ok(()) /// }) /// } /// ``` /// /// [`Ok(n)`]: Ok pub async fn write_at(&self, buf: T, pos: u64) -> crate::BufResult { let op = Op::write_at(&self.fd, buf, pos).unwrap(); op.await } /// Attempts to sync all OS-internal metadata to disk. /// /// This function will attempt to ensure that all in-memory data reaches the /// filesystem before completing. /// /// This can be used to handle errors that would otherwise only be caught /// when the `File` is closed. Dropping a file will ignore errors in /// synchronizing this in-memory data. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let f = File::create("foo.txt").await?; /// let (res, buf) = f.write_at(&b"Hello, world!"[..], 0).await; /// let n = res?; /// /// f.sync_all().await?; /// /// // Close the file /// f.close().await?; /// Ok(()) /// }) /// } /// ``` pub async fn sync_all(&self) -> io::Result<()> { Op::fsync(&self.fd)?.await } /// Attempts to sync file data to disk. /// /// This method is similar to [`sync_all`], except that it may not /// synchronize file metadata to the filesystem. /// /// This is intended for use cases that must synchronize content, but don't /// need the metadata on disk. The goal of this method is to reduce disk /// operations. /// /// Note that some platforms may simply implement this in terms of /// [`sync_all`]. /// /// [`sync_all`]: File::sync_all /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let f = File::create("foo.txt").await?; /// let (res, buf) = f.write_at(&b"Hello, world!"[..], 0).await; /// let n = res?; /// /// f.sync_data().await?; /// /// // Close the file /// f.close().await?; /// Ok(()) /// }) /// } /// ``` pub async fn sync_data(&self) -> io::Result<()> { Op::datasync(&self.fd)?.await } /// Closes the file. /// /// The method completes once the close operation has completed, /// guaranteeing that resources associated with the file have been released. /// /// If `close` is not called before dropping the file, the file is closed in /// the background, but there is no guarantee as to **when** the close /// operation will complete. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// // Open the file /// let f = File::open("foo.txt").await?; /// // Close the file /// f.close().await?; /// /// Ok(()) /// }) /// } /// ``` pub async fn close(self) -> io::Result<()> { self.fd.close().await; Ok(()) } } impl FromRawFd for File { unsafe fn from_raw_fd(fd: RawFd) -> Self { File::from_shared_fd(SharedFd::new(fd)) } } impl AsRawFd for File { fn as_raw_fd(&self) -> RawFd { self.fd.raw_fd() } } impl fmt::Debug for File { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("File") .field("fd", &self.fd.raw_fd()) .finish() } } /// Removes a File /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::remove_file; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// remove_file("/some/file.txt").await?; /// Ok::<(), std::io::Error>(()) /// })?; /// Ok(()) /// } /// ``` pub async fn remove_file>(path: P) -> io::Result<()> { Op::unlink_file(path.as_ref())?.await } /// Renames a file or directory to a new name, replacing the original file if /// `to` already exists. /// /// This will not work if the new name is on a different mount point. /// /// # Example /// /// ```no_run /// use tokio_uring::fs::rename; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// rename("a.txt", "b.txt").await?; // Rename a.txt to b.txt /// Ok::<(), std::io::Error>(()) /// })?; /// Ok(()) /// } /// ``` pub async fn rename(from: impl AsRef, to: impl AsRef) -> io::Result<()> { Op::rename_at(from.as_ref(), to.as_ref(), 0)?.await } tokio-uring-0.4.0/src/fs/mod.rs000064400000000000000000000003351046102023000144210ustar 00000000000000//! Filesystem manipulation operations. mod directory; pub use directory::remove_dir; mod file; pub use file::remove_file; pub use file::rename; pub use file::File; mod open_options; pub use open_options::OpenOptions; tokio-uring-0.4.0/src/fs/open_options.rs000064400000000000000000000303341046102023000163600ustar 00000000000000use crate::driver::Op; use crate::fs::File; use std::io; use std::os::unix::fs::OpenOptionsExt; use std::path::Path; /// Options and flags which can be used to configure how a file is opened. /// /// This builder exposes the ability to configure how a [`File`] is opened and /// what operations are permitted on the open file. The [`File::open`] and /// [`File::create`] methods are aliases for commonly used options using this /// builder. /// /// Generally speaking, when using `OpenOptions`, you'll first call /// [`OpenOptions::new`], then chain calls to methods to set each option, then /// call [`OpenOptions::open`], passing the path of the file you're trying to /// open. This will give you a [`io::Result`] with a [`File`] inside that you /// can further operate on. /// /// # Examples /// /// Opening a file to read: /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .read(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` /// /// Opening a file for both reading and writing, as well as creating it if it /// doesn't exist: /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .read(true) /// .write(true) /// .create(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` #[derive(Debug, Clone)] pub struct OpenOptions { read: bool, write: bool, append: bool, truncate: bool, create: bool, create_new: bool, pub(crate) mode: libc::mode_t, pub(crate) custom_flags: libc::c_int, } impl OpenOptions { /// Creates a blank new set of options ready for configuration. /// /// All options are initially set to `false`. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .read(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` pub fn new() -> OpenOptions { OpenOptions { // generic read: false, write: false, append: false, truncate: false, create: false, create_new: false, mode: 0o666, custom_flags: 0, } } /// Sets the option for read access. /// /// This option, when true, will indicate that the file should be /// `read`-able if opened. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .read(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` pub fn read(&mut self, read: bool) -> &mut OpenOptions { self.read = read; self } /// Sets the option for write access. /// /// This option, when true, will indicate that the file should be /// `write`-able if opened. /// /// If the file already exists, any write calls on it will overwrite its /// contents, without truncating it. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .write(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` pub fn write(&mut self, write: bool) -> &mut OpenOptions { self.write = write; self } /// Sets the option for the append mode. /// /// This option, when true, means that writes will append to a file instead /// of overwriting previous contents. Note that setting /// `.write(true).append(true)` has the same effect as setting only /// `.append(true)`. /// /// For most filesystems, the operating system guarantees that all writes /// are atomic: no writes get mangled because another process writes at the /// same time. /// /// ## Note /// /// This function doesn't create the file if it doesn't exist. Use the /// [`OpenOptions::create`] method to do so. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .append(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` pub fn append(&mut self, append: bool) -> &mut OpenOptions { self.append = append; self } /// Sets the option for truncating a previous file. /// /// If a file is successfully opened with this option set it will truncate /// the file to 0 length if it already exists. /// /// The file must be opened with write access for truncate to work. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .write(true) /// .truncate(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions { self.truncate = truncate; self } /// Sets the option to create a new file, or open it if it already exists. /// /// In order for the file to be created, [`OpenOptions::write`] or /// [`OpenOptions::append`] access must be used. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .write(true) /// .create(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` pub fn create(&mut self, create: bool) -> &mut OpenOptions { self.create = create; self } /// Sets the option to create a new file, failing if it already exists. /// /// No file is allowed to exist at the target location, also no (dangling) symlink. In this /// way, if the call succeeds, the file returned is guaranteed to be new. /// /// This option is useful because it is atomic. Otherwise between checking /// whether a file exists and creating a new one, the file may have been /// created by another process (a TOCTOU race condition / attack). /// /// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are /// ignored. /// /// The file must be opened with write or append access in order to create /// a new file. /// /// [`.create()`]: OpenOptions::create /// [`.truncate()`]: OpenOptions::truncate /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .write(true) /// .create_new(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions { self.create_new = create_new; self } /// Opens a file at `path` with the options specified by `self`. /// /// # Errors /// /// This function will return an error under a number of different /// circumstances. Some of these error conditions are listed here, together /// with their [`io::ErrorKind`]. The mapping to [`io::ErrorKind`]s is not /// part of the compatibility contract of the function, especially the /// [`Other`] kind might change to more specific kinds in the future. /// /// * [`NotFound`]: The specified file does not exist and neither `create` /// or `create_new` is set. /// * [`NotFound`]: One of the directory components of the file path does /// not exist. /// * [`PermissionDenied`]: The user lacks permission to get the specified /// access rights for the file. /// * [`PermissionDenied`]: The user lacks permission to open one of the /// directory components of the specified path. /// * [`AlreadyExists`]: `create_new` was specified and the file already /// exists. /// * [`InvalidInput`]: Invalid combinations of open options (truncate /// without write access, no access mode set, etc.). /// * [`Other`]: One of the directory components of the specified file path /// was not, in fact, a directory. /// * [`Other`]: Filesystem-level errors: full disk, write permission /// requested on a read-only file system, exceeded disk quota, too many /// open files, too long filename, too many symbolic links in the /// specified path (Unix-like systems only), etc. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::OpenOptions; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let file = OpenOptions::new() /// .read(true) /// .open("foo.txt") /// .await?; /// Ok(()) /// }) /// } /// ``` /// /// [`AlreadyExists`]: io::ErrorKind::AlreadyExists /// [`InvalidInput`]: io::ErrorKind::InvalidInput /// [`NotFound`]: io::ErrorKind::NotFound /// [`Other`]: io::ErrorKind::Other /// [`PermissionDenied`]: io::ErrorKind::PermissionDenied pub async fn open(&self, path: impl AsRef) -> io::Result { Op::open(path.as_ref(), self)?.await } pub(crate) fn access_mode(&self) -> io::Result { match (self.read, self.write, self.append) { (true, false, false) => Ok(libc::O_RDONLY), (false, true, false) => Ok(libc::O_WRONLY), (true, true, false) => Ok(libc::O_RDWR), (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND), (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND), (false, false, false) => Err(io::Error::from_raw_os_error(libc::EINVAL)), } } pub(crate) fn creation_mode(&self) -> io::Result { match (self.write, self.append) { (true, false) => {} (false, false) => { if self.truncate || self.create || self.create_new { return Err(io::Error::from_raw_os_error(libc::EINVAL)); } } (_, true) => { if self.truncate && !self.create_new { return Err(io::Error::from_raw_os_error(libc::EINVAL)); } } } Ok(match (self.create, self.truncate, self.create_new) { (false, false, false) => 0, (true, false, false) => libc::O_CREAT, (false, true, false) => libc::O_TRUNC, (true, true, false) => libc::O_CREAT | libc::O_TRUNC, (_, _, true) => libc::O_CREAT | libc::O_EXCL, }) } } impl Default for OpenOptions { fn default() -> Self { Self::new() } } impl OpenOptionsExt for OpenOptions { fn mode(&mut self, mode: u32) -> &mut OpenOptions { self.mode = mode; self } fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions { self.custom_flags = flags; self } } tokio-uring-0.4.0/src/future.rs000064400000000000000000000013531046102023000145450ustar 00000000000000use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; macro_rules! ready { ($e:expr $(,)?) => { match $e { std::task::Poll::Ready(t) => t, std::task::Poll::Pending => return std::task::Poll::Pending, } }; } #[must_use = "futures do nothing unless you `.await` or poll them"] pub(crate) struct PollFn { f: F, } impl Unpin for PollFn {} pub(crate) fn poll_fn(f: F) -> PollFn where F: FnMut(&mut Context<'_>) -> Poll, { PollFn { f } } impl Future for PollFn where F: FnMut(&mut Context<'_>) -> Poll, { type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { (self.f)(cx) } } tokio-uring-0.4.0/src/lib.rs000064400000000000000000000217701046102023000140060ustar 00000000000000//! Tokio-uring provides a safe [io-uring] interface for the Tokio runtime. The //! library requires Linux kernel 5.10 or later. //! //! [io-uring]: https://kernel.dk/io_uring.pdf //! //! # Getting started //! //! Using `tokio-uring` requires starting a [`tokio-uring`] runtime. This //! runtime internally manages the main Tokio runtime and a `io-uring` driver. //! //! ```no_run //! use tokio_uring::fs::File; //! //! fn main() -> Result<(), Box> { //! tokio_uring::start(async { //! // Open a file //! let file = File::open("hello.txt").await?; //! //! let buf = vec![0; 4096]; //! // Read some data, the buffer is passed by ownership and //! // submitted to the kernel. When the operation completes, //! // we get the buffer back. //! let (res, buf) = file.read_at(buf, 0).await; //! let n = res?; //! //! // Display the contents //! println!("{:?}", &buf[..n]); //! //! Ok(()) //! }) //! } //! ``` //! //! Under the hood, `tokio_uring::start` starts a [`current-thread`] Runtime. //! For concurrency, spawn multiple threads, each with a `tokio-uring` runtime. //! The `tokio-uring` resource types are optimized for single-threaded usage and //! most are `!Sync`. //! //! # Submit-based operations //! //! Unlike Tokio proper, `io-uring` is based on submission based operations. //! Ownership of resources are passed to the kernel, which then performs the //! operation. When the operation completes, ownership is passed back to the //! caller. Because of this difference, the `tokio-uring` APIs diverge. //! //! For example, in the above example, reading from a `File` requires passing //! ownership of the buffer. //! //! # Closing resources //! //! With `io-uring`, closing a resource (e.g. a file) is an asynchronous //! operation. Because Rust does not support asynchronous drop yet, resource //! types provide an explicit `close()` function. If the `close()` function is //! not called, the resource will still be closed on drop, but the operation //! will happen in the background. There is no guarantee as to **when** the //! implicit close-on-drop operation happens, so it is recommended to explicitly //! call `close()`. #![warn(missing_docs)] macro_rules! syscall { ($fn: ident ( $($arg: expr),* $(,)* ) ) => {{ let res = unsafe { libc::$fn($($arg, )*) }; if res == -1 { Err(std::io::Error::last_os_error()) } else { Ok(res) } }}; } #[macro_use] mod future; mod driver; mod runtime; mod util; pub mod buf; pub mod fs; pub mod net; pub use runtime::spawn; pub use runtime::Runtime; use std::future::Future; /// Start an `io_uring` enabled Tokio runtime. /// /// All `tokio-uring` resource types must be used from within the context of a /// runtime. The `start` method initializes the runtime and runs it for the /// duration of `future`. /// /// The `tokio-uring` runtime is compatible with all Tokio, so it is possible to /// run Tokio based libraries (e.g. hyper) from within the tokio-uring runtime. /// A `tokio-uring` runtime consists of a Tokio `current_thread` runtime and an /// `io-uring` driver. All tasks spawned on the `tokio-uring` runtime are /// executed on the current thread. To add concurrency, spawn multiple threads, /// each with a `tokio-uring` runtime. /// /// # Examples /// /// Basic usage /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// // Open a file /// let file = File::open("hello.txt").await?; /// /// let buf = vec![0; 4096]; /// // Read some data, the buffer is passed by ownership and /// // submitted to the kernel. When the operation completes, /// // we get the buffer back. /// let (res, buf) = file.read_at(buf, 0).await; /// let n = res?; /// /// // Display the contents /// println!("{:?}", &buf[..n]); /// /// Ok(()) /// }) /// } /// ``` /// /// Using Tokio types from the `tokio-uring` runtime /// /// /// ```no_run /// use tokio::net::TcpListener; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// let listener = TcpListener::bind("127.0.0.1:8080").await?; /// /// loop { /// let (socket, _) = listener.accept().await?; /// // process socket /// } /// }) /// } /// ``` pub fn start(future: F) -> F::Output { let rt = runtime::Runtime::new(&builder()).unwrap(); rt.block_on(future) } /// Create and return an io_uring::Builder that can then be modified /// through its implementation methods. /// /// This function is provided to avoid requiring the user of this crate from /// having to use the io_uring crate as well. Refer to Builder::start example /// for its intended usage. pub fn uring_builder() -> io_uring::Builder { io_uring::IoUring::builder() } /// Builder API to allow starting the runtime and creating the io_uring driver with non-default /// parameters. // #[derive(Clone, Default)] pub struct Builder { entries: u32, urb: io_uring::Builder, } /// Return a Builder to allow setting parameters before calling the start method. /// Returns a Builder with our default values, all of which can be replaced with the methods below. /// /// Refer to Builder::start for an example. pub fn builder() -> Builder { Builder { entries: 256, urb: io_uring::IoUring::builder(), } } impl Builder { /// Set number of submission queue entries in uring. /// /// The kernel will ensure it uses a power of two and will round this up if necessary. /// The kernel requires the number of completion queue entries to be larger than /// the submission queue entries so generally will double the sq entries count. /// /// The caller can specify even a larger cq entries count by using the uring_builder /// as shown in the start example below. pub fn entries(&mut self, e: u32) -> &mut Self { self.entries = e; self } /// Replace the default io_uring Builder. This allows the caller to craft the io_uring Builder /// using the io_uring crate's Builder API. /// /// Refer to the Builder start method for an example. /// Refer to the io_uring::builder documentation for all the supported methods. pub fn uring_builder(&mut self, b: &io_uring::Builder) -> &mut Self { self.urb = b.clone(); self } /// Start an `io_uring` enabled Tokio runtime. /// /// # Examples /// /// Creating a uring driver with only 64 submission queue entries but /// many more completion queue entries. /// /// ```no_run /// use tokio::net::TcpListener; /// /// fn main() -> Result<(), Box> { /// tokio_uring::builder() /// .entries(64) /// .uring_builder(tokio_uring::uring_builder() /// .setup_cqsize(1024) /// ) /// .start(async { /// let listener = TcpListener::bind("127.0.0.1:8080").await?; /// /// loop { /// let (socket, _) = listener.accept().await?; /// // process socket /// } /// } /// ) /// } /// ``` pub fn start(&self, future: F) -> F::Output { let rt = runtime::Runtime::new(self).unwrap(); rt.block_on(future) } } /// A specialized `Result` type for `io-uring` operations with buffers. /// /// This type is used as a return value for asynchronous `io-uring` methods that /// require passing ownership of a buffer to the runtime. When the operation /// completes, the buffer is returned whether or not the operation completed /// successfully. /// /// # Examples /// /// ```no_run /// use tokio_uring::fs::File; /// /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// // Open a file /// let file = File::open("hello.txt").await?; /// /// let buf = vec![0; 4096]; /// // Read some data, the buffer is passed by ownership and /// // submitted to the kernel. When the operation completes, /// // we get the buffer back. /// let (res, buf) = file.read_at(buf, 0).await; /// let n = res?; /// /// // Display the contents /// println!("{:?}", &buf[..n]); /// /// Ok(()) /// }) /// } /// ``` pub type BufResult = (std::io::Result, B); /// The simplest possible operation. Just posts a completion event, nothing else. /// /// This has a place in benchmarking and sanity checking uring. /// /// # Examples /// /// ```no_run /// fn main() -> Result<(), Box> { /// tokio_uring::start(async { /// // Place a NoOp on the ring, and await completion event /// tokio_uring::no_op().await?; /// Ok(()) /// }) /// } /// ``` pub async fn no_op() -> std::io::Result<()> { let op = driver::Op::::no_op().unwrap(); op.await } tokio-uring-0.4.0/src/net/mod.rs000064400000000000000000000011361046102023000145770ustar 00000000000000//! TCP/UDP bindings for `tokio-uring`. //! //! This module contains the TCP/UDP networking types, similar to the standard //! library, which can be used to implement networking protocols. //! //! # Organization //! //! * [`TcpListener`] and [`TcpStream`] provide functionality for communication over TCP //! * [`UdpSocket`] provides functionality for communication over UDP //! //! [`TcpListener`]: TcpListener //! [`TcpStream`]: TcpStream //! [`UdpSocket`]: UdpSocket mod tcp; mod udp; mod unix; pub use tcp::{TcpListener, TcpStream}; pub use udp::UdpSocket; pub use unix::{UnixListener, UnixStream}; tokio-uring-0.4.0/src/net/tcp/listener.rs000064400000000000000000000070251046102023000164360ustar 00000000000000use super::TcpStream; use crate::driver::Socket; use std::{io, net::SocketAddr}; /// A TCP socket server, listening for connections. /// /// You can accept a new connection by using the [`accept`](`TcpListener::accept`) /// method. /// /// # Examples /// /// ``` /// use tokio_uring::net::TcpListener; /// use tokio_uring::net::TcpStream; /// /// let listener = TcpListener::bind("127.0.0.1:2345".parse().unwrap()).unwrap(); /// /// tokio_uring::start(async move { /// let (tx_ch, rx_ch) = tokio::sync::oneshot::channel(); /// /// tokio_uring::spawn(async move { /// let (rx, _) = listener.accept().await.unwrap(); /// if let Err(_) = tx_ch.send(rx) { /// panic!("The receiver dropped"); /// } /// }); /// tokio::task::yield_now().await; // Ensure the listener.accept().await has been kicked off. /// /// let tx = TcpStream::connect("127.0.0.1:2345".parse().unwrap()).await.unwrap(); /// let rx = rx_ch.await.expect("The spawned task expected to send a TcpStream"); /// /// tx.write(b"test" as &'static [u8]).await.0.unwrap(); /// /// let (_, buf) = rx.read(vec![0; 4]).await; /// /// assert_eq!(buf, b"test"); /// }); /// ``` pub struct TcpListener { inner: Socket, } impl TcpListener { /// Creates a new TcpListener, which will be bound to the specified address. /// /// The returned listener is ready for accepting connections. /// /// Binding with a port number of 0 will request that the OS assigns a port /// to this listener. pub fn bind(addr: SocketAddr) -> io::Result { let socket = Socket::bind(addr, libc::SOCK_STREAM)?; socket.listen(1024)?; Ok(TcpListener { inner: socket }) } /// Returns the local address that this listener is bound to. /// /// This can be useful, for example, when binding to port 0 to /// figure out which port was actually bound. /// /// # Examples /// /// ``` /// use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; /// use tokio_uring::net::TcpListener; /// /// let listener = TcpListener::bind("127.0.0.1:8080".parse().unwrap()).unwrap(); /// /// let addr = listener.local_addr().expect("Couldn't get local address"); /// assert_eq!(addr, SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080))); /// ``` pub fn local_addr(&self) -> io::Result { use std::os::unix::io::{AsRawFd, FromRawFd}; let fd = self.inner.as_raw_fd(); // SAFETY: Our fd is the handle the kernel has given us for a TcpListener. // Create a std::net::TcpListener long enough to call its local_addr method // and then forget it so the socket is not closed here. let l = unsafe { std::net::TcpListener::from_raw_fd(fd) }; let local_addr = l.local_addr(); std::mem::forget(l); local_addr } /// Accepts a new incoming connection from this listener. /// /// This function will yield once a new TCP connection is established. When /// established, the corresponding [`TcpStream`] and the remote peer's /// address will be returned. /// /// [`TcpStream`]: struct@crate::net::TcpStream pub async fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { let (socket, socket_addr) = self.inner.accept().await?; let stream = TcpStream { inner: socket }; let socket_addr = socket_addr.ok_or_else(|| { io::Error::new(io::ErrorKind::Other, "Could not get socket IP address") })?; Ok((stream, socket_addr)) } } tokio-uring-0.4.0/src/net/tcp/mod.rs000064400000000000000000000001251046102023000153620ustar 00000000000000mod listener; pub use listener::TcpListener; mod stream; pub use stream::TcpStream; tokio-uring-0.4.0/src/net/tcp/stream.rs000064400000000000000000000167221046102023000161100ustar 00000000000000use std::{ io, net::SocketAddr, os::unix::prelude::{AsRawFd, FromRawFd, RawFd}, }; use crate::{ buf::{IoBuf, IoBufMut}, driver::{SharedFd, Socket}, }; /// A TCP stream between a local and a remote socket. /// /// A TCP stream can either be created by connecting to an endpoint, via the /// [`connect`] method, or by [`accepting`] a connection from a [`listener`]. /// /// # Examples /// /// ```no_run /// use tokio_uring::net::TcpStream; /// use std::net::ToSocketAddrs; /// /// fn main() -> std::io::Result<()> { /// tokio_uring::start(async { /// // Connect to a peer /// let mut stream = TcpStream::connect("127.0.0.1:8080".parse().unwrap()).await?; /// /// // Write some data. /// let (result, _) = stream.write(b"hello world!".as_slice()).await; /// result.unwrap(); /// /// Ok(()) /// }) /// } /// ``` /// /// [`connect`]: TcpStream::connect /// [`accepting`]: crate::net::TcpListener::accept /// [`listener`]: crate::net::TcpListener pub struct TcpStream { pub(super) inner: Socket, } impl TcpStream { /// Opens a TCP connection to a remote host at the given `SocketAddr` pub async fn connect(addr: SocketAddr) -> io::Result { let socket = Socket::new(addr, libc::SOCK_STREAM)?; socket.connect(socket2::SockAddr::from(addr)).await?; let tcp_stream = TcpStream { inner: socket }; Ok(tcp_stream) } /// Creates new `TcpStream` from a previously bound `std::net::TcpStream`. /// /// This function is intended to be used to wrap a TCP stream from the /// standard library in the tokio-uring equivalent. The conversion assumes nothing /// about the underlying socket; it is left up to the user to decide what socket /// options are appropriate for their use case. /// /// This can be used in conjunction with socket2's `Socket` interface to /// configure a socket before it's handed off, such as setting options like /// `reuse_address` or binding to multiple addresses. pub fn from_std(socket: std::net::TcpStream) -> Self { let inner = Socket::from_std(socket); Self { inner } } pub(crate) fn from_socket(inner: Socket) -> Self { Self { inner } } /// Read some data from the stream into the buffer, returning the original buffer and /// quantity of data read. pub async fn read(&self, buf: T) -> crate::BufResult { self.inner.read(buf).await } /// Write some data to the stream from the buffer, returning the original buffer and /// quantity of data written. pub async fn write(&self, buf: T) -> crate::BufResult { self.inner.write(buf).await } /// Attempts to write an entire buffer to the stream. /// /// This method will continuously call [`write`] until there is no more data to be /// written or an error is returned. This method will not return until the entire /// buffer has been successfully written or an error has occurred. /// /// If the buffer contains no data, this will never call [`write`]. /// /// # Errors /// /// This function will return the first error that [`write`] returns. /// /// # Examples /// /// ```no_run /// use std::net::SocketAddr; /// use tokio_uring::net::TcpListener; /// use tokio_uring::buf::IoBuf; /// /// let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); /// /// tokio_uring::start(async { /// let listener = TcpListener::bind(addr).unwrap(); /// /// println!("Listening on {}", listener.local_addr().unwrap()); /// /// loop { /// let (stream, _) = listener.accept().await.unwrap(); /// tokio_uring::spawn(async move { /// let mut n = 0; /// let mut buf = vec![0u8; 4096]; /// loop { /// let (result, nbuf) = stream.read(buf).await; /// buf = nbuf; /// let read = result.unwrap(); /// if read == 0 { /// break; /// } /// /// let (res, slice) = stream.write_all(buf.slice(..read)).await; /// let _ = res.unwrap(); /// buf = slice.into_inner(); /// n += read; /// } /// }); /// } /// }); /// ``` /// /// [`write`]: Self::write pub async fn write_all(&self, mut buf: T) -> crate::BufResult<(), T> { let mut n = 0; while n < buf.bytes_init() { let res = self.write(buf.slice(n..)).await; match res { (Ok(0), slice) => { return ( Err(std::io::Error::new( std::io::ErrorKind::WriteZero, "failed to write whole buffer", )), slice.into_inner(), ) } (Ok(m), slice) => { n += m; buf = slice.into_inner(); } // This match on an EINTR error is not performed because this // crate's design ensures we are not calling the 'wait' option // in the ENTER syscall. Only an Enter with 'wait' can generate // an EINTR according to the io_uring man pages. // (Err(ref e), slice) if e.kind() == std::io::ErrorKind::Interrupted => { // buf = slice.into_inner(); // }, (Err(e), slice) => return (Err(e), slice.into_inner()), } } (Ok(()), buf) } /// Write data from buffers into this socket returning how many bytes were /// written. /// /// This function will attempt to write the entire contents of `bufs`, but /// the entire write may not succeed, or the write may also generate an /// error. The bytes will be written starting at the specified offset. /// /// # Return /// /// The method returns the operation result and the same array of buffers /// passed in as an argument. A return value of `0` typically means that the /// underlying socket is no longer able to accept bytes and will likely not /// be able to in the future as well, or that the buffer provided is empty. /// /// # Errors /// /// Each call to `write` may generate an I/O error indicating that the /// operation could not be completed. If an error is returned then no bytes /// in the buffer were written to this writer. /// /// It is **not** considered an error if the entire buffer could not be /// written to this writer. /// /// [`Ok(n)`]: Ok pub async fn writev(&self, buf: Vec) -> crate::BufResult> { self.inner.writev(buf).await } /// Shuts down the read, write, or both halves of this connection. /// /// This function will cause all pending and future I/O on the specified portions to return /// immediately with an appropriate value. pub fn shutdown(&self, how: std::net::Shutdown) -> io::Result<()> { self.inner.shutdown(how) } } impl FromRawFd for TcpStream { unsafe fn from_raw_fd(fd: RawFd) -> Self { TcpStream::from_socket(Socket::from_shared_fd(SharedFd::new(fd))) } } impl AsRawFd for TcpStream { fn as_raw_fd(&self) -> RawFd { self.inner.as_raw_fd() } } tokio-uring-0.4.0/src/net/udp.rs000064400000000000000000000172331046102023000146150ustar 00000000000000use crate::{ buf::{IoBuf, IoBufMut}, driver::{SharedFd, Socket}, }; use socket2::SockAddr; use std::{ io, net::SocketAddr, os::unix::prelude::{AsRawFd, FromRawFd, RawFd}, }; /// A UDP socket. /// /// UDP is "connectionless", unlike TCP. Meaning, regardless of what address you've bound to, a `UdpSocket` /// is free to communicate with many different remotes. In tokio there are basically two main ways to use `UdpSocket`: /// /// * one to many: [`bind`](`UdpSocket::bind`) and use [`send_to`](`UdpSocket::send_to`) /// and [`recv_from`](`UdpSocket::recv_from`) to communicate with many different addresses /// * one to one: [`connect`](`UdpSocket::connect`) and associate with a single address, using [`write`](`UdpSocket::write`) /// and [`read`](`UdpSocket::read`) to communicate only with that remote address /// /// # Examples /// Bind and connect a pair of sockets and send a packet: /// /// ``` /// use tokio_uring::net::UdpSocket; /// use std::net::SocketAddr; /// fn main() -> std::io::Result<()> { /// tokio_uring::start(async { /// let first_addr: SocketAddr = "127.0.0.1:2401".parse().unwrap(); /// let second_addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); /// /// // bind sockets /// let socket = UdpSocket::bind(first_addr.clone()).await?; /// let other_socket = UdpSocket::bind(second_addr.clone()).await?; /// /// // connect sockets /// socket.connect(second_addr).await.unwrap(); /// other_socket.connect(first_addr).await.unwrap(); /// /// let buf = vec![0; 32]; /// /// // write data /// let (result, _) = socket.write(b"hello world".as_slice()).await; /// result.unwrap(); /// /// // read data /// let (result, buf) = other_socket.read(buf).await; /// let n_bytes = result.unwrap(); /// /// assert_eq!(b"hello world", &buf[..n_bytes]); /// /// Ok(()) /// }) /// } /// ``` /// Send and receive packets without connecting: /// /// ``` /// use tokio_uring::net::UdpSocket; /// use std::net::SocketAddr; /// fn main() -> std::io::Result<()> { /// tokio_uring::start(async { /// let first_addr: SocketAddr = "127.0.0.1:2401".parse().unwrap(); /// let second_addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); /// /// // bind sockets /// let socket = UdpSocket::bind(first_addr.clone()).await?; /// let other_socket = UdpSocket::bind(second_addr.clone()).await?; /// /// let buf = vec![0; 32]; /// /// // write data /// let (result, _) = socket.send_to(b"hello world".as_slice(), second_addr).await; /// result.unwrap(); /// /// // read data /// let (result, buf) = other_socket.recv_from(buf).await; /// let (n_bytes, addr) = result.unwrap(); /// /// assert_eq!(addr, first_addr); /// assert_eq!(b"hello world", &buf[..n_bytes]); /// /// Ok(()) /// }) /// } /// ``` pub struct UdpSocket { pub(super) inner: Socket, } impl UdpSocket { /// Creates a new UDP socket and attempt to bind it to the addr provided. pub async fn bind(socket_addr: SocketAddr) -> io::Result { let socket = Socket::bind(socket_addr, libc::SOCK_DGRAM)?; Ok(UdpSocket { inner: socket }) } /// Creates new `UdpSocket` from a previously bound `std::net::UdpSocket`. /// /// This function is intended to be used to wrap a UDP socket from the /// standard library in the tokio-uring equivalent. The conversion assumes nothing /// about the underlying socket; it is left up to the user to decide what socket /// options are appropriate for their use case. /// /// This can be used in conjunction with socket2's `Socket` interface to /// configure a socket before it's handed off, such as setting options like /// `reuse_address` or binding to multiple addresses. /// /// # Example /// /// ``` /// use socket2::{Protocol, Socket, Type}; /// use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; /// use tokio_uring::net::UdpSocket; /// /// fn main() -> std::io::Result<()> { /// tokio_uring::start(async { /// let std_addr: SocketAddr = "127.0.0.1:2401".parse().unwrap(); /// let second_addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); /// let sock = Socket::new(socket2::Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?; /// sock.set_reuse_port(true)?; /// sock.set_nonblocking(true)?; /// sock.bind(&std_addr.into())?; /// /// let std_socket = UdpSocket::from_std(sock.into()); /// let other_socket = UdpSocket::bind(second_addr).await?; /// /// let buf = vec![0; 32]; /// /// // write data /// let (result, _) = std_socket /// .send_to(b"hello world".as_slice(), second_addr) /// .await; /// result.unwrap(); /// /// // read data /// let (result, buf) = other_socket.recv_from(buf).await; /// let (n_bytes, addr) = result.unwrap(); /// /// assert_eq!(addr, std_addr); /// assert_eq!(b"hello world", &buf[..n_bytes]); /// /// Ok(()) /// }) /// } /// ``` pub fn from_std(socket: std::net::UdpSocket) -> Self { let inner = Socket::from_std(socket); Self { inner } } pub(crate) fn from_socket(inner: Socket) -> Self { Self { inner } } /// Connects this UDP socket to a remote address, allowing the `write` and /// `read` syscalls to be used to send data and also applies filters to only /// receive data from the specified address. /// /// Note that usually, a successful `connect` call does not specify /// that there is a remote server listening on the port, rather, such an /// error would only be detected after the first send. pub async fn connect(&self, socket_addr: SocketAddr) -> io::Result<()> { self.inner.connect(SockAddr::from(socket_addr)).await } /// Sends data on the socket to the given address. On success, returns the /// number of bytes written. pub async fn send_to( &self, buf: T, socket_addr: SocketAddr, ) -> crate::BufResult { self.inner.send_to(buf, socket_addr).await } /// Receives a single datagram message on the socket. On success, returns /// the number of bytes read and the origin. pub async fn recv_from(&self, buf: T) -> crate::BufResult<(usize, SocketAddr), T> { self.inner.recv_from(buf).await } /// Read a packet of data from the socket into the buffer, returning the original buffer and /// quantity of data read. pub async fn read(&self, buf: T) -> crate::BufResult { self.inner.read(buf).await } /// Write some data to the socket from the buffer, returning the original buffer and /// quantity of data written. pub async fn write(&self, buf: T) -> crate::BufResult { self.inner.write(buf).await } /// Shuts down the read, write, or both halves of this connection. /// /// This function will cause all pending and future I/O on the specified portions to return /// immediately with an appropriate value. pub fn shutdown(&self, how: std::net::Shutdown) -> io::Result<()> { self.inner.shutdown(how) } } impl FromRawFd for UdpSocket { unsafe fn from_raw_fd(fd: RawFd) -> Self { UdpSocket::from_socket(Socket::from_shared_fd(SharedFd::new(fd))) } } impl AsRawFd for UdpSocket { fn as_raw_fd(&self) -> RawFd { self.inner.as_raw_fd() } } tokio-uring-0.4.0/src/net/unix/listener.rs000064400000000000000000000064201046102023000166310ustar 00000000000000use super::UnixStream; use crate::driver::Socket; use std::{io, path::Path}; /// A Unix socket server, listening for connections. /// /// You can accept a new connection by using the [`accept`](`UnixListener::accept`) /// method. /// /// # Examples /// /// ``` /// use tokio_uring::net::UnixListener; /// use tokio_uring::net::UnixStream; /// /// let sock_file = "/tmp/tokio-uring-unix-test.sock"; /// let listener = UnixListener::bind(&sock_file).unwrap(); /// /// tokio_uring::start(async move { /// let (tx_ch, rx_ch) = tokio::sync::oneshot::channel(); /// /// tokio_uring::spawn(async move { /// let rx = listener.accept().await.unwrap(); /// if let Err(_) = tx_ch.send(rx) { /// panic!("The receiver dropped"); /// } /// }); /// tokio::task::yield_now().await; // Ensure the listener.accept().await has been kicked off. /// /// let tx = UnixStream::connect(&sock_file).await.unwrap(); /// let rx = rx_ch.await.expect("The spawned task expected to send a UnixStream"); /// /// tx.write(b"test" as &'static [u8]).await.0.unwrap(); /// /// let (_, buf) = rx.read(vec![0; 4]).await; /// /// assert_eq!(buf, b"test"); /// }); /// /// std::fs::remove_file(&sock_file).unwrap(); /// ``` pub struct UnixListener { inner: Socket, } impl UnixListener { /// Creates a new UnixListener, which will be bound to the specified file path. /// The file path cannnot yet exist, and will be cleaned up upon dropping `UnixListener` pub fn bind>(path: P) -> io::Result { let socket = Socket::bind_unix(path, libc::SOCK_STREAM)?; socket.listen(1024)?; Ok(UnixListener { inner: socket }) } /// Returns the local address that this listener is bound to. /// /// # Examples /// /// ``` /// use tokio_uring::net::UnixListener; /// use std::path::Path; /// /// let sock_file = "/tmp/tokio-uring-unix-test.sock"; /// let listener = UnixListener::bind(&sock_file).unwrap(); /// /// let addr = listener.local_addr().expect("Couldn't get local address"); /// assert_eq!(addr.as_pathname(), Some(Path::new(sock_file))); /// /// std::fs::remove_file(&sock_file).unwrap(); /// ``` pub fn local_addr(&self) -> io::Result { use std::os::unix::io::{AsRawFd, FromRawFd}; let fd = self.inner.as_raw_fd(); // SAFETY: Our fd is the handle the kernel has given us for a UnixListener. // Create a std::net::UnixListener long enough to call its local_addr method // and then forget it so the socket is not closed here. let l = unsafe { std::os::unix::net::UnixListener::from_raw_fd(fd) }; let local_addr = l.local_addr(); std::mem::forget(l); local_addr } /// Accepts a new incoming connection from this listener. /// /// This function will yield once a new Unix domain socket connection /// is established. When established, the corresponding [`UnixStream`] and /// will be returned. /// /// [`UnixStream`]: struct@crate::net::UnixStream pub async fn accept(&self) -> io::Result { let (socket, _) = self.inner.accept().await?; let stream = UnixStream { inner: socket }; Ok(stream) } } tokio-uring-0.4.0/src/net/unix/mod.rs000064400000000000000000000001271046102023000155610ustar 00000000000000mod listener; pub use listener::UnixListener; mod stream; pub use stream::UnixStream; tokio-uring-0.4.0/src/net/unix/stream.rs000064400000000000000000000150101046102023000162720ustar 00000000000000use crate::{ buf::{IoBuf, IoBufMut}, driver::{SharedFd, Socket}, }; use socket2::SockAddr; use std::{ io, os::unix::prelude::{AsRawFd, FromRawFd, RawFd}, path::Path, }; /// A Unix stream between two local sockets on a Unix OS. /// /// A Unix stream can either be created by connecting to an endpoint, via the /// [`connect`] method, or by [`accepting`] a connection from a [`listener`]. /// /// # Examples /// /// ```no_run /// use tokio_uring::net::UnixStream; /// use std::net::ToSocketAddrs; /// /// fn main() -> std::io::Result<()> { /// tokio_uring::start(async { /// // Connect to a peer /// let mut stream = UnixStream::connect("/tmp/tokio-uring-unix-test.sock").await?; /// /// // Write some data. /// let (result, _) = stream.write(b"hello world!".as_slice()).await; /// result.unwrap(); /// /// Ok(()) /// }) /// } /// ``` /// /// [`connect`]: UnixStream::connect /// [`accepting`]: crate::net::UnixListener::accept /// [`listener`]: crate::net::UnixListener pub struct UnixStream { pub(super) inner: Socket, } impl UnixStream { /// Opens a Unix connection to the specified file path. There must be a /// `UnixListener` or equivalent listening on the corresponding Unix domain socket /// to successfully connect and return a `UnixStream`. pub async fn connect>(path: P) -> io::Result { let socket = Socket::new_unix(libc::SOCK_STREAM)?; socket.connect(SockAddr::unix(path)?).await?; let unix_stream = UnixStream { inner: socket }; Ok(unix_stream) } /// Creates new `UnixStream` from a previously bound `std::os::unix::net::UnixStream`. /// /// This function is intended to be used to wrap a TCP stream from the /// standard library in the tokio-uring equivalent. The conversion assumes nothing /// about the underlying socket; it is left up to the user to decide what socket /// options are appropriate for their use case. /// /// This can be used in conjunction with socket2's `Socket` interface to /// configure a socket before it's handed off, such as setting options like /// `reuse_address` or binding to multiple addresses. pub fn from_std(socket: std::os::unix::net::UnixStream) -> UnixStream { let inner = Socket::from_std(socket); Self { inner } } pub(crate) fn from_socket(inner: Socket) -> Self { Self { inner } } /// Read some data from the stream into the buffer, returning the original buffer and /// quantity of data read. pub async fn read(&self, buf: T) -> crate::BufResult { self.inner.read(buf).await } /// Write some data to the stream from the buffer, returning the original buffer and /// quantity of data written. pub async fn write(&self, buf: T) -> crate::BufResult { self.inner.write(buf).await } /// Attempts to write an entire buffer to the stream. /// /// This method will continuously call [`write`] until there is no more data to be /// written or an error is returned. This method will not return until the entire /// buffer has been successfully written or an error has occurred. /// /// If the buffer contains no data, this will never call [`write`]. /// /// # Errors /// /// This function will return the first error that [`write`] returns. /// /// [`write`]: Self::write pub async fn write_all(&self, mut buf: T) -> crate::BufResult<(), T> { // This function is copied from the TcpStream impl. let mut n = 0; while n < buf.bytes_init() { let res = self.write(buf.slice(n..)).await; match res { (Ok(0), slice) => { return ( Err(std::io::Error::new( std::io::ErrorKind::WriteZero, "failed to write whole buffer", )), slice.into_inner(), ) } (Ok(m), slice) => { n += m; buf = slice.into_inner(); } // This match on an EINTR error is not performed because this // crate's design ensures we are not calling the 'wait' option // in the ENTER syscall. Only an Enter with 'wait' can generate // an EINTR according to the io_uring man pages. // (Err(ref e), slice) if e.kind() == std::io::ErrorKind::Interrupted => { // buf = slice.into_inner(); // }, (Err(e), slice) => return (Err(e), slice.into_inner()), } } (Ok(()), buf) } /// Write data from buffers into this socket returning how many bytes were /// written. /// /// This function will attempt to write the entire contents of `bufs`, but /// the entire write may not succeed, or the write may also generate an /// error. The bytes will be written starting at the specified offset. /// /// # Return /// /// The method returns the operation result and the same array of buffers /// passed in as an argument. A return value of `0` typically means that the /// underlying socket is no longer able to accept bytes and will likely not /// be able to in the future as well, or that the buffer provided is empty. /// /// # Errors /// /// Each call to `write` may generate an I/O error indicating that the /// operation could not be completed. If an error is returned then no bytes /// in the buffer were written to this writer. /// /// It is **not** considered an error if the entire buffer could not be /// written to this writer. /// /// [`Ok(n)`]: Ok pub async fn writev(&self, buf: Vec) -> crate::BufResult> { self.inner.writev(buf).await } /// Shuts down the read, write, or both halves of this connection. /// /// This function will cause all pending and future I/O on the specified portions to return /// immediately with an appropriate value. pub fn shutdown(&self, how: std::net::Shutdown) -> io::Result<()> { self.inner.shutdown(how) } } impl FromRawFd for UnixStream { unsafe fn from_raw_fd(fd: RawFd) -> Self { UnixStream::from_socket(Socket::from_shared_fd(SharedFd::new(fd))) } } impl AsRawFd for UnixStream { fn as_raw_fd(&self) -> RawFd { self.inner.as_raw_fd() } } tokio-uring-0.4.0/src/runtime/context.rs000064400000000000000000000030101046102023000163720ustar 00000000000000use crate::driver::Driver; use crate::util::PhantomUnsendUnsync; use std::cell::RefCell; use std::marker::PhantomData; /// Owns the driver and resides in thread-local storage. pub(crate) struct RuntimeContext { driver: RefCell>, _phantom: PhantomUnsendUnsync, } impl RuntimeContext { /// Construct the context with an uninitialized driver. pub(crate) const fn new() -> Self { Self { driver: RefCell::new(None), _phantom: PhantomData, } } /// Initialize the driver. pub(crate) fn set_driver(&self, driver: Driver) { let mut guard = self.driver.borrow_mut(); assert!(guard.is_none(), "Attempted to initialize the driver twice"); *guard = Some(driver); } pub(crate) fn unset_driver(&self) { let mut guard = self.driver.borrow_mut(); assert!(guard.is_some(), "Attempted to clear nonexistent driver"); *guard = None; } /// Check if driver is initialized pub(crate) fn is_set(&self) -> bool { self.driver .try_borrow() .map(|b| b.is_some()) .unwrap_or(false) } /// Execute a function which requires mutable access to the driver. pub(crate) fn with_driver_mut(&self, f: F) -> R where F: FnOnce(&mut Driver) -> R, { let mut guard = self.driver.borrow_mut(); let driver = guard .as_mut() .expect("Attempted to access driver in invalid context"); f(driver) } } tokio-uring-0.4.0/src/runtime/mod.rs000064400000000000000000000074171046102023000155040ustar 00000000000000use crate::driver::Driver; use std::future::Future; use std::io; use std::mem::ManuallyDrop; use std::os::unix::io::AsRawFd; use tokio::io::unix::AsyncFd; use tokio::task::LocalSet; mod context; pub(crate) use context::RuntimeContext; thread_local! { pub(crate) static CONTEXT: RuntimeContext = RuntimeContext::new(); } /// The Runtime executor pub struct Runtime { /// LocalSet for !Send tasks local: ManuallyDrop, /// Tokio runtime, always current-thread rt: ManuallyDrop, } /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it. /// /// Spawning a task enables the task to execute concurrently to other tasks. /// There is no guarantee that a spawned task will execute to completion. When a /// runtime is shutdown, all outstanding tasks are dropped, regardless of the /// lifecycle of that task. /// /// This function must be called from the context of a `tokio-uring` runtime. /// /// [`JoinHandle`]: tokio::task::JoinHandle /// /// # Examples /// /// In this example, a server is started and `spawn` is used to start a new task /// that processes each received connection. /// /// ```no_run /// tokio_uring::start(async { /// let handle = tokio_uring::spawn(async { /// println!("hello from a background task"); /// }); /// /// // Let the task complete /// handle.await.unwrap(); /// }); /// ``` pub fn spawn(task: T) -> tokio::task::JoinHandle { tokio::task::spawn_local(task) } impl Runtime { /// Create a new tokio_uring runtime on the current thread pub fn new(b: &crate::Builder) -> io::Result { let rt = tokio::runtime::Builder::new_current_thread() .on_thread_park(|| { CONTEXT.with(|x| { let _ = x.with_driver_mut(|d| d.uring.submit()); }); }) .enable_all() .build()?; let rt = ManuallyDrop::new(rt); let local = ManuallyDrop::new(LocalSet::new()); let driver = Driver::new(b)?; let driver_fd = driver.as_raw_fd(); CONTEXT.with(|cx| cx.set_driver(driver)); let drive = { let _guard = rt.enter(); let driver = AsyncFd::new(driver_fd).unwrap(); async move { loop { // Wait for read-readiness let mut guard = driver.readable().await.unwrap(); CONTEXT.with(|cx| cx.with_driver_mut(|driver| driver.tick())); guard.clear_ready(); } } }; local.spawn_local(drive); Ok(Runtime { local, rt }) } /// Runs a future to completion on the current runtime pub fn block_on(&self, future: F) -> F::Output where F: Future, { tokio::pin!(future); self.rt .block_on(self.local.run_until(crate::future::poll_fn(|cx| { // assert!(drive.as_mut().poll(cx).is_pending()); future.as_mut().poll(cx) }))) } } impl Drop for Runtime { fn drop(&mut self) { // drop tasks unsafe { ManuallyDrop::drop(&mut self.local); ManuallyDrop::drop(&mut self.rt); } // once tasks are dropped, we can unset the driver // this will block until all completions are received CONTEXT.with(|rc| rc.unset_driver()) } } #[cfg(test)] mod test { use super::*; use crate::builder; #[test] fn block_on() { let rt = Runtime::new(&builder()).unwrap(); rt.block_on(async move { () }); } #[test] fn block_on_twice() { let rt = Runtime::new(&builder()).unwrap(); rt.block_on(async move { () }); rt.block_on(async move { () }); } } tokio-uring-0.4.0/src/util.rs000064400000000000000000000002361046102023000142070ustar 00000000000000use std::marker::PhantomData; /// Utility ZST for ensuring that opcodes are `!Send` and `!Sync`. pub(crate) type PhantomUnsendUnsync = PhantomData<*mut ()>; tokio-uring-0.4.0/tests/buf.rs000064400000000000000000000076051046102023000143700ustar 00000000000000use tokio_uring::buf::{IoBuf, IoBufMut}; use std::mem; #[test] fn test_vec() { let mut v = vec![]; assert_eq!(v.as_ptr(), v.stable_ptr()); assert_eq!(v.as_mut_ptr(), v.stable_mut_ptr()); assert_eq!(v.bytes_init(), 0); assert_eq!(v.bytes_total(), 0); v.reserve(100); assert_eq!(v.as_ptr(), v.stable_ptr()); assert_eq!(v.as_mut_ptr(), v.stable_mut_ptr()); assert_eq!(v.bytes_init(), 0); assert_eq!(v.bytes_total(), v.capacity()); v.extend(b"hello"); assert_eq!(v.as_ptr(), v.stable_ptr()); assert_eq!(v.as_mut_ptr(), v.stable_mut_ptr()); assert_eq!(v.bytes_init(), 5); assert_eq!(v.bytes_total(), v.capacity()); // Assume init does not go backwards unsafe { v.set_init(3); } assert_eq!(&v[..], b"hello"); // Initializing goes forward unsafe { std::ptr::copy(DATA.as_ptr(), v.stable_mut_ptr(), 10); v.set_init(10); } assert_eq!(&v[..], &DATA[..10]); } #[test] fn test_slice() { let v = &b""[..]; assert_eq!(v.as_ptr(), v.stable_ptr()); assert_eq!(v.bytes_init(), 0); assert_eq!(v.bytes_total(), 0); let v = &b"hello"[..]; assert_eq!(v.as_ptr(), v.stable_ptr()); assert_eq!(v.bytes_init(), 5); assert_eq!(v.bytes_total(), 5); } const DATA: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789!?"; macro_rules! test_slice { ( $( $name:ident => $buf:expr; )* ) => { $( mod $name { use super::*; #[test] fn test_slice_read() { let buf = $buf; let slice = buf.slice(..); assert_eq!(slice.begin(), 0); assert_eq!(slice.end(), DATA.len()); assert_eq!(&slice[..], DATA); assert_eq!(&slice[5..], &DATA[5..]); assert_eq!(&slice[10..15], &DATA[10..15]); assert_eq!(&slice[..15], &DATA[..15]); let buf = slice.into_inner(); let slice = buf.slice(10..); assert_eq!(slice.begin(), 10); assert_eq!(slice.end(), DATA.len()); assert_eq!(&slice[..], &DATA[10..]); assert_eq!(&slice[10..], &DATA[20..]); assert_eq!(&slice[5..15], &DATA[15..25]); assert_eq!(&slice[..15], &DATA[10..25]); let buf = slice.into_inner(); let slice = buf.slice(5..15); assert_eq!(slice.begin(), 5); assert_eq!(slice.end(), 15); assert_eq!(&slice[..], &DATA[5..15]); assert_eq!(&slice[5..], &DATA[10..15]); assert_eq!(&slice[5..8], &DATA[10..13]); assert_eq!(&slice[..5], &DATA[5..10]); let buf = slice.into_inner(); let slice = buf.slice(..15); assert_eq!(slice.begin(), 0); assert_eq!(slice.end(), 15); assert_eq!(&slice[..], &DATA[..15]); assert_eq!(&slice[5..], &DATA[5..15]); assert_eq!(&slice[5..10], &DATA[5..10]); assert_eq!(&slice[..5], &DATA[..5]); } } )* }; } test_slice! { vec => Vec::from(DATA); slice => DATA; } #[test] fn can_deref_slice_into_uninit_buf() { let buf = Vec::with_capacity(10).slice(..); let _ = buf.stable_ptr(); assert_eq!(buf.bytes_init(), 0); assert_eq!(buf.bytes_total(), 10); assert!(buf[..].is_empty()); let mut v = Vec::with_capacity(10); v.push(42); let mut buf = v.slice(..); let _ = buf.stable_mut_ptr(); assert_eq!(buf.bytes_init(), 1); assert_eq!(buf.bytes_total(), 10); assert_eq!(mem::replace(&mut buf[0], 0), 42); buf.copy_from_slice(&[43]); assert_eq!(&buf[..], &[43]); } tokio-uring-0.4.0/tests/directory.rs000064400000000000000000000004061046102023000156100ustar 00000000000000#[test] fn basic_remove_dir() { tokio_uring::start(async { let temp_dir = tempfile::TempDir::new().unwrap(); tokio_uring::fs::remove_dir(temp_dir.path()).await.unwrap(); assert!(std::fs::metadata(temp_dir.path()).is_err()); }); } tokio-uring-0.4.0/tests/driver.rs000064400000000000000000000063571046102023000151120ustar 00000000000000use tempfile::NamedTempFile; use tokio_uring::{buf::IoBuf, fs::File}; #[path = "../src/future.rs"] #[allow(warnings)] mod future; #[test] fn complete_ops_on_drop() { use std::sync::Arc; struct MyBuf { data: Vec, _ref_cnt: Arc<()>, } unsafe impl IoBuf for MyBuf { fn stable_ptr(&self) -> *const u8 { self.data.stable_ptr() } fn bytes_init(&self) -> usize { self.data.bytes_init() } fn bytes_total(&self) -> usize { self.data.bytes_total() } } unsafe impl tokio_uring::buf::IoBufMut for MyBuf { fn stable_mut_ptr(&mut self) -> *mut u8 { self.data.stable_mut_ptr() } unsafe fn set_init(&mut self, pos: usize) { self.data.set_init(pos); } } // Used to test if the buffer dropped. let ref_cnt = Arc::new(()); let tempfile = tempfile(); let vec = vec![0; 50 * 1024 * 1024]; let mut file = std::fs::File::create(tempfile.path()).unwrap(); std::io::Write::write_all(&mut file, &vec).unwrap(); let file = tokio_uring::start(async { let file = File::create(tempfile.path()).await.unwrap(); poll_once(async { file.read_at( MyBuf { data: vec![0; 64 * 1024], _ref_cnt: ref_cnt.clone(), }, 25 * 1024 * 1024, ) .await .0 .unwrap(); }) .await; file }); assert_eq!(Arc::strong_count(&ref_cnt), 1); // little sleep std::thread::sleep(std::time::Duration::from_millis(100)); drop(file); } #[test] fn too_many_submissions() { let tempfile = tempfile(); tokio_uring::start(async { let file = File::create(tempfile.path()).await.unwrap(); for _ in 0..600 { poll_once(async { file.write_at(b"hello world".to_vec(), 0).await.0.unwrap(); }) .await; } }); } #[test] fn completion_overflow() { use std::process; use std::{thread, time}; use tokio::task::JoinSet; let spawn_cnt = 50; let squeue_entries = 2; let cqueue_entries = 2 * squeue_entries; std::thread::spawn(|| { thread::sleep(time::Duration::from_secs(8)); // 1000 times longer than it takes on a slow machine eprintln!("Timeout reached. The uring completions are hung."); process::exit(1); }); tokio_uring::builder() .entries(squeue_entries) .uring_builder(tokio_uring::uring_builder().setup_cqsize(cqueue_entries)) .start(async move { let mut js = JoinSet::new(); for _ in 0..spawn_cnt { js.spawn_local(tokio_uring::no_op()); } while let Some(res) = js.join_next().await { res.unwrap().unwrap(); } }); } fn tempfile() -> NamedTempFile { NamedTempFile::new().unwrap() } async fn poll_once(future: impl std::future::Future) { // use std::future::Future; use std::task::Poll; use tokio::pin; pin!(future); future::poll_fn(|cx| { assert!(future.as_mut().poll(cx).is_pending()); Poll::Ready(()) }) .await; } tokio-uring-0.4.0/tests/fs_file.rs000064400000000000000000000123431046102023000152160ustar 00000000000000use std::{ io::prelude::*, os::unix::io::{AsRawFd, FromRawFd, RawFd}, }; use tempfile::NamedTempFile; use tokio_uring::fs::File; #[path = "../src/future.rs"] #[allow(warnings)] mod future; const HELLO: &[u8] = b"hello world..."; async fn read_hello(file: &File) { let buf = Vec::with_capacity(1024); let (res, buf) = file.read_at(buf, 0).await; let n = res.unwrap(); assert_eq!(n, HELLO.len()); assert_eq!(&buf[..n], HELLO); } #[test] fn basic_read() { tokio_uring::start(async { let mut tempfile = tempfile(); tempfile.write_all(HELLO).unwrap(); let file = File::open(tempfile.path()).await.unwrap(); read_hello(&file).await; }); } #[test] fn basic_write() { tokio_uring::start(async { let tempfile = tempfile(); let file = File::create(tempfile.path()).await.unwrap(); file.write_at(HELLO, 0).await.0.unwrap(); let file = std::fs::read(tempfile.path()).unwrap(); assert_eq!(file, HELLO); }); } #[test] fn vectored_read() { tokio_uring::start(async { let mut tempfile = tempfile(); tempfile.write_all(HELLO).unwrap(); let file = File::open(tempfile.path()).await.unwrap(); let bufs = vec![Vec::::with_capacity(5), Vec::::with_capacity(9)]; let (res, bufs) = file.readv_at(bufs, 0).await; let n = res.unwrap(); assert_eq!(n, HELLO.len()); assert_eq!(bufs[1][0], b' '); }); } #[test] fn vectored_write() { tokio_uring::start(async { let tempfile = tempfile(); let file = File::create(tempfile.path()).await.unwrap(); let buf1 = "hello".to_owned().into_bytes(); let buf2 = " world...".to_owned().into_bytes(); let bufs = vec![buf1, buf2]; file.writev_at(bufs, 0).await.0.unwrap(); let file = std::fs::read(tempfile.path()).unwrap(); assert_eq!(file, HELLO); }); } #[test] fn cancel_read() { tokio_uring::start(async { let mut tempfile = tempfile(); tempfile.write_all(HELLO).unwrap(); let file = File::open(tempfile.path()).await.unwrap(); // Poll the future once, then cancel it poll_once(async { read_hello(&file).await }).await; read_hello(&file).await; }); } #[test] fn explicit_close() { let mut tempfile = tempfile(); tempfile.write_all(HELLO).unwrap(); tokio_uring::start(async { let file = File::open(tempfile.path()).await.unwrap(); let fd = file.as_raw_fd(); file.close().await.unwrap(); assert_invalid_fd(fd); }) } #[test] fn drop_open() { tokio_uring::start(async { let tempfile = tempfile(); let _ = File::create(tempfile.path()); // Do something else let file = File::create(tempfile.path()).await.unwrap(); file.write_at(HELLO, 0).await.0.unwrap(); let file = std::fs::read(tempfile.path()).unwrap(); assert_eq!(file, HELLO); }); } #[test] fn drop_off_runtime() { let file = tokio_uring::start(async { let tempfile = tempfile(); File::open(tempfile.path()).await.unwrap() }); let fd = file.as_raw_fd(); drop(file); assert_invalid_fd(fd); } #[test] fn sync_doesnt_kill_anything() { let tempfile = tempfile(); tokio_uring::start(async { let file = File::create(tempfile.path()).await.unwrap(); file.sync_all().await.unwrap(); file.sync_data().await.unwrap(); file.write_at(&b"foo"[..], 0).await.0.unwrap(); file.sync_all().await.unwrap(); file.sync_data().await.unwrap(); }); } #[test] fn rename() { use std::ffi::OsStr; tokio_uring::start(async { let mut tempfile = tempfile(); tempfile.write_all(HELLO).unwrap(); let old_path = tempfile.path(); let old_file = File::open(old_path).await.unwrap(); read_hello(&old_file).await; old_file.close().await.unwrap(); let mut new_file_name = old_path .file_name() .unwrap_or_else(|| OsStr::new("")) .to_os_string(); new_file_name.push("_renamed"); let new_path = old_path.with_file_name(new_file_name); tokio_uring::fs::rename(&old_path, &new_path).await.unwrap(); let new_file = File::open(&new_path).await.unwrap(); read_hello(&new_file).await; let old_file = File::open(old_path).await; assert!(old_file.is_err()); // Since the file has been renamed, it won't be deleted // in the TempPath destructor. We have to manually delete it. std::fs::remove_file(&new_path).unwrap(); }) } fn tempfile() -> NamedTempFile { NamedTempFile::new().unwrap() } async fn poll_once(future: impl std::future::Future) { use future::poll_fn; // use std::future::Future; use std::task::Poll; use tokio::pin; pin!(future); poll_fn(|cx| { assert!(future.as_mut().poll(cx).is_pending()); Poll::Ready(()) }) .await; } fn assert_invalid_fd(fd: RawFd) { use std::fs::File; let mut f = unsafe { File::from_raw_fd(fd) }; let mut buf = vec![]; match f.read_to_end(&mut buf) { Err(ref e) if e.raw_os_error() == Some(libc::EBADF) => {} res => panic!("{:?}", res), } } tokio-uring-0.4.0/tests/runtime.rs000064400000000000000000000016061046102023000152720ustar 00000000000000use tokio::net::{TcpListener, TcpStream}; #[test] fn use_tokio_types_from_runtime() { tokio_uring::start(async { let listener = TcpListener::bind("0.0.0.0:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let task = tokio::spawn(async move { let _socket = TcpStream::connect(addr).await.unwrap(); }); // Accept a connection let (_socket, _) = listener.accept().await.unwrap(); // Wait for the task to complete task.await.unwrap(); }); } #[test] fn spawn_a_task() { use std::cell::RefCell; use std::rc::Rc; tokio_uring::start(async { let cell = Rc::new(RefCell::new(1)); let c = cell.clone(); let handle = tokio_uring::spawn(async move { *c.borrow_mut() = 2; }); handle.await.unwrap(); assert_eq!(2, *cell.borrow()); }); }