diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9971c06..2d229f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: run: cargo build --verbose - name: Run tests - run: cargo test --all --verbose + run: cargo test --verbose --all-features clippy: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index f9a2f07..35f0bc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -11,6 +26,157 @@ dependencies = [ "memchr", ] +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.1", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "bindgen" version = "0.72.1" @@ -28,7 +194,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.106", ] [[package]] @@ -37,6 +203,31 @@ version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + [[package]] name = "cexpr" version = "0.6.0" @@ -63,6 +254,21 @@ dependencies = [ "libloading", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "either" version = "1.15.0" @@ -76,7 +282,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.1", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", ] [[package]] @@ -85,6 +318,108 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -94,15 +429,50 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasi 0.14.7+wasi-0.2.4", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "itertools" version = "0.13.0" @@ -112,6 +482,25 @@ dependencies = [ "either", ] +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "libc" version = "0.2.176" @@ -132,11 +521,14 @@ dependencies = [ name = "liburing-rs" version = "0.1.0" dependencies = [ + "async-std", "bindgen", "bitflags", + "futures", "libc", "pkg-config", "tempfile", + "tokio", ] [[package]] @@ -145,11 +537,24 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +dependencies = [ + "value-bag", +] [[package]] name = "memchr" @@ -163,6 +568,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + [[package]] name = "nom" version = "7.1.3" @@ -173,18 +598,93 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.1", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -192,7 +692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.106", ] [[package]] @@ -219,6 +719,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.11.3" @@ -248,6 +757,12 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -264,15 +779,69 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.1", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.106" @@ -294,7 +863,38 @@ dependencies = [ "getrandom", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.1", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -303,6 +903,18 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasi" version = "0.14.7+wasi-0.2.4" @@ -321,12 +933,103 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "windows-link" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.1" @@ -336,6 +1039,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index 5fb26a7..a6b24ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,9 @@ links = "uring" [dependencies] libc = "0.2" bitflags = "2.6" +tokio = { version = "1.0", features = ["net", "io-util", "rt"], optional = true } +async-std = { version = "1.0", optional = true } +futures = { version = "0.3", optional = true } [build-dependencies] bindgen = "0.72" @@ -25,9 +28,26 @@ pkg-config = "0.3" [dev-dependencies] tempfile = "3.0" +tokio = { version = "1.0", features = ["full"] } +async-std = { version = "1.0", features = ["attributes"] } [features] default = [] -# Future: async support via tokio/async-std -async-tokio = [] -async-std = [] +async-tokio = ["tokio", "futures"] +async-async-std = ["async-std", "futures"] + +[[example]] +name = "async_nop_tokio" +required-features = ["async-tokio"] + +[[example]] +name = "async_nop_async_std" +required-features = ["async-async-std"] + +[[example]] +name = "async_poll_bench" +required-features = ["async-tokio"] + +[[example]] +name = "async_poll_bench_async_std" +required-features = ["async-async-std"] diff --git a/README.rst b/README.rst index 1873861..cba16e6 100644 --- a/README.rst +++ b/README.rst @@ -58,6 +58,8 @@ The build script: Usage ----- +**Synchronous API:** + .. code:: rust use liburing_rs::{IoUring, ops::*}; @@ -79,9 +81,36 @@ Usage let cqe = cq.wait_cqe()?; println!("Result: {}", cqe.result()); +**Async API (tokio):** + +.. code:: rust + + use liburing_rs::async_io::AsyncIoUring; + use liburing_rs::ops::Nop; + + let mut ring = AsyncIoUring::new(32)?; + let result = ring.submit_op(Nop).await?; + println!("Result: {}", result); + +Enable with ``async-tokio`` feature: + +.. code:: toml + + liburing-rs = { version = "0.1", features = ["async-tokio"] } + +**Async API (async-std):** + +Enable with ``async-async-std`` feature: + +.. code:: toml + + liburing-rs = { version = "0.1", features = ["async-async-std"] } + Examples -------- +**Synchronous examples:** + .. code:: bash # Basic NOP operation @@ -96,6 +125,22 @@ Examples # Polling benchmark cargo run --release --example poll-bench +**Async examples:** + +.. code:: bash + + # Async NOP with tokio + cargo run --example async_nop_tokio --features async-tokio + + # Async NOP with async-std + cargo run --example async_nop_async_std --features async-async-std + + # Async polling benchmark (tokio) + cargo run --release --example async_poll_bench --features async-tokio + + # Async polling benchmark (async-std) + cargo run --release --example async_poll_bench_async_std --features async-async-std + Tests ----- @@ -113,11 +158,12 @@ Coverage includes: Architecture ------------ -Three layers: +Four layers: 1. **sys**: Raw FFI bindings (unsafe) 2. **Safe wrappers**: RAII types (IoUring, SubmissionQueue, CompletionQueue) 3. **Operations**: Type-safe operation builders (Read, Write, etc.) +4. **Async runtime integration**: AsyncIoUring for tokio and async-std (optional) Performance ----------- diff --git a/examples/async_nop_async_std.rs b/examples/async_nop_async_std.rs new file mode 100644 index 0000000..38bb41c --- /dev/null +++ b/examples/async_nop_async_std.rs @@ -0,0 +1,22 @@ +//! Simple async NOP example with async-std runtime +//! +//! Run with: cargo run --example async_nop_async_std --features async-async-std + +use liburing_rs::async_io::AsyncIoUring; +use liburing_rs::ops::Nop; + +fn main() -> Result<(), Box> { + async_std::task::block_on(async { + println!("Creating async io_uring with 8 entries..."); + let mut ring = AsyncIoUring::new(8)?; + + println!("Submitting NOP operation..."); + let result = ring.submit_op(Nop).await?; + + println!("Async NOP completed!"); + println!(" result: {}", result); + println!(" success: {}", result == 0); + + Ok(()) + }) +} diff --git a/examples/async_nop_tokio.rs b/examples/async_nop_tokio.rs new file mode 100644 index 0000000..7ccb0de --- /dev/null +++ b/examples/async_nop_tokio.rs @@ -0,0 +1,21 @@ +//! Simple async NOP example with tokio runtime +//! +//! Run with: cargo run --example async_nop_tokio --features async-tokio + +use liburing_rs::async_io::AsyncIoUring; +use liburing_rs::ops::Nop; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Creating async io_uring with 8 entries..."); + let mut ring = AsyncIoUring::new(8)?; + + println!("Submitting NOP operation..."); + let result = ring.submit_op(Nop).await?; + + println!("Async NOP completed!"); + println!(" result: {}", result); + println!(" success: {}", result == 0); + + Ok(()) +} diff --git a/examples/async_poll_bench.rs b/examples/async_poll_bench.rs new file mode 100644 index 0000000..34291eb --- /dev/null +++ b/examples/async_poll_bench.rs @@ -0,0 +1,118 @@ +//! Async polling benchmark using io_uring with tokio +//! +//! This benchmark measures how many poll operations per second can be +//! processed through io_uring using the async API. It: +//! - Creates a pipe +//! - Submits POLL_ADD operations using async/await +//! - Measures throughput in requests/second +//! +//! This demonstrates io_uring's async polling capabilities. +//! +//! Usage: cargo run --release --example async_poll_bench --features async-tokio + +use liburing_rs::async_io::tokio_impl::AsyncIoUring; +use liburing_rs::ops::PrepareOp; +use std::time::Instant; + +const BATCH_SIZE: usize = 32; +const RUNTIME_MS: u64 = 10000; // 10 seconds + +struct PollOp { + fd: i32, + poll_mask: u32, +} + +impl PrepareOp for PollOp { + fn prepare(&self, sqe: &mut liburing_rs::sys::io_uring_sqe) { + unsafe { + liburing_rs::sys::io_uring_prep_poll_add(sqe, self.fd, self.poll_mask); + } + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a pipe + let mut pipe_fds = [0i32; 2]; + let ret = unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; + if ret != 0 { + return Err("Failed to create pipe".into()); + } + + let (read_fd, write_fd) = (pipe_fds[0], pipe_fds[1]); + + println!("Creating async io_uring with batch size {}...", BATCH_SIZE); + + let mut ring = AsyncIoUring::new(256)?; + + println!( + "\nRunning async benchmark for {} seconds...", + RUNTIME_MS / 1000 + ); + let start = Instant::now(); + let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_millis(RUNTIME_MS); + + let mut nr_reqs = 0u64; + let buf = [0u8; 1]; + + loop { + if tokio::time::Instant::now() >= deadline { + break; + } + + // Submit a batch of poll operations sequentially + for _ in 0..BATCH_SIZE { + // Write 1 byte to trigger the poll + let ret = unsafe { libc::write(write_fd, buf.as_ptr() as *const _, 1) }; + if ret != 1 { + return Err("Write failed".into()); + } + + // Submit poll operation and await completion + match ring + .submit_op(PollOp { + fd: read_fd, + poll_mask: libc::POLLIN as u32, + }) + .await + { + Ok(result) => { + if result >= 0 { + nr_reqs += 1; + } else { + eprintln!("Poll failed: {}", result); + } + } + Err(e) => { + eprintln!("Poll error: {:?}", e); + } + } + + // Read 1 byte back + let ret = unsafe { libc::read(read_fd, buf.as_ptr() as *mut _, 1) }; + if ret != 1 { + return Err("Read failed".into()); + } + } + } + + let elapsed = start.elapsed(); + let requests_per_sec = (nr_reqs * 1000) / RUNTIME_MS; + + println!("\n=== Async Results ==="); + println!("Total requests: {}", nr_reqs); + println!("Elapsed time: {:.2}s", elapsed.as_secs_f64()); + println!("Requests/second: {}", requests_per_sec); + println!( + "Throughput: {:.2} K ops/sec", + requests_per_sec as f64 / 1_000.0 + ); + + // Cleanup + unsafe { + libc::close(read_fd); + libc::close(write_fd); + } + + Ok(()) +} diff --git a/examples/async_poll_bench_async_std.rs b/examples/async_poll_bench_async_std.rs new file mode 100644 index 0000000..a126949 --- /dev/null +++ b/examples/async_poll_bench_async_std.rs @@ -0,0 +1,118 @@ +//! Async polling benchmark using io_uring with async-std +//! +//! This benchmark measures how many poll operations per second can be +//! processed through io_uring using the async API. It: +//! - Creates a pipe +//! - Submits POLL_ADD operations using async/await +//! - Measures throughput in requests/second +//! +//! This demonstrates io_uring's async polling capabilities with async-std. +//! +//! Usage: cargo run --release --example async_poll_bench_async_std --features async-async-std + +use liburing_rs::async_io::async_std_impl::AsyncIoUring; +use liburing_rs::ops::PrepareOp; +use std::time::Instant; + +const BATCH_SIZE: usize = 32; +const RUNTIME_MS: u64 = 10000; // 10 seconds + +struct PollOp { + fd: i32, + poll_mask: u32, +} + +impl PrepareOp for PollOp { + fn prepare(&self, sqe: &mut liburing_rs::sys::io_uring_sqe) { + unsafe { + liburing_rs::sys::io_uring_prep_poll_add(sqe, self.fd, self.poll_mask); + } + } +} + +fn main() -> Result<(), Box> { + async_std::task::block_on(async { + // Create a pipe + let mut pipe_fds = [0i32; 2]; + let ret = unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; + if ret != 0 { + return Err("Failed to create pipe".into()); + } + + let (read_fd, write_fd) = (pipe_fds[0], pipe_fds[1]); + + println!("Creating async io_uring with batch size {}...", BATCH_SIZE); + + let mut ring = AsyncIoUring::new(256)?; + + println!( + "\nRunning async benchmark for {} seconds...", + RUNTIME_MS / 1000 + ); + let start = Instant::now(); + + let mut nr_reqs = 0u64; + let buf = [0u8; 1]; + + loop { + if start.elapsed().as_millis() as u64 >= RUNTIME_MS { + break; + } + + // Submit a batch of poll operations sequentially + for _ in 0..BATCH_SIZE { + // Write 1 byte to trigger the poll + let ret = unsafe { libc::write(write_fd, buf.as_ptr() as *const _, 1) }; + if ret != 1 { + return Err("Write failed".into()); + } + + // Submit poll operation and await completion + match ring + .submit_op(PollOp { + fd: read_fd, + poll_mask: libc::POLLIN as u32, + }) + .await + { + Ok(result) => { + if result >= 0 { + nr_reqs += 1; + } else { + eprintln!("Poll failed: {}", result); + } + } + Err(e) => { + eprintln!("Poll error: {:?}", e); + } + } + + // Read 1 byte back + let ret = unsafe { libc::read(read_fd, buf.as_ptr() as *mut _, 1) }; + if ret != 1 { + return Err("Read failed".into()); + } + } + } + + let elapsed = start.elapsed(); + let requests_per_sec = (nr_reqs * 1000) / RUNTIME_MS; + + println!("\n=== Async Results ==="); + println!("Total requests: {}", nr_reqs); + println!("Elapsed time: {:.2}s", elapsed.as_secs_f64()); + println!("Requests/second: {}", requests_per_sec); + println!( + "Throughput: {:.2} K ops/sec", + requests_per_sec as f64 / 1_000.0 + ); + + // Cleanup + unsafe { + libc::close(read_fd); + libc::close(write_fd); + } + + Ok(()) + }) +} diff --git a/src/async_io.rs b/src/async_io.rs new file mode 100644 index 0000000..22e227f --- /dev/null +++ b/src/async_io.rs @@ -0,0 +1,21 @@ +//! Async I/O support for io_uring +//! +//! This module provides async/await interfaces for io_uring operations. +//! Enable with the `async-tokio` or `async-async-std` features. +//! +//! **Note**: Only one async runtime feature should be enabled at a time. +//! If both are enabled, tokio will be used by default. + +#[cfg(feature = "async-tokio")] +pub mod tokio_impl; + +#[cfg(feature = "async-async-std")] +pub mod async_std_impl; + +// Re-export AsyncIoUring from the appropriate runtime implementation +// If both features are enabled, prefer tokio +#[cfg(feature = "async-tokio")] +pub use tokio_impl::AsyncIoUring; + +#[cfg(all(feature = "async-async-std", not(feature = "async-tokio")))] +pub use async_std_impl::AsyncIoUring; diff --git a/src/async_io/async_std_impl.rs b/src/async_io/async_std_impl.rs new file mode 100644 index 0000000..df070fe --- /dev/null +++ b/src/async_io/async_std_impl.rs @@ -0,0 +1,88 @@ +//! async-std runtime integration for io_uring + +use crate::{ + ops::{PrepareOp, SqeExt}, + IoUring, Result, +}; +use std::sync::{Arc, Mutex}; + +/// Async io_uring instance integrated with async-std runtime +/// +/// This wraps an `IoUring` instance and integrates it with async-std's async runtime, +/// allowing you to use async/await with io_uring operations. +/// +/// # Example +/// +/// ```no_run +/// # async_std::task::block_on(async { +/// use liburing_rs::async_io::AsyncIoUring; +/// use liburing_rs::ops::Nop; +/// +/// let mut ring = AsyncIoUring::new(32)?; +/// +/// // Submit a NOP operation and await its completion +/// let result = ring.submit_op(Nop).await?; +/// println!("NOP completed with result: {}", result); +/// # Ok::<(), liburing_rs::Error>(()) +/// # }).unwrap(); +/// ``` +pub struct AsyncIoUring { + ring: Arc>, +} + +impl AsyncIoUring { + /// Create a new async io_uring instance with the specified number of entries + /// + /// # Arguments + /// + /// * `entries` - Number of submission queue entries (will be rounded up to power of 2) + /// + /// # Errors + /// + /// Returns an error if the kernel doesn't support io_uring or if setup fails. + pub fn new(entries: u32) -> Result { + let ring = IoUring::new(entries)?; + Ok(Self { + ring: Arc::new(Mutex::new(ring)), + }) + } + + /// Submit an operation and wait for its completion asynchronously + /// + /// # Arguments + /// + /// * `op` - The operation to submit (implements `PrepareOp`) + /// + /// # Returns + /// + /// A future that resolves to the result code of the operation + pub async fn submit_op(&mut self, op: Op) -> Result { + let ring = self.ring.clone(); + + async_std::task::spawn_blocking(move || { + let mut ring = ring.lock().unwrap(); + + // Submit the operation + let user_data = 1u64; + { + let mut sq = ring.submission(); + let sqe = sq.get_sqe_or_err()?; + op.prepare(sqe); + sqe.set_user_data(user_data); + } + + // Submit to kernel + ring.submit()?; + + // Wait for completion + let mut cq = ring.completion(); + let cqe = cq.wait_cqe()?; + Ok(cqe.result()) + }) + .await + } +} + +// AsyncIoUring can be sent between threads +unsafe impl Send for AsyncIoUring {} +unsafe impl Sync for AsyncIoUring {} diff --git a/src/async_io/tokio_impl.rs b/src/async_io/tokio_impl.rs new file mode 100644 index 0000000..f17d26d --- /dev/null +++ b/src/async_io/tokio_impl.rs @@ -0,0 +1,202 @@ +//! Tokio async runtime integration for io_uring + +use crate::{ + ops::{PrepareOp, SqeExt}, + Error, IoUring, Result, +}; +use std::collections::HashMap; +use std::future::Future; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Waker}; +use tokio::io::unix::AsyncFd; +use tokio::io::Interest; + +/// Async io_uring instance integrated with tokio runtime +/// +/// This wraps an `IoUring` instance and integrates it with tokio's async runtime, +/// allowing you to use async/await with io_uring operations. +/// +/// # Example +/// +/// ```no_run +/// # #[tokio::main] +/// # async fn main() -> Result<(), Box> { +/// use liburing_rs::async_io::AsyncIoUring; +/// use liburing_rs::ops::Nop; +/// +/// let mut ring = AsyncIoUring::new(32)?; +/// +/// // Submit a NOP operation and await its completion +/// let result = ring.submit_op(Nop).await?; +/// println!("NOP completed with result: {}", result); +/// # Ok(()) +/// # } +/// ``` +pub struct AsyncIoUring { + inner: Arc>, +} + +struct AsyncIoUringInner { + ring: IoUring, + async_fd: AsyncFd, + wakers: HashMap, + next_user_data: u64, +} + +/// Wrapper to make RawFd work with AsyncFd +struct RawFdWrapper(std::os::unix::io::RawFd); + +impl std::os::unix::io::AsRawFd for RawFdWrapper { + fn as_raw_fd(&self) -> std::os::unix::io::RawFd { + self.0 + } +} + +impl AsyncIoUring { + /// Create a new async io_uring instance with the specified number of entries + /// + /// # Arguments + /// + /// * `entries` - Number of submission queue entries (will be rounded up to power of 2) + /// + /// # Errors + /// + /// Returns an error if the kernel doesn't support io_uring or if setup fails. + pub fn new(entries: u32) -> Result { + let ring = IoUring::new(entries)?; + let fd = ring.as_raw_fd(); + + // Wrap the fd in our wrapper type + let fd_wrapper = RawFdWrapper(fd); + + // Create AsyncFd with READABLE interest (io_uring fd becomes readable when completions arrive) + let async_fd = AsyncFd::with_interest(fd_wrapper, Interest::READABLE).map_err(Error::Io)?; + + Ok(Self { + inner: Arc::new(Mutex::new(AsyncIoUringInner { + ring, + async_fd, + wakers: HashMap::new(), + next_user_data: 1, + })), + }) + } + + /// Submit an operation and wait for its completion asynchronously + /// + /// # Arguments + /// + /// * `op` - The operation to submit (implements `PrepareOp`) + /// + /// # Returns + /// + /// A future that resolves to the result code of the operation + pub fn submit_op( + &mut self, + op: Op, + ) -> impl Future> { + SubmitFuture { + ring: self.inner.clone(), + op: Some(op), + user_data: None, + } + } +} + +struct SubmitFuture { + ring: Arc>, + op: Option, + user_data: Option, +} + +// SubmitFuture doesn't need to be pinned +impl Unpin for SubmitFuture {} + +impl Future for SubmitFuture { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let future = self.get_mut(); + let mut inner = future.ring.lock().unwrap(); + + // If we haven't submitted yet, do so now + if let Some(op) = future.op.take() { + // Get the next user_data value + let user_data = inner.next_user_data; + inner.next_user_data = inner.next_user_data.wrapping_add(1); + future.user_data = Some(user_data); + + // Submit the operation + { + let mut sq = inner.ring.submission(); + let sqe = match sq.get_sqe_or_err() { + Ok(sqe) => sqe, + Err(e) => return Poll::Ready(Err(e)), + }; + op.prepare(sqe); + sqe.set_user_data(user_data); + } + + // Submit to kernel + if let Err(e) = inner.ring.submit() { + return Poll::Ready(Err(e)); + } + + // Register our waker + inner.wakers.insert(user_data, cx.waker().clone()); + } + + // Try to get completion + let user_data = future.user_data.unwrap(); + + // Check for completions + loop { + let cqe_data: Option<(u64, i32)> = { + let mut cq = inner.ring.completion(); + cq.peek_cqe().map(|cqe| (cqe.user_data(), cqe.result())) + }; + + match cqe_data { + Some((cqe_user_data, result)) => { + if cqe_user_data == user_data { + // This is our completion + inner.wakers.remove(&user_data); + return Poll::Ready(Ok(result)); + } else { + // Wake up the other task + if let Some(waker) = inner.wakers.remove(&cqe_user_data) { + waker.wake(); + } + } + } + None => { + // No more completions available, wait for fd to become readable + break; + } + } + } + + // Wait for the fd to become readable (more completions available) + match inner.async_fd.poll_read_ready(cx) { + Poll::Ready(Ok(mut guard)) => { + // Clear the ready state + guard.clear_ready(); + // Re-register our waker and return pending + // The next poll will check for completions again + inner.wakers.insert(user_data, cx.waker().clone()); + Poll::Pending + } + Poll::Ready(Err(e)) => Poll::Ready(Err(Error::Io(e))), + Poll::Pending => { + // Make sure our waker is registered + inner.wakers.insert(user_data, cx.waker().clone()); + Poll::Pending + } + } + } +} + +// AsyncIoUring can be sent between threads +unsafe impl Send for AsyncIoUring {} +unsafe impl Sync for AsyncIoUring {} diff --git a/src/lib.rs b/src/lib.rs index fe16ea8..9c50893 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,9 @@ pub mod ops; mod queue; mod uring; +#[cfg(any(feature = "async-tokio", feature = "async-async-std"))] +pub mod async_io; + pub use error::{Error, Result}; pub use queue::{CompletionQueue, Cqe, SubmissionQueue}; pub use uring::IoUring; diff --git a/tests/async_ops.rs b/tests/async_ops.rs new file mode 100644 index 0000000..464d8ad --- /dev/null +++ b/tests/async_ops.rs @@ -0,0 +1,365 @@ +//! Async operation tests + +#[cfg(feature = "async-tokio")] +mod tokio_tests { + use liburing_rs::async_io::tokio_impl::AsyncIoUring; + use liburing_rs::ops::{Nop, PrepareOp}; + use liburing_rs::Result; + use std::os::unix::io::AsRawFd; + + #[tokio::test] + async fn test_async_nop() -> Result<()> { + let mut ring = AsyncIoUring::new(8)?; + let result = ring.submit_op(Nop).await?; + assert_eq!(result, 0); + Ok(()) + } + + #[tokio::test] + async fn test_async_multiple_nops() -> Result<()> { + let mut ring = AsyncIoUring::new(8)?; + + for _ in 0..5 { + let result = ring.submit_op(Nop).await?; + assert_eq!(result, 0); + } + + Ok(()) + } + + #[tokio::test] + async fn test_async_send_recv() -> Result<()> { + // Create a socket pair + let mut fds = [0i32; 2]; + let ret = + unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }; + assert_eq!(ret, 0); + + let (sock1, sock2) = (fds[0], fds[1]); + + let mut ring = AsyncIoUring::new(8)?; + + // Send data + let send_data = b"Hello async io_uring!"; + + // Prepare send operation + struct SendOp { + fd: i32, + data: &'static [u8], + } + + impl PrepareOp for SendOp { + fn prepare(&self, sqe: &mut liburing_rs::sys::io_uring_sqe) { + unsafe { + liburing_rs::sys::io_uring_prep_send( + sqe, + self.fd, + self.data.as_ptr() as *const _, + self.data.len(), + 0, + ); + } + } + } + + let send_result = ring + .submit_op(SendOp { + fd: sock1, + data: send_data, + }) + .await?; + assert_eq!(send_result as usize, send_data.len()); + + // Receive data + let mut recv_buf = vec![0u8; send_data.len()]; + let buf_ptr = recv_buf.as_mut_ptr(); + let buf_len = recv_buf.len(); + + struct RecvOp { + fd: i32, + buf: *mut u8, + len: usize, + } + + unsafe impl Send for RecvOp {} + + impl PrepareOp for RecvOp { + fn prepare(&self, sqe: &mut liburing_rs::sys::io_uring_sqe) { + unsafe { + liburing_rs::sys::io_uring_prep_recv( + sqe, + self.fd, + self.buf as *mut _, + self.len, + 0, + ); + } + } + } + + let recv_result = ring + .submit_op(RecvOp { + fd: sock2, + buf: buf_ptr, + len: buf_len, + }) + .await?; + assert_eq!(recv_result as usize, send_data.len()); + assert_eq!(&recv_buf[..], send_data); + + // Clean up + unsafe { + libc::close(sock1); + libc::close(sock2); + } + + Ok(()) + } + + #[tokio::test] + async fn test_async_file_io() -> Result<()> { + use std::fs::File; + use std::io::Write; + use tempfile::tempdir; + + let dir = tempdir().unwrap(); + let file_path = dir.path().join("async_test.txt"); + + // Write some data synchronously + let test_data = b"Async file I/O test"; + { + let mut file = File::create(&file_path).unwrap(); + file.write_all(test_data).unwrap(); + } + + // Open for reading + let file = File::open(&file_path).unwrap(); + let fd = file.as_raw_fd(); + + let mut ring = AsyncIoUring::new(8)?; + + // Read data asynchronously + let mut read_buf = vec![0u8; test_data.len()]; + let buf_ptr = read_buf.as_mut_ptr(); + let buf_len = read_buf.len(); + + struct ReadOp { + fd: i32, + buf: *mut u8, + len: usize, + offset: u64, + } + + unsafe impl Send for ReadOp {} + + impl PrepareOp for ReadOp { + fn prepare(&self, sqe: &mut liburing_rs::sys::io_uring_sqe) { + unsafe { + liburing_rs::sys::io_uring_prep_read( + sqe, + self.fd, + self.buf as *mut _, + self.len as u32, + self.offset, + ); + } + } + } + + let result = ring + .submit_op(ReadOp { + fd, + buf: buf_ptr, + len: buf_len, + offset: 0, + }) + .await?; + + assert_eq!(result as usize, test_data.len()); + assert_eq!(&read_buf[..], test_data); + + Ok(()) + } +} + +#[cfg(feature = "async-async-std")] +mod async_std_tests { + use liburing_rs::async_io::async_std_impl::AsyncIoUring; + use liburing_rs::ops::{Nop, PrepareOp}; + use liburing_rs::Result; + use std::os::unix::io::AsRawFd; + + #[async_std::test] + async fn test_async_nop() -> Result<()> { + let mut ring = AsyncIoUring::new(8)?; + let result = ring.submit_op(Nop).await?; + assert_eq!(result, 0); + Ok(()) + } + + #[async_std::test] + async fn test_async_multiple_nops() -> Result<()> { + let mut ring = AsyncIoUring::new(8)?; + + for _ in 0..5 { + let result = ring.submit_op(Nop).await?; + assert_eq!(result, 0); + } + + Ok(()) + } + + #[async_std::test] + async fn test_async_send_recv() -> Result<()> { + // Create a socket pair + let mut fds = [0i32; 2]; + let ret = + unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) }; + assert_eq!(ret, 0); + + let (sock1, sock2) = (fds[0], fds[1]); + + let mut ring = AsyncIoUring::new(8)?; + + // Send data + let send_data = b"Hello async io_uring!"; + + // Prepare send operation + struct SendOp { + fd: i32, + data: &'static [u8], + } + + impl PrepareOp for SendOp { + fn prepare(&self, sqe: &mut liburing_rs::sys::io_uring_sqe) { + unsafe { + liburing_rs::sys::io_uring_prep_send( + sqe, + self.fd, + self.data.as_ptr() as *const _, + self.data.len(), + 0, + ); + } + } + } + + let send_result = ring + .submit_op(SendOp { + fd: sock1, + data: send_data, + }) + .await?; + assert_eq!(send_result as usize, send_data.len()); + + // Receive data + let mut recv_buf = vec![0u8; send_data.len()]; + let buf_ptr = recv_buf.as_mut_ptr(); + let buf_len = recv_buf.len(); + + struct RecvOp { + fd: i32, + buf: *mut u8, + len: usize, + } + + unsafe impl Send for RecvOp {} + + impl PrepareOp for RecvOp { + fn prepare(&self, sqe: &mut liburing_rs::sys::io_uring_sqe) { + unsafe { + liburing_rs::sys::io_uring_prep_recv( + sqe, + self.fd, + self.buf as *mut _, + self.len, + 0, + ); + } + } + } + + let recv_result = ring + .submit_op(RecvOp { + fd: sock2, + buf: buf_ptr, + len: buf_len, + }) + .await?; + assert_eq!(recv_result as usize, send_data.len()); + assert_eq!(&recv_buf[..], send_data); + + // Clean up + unsafe { + libc::close(sock1); + libc::close(sock2); + } + + Ok(()) + } + + #[async_std::test] + async fn test_async_file_io() -> Result<()> { + use std::fs::File; + use std::io::Write; + use tempfile::tempdir; + + let dir = tempdir().unwrap(); + let file_path = dir.path().join("async_test.txt"); + + // Write some data synchronously + let test_data = b"Async file I/O test"; + { + let mut file = File::create(&file_path).unwrap(); + file.write_all(test_data).unwrap(); + } + + // Open for reading + let file = File::open(&file_path).unwrap(); + let fd = file.as_raw_fd(); + + let mut ring = AsyncIoUring::new(8)?; + + // Read data asynchronously + let mut read_buf = vec![0u8; test_data.len()]; + let buf_ptr = read_buf.as_mut_ptr(); + let buf_len = read_buf.len(); + + struct ReadOp { + fd: i32, + buf: *mut u8, + len: usize, + offset: u64, + } + + unsafe impl Send for ReadOp {} + + impl PrepareOp for ReadOp { + fn prepare(&self, sqe: &mut liburing_rs::sys::io_uring_sqe) { + unsafe { + liburing_rs::sys::io_uring_prep_read( + sqe, + self.fd, + self.buf as *mut _, + self.len as u32, + self.offset, + ); + } + } + } + + let result = ring + .submit_op(ReadOp { + fd, + buf: buf_ptr, + len: buf_len, + offset: 0, + }) + .await?; + + assert_eq!(result as usize, test_data.len()); + assert_eq!(&read_buf[..], test_data); + + Ok(()) + } +}