├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── http-server.rs └── timers.rs └── src ├── executor.rs ├── lib.rs ├── net.rs ├── reactor ├── io.rs ├── mod.rs └── timers.rs └── ready.rs /.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 = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "bytes" 19 | version = "1.1.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 22 | 23 | [[package]] 24 | name = "cfg-if" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 28 | 29 | [[package]] 30 | name = "chashmap" 31 | version = "2.2.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45" 34 | dependencies = [ 35 | "owning_ref", 36 | "parking_lot 0.4.8", 37 | ] 38 | 39 | [[package]] 40 | name = "educe" 41 | version = "0.4.19" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "c07b7cc9cd8c08d10db74fca3b20949b9b6199725c04a0cce6d543496098fcac" 44 | dependencies = [ 45 | "enum-ordinalize", 46 | "proc-macro2", 47 | "quote", 48 | "syn", 49 | ] 50 | 51 | [[package]] 52 | name = "enum-ordinalize" 53 | version = "3.1.10" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "0b166c9e378360dd5a6666a9604bb4f54ae0cac39023ffbac425e917a2a04fef" 56 | dependencies = [ 57 | "num-bigint", 58 | "num-traits", 59 | "proc-macro2", 60 | "quote", 61 | "syn", 62 | ] 63 | 64 | [[package]] 65 | name = "fnv" 66 | version = "1.0.7" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 69 | 70 | [[package]] 71 | name = "fuchsia-cprng" 72 | version = "0.1.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 75 | 76 | [[package]] 77 | name = "futures" 78 | version = "0.3.21" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 81 | dependencies = [ 82 | "futures-channel", 83 | "futures-core", 84 | "futures-executor", 85 | "futures-io", 86 | "futures-sink", 87 | "futures-task", 88 | "futures-util", 89 | ] 90 | 91 | [[package]] 92 | name = "futures-channel" 93 | version = "0.3.21" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 96 | dependencies = [ 97 | "futures-core", 98 | "futures-sink", 99 | ] 100 | 101 | [[package]] 102 | name = "futures-core" 103 | version = "0.3.21" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 106 | 107 | [[package]] 108 | name = "futures-executor" 109 | version = "0.3.21" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 112 | dependencies = [ 113 | "futures-core", 114 | "futures-task", 115 | "futures-util", 116 | ] 117 | 118 | [[package]] 119 | name = "futures-io" 120 | version = "0.3.21" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 123 | 124 | [[package]] 125 | name = "futures-macro" 126 | version = "0.3.21" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 129 | dependencies = [ 130 | "proc-macro2", 131 | "quote", 132 | "syn", 133 | ] 134 | 135 | [[package]] 136 | name = "futures-sink" 137 | version = "0.3.21" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 140 | 141 | [[package]] 142 | name = "futures-task" 143 | version = "0.3.21" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 146 | 147 | [[package]] 148 | name = "futures-util" 149 | version = "0.3.21" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 152 | dependencies = [ 153 | "futures-channel", 154 | "futures-core", 155 | "futures-io", 156 | "futures-macro", 157 | "futures-sink", 158 | "futures-task", 159 | "memchr", 160 | "pin-project-lite", 161 | "pin-utils", 162 | "slab", 163 | ] 164 | 165 | [[package]] 166 | name = "getrandom" 167 | version = "0.2.5" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" 170 | dependencies = [ 171 | "cfg-if", 172 | "libc", 173 | "wasi", 174 | ] 175 | 176 | [[package]] 177 | name = "http" 178 | version = "0.2.6" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" 181 | dependencies = [ 182 | "bytes", 183 | "fnv", 184 | "itoa", 185 | ] 186 | 187 | [[package]] 188 | name = "httparse" 189 | version = "1.6.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" 192 | 193 | [[package]] 194 | name = "httpdate" 195 | version = "1.0.2" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 198 | 199 | [[package]] 200 | name = "itoa" 201 | version = "1.0.1" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 204 | 205 | [[package]] 206 | name = "libc" 207 | version = "0.2.119" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" 210 | 211 | [[package]] 212 | name = "lock_api" 213 | version = "0.4.6" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 216 | dependencies = [ 217 | "scopeguard", 218 | ] 219 | 220 | [[package]] 221 | name = "log" 222 | version = "0.4.14" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 225 | dependencies = [ 226 | "cfg-if", 227 | ] 228 | 229 | [[package]] 230 | name = "maybe-uninit" 231 | version = "2.0.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 234 | 235 | [[package]] 236 | name = "memchr" 237 | version = "2.4.1" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 240 | 241 | [[package]] 242 | name = "mio" 243 | version = "0.8.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" 246 | dependencies = [ 247 | "libc", 248 | "log", 249 | "miow", 250 | "ntapi", 251 | "winapi", 252 | ] 253 | 254 | [[package]] 255 | name = "miow" 256 | version = "0.3.7" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 259 | dependencies = [ 260 | "winapi", 261 | ] 262 | 263 | [[package]] 264 | name = "ntapi" 265 | version = "0.3.7" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 268 | dependencies = [ 269 | "winapi", 270 | ] 271 | 272 | [[package]] 273 | name = "num-bigint" 274 | version = "0.4.3" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 277 | dependencies = [ 278 | "autocfg", 279 | "num-integer", 280 | "num-traits", 281 | ] 282 | 283 | [[package]] 284 | name = "num-integer" 285 | version = "0.1.44" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 288 | dependencies = [ 289 | "autocfg", 290 | "num-traits", 291 | ] 292 | 293 | [[package]] 294 | name = "num-traits" 295 | version = "0.2.14" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 298 | dependencies = [ 299 | "autocfg", 300 | ] 301 | 302 | [[package]] 303 | name = "owning_ref" 304 | version = "0.3.3" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" 307 | dependencies = [ 308 | "stable_deref_trait", 309 | ] 310 | 311 | [[package]] 312 | name = "parking_lot" 313 | version = "0.4.8" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" 316 | dependencies = [ 317 | "owning_ref", 318 | "parking_lot_core 0.2.14", 319 | ] 320 | 321 | [[package]] 322 | name = "parking_lot" 323 | version = "0.12.0" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 326 | dependencies = [ 327 | "lock_api", 328 | "parking_lot_core 0.9.1", 329 | ] 330 | 331 | [[package]] 332 | name = "parking_lot_core" 333 | version = "0.2.14" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" 336 | dependencies = [ 337 | "libc", 338 | "rand 0.4.6", 339 | "smallvec 0.6.14", 340 | "winapi", 341 | ] 342 | 343 | [[package]] 344 | name = "parking_lot_core" 345 | version = "0.9.1" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" 348 | dependencies = [ 349 | "cfg-if", 350 | "libc", 351 | "redox_syscall", 352 | "smallvec 1.8.0", 353 | "windows-sys", 354 | ] 355 | 356 | [[package]] 357 | name = "pin-project" 358 | version = "1.0.10" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" 361 | dependencies = [ 362 | "pin-project-internal", 363 | ] 364 | 365 | [[package]] 366 | name = "pin-project-internal" 367 | version = "1.0.10" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" 370 | dependencies = [ 371 | "proc-macro2", 372 | "quote", 373 | "syn", 374 | ] 375 | 376 | [[package]] 377 | name = "pin-project-lite" 378 | version = "0.2.8" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 381 | 382 | [[package]] 383 | name = "pin-utils" 384 | version = "0.1.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 387 | 388 | [[package]] 389 | name = "ppv-lite86" 390 | version = "0.2.16" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 393 | 394 | [[package]] 395 | name = "proc-macro2" 396 | version = "1.0.36" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 399 | dependencies = [ 400 | "unicode-xid", 401 | ] 402 | 403 | [[package]] 404 | name = "quote" 405 | version = "1.0.15" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 408 | dependencies = [ 409 | "proc-macro2", 410 | ] 411 | 412 | [[package]] 413 | name = "rand" 414 | version = "0.4.6" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 417 | dependencies = [ 418 | "fuchsia-cprng", 419 | "libc", 420 | "rand_core 0.3.1", 421 | "rdrand", 422 | "winapi", 423 | ] 424 | 425 | [[package]] 426 | name = "rand" 427 | version = "0.8.5" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 430 | dependencies = [ 431 | "libc", 432 | "rand_chacha", 433 | "rand_core 0.6.3", 434 | ] 435 | 436 | [[package]] 437 | name = "rand_chacha" 438 | version = "0.3.1" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 441 | dependencies = [ 442 | "ppv-lite86", 443 | "rand_core 0.6.3", 444 | ] 445 | 446 | [[package]] 447 | name = "rand_core" 448 | version = "0.3.1" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 451 | dependencies = [ 452 | "rand_core 0.4.2", 453 | ] 454 | 455 | [[package]] 456 | name = "rand_core" 457 | version = "0.4.2" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 460 | 461 | [[package]] 462 | name = "rand_core" 463 | version = "0.6.3" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 466 | dependencies = [ 467 | "getrandom", 468 | ] 469 | 470 | [[package]] 471 | name = "rdrand" 472 | version = "0.4.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 475 | dependencies = [ 476 | "rand_core 0.3.1", 477 | ] 478 | 479 | [[package]] 480 | name = "redox_syscall" 481 | version = "0.2.11" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" 484 | dependencies = [ 485 | "bitflags", 486 | ] 487 | 488 | [[package]] 489 | name = "ryu" 490 | version = "1.0.9" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 493 | 494 | [[package]] 495 | name = "scopeguard" 496 | version = "1.1.0" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 499 | 500 | [[package]] 501 | name = "serde" 502 | version = "1.0.136" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 505 | dependencies = [ 506 | "serde_derive", 507 | ] 508 | 509 | [[package]] 510 | name = "serde_derive" 511 | version = "1.0.136" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 514 | dependencies = [ 515 | "proc-macro2", 516 | "quote", 517 | "syn", 518 | ] 519 | 520 | [[package]] 521 | name = "serde_json" 522 | version = "1.0.79" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" 525 | dependencies = [ 526 | "itoa", 527 | "ryu", 528 | "serde", 529 | ] 530 | 531 | [[package]] 532 | name = "slab" 533 | version = "0.4.5" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 536 | 537 | [[package]] 538 | name = "smallvec" 539 | version = "0.6.14" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" 542 | dependencies = [ 543 | "maybe-uninit", 544 | ] 545 | 546 | [[package]] 547 | name = "smallvec" 548 | version = "1.8.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 551 | 552 | [[package]] 553 | name = "socket2" 554 | version = "0.4.4" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 557 | dependencies = [ 558 | "libc", 559 | "winapi", 560 | ] 561 | 562 | [[package]] 563 | name = "stable_deref_trait" 564 | version = "1.2.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 567 | 568 | [[package]] 569 | name = "syn" 570 | version = "1.0.86" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 573 | dependencies = [ 574 | "proc-macro2", 575 | "quote", 576 | "unicode-xid", 577 | ] 578 | 579 | [[package]] 580 | name = "tl-async-runtime" 581 | version = "0.1.2" 582 | dependencies = [ 583 | "bytes", 584 | "chashmap", 585 | "educe", 586 | "futures", 587 | "http", 588 | "httparse", 589 | "httpdate", 590 | "mio", 591 | "parking_lot 0.12.0", 592 | "pin-project", 593 | "rand 0.8.5", 594 | "serde", 595 | "serde_json", 596 | "tokio", 597 | "tokio-util", 598 | ] 599 | 600 | [[package]] 601 | name = "tokio" 602 | version = "1.17.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" 605 | dependencies = [ 606 | "bytes", 607 | "libc", 608 | "memchr", 609 | "mio", 610 | "pin-project-lite", 611 | "socket2", 612 | "winapi", 613 | ] 614 | 615 | [[package]] 616 | name = "tokio-util" 617 | version = "0.7.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" 620 | dependencies = [ 621 | "bytes", 622 | "futures-core", 623 | "futures-io", 624 | "futures-sink", 625 | "futures-util", 626 | "log", 627 | "pin-project-lite", 628 | "slab", 629 | "tokio", 630 | ] 631 | 632 | [[package]] 633 | name = "unicode-xid" 634 | version = "0.2.2" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 637 | 638 | [[package]] 639 | name = "wasi" 640 | version = "0.10.2+wasi-snapshot-preview1" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 643 | 644 | [[package]] 645 | name = "winapi" 646 | version = "0.3.9" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 649 | dependencies = [ 650 | "winapi-i686-pc-windows-gnu", 651 | "winapi-x86_64-pc-windows-gnu", 652 | ] 653 | 654 | [[package]] 655 | name = "winapi-i686-pc-windows-gnu" 656 | version = "0.4.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 659 | 660 | [[package]] 661 | name = "winapi-x86_64-pc-windows-gnu" 662 | version = "0.4.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 665 | 666 | [[package]] 667 | name = "windows-sys" 668 | version = "0.32.0" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" 671 | dependencies = [ 672 | "windows_aarch64_msvc", 673 | "windows_i686_gnu", 674 | "windows_i686_msvc", 675 | "windows_x86_64_gnu", 676 | "windows_x86_64_msvc", 677 | ] 678 | 679 | [[package]] 680 | name = "windows_aarch64_msvc" 681 | version = "0.32.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" 684 | 685 | [[package]] 686 | name = "windows_i686_gnu" 687 | version = "0.32.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" 690 | 691 | [[package]] 692 | name = "windows_i686_msvc" 693 | version = "0.32.0" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" 696 | 697 | [[package]] 698 | name = "windows_x86_64_gnu" 699 | version = "0.32.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" 702 | 703 | [[package]] 704 | name = "windows_x86_64_msvc" 705 | version = "0.32.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" 708 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tl-async-runtime" 3 | version = "0.1.2" 4 | edition = "2021" 5 | description = "A bad runtime impl for educational purposes only" 6 | license = "MIT" 7 | repository = "https://github.com/conradludgate/tl-async-runtime" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | futures = "0.3" 13 | rand = "0.8" 14 | pin-project = "1.0" 15 | mio = { version = "0.8", features = ["os-poll", "net"] } 16 | chashmap = "2.2.2" 17 | parking_lot = "0.12" 18 | tokio = "1" # might be cheating. I'm using tokio's AsyncRead/Write traits and helpers 19 | 20 | [dependencies.educe] 21 | version = "*" 22 | features = ["Default", "PartialEq", "Eq", "PartialOrd", "Ord"] 23 | default-features = false 24 | 25 | [dev-dependencies] 26 | bytes = "1.0.0" 27 | http = "0.2" 28 | serde = { version = "1.0", features = ["derive"] } 29 | serde_json = "1.0" 30 | httparse = "1.0" 31 | httpdate = "1.0" 32 | 33 | tokio-util = { version = "0.7.0", features = ["full"] } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tl-async-runtime 2 | 3 | A fairly basic runtime example in <600 lines of logic 4 | 5 | ## Features 6 | 7 | * Single threaded async 8 | * Multi threaded async 9 | * Timer support 10 | * Basic IO (only TCP for now) 11 | 12 | ## Bugs 13 | 14 | Many. 15 | 16 | ## Architecure 17 | 18 | The runtime is first created by calling the `block_on` function, passing in a future. 19 | The runtime will start up the threads and spawns the given future on the runtime. 20 | 21 | At this point, all threads are 'workers'. Workers do a few things 22 | 23 | 1. The thread run some basic book-keeping tasks (more on this later). 24 | 2. Request a ready task from the runtime 25 | * If it can get a task, it will poll it once 26 | * If it can't get a task, it will 'park' the thread 27 | 3. Repeat 28 | 29 | The main thread does 1 extra step, which is polling the channel future returned by the initial spawn. 30 | This is so that we can exit as soon as that task has been completed. 31 | 32 | ### Bookkeeping 33 | 34 | The core of the async runtime is scheduling tasks and managing the threads. 35 | 36 | There's only 2 steps to this: 37 | 1. Looping through elapsed timers and putting them in a ready state 38 | 2. Polling the OS for events and dispatching them 39 | 40 | ### Timers 41 | 42 | The timers are very rudementary. When a timer is first polled, it 43 | gets the thread-local executor object and pushes a `(Time, Waker)` pair 44 | into a priority queue (ordered by time ascending). 45 | 46 | The book-keepers will loop through this priority queue and call the respective wakers 47 | 48 | ### OS Events 49 | 50 | Using [mio](https://crates.io/crates/mio), event sources are registered 51 | with the OS when they are created. 52 | 53 | When events are requested for a specific source, a `[RegistrationToken -> Sender]` entry is pushed into 54 | a hashmap. 55 | 56 | The book-keepers will poll the OS for new events. If a corresponding token is found in the hashmap, 57 | it will send the event along the channel. Since it's using a future-aware channel, it will auto-wake 58 | any tasks that are waiting for the event 59 | 60 | ## Benchmark 61 | 62 | The [http-server](examples/http-server.rs) example was taken from [tokio's tinyhttp](https://github.com/tokio-rs/tokio/blob/e8ae65a697d04aa11d5587c45caf999cb3b7f36e/examples/tinyhttp.rs) example. 63 | I ran both of them and performed the following [wrk](https://github.com/wg/wrk) benchmark: 64 | 65 | ```sh 66 | wrk -t12 -c500 -d20s http://localhost:8080/json 67 | ``` 68 | 69 | I got the following results: 70 | 71 | ### Multi threaded 72 | 73 | #### tl-async-runtime 74 | ``` 75 | Running 20s test @ http://localhost:8080/json 76 | 12 threads and 500 connections 77 | Thread Stats Avg Stdev Max +/- Stdev 78 | Latency 74.78ms 14.82ms 173.46ms 59.86% 79 | Req/Sec 117.42 109.90 404.00 76.17% 80 | 25755 requests in 20.10s, 3.73MB read 81 | Requests/sec: 1281.44 82 | Transfer/sec: 190.21KB 83 | ``` 84 | 85 | #### Tokio 86 | ``` 87 | Running 20s test @ http://localhost:8080/json 88 | 12 threads and 500 connections 89 | Thread Stats Avg Stdev Max +/- Stdev 90 | Latency 78.58ms 19.57ms 252.46ms 75.52% 91 | Req/Sec 527.69 72.58 747.00 88.10% 92 | 125196 requests in 20.09s, 18.15MB read 93 | Requests/sec: 6231.95 94 | Transfer/sec: 0.90MB 95 | ``` 96 | 97 | ### Single threaded 98 | 99 | #### tl-async-runtime 100 | ``` 101 | Running 20s test @ http://localhost:8080/json 102 | 12 threads and 500 connections 103 | Thread Stats Avg Stdev Max +/- Stdev 104 | Latency 75.63ms 16.24ms 199.04ms 63.16% 105 | Req/Sec 282.34 112.53 707.00 70.57% 106 | 61703 requests in 20.07s, 8.94MB read 107 | Requests/sec: 3074.14 108 | Transfer/sec: 456.32KB 109 | ``` 110 | 111 | #### Tokio 112 | ``` 113 | Running 20s test @ http://localhost:8080/json 114 | 12 threads and 500 connections 115 | Thread Stats Avg Stdev Max +/- Stdev 116 | Latency 76.28ms 15.26ms 185.57ms 60.50% 117 | Req/Sec 539.62 52.73 780.00 90.20% 118 | 128620 requests in 20.08s, 18.64MB read 119 | Requests/sec: 6406.56 120 | Transfer/sec: 0.93MB 121 | ``` 122 | 123 | ### Conclusion 124 | 125 | Tokio's has a similar latency and roughly 2x the throughput on the single threaded executor 126 | 127 | Tokio also has a similar multi-threaded vs single-threaded performance in this example. 128 | This runtime seems to have a degraded performance when attempting to use multiple threads. 129 | This may be a result of lock contensions as a result of not optimising the data structures. 130 | 131 | Using [lines of code](https://gist.github.com/conradludgate/417ef86f1764b41606f400de247692bf) as an estimate for complexity, tokio is ~60x more complex. 132 | 133 | I would consider this a decent attempt at a runtime. It's not the most effective but it certainly does good enough, 134 | especially for the intended goal of simplicity. 135 | 136 | ### Alternatives 137 | 138 | When benchmarking a non-async version that uses an OS thread per active connection seems surprisingly effective on my 8-core Linux machine. 139 | 140 | ``` 141 | Running 20s test @ http://localhost:8080/json 142 | 12 threads and 500 connections 143 | Thread Stats Avg Stdev Max +/- Stdev 144 | Latency 140.12ms 122.82ms 780.91ms 16.68% 145 | Req/Sec 3.20k 2.30k 18.82k 68.92% 146 | 750797 requests in 20.07s, 108.83MB read 147 | Requests/sec: 37408.86 148 | Transfer/sec: 5.42MB 149 | ``` 150 | -------------------------------------------------------------------------------- /examples/http-server.rs: -------------------------------------------------------------------------------- 1 | //! A "tiny" example of HTTP request/response handling using transports. 2 | //! 3 | //! This example is intended for *learning purposes* to see how various pieces 4 | //! hook up together and how HTTP can get up and running. Note that this example 5 | //! is written with the restriction that it *can't* use any "big" library other 6 | //! than Tokio, if you'd like a "real world" HTTP library you likely want a 7 | //! crate like Hyper. 8 | //! 9 | //! Code here is based on the `echo-threads` example and implements two paths, 10 | //! the `/plaintext` and `/json` routes to respond with some text and json, 11 | //! respectively. By default this will run I/O on all the cores your system has 12 | //! available, and it doesn't support HTTP request bodies. 13 | 14 | use bytes::BytesMut; 15 | use futures::{SinkExt, StreamExt}; 16 | use http::{header::HeaderValue, Request, Response, StatusCode}; 17 | use httpdate::HttpDate; 18 | use rand::Rng; 19 | use serde::Serialize; 20 | use std::{ 21 | env, 22 | error::Error, 23 | fmt, io, 24 | net::SocketAddr, 25 | str::FromStr, 26 | time::{Duration, SystemTime}, 27 | }; 28 | use tl_async_runtime::{ 29 | block_on, 30 | net::{TcpListener, TcpStream}, 31 | spawn, Sleep, 32 | }; 33 | use tokio_util::codec::{Decoder, Encoder, Framed}; 34 | 35 | fn main() -> Result<(), Box> { 36 | block_on(async { 37 | // Parse the arguments, bind the TCP socket we'll be listening to, spin up 38 | // our worker threads, and start shipping sockets to those worker threads. 39 | let addr = env::args() 40 | .nth(1) 41 | .unwrap_or_else(|| "127.0.0.1:8080".to_string()); 42 | let socket = SocketAddr::from_str(&addr).unwrap(); 43 | let server = TcpListener::bind(socket)?; 44 | println!("Listening on: {}", addr); 45 | 46 | loop { 47 | let (stream, _) = server.accept().await?; 48 | spawn(async move { 49 | if let Err(e) = process(stream).await { 50 | println!("failed to process connection; error = {}", e); 51 | } 52 | }); 53 | } 54 | }) 55 | } 56 | 57 | async fn process(stream: TcpStream) -> Result<(), Box> { 58 | let mut transport = Framed::new(stream, Http); 59 | 60 | while let Some(request) = transport.next().await { 61 | match request { 62 | Ok(request) => { 63 | let response = respond(request).await?; 64 | transport.send(response).await?; 65 | } 66 | Err(e) => return Err(e.into()), 67 | } 68 | } 69 | 70 | Ok(()) 71 | } 72 | 73 | async fn respond( 74 | req: Request<()>, 75 | ) -> Result, Box> { 76 | let mut response = Response::builder(); 77 | let ms = rand::thread_rng().gen_range(50..100); 78 | Sleep::duration(Duration::from_millis(ms)).await; 79 | let body = match req.uri().path() { 80 | "/plaintext" => { 81 | response = response.header("Content-Type", "text/plain"); 82 | "Hello, World!".to_string() 83 | } 84 | "/json" => { 85 | response = response.header("Content-Type", "application/json"); 86 | 87 | #[derive(Serialize)] 88 | struct Message { 89 | message: &'static str, 90 | } 91 | serde_json::to_string(&Message { 92 | message: "Hello, World!", 93 | })? 94 | } 95 | _ => { 96 | response = response.status(StatusCode::NOT_FOUND); 97 | String::new() 98 | } 99 | }; 100 | let response = response 101 | .body(body) 102 | .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; 103 | 104 | Ok(response) 105 | } 106 | 107 | struct Http; 108 | 109 | /// Implementation of encoding an HTTP response into a `BytesMut`, basically 110 | /// just writing out an HTTP/1.1 response. 111 | impl Encoder> for Http { 112 | type Error = io::Error; 113 | 114 | fn encode(&mut self, item: Response, dst: &mut BytesMut) -> io::Result<()> { 115 | use std::fmt::Write; 116 | 117 | write!( 118 | BytesWrite(dst), 119 | "\ 120 | HTTP/1.1 {}\r\n\ 121 | Server: Example\r\n\ 122 | Content-Length: {}\r\n\ 123 | Date: {}\r\n\ 124 | ", 125 | item.status(), 126 | item.body().len(), 127 | HttpDate::from(SystemTime::now()), 128 | ) 129 | .unwrap(); 130 | 131 | for (k, v) in item.headers() { 132 | dst.extend_from_slice(k.as_str().as_bytes()); 133 | dst.extend_from_slice(b": "); 134 | dst.extend_from_slice(v.as_bytes()); 135 | dst.extend_from_slice(b"\r\n"); 136 | } 137 | 138 | dst.extend_from_slice(b"\r\n"); 139 | dst.extend_from_slice(item.body().as_bytes()); 140 | 141 | return Ok(()); 142 | 143 | // Right now `write!` on `Vec` goes through io::Write and is not 144 | // super speedy, so inline a less-crufty implementation here which 145 | // doesn't go through io::Error. 146 | struct BytesWrite<'a>(&'a mut BytesMut); 147 | 148 | impl fmt::Write for BytesWrite<'_> { 149 | fn write_str(&mut self, s: &str) -> fmt::Result { 150 | self.0.extend_from_slice(s.as_bytes()); 151 | Ok(()) 152 | } 153 | 154 | fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { 155 | fmt::write(self, args) 156 | } 157 | } 158 | } 159 | } 160 | 161 | /// Implementation of decoding an HTTP request from the bytes we've read so far. 162 | /// This leverages the `httparse` crate to do the actual parsing and then we use 163 | /// that information to construct an instance of a `http::Request` object, 164 | /// trying to avoid allocations where possible. 165 | impl Decoder for Http { 166 | type Item = Request<()>; 167 | type Error = io::Error; 168 | 169 | fn decode(&mut self, src: &mut BytesMut) -> io::Result>> { 170 | // TODO: we should grow this headers array if parsing fails and asks 171 | // for more headers 172 | let mut headers = [None; 16]; 173 | let (method, path, version, amt) = { 174 | let mut parsed_headers = [httparse::EMPTY_HEADER; 16]; 175 | let mut r = httparse::Request::new(&mut parsed_headers); 176 | let status = r.parse(src).map_err(|e| { 177 | let msg = format!("failed to parse http request: {:?}", e); 178 | io::Error::new(io::ErrorKind::Other, msg) 179 | })?; 180 | 181 | let amt = match status { 182 | httparse::Status::Complete(amt) => amt, 183 | httparse::Status::Partial => return Ok(None), 184 | }; 185 | 186 | let toslice = |a: &[u8]| { 187 | let start = a.as_ptr() as usize - src.as_ptr() as usize; 188 | assert!(start < src.len()); 189 | (start, start + a.len()) 190 | }; 191 | 192 | for (i, header) in r.headers.iter().enumerate() { 193 | let k = toslice(header.name.as_bytes()); 194 | let v = toslice(header.value); 195 | headers[i] = Some((k, v)); 196 | } 197 | 198 | ( 199 | toslice(r.method.unwrap().as_bytes()), 200 | toslice(r.path.unwrap().as_bytes()), 201 | r.version.unwrap(), 202 | amt, 203 | ) 204 | }; 205 | if version != 1 { 206 | return Err(io::Error::new( 207 | io::ErrorKind::Other, 208 | "only HTTP/1.1 accepted", 209 | )); 210 | } 211 | let data = src.split_to(amt).freeze(); 212 | let mut ret = Request::builder(); 213 | ret = ret.method(&data[method.0..method.1]); 214 | let s = data.slice(path.0..path.1); 215 | let s = unsafe { String::from_utf8_unchecked(Vec::from(s.as_ref())) }; 216 | ret = ret.uri(s); 217 | ret = ret.version(http::Version::HTTP_11); 218 | for header in headers.iter() { 219 | let (k, v) = match *header { 220 | Some((ref k, ref v)) => (k, v), 221 | None => break, 222 | }; 223 | let value = HeaderValue::from_bytes(data.slice(v.0..v.1).as_ref()) 224 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "header decode error"))?; 225 | ret = ret.header(&data[k.0..k.1], value); 226 | } 227 | 228 | let req = ret 229 | .body(()) 230 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 231 | Ok(Some(req)) 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /examples/timers.rs: -------------------------------------------------------------------------------- 1 | use std::{thread, time::Duration}; 2 | 3 | use futures::{stream::FuturesUnordered, StreamExt}; 4 | use rand::Rng; 5 | use tl_async_runtime::{block_on, spawn, Sleep}; 6 | 7 | fn main() { 8 | block_on(async { 9 | let futs = FuturesUnordered::new(); 10 | for i in 0..10 { 11 | futs.push(spawn(async move { 12 | let ms = rand::thread_rng().gen_range(0..1000); 13 | Sleep::duration(Duration::from_millis(ms)).await; 14 | print_from_thread(i).await; 15 | })); 16 | } 17 | futs.collect::<()>().await; 18 | print_from_thread(10).await; 19 | }); 20 | } 21 | 22 | async fn print_from_thread(i: usize) { 23 | Sleep::duration(Duration::from_millis(10)).await; 24 | println!("Hi from inside future {}! {:?}", i, thread::current().id()); 25 | } 26 | -------------------------------------------------------------------------------- /src/executor.rs: -------------------------------------------------------------------------------- 1 | use futures::{channel::oneshot, pin_mut, Future, FutureExt}; 2 | use parking_lot::Mutex; 3 | use pin_project::pin_project; 4 | use std::{ 5 | cell::RefCell, 6 | ops::ControlFlow, 7 | pin::Pin, 8 | sync::Arc, 9 | task::{Context, Poll, Wake, Waker}, 10 | }; 11 | 12 | use crate::{reactor::Reactor, ready}; 13 | 14 | thread_local! { 15 | static EXECUTOR: RefCell>> = RefCell::new(None); 16 | } 17 | 18 | pub(crate) fn context(f: impl FnOnce(&Arc) -> R) -> R { 19 | EXECUTOR.with(|exec| { 20 | let exec = exec.borrow(); 21 | let exec = exec 22 | .as_ref() 23 | .expect("spawn called outside of an executor context"); 24 | f(exec) 25 | }) 26 | } 27 | 28 | struct TaskWaker { 29 | executor: Arc, 30 | task: Arc>>, 31 | } 32 | 33 | impl Wake for TaskWaker { 34 | fn wake(self: Arc) { 35 | if let Some(task) = self.task.lock().take() { 36 | self.executor.ready.signal(task); 37 | } 38 | } 39 | } 40 | 41 | pub type Task = Pin + Send + Sync + 'static>>; 42 | #[derive(Default)] 43 | pub struct Executor { 44 | pub(crate) reactor: Reactor, 45 | pub(crate) ready: ready::Queue, 46 | } 47 | 48 | impl Executor { 49 | /// Get a single task from the queue. 50 | /// 51 | /// Parks if there are no tasks available. 52 | fn acquire_task(&self) -> ControlFlow<(), Option> { 53 | let should_wait = self.ready.should_wait() && self.reactor.book_keeping() == 0; 54 | ControlFlow::Continue(self.ready.acquire_task(should_wait)) 55 | } 56 | 57 | /// Try run a single task. 58 | /// 59 | /// Parks if there are no tasks available. 60 | /// Returns Break if the task queue is broken. 61 | pub(crate) fn run_task(self: &Arc) -> ControlFlow<()> { 62 | // remove a task from the ledger to work on 63 | let mut fut = match self.acquire_task()? { 64 | Some(fut) => fut, 65 | None => return ControlFlow::Continue(()), 66 | }; 67 | 68 | let task_ref = Arc::new(Mutex::new(None)); 69 | 70 | // Create a new waker for the current task. 71 | // When the wake is called, it tells the executor 72 | // that the task is once again ready for work 73 | // and will be picked up by an available thread 74 | let executor = self.clone(); 75 | let waker = Waker::from(Arc::new(TaskWaker { 76 | task: task_ref.clone(), 77 | executor, 78 | })); 79 | let mut cx = Context::from_waker(&waker); 80 | 81 | if fut.as_mut().poll(&mut cx).is_pending() { 82 | task_ref.lock().replace(fut); 83 | } 84 | ControlFlow::Continue(()) 85 | } 86 | 87 | /// register this executor on the current thread 88 | pub(crate) fn register(self: &Arc) { 89 | EXECUTOR.with(|exec| *exec.borrow_mut() = Some(self.clone())); 90 | } 91 | 92 | /// Spawns a task in this executor 93 | pub(crate) fn spawn(&self, fut: F) -> JoinHandle 94 | where 95 | F: Future + Send + Sync + 'static, 96 | F::Output: Send + Sync + 'static, 97 | { 98 | let (sender, receiver) = oneshot::channel(); 99 | 100 | // Pin the future. Also wrap it s.t. it sends it's output over the channel 101 | let fut = Box::pin(fut.map(|out| sender.send(out).unwrap_or_default())); 102 | // insert the task into the runtime and signal that it is ready for processing 103 | self.ready.signal(fut); 104 | 105 | // return the handle to the reciever so that it can be `await`ed with it's output value 106 | JoinHandle(receiver) 107 | } 108 | 109 | /// Run a future to completion. 110 | /// 111 | /// Starts a new runtime and spawns the future on it. 112 | pub fn block_on(self: &Arc, fut: F) -> R 113 | where 114 | F: Future + Send + Sync + 'static, 115 | R: Send + Sync + 'static, 116 | { 117 | // register this thread as a worker 118 | self.register(); 119 | 120 | // spawn a bunch of worker threads 121 | for i in 1..self.ready.max_waiting { 122 | let exec = self.clone(); 123 | std::thread::Builder::new() 124 | .name(format!("tl-async-runtime-worker-{}", i)) 125 | .spawn(move || { 126 | // register this new thread as a worker in the runtime 127 | exec.register(); 128 | // Run tasks until told to exit 129 | while let ControlFlow::Continue(_) = exec.run_task() {} 130 | }) 131 | .unwrap(); 132 | } 133 | 134 | // Spawn the task in the newly created runtime 135 | let handle = self.spawn(fut); 136 | pin_mut!(handle); 137 | 138 | // Waker specifically for the main thread. 139 | // Used to wake up the main thread when the output value is ready 140 | let waker = Waker::from(Arc::new(ThreadWaker(self.clone()))); 141 | let mut cx = Context::from_waker(&waker); 142 | 143 | // Run the future to completion. 144 | loop { 145 | // if the output value is ready, return 146 | if let Poll::Ready(res) = handle.as_mut().poll(&mut cx) { 147 | break res; 148 | } 149 | 150 | // make the main thread busy and also run some tasks 151 | self.run_task(); 152 | } 153 | } 154 | } 155 | 156 | #[pin_project] 157 | pub struct JoinHandle(#[pin] oneshot::Receiver); 158 | 159 | impl Future for JoinHandle { 160 | type Output = R; 161 | 162 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 163 | let mut this = self.project(); 164 | // poll the inner channel for the spawned future's result 165 | this.0.as_mut().poll(cx).map(|x| x.unwrap()) 166 | } 167 | } 168 | 169 | struct ThreadWaker(Arc); 170 | 171 | impl Wake for ThreadWaker { 172 | fn wake(self: Arc) { 173 | self.0.ready.wake_all(); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![forbid(unsafe_code)] 2 | 3 | #[macro_use] 4 | extern crate educe; 5 | 6 | use executor::{context, Executor}; 7 | use std::future::Future; 8 | use std::sync::Arc; 9 | 10 | mod executor; 11 | mod reactor; 12 | mod ready; 13 | 14 | /// Networking specific handlers 15 | pub mod net; 16 | pub use executor::JoinHandle; 17 | pub use reactor::timers::Sleep; 18 | 19 | /// Spawn a future on the current runtime. 20 | /// Returns a new future that can be later awaited for it's output. 21 | /// Task execution begins eagerly, without needing you to await it 22 | pub fn spawn(fut: F) -> JoinHandle 23 | where 24 | F: Future + Send + Sync + 'static, 25 | R: Send + Sync + 'static, 26 | { 27 | context(|exec| exec.clone().spawn(fut)) 28 | } 29 | 30 | /// Run a future to completion. 31 | /// 32 | /// Starts a new runtime and spawns the future on it. 33 | pub fn block_on(fut: F) -> R 34 | where 35 | F: Future + Send + Sync + 'static, 36 | R: Send + Sync + 'static, 37 | { 38 | Arc::new(Executor::default()).block_on(fut) 39 | } 40 | -------------------------------------------------------------------------------- /src/net.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, Read, Write}, 3 | net::SocketAddr, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use futures::{channel::mpsc::UnboundedReceiver, Stream, StreamExt}; 9 | use mio::Interest; 10 | use pin_project::pin_project; 11 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 12 | 13 | use crate::reactor::io::{Event, Registration}; 14 | 15 | /// Listener for TCP events 16 | pub struct TcpListener { 17 | registration: Registration, 18 | } 19 | 20 | impl TcpListener { 21 | /// Create a new TcpListener bound to the socket 22 | pub fn bind(addr: SocketAddr) -> std::io::Result { 23 | let listener = mio::net::TcpListener::bind(addr)?; 24 | let registration = Registration::new(listener, Interest::READABLE)?; 25 | Ok(Self { registration }) 26 | } 27 | 28 | /// Accept a new TcpStream to communicate with 29 | pub async fn accept(&self) -> std::io::Result<(TcpStream, SocketAddr)> { 30 | loop { 31 | self.registration.events().next().await; 32 | match self.registration.accept() { 33 | Ok((stream, socket)) => break Ok((TcpStream::from_mio(stream)?, socket)), 34 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {} 35 | Err(e) => break Err(e), 36 | } 37 | } 38 | } 39 | } 40 | 41 | /// Handles communication over a TCP connection 42 | #[pin_project] 43 | pub struct TcpStream { 44 | registration: Registration, 45 | 46 | readable: Option<()>, 47 | writable: Option<()>, 48 | 49 | #[pin] 50 | events: UnboundedReceiver, 51 | } 52 | 53 | impl TcpStream { 54 | pub(crate) fn from_mio(stream: mio::net::TcpStream) -> std::io::Result { 55 | // register the stream to the OS 56 | let registration = Registration::new(stream, Interest::READABLE | Interest::WRITABLE)?; 57 | let events = registration.events(); 58 | Ok(Self { 59 | registration, 60 | readable: Some(()), 61 | writable: Some(()), 62 | events, 63 | }) 64 | } 65 | 66 | fn poll_event(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 67 | let mut this = self.as_mut().project(); 68 | let event = match this.events.as_mut().poll_next(cx) { 69 | Poll::Ready(Some(event)) => event, 70 | Poll::Ready(None) => { 71 | return Poll::Ready(Err(io::Error::new( 72 | io::ErrorKind::BrokenPipe, 73 | "channel disconnected", 74 | ))) 75 | } 76 | Poll::Pending => return Poll::Pending, 77 | }; 78 | 79 | if event.is_readable() { 80 | *this.readable = Some(()); 81 | } 82 | if event.is_writable() { 83 | *this.writable = Some(()); 84 | } 85 | Poll::Ready(Ok(())) 86 | } 87 | } 88 | 89 | impl AsyncRead for TcpStream { 90 | fn poll_read( 91 | mut self: Pin<&mut Self>, 92 | cx: &mut Context<'_>, 93 | buf: &mut ReadBuf<'_>, 94 | ) -> Poll> { 95 | loop { 96 | // if the stream is readable 97 | if let Some(()) = self.readable.take() { 98 | // try read some bytes 99 | let b = buf.initialize_unfilled(); 100 | match self.registration.read(b) { 101 | Ok(n) => { 102 | // if bytes were read, mark them 103 | buf.advance(n); 104 | // ensure that we attempt another read next time 105 | // since no new readable events will come through 106 | // https://docs.rs/mio/0.8.0/mio/struct.Poll.html#draining-readiness 107 | self.readable = Some(()); 108 | return Poll::Ready(Ok(())); 109 | } 110 | // if reading would block the thread, continue to event polling 111 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => {} 112 | // if there was some other io error, bail 113 | Err(e) => return Poll::Ready(Err(e)), 114 | } 115 | } 116 | 117 | match self.as_mut().poll_event(cx)? { 118 | Poll::Ready(()) => {} 119 | Poll::Pending => return Poll::Pending, 120 | } 121 | } 122 | } 123 | } 124 | 125 | impl AsyncWrite for TcpStream { 126 | fn poll_write( 127 | mut self: Pin<&mut Self>, 128 | cx: &mut Context<'_>, 129 | buf: &[u8], 130 | ) -> Poll> { 131 | loop { 132 | // if the stream is writeable 133 | if let Some(()) = self.writable.take() { 134 | // try write some bytes 135 | match self.registration.write(buf) { 136 | Ok(n) => { 137 | // ensure that we attempt another write next time 138 | // since no new writeable events will come through 139 | // https://docs.rs/mio/0.8.0/mio/struct.Poll.html#draining-readiness 140 | self.writable = Some(()); 141 | return Poll::Ready(Ok(n)); 142 | } 143 | // if writing would block the thread, continue to event polling 144 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => {} 145 | // if there was some other io error, bail 146 | Err(e) => return Poll::Ready(Err(e)), 147 | } 148 | } 149 | 150 | match self.as_mut().poll_event(cx)? { 151 | Poll::Ready(()) => {} 152 | Poll::Pending => return Poll::Pending, 153 | } 154 | } 155 | } 156 | 157 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 158 | loop { 159 | // if the stream is writeable 160 | if let Some(()) = self.writable.take() { 161 | // try flush the bytes 162 | match self.registration.flush() { 163 | Ok(()) => { 164 | // ensure that we attempt another write next time 165 | // since no new writeable events will come through 166 | // https://docs.rs/mio/0.8.0/mio/struct.Poll.html#draining-readiness 167 | self.writable = Some(()); 168 | return Poll::Ready(Ok(())); 169 | } 170 | // if flushing would block the thread, continue to event polling 171 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => {} 172 | // if there was some other io error, bail 173 | Err(e) => return Poll::Ready(Err(e)), 174 | } 175 | } 176 | 177 | match self.as_mut().poll_event(cx)? { 178 | Poll::Ready(()) => {} 179 | Poll::Pending => return Poll::Pending, 180 | } 181 | } 182 | } 183 | 184 | fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { 185 | // shutdowns are immediate 186 | Poll::Ready(self.registration.shutdown(std::net::Shutdown::Write)) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/reactor/io.rs: -------------------------------------------------------------------------------- 1 | use crate::{executor::context, Executor}; 2 | use chashmap::CHashMap; 3 | use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; 4 | use mio::event::Source; 5 | use parking_lot::RwLock; 6 | use rand::Rng; 7 | use std::{ 8 | ops::{Deref, DerefMut}, 9 | sync::Arc, 10 | time::Duration, 11 | }; 12 | 13 | #[derive(Debug)] 14 | pub(crate) struct Event(u8); 15 | impl Event { 16 | pub fn is_readable(&self) -> bool { 17 | self.0 & 1 != 0 || self.is_read_closed() 18 | } 19 | pub fn is_writable(&self) -> bool { 20 | self.0 & 2 != 0 || self.is_write_closed() 21 | } 22 | pub fn is_read_closed(&self) -> bool { 23 | self.0 & 4 != 0 24 | } 25 | pub fn is_write_closed(&self) -> bool { 26 | self.0 & 8 != 0 27 | } 28 | } 29 | 30 | impl From<&mio::event::Event> for Event { 31 | fn from(e: &mio::event::Event) -> Self { 32 | let mut event = 0; 33 | event |= (e.is_readable() as u8) << 1; 34 | event |= (e.is_writable() as u8) << 2; 35 | event |= (e.is_read_closed() as u8) << 3; 36 | event |= (e.is_write_closed() as u8) << 4; 37 | Event(event) 38 | } 39 | } 40 | 41 | pub(crate) struct Os { 42 | pub poll: RwLock, 43 | pub events: RwLock, 44 | pub tasks: CHashMap>, 45 | } 46 | 47 | impl Default for Os { 48 | fn default() -> Self { 49 | Self { 50 | poll: RwLock::new(mio::Poll::new().unwrap()), 51 | events: RwLock::new(mio::Events::with_capacity(128)), 52 | tasks: CHashMap::new(), 53 | } 54 | } 55 | } 56 | 57 | impl Os { 58 | /// Polls the OS for new events, and dispatches those to any awaiting tasks 59 | pub(crate) fn process(&self) -> usize { 60 | let mut n = 0; 61 | 62 | self.poll 63 | .write() 64 | .poll(&mut self.events.write(), Some(Duration::from_micros(100))) 65 | .unwrap(); 66 | 67 | let mut remove = vec![]; 68 | 69 | for event in &*self.events.read() { 70 | if let Some(sender) = self.tasks.get(&event.token()) { 71 | match sender.unbounded_send(event.into()) { 72 | Ok(()) => n += 1, 73 | Err(_) => remove.push(event.token()), 74 | } 75 | } 76 | } 77 | 78 | for token in remove { 79 | self.tasks.remove(&token); 80 | } 81 | 82 | n 83 | } 84 | } 85 | 86 | pub(crate) struct Registration { 87 | pub exec: Arc, 88 | pub token: mio::Token, 89 | pub source: S, 90 | } 91 | 92 | // allow internal access to the source 93 | impl Deref for Registration { 94 | type Target = S; 95 | 96 | fn deref(&self) -> &Self::Target { 97 | &self.source 98 | } 99 | } 100 | impl DerefMut for Registration { 101 | fn deref_mut(&mut self) -> &mut Self::Target { 102 | &mut self.source 103 | } 104 | } 105 | 106 | impl Registration { 107 | pub fn new(mut source: S, interests: mio::Interest) -> std::io::Result { 108 | context(|exec| { 109 | let token = mio::Token(rand::thread_rng().gen()); 110 | let poll = exec.reactor.os.poll.read(); 111 | poll.registry().register(&mut source, token, interests)?; 112 | Ok(Self { 113 | exec: exec.clone(), 114 | token, 115 | source, 116 | }) 117 | }) 118 | } 119 | 120 | // register this token on the event dispatcher 121 | // and return a receiver to it 122 | pub fn events(&self) -> UnboundedReceiver { 123 | let (sender, receiver) = unbounded(); 124 | self.exec.reactor.os.tasks.insert(self.token, sender); 125 | receiver 126 | } 127 | } 128 | 129 | impl Drop for Registration { 130 | fn drop(&mut self) { 131 | // deregister the source from the OS 132 | let poll = self.exec.reactor.os.poll.read(); 133 | poll.registry().deregister(&mut self.source).unwrap(); 134 | // remove the event dispatcher 135 | self.exec.reactor.os.tasks.remove(&self.token); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/reactor/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod io; 2 | pub mod timers; 3 | 4 | #[derive(Default)] 5 | pub struct Reactor { 6 | os: io::Os, 7 | timers: timers::Queue, 8 | } 9 | 10 | impl Reactor { 11 | // this is run by any thread that currently is not busy. 12 | // It manages the timers and OS polling in order to wake up tasks 13 | pub fn book_keeping(&self) -> usize { 14 | let mut n = 0; 15 | // get the current task timers that have elapsed and insert them into the ready tasks 16 | for task in &self.timers { 17 | task.wake(); 18 | n += 1; 19 | } 20 | 21 | // get the OS events 22 | n + self.os.process() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/reactor/timers.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Reverse, 3 | pin::Pin, 4 | task::{Context, Poll, Waker}, 5 | time::{Duration, Instant}, 6 | }; 7 | 8 | use futures::Future; 9 | use parking_lot::{Mutex, MutexGuard}; 10 | use pin_project::pin_project; 11 | 12 | use crate::executor::context; 13 | 14 | type TimerQueue = PriorityQueue>; 15 | #[derive(Default)] 16 | pub(crate) struct Queue(Mutex); 17 | impl Queue { 18 | pub fn insert(&self, instant: Instant, task: Waker) { 19 | let entry = PriorityQueueEntry(task, Reverse(instant)); 20 | let mut queue = self.0.lock(); 21 | let index = match queue.binary_search(&entry) { 22 | Ok(index) => index, 23 | Err(index) => index, 24 | }; 25 | queue.insert(index, entry); 26 | } 27 | } 28 | 29 | impl<'a> IntoIterator for &'a Queue { 30 | type Item = Waker; 31 | type IntoIter = QueueIter<'a>; 32 | 33 | fn into_iter(self) -> Self::IntoIter { 34 | QueueIter(self.0.lock(), Instant::now()) 35 | } 36 | } 37 | 38 | pub(crate) struct QueueIter<'a>(MutexGuard<'a, TimerQueue>, Instant); 39 | impl<'a> Iterator for QueueIter<'a> { 40 | type Item = Waker; 41 | 42 | fn next(&mut self) -> Option { 43 | let PriorityQueueEntry(task, Reverse(time)) = self.0.pop()?; 44 | if time > self.1 { 45 | self.0.push(PriorityQueueEntry(task, Reverse(time))); 46 | None 47 | } else { 48 | Some(task) 49 | } 50 | } 51 | } 52 | 53 | /// Future for sleeping fixed amounts of time. 54 | /// Does not block the thread 55 | #[pin_project] 56 | pub struct Sleep { 57 | instant: Instant, 58 | } 59 | 60 | impl Future for Sleep { 61 | type Output = (); 62 | 63 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 64 | let instant = *self.project().instant; 65 | // if the future is not yet ready 66 | if instant > Instant::now() { 67 | context(|exec| exec.reactor.timers.insert(instant, cx.waker().clone())); 68 | Poll::Pending 69 | } else { 70 | Poll::Ready(()) 71 | } 72 | } 73 | } 74 | 75 | impl Sleep { 76 | /// sleep until a specific point in time 77 | pub fn until(instant: Instant) -> Sleep { 78 | Self { instant } 79 | } 80 | /// sleep for a specific duration of time 81 | pub fn duration(duration: Duration) -> Sleep { 82 | Sleep::until(Instant::now() + duration) 83 | } 84 | } 85 | 86 | // eq/ord only by P 87 | #[derive(Educe)] 88 | #[educe(PartialEq(bound = "P: std::cmp::PartialEq"))] 89 | #[educe(Eq(bound = "P: std::cmp::Eq"))] 90 | #[educe(PartialOrd(bound = "P: std::cmp::PartialOrd"))] 91 | #[educe(Ord(bound = "P: std::cmp::Ord"))] 92 | struct PriorityQueueEntry( 93 | #[educe(PartialEq(ignore))] 94 | #[educe(Eq(ignore))] 95 | #[educe(PartialOrd(ignore))] 96 | #[educe(Ord(ignore))] 97 | I, 98 | P, 99 | ); 100 | 101 | type PriorityQueue = Vec>; 102 | -------------------------------------------------------------------------------- /src/ready.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::{Condvar, Mutex}; 2 | use std::{ 3 | collections::VecDeque, 4 | sync::atomic::{AtomicUsize, Ordering}, 5 | }; 6 | 7 | #[derive(Educe)] 8 | #[educe(Default)] 9 | pub(crate) struct Queue { 10 | len: AtomicUsize, 11 | tasks: Mutex>, 12 | waker: Condvar, 13 | waiting: AtomicUsize, 14 | #[educe(Default(expression = "get_thread_count()"))] 15 | pub max_waiting: usize, 16 | } 17 | 18 | fn get_thread_count() -> usize { 19 | std::thread::available_parallelism().map_or(4, |t| t.get()) 20 | } 21 | 22 | impl Queue { 23 | /// Signals that a task is now ready to be worked on 24 | pub(crate) fn signal(&self, task: crate::executor::Task) { 25 | self.tasks.lock().push_back(task); 26 | self.len.fetch_add(1, Ordering::Relaxed); 27 | self.waker.notify_one(); 28 | } 29 | 30 | pub fn wake_all(&self) { 31 | self.waker.notify_all(); 32 | } 33 | 34 | pub fn should_wait(&self) -> bool { 35 | self.len.load(Ordering::SeqCst) == 0 36 | } 37 | 38 | /// Get a single task from the queue. 39 | /// 40 | /// Parks if there are no tasks available. 41 | /// Returns Break if the task queue is broken. 42 | pub fn acquire_task(&self, should_wait: bool) -> Option { 43 | let mut tasks = self.tasks.lock(); 44 | if should_wait { 45 | let waiting = self.waiting.fetch_add(1, Ordering::Relaxed); 46 | if waiting + 1 < self.max_waiting { 47 | self.waker.wait(&mut tasks); 48 | } 49 | self.waiting.fetch_sub(1, Ordering::Relaxed); 50 | } 51 | match tasks.pop_front() { 52 | Some(t) => { 53 | self.len.fetch_sub(1, Ordering::Relaxed); 54 | Some(t) 55 | } 56 | None => None, 57 | } 58 | } 59 | } 60 | --------------------------------------------------------------------------------