├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── sub_tasks.rs ├── tcp.rs ├── tcp_serv.rs ├── timer.rs ├── udp.rs └── web_server.rs ├── flake.lock ├── flake.nix └── src ├── futures ├── event.rs ├── fs.rs ├── mod.rs ├── mutex.rs ├── read.rs ├── sock_addr.rs ├── tcp.rs ├── timer.rs ├── udp.rs └── write.rs ├── lib.rs ├── reactor ├── mod.rs ├── uring.rs └── uring │ ├── io │ ├── mod.rs │ ├── multishot.rs │ └── oneshot.rs │ └── result.rs └── task.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | - name: Build docs 24 | run: cargo doc 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.6" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys", 76 | ] 77 | 78 | [[package]] 79 | name = "anyhow" 80 | version = "1.0.95" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 83 | 84 | [[package]] 85 | name = "assert_fs" 86 | version = "1.1.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674" 89 | dependencies = [ 90 | "anstyle", 91 | "doc-comment", 92 | "globwalk", 93 | "predicates", 94 | "predicates-core", 95 | "predicates-tree", 96 | "tempfile", 97 | ] 98 | 99 | [[package]] 100 | name = "autocfg" 101 | version = "1.4.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 104 | 105 | [[package]] 106 | name = "backtrace" 107 | version = "0.3.74" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 110 | dependencies = [ 111 | "addr2line", 112 | "cfg-if", 113 | "libc", 114 | "miniz_oxide", 115 | "object", 116 | "rustc-demangle", 117 | "windows-targets", 118 | ] 119 | 120 | [[package]] 121 | name = "bitflags" 122 | version = "2.8.0" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 125 | 126 | [[package]] 127 | name = "bstr" 128 | version = "1.11.3" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" 131 | dependencies = [ 132 | "memchr", 133 | "serde", 134 | ] 135 | 136 | [[package]] 137 | name = "cfg-if" 138 | version = "1.0.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 141 | 142 | [[package]] 143 | name = "clap" 144 | version = "4.5.28" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" 147 | dependencies = [ 148 | "clap_builder", 149 | "clap_derive", 150 | ] 151 | 152 | [[package]] 153 | name = "clap_builder" 154 | version = "4.5.27" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" 157 | dependencies = [ 158 | "anstream", 159 | "anstyle", 160 | "clap_lex", 161 | "strsim", 162 | ] 163 | 164 | [[package]] 165 | name = "clap_derive" 166 | version = "4.5.28" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" 169 | dependencies = [ 170 | "heck", 171 | "proc-macro2", 172 | "quote", 173 | "syn", 174 | ] 175 | 176 | [[package]] 177 | name = "clap_lex" 178 | version = "0.7.4" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 181 | 182 | [[package]] 183 | name = "colorchoice" 184 | version = "1.0.3" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 187 | 188 | [[package]] 189 | name = "crossbeam-deque" 190 | version = "0.8.6" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 193 | dependencies = [ 194 | "crossbeam-epoch", 195 | "crossbeam-utils", 196 | ] 197 | 198 | [[package]] 199 | name = "crossbeam-epoch" 200 | version = "0.9.18" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 203 | dependencies = [ 204 | "crossbeam-utils", 205 | ] 206 | 207 | [[package]] 208 | name = "crossbeam-utils" 209 | version = "0.8.21" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 212 | 213 | [[package]] 214 | name = "difflib" 215 | version = "0.4.0" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 218 | 219 | [[package]] 220 | name = "doc-comment" 221 | version = "0.3.3" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 224 | 225 | [[package]] 226 | name = "env_filter" 227 | version = "0.1.3" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 230 | dependencies = [ 231 | "log", 232 | "regex", 233 | ] 234 | 235 | [[package]] 236 | name = "env_logger" 237 | version = "0.11.6" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 240 | dependencies = [ 241 | "anstream", 242 | "anstyle", 243 | "env_filter", 244 | "humantime", 245 | "log", 246 | ] 247 | 248 | [[package]] 249 | name = "errno" 250 | version = "0.3.10" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 253 | dependencies = [ 254 | "libc", 255 | "windows-sys", 256 | ] 257 | 258 | [[package]] 259 | name = "fastrand" 260 | version = "2.3.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 263 | 264 | [[package]] 265 | name = "futures-core" 266 | version = "0.3.31" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 269 | 270 | [[package]] 271 | name = "getrandom" 272 | version = "0.3.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 275 | dependencies = [ 276 | "cfg-if", 277 | "libc", 278 | "wasi", 279 | "windows-targets", 280 | ] 281 | 282 | [[package]] 283 | name = "gimli" 284 | version = "0.31.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 287 | 288 | [[package]] 289 | name = "globset" 290 | version = "0.4.15" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" 293 | dependencies = [ 294 | "aho-corasick", 295 | "bstr", 296 | "log", 297 | "regex-automata", 298 | "regex-syntax", 299 | ] 300 | 301 | [[package]] 302 | name = "globwalk" 303 | version = "0.9.1" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" 306 | dependencies = [ 307 | "bitflags", 308 | "ignore", 309 | "walkdir", 310 | ] 311 | 312 | [[package]] 313 | name = "heck" 314 | version = "0.5.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 317 | 318 | [[package]] 319 | name = "humantime" 320 | version = "2.1.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 323 | 324 | [[package]] 325 | name = "ignore" 326 | version = "0.4.23" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" 329 | dependencies = [ 330 | "crossbeam-deque", 331 | "globset", 332 | "log", 333 | "memchr", 334 | "regex-automata", 335 | "same-file", 336 | "walkdir", 337 | "winapi-util", 338 | ] 339 | 340 | [[package]] 341 | name = "io-uring" 342 | version = "0.7.4" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "ab01638bb6a279897b7691f87f3f3c232451711fd419a69ced980ce61074fa46" 345 | dependencies = [ 346 | "bitflags", 347 | "cfg-if", 348 | "libc", 349 | ] 350 | 351 | [[package]] 352 | name = "is_terminal_polyfill" 353 | version = "1.70.1" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 356 | 357 | [[package]] 358 | name = "libc" 359 | version = "0.2.167" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" 362 | 363 | [[package]] 364 | name = "linux-raw-sys" 365 | version = "0.4.15" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 368 | 369 | [[package]] 370 | name = "log" 371 | version = "0.4.22" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 374 | 375 | [[package]] 376 | name = "memchr" 377 | version = "2.7.4" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 380 | 381 | [[package]] 382 | name = "miniz_oxide" 383 | version = "0.8.4" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" 386 | dependencies = [ 387 | "adler2", 388 | ] 389 | 390 | [[package]] 391 | name = "object" 392 | version = "0.36.7" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 395 | dependencies = [ 396 | "memchr", 397 | ] 398 | 399 | [[package]] 400 | name = "once_cell" 401 | version = "1.20.2" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 404 | 405 | [[package]] 406 | name = "pin-project-lite" 407 | version = "0.2.16" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 410 | 411 | [[package]] 412 | name = "predicates" 413 | version = "3.1.3" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" 416 | dependencies = [ 417 | "anstyle", 418 | "difflib", 419 | "predicates-core", 420 | ] 421 | 422 | [[package]] 423 | name = "predicates-core" 424 | version = "1.0.9" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 427 | 428 | [[package]] 429 | name = "predicates-tree" 430 | version = "1.0.12" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 433 | dependencies = [ 434 | "predicates-core", 435 | "termtree", 436 | ] 437 | 438 | [[package]] 439 | name = "proc-macro2" 440 | version = "1.0.93" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 443 | dependencies = [ 444 | "unicode-ident", 445 | ] 446 | 447 | [[package]] 448 | name = "quote" 449 | version = "1.0.38" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 452 | dependencies = [ 453 | "proc-macro2", 454 | ] 455 | 456 | [[package]] 457 | name = "regex" 458 | version = "1.11.1" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 461 | dependencies = [ 462 | "aho-corasick", 463 | "memchr", 464 | "regex-automata", 465 | "regex-syntax", 466 | ] 467 | 468 | [[package]] 469 | name = "regex-automata" 470 | version = "0.4.9" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 473 | dependencies = [ 474 | "aho-corasick", 475 | "memchr", 476 | "regex-syntax", 477 | ] 478 | 479 | [[package]] 480 | name = "regex-syntax" 481 | version = "0.8.5" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 484 | 485 | [[package]] 486 | name = "ringbuffer" 487 | version = "0.15.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" 490 | 491 | [[package]] 492 | name = "rustc-demangle" 493 | version = "0.1.24" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 496 | 497 | [[package]] 498 | name = "rustix" 499 | version = "0.38.44" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 502 | dependencies = [ 503 | "bitflags", 504 | "errno", 505 | "libc", 506 | "linux-raw-sys", 507 | "windows-sys", 508 | ] 509 | 510 | [[package]] 511 | name = "same-file" 512 | version = "1.0.6" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 515 | dependencies = [ 516 | "winapi-util", 517 | ] 518 | 519 | [[package]] 520 | name = "serde" 521 | version = "1.0.217" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 524 | dependencies = [ 525 | "serde_derive", 526 | ] 527 | 528 | [[package]] 529 | name = "serde_derive" 530 | version = "1.0.217" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 533 | dependencies = [ 534 | "proc-macro2", 535 | "quote", 536 | "syn", 537 | ] 538 | 539 | [[package]] 540 | name = "slab" 541 | version = "0.4.9" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 544 | dependencies = [ 545 | "autocfg", 546 | ] 547 | 548 | [[package]] 549 | name = "strsim" 550 | version = "0.11.1" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 553 | 554 | [[package]] 555 | name = "syn" 556 | version = "2.0.98" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 559 | dependencies = [ 560 | "proc-macro2", 561 | "quote", 562 | "unicode-ident", 563 | ] 564 | 565 | [[package]] 566 | name = "tempfile" 567 | version = "3.16.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" 570 | dependencies = [ 571 | "cfg-if", 572 | "fastrand", 573 | "getrandom", 574 | "once_cell", 575 | "rustix", 576 | "windows-sys", 577 | ] 578 | 579 | [[package]] 580 | name = "termtree" 581 | version = "0.5.1" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 584 | 585 | [[package]] 586 | name = "tokio" 587 | version = "1.43.0" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 590 | dependencies = [ 591 | "backtrace", 592 | "pin-project-lite", 593 | ] 594 | 595 | [[package]] 596 | name = "tokio-stream" 597 | version = "0.1.17" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 600 | dependencies = [ 601 | "futures-core", 602 | "pin-project-lite", 603 | "tokio", 604 | ] 605 | 606 | [[package]] 607 | name = "trale" 608 | version = "0.3.0" 609 | dependencies = [ 610 | "anyhow", 611 | "assert_fs", 612 | "clap", 613 | "env_logger", 614 | "io-uring", 615 | "libc", 616 | "log", 617 | "ringbuffer", 618 | "slab", 619 | "tokio-stream", 620 | ] 621 | 622 | [[package]] 623 | name = "unicode-ident" 624 | version = "1.0.16" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 627 | 628 | [[package]] 629 | name = "utf8parse" 630 | version = "0.2.2" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 633 | 634 | [[package]] 635 | name = "walkdir" 636 | version = "2.5.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 639 | dependencies = [ 640 | "same-file", 641 | "winapi-util", 642 | ] 643 | 644 | [[package]] 645 | name = "wasi" 646 | version = "0.13.3+wasi-0.2.2" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 649 | dependencies = [ 650 | "wit-bindgen-rt", 651 | ] 652 | 653 | [[package]] 654 | name = "winapi-util" 655 | version = "0.1.9" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 658 | dependencies = [ 659 | "windows-sys", 660 | ] 661 | 662 | [[package]] 663 | name = "windows-sys" 664 | version = "0.59.0" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 667 | dependencies = [ 668 | "windows-targets", 669 | ] 670 | 671 | [[package]] 672 | name = "windows-targets" 673 | version = "0.52.6" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 676 | dependencies = [ 677 | "windows_aarch64_gnullvm", 678 | "windows_aarch64_msvc", 679 | "windows_i686_gnu", 680 | "windows_i686_gnullvm", 681 | "windows_i686_msvc", 682 | "windows_x86_64_gnu", 683 | "windows_x86_64_gnullvm", 684 | "windows_x86_64_msvc", 685 | ] 686 | 687 | [[package]] 688 | name = "windows_aarch64_gnullvm" 689 | version = "0.52.6" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 692 | 693 | [[package]] 694 | name = "windows_aarch64_msvc" 695 | version = "0.52.6" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 698 | 699 | [[package]] 700 | name = "windows_i686_gnu" 701 | version = "0.52.6" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 704 | 705 | [[package]] 706 | name = "windows_i686_gnullvm" 707 | version = "0.52.6" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 710 | 711 | [[package]] 712 | name = "windows_i686_msvc" 713 | version = "0.52.6" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 716 | 717 | [[package]] 718 | name = "windows_x86_64_gnu" 719 | version = "0.52.6" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 722 | 723 | [[package]] 724 | name = "windows_x86_64_gnullvm" 725 | version = "0.52.6" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 728 | 729 | [[package]] 730 | name = "windows_x86_64_msvc" 731 | version = "0.52.6" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 734 | 735 | [[package]] 736 | name = "wit-bindgen-rt" 737 | version = "0.33.0" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 740 | dependencies = [ 741 | "bitflags", 742 | ] 743 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trale" 3 | version = "0.3.0" 4 | edition = "2021" 5 | authors = ["Matthew Leach "] 6 | license = "MIT" 7 | readme = "README.md" 8 | repository = "https://github.com/hexagonal-sun/trale" 9 | description = """ 10 | Trale is a minimalistic Rust async executor using io_uring for efficient, correct 11 | task execution. 12 | """ 13 | categories = ["asynchronous", "network-programming"] 14 | keywords = ["io", "async", "non-blocking", "futures"] 15 | 16 | 17 | [dependencies] 18 | io-uring = "0.7.4" 19 | libc = "0.2.167" 20 | log = "0.4.22" 21 | ringbuffer = "0.15.0" 22 | slab = "0.4.9" 23 | tokio-stream = { version = "0.1.17", default-features = false } 24 | 25 | [dev-dependencies] 26 | anyhow = "1.0.81" 27 | assert_fs = "1.1.2" 28 | clap = { version = "4.5.28", features = ["derive"] } 29 | env_logger = "0.11.6" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Matthew Leach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `trale`: Tiny Rust Async Linux Executor 2 | 3 | [![Crates.io][crates-badge]][crates-url] 4 | [![MIT licensed][mit-badge]][mit-url] 5 | [![Build Status][actions-badge]][actions-url] 6 | 7 | [crates-badge]: https://img.shields.io/crates/v/trale.svg 8 | [crates-url]: https://crates.io/crates/trale 9 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 10 | [mit-url]: https://github.com/hexagonal-sun/trale/blob/master/LICENSE 11 | [actions-badge]: https://github.com/hexagonal-sun/trale/workflows/Rust/badge.svg 12 | [actions-url]: https://github.com/hexagonal-sun/trale/actions?query=branch%3Amaster 13 | 14 | This project implements a minimalistic asynchronous Rust executor, written in as 15 | few lines as possible. Its primary goal is to serve as an educational resource 16 | for those studying Rust's async ecosystem. It provides a *real* executor capable 17 | of running multiple async tasks on a single thread, showcasing a simple yet 18 | functional concrete implementation. 19 | 20 | To achieve this, `trale` tightly integrates with Linux's `io_uring` interface, 21 | opting for minimal abstractions to prioritise performance. While it sacrifices 22 | some abstraction in favour of efficiency, **correctness** is not compromised. 23 | 24 | ## Supported Features 25 | 26 | - **`io_uring`-based reactor**: State-of-the-art reactor utilising Linux's 27 | latest I/O userspace 28 | interface,[`io_uring`](https://man7.org/linux/man-pages/man7/io_uring.7.html). 29 | - **Single-threaded executor**: Polls tasks on a runqueue and moves them to an 30 | idle queue when waiting for wakeups from the reactor. 31 | - **Timer using `TimerFd`**: Leverages Linux's 32 | [`timerfd_create`](https://linux.die.net/man/2/timerfd_create). 33 | - **UDP sockets**: Non-blocking `std::net::UdpSocket` support. 34 | - **TCP sockets**: Basic TCP socket support. 35 | - **Inter-task events**: Uses [`EventFd`](https://linux.die.net/man/2/eventfd) 36 | for inter-task communication. 37 | - **Task synchronization**: Implements synchronization via a `Mutex` type, 38 | backed by `EventFd` as the primitive. 39 | 40 | Example Usage 41 | ----- 42 | 43 | To see various examples, see the `examples/` directory in the root of 44 | the project. As a starting point: 45 | 46 | ```rust 47 | use std::time::Duration; 48 | 49 | use trale::{futures::timer::Timer, task::Executor}; 50 | 51 | fn main() { 52 | env_logger::init(); 53 | 54 | Executor::spawn(async { 55 | Timer::sleep(Duration::from_secs(2)).unwrap().await; 56 | println!("Hello A!"); 57 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 58 | println!("Hello B!"); 59 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 60 | println!("Hello C!"); 61 | }); 62 | 63 | Executor::spawn(async { 64 | Timer::sleep(Duration::from_secs(2)).unwrap().await; 65 | println!("Hello a!"); 66 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 67 | println!("Hello b!"); 68 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 69 | println!("Hello c!"); 70 | }); 71 | 72 | Executor::run(); 73 | } 74 | 75 | ``` 76 | 77 | - `timer`: This example spawns two tasks which, both racing to print 78 | messages to the terminal. 79 | - `udp`: This example transfers twenty bytes between two tasks usng 80 | UDP sockets on the localhost interface whilst a third task is 81 | printing messages to the terminal. 82 | - `tcp`: This is an implementation of a TCP echo client (connecting to 83 | `127.0.0.1:5000`) whilst another task prints out messages to the 84 | terminal. 85 | - `tcp_serv`: This is an implementation of a TCP echo server (waiting for 86 | connections to `127.0.0.1:5000`) whilst another task prints out messages to 87 | the terminal. 88 | - `sub_tasks`: This demonstates how a subtask can be spawned from a 89 | parent task and the joinhandle can be `.await`ed on without blocking 90 | the runqueue. 91 | 92 | ## Implementation 93 | 94 | ### Tasks & Executor 95 | 96 | Each `Task` represents an asynchronous computation that needs to be executed. It 97 | contains a top-level pinned and boxed future, referred to as the `future`, which 98 | is typically an `async` block passed to the `spawn()` function. This function 99 | creates a `Task` object and places it on the executor's per-thread run queue. 100 | 101 | It is the responsibility of the `Executor` to call `poll()` on a task's 102 | top-level future to advance its execution. Since futures are state machines, 103 | each call to `poll()` modifies the future’s internal state on the heap. If 104 | `poll()` returns `Poll::Pending`, the future will 'resume' execution from the 105 | same point the next time `poll()` is called. This process is recursive: if a 106 | future `await`s other futures, `poll()` will be called on those sub-futures as 107 | well. The recursion continues until a "terminal" future is reached, which 108 | typically interacts with the reactor to schedule a wakeup when execution can 109 | proceed. 110 | 111 | The `Executor` is responsible for pushing tasks to completion. It consists of a 112 | run queue and a wait queue. When synchronous code calls either `Executor::run` 113 | or `Executor::spawn_blocking`, the `Executor::executor_loop` function is invoked 114 | on the same thread, orchestrating the execution of futures. 115 | 116 | The execution loop follows these steps: 117 | 118 | 1. **Check for Tasks to Process**: The loop first checks if both the run queue 119 | and the wait queue are empty. If both are empty, the loop exits, as there are 120 | no more tasks to process. 121 | 2. **Remove a Task**: If there are tasks to process, a task is removed from the 122 | run queue. 123 | - If the run queue is empty, `io_uring_enter` is invoked, via the reactor, to 124 | block the thread until an I/O event completes. 125 | - When `io_uring_enter` returns, the corresponding all I/O events that have 126 | completed have their corresponding `Waker`s called which places the task on 127 | the runqueue. 128 | 3. **Poll the Task**: The `Executor` calls `poll()` on the task's future to make 129 | progress. 130 | - If `poll()` returns `Poll::Ready`, the task is discarded as it has 131 | completed. 132 | - If `poll()` returns `Poll::Pending`, the task is placed in the wait queue 133 | for later processing. 134 | 135 | ### Reactor 136 | 137 | The Reactor is responsible for invoking `Task::wake()` whenever a task can make 138 | progress. This typically occurs when some I/O operation completes. Each future 139 | that requires I/O interaction needs to obtain a handle to the reactor's I/O 140 | interface via `Reactor::new_io()`. This handle, represented by `ReactorIo`, is 141 | used for submitting I/O operations and retrieving their results. The key 142 | function for interacting with the reactor is `submit_or_get_result()`. This 143 | function is designed to be called within the `poll()` method of a future, 144 | providing a bridge between the future and the reactor. 145 | 146 | The `submit_or_get_result()` function takes a closure that is responsible for 147 | creating an I/O operation, which is encapsulated in an `Entry`, and associates 148 | it with a `Waker` to notify the task when the operation has completed. The 149 | `Entry` describes the type of I/O operation, such as a read or write, and 150 | contains the necessary arguments to be passed to the kernel. The `Waker` is used 151 | to wake the task once the I/O operation is ready to be processed. 152 | 153 | One of the most important characteristics of this system is that the closure 154 | provided to `submit_or_get_result()` is invoked only once to submit the I/O 155 | request to the kernel. This design isn't just a performance optimisation; it 156 | also addresses soundness concerns. Since buffers and other resources are shared 157 | between the user space and the kernel, submitting the same I/O operation 158 | multiple times could lead to serious issues. For instance, if a future were 159 | polled more than once and the I/O request were re-submitted, the original 160 | submission might contain references to deallocated memory, invalid file 161 | descriptors, or other corrupted state. By ensuring that the closure is only 162 | called once, we avoid these potential pitfalls. On the first call, the function 163 | returns `Poll::Pending`, placing the task in a pending state until the operation 164 | completes. If the task is polled again before the I/O has finished, it simply 165 | returns `Poll::Pending` without invoking the closure, as the reactor already 166 | knows about the pending I/O operation. Once the I/O completes, 167 | `submit_or_get_result()` returns `Poll::Ready` with the result of the I/O 168 | operation, encapsulated in a `std::io::Result`. 169 | -------------------------------------------------------------------------------- /examples/sub_tasks.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use trale::{futures::timer::Timer, task::Executor}; 4 | 5 | fn main() { 6 | Executor::block_on(async { 7 | Timer::sleep(Duration::from_secs(2)).unwrap().await; 8 | println!("Hello A!"); 9 | 10 | let task2 = Executor::spawn(async { 11 | println!("Hello from other task"); 12 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 13 | println!("Bye bye from other task"); 14 | 15 | 24 16 | }); 17 | 18 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 19 | println!("Hello B!"); 20 | 21 | assert_eq!(task2.await, 24); 22 | 23 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 24 | println!("Hello C!"); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /examples/tcp.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use anyhow::{Context, Result}; 4 | use trale::{ 5 | futures::{read::AsyncRead, tcp::TcpStream, timer::Timer, write::AsyncWrite}, 6 | task::{Executor, TaskJoiner}, 7 | }; 8 | 9 | fn main() -> Result<()> { 10 | Executor::spawn(async { 11 | Timer::sleep(Duration::from_millis(500)).unwrap().await; 12 | println!("Hello A!"); 13 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 14 | println!("Hello B!"); 15 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 16 | println!("Hello C!"); 17 | }); 18 | 19 | let echo_task: TaskJoiner> = Executor::spawn(async { 20 | let mut buf = [0u8; 1]; 21 | let mut bytes_read: usize = 0; 22 | let mut sock = TcpStream::connect("127.0.0.1:5000") 23 | .await 24 | .context("Could not connect")?; 25 | loop { 26 | let len = sock.read(&mut buf).await?; 27 | if len == 0 { 28 | return Ok(bytes_read); 29 | } 30 | bytes_read += 1; 31 | sock.write(&buf).await?; 32 | } 33 | }); 34 | 35 | Executor::run(); 36 | 37 | let bytes_read = echo_task.join()?; 38 | eprintln!("Conversation finished. Read {bytes_read} bytes"); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /examples/tcp_serv.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use anyhow::{Context, Result}; 4 | use log::debug; 5 | use tokio_stream::StreamExt; 6 | use trale::{ 7 | futures::{read::AsyncRead, tcp::TcpListener, timer::Timer, write::AsyncWrite}, 8 | task::{Executor, TaskJoiner}, 9 | }; 10 | 11 | fn main() -> Result<()> { 12 | env_logger::init(); 13 | 14 | Executor::spawn(async { 15 | Timer::sleep(Duration::from_millis(500)).unwrap().await; 16 | println!("Hello A!"); 17 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 18 | println!("Hello B!"); 19 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 20 | println!("Hello C!"); 21 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 22 | println!("Hello D!"); 23 | }); 24 | 25 | let echo_task: TaskJoiner> = Executor::spawn(async { 26 | let mut buf = [0u8; 1]; 27 | let mut bytes_read: usize = 0; 28 | let mut listener = TcpListener::bind("127.0.0.1:5000").context("Could not bind")?; 29 | 30 | println!("Waiting for connection on 127.0.0.1:5000"); 31 | 32 | let mut conn = listener 33 | .next() 34 | .await 35 | .unwrap() 36 | .context("Could not accept incoming connection")?; 37 | 38 | // We only want to accept a single connection. Drop the lisetner once 39 | // we've accepted a connection. 40 | drop(listener); 41 | 42 | loop { 43 | debug!("Reading from socket"); 44 | let len = conn.read(&mut buf).await?; 45 | if len == 0 { 46 | return Ok(bytes_read); 47 | } 48 | bytes_read += 1; 49 | conn.write(&buf).await?; 50 | } 51 | }); 52 | 53 | Executor::run(); 54 | 55 | let bytes_read = echo_task.join()?; 56 | eprintln!("Conversation finished. Read {bytes_read} bytes"); 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /examples/timer.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use trale::{futures::timer::Timer, task::Executor}; 4 | 5 | fn main() { 6 | env_logger::init(); 7 | 8 | Executor::spawn(async { 9 | Timer::sleep(Duration::from_secs(2)).unwrap().await; 10 | println!("Hello A!"); 11 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 12 | println!("Hello B!"); 13 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 14 | println!("Hello C!"); 15 | }); 16 | 17 | Executor::spawn(async { 18 | Timer::sleep(Duration::from_secs(2)).unwrap().await; 19 | println!("Hello a!"); 20 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 21 | println!("Hello b!"); 22 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 23 | println!("Hello c!"); 24 | }); 25 | 26 | Executor::run(); 27 | } 28 | -------------------------------------------------------------------------------- /examples/udp.rs: -------------------------------------------------------------------------------- 1 | use std::{net::Ipv4Addr, time::Duration}; 2 | 3 | use trale::{ 4 | futures::{timer::Timer, udp::UdpSocket}, 5 | task::Executor, 6 | }; 7 | 8 | fn main() { 9 | Executor::spawn(async { 10 | Timer::sleep(Duration::from_millis(500)).unwrap().await; 11 | println!("Hello A!"); 12 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 13 | println!("Hello B!"); 14 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 15 | println!("Hello C!"); 16 | }); 17 | 18 | Executor::spawn(async { 19 | let mut buf = [0u8; 1500]; 20 | let mut udpsock = UdpSocket::bind((Ipv4Addr::LOCALHOST, 9998)).unwrap(); 21 | let (len, src) = udpsock.recv_from(&mut buf).await.unwrap(); 22 | 23 | println!("Received {} bytes from {:?}", len, src); 24 | }); 25 | 26 | Executor::spawn(async { 27 | let buf = [0xadu8; 20]; 28 | let udpsock = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap(); 29 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 30 | let len = udpsock 31 | .send_to(&buf, (Ipv4Addr::LOCALHOST, 9998)) 32 | .await 33 | .unwrap(); 34 | 35 | println!("Sent {} bytes", len); 36 | }); 37 | 38 | Executor::run() 39 | } 40 | -------------------------------------------------------------------------------- /examples/web_server.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use clap::Parser; 3 | use core::str; 4 | use log::{debug, error}; 5 | use std::{ 6 | io::{ErrorKind, Seek}, 7 | net::Ipv4Addr, 8 | path::PathBuf, 9 | sync::OnceLock, 10 | }; 11 | use tokio_stream::StreamExt; 12 | use trale::{ 13 | futures::{ 14 | fs::File, 15 | read::AsyncRead, 16 | tcp::{TcpListener, TcpStream}, 17 | write::AsyncWrite, 18 | }, 19 | task::Executor, 20 | }; 21 | 22 | #[derive(Clone)] 23 | enum Response { 24 | NotImplemented, 25 | ServerError, 26 | Ok, 27 | NotFound, 28 | } 29 | 30 | static ARGS: OnceLock = OnceLock::new(); 31 | 32 | impl From<&Response> for u32 { 33 | fn from(value: &Response) -> Self { 34 | match value { 35 | Response::NotImplemented => 501, 36 | Response::ServerError => 500, 37 | Response::Ok => 200, 38 | Response::NotFound => 404, 39 | } 40 | } 41 | } 42 | 43 | impl From<&Response> for &str { 44 | fn from(value: &Response) -> Self { 45 | match value { 46 | Response::NotImplemented => "Not Implemented", 47 | Response::ServerError => "Internal Server Error", 48 | Response::Ok => "OK", 49 | Response::NotFound => "Not Found", 50 | } 51 | } 52 | } 53 | 54 | /// A trale webserver example. 55 | /// 56 | /// This example will server out files that live under `webroot` via the HTTP. 57 | #[derive(Parser, Debug)] 58 | struct Args { 59 | /// The port number that the web server should listen on. 60 | #[arg(short, long, default_value_t = 80)] 61 | port: u16, 62 | 63 | /// The base directory where html files should be searched. 64 | webroot: PathBuf, 65 | } 66 | 67 | async fn send_response_hdr( 68 | conn: &mut TcpStream, 69 | code: Response, 70 | content_length: usize, 71 | ) -> anyhow::Result<()> { 72 | let response = format!( 73 | "HTTP/1.1 {} {}\r\nServer: tws\r\nContent-Length: {}\r\n\r\n", 74 | <&Response as Into>::into(&code), 75 | <&Response as Into<&str>>::into(&code), 76 | content_length, 77 | ); 78 | 79 | Ok(conn.write(response.as_bytes()).await.map(|_| ())?) 80 | } 81 | 82 | async fn send_file(mut conn: TcpStream, path: PathBuf) -> anyhow::Result<()> { 83 | match File::open(path).await { 84 | Ok(mut f) => { 85 | let len = f.seek(std::io::SeekFrom::End(0))? as usize; 86 | 87 | f.seek(std::io::SeekFrom::Start(0))?; 88 | 89 | send_response_hdr(&mut conn, Response::Ok, len).await?; 90 | 91 | loop { 92 | let mut buf = [0; 4096]; 93 | let len = f.read(&mut buf).await?; 94 | 95 | if len == 0 { 96 | break; 97 | } 98 | 99 | conn.write(&buf).await?; 100 | } 101 | 102 | Ok(()) 103 | } 104 | Err(e) if e.kind() == ErrorKind::NotFound => { 105 | send_response_hdr(&mut conn, Response::NotFound, 0).await 106 | } 107 | _ => send_response_hdr(&mut conn, Response::ServerError, 0).await, 108 | } 109 | } 110 | 111 | async fn handle_connection(mut conn: TcpStream) -> anyhow::Result<()> { 112 | debug!("New Connection"); 113 | let mut buf = [0; 1024]; 114 | let mut request = String::new(); 115 | 116 | while !request.contains("\r\n\r\n") { 117 | let len = conn.read(&mut buf).await?; 118 | request.push_str(str::from_utf8(&buf[..len]).unwrap()); 119 | } 120 | 121 | debug!("Got request: {}", request); 122 | 123 | let req_hdr = request.split("\n").next().unwrap().trim(); 124 | 125 | let parts: Vec<_> = req_hdr.split(" ").collect(); 126 | 127 | let (method, path) = (parts[0], parts[1]); 128 | 129 | if method.to_lowercase() != "get" { 130 | return send_response_hdr(&mut conn, Response::NotImplemented, 0).await; 131 | } 132 | 133 | let path = PathBuf::from(path); 134 | 135 | let file = if path == PathBuf::from("/") { 136 | ARGS.get().unwrap().webroot.join("index.html") 137 | } else if let Ok(path) = path.strip_prefix("/") { 138 | ARGS.get().unwrap().webroot.join(path) 139 | } else { 140 | return send_response_hdr(&mut conn, Response::NotFound, 0).await; 141 | }; 142 | 143 | send_file(conn, file).await 144 | } 145 | 146 | fn main() -> anyhow::Result<()> { 147 | env_logger::init(); 148 | 149 | let args = Args::parse(); 150 | 151 | ARGS.set(args).expect("Should have never been set"); 152 | 153 | let mut listener = TcpListener::bind((Ipv4Addr::UNSPECIFIED, ARGS.get().unwrap().port)) 154 | .context("Could not setup socket listener")?; 155 | 156 | Executor::block_on(async move { 157 | while let Some(conn) = listener.next().await { 158 | match conn { 159 | Ok(conn) => { 160 | Executor::spawn(async { 161 | if let Err(e) = handle_connection(conn).await { 162 | error!("Error handling connection: {e:#}"); 163 | } 164 | }); 165 | } 166 | Err(e) => error!("Could not accept incoming connection: {e:?}"), 167 | } 168 | } 169 | eprintln!("Bye!"); 170 | }); 171 | 172 | Ok(()) 173 | } 174 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "fenix": { 4 | "inputs": { 5 | "nixpkgs": "nixpkgs", 6 | "rust-analyzer-src": "rust-analyzer-src" 7 | }, 8 | "locked": { 9 | "lastModified": 1722752898, 10 | "narHash": "sha256-8BwcY0yYlUxpoqpAoeXKxVG+w97LUBp+pfzUiISOmeY=", 11 | "owner": "nix-community", 12 | "repo": "fenix", 13 | "rev": "0caa626457f1f4f8273a89775bf9081b7fc09823", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "nix-community", 18 | "repo": "fenix", 19 | "type": "github" 20 | } 21 | }, 22 | "nixpkgs": { 23 | "locked": { 24 | "lastModified": 1722421184, 25 | "narHash": "sha256-/DJBI6trCeVnasdjUo9pbnodCLZcFqnVZiLUfqLH4jA=", 26 | "owner": "nixos", 27 | "repo": "nixpkgs", 28 | "rev": "9f918d616c5321ad374ae6cb5ea89c9e04bf3e58", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "nixos", 33 | "ref": "nixos-unstable", 34 | "repo": "nixpkgs", 35 | "type": "github" 36 | } 37 | }, 38 | "nixpkgs_2": { 39 | "locked": { 40 | "lastModified": 1722062969, 41 | "narHash": "sha256-QOS0ykELUmPbrrUGmegAUlpmUFznDQeR4q7rFhl8eQg=", 42 | "path": "/nix/store/qxf6anli54ij0q1sdlnlgx9hyl658a4v-source", 43 | "rev": "b73c2221a46c13557b1b3be9c2070cc42cf01eb3", 44 | "type": "path" 45 | }, 46 | "original": { 47 | "id": "nixpkgs", 48 | "type": "indirect" 49 | } 50 | }, 51 | "root": { 52 | "inputs": { 53 | "fenix": "fenix", 54 | "nixpkgs": "nixpkgs_2", 55 | "utils": "utils" 56 | } 57 | }, 58 | "rust-analyzer-src": { 59 | "flake": false, 60 | "locked": { 61 | "lastModified": 1722589793, 62 | "narHash": "sha256-OYDIo1Iqb6ldcC6JdqzKAKSRiXjDOwOAJKKMH8OZutk=", 63 | "owner": "rust-lang", 64 | "repo": "rust-analyzer", 65 | "rev": "aa00ddcf654a35ba0eafe17247cf189958d33182", 66 | "type": "github" 67 | }, 68 | "original": { 69 | "owner": "rust-lang", 70 | "ref": "nightly", 71 | "repo": "rust-analyzer", 72 | "type": "github" 73 | } 74 | }, 75 | "systems": { 76 | "locked": { 77 | "lastModified": 1681028828, 78 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 79 | "owner": "nix-systems", 80 | "repo": "default", 81 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "owner": "nix-systems", 86 | "repo": "default", 87 | "type": "github" 88 | } 89 | }, 90 | "utils": { 91 | "inputs": { 92 | "systems": "systems" 93 | }, 94 | "locked": { 95 | "lastModified": 1710146030, 96 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 97 | "owner": "numtide", 98 | "repo": "flake-utils", 99 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 100 | "type": "github" 101 | }, 102 | "original": { 103 | "owner": "numtide", 104 | "repo": "flake-utils", 105 | "type": "github" 106 | } 107 | } 108 | }, 109 | "root": "root", 110 | "version": 7 111 | } 112 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | fenix.url = "github:nix-community/fenix"; 4 | utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | outputs = { self, nixpkgs, utils, fenix }: 8 | utils.lib.eachDefaultSystem (system: 9 | let 10 | pkgs = import nixpkgs { inherit system; }; 11 | toolchain = fenix.packages.${system}.latest.toolchain; 12 | in { 13 | devShell = pkgs.mkShell { 14 | buildInputs = [ toolchain ]; 15 | }; 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/futures/event.rs: -------------------------------------------------------------------------------- 1 | //! Async event synchronisation. 2 | //! 3 | //! This module implements an event synchroniser between tasks. It is backed by 4 | //! the Linux kernel's 5 | //! [eventfd](https://man7.org/linux/man-pages/man2/eventfd.2.html). It allows 6 | //! one task to inform another that an event has taken place. 7 | //! 8 | //! # Example 9 | //! 10 | //! Here is an example of one task that waits for an event from another. 11 | //! ``` 12 | //! use trale::futures::event::Event; 13 | //! use trale::futures::timer::Timer; 14 | //! use trale::task::Executor; 15 | //! use std::time::Duration; 16 | //!# Executor::block_on( 17 | //! async { 18 | //! let evt = Event::new()?; 19 | //! let mut evt2 = evt.clone(); 20 | //! let tsk = Executor::spawn(async move { 21 | //! evt2.wait().await.unwrap(); 22 | //! }); 23 | //! 24 | //! Timer::sleep(Duration::from_secs(1)).unwrap().await; 25 | //! 26 | //! evt.notify_one()?; 27 | //! 28 | //! tsk.await; 29 | //!# Ok::<(), std::io::Error>(()) 30 | //! } 31 | //!# ); 32 | //! ``` 33 | //! 34 | //! Note that the events can also act as a semaphore, allowing multiple events 35 | //! to be queued up and awaited: 36 | //! ``` 37 | //! use trale::futures::event::Event; 38 | //! use trale::futures::timer::Timer; 39 | //! use trale::task::Executor; 40 | //! use std::time::Duration; 41 | //! use std::thread; 42 | //!# Executor::block_on( 43 | //! async { 44 | //! let evt = Event::new()?; 45 | //! let mut evt2 = evt.clone(); 46 | //! let evt3 = evt.clone(); 47 | //! let tsk = Executor::spawn(async move { 48 | //! let mut count = 0; 49 | //! 50 | //! while count < 20 { 51 | //! evt2.wait().await.unwrap(); 52 | //! count += 1; 53 | //! } 54 | //! }); 55 | //! 56 | //! thread::spawn(move || { 57 | //! for _ in 0..10 { 58 | //! evt3.notify_one(); 59 | //! } 60 | //! }); 61 | //! 62 | //! for _ in 0..10 { 63 | //! evt.notify_one(); 64 | //! } 65 | //! 66 | //! tsk.await; 67 | //!# Ok::<(), std::io::Error>(()) 68 | //! } 69 | //!# ); 70 | //! ``` 71 | use io_uring::{opcode, types}; 72 | use libc::{eventfd, EFD_NONBLOCK, EFD_SEMAPHORE}; 73 | use std::{ 74 | ffi::c_void, 75 | future::Future, 76 | io::{ErrorKind, Result}, 77 | os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, 78 | pin::Pin, 79 | task::{Context, Poll}, 80 | }; 81 | 82 | use crate::reactor::{Reactor, ReactorIo}; 83 | 84 | /// Async event signaller 85 | /// 86 | /// Allows events to be signaled between async tasks. Use [Event::new] to create an 87 | /// event object, await the [Event::wait] future to suspend execution until an event 88 | /// arrives and [Event::notify_one] to send an event. 89 | pub struct Event { 90 | inner: OwnedFd, 91 | } 92 | 93 | /// Event wait future 94 | /// 95 | /// A future that will wait for the assoicated Event to become ready. See 96 | /// [Event::wait()]. 97 | pub struct EventWaiter<'fd> { 98 | inner: BorrowedFd<'fd>, 99 | io: ReactorIo, 100 | wait_buf: [u8; std::mem::size_of::()], 101 | } 102 | 103 | impl Event { 104 | /// Construct a new event object. 105 | /// 106 | /// This function calls the `eventfd()` function and returns the wrapped 107 | /// file descriptor in an Event object. If the `eventfd()` function fails 108 | /// this function will return `Err` with an associated error object. 109 | pub fn new() -> Result { 110 | let fd = unsafe { eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE) }; 111 | 112 | if fd == -1 { 113 | Err(std::io::Error::last_os_error())?; 114 | } 115 | 116 | Ok(Self { 117 | inner: unsafe { OwnedFd::from_raw_fd(fd) }, 118 | }) 119 | } 120 | 121 | /// Notify of an event. 122 | /// 123 | /// This function sets the event as triggered. Any associated waiters will 124 | /// be awoken and the [EventWaiter] future will complete. If there are no 125 | /// tasks awaiting the event when this function is called, the event will be 126 | /// latched such that any subsequent awaits on [Event::wait] will *not* 127 | /// suspend execution, but will poll as `Ready`. 128 | pub fn notify_one(&self) -> Result<()> { 129 | let buffer = 1_u64.to_ne_bytes(); 130 | let ret = unsafe { 131 | libc::write( 132 | self.inner.as_raw_fd(), 133 | buffer.as_ptr() as *const c_void, 134 | buffer.len(), 135 | ) 136 | }; 137 | 138 | if ret == -1 { 139 | Err(std::io::Error::last_os_error())? 140 | } 141 | 142 | if ret as usize != buffer.len() { 143 | return Err(std::io::Error::new( 144 | ErrorKind::UnexpectedEof, 145 | "Failed to write entire event fd buffer", 146 | )); 147 | } 148 | 149 | Ok(()) 150 | } 151 | 152 | /// Wait for an event 153 | /// 154 | /// This function returns an [EventWaiter] object which can be `.await`ed to 155 | /// suspend execution until an event is sent via [Event::notify_one]. Note 156 | /// that if an event has already been sent *before* this function was 157 | /// called, `.await`ing on the return from this function will return 158 | /// `Ready`. 159 | pub fn wait(&mut self) -> EventWaiter<'_> { 160 | EventWaiter { 161 | inner: self.inner.as_fd(), 162 | io: Reactor::new_io(), 163 | wait_buf: [0; std::mem::size_of::()], 164 | } 165 | } 166 | } 167 | 168 | impl Clone for Event { 169 | fn clone(&self) -> Self { 170 | Self { 171 | inner: self.inner.try_clone().unwrap(), 172 | } 173 | } 174 | } 175 | 176 | impl Future for EventWaiter<'_> { 177 | type Output = Result<()>; 178 | 179 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { 180 | let this = unsafe { self.get_unchecked_mut() }; 181 | 182 | this.io 183 | .submit_or_get_result(|| { 184 | ( 185 | opcode::Read::new( 186 | types::Fd(this.inner.as_raw_fd()), 187 | this.wait_buf.as_mut_ptr(), 188 | this.wait_buf.len() as _, 189 | ) 190 | .build(), 191 | ctx.waker().clone(), 192 | ) 193 | }) 194 | .map(|x| x.map(|_| ())) 195 | } 196 | } 197 | 198 | #[cfg(test)] 199 | mod tests { 200 | use std::thread; 201 | 202 | use super::Event; 203 | use crate::task::Executor; 204 | 205 | #[test] 206 | fn simple() { 207 | Executor::block_on(async { 208 | let evt = Event::new().unwrap(); 209 | 210 | let task = { 211 | let mut evt = evt.clone(); 212 | Executor::spawn(async move { 213 | evt.wait().await.unwrap(); 214 | }) 215 | }; 216 | 217 | evt.notify_one().unwrap(); 218 | task.await; 219 | }); 220 | } 221 | 222 | #[test] 223 | fn multi_notifiers() { 224 | Executor::block_on(async { 225 | let evt = Event::new().unwrap(); 226 | 227 | let task = { 228 | let mut evt = evt.clone(); 229 | Executor::spawn(async move { 230 | let mut count = 0; 231 | while count < 40 { 232 | evt.wait().await.unwrap(); 233 | count += 1; 234 | } 235 | }) 236 | }; 237 | 238 | let t1 = { 239 | let evt = evt.clone(); 240 | thread::spawn(move || { 241 | for _ in 0..20 { 242 | evt.notify_one().unwrap(); 243 | } 244 | }) 245 | }; 246 | 247 | let t2 = { 248 | let evt = evt.clone(); 249 | thread::spawn(move || { 250 | for _ in 0..20 { 251 | evt.notify_one().unwrap(); 252 | } 253 | }) 254 | }; 255 | 256 | t1.join().unwrap(); 257 | t2.join().unwrap(); 258 | task.await; 259 | }); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/futures/fs.rs: -------------------------------------------------------------------------------- 1 | //! Async filesystem operations. 2 | //! 3 | //! This module implements an async version of [std::fs]. It allows files to be 4 | //! opened, read from and written to asynchronously. 5 | //! 6 | //! # Example 7 | //! 8 | //! Here is an example of creating a new file and writing the string "Hello, 9 | //! world!", seeking to the start and reading it back into a buffer. 10 | //! 11 | //! ``` 12 | //! use trale::task::Executor; 13 | //! use trale::futures::fs::File; 14 | //! use trale::futures::write::AsyncWrite; 15 | //! use crate::trale::futures::read::AsyncRead; 16 | //! use std::io::Seek; 17 | //!# use assert_fs::TempDir; 18 | //!# use assert_fs::fixture::PathChild; 19 | //! 20 | //! Executor::block_on(async { 21 | //!# let dir = TempDir::new().unwrap(); 22 | //!# let path = dir.child("test.txt").to_path_buf(); 23 | //! let mut buf = [0; 1024]; 24 | //! let mut file = File::create(path).await?; 25 | //! file.write("Hello, world!".as_bytes()).await?; 26 | //! 27 | //! file.rewind()?; 28 | //! 29 | //! let len = file.read(&mut buf).await?; 30 | //! assert_eq!(&buf[..len], "Hello, world!".as_bytes()); 31 | //!# Ok::<(), std::io::Error>(()) 32 | //! }); 33 | //! ``` 34 | use std::{ 35 | ffi::CString, 36 | future::Future, 37 | io::{self, Result, Seek}, 38 | os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd}, 39 | path::Path, 40 | }; 41 | 42 | use io_uring::{opcode, types}; 43 | use libc::{O_CREAT, O_RDWR}; 44 | 45 | use crate::reactor::{Reactor, ReactorIo}; 46 | 47 | use super::{ 48 | read::{AsyncRead, AsyncReader}, 49 | write::{AsyncWrite, AsyncWriter}, 50 | }; 51 | 52 | /// An open file. 53 | /// 54 | /// This object represents a file that has been opened by one of the 55 | /// [File::create] or [File::open] methods. It can be used for reading and 56 | /// writing data via the [File::read] and [File::write] functions respectively. 57 | pub struct File { 58 | inner: OwnedFd, 59 | } 60 | 61 | /// A future for creating a directory. 62 | /// 63 | /// This future can be `.await`ed in order to create a directory. If the 64 | /// directory could not be created an `Err` value is returned with the 65 | /// underlying error indicating the reason for failure. 66 | pub struct Mkdir { 67 | io: ReactorIo, 68 | path: CString, 69 | } 70 | 71 | /// A future for opening a file. 72 | /// 73 | /// This future can be `.await`ed in order to obtain an open [File] object. It 74 | /// is created by one of the [File::open] or [File::create] methods. Note that 75 | /// if the file could not be opened and/or created an `Err` value is returned 76 | /// with the underlying error indicating the reason for failure. 77 | pub struct FileOpen { 78 | path: CString, 79 | flags: i32, 80 | io: ReactorIo, 81 | } 82 | 83 | impl Future for FileOpen { 84 | type Output = Result; 85 | 86 | fn poll( 87 | self: std::pin::Pin<&mut Self>, 88 | cx: &mut std::task::Context<'_>, 89 | ) -> std::task::Poll { 90 | let this = unsafe { self.get_unchecked_mut() }; 91 | 92 | this.io 93 | .submit_or_get_result(|| { 94 | ( 95 | opcode::OpenAt::new(types::Fd(libc::AT_FDCWD), this.path.as_ptr()) 96 | .flags(this.flags) 97 | .mode(0o777) 98 | .build(), 99 | cx.waker().clone(), 100 | ) 101 | }) 102 | .map(|x| { 103 | x.map(|x| File { 104 | inner: unsafe { OwnedFd::from_raw_fd(x) }, 105 | }) 106 | }) 107 | } 108 | } 109 | 110 | impl Future for Mkdir { 111 | type Output = Result<()>; 112 | 113 | fn poll( 114 | self: std::pin::Pin<&mut Self>, 115 | cx: &mut std::task::Context<'_>, 116 | ) -> std::task::Poll { 117 | let this = unsafe { self.get_unchecked_mut() }; 118 | 119 | this.io 120 | .submit_or_get_result(|| { 121 | ( 122 | opcode::MkDirAt::new(types::Fd(libc::AT_FDCWD), this.path.as_ptr()) 123 | .mode(0o777) 124 | .build(), 125 | cx.waker().clone(), 126 | ) 127 | }) 128 | .map(|x| x.map(|_| ())) 129 | } 130 | } 131 | impl File { 132 | /// Attempt to open an existing file. 133 | /// 134 | /// This function takes a path to an already existant path and returns a 135 | /// future which attempts to open it in read/write mode. If the path does 136 | /// not exist `.await`ing the returned [FileOpen] future will yield an 137 | /// error. 138 | pub fn open(path: impl AsRef) -> FileOpen { 139 | FileOpen { 140 | path: CString::new(path.as_ref().as_os_str().as_encoded_bytes()).unwrap(), 141 | flags: O_RDWR, 142 | io: Reactor::new_io(), 143 | } 144 | } 145 | 146 | /// Attempt to create a new file. 147 | /// 148 | /// This function takes a path and returns a future which attempts to create 149 | /// it if it does not exist. If the path already exists, the file is opened. 150 | /// In both cases, the file is opened in read/write mode. 151 | pub fn create(path: impl AsRef) -> FileOpen { 152 | FileOpen { 153 | path: CString::new(path.as_ref().as_os_str().as_encoded_bytes()).unwrap(), 154 | flags: O_RDWR | O_CREAT, 155 | io: Reactor::new_io(), 156 | } 157 | } 158 | 159 | /// Attempt to create a new directory. 160 | /// 161 | /// This function takes a path and returns a future which attempts to create 162 | /// the specified directory. If the path is relative, the base path is the 163 | /// CWD of the program. 164 | pub fn mkdir(path: impl AsRef) -> Mkdir { 165 | Mkdir { 166 | io: Reactor::new_io(), 167 | path: CString::new(path.as_ref().as_os_str().as_encoded_bytes()).unwrap(), 168 | } 169 | } 170 | } 171 | 172 | impl AsyncRead for File { 173 | fn read(&mut self, buf: &mut [u8]) -> impl Future> { 174 | AsyncReader { 175 | fd: self.inner.as_fd(), 176 | buf, 177 | io: Reactor::new_io(), 178 | seekable: true, 179 | } 180 | } 181 | } 182 | 183 | impl AsyncWrite for File { 184 | fn write(&mut self, buf: &[u8]) -> impl Future> { 185 | AsyncWriter { 186 | fd: self.inner.as_fd(), 187 | buf, 188 | io: Reactor::new_io(), 189 | seekable: true, 190 | } 191 | } 192 | } 193 | 194 | impl Seek for File { 195 | fn seek(&mut self, pos: io::SeekFrom) -> Result { 196 | let (off, whence) = match pos { 197 | io::SeekFrom::Start(off) => (off as i64, libc::SEEK_SET), 198 | io::SeekFrom::End(off) => (off, libc::SEEK_END), 199 | io::SeekFrom::Current(off) => (off, libc::SEEK_CUR), 200 | }; 201 | 202 | let res = unsafe { libc::lseek(self.inner.as_raw_fd(), off, whence) }; 203 | 204 | if res == -1 { 205 | Err(std::io::Error::last_os_error()) 206 | } else { 207 | Ok(res as u64) 208 | } 209 | } 210 | } 211 | 212 | #[cfg(test)] 213 | mod tests { 214 | use std::io::Seek; 215 | 216 | use assert_fs::{ 217 | assert::PathAssert, 218 | prelude::{FileWriteStr, PathChild}, 219 | TempDir, 220 | }; 221 | 222 | use crate::{ 223 | futures::{read::AsyncRead, write::AsyncWrite}, 224 | task::Executor, 225 | }; 226 | 227 | #[test] 228 | fn simple_write() { 229 | let dir = TempDir::new().unwrap(); 230 | let child = dir.child("test.txt"); 231 | let child_path = child.to_path_buf(); 232 | 233 | Executor::block_on(async move { 234 | let mut f = super::File::create(child_path).await.unwrap(); 235 | f.write("Hello, world!".as_bytes()).await.unwrap() 236 | }); 237 | 238 | child.assert("Hello, world!"); 239 | } 240 | 241 | #[test] 242 | fn simple_read() { 243 | let dir = TempDir::new().unwrap(); 244 | let child = dir.child("test.txt"); 245 | child.write_str("Hello, async!").unwrap(); 246 | 247 | let child_path = child.to_path_buf(); 248 | 249 | Executor::block_on(async move { 250 | let mut f = super::File::open(child_path).await.unwrap(); 251 | let mut buf = [0; 1024]; 252 | let len = f.read(&mut buf).await.unwrap(); 253 | 254 | assert_eq!(&buf[..len], "Hello, async!".as_bytes()); 255 | }); 256 | } 257 | 258 | #[test] 259 | fn no_file_open_error() { 260 | let dir = TempDir::new().unwrap(); 261 | let child = dir.child("test.txt"); 262 | let child_path = child.to_path_buf(); 263 | 264 | Executor::block_on(async move { 265 | let f = super::File::open(child_path).await; 266 | 267 | assert!(f.is_err()); 268 | }); 269 | } 270 | 271 | #[test] 272 | fn create_on_existing_file() { 273 | let dir = TempDir::new().unwrap(); 274 | let child = dir.child("test.txt"); 275 | child.write_str("data").unwrap(); 276 | let child_path = child.to_path_buf(); 277 | 278 | Executor::block_on(async move { 279 | let f = super::File::create(child_path).await; 280 | 281 | assert!(f.is_ok()); 282 | }); 283 | 284 | child.assert("data"); 285 | } 286 | 287 | #[test] 288 | fn consecutive_writes() { 289 | let dir = TempDir::new().unwrap(); 290 | let child = dir.child("test.txt"); 291 | let child_path = child.to_path_buf(); 292 | 293 | Executor::block_on(async move { 294 | let mut f = super::File::create(child_path).await.unwrap(); 295 | 296 | f.write("Hello".as_bytes()).await.unwrap(); 297 | 298 | f.write("ABCD".as_bytes()).await.unwrap(); 299 | }); 300 | 301 | child.assert("HelloABCD"); 302 | } 303 | 304 | #[test] 305 | fn consecutive_reads() { 306 | let dir = TempDir::new().unwrap(); 307 | let child = dir.child("test.txt"); 308 | child.write_str("abcdef").unwrap(); 309 | let child_path = child.to_path_buf(); 310 | 311 | Executor::block_on(async move { 312 | let mut buf = [0; 2]; 313 | let mut f = super::File::open(child_path).await.unwrap(); 314 | 315 | f.read(&mut buf).await.unwrap(); 316 | assert_eq!(buf, "ab".as_bytes()); 317 | 318 | f.read(&mut buf).await.unwrap(); 319 | assert_eq!(buf, "cd".as_bytes()); 320 | 321 | f.read(&mut buf).await.unwrap(); 322 | assert_eq!(buf, "ef".as_bytes()); 323 | }); 324 | } 325 | 326 | #[test] 327 | fn simple_seek() { 328 | let dir = TempDir::new().unwrap(); 329 | let child = dir.child("test.txt"); 330 | child.write_str("abcdef").unwrap(); 331 | let child_path = child.to_path_buf(); 332 | 333 | Executor::block_on(async move { 334 | let mut buf = [0; 2]; 335 | let mut f = super::File::open(child_path).await.unwrap(); 336 | 337 | f.read(&mut buf).await.unwrap(); 338 | assert_eq!(buf, "ab".as_bytes()); 339 | 340 | f.read(&mut buf).await.unwrap(); 341 | assert_eq!(buf, "cd".as_bytes()); 342 | 343 | f.read(&mut buf).await.unwrap(); 344 | assert_eq!(buf, "ef".as_bytes()); 345 | 346 | f.seek(std::io::SeekFrom::Current(-4)).unwrap(); 347 | 348 | f.read(&mut buf).await.unwrap(); 349 | assert_eq!(buf, "cd".as_bytes()); 350 | 351 | f.read(&mut buf).await.unwrap(); 352 | assert_eq!(buf, "ef".as_bytes()); 353 | }); 354 | } 355 | 356 | #[test] 357 | fn simple_mkdir() { 358 | let dir = TempDir::new().unwrap(); 359 | let dir_path = dir.to_path_buf(); 360 | 361 | Executor::block_on(async move { 362 | super::File::mkdir(dir_path.join("test_dir")).await.unwrap(); 363 | }); 364 | 365 | assert!(dir.child("test_dir").is_dir()); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/futures/mod.rs: -------------------------------------------------------------------------------- 1 | //! Future sub-modules. 2 | //! 3 | //! The `futures` module serves as the foundation for various types of futures 4 | //! used within the `trale` executor. It encapsulates a collection of 5 | //! sub-modules, each implementing a different kind of future. These futures are 6 | //! crucial building blocks for the system, as they represent computations that 7 | //! the reactor can sleep on while waiting for events or conditions to change. 8 | //! 9 | //! Each future type is designed to handle specific aspects of asynchronous 10 | //! execution, such as file descriptor monitoring, timers, or inter-task 11 | //! synchronization. The reactor interacts with these futures to determine when 12 | //! tasks can make progress, allowing the executor to efficiently manage 13 | //! multiple tasks concurrently. 14 | //! 15 | //! The following sub-modules are exposed by the `futures` module: 16 | //! 17 | //! - `event`: Provides futures for inter-task event signaling. 18 | //! - `fs`: Provides futures for interacting with the\ filesystem. 19 | //! - `mutex`: Implements futures for task synchronization using a mutex-like primitive. 20 | //! - `read`: Implements futures for reading from non-blocking file descriptors. 21 | //! - `tcp`: Provides futures for handling TCP socket operations. 22 | //! - `timer`: Implements futures for timer-based tasks using `timerfd`. 23 | //! - `udp`: Provides futures for handling UDP socket operations. 24 | //! - `write`: Implements futures for writing to non-blocking file descriptors. 25 | //! 26 | //! Together, these futures form the core of the `trale` executor's 27 | //! functionality, enabling the reactor to monitor and interact with various 28 | //! asynchronous operations. 29 | pub mod event; 30 | pub mod fs; 31 | pub mod mutex; 32 | pub mod read; 33 | mod sock_addr; 34 | pub mod tcp; 35 | pub mod timer; 36 | pub mod udp; 37 | pub mod write; 38 | -------------------------------------------------------------------------------- /src/futures/mutex.rs: -------------------------------------------------------------------------------- 1 | //! ### Async Mutexes 2 | //! 3 | //! This module provides **cross-thread, non-blocking mutexes** for 4 | //! synchronization in asynchronous contexts. Unlike traditional mutexes, when a 5 | //! `Mutex` is already locked, an attempt to acquire it will **not block** the 6 | //! executor. Instead, the task will yield, allowing other tasks to continue 7 | //! executing. Once the mutex is unlocked, the waiting task will be resumed. 8 | //! 9 | //! The API is designed to be as close to `std::sync::Mutex` as possible, with 10 | //! some differences to support asynchronous operation. 11 | //! 12 | //! #### Example 13 | //! 14 | //! ```rust 15 | //! use trale::task::Executor; 16 | //! use std::sync::Arc; 17 | //! use trale::futures::mutex::Mutex; 18 | //! use trale::futures::timer::Timer; 19 | //! use std::time::Duration; 20 | //! use std::thread; 21 | //! 22 | //! let cell = Arc::new(Mutex::new(0).unwrap()); 23 | //! 24 | //! { 25 | //! let cell = cell.clone(); 26 | //! Executor::spawn(async move { 27 | //! let mut cell = cell.lock().await; 28 | //! Timer::sleep(Duration::from_secs(1)).unwrap().await; 29 | //! *cell += 1; 30 | //! }); 31 | //! } 32 | //! 33 | //! { 34 | //! let cell = cell.clone(); 35 | //! Executor::spawn(async move { 36 | //! Timer::sleep(Duration::from_millis(100)).unwrap().await; 37 | //! *cell.lock().await += 1; 38 | //! }); 39 | //! }; 40 | //! 41 | //! Executor::run(); 42 | //! Executor::block_on(async move { assert_eq!(*cell.lock().await, 2); }); 43 | //! ``` 44 | //! In this example: 45 | //! 46 | //! 1. We create a shared Mutex wrapped in an Arc to allow safe concurrent 47 | //! access across threads. 48 | //! 2. We spawn two asynchronous tasks: 49 | //! - The first task locks the mutex, then sleeps for 1 second before 50 | //! modifying the value inside the mutex. 51 | //! - The second task sleeps for 100 milliseconds, then locks the mutex and 52 | //! increments the value. 53 | //! 3. By using async mutexes, the tasks can proceed without blocking the 54 | //! executor, allowing the tasks to run concurrently. 55 | //! 4. After both tasks finish, we assert that the value inside the mutex has 56 | //! been incremented correctly. 57 | //! 58 | //! Without an async-aware mutex, if one task holds the lock, the other would 59 | //! block the thread, causing a deadlock in the main thread because the async 60 | //! runtime wouldn't be able to progress. 61 | use super::event::Event; 62 | use std::{ 63 | cell::UnsafeCell, 64 | ops::{Deref, DerefMut}, 65 | }; 66 | 67 | /// An async-aware mutex 68 | /// 69 | /// See the [module-level documentation](self) for more information. 70 | pub struct Mutex { 71 | evt: Event, 72 | obj: UnsafeCell, 73 | } 74 | 75 | unsafe impl Send for Mutex {} 76 | unsafe impl Sync for Mutex {} 77 | 78 | /// A lock held by a task. 79 | /// 80 | /// The `MutexGuard` type represents a **locked** state of a `Mutex`. It is 81 | /// returned when a task successfully locks the mutex via [Mutex::lock], and it 82 | /// automatically releases the lock when it is dropped. 83 | /// 84 | /// - The `MutexGuard` ensures that the lock is released as soon as the guard 85 | /// goes out of scope, preventing accidental deadlocks and making sure the mutex 86 | /// is unlocked when no longer needed. 87 | /// - It implements `DerefMut`, so you can mutate the data inside the mutex 88 | /// directly through the guard. 89 | pub struct LockGuard<'a, T> { 90 | mtx: &'a Mutex, 91 | } 92 | 93 | impl Mutex { 94 | /// Create a new mutex by taking an initial value of an object. If the mutex 95 | /// fails to be created, then the value is dropped and an error returned. If 96 | /// the mutex was created, then the mutex which wraps the object is 97 | /// returned. 98 | pub fn new(obj: T) -> std::io::Result { 99 | let evt = Event::new()?; 100 | evt.notify_one()?; 101 | 102 | Ok(Self { 103 | evt, 104 | obj: UnsafeCell::new(obj), 105 | }) 106 | } 107 | 108 | /// Attempt to lock the mutex. This function should be `.await`ex to allow 109 | /// blocking if the mutex is already locked. If the mutex isn't locked, then 110 | /// the [LockGuard] is returned without yielding. If the mutex is locked, 111 | /// then the task is put to sleep and will be rescheduled by the run-time 112 | /// once the mutex has been unlocked by another task. 113 | pub async fn lock(&self) -> LockGuard { 114 | let mut evt = self.evt.clone(); 115 | evt.wait().await.unwrap(); 116 | LockGuard { mtx: self } 117 | } 118 | } 119 | 120 | impl Deref for LockGuard<'_, T> { 121 | type Target = T; 122 | 123 | fn deref(&self) -> &Self::Target { 124 | unsafe { &*self.mtx.obj.get() } 125 | } 126 | } 127 | 128 | impl DerefMut for LockGuard<'_, T> { 129 | fn deref_mut(&mut self) -> &mut Self::Target { 130 | unsafe { &mut *self.mtx.obj.get() } 131 | } 132 | } 133 | 134 | impl Drop for LockGuard<'_, T> { 135 | fn drop(&mut self) { 136 | self.mtx.evt.notify_one().unwrap() 137 | } 138 | } 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use super::Mutex; 143 | use crate::{futures::timer::Timer, task::Executor}; 144 | use anyhow::Result; 145 | use std::{sync::Arc, time::Duration}; 146 | 147 | #[test] 148 | fn simple() -> Result<()> { 149 | let v = Arc::new(Mutex::new(vec![0u8])?); 150 | let v2 = v.clone(); 151 | 152 | Executor::block_on(async move { 153 | let mut lock = v.lock().await; 154 | 155 | let v2 = v.clone(); 156 | 157 | let t2 = Executor::spawn(async move { 158 | v2.lock().await.push(2); 159 | }); 160 | Timer::sleep(Duration::from_millis(250)).unwrap().await; 161 | lock.push(1); 162 | 163 | drop(lock); 164 | 165 | t2.await; 166 | }); 167 | 168 | assert_eq!(Arc::into_inner(v2).unwrap().obj.into_inner(), vec![0, 1, 2]); 169 | 170 | Ok(()) 171 | } 172 | 173 | #[test] 174 | fn multiple_locks() -> Result<()> { 175 | let vv = Arc::new(Mutex::new(vec![0u8])?); 176 | let v2 = vv.clone(); 177 | Executor::block_on(async move { 178 | let mut lock = vv.lock().await; 179 | let v = vv.clone(); 180 | 181 | let t2 = Executor::spawn(async move { 182 | v.lock().await.push(2); 183 | }); 184 | let v = vv.clone(); 185 | 186 | let t3 = Executor::spawn(async move { 187 | v.lock().await.push(2); 188 | }); 189 | let v = vv.clone(); 190 | 191 | let t4 = Executor::spawn(async move { 192 | v.lock().await.push(2); 193 | }); 194 | let v = vv.clone(); 195 | 196 | let t5 = Executor::spawn(async move { 197 | Timer::sleep(Duration::from_millis(500)).unwrap().await; 198 | v.lock().await.push(2); 199 | }); 200 | Timer::sleep(Duration::from_millis(500)).unwrap().await; 201 | lock.push(1); 202 | 203 | drop(lock); 204 | 205 | t2.await; 206 | t3.await; 207 | t5.await; 208 | t4.await; 209 | }); 210 | 211 | assert_eq!( 212 | Arc::into_inner(v2).unwrap().obj.into_inner(), 213 | vec![0, 1, 2, 2, 2, 2] 214 | ); 215 | 216 | Ok(()) 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/futures/read.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous reads. 2 | //! 3 | //! The `read` module provides functionality for performing asynchronous reads 4 | //! to non-blocking file descriptors (FDs). It allows tasks to attempt reading 5 | //! data from an FD without blocking the executor, enabling efficient handling 6 | //! of I/O operations in an async environment. 7 | //! 8 | //! Various futures within trale implement the `AsyncRead` trait, and those that 9 | //! do will allow you to await a call to [AsyncRead::read]. 10 | use std::{ 11 | future::Future, 12 | io, 13 | os::fd::{AsFd, AsRawFd}, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | }; 17 | 18 | use io_uring::{opcode, types}; 19 | 20 | use crate::reactor::ReactorIo; 21 | 22 | /// Asynchronous reads. 23 | /// 24 | /// All futures in trale that can be read from will implement this type. You can 25 | /// call the [AsyncRead::read] function to obtain a future which will complete 26 | /// once the requested read has finished (successfully or not). 27 | pub trait AsyncRead { 28 | /// Return a future that, when `.await`ed will block until the read has been 29 | /// successful or not. When successful, the number of bytes that were read 30 | /// is returned. Note that this might be *less* than the number of bytes in 31 | /// `buf`. 32 | fn read(&mut self, buf: &mut [u8]) -> impl Future>; 33 | } 34 | 35 | pub(crate) struct AsyncReader<'a, T: AsFd + Unpin> { 36 | pub(crate) fd: T, 37 | pub(crate) io: ReactorIo, 38 | pub(crate) buf: &'a mut [u8], 39 | pub(crate) seekable: bool, 40 | } 41 | 42 | impl Future for AsyncReader<'_, T> { 43 | type Output = io::Result; 44 | 45 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 46 | let this = unsafe { self.get_unchecked_mut() }; 47 | 48 | this.io 49 | .submit_or_get_result(|| { 50 | ( 51 | opcode::Read::new( 52 | types::Fd(this.fd.as_fd().as_raw_fd()), 53 | this.buf.as_mut_ptr(), 54 | this.buf.len() as _, 55 | ) 56 | .offset(if this.seekable { u64::MAX } else { 0 }) 57 | .build(), 58 | cx.waker().clone(), 59 | ) 60 | }) 61 | .map(|x| x.map(|x| x as _)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/futures/sock_addr.rs: -------------------------------------------------------------------------------- 1 | use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; 2 | 3 | pub union CSockAddrs { 4 | v4: libc::sockaddr_in, 5 | v6: libc::sockaddr_in6, 6 | } 7 | 8 | pub struct CSockAddr { 9 | pub addr: CSockAddrs, 10 | pub len: usize, 11 | } 12 | 13 | impl From for CSockAddr { 14 | fn from(value: SocketAddr) -> Self { 15 | match value { 16 | SocketAddr::V4(addr) => { 17 | let mut sin_addr: CSockAddrs = unsafe { std::mem::zeroed() }; 18 | sin_addr.v4.sin_family = libc::AF_INET as u16; 19 | sin_addr.v4.sin_addr.s_addr = libc::htonl(addr.ip().to_bits()); 20 | sin_addr.v4.sin_port = libc::htons(addr.port()); 21 | 22 | CSockAddr { 23 | addr: sin_addr, 24 | len: std::mem::size_of::(), 25 | } 26 | } 27 | SocketAddr::V6(addr) => { 28 | let mut sin_addr: CSockAddrs = unsafe { std::mem::zeroed() }; 29 | sin_addr.v6.sin6_family = libc::AF_INET6 as u16; 30 | sin_addr.v6.sin6_addr.s6_addr = addr.ip().octets(); 31 | sin_addr.v6.sin6_port = libc::htons(addr.port()); 32 | 33 | CSockAddr { 34 | addr: sin_addr, 35 | len: std::mem::size_of::(), 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | impl TryFrom<&CSockAddr> for SocketAddr { 43 | type Error = std::io::Error; 44 | 45 | fn try_from(value: &CSockAddr) -> Result { 46 | const V4_LEN: usize = std::mem::size_of::(); 47 | const V6_LEN: usize = std::mem::size_of::(); 48 | 49 | match value.len { 50 | V4_LEN => unsafe { 51 | Ok(SocketAddr::V4(SocketAddrV4::new( 52 | Ipv4Addr::from_bits(libc::ntohl(value.addr.v4.sin_addr.s_addr)), 53 | libc::ntohs(value.addr.v4.sin_port), 54 | ))) 55 | }, 56 | V6_LEN => unsafe { 57 | Ok(SocketAddr::V6(SocketAddrV6::new( 58 | Ipv6Addr::from_bits(u128::from_be_bytes(value.addr.v6.sin6_addr.s6_addr)), 59 | libc::ntohs(value.addr.v6.sin6_port), 60 | value.addr.v6.sin6_flowinfo, 61 | value.addr.v6.sin6_scope_id, 62 | ))) 63 | }, 64 | _ => Err(std::io::ErrorKind::InvalidData.into()), 65 | } 66 | } 67 | } 68 | 69 | impl CSockAddr { 70 | pub fn as_ptr(&self) -> *const libc::sockaddr { 71 | &self.addr as *const _ as *const _ 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/futures/tcp.rs: -------------------------------------------------------------------------------- 1 | //! Async TCP Sockets. 2 | //! 3 | //! This module implements an async version of [std::net::TcpStream]. It allows 4 | //! reception and transmission to and from TCP sockets to be defered by 5 | //! `.await`ing the return values of [AsyncRead::read] and 6 | //! [AsyncWrite::write]. 7 | //! 8 | //! # Example 9 | //! 10 | //! Here is an example of an async tcp echo server. 11 | //! ```no_run 12 | //! use std::net::Ipv4Addr; 13 | //! use trale::futures::tcp::TcpListener; 14 | //! use trale::futures::read::AsyncRead; 15 | //! use trale::futures::write::AsyncWrite; 16 | //! use tokio_stream::StreamExt; 17 | //! async { 18 | //! let mut listener = TcpListener::bind("0.0.0.0:8888")?; 19 | //! while let Some(Ok(mut sock)) = listener.next().await { 20 | //! let mut buf = [0u8; 1]; 21 | //! loop { 22 | //! let len = sock.read(&mut buf).await?; 23 | //! if len == 0 { 24 | //! return Ok(()); 25 | //! } 26 | //! sock.write(&buf).await?; 27 | //! } 28 | //! } 29 | //!# Ok::<(), std::io::Error>(()) 30 | //! }; 31 | //! ``` 32 | use std::{ 33 | future::Future, 34 | io::{self, ErrorKind}, 35 | net::{SocketAddr, ToSocketAddrs}, 36 | os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, 37 | pin::Pin, 38 | task::{Context, Poll}, 39 | }; 40 | 41 | use io_uring::{opcode, types}; 42 | use libc::{AF_INET, AF_INET6, SOCK_STREAM}; 43 | use tokio_stream::Stream; 44 | 45 | use crate::reactor::{MultishotReactorIo, Reactor, ReactorIo}; 46 | 47 | use super::{ 48 | read::{AsyncRead, AsyncReader}, 49 | sock_addr::CSockAddr, 50 | write::{AsyncWrite, AsyncWriter}, 51 | }; 52 | 53 | /// A socket that is listening for incoming connections. 54 | /// 55 | /// Use the [TcpListener::bind] function to obtain a listener that is ready to 56 | /// accept connections on the specified address. 57 | pub struct TcpListener { 58 | inner: OwnedFd, 59 | io: MultishotReactorIo, 60 | } 61 | 62 | fn mk_sock(addr: &SocketAddr) -> std::io::Result { 63 | let family = if addr.is_ipv4() { AF_INET } else { AF_INET6 }; 64 | 65 | let sock = unsafe { libc::socket(family, SOCK_STREAM, 0) }; 66 | 67 | if sock == -1 { 68 | Err(std::io::Error::last_os_error())?; 69 | } 70 | 71 | let sock = unsafe { OwnedFd::from_raw_fd(sock) }; 72 | 73 | Ok(sock) 74 | } 75 | 76 | impl TcpListener { 77 | /// Bind a new socket and return a listener. 78 | /// 79 | /// This function will create a new socket, bind it to one of the specified 80 | /// `addrs` and returna [TcpListener]. If binding to *all* of the specified 81 | /// addresses fails then the reason for failing to bind to the *last* 82 | /// address is returned. Otherwise, `.await` on the listener to await a new 83 | /// connection. 84 | /// 85 | /// When this future returns None, the listener will no long accept any 86 | /// further connections and should be dropped. 87 | pub fn bind(addrs: impl ToSocketAddrs) -> std::io::Result { 88 | let addrs = addrs.to_socket_addrs()?; 89 | let mut last_err = ErrorKind::NotFound.into(); 90 | 91 | for addr in addrs { 92 | let sock = mk_sock(&addr)?; 93 | let caddr: CSockAddr = addr.into(); 94 | 95 | if unsafe { libc::bind(sock.as_raw_fd(), caddr.as_ptr(), caddr.len as _) } == -1 { 96 | last_err = std::io::Error::last_os_error(); 97 | continue; 98 | } 99 | 100 | match unsafe { libc::listen(sock.as_raw_fd(), 1024) } { 101 | -1 => last_err = std::io::Error::last_os_error(), 102 | 0 => { 103 | return Ok(Self { 104 | inner: sock, 105 | io: Reactor::new_multishot_io(), 106 | }) 107 | } 108 | _ => unreachable!("listen() cannot return a value other than 0 or -1"), 109 | } 110 | } 111 | 112 | Err(last_err) 113 | } 114 | } 115 | 116 | impl Stream for TcpListener { 117 | type Item = std::io::Result; 118 | 119 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 120 | let this = self.get_mut(); 121 | 122 | this.io 123 | .submit_or_get_result(|| { 124 | ( 125 | opcode::AcceptMulti::new(types::Fd(this.inner.as_raw_fd())).build(), 126 | cx.waker().clone(), 127 | ) 128 | }) 129 | .map(|x| { 130 | x.map(|x| { 131 | x.map(|fd| TcpStream { 132 | inner: unsafe { OwnedFd::from_raw_fd(fd) }, 133 | }) 134 | }) 135 | }) 136 | } 137 | } 138 | 139 | /// A connection to a peer via the TCP. 140 | /// 141 | /// There are two ways to obtain a connection, either through 142 | /// [TcpListener::bind] to wait for incoming connections, or attempt to 143 | /// establish a new connection with [TcpStream::connect]. This type implements 144 | /// the [AsyncRead] and [AsyncWrite] traits to read and write from the socket. 145 | pub struct TcpStream { 146 | inner: OwnedFd, 147 | } 148 | 149 | impl TcpStream { 150 | /// Attempt to connect to a peer 151 | /// 152 | /// This function will attempt to connect to the specified addresses via 153 | /// TCP. If a more than one address is specified, then the first successful 154 | /// connection that was able to be established is returned. If connection to 155 | /// all the addresses failed, then the reason the failture for the *last* 156 | /// address is returned. 157 | pub async fn connect(addrs: A) -> std::io::Result { 158 | let addrs = addrs.to_socket_addrs()?; 159 | let mut last_err: std::io::Error = ErrorKind::InvalidData.into(); 160 | 161 | for addr in addrs { 162 | let sock = mk_sock(&addr)?; 163 | 164 | let connect = SockConnect { 165 | fd: sock.as_fd(), 166 | io: Reactor::new_io(), 167 | addr, 168 | }; 169 | 170 | match connect.await { 171 | Ok(()) => return Ok(Self { inner: sock }), 172 | Err(e) => last_err = e, 173 | } 174 | } 175 | 176 | Err(last_err) 177 | } 178 | } 179 | 180 | struct SockConnect<'fd> { 181 | fd: BorrowedFd<'fd>, 182 | io: ReactorIo, 183 | addr: SocketAddr, 184 | } 185 | 186 | impl Future for SockConnect<'_> { 187 | type Output = io::Result<()>; 188 | 189 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 190 | let addr: CSockAddr = self.addr.into(); 191 | let entry = 192 | opcode::Connect::new(types::Fd(self.fd.as_raw_fd()), addr.as_ptr(), addr.len as _); 193 | 194 | self.io 195 | .submit_or_get_result(|| (entry.build(), cx.waker().clone())) 196 | .map(|x| x.map(|_| ())) 197 | } 198 | } 199 | 200 | impl AsyncRead for TcpStream { 201 | fn read(&mut self, buf: &mut [u8]) -> impl Future> { 202 | AsyncReader { 203 | fd: self.inner.as_fd(), 204 | io: Reactor::new_io(), 205 | buf, 206 | seekable: false, 207 | } 208 | } 209 | } 210 | 211 | impl AsyncWrite for TcpStream { 212 | fn write(&mut self, buf: &[u8]) -> impl Future> { 213 | AsyncWriter { 214 | fd: self.inner.as_fd(), 215 | io: Reactor::new_io(), 216 | buf, 217 | seekable: false, 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/futures/timer.rs: -------------------------------------------------------------------------------- 1 | //! Async timer related futures. 2 | //! 3 | //! This module uses the Linux kernel's 4 | //! [timerfd](https://man7.org/linux/man-pages/man2/timerfd_create.2.html) 5 | //! facility to implement asynchronous timers. The main use-case for this is to 6 | //! put a task to sleep for a specific period of time. 7 | //! 8 | //! # Example 9 | //! Let's put a task to sleep for 2 seconds. 10 | //! ``` 11 | //! use trale::futures::timer::Timer; 12 | //! use trale::task::Executor; 13 | //! use std::time::{Duration, Instant}; 14 | //!# Executor::block_on( 15 | //! async { 16 | //! let now = Instant::now(); 17 | //! 18 | //! Timer::sleep(Duration::from_secs(2)).unwrap().await; 19 | //! 20 | //! assert!(now.elapsed() > Duration::from_secs(2)); 21 | //!# Ok::<(), std::io::Error>(()) 22 | //! } 23 | //!# ); 24 | //! ``` 25 | 26 | use std::{ 27 | future::Future, 28 | io::Result, 29 | marker::PhantomPinned, 30 | os::fd::{AsRawFd, FromRawFd, OwnedFd}, 31 | pin::Pin, 32 | ptr::null_mut, 33 | task::{Context, Poll}, 34 | time::{Duration, SystemTime}, 35 | }; 36 | 37 | use io_uring::{opcode, types}; 38 | use libc::{CLOCK_MONOTONIC, TFD_NONBLOCK}; 39 | 40 | use crate::reactor::{Reactor, ReactorIo}; 41 | 42 | /// Asynchronous timer. 43 | /// 44 | /// This structure is a future that will expire at some point in the future. It 45 | /// can be obtained via the [Timer::sleep] function. 46 | pub struct Timer { 47 | expiration: SystemTime, 48 | io: ReactorIo, 49 | buf: [u8; std::mem::size_of::()], 50 | fd: OwnedFd, 51 | _phantom: PhantomPinned, 52 | } 53 | 54 | impl Timer { 55 | /// Put the current task to sleep for the specified duration. 56 | /// 57 | /// This function returns a future, that when `.await`ed will suspend the 58 | /// execution of the current task until the specified duration has elapsed. 59 | /// At that point the runtime will queue the task for execution. Note that 60 | /// it is guaranteed that the task will be suspended for *at least* the 61 | /// specified duration; it could sleep for longer. 62 | pub fn sleep(d: Duration) -> Result { 63 | let expiration = SystemTime::now() + d; 64 | let timer = unsafe { libc::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK) }; 65 | 66 | if timer == -1 { 67 | return Err(std::io::Error::last_os_error()); 68 | } 69 | 70 | Ok(Self { 71 | expiration, 72 | io: Reactor::new_io(), 73 | buf: [0; std::mem::size_of::()], 74 | fd: unsafe { OwnedFd::from_raw_fd(timer) }, 75 | _phantom: PhantomPinned, 76 | }) 77 | } 78 | } 79 | 80 | impl Future for Timer { 81 | type Output = (); 82 | 83 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 84 | if SystemTime::now() > self.expiration { 85 | return Poll::Ready(()); 86 | } 87 | 88 | let this = unsafe { self.get_unchecked_mut() }; 89 | 90 | this.io 91 | .submit_or_get_result(|| { 92 | let expiration = this.expiration.duration_since(SystemTime::now()).unwrap(); 93 | let mut tspec = unsafe { std::mem::zeroed::() }; 94 | 95 | tspec.it_value.tv_sec = expiration.as_secs() as _; 96 | tspec.it_value.tv_nsec = expiration.subsec_nanos() as _; 97 | 98 | let ret = unsafe { 99 | libc::timerfd_settime(this.fd.as_raw_fd(), 0, &tspec as *const _, null_mut()) 100 | }; 101 | 102 | if ret == -1 { 103 | panic!("timerfd_settime returned error"); 104 | } 105 | 106 | ( 107 | opcode::Read::new( 108 | types::Fd(this.fd.as_raw_fd()), 109 | this.buf.as_mut_ptr(), 110 | this.buf.len() as _, 111 | ) 112 | .build(), 113 | cx.waker().clone(), 114 | ) 115 | }) 116 | .map(|_| ()) 117 | } 118 | } 119 | 120 | #[cfg(test)] 121 | mod tests { 122 | use std::time::{Duration, Instant}; 123 | 124 | use crate::task::Executor; 125 | 126 | use super::Timer; 127 | 128 | #[test] 129 | fn sleep_simple() { 130 | let before = Instant::now(); 131 | Executor::block_on(async { 132 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 133 | }); 134 | assert!(Instant::now() - before > Duration::from_millis(900)); 135 | } 136 | 137 | #[test] 138 | fn sleep_multiple_tasks() { 139 | Executor::block_on(async { 140 | let before = Instant::now(); 141 | let t1 = Executor::spawn(async { 142 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 143 | }); 144 | let t2 = Executor::spawn(async { 145 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 146 | }); 147 | let t3 = Executor::spawn(async { 148 | Timer::sleep(Duration::from_secs(2)).unwrap().await; 149 | }); 150 | 151 | t1.await; 152 | t2.await; 153 | assert!(Instant::now() - before > Duration::from_millis(900)); 154 | assert!(Instant::now() - before < Duration::from_millis(1100)); 155 | 156 | t3.await; 157 | assert!(Instant::now() - before > Duration::from_millis(1900)); 158 | assert!(Instant::now() - before < Duration::from_millis(2100)); 159 | }); 160 | } 161 | 162 | #[test] 163 | fn sleep_subtasks() { 164 | let before = Instant::now(); 165 | Executor::block_on(async move { 166 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 167 | assert!(Instant::now() - before > Duration::from_millis(900)); 168 | assert!(Instant::now() - before < Duration::from_millis(1100)); 169 | 170 | let t1 = Executor::spawn(async { 171 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 172 | }); 173 | let t2 = Executor::spawn(async { 174 | Timer::sleep(Duration::from_secs(1)).unwrap().await; 175 | }); 176 | 177 | t1.await; 178 | t2.await; 179 | assert!(Instant::now() - before > Duration::from_millis(1900)); 180 | assert!(Instant::now() - before < Duration::from_millis(2100)); 181 | }); 182 | assert!(Instant::now() - before > Duration::from_millis(1900)); 183 | assert!(Instant::now() - before < Duration::from_millis(2100)); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/futures/udp.rs: -------------------------------------------------------------------------------- 1 | //! Async UDP Sockets. 2 | //! 3 | //! This module implements an async version of [std::net::UdpSocket]. 4 | //! It allows reception and transmission to and from UDP sockets to 5 | //! be defered by `.await`ing the return values of 6 | //! [UdpSocket::recv_from] and [UdpSocket::send_to]. 7 | //! 8 | //! # Example 9 | //! 10 | //! Here is an example of an async UDP echo server. 11 | //! ```no_run 12 | //! use std::net::Ipv4Addr; 13 | //! use trale::futures::udp::UdpSocket; 14 | //! async { 15 | //! let mut sock = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 8888))?; 16 | //! let mut buf = [0u8; 1500]; 17 | //! loop { 18 | //! let (len, src) = sock.recv_from(&mut buf).await?; 19 | //! sock.send_to(&buf[..len], src).await?; 20 | //! } 21 | //!# Ok::<(), std::io::Error>(()) 22 | //! }; 23 | //! ``` 24 | 25 | use std::{ 26 | future::Future, 27 | io::Result, 28 | net::{SocketAddr, ToSocketAddrs, UdpSocket as StdUdpSocket}, 29 | os::fd::AsRawFd, 30 | pin::Pin, 31 | task::{Context, Poll}, 32 | }; 33 | 34 | use io_uring::{opcode, types}; 35 | use libc::{iovec, msghdr}; 36 | 37 | use crate::reactor::{Reactor, ReactorIo}; 38 | 39 | use super::sock_addr::CSockAddr; 40 | 41 | /// An async datagram socket. 42 | pub struct UdpSocket { 43 | inner: StdUdpSocket, 44 | } 45 | 46 | impl UdpSocket { 47 | /// Attempt to bind to the local address `A` and return a new 48 | /// [UdpSocket]. [Self::recv_from] and [Self::send_to] can then 49 | /// be called on this socket once bound. 50 | pub fn bind(addr: A) -> Result { 51 | let sock = StdUdpSocket::bind(addr)?; 52 | 53 | Ok(Self { inner: sock }) 54 | } 55 | 56 | /// Wait for reception of a datagram. 57 | /// 58 | /// # Example 59 | /// 60 | /// ```no_run 61 | /// use std::net::Ipv4Addr; 62 | /// use trale::futures::udp::UdpSocket; 63 | /// async { 64 | /// let mut sock = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?; 65 | /// let mut buf = [0u8; 1500]; 66 | /// let (len, src) = sock.recv_from(&mut buf).await?; 67 | ///# Ok::<(), std::io::Error>(()) 68 | /// }; 69 | /// ``` 70 | pub fn recv_from<'a, 'b>(&'a mut self, buf: &'b mut [u8]) -> RecvFrom<'a, 'b> { 71 | RecvFrom { 72 | sock: &self.inner, 73 | io: Reactor::new_io(), 74 | hdr: unsafe { std::mem::zeroed() }, 75 | iov: unsafe { std::mem::zeroed() }, 76 | csock: unsafe { std::mem::zeroed() }, 77 | buf, 78 | } 79 | } 80 | 81 | /// Wait for reception of a datagram. 82 | /// 83 | /// # Example 84 | /// 85 | /// ```no_run 86 | /// use std::net::Ipv4Addr; 87 | /// use trale::futures::udp::UdpSocket; 88 | /// async { 89 | /// let sock = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?; 90 | /// let buf = [0xad, 0xbe, 0xef]; 91 | /// sock.send_to(&buf, (Ipv4Addr::new(192, 168, 0, 1), 8080)).await?; 92 | ///# Ok::<(), std::io::Error>(()) 93 | /// }; 94 | /// ``` 95 | pub fn send_to<'a, 'b, A: ToSocketAddrs>( 96 | &'a self, 97 | buf: &'b [u8], 98 | target: A, 99 | ) -> SendTo<'a, 'b, A> { 100 | SendTo { 101 | sock: &self.inner, 102 | dst: target, 103 | io: Reactor::new_io(), 104 | buf, 105 | hdr: unsafe { std::mem::zeroed() }, 106 | csock: unsafe { std::mem::zeroed() }, 107 | iov: unsafe { std::mem::zeroed() }, 108 | } 109 | } 110 | } 111 | 112 | /// A future that receives data into a datagram socket, see 113 | /// [UdpSocket::recv_from]. 114 | pub struct RecvFrom<'a, 'b> { 115 | sock: &'a StdUdpSocket, 116 | io: ReactorIo, 117 | hdr: msghdr, 118 | iov: iovec, 119 | csock: CSockAddr, 120 | buf: &'b mut [u8], 121 | } 122 | 123 | impl Future for RecvFrom<'_, '_> { 124 | type Output = Result<(usize, SocketAddr)>; 125 | 126 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 127 | let this = unsafe { self.get_unchecked_mut() }; 128 | 129 | this.io 130 | .submit_or_get_result(|| { 131 | this.iov.iov_base = this.buf.as_mut_ptr() as *mut _; 132 | this.iov.iov_len = this.buf.len(); 133 | this.hdr.msg_iov = &mut this.iov as *mut _; 134 | this.hdr.msg_iovlen = 1; 135 | this.hdr.msg_name = &mut this.csock.addr as *mut _ as *mut _; 136 | this.hdr.msg_namelen = std::mem::size_of_val(&this.csock.addr) as _; 137 | 138 | ( 139 | opcode::RecvMsg::new(types::Fd(this.sock.as_raw_fd()), &mut this.hdr as *mut _) 140 | .build(), 141 | cx.waker().clone(), 142 | ) 143 | }) 144 | .map(|x| { 145 | let sz = x?; 146 | this.csock.len = this.hdr.msg_namelen as _; 147 | 148 | match <&CSockAddr as TryInto>::try_into(&this.csock) { 149 | Ok(addr) => Ok((sz as _, addr)), 150 | Err(e) => Err(e), 151 | } 152 | }) 153 | } 154 | } 155 | 156 | /// A future that send data from a datagram socket, see 157 | /// [UdpSocket::send_to]. 158 | pub struct SendTo<'a, 'b, A: ToSocketAddrs> { 159 | sock: &'a StdUdpSocket, 160 | dst: A, 161 | io: ReactorIo, 162 | hdr: msghdr, 163 | csock: CSockAddr, 164 | iov: iovec, 165 | buf: &'b [u8], 166 | } 167 | 168 | impl Future for SendTo<'_, '_, A> { 169 | type Output = Result; 170 | 171 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 172 | let this = unsafe { self.get_unchecked_mut() }; 173 | 174 | this.io 175 | .submit_or_get_result(|| { 176 | this.csock = this.dst.to_socket_addrs().unwrap().next().unwrap().into(); 177 | this.hdr.msg_namelen = this.csock.len as _; 178 | this.hdr.msg_name = &mut this.csock.addr as *mut _ as *mut _; 179 | this.iov.iov_base = this.buf.as_ptr() as *mut _; 180 | this.iov.iov_len = this.buf.len(); 181 | this.hdr.msg_iov = &mut this.iov as *mut _ as *mut _; 182 | this.hdr.msg_iovlen = 1; 183 | 184 | ( 185 | opcode::SendMsg::new(types::Fd(this.sock.as_raw_fd()), &this.hdr as *const _) 186 | .build(), 187 | cx.waker().clone(), 188 | ) 189 | }) 190 | .map(|x| x.map(|x| x as _)) 191 | } 192 | } 193 | 194 | #[cfg(test)] 195 | mod tests { 196 | use super::UdpSocket; 197 | use crate::task::Executor; 198 | use std::net::Ipv4Addr; 199 | 200 | #[test] 201 | fn send_recv() { 202 | Executor::block_on(async { 203 | let dst = (Ipv4Addr::LOCALHOST, 8086); 204 | let tx_sock = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).unwrap(); 205 | let mut rx_sock = UdpSocket::bind(dst).unwrap(); 206 | 207 | let task = Executor::spawn(async move { 208 | let mut buf = [0; 4]; 209 | rx_sock.recv_from(&mut buf).await.unwrap(); 210 | }); 211 | 212 | tx_sock 213 | .send_to(&0xdeadbeef_u32.to_le_bytes(), dst) 214 | .await 215 | .unwrap(); 216 | 217 | task.await; 218 | }); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/futures/write.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous writes. 2 | //! 3 | //! The `write` module provides functionality for performing asynchronous writes 4 | //! to non-blocking file descriptors (FDs). It allows tasks to attempt writing 5 | //! data to an FD without blocking the executor, enabling efficient handling of 6 | //! I/O operations in an async environment. 7 | //! 8 | //! Various futures within trale implement the `AsyncWrite` trait, and those 9 | //! that do will allow you to await a call to [AsyncWrite::write]. 10 | use std::{ 11 | future::Future, 12 | io, 13 | os::fd::{AsFd, AsRawFd}, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | }; 17 | 18 | use io_uring::{opcode, types}; 19 | 20 | use crate::reactor::ReactorIo; 21 | 22 | /// Asynchronous writes. 23 | /// 24 | /// All futures in trale that can be written to will implement this type. You 25 | /// can call the [AsyncWrite::write] function to obtain a future which will 26 | /// complete once the requested write has finished (successfully or not). 27 | pub trait AsyncWrite { 28 | /// Return a future that, when `.await`ed will block until the write has 29 | /// been successful or not. When successful, the number of bytes that were 30 | /// written is returned. Note that this might be *less* than the number of 31 | /// bytes in `buf`. 32 | fn write(&mut self, buf: &[u8]) -> impl Future>; 33 | } 34 | 35 | pub(crate) struct AsyncWriter<'a, T: AsFd + Unpin> { 36 | pub(crate) fd: T, 37 | pub(crate) io: ReactorIo, 38 | pub(crate) buf: &'a [u8], 39 | pub(crate) seekable: bool, 40 | } 41 | 42 | impl Future for AsyncWriter<'_, T> { 43 | type Output = io::Result; 44 | 45 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 46 | let this = unsafe { self.get_unchecked_mut() }; 47 | 48 | this.io 49 | .submit_or_get_result(|| { 50 | ( 51 | opcode::Write::new( 52 | types::Fd(this.fd.as_fd().as_raw_fd()), 53 | this.buf.as_ptr(), 54 | this.buf.len() as _, 55 | ) 56 | .offset(if this.seekable { u64::MAX } else { 0 }) 57 | .build(), 58 | cx.waker().clone(), 59 | ) 60 | }) 61 | .map(|x| x.map(|x| x as _)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # `trale`: Tiny Rust Async Linux Executor 2 | //! 3 | //! This project implements a minimalistic asynchronous Rust executor, written 4 | //! in as few lines as possible. Its primary goal is to serve as an educational 5 | //! resource for those studying Rust's async ecosystem. It provides a *real* 6 | //! executor capable of running multiple async tasks on a single thread, 7 | //! showcasing a simple yet functional concrete implementation. 8 | //! 9 | //! To achieve this, `trale` tightly integrates with Linux's `io_uring` 10 | //! interface, opting for minimal abstractions to prioritize performance. While 11 | //! it sacrifices some abstraction in favor of efficiency, **correctness** is 12 | //! not compromised. 13 | //! 14 | //! For information about spawning and managing tasks, refer to the [task] 15 | //! module. To see what futures are provided, see the [futures] module. 16 | //! 17 | //! ## Example 18 | //! 19 | //! This is a simple example of printing out `Hello, world!` based on timers: 20 | //! 21 | //! ``` 22 | //! use trale::futures::timer::Timer; 23 | //! use trale::task::Executor; 24 | //! use std::time::{Duration, Instant}; 25 | //! Executor::spawn( async { 26 | //! Timer::sleep(Duration::from_secs(1)).unwrap().await; 27 | //! println!("Hello, "); 28 | //! }); 29 | //! Executor::spawn( async { 30 | //! Timer::sleep(Duration::from_secs(2)).unwrap().await; 31 | //! println!("World!"); 32 | //! }); 33 | //! Executor::run(); 34 | //! ``` 35 | pub mod futures; 36 | pub(crate) mod reactor; 37 | pub mod task; 38 | -------------------------------------------------------------------------------- /src/reactor/mod.rs: -------------------------------------------------------------------------------- 1 | use std::task::Waker; 2 | 3 | use uring::{MultishotUringIo, OneshotUringIo, ReactorUring}; 4 | 5 | mod uring; 6 | 7 | pub type ReactorIo = OneshotUringIo; 8 | pub type MultishotReactorIo = MultishotUringIo; 9 | 10 | pub(crate) struct Reactor {} 11 | 12 | thread_local! { 13 | static REACTOR: ReactorUring = ReactorUring::new(); 14 | } 15 | 16 | impl Reactor { 17 | pub fn new_io() -> ReactorIo { 18 | REACTOR.with(|r| r.new_oneshot_io()) 19 | } 20 | 21 | pub fn new_multishot_io() -> MultishotReactorIo { 22 | REACTOR.with(|r| r.new_multishot_io()) 23 | } 24 | 25 | pub fn react() { 26 | REACTOR.with(|r| { 27 | for waker in r.react() { 28 | waker.wake(); 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/reactor/uring.rs: -------------------------------------------------------------------------------- 1 | pub(crate) use io::{multishot::MultishotUringIo, oneshot::OneshotUringIo}; 2 | use io_uring::{cqueue, squeue, CompletionQueue, IoUring}; 3 | use result::RingResults; 4 | use slab::Slab; 5 | use std::{ 6 | cell::{RefCell, RefMut}, 7 | rc::Rc, 8 | }; 9 | 10 | mod io; 11 | mod result; 12 | 13 | pub struct ReactorUring { 14 | inner: Rc>>, 15 | } 16 | 17 | impl ReactorUring { 18 | pub fn new() -> Self { 19 | Self { 20 | inner: Rc::new(RefCell::new(ReactorInner::new())), 21 | } 22 | } 23 | 24 | pub fn new_oneshot_io(&self) -> OneshotUringIo { 25 | OneshotUringIo::new(self.inner.clone()) 26 | } 27 | 28 | pub fn new_multishot_io(&self) -> MultishotUringIo { 29 | MultishotUringIo::new(self.inner.clone()) 30 | } 31 | 32 | pub fn react(&self) -> IoCompletionIter<'_, T> { 33 | let mut borrow = self.inner.borrow_mut(); 34 | 35 | borrow.uring.submit_and_wait(1).unwrap(); 36 | 37 | // SAFETY: This object lives along side both the `objs` and `results` 38 | // RefMuts. Therefore, `borrow` will remained borrowed for the lifetime 39 | // of both `objs` and `results` making the change to `'a` safe. 40 | let compl_queue = unsafe { 41 | std::mem::transmute::, io_uring::CompletionQueue<'_>>( 42 | borrow.uring.completion(), 43 | ) 44 | }; 45 | 46 | IoCompletionIter { 47 | compl_queue, 48 | ring: borrow, 49 | } 50 | } 51 | } 52 | 53 | pub(crate) struct ReactorInner { 54 | uring: IoUring, 55 | pending: Slab>, 56 | results: RingResults, 57 | } 58 | 59 | #[derive(Clone, Copy)] 60 | enum IoKind { 61 | Oneshot, 62 | Multi, 63 | } 64 | 65 | #[derive(Clone)] 66 | struct PendingIo { 67 | assoc_obj: T, 68 | result_slab_idx: usize, 69 | kind: IoKind, 70 | } 71 | 72 | impl ReactorInner { 73 | fn new() -> Self { 74 | Self { 75 | uring: IoUring::new(1024).unwrap(), 76 | pending: Slab::new(), 77 | results: RingResults::new(), 78 | } 79 | } 80 | 81 | fn submit_io(&mut self, entry: squeue::Entry, obj: T, kind: IoKind) -> (u64, usize) { 82 | let result_slab_idx = match kind { 83 | IoKind::Oneshot => self.results.get_oneshot().create_slot(), 84 | IoKind::Multi => self.results.get_multishot().create_slot(), 85 | }; 86 | 87 | let slot = self.pending.insert(PendingIo { 88 | assoc_obj: obj, 89 | result_slab_idx, 90 | kind, 91 | }); 92 | 93 | unsafe { 94 | self.uring 95 | .submission() 96 | .push(&entry.user_data(slot as u64)) 97 | .unwrap(); 98 | } 99 | 100 | (slot as u64, result_slab_idx) 101 | } 102 | } 103 | 104 | pub struct IoCompletionIter<'a, T: Clone> { 105 | compl_queue: CompletionQueue<'a>, 106 | ring: RefMut<'a, ReactorInner>, 107 | } 108 | 109 | impl Iterator for IoCompletionIter<'_, T> { 110 | type Item = T; 111 | 112 | fn next(&mut self) -> Option { 113 | let entry = self.compl_queue.next()?; 114 | 115 | let pending_io = self 116 | .ring 117 | .pending 118 | .get_mut(entry.user_data() as usize) 119 | .unwrap() 120 | .clone(); 121 | 122 | match pending_io.kind { 123 | IoKind::Oneshot => { 124 | self.ring 125 | .results 126 | .get_oneshot() 127 | .set_result(entry.result(), pending_io.result_slab_idx); 128 | self.ring.pending.remove(entry.user_data() as usize); 129 | } 130 | IoKind::Multi => { 131 | let results = self.ring.results.get_multishot(); 132 | results.push_result(entry.result(), pending_io.result_slab_idx); 133 | if !cqueue::more(entry.flags()) { 134 | results.set_finished(pending_io.result_slab_idx); 135 | } 136 | } 137 | } 138 | 139 | Some(pending_io.assoc_obj) 140 | } 141 | } 142 | 143 | #[cfg(test)] 144 | mod tests { 145 | use std::{ 146 | os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd}, 147 | task::Poll, 148 | }; 149 | 150 | use io_uring::{opcode, types}; 151 | use libc::{AF_LOCAL, SOCK_NONBLOCK, SOCK_STREAM}; 152 | 153 | use super::ReactorUring; 154 | 155 | fn write(fd: impl AsFd, buf: &[u8]) { 156 | let ret = unsafe { 157 | libc::write( 158 | fd.as_fd().as_raw_fd(), 159 | buf.as_ptr() as *const _, 160 | buf.len() as _, 161 | ) 162 | }; 163 | 164 | if ret == -1 { 165 | panic!("write failed"); 166 | } 167 | } 168 | 169 | fn read(fd: impl AsFd, buf: &mut [u8]) { 170 | let ret = unsafe { 171 | libc::read( 172 | fd.as_fd().as_raw_fd(), 173 | buf.as_mut_ptr() as *mut _, 174 | buf.len() as _, 175 | ) 176 | }; 177 | 178 | if ret == -1 { 179 | panic!("write failed"); 180 | } 181 | } 182 | 183 | fn run_test(f: impl FnOnce(OwnedFd, OwnedFd, &mut ReactorUring)) { 184 | let mut fds = [0, 0]; 185 | let ret = 186 | unsafe { libc::socketpair(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK, 0, fds.as_mut_ptr()) }; 187 | 188 | if ret == -1 { 189 | panic!("Pipe failed"); 190 | } 191 | 192 | let a = unsafe { OwnedFd::from_raw_fd(fds[0]) }; 193 | let b = unsafe { OwnedFd::from_raw_fd(fds[1]) }; 194 | let mut uring = ReactorUring::new(); 195 | 196 | f(a, b, &mut uring); 197 | 198 | assert!(uring.inner.borrow().results.is_empty()); 199 | } 200 | 201 | #[test] 202 | fn single_wakeup_read() { 203 | run_test(|a, b, uring| { 204 | let mut buf = [0]; 205 | 206 | let mut io = uring.new_oneshot_io(); 207 | let result = io.submit_or_get_result(|| { 208 | ( 209 | opcode::Read::new(types::Fd(a.as_raw_fd()), buf.as_mut_ptr(), 1).build(), 210 | 10, 211 | ) 212 | }); 213 | 214 | assert!(matches!(result, Poll::Pending)); 215 | 216 | let t1 = std::thread::spawn(move || { 217 | write(b, &[2]); 218 | }); 219 | 220 | let mut objs = uring.react(); 221 | 222 | assert_eq!(objs.next(), Some(10)); 223 | assert_eq!(objs.next(), None); 224 | 225 | drop(objs); 226 | 227 | let result = 228 | io.submit_or_get_result(|| panic!("Should not be called, as result will be ready")); 229 | 230 | assert!(matches!(result, Poll::Ready(Ok(1)))); 231 | 232 | t1.join().unwrap(); 233 | }); 234 | } 235 | 236 | #[test] 237 | fn io_dropped_before_react_cleanup() { 238 | run_test(|a, b, uring| { 239 | let mut buf = [0]; 240 | 241 | let mut io = uring.new_oneshot_io(); 242 | assert!(matches!( 243 | io.submit_or_get_result(|| { 244 | ( 245 | opcode::Read::new(types::Fd(a.as_raw_fd()), buf.as_mut_ptr(), 1).build(), 246 | 10, 247 | ) 248 | }), 249 | Poll::Pending 250 | )); 251 | 252 | drop(io); 253 | 254 | let t1 = std::thread::spawn(move || { 255 | write(b, &[2]); 256 | }); 257 | 258 | let mut objs = uring.react(); 259 | 260 | assert_eq!(objs.next(), Some(10)); 261 | assert_eq!(objs.next(), None); 262 | 263 | t1.join().unwrap(); 264 | }); 265 | } 266 | 267 | #[test] 268 | fn single_wakeup_write() { 269 | run_test(|a, b, uring| { 270 | let buf = [0]; 271 | 272 | let mut io = uring.new_oneshot_io(); 273 | let result = io.submit_or_get_result(|| { 274 | ( 275 | opcode::Write::new(types::Fd(a.as_raw_fd()), buf.as_ptr(), buf.len() as _) 276 | .build(), 277 | 20, 278 | ) 279 | }); 280 | 281 | assert!(matches!(result, Poll::Pending)); 282 | 283 | let t1 = std::thread::spawn(move || { 284 | let mut buf = [10]; 285 | read(b, &mut buf); 286 | assert_eq!(buf, [0]); 287 | }); 288 | 289 | let mut objs = uring.react(); 290 | 291 | assert_eq!(objs.next(), Some(20)); 292 | assert_eq!(objs.next(), None); 293 | 294 | drop(objs); 295 | 296 | let result = 297 | io.submit_or_get_result(|| panic!("Should not be called, as result will be ready")); 298 | 299 | assert!(matches!(result, Poll::Ready(Ok(1)))); 300 | 301 | t1.join().unwrap(); 302 | }); 303 | } 304 | 305 | #[test] 306 | fn multi_events_same_fd_read() { 307 | run_test(|a, b, uring| { 308 | let mut buf = [0, 0]; 309 | 310 | let mut io1 = uring.new_oneshot_io(); 311 | assert!(matches!( 312 | io1.submit_or_get_result(|| { 313 | ( 314 | opcode::Read::new(types::Fd(a.as_raw_fd()), buf.as_mut_ptr(), 1).build(), 315 | 10, 316 | ) 317 | }), 318 | Poll::Pending 319 | )); 320 | 321 | let mut io2 = uring.new_oneshot_io(); 322 | assert!(matches!( 323 | io2.submit_or_get_result(|| { 324 | ( 325 | opcode::Read::new(types::Fd(a.as_raw_fd()), buf.as_mut_ptr(), 1).build(), 326 | 20, 327 | ) 328 | }), 329 | Poll::Pending 330 | )); 331 | 332 | let t1 = std::thread::spawn(move || { 333 | write(b, &[0xde, 0xad]); 334 | }); 335 | 336 | let objs: Vec<_> = uring.react().collect(); 337 | 338 | assert_eq!(objs.len(), 2); 339 | assert!(objs.contains(&10)); 340 | assert!(objs.contains(&20)); 341 | 342 | assert!(matches!( 343 | io1.submit_or_get_result(|| panic!("Should not be called")), 344 | Poll::Ready(Ok(1)) 345 | )); 346 | assert!(matches!( 347 | io2.submit_or_get_result(|| panic!("Should not be called")), 348 | Poll::Ready(Ok(1)) 349 | )); 350 | assert_eq!(buf, [0xad, 0]); 351 | 352 | t1.join().unwrap(); 353 | }); 354 | } 355 | 356 | #[test] 357 | fn multi_events_same_fd_write() { 358 | run_test(|a, b, uring| { 359 | let buf = [0xbe, 0xef]; 360 | 361 | let mut io1 = uring.new_oneshot_io(); 362 | assert!(matches!( 363 | io1.submit_or_get_result(|| { 364 | ( 365 | opcode::Write::new(types::Fd(a.as_raw_fd()), buf.as_ptr(), 2).build(), 366 | 10, 367 | ) 368 | }), 369 | Poll::Pending 370 | )); 371 | 372 | let mut io2 = uring.new_oneshot_io(); 373 | assert!(matches!( 374 | io2.submit_or_get_result(|| { 375 | ( 376 | opcode::Write::new(types::Fd(a.as_raw_fd()), buf.as_ptr(), 2).build(), 377 | 20, 378 | ) 379 | }), 380 | Poll::Pending 381 | )); 382 | 383 | let t1 = std::thread::spawn(move || { 384 | let mut buf = [0, 0]; 385 | read(b.as_fd(), &mut buf); 386 | assert_eq!(buf, [0xbe, 0xef]); 387 | read(b, &mut buf); 388 | }); 389 | 390 | let objs: Vec<_> = uring.react().collect(); 391 | 392 | assert_eq!(objs.len(), 2); 393 | assert!(objs.contains(&10)); 394 | assert!(objs.contains(&20)); 395 | 396 | assert!(matches!( 397 | io1.submit_or_get_result(|| panic!("Should not be called")), 398 | Poll::Ready(Ok(2)) 399 | )); 400 | assert!(matches!( 401 | io2.submit_or_get_result(|| panic!("Should not be called")), 402 | Poll::Ready(Ok(2)) 403 | )); 404 | 405 | t1.join().unwrap(); 406 | }); 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /src/reactor/uring/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod multishot; 2 | pub mod oneshot; 3 | 4 | fn reactor_value_to_result(v: i32) -> std::io::Result { 5 | if v < 0 { 6 | Err(std::io::Error::from_raw_os_error(v.abs())) 7 | } else { 8 | Ok(v) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/reactor/uring/io/multishot.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc, task::Poll}; 2 | 3 | use io_uring::{squeue, types::CancelBuilder}; 4 | 5 | use crate::reactor::uring::{result::MultishotResult, IoKind, ReactorInner}; 6 | 7 | use super::reactor_value_to_result; 8 | 9 | #[derive(Debug)] 10 | enum IoState { 11 | New, 12 | Submitted(usize, u64), 13 | } 14 | 15 | pub(crate) struct MultishotUringIo { 16 | state: IoState, 17 | ring: Rc>>, 18 | } 19 | 20 | impl MultishotUringIo { 21 | pub(crate) fn new(ring: Rc>>) -> Self { 22 | Self { 23 | state: IoState::New, 24 | ring, 25 | } 26 | } 27 | 28 | pub fn submit_or_get_result( 29 | &mut self, 30 | f: impl FnOnce() -> (squeue::Entry, T), 31 | ) -> Poll>> { 32 | match self.state { 33 | IoState::New => { 34 | let (entry, obj) = f(); 35 | let (user_data, result_slot) = 36 | self.ring.borrow_mut().submit_io(entry, obj, IoKind::Multi); 37 | self.state = IoState::Submitted(result_slot, user_data); 38 | Poll::Pending 39 | } 40 | IoState::Submitted(slot, _) => { 41 | let mut ring = self.ring.borrow_mut(); 42 | let result_store = ring.results.get_multishot(); 43 | 44 | match result_store.pop_result(slot) { 45 | MultishotResult::Value(v) => Poll::Ready(Some(reactor_value_to_result(v))), 46 | MultishotResult::Pending => Poll::Pending, 47 | MultishotResult::Finished => Poll::Ready(None), 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | impl Drop for MultishotUringIo { 55 | fn drop(&mut self) { 56 | if let IoState::Submitted(slot, user_data) = self.state { 57 | let mut ring = self.ring.borrow_mut(); 58 | 59 | ring.uring 60 | .submitter() 61 | .register_sync_cancel(None, CancelBuilder::user_data(user_data)) 62 | .expect("Should be able to cancel in-flight multishot IO"); 63 | 64 | ring.results.get_multishot().drop_result(slot); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/reactor/uring/io/oneshot.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, rc::Rc, task::Poll}; 2 | 3 | use io_uring::squeue; 4 | 5 | use crate::reactor::uring::{IoKind, ReactorInner}; 6 | 7 | use super::reactor_value_to_result; 8 | 9 | #[derive(Debug)] 10 | enum IoState { 11 | New, 12 | Submitted(usize), 13 | Finished(i32), 14 | } 15 | 16 | pub(crate) struct OneshotUringIo { 17 | state: IoState, 18 | ring: Rc>>, 19 | } 20 | 21 | impl From<&IoState> for Poll> { 22 | fn from(value: &IoState) -> Self { 23 | match value { 24 | IoState::New => Poll::Pending, 25 | IoState::Submitted(_) => Poll::Pending, 26 | IoState::Finished(result) => Poll::Ready(reactor_value_to_result(*result)), 27 | } 28 | } 29 | } 30 | 31 | impl OneshotUringIo { 32 | pub fn new(ring: Rc>>) -> Self { 33 | Self { 34 | state: IoState::New, 35 | ring, 36 | } 37 | } 38 | 39 | pub fn submit_or_get_result( 40 | &mut self, 41 | f: impl FnOnce() -> (squeue::Entry, T), 42 | ) -> Poll> { 43 | match self.state { 44 | IoState::New => { 45 | let (entry, obj) = f(); 46 | let (_, result_slot) = 47 | self.ring 48 | .borrow_mut() 49 | .submit_io(entry, obj, IoKind::Oneshot); 50 | self.state = IoState::Submitted(result_slot); 51 | } 52 | IoState::Submitted(slot) => { 53 | let mut ring = self.ring.borrow_mut(); 54 | let result_store = ring.results.get_oneshot(); 55 | 56 | if let Some(res) = result_store.get_result(slot) { 57 | self.state = IoState::Finished(res); 58 | } 59 | } 60 | IoState::Finished(_) => {} 61 | } 62 | 63 | (&self.state).into() 64 | } 65 | } 66 | 67 | impl Drop for OneshotUringIo { 68 | fn drop(&mut self) { 69 | if let IoState::Submitted(slot) = self.state { 70 | let mut ring = self.ring.borrow_mut(); 71 | 72 | ring.results.get_oneshot().drop_result(slot); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/reactor/uring/result.rs: -------------------------------------------------------------------------------- 1 | use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; 2 | use slab::Slab; 3 | 4 | pub(super) enum ResultState { 5 | Pending, 6 | Set(i32), 7 | Dropped, 8 | } 9 | 10 | pub(crate) struct OneshotStore(Slab); 11 | 12 | impl OneshotStore { 13 | pub fn new() -> Self { 14 | Self(Slab::new()) 15 | } 16 | 17 | #[cfg(test)] 18 | pub fn is_empty(&self) -> bool { 19 | self.0.is_empty() 20 | } 21 | 22 | pub fn set_result(&mut self, result: i32, idx: usize) { 23 | let r_entry = self.0.get_mut(idx).unwrap(); 24 | 25 | if matches!(r_entry, ResultState::Dropped) { 26 | self.0.remove(idx); 27 | } else { 28 | *r_entry = ResultState::Set(result); 29 | } 30 | } 31 | 32 | pub fn get_result(&mut self, idx: usize) -> Option { 33 | let res = match self.0.get(idx).unwrap() { 34 | ResultState::Pending => None, 35 | ResultState::Set(result) => { 36 | let ret = Some(*result); 37 | self.0.remove(idx); 38 | ret 39 | } 40 | ResultState::Dropped => panic!("Should not be able to get a dropped result"), 41 | }; 42 | 43 | res 44 | } 45 | 46 | pub fn drop_result(&mut self, idx: usize) { 47 | let r_entry = self.0.get_mut(idx).unwrap(); 48 | 49 | if matches!(r_entry, ResultState::Set(_)) { 50 | self.0.remove(idx); 51 | } else { 52 | *r_entry = ResultState::Dropped; 53 | } 54 | } 55 | 56 | pub fn create_slot(&mut self) -> usize { 57 | self.0.insert(ResultState::Pending) 58 | } 59 | } 60 | 61 | struct MultishotResultState { 62 | results: ConstGenericRingBuffer, 63 | dropped: bool, 64 | finished: bool, 65 | } 66 | 67 | pub enum MultishotResult { 68 | Value(i32), 69 | Pending, 70 | Finished, 71 | } 72 | 73 | pub(crate) struct MultishotStore(Slab); 74 | 75 | impl MultishotStore { 76 | fn new() -> Self { 77 | Self(Slab::new()) 78 | } 79 | 80 | #[cfg(test)] 81 | pub fn is_empty(&self) -> bool { 82 | self.0.is_empty() 83 | } 84 | 85 | pub fn push_result(&mut self, result: i32, idx: usize) { 86 | self.0.get_mut(idx).unwrap().results.push(result); 87 | } 88 | 89 | pub fn pop_result(&mut self, idx: usize) -> MultishotResult { 90 | let result = self.0.get_mut(idx).unwrap(); 91 | 92 | match result.results.dequeue() { 93 | Some(v) => MultishotResult::Value(v), 94 | None => { 95 | if result.finished { 96 | MultishotResult::Finished 97 | } else { 98 | MultishotResult::Pending 99 | } 100 | } 101 | } 102 | } 103 | 104 | pub fn drop_result(&mut self, idx: usize) { 105 | if self.0.get_mut(idx).unwrap().finished { 106 | self.0.remove(idx); 107 | } else { 108 | self.0.get_mut(idx).unwrap().dropped = true; 109 | } 110 | } 111 | 112 | pub fn create_slot(&mut self) -> usize { 113 | self.0.insert(MultishotResultState { 114 | results: ConstGenericRingBuffer::new(), 115 | dropped: false, 116 | finished: false, 117 | }) 118 | } 119 | 120 | pub fn set_finished(&mut self, idx: usize) { 121 | if self.0.get(idx).unwrap().dropped { 122 | self.0.remove(idx); 123 | } else { 124 | self.0.get_mut(idx).unwrap().finished = true; 125 | } 126 | } 127 | } 128 | 129 | pub struct RingResults { 130 | oneshot: OneshotStore, 131 | multishot: MultishotStore, 132 | } 133 | 134 | impl RingResults { 135 | pub fn new() -> Self { 136 | Self { 137 | oneshot: OneshotStore::new(), 138 | multishot: MultishotStore::new(), 139 | } 140 | } 141 | 142 | pub fn get_oneshot(&mut self) -> &mut OneshotStore { 143 | &mut self.oneshot 144 | } 145 | 146 | pub fn get_multishot(&mut self) -> &mut MultishotStore { 147 | &mut self.multishot 148 | } 149 | 150 | #[cfg(test)] 151 | pub fn is_empty(&self) -> bool { 152 | self.oneshot.is_empty() && self.multishot.is_empty() 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/task.rs: -------------------------------------------------------------------------------- 1 | //! Task and execution management 2 | //! 3 | //! This module provides the methods needed to spawn tasks and execute them 4 | //! until completion. Trale uses a per-thread exector model which means that 5 | //! each new OS thread that is spawned has it's own execution environemnt. This 6 | //! means that: 7 | //! 8 | //! 1. The thread upon which a task is spawned is the same thread that will 9 | //! execute it. 10 | //! 2. Each thread needs to call one of [Executor::block_on] or [Executor::run] 11 | //! to do any work. Use the former if you want to run a single top-level 12 | //! task, or the latter if you wish to run multiple top-level tasks. 13 | //! 14 | //! # Example 15 | //! 16 | //! Here is a simple hello world using [Executor::block_on]. 17 | //! 18 | //! ``` 19 | //! use trale::task::Executor; 20 | //! Executor::block_on(async { println!("Hello, world!"); }); 21 | //! ``` 22 | //! 23 | //! You can also use [Executor::block_on] to easily obtain the result of a 24 | //! future: 25 | //! 26 | //! ``` 27 | //! use trale::task::Executor; 28 | //! let x = Executor::block_on(async { 2 + 8 }); 29 | //! assert_eq!(x, 10); 30 | //! ``` 31 | //! 32 | //! Here is the same but spawning multiple top-level tasks. 33 | //! 34 | //! ``` 35 | //! use trale::task::Executor; 36 | //! Executor::spawn(async { println!("Hello"); }); 37 | //! Executor::spawn(async { println!("World!"); }); 38 | //! Executor::run(); 39 | //! ``` 40 | //! 41 | //! # Threading Model 42 | //! 43 | //! Since each thread has it's own execution state, if you don't spawn any new 44 | //! threads you can guarantee that only a single task will be executing at once. 45 | //! This allows for `!Sync` futures to be executed: 46 | //! 47 | //! ``` 48 | //! use trale::task::Executor; 49 | //! use std::cell::RefCell; 50 | //! use std::rc::Rc; 51 | //! let cell = Rc::new(RefCell::new(0)); 52 | //! { 53 | //! let cell = cell.clone(); 54 | //! Executor::spawn(async move { *cell.borrow_mut() += 10 }); 55 | //! } 56 | //! { 57 | //! let cell = cell.clone(); 58 | //! Executor::spawn(async move { *cell.borrow_mut() += 10 }); 59 | //! } 60 | //! Executor::run(); 61 | //! assert_eq!(*cell.borrow(), 20); 62 | //! ``` 63 | //! 64 | //! Note, however, that one needs to call [Executor::run] for *all* thread that 65 | //! need to execute futures: 66 | //! ``` 67 | //! use trale::task::Executor; 68 | //! use std::sync::Arc; 69 | //! use std::sync::Mutex; 70 | //! use std::thread; 71 | //! let cell = Arc::new(Mutex::new(0)); 72 | //! { 73 | //! let cell = cell.clone(); 74 | //! Executor::spawn(async move { *cell.lock().unwrap() += 1 }); 75 | //! } 76 | //! { 77 | //! let cell = cell.clone(); 78 | //! Executor::spawn(async move { *cell.lock().unwrap() += 1 }); 79 | //! } 80 | //! 81 | //! let thread = { 82 | //! let cell = cell.clone(); 83 | //! thread::spawn(move || { 84 | //! Executor::spawn(async move { *cell.lock().unwrap() += 1 }); 85 | //! Executor::run(); 86 | //! }) 87 | //! }; 88 | //! 89 | //! thread.join(); 90 | //! assert_eq!(*cell.lock().unwrap(), 1); 91 | //! Executor::run(); 92 | //! assert_eq!(*cell.lock().unwrap(), 3); 93 | //! ``` 94 | use std::{ 95 | cell::RefCell, 96 | future::Future, 97 | mem::transmute, 98 | pin::Pin, 99 | sync::{ 100 | atomic::{AtomicUsize, Ordering}, 101 | mpsc::{sync_channel, Receiver}, 102 | Arc, 103 | }, 104 | task::{ready, Context, Poll, Wake, Waker}, 105 | }; 106 | 107 | use slab::Slab; 108 | 109 | use crate::{ 110 | futures::event::{Event, EventWaiter}, 111 | reactor::Reactor, 112 | }; 113 | 114 | struct TaskId(AtomicUsize); 115 | 116 | impl Wake for TaskId { 117 | fn wake(self: Arc) { 118 | EXEC.with(|exec| { 119 | let mut exec = exec.borrow_mut(); 120 | if let Some(task) = exec.waiting.try_remove(self.0.load(Ordering::Relaxed)) { 121 | exec.run_q.push(task); 122 | } 123 | }); 124 | } 125 | } 126 | 127 | struct Task { 128 | id: Arc, 129 | future: Pin>>, 130 | } 131 | 132 | /// The async executor. 133 | /// 134 | /// A type that is responsible for pushing futures through to 135 | /// completion. You can begin execution of a new task by calling the 136 | /// [Executor::block_on] function. 137 | pub struct Executor { 138 | waiting: Slab, 139 | run_q: Vec, 140 | } 141 | 142 | thread_local! { 143 | static EXEC: RefCell = const { RefCell::new( 144 | Executor { 145 | waiting: Slab::new(), 146 | run_q: Vec::new(), 147 | } 148 | )} 149 | } 150 | 151 | /// A handle to a running task. 152 | /// 153 | /// You can call [TaskJoiner::join] from a synchronous context to block 154 | /// execution and yield the future's value. If you want to wait for execution to 155 | /// finish from an asynchronous context, use `.await` on the joiner. If the 156 | /// joiner is dropped then execution of the future continues to completion but 157 | /// the return value is lost, aka detatch-on-drop. 158 | pub struct TaskJoiner<'a, T> { 159 | rx: Receiver, 160 | _evt: Event, 161 | finished: EventWaiter<'a>, 162 | } 163 | 164 | impl<'a, T> TaskJoiner<'a, T> { 165 | /// Block execution and wait for a task to finish executing. The return 166 | /// value `T` is the value yielded by the task's future. 167 | /// 168 | /// *Note* This function should only be called from synchronous contexts. To 169 | /// prevent deadlocks in an asynchronous context, use `.await` instead. 170 | pub fn join(self) -> T { 171 | self.rx.recv().unwrap() 172 | } 173 | } 174 | 175 | impl<'a, T> Future for TaskJoiner<'a, T> { 176 | type Output = T; 177 | 178 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 179 | ready!(Pin::new(&mut self.finished).poll(cx)).unwrap(); 180 | 181 | Poll::Ready(self.rx.recv().unwrap()) 182 | } 183 | } 184 | 185 | impl Executor { 186 | /// Spawn a new future and add it to this thread's run queue. If called from 187 | /// an already-running asynchronous task, the future will be queued for 188 | /// execution. If called from a synchronous context, the task will *not* be 189 | /// executed until [Executor::run] is called. 190 | /// 191 | /// A [TaskJoiner] is returned which can be used to wait for completion of 192 | /// the future `f` and obtain it's return value. 193 | pub fn spawn<'a, Fut, T>(f: Fut) -> TaskJoiner<'a, T> 194 | where 195 | Fut: Future + 'static, 196 | T: Send + 'static, 197 | { 198 | let (tx, rx) = sync_channel(1); 199 | let mut evt = Event::new().unwrap(); 200 | let evt2 = evt.clone(); 201 | 202 | let fut = async move { 203 | let value = f.await; 204 | let _ = evt2.notify_one(); 205 | let _ = tx.send(value); 206 | }; 207 | 208 | let task = Task { 209 | id: Arc::new(TaskId(AtomicUsize::new(0))), 210 | future: Box::pin(fut), 211 | }; 212 | 213 | EXEC.with(|exec| { 214 | exec.borrow_mut().run_q.push(task); 215 | }); 216 | 217 | // SAFETY: This is safe since the borrowed FD is in the same structure 218 | // that contains the waiter, therefore waiter can never outlive the 219 | // event. 220 | let waiter: EventWaiter<'static> = unsafe { transmute(evt.wait()) }; 221 | 222 | TaskJoiner { 223 | rx, 224 | _evt: evt, 225 | finished: waiter, 226 | } 227 | } 228 | 229 | /// A convenience function for waiting on a future from a synchronous 230 | /// context. This is the equivalent of calling: 231 | /// 232 | /// ``` 233 | /// # use trale::task::Executor; 234 | /// # use std::future::Future; 235 | /// # fn x + Send + 'static>(f: Fut) { 236 | /// let task = Executor::spawn(f); 237 | /// Executor::run(); 238 | /// task.join(); 239 | /// # } 240 | /// ``` 241 | pub fn block_on(f: Fut) -> T 242 | where 243 | Fut: Future + 'static, 244 | T: Send + 'static, 245 | { 246 | let joiner = Self::spawn(f); 247 | 248 | Self::executor_loop(); 249 | 250 | joiner.join() 251 | } 252 | 253 | /// Run the executor for this thread. 254 | /// 255 | /// This function will schedule and run all tasks that have been previously 256 | /// spawned with [Executor::spawn]. *Note* each thread has it's own set of 257 | /// tasks and execution envionrment. If you call this function, only tasks 258 | /// that have been spaned on *this* thread will be executed. 259 | /// 260 | /// Blocks until all tasks have finished executing. 261 | pub fn run() { 262 | Self::executor_loop() 263 | } 264 | 265 | fn executor_loop() { 266 | EXEC.with(|exec| loop { 267 | if exec.borrow().run_q.is_empty() { 268 | Reactor::react(); 269 | } 270 | 271 | let mut task = exec.borrow_mut().run_q.pop().unwrap(); 272 | 273 | let waker = Waker::from(task.id.clone()); 274 | 275 | let mut cx = Context::from_waker(&waker); 276 | 277 | match task.future.as_mut().poll(&mut cx) { 278 | Poll::Ready(()) => {} 279 | Poll::Pending => { 280 | let waiting = &mut exec.borrow_mut().waiting; 281 | 282 | let slot = waiting.vacant_entry(); 283 | 284 | task.id.0.store(slot.key(), Ordering::Relaxed); 285 | 286 | slot.insert(task); 287 | } 288 | } 289 | 290 | if exec.borrow().run_q.is_empty() && exec.borrow().waiting.is_empty() { 291 | return; 292 | } 293 | }); 294 | } 295 | } 296 | --------------------------------------------------------------------------------