├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── alloc.rs ├── async.rs ├── throughput.rs └── uncontended.rs ├── results ├── 9343.csv ├── apple-m2-air.csv ├── atom-d2700.csv └── tiamat.csv ├── s ├── check ├── ci ├── doc ├── lint ├── miri └── publish ├── src ├── example.md └── lib.rs └── tests ├── api.rs ├── bounded.rs ├── fixture.rs └── unbounded.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | env: 6 | RUST_BACKTRACE: 1 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | matrix: 12 | image: 13 | - ubuntu-latest 14 | - windows-latest 15 | - macos-latest # ARM 16 | - macos-13 # Intel 17 | 18 | runs-on: ${{ matrix.image }} 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Install stable toolchain 23 | uses: dtolnay/rust-toolchain@stable 24 | - uses: taiki-e/install-action@cargo-hack 25 | - run: cargo hack test --feature-powerset 26 | 27 | minimum-dependency-versions: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | - name: Install nightly toolchain 33 | uses: dtolnay/rust-toolchain@nightly 34 | with: 35 | components: miri,rust-src 36 | - run: cargo +nightly -Z minimal-versions update 37 | - run: cargo +nightly check --all-targets --all-features 38 | 39 | miri: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Checkout 43 | uses: actions/checkout@v4 44 | - name: Install nightly toolchain 45 | uses: dtolnay/rust-toolchain@nightly 46 | with: 47 | components: miri,rust-src 48 | - run: cargo +nightly miri test 49 | 50 | asan: 51 | runs-on: ubuntu-latest 52 | env: 53 | # ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-14 ? 54 | RUSTFLAGS: -Zsanitizer=address 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v4 58 | - name: Install nightly toolchain 59 | uses: dtolnay/rust-toolchain@nightly 60 | with: 61 | components: rust-src 62 | - run: cargo +nightly test -Zbuild-std --target x86_64-unknown-linux-gnu --lib --bins --tests 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "One" 2 | imports_granularity = "Item" 3 | -------------------------------------------------------------------------------- /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.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys", 77 | ] 78 | 79 | [[package]] 80 | name = "anyhow" 81 | version = "1.0.98" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 84 | 85 | [[package]] 86 | name = "arrayvec" 87 | version = "0.7.6" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 90 | 91 | [[package]] 92 | name = "async-channel" 93 | version = "1.9.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 96 | dependencies = [ 97 | "concurrent-queue", 98 | "event-listener 2.5.3", 99 | "futures-core", 100 | ] 101 | 102 | [[package]] 103 | name = "async-channel" 104 | version = "2.3.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" 107 | dependencies = [ 108 | "concurrent-queue", 109 | "event-listener-strategy", 110 | "futures-core", 111 | "pin-project-lite", 112 | ] 113 | 114 | [[package]] 115 | name = "async-executor" 116 | version = "1.13.1" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" 119 | dependencies = [ 120 | "async-task", 121 | "concurrent-queue", 122 | "fastrand", 123 | "futures-lite", 124 | "slab", 125 | ] 126 | 127 | [[package]] 128 | name = "async-global-executor" 129 | version = "2.4.1" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" 132 | dependencies = [ 133 | "async-channel 2.3.1", 134 | "async-executor", 135 | "async-io", 136 | "async-lock", 137 | "blocking", 138 | "futures-lite", 139 | "once_cell", 140 | ] 141 | 142 | [[package]] 143 | name = "async-io" 144 | version = "2.4.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" 147 | dependencies = [ 148 | "async-lock", 149 | "cfg-if", 150 | "concurrent-queue", 151 | "futures-io", 152 | "futures-lite", 153 | "parking", 154 | "polling", 155 | "rustix 0.38.44", 156 | "slab", 157 | "tracing", 158 | "windows-sys", 159 | ] 160 | 161 | [[package]] 162 | name = "async-lock" 163 | version = "3.4.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" 166 | dependencies = [ 167 | "event-listener 5.4.0", 168 | "event-listener-strategy", 169 | "pin-project-lite", 170 | ] 171 | 172 | [[package]] 173 | name = "async-std" 174 | version = "1.13.1" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" 177 | dependencies = [ 178 | "async-channel 1.9.0", 179 | "async-global-executor", 180 | "async-io", 181 | "async-lock", 182 | "crossbeam-utils", 183 | "futures-channel", 184 | "futures-core", 185 | "futures-io", 186 | "futures-lite", 187 | "gloo-timers", 188 | "kv-log-macro", 189 | "log", 190 | "memchr", 191 | "once_cell", 192 | "pin-project-lite", 193 | "pin-utils", 194 | "slab", 195 | "wasm-bindgen-futures", 196 | ] 197 | 198 | [[package]] 199 | name = "async-task" 200 | version = "4.7.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 203 | 204 | [[package]] 205 | name = "atomic-waker" 206 | version = "1.1.2" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 209 | 210 | [[package]] 211 | name = "autocfg" 212 | version = "1.4.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 215 | 216 | [[package]] 217 | name = "backtrace" 218 | version = "0.3.74" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 221 | dependencies = [ 222 | "addr2line", 223 | "cfg-if", 224 | "libc", 225 | "miniz_oxide", 226 | "object", 227 | "rustc-demangle", 228 | "windows-targets", 229 | ] 230 | 231 | [[package]] 232 | name = "batch-channel" 233 | version = "0.4.3" 234 | dependencies = [ 235 | "anyhow", 236 | "async-channel 2.3.1", 237 | "async-std", 238 | "clap", 239 | "crossbeam", 240 | "divan", 241 | "futures", 242 | "futures-core", 243 | "itertools", 244 | "kanal", 245 | "lazy_static", 246 | "pin-project", 247 | "pinned-mutex", 248 | "splitrc", 249 | "tokio", 250 | "wakerset", 251 | ] 252 | 253 | [[package]] 254 | name = "bitflags" 255 | version = "2.9.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 258 | 259 | [[package]] 260 | name = "blocking" 261 | version = "1.6.1" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" 264 | dependencies = [ 265 | "async-channel 2.3.1", 266 | "async-task", 267 | "futures-io", 268 | "futures-lite", 269 | "piper", 270 | ] 271 | 272 | [[package]] 273 | name = "bumpalo" 274 | version = "3.17.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 277 | 278 | [[package]] 279 | name = "cfg-if" 280 | version = "1.0.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 283 | 284 | [[package]] 285 | name = "clap" 286 | version = "4.5.37" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" 289 | dependencies = [ 290 | "clap_builder", 291 | "clap_derive", 292 | ] 293 | 294 | [[package]] 295 | name = "clap_builder" 296 | version = "4.5.37" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" 299 | dependencies = [ 300 | "anstream", 301 | "anstyle", 302 | "clap_lex", 303 | "strsim", 304 | "terminal_size", 305 | ] 306 | 307 | [[package]] 308 | name = "clap_derive" 309 | version = "4.5.32" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 312 | dependencies = [ 313 | "heck", 314 | "proc-macro2", 315 | "quote", 316 | "syn", 317 | ] 318 | 319 | [[package]] 320 | name = "clap_lex" 321 | version = "0.7.4" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 324 | 325 | [[package]] 326 | name = "colorchoice" 327 | version = "1.0.3" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 330 | 331 | [[package]] 332 | name = "concurrent-queue" 333 | version = "2.5.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 336 | dependencies = [ 337 | "crossbeam-utils", 338 | ] 339 | 340 | [[package]] 341 | name = "condtype" 342 | version = "1.3.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" 345 | 346 | [[package]] 347 | name = "crossbeam" 348 | version = "0.8.4" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" 351 | dependencies = [ 352 | "crossbeam-channel", 353 | "crossbeam-deque", 354 | "crossbeam-epoch", 355 | "crossbeam-queue", 356 | "crossbeam-utils", 357 | ] 358 | 359 | [[package]] 360 | name = "crossbeam-channel" 361 | version = "0.5.15" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 364 | dependencies = [ 365 | "crossbeam-utils", 366 | ] 367 | 368 | [[package]] 369 | name = "crossbeam-deque" 370 | version = "0.8.6" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 373 | dependencies = [ 374 | "crossbeam-epoch", 375 | "crossbeam-utils", 376 | ] 377 | 378 | [[package]] 379 | name = "crossbeam-epoch" 380 | version = "0.9.18" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 383 | dependencies = [ 384 | "crossbeam-utils", 385 | ] 386 | 387 | [[package]] 388 | name = "crossbeam-queue" 389 | version = "0.3.12" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 392 | dependencies = [ 393 | "crossbeam-utils", 394 | ] 395 | 396 | [[package]] 397 | name = "crossbeam-utils" 398 | version = "0.8.21" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 401 | 402 | [[package]] 403 | name = "divan" 404 | version = "0.1.21" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "a405457ec78b8fe08b0e32b4a3570ab5dff6dd16eb9e76a5ee0a9d9cbd898933" 407 | dependencies = [ 408 | "cfg-if", 409 | "clap", 410 | "condtype", 411 | "divan-macros", 412 | "libc", 413 | "regex-lite", 414 | ] 415 | 416 | [[package]] 417 | name = "divan-macros" 418 | version = "0.1.21" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323" 421 | dependencies = [ 422 | "proc-macro2", 423 | "quote", 424 | "syn", 425 | ] 426 | 427 | [[package]] 428 | name = "either" 429 | version = "1.15.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 432 | 433 | [[package]] 434 | name = "errno" 435 | version = "0.3.11" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 438 | dependencies = [ 439 | "libc", 440 | "windows-sys", 441 | ] 442 | 443 | [[package]] 444 | name = "event-listener" 445 | version = "2.5.3" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 448 | 449 | [[package]] 450 | name = "event-listener" 451 | version = "5.4.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 454 | dependencies = [ 455 | "concurrent-queue", 456 | "parking", 457 | "pin-project-lite", 458 | ] 459 | 460 | [[package]] 461 | name = "event-listener-strategy" 462 | version = "0.5.4" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 465 | dependencies = [ 466 | "event-listener 5.4.0", 467 | "pin-project-lite", 468 | ] 469 | 470 | [[package]] 471 | name = "fastrand" 472 | version = "2.3.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 475 | 476 | [[package]] 477 | name = "futures" 478 | version = "0.3.31" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 481 | dependencies = [ 482 | "futures-channel", 483 | "futures-core", 484 | "futures-executor", 485 | "futures-io", 486 | "futures-sink", 487 | "futures-task", 488 | "futures-util", 489 | ] 490 | 491 | [[package]] 492 | name = "futures-channel" 493 | version = "0.3.31" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 496 | dependencies = [ 497 | "futures-core", 498 | "futures-sink", 499 | ] 500 | 501 | [[package]] 502 | name = "futures-core" 503 | version = "0.3.31" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 506 | 507 | [[package]] 508 | name = "futures-executor" 509 | version = "0.3.31" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 512 | dependencies = [ 513 | "futures-core", 514 | "futures-task", 515 | "futures-util", 516 | ] 517 | 518 | [[package]] 519 | name = "futures-io" 520 | version = "0.3.31" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 523 | 524 | [[package]] 525 | name = "futures-lite" 526 | version = "2.6.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" 529 | dependencies = [ 530 | "fastrand", 531 | "futures-core", 532 | "futures-io", 533 | "parking", 534 | "pin-project-lite", 535 | ] 536 | 537 | [[package]] 538 | name = "futures-macro" 539 | version = "0.3.31" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 542 | dependencies = [ 543 | "proc-macro2", 544 | "quote", 545 | "syn", 546 | ] 547 | 548 | [[package]] 549 | name = "futures-sink" 550 | version = "0.3.31" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 553 | 554 | [[package]] 555 | name = "futures-task" 556 | version = "0.3.31" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 559 | 560 | [[package]] 561 | name = "futures-util" 562 | version = "0.3.31" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 565 | dependencies = [ 566 | "futures-channel", 567 | "futures-core", 568 | "futures-io", 569 | "futures-macro", 570 | "futures-sink", 571 | "futures-task", 572 | "memchr", 573 | "pin-project-lite", 574 | "pin-utils", 575 | "slab", 576 | ] 577 | 578 | [[package]] 579 | name = "generator" 580 | version = "0.8.4" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" 583 | dependencies = [ 584 | "cfg-if", 585 | "libc", 586 | "log", 587 | "rustversion", 588 | "windows", 589 | ] 590 | 591 | [[package]] 592 | name = "gimli" 593 | version = "0.31.1" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 596 | 597 | [[package]] 598 | name = "gloo-timers" 599 | version = "0.3.0" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 602 | dependencies = [ 603 | "futures-channel", 604 | "futures-core", 605 | "js-sys", 606 | "wasm-bindgen", 607 | ] 608 | 609 | [[package]] 610 | name = "heck" 611 | version = "0.5.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 614 | 615 | [[package]] 616 | name = "hermit-abi" 617 | version = "0.4.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 620 | 621 | [[package]] 622 | name = "is_terminal_polyfill" 623 | version = "1.70.1" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 626 | 627 | [[package]] 628 | name = "itertools" 629 | version = "0.14.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 632 | dependencies = [ 633 | "either", 634 | ] 635 | 636 | [[package]] 637 | name = "js-sys" 638 | version = "0.3.77" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 641 | dependencies = [ 642 | "once_cell", 643 | "wasm-bindgen", 644 | ] 645 | 646 | [[package]] 647 | name = "kanal" 648 | version = "0.1.1" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "9e3953adf0cd667798b396c2fa13552d6d9b3269d7dd1154c4c416442d1ff574" 651 | dependencies = [ 652 | "futures-core", 653 | "lock_api", 654 | ] 655 | 656 | [[package]] 657 | name = "kv-log-macro" 658 | version = "1.0.7" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 661 | dependencies = [ 662 | "log", 663 | ] 664 | 665 | [[package]] 666 | name = "lazy_static" 667 | version = "1.5.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 670 | 671 | [[package]] 672 | name = "libc" 673 | version = "0.2.172" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 676 | 677 | [[package]] 678 | name = "linux-raw-sys" 679 | version = "0.4.15" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 682 | 683 | [[package]] 684 | name = "linux-raw-sys" 685 | version = "0.9.4" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 688 | 689 | [[package]] 690 | name = "lock_api" 691 | version = "0.4.12" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 694 | dependencies = [ 695 | "autocfg", 696 | "scopeguard", 697 | ] 698 | 699 | [[package]] 700 | name = "log" 701 | version = "0.4.27" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 704 | dependencies = [ 705 | "value-bag", 706 | ] 707 | 708 | [[package]] 709 | name = "loom" 710 | version = "0.7.2" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 713 | dependencies = [ 714 | "cfg-if", 715 | "generator", 716 | "scoped-tls", 717 | "tracing", 718 | "tracing-subscriber", 719 | ] 720 | 721 | [[package]] 722 | name = "matchers" 723 | version = "0.1.0" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 726 | dependencies = [ 727 | "regex-automata 0.1.10", 728 | ] 729 | 730 | [[package]] 731 | name = "memchr" 732 | version = "2.7.4" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 735 | 736 | [[package]] 737 | name = "miniz_oxide" 738 | version = "0.8.8" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 741 | dependencies = [ 742 | "adler2", 743 | ] 744 | 745 | [[package]] 746 | name = "nu-ansi-term" 747 | version = "0.46.0" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 750 | dependencies = [ 751 | "overload", 752 | "winapi", 753 | ] 754 | 755 | [[package]] 756 | name = "object" 757 | version = "0.36.7" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 760 | dependencies = [ 761 | "memchr", 762 | ] 763 | 764 | [[package]] 765 | name = "once_cell" 766 | version = "1.21.3" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 769 | 770 | [[package]] 771 | name = "overload" 772 | version = "0.1.1" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 775 | 776 | [[package]] 777 | name = "parking" 778 | version = "2.2.1" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 781 | 782 | [[package]] 783 | name = "parking_lot" 784 | version = "0.12.3" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 787 | dependencies = [ 788 | "lock_api", 789 | "parking_lot_core", 790 | ] 791 | 792 | [[package]] 793 | name = "parking_lot_core" 794 | version = "0.9.10" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 797 | dependencies = [ 798 | "cfg-if", 799 | "libc", 800 | "redox_syscall", 801 | "smallvec", 802 | "windows-targets", 803 | ] 804 | 805 | [[package]] 806 | name = "pin-project" 807 | version = "1.1.10" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 810 | dependencies = [ 811 | "pin-project-internal", 812 | ] 813 | 814 | [[package]] 815 | name = "pin-project-internal" 816 | version = "1.1.10" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 819 | dependencies = [ 820 | "proc-macro2", 821 | "quote", 822 | "syn", 823 | ] 824 | 825 | [[package]] 826 | name = "pin-project-lite" 827 | version = "0.2.16" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 830 | 831 | [[package]] 832 | name = "pin-utils" 833 | version = "0.1.0" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 836 | 837 | [[package]] 838 | name = "pinned-mutex" 839 | version = "0.3.2" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "468bf9b6622aa39e7494cafc8fcee47e9731dce5ebe06187b10e45075b26ce72" 842 | dependencies = [ 843 | "parking_lot", 844 | ] 845 | 846 | [[package]] 847 | name = "piper" 848 | version = "0.2.4" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 851 | dependencies = [ 852 | "atomic-waker", 853 | "fastrand", 854 | "futures-io", 855 | ] 856 | 857 | [[package]] 858 | name = "polling" 859 | version = "3.7.4" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" 862 | dependencies = [ 863 | "cfg-if", 864 | "concurrent-queue", 865 | "hermit-abi", 866 | "pin-project-lite", 867 | "rustix 0.38.44", 868 | "tracing", 869 | "windows-sys", 870 | ] 871 | 872 | [[package]] 873 | name = "proc-macro2" 874 | version = "1.0.95" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 877 | dependencies = [ 878 | "unicode-ident", 879 | ] 880 | 881 | [[package]] 882 | name = "quote" 883 | version = "1.0.40" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 886 | dependencies = [ 887 | "proc-macro2", 888 | ] 889 | 890 | [[package]] 891 | name = "redox_syscall" 892 | version = "0.5.11" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 895 | dependencies = [ 896 | "bitflags", 897 | ] 898 | 899 | [[package]] 900 | name = "regex" 901 | version = "1.11.1" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 904 | dependencies = [ 905 | "aho-corasick", 906 | "memchr", 907 | "regex-automata 0.4.9", 908 | "regex-syntax 0.8.5", 909 | ] 910 | 911 | [[package]] 912 | name = "regex-automata" 913 | version = "0.1.10" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 916 | dependencies = [ 917 | "regex-syntax 0.6.29", 918 | ] 919 | 920 | [[package]] 921 | name = "regex-automata" 922 | version = "0.4.9" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 925 | dependencies = [ 926 | "aho-corasick", 927 | "memchr", 928 | "regex-syntax 0.8.5", 929 | ] 930 | 931 | [[package]] 932 | name = "regex-lite" 933 | version = "0.1.6" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" 936 | 937 | [[package]] 938 | name = "regex-syntax" 939 | version = "0.6.29" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 942 | 943 | [[package]] 944 | name = "regex-syntax" 945 | version = "0.8.5" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 948 | 949 | [[package]] 950 | name = "rustc-demangle" 951 | version = "0.1.24" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 954 | 955 | [[package]] 956 | name = "rustix" 957 | version = "0.38.44" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 960 | dependencies = [ 961 | "bitflags", 962 | "errno", 963 | "libc", 964 | "linux-raw-sys 0.4.15", 965 | "windows-sys", 966 | ] 967 | 968 | [[package]] 969 | name = "rustix" 970 | version = "1.0.5" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 973 | dependencies = [ 974 | "bitflags", 975 | "errno", 976 | "libc", 977 | "linux-raw-sys 0.9.4", 978 | "windows-sys", 979 | ] 980 | 981 | [[package]] 982 | name = "rustversion" 983 | version = "1.0.20" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 986 | 987 | [[package]] 988 | name = "scoped-tls" 989 | version = "1.0.1" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 992 | 993 | [[package]] 994 | name = "scopeguard" 995 | version = "1.2.0" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 998 | 999 | [[package]] 1000 | name = "sharded-slab" 1001 | version = "0.1.7" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1004 | dependencies = [ 1005 | "lazy_static", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "slab" 1010 | version = "0.4.9" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1013 | dependencies = [ 1014 | "autocfg", 1015 | ] 1016 | 1017 | [[package]] 1018 | name = "smallvec" 1019 | version = "1.15.0" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1022 | 1023 | [[package]] 1024 | name = "splitrc" 1025 | version = "0.1.12" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "4a2ce692d1bbd1cbda76240618a3a521d2306df6970be32fed03f8d9ac21c9a2" 1028 | dependencies = [ 1029 | "loom", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "strsim" 1034 | version = "0.11.1" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1037 | 1038 | [[package]] 1039 | name = "syn" 1040 | version = "2.0.100" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 1043 | dependencies = [ 1044 | "proc-macro2", 1045 | "quote", 1046 | "unicode-ident", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "terminal_size" 1051 | version = "0.4.2" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" 1054 | dependencies = [ 1055 | "rustix 1.0.5", 1056 | "windows-sys", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "thread_local" 1061 | version = "1.1.8" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1064 | dependencies = [ 1065 | "cfg-if", 1066 | "once_cell", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "tokio" 1071 | version = "1.44.2" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" 1074 | dependencies = [ 1075 | "backtrace", 1076 | "pin-project-lite", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "tracing" 1081 | version = "0.1.41" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1084 | dependencies = [ 1085 | "pin-project-lite", 1086 | "tracing-core", 1087 | ] 1088 | 1089 | [[package]] 1090 | name = "tracing-core" 1091 | version = "0.1.33" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1094 | dependencies = [ 1095 | "once_cell", 1096 | "valuable", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "tracing-log" 1101 | version = "0.2.0" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1104 | dependencies = [ 1105 | "log", 1106 | "once_cell", 1107 | "tracing-core", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "tracing-subscriber" 1112 | version = "0.3.19" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1115 | dependencies = [ 1116 | "matchers", 1117 | "nu-ansi-term", 1118 | "once_cell", 1119 | "regex", 1120 | "sharded-slab", 1121 | "smallvec", 1122 | "thread_local", 1123 | "tracing", 1124 | "tracing-core", 1125 | "tracing-log", 1126 | ] 1127 | 1128 | [[package]] 1129 | name = "unicode-ident" 1130 | version = "1.0.18" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1133 | 1134 | [[package]] 1135 | name = "utf8parse" 1136 | version = "0.2.2" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1139 | 1140 | [[package]] 1141 | name = "valuable" 1142 | version = "0.1.1" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1145 | 1146 | [[package]] 1147 | name = "value-bag" 1148 | version = "1.11.1" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" 1151 | 1152 | [[package]] 1153 | name = "wakerset" 1154 | version = "0.1.1" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "73c092ed67dbf45badda8c45b10c15d20d0389bddefacd99ba33ae00a6076a9d" 1157 | dependencies = [ 1158 | "arrayvec", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "wasm-bindgen" 1163 | version = "0.2.100" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1166 | dependencies = [ 1167 | "cfg-if", 1168 | "once_cell", 1169 | "rustversion", 1170 | "wasm-bindgen-macro", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "wasm-bindgen-backend" 1175 | version = "0.2.100" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1178 | dependencies = [ 1179 | "bumpalo", 1180 | "log", 1181 | "proc-macro2", 1182 | "quote", 1183 | "syn", 1184 | "wasm-bindgen-shared", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "wasm-bindgen-futures" 1189 | version = "0.4.50" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 1192 | dependencies = [ 1193 | "cfg-if", 1194 | "js-sys", 1195 | "once_cell", 1196 | "wasm-bindgen", 1197 | "web-sys", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "wasm-bindgen-macro" 1202 | version = "0.2.100" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1205 | dependencies = [ 1206 | "quote", 1207 | "wasm-bindgen-macro-support", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "wasm-bindgen-macro-support" 1212 | version = "0.2.100" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1215 | dependencies = [ 1216 | "proc-macro2", 1217 | "quote", 1218 | "syn", 1219 | "wasm-bindgen-backend", 1220 | "wasm-bindgen-shared", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "wasm-bindgen-shared" 1225 | version = "0.2.100" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1228 | dependencies = [ 1229 | "unicode-ident", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "web-sys" 1234 | version = "0.3.77" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 1237 | dependencies = [ 1238 | "js-sys", 1239 | "wasm-bindgen", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "winapi" 1244 | version = "0.3.9" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1247 | dependencies = [ 1248 | "winapi-i686-pc-windows-gnu", 1249 | "winapi-x86_64-pc-windows-gnu", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "winapi-i686-pc-windows-gnu" 1254 | version = "0.4.0" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1257 | 1258 | [[package]] 1259 | name = "winapi-x86_64-pc-windows-gnu" 1260 | version = "0.4.0" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1263 | 1264 | [[package]] 1265 | name = "windows" 1266 | version = "0.58.0" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" 1269 | dependencies = [ 1270 | "windows-core", 1271 | "windows-targets", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "windows-core" 1276 | version = "0.58.0" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" 1279 | dependencies = [ 1280 | "windows-implement", 1281 | "windows-interface", 1282 | "windows-result", 1283 | "windows-strings", 1284 | "windows-targets", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "windows-implement" 1289 | version = "0.58.0" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" 1292 | dependencies = [ 1293 | "proc-macro2", 1294 | "quote", 1295 | "syn", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "windows-interface" 1300 | version = "0.58.0" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" 1303 | dependencies = [ 1304 | "proc-macro2", 1305 | "quote", 1306 | "syn", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "windows-result" 1311 | version = "0.2.0" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1314 | dependencies = [ 1315 | "windows-targets", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "windows-strings" 1320 | version = "0.1.0" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1323 | dependencies = [ 1324 | "windows-result", 1325 | "windows-targets", 1326 | ] 1327 | 1328 | [[package]] 1329 | name = "windows-sys" 1330 | version = "0.59.0" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1333 | dependencies = [ 1334 | "windows-targets", 1335 | ] 1336 | 1337 | [[package]] 1338 | name = "windows-targets" 1339 | version = "0.52.6" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1342 | dependencies = [ 1343 | "windows_aarch64_gnullvm", 1344 | "windows_aarch64_msvc", 1345 | "windows_i686_gnu", 1346 | "windows_i686_gnullvm", 1347 | "windows_i686_msvc", 1348 | "windows_x86_64_gnu", 1349 | "windows_x86_64_gnullvm", 1350 | "windows_x86_64_msvc", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "windows_aarch64_gnullvm" 1355 | version = "0.52.6" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1358 | 1359 | [[package]] 1360 | name = "windows_aarch64_msvc" 1361 | version = "0.52.6" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1364 | 1365 | [[package]] 1366 | name = "windows_i686_gnu" 1367 | version = "0.52.6" 1368 | source = "registry+https://github.com/rust-lang/crates.io-index" 1369 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1370 | 1371 | [[package]] 1372 | name = "windows_i686_gnullvm" 1373 | version = "0.52.6" 1374 | source = "registry+https://github.com/rust-lang/crates.io-index" 1375 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1376 | 1377 | [[package]] 1378 | name = "windows_i686_msvc" 1379 | version = "0.52.6" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1382 | 1383 | [[package]] 1384 | name = "windows_x86_64_gnu" 1385 | version = "0.52.6" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1388 | 1389 | [[package]] 1390 | name = "windows_x86_64_gnullvm" 1391 | version = "0.52.6" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1394 | 1395 | [[package]] 1396 | name = "windows_x86_64_msvc" 1397 | version = "0.52.6" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1400 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "batch-channel" 3 | version = "0.4.3" 4 | authors = ["Chad Austin "] 5 | edition = "2021" 6 | rust-version = "1.70" 7 | license = "MIT" 8 | description = "async channel that reduces overhead by reading and writing many values at once" 9 | repository = "https://github.com/chadaustin/batch-channel" 10 | keywords = ["async", "channel"] 11 | categories = ["asynchronous"] 12 | 13 | [profile.release-with-debug] 14 | inherits = "release" 15 | debug = true 16 | 17 | [profile.bench] 18 | debug = true 19 | 20 | [features] 21 | parking_lot = ["pinned-mutex/parking_lot"] 22 | fast = ["parking_lot"] 23 | 24 | [dependencies] 25 | futures-core = "0.3" 26 | pin-project = "1.1" 27 | pinned-mutex = "0.3.2" 28 | splitrc = "0.1.11" 29 | wakerset = "0.1" 30 | 31 | [dev-dependencies] 32 | anyhow = "1.0.14" 33 | async-channel = "2.2" 34 | async-std = "1.1" 35 | clap = { version = "4.5", features = ["derive"] } 36 | crossbeam = "0.8" 37 | divan = "0.1.1" 38 | futures = "0.3" 39 | itertools = "0.14.0" 40 | kanal = "0.1.0-pre8" 41 | lazy_static = "1.4" 42 | tokio = { version = "1", features = ["rt-multi-thread"] } 43 | 44 | [[bench]] 45 | name = "async" 46 | harness = false 47 | 48 | [[bench]] 49 | name = "alloc" 50 | harness = false 51 | 52 | [[bench]] 53 | name = "throughput" 54 | harness = false 55 | 56 | [[bench]] 57 | name = "uncontended" 58 | harness = false 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Chad Austin 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 | async MPMC channel that reduces overhead by reading and writing many 2 | values at once. 3 | 4 | Sometimes large volumes of small values are farmed out to workers 5 | through a channel. Consider directory traversal: each readdir() 6 | call produces a batch of directory entries. Batching can help the 7 | consumption side too. Consider querying SQLite with received 8 | values -- larger batches reduce the amortized cost of acquiring 9 | SQLite's lock and bulk queries can be issued. 10 | 11 | One can imagine natural, currently unimplemented, extensions to this 12 | crate: 13 | * Channels with priority 14 | * impls for `futures::sink::Sink` and `futures::stream::Stream` 15 | 16 | Ask if they would be helpful. 17 | -------------------------------------------------------------------------------- /benches/alloc.rs: -------------------------------------------------------------------------------- 1 | use divan::Bencher; 2 | 3 | #[divan::bench] 4 | fn alloc_unbounded() -> (batch_channel::Sender, batch_channel::Receiver) { 5 | batch_channel::unbounded() 6 | } 7 | 8 | #[divan::bench] 9 | fn alloc_dealloc_unbounded() { 10 | let _ = batch_channel::unbounded::(); 11 | } 12 | 13 | #[divan::bench] 14 | fn clone_rx(bencher: Bencher) { 15 | let (_tx, mut rx) = batch_channel::unbounded::(); 16 | bencher.bench_local(|| { 17 | rx = rx.clone(); 18 | }); 19 | } 20 | 21 | #[divan::bench] 22 | fn clone_tx(bencher: Bencher) { 23 | let (mut tx, _rx) = batch_channel::unbounded::(); 24 | bencher.bench_local(|| { 25 | tx = tx.clone(); 26 | }); 27 | } 28 | 29 | fn main() { 30 | divan::main() 31 | } 32 | -------------------------------------------------------------------------------- /benches/async.rs: -------------------------------------------------------------------------------- 1 | use anyhow::anyhow; 2 | use async_std::task::yield_now; 3 | use futures::executor::LocalPool; 4 | use futures::task::SpawnExt; 5 | use futures::StreamExt; 6 | use std::fmt; 7 | use std::future::Future; 8 | use std::sync::mpsc::TryRecvError; 9 | 10 | trait UnboundedChannel: 'static { 11 | type Sender: Send; 12 | type Receiver: Send; 13 | 14 | fn new() -> (Self::Sender, Self::Receiver); 15 | fn send(tx: &Self::Sender, value: T) -> anyhow::Result<()>; 16 | fn recv(rx: &mut Self::Receiver) -> impl Future> + Send; 17 | 18 | fn send_vec( 19 | tx: &Self::Sender, 20 | mut values: Vec, 21 | ) -> anyhow::Result> { 22 | for v in values.drain(..) { 23 | Self::send(tx, v)?; 24 | } 25 | Ok(values) 26 | } 27 | 28 | fn recv_batch( 29 | rx: &mut Self::Receiver, 30 | element_limit: usize, 31 | ) -> impl Future> + Send { 32 | async move { 33 | let mut v = Vec::with_capacity(element_limit); 34 | loop { 35 | match Self::recv(rx).await { 36 | Some(value) => { 37 | v.push(value); 38 | if v.len() == element_limit { 39 | return v; 40 | } 41 | } 42 | None => { 43 | return v; 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | struct BatchChannel; 52 | 53 | impl UnboundedChannel for BatchChannel { 54 | type Sender = batch_channel::SyncSender; 55 | type Receiver = batch_channel::Receiver; 56 | 57 | fn new() -> (Self::Sender, Self::Receiver) { 58 | let (tx, rx) = batch_channel::unbounded(); 59 | (tx.into_sync(), rx) 60 | } 61 | fn send(tx: &Self::Sender, value: T) -> anyhow::Result<()> { 62 | Ok(tx.send(value).map_err(|_| anyhow!("failed to send"))?) 63 | } 64 | async fn recv(rx: &mut Self::Receiver) -> Option { 65 | rx.recv().await 66 | } 67 | fn send_vec( 68 | tx: &Self::Sender, 69 | values: Vec, 70 | ) -> anyhow::Result> { 71 | Ok(tx.send_vec(values).map_err(|_| anyhow!("failed to send"))?) 72 | } 73 | async fn recv_batch(rx: &mut Self::Receiver, element_limit: usize) -> Vec { 74 | rx.recv_batch(element_limit).await 75 | } 76 | } 77 | 78 | struct StdChannel; 79 | 80 | impl UnboundedChannel for StdChannel { 81 | type Sender = std::sync::mpsc::Sender; 82 | type Receiver = std::sync::mpsc::Receiver; 83 | 84 | fn new() -> (Self::Sender, Self::Receiver) { 85 | std::sync::mpsc::channel() 86 | } 87 | fn send(tx: &Self::Sender, value: T) -> anyhow::Result<()> { 88 | Ok(tx.send(value).map_err(|_| anyhow!("failed to send"))?) 89 | } 90 | async fn recv(rx: &mut Self::Receiver) -> Option { 91 | loop { 92 | let r = rx.try_recv(); 93 | match r { 94 | Ok(value) => return Some(value), 95 | Err(TryRecvError::Empty) => { 96 | yield_now().await; 97 | continue; 98 | } 99 | Err(TryRecvError::Disconnected) => return None, 100 | } 101 | } 102 | } 103 | } 104 | 105 | struct CrossbeamChannel; 106 | 107 | impl UnboundedChannel for CrossbeamChannel { 108 | type Sender = crossbeam::channel::Sender; 109 | type Receiver = crossbeam::channel::Receiver; 110 | 111 | fn new() -> (Self::Sender, Self::Receiver) { 112 | crossbeam::channel::unbounded() 113 | } 114 | fn send(tx: &Self::Sender, value: T) -> anyhow::Result<()> { 115 | Ok(tx.send(value).map_err(|_| anyhow!("failed to send"))?) 116 | } 117 | async fn recv(rx: &mut Self::Receiver) -> Option { 118 | loop { 119 | let r = rx.try_recv(); 120 | match r { 121 | Ok(value) => return Some(value), 122 | Err(crossbeam::channel::TryRecvError::Empty) => { 123 | yield_now().await; 124 | continue; 125 | } 126 | Err(crossbeam::channel::TryRecvError::Disconnected) => return None, 127 | } 128 | } 129 | } 130 | } 131 | 132 | struct FuturesChannel; 133 | 134 | impl UnboundedChannel for FuturesChannel { 135 | type Sender = futures::channel::mpsc::UnboundedSender; 136 | type Receiver = futures::channel::mpsc::UnboundedReceiver; 137 | 138 | fn new() -> (Self::Sender, Self::Receiver) { 139 | futures::channel::mpsc::unbounded() 140 | } 141 | fn send(tx: &Self::Sender, value: T) -> anyhow::Result<()> { 142 | Ok(tx 143 | .unbounded_send(value) 144 | .map_err(|_| anyhow!("failed to send"))?) 145 | } 146 | async fn recv(rx: &mut Self::Receiver) -> Option { 147 | rx.next().await 148 | } 149 | } 150 | 151 | struct KanalChannel; 152 | 153 | impl UnboundedChannel for KanalChannel { 154 | type Sender = kanal::Sender; 155 | type Receiver = kanal::AsyncReceiver; 156 | 157 | fn new() -> (Self::Sender, Self::Receiver) { 158 | let (tx, rx) = kanal::unbounded(); 159 | let rx = rx.to_async(); 160 | (tx, rx) 161 | } 162 | fn send(tx: &Self::Sender, value: T) -> anyhow::Result<()> { 163 | Ok(tx.send(value)?) 164 | } 165 | async fn recv(rx: &mut Self::Receiver) -> Option { 166 | rx.recv().await.ok() 167 | } 168 | } 169 | 170 | async fn sender( 171 | tx: UC::Sender, 172 | iteration_count: usize, 173 | batch_size: usize, 174 | ) { 175 | if batch_size == 1 { 176 | for i in 0..iteration_count { 177 | UC::send(&tx, i).unwrap(); 178 | // The intent of this benchmark is to interleave send and recv. 179 | yield_now().await; 180 | } 181 | } else { 182 | let mut vec = Vec::with_capacity(batch_size); 183 | for i in 0..iteration_count { 184 | if vec.len() < batch_size { 185 | vec.push(i); 186 | } else { 187 | vec = UC::send_vec(&tx, vec).unwrap(); 188 | // The intent of this benchmark is to interleave send and recv. 189 | yield_now().await; 190 | } 191 | } 192 | if !vec.is_empty() { 193 | _ = UC::send_vec(&tx, vec).unwrap(); 194 | } 195 | } 196 | } 197 | 198 | async fn receiver(mut rx: UC::Receiver, batch_size: usize) { 199 | if batch_size == 1 { 200 | while let Some(_) = UC::recv(&mut rx).await {} 201 | } else { 202 | loop { 203 | let v = UC::recv_batch(&mut rx, batch_size).await; 204 | if v.is_empty() { 205 | break; 206 | } 207 | } 208 | } 209 | } 210 | 211 | #[divan::bench( 212 | types = [BatchChannel, StdChannel, CrossbeamChannel, FuturesChannel, KanalChannel], 213 | consts = [1, 10, 100], 214 | )] 215 | fn batch_size_tx_first(bencher: divan::Bencher) 216 | where 217 | UC: UnboundedChannel + Send, 218 | { 219 | let iteration_count = 100; 220 | bencher 221 | .counter(divan::counter::ItemsCount::new(N * iteration_count)) 222 | .with_inputs(|| { 223 | let pool = LocalPool::new(); 224 | let spawner = pool.spawner(); 225 | let (tx, rx) = UC::new(); 226 | () = spawner.spawn(sender::(tx, iteration_count, N)).unwrap(); 227 | () = spawner.spawn(receiver::(rx, N)).unwrap(); 228 | pool 229 | }) 230 | .bench_local_values(|mut pool| { 231 | pool.run_until_stalled(); 232 | }) 233 | } 234 | 235 | #[divan::bench( 236 | types = [BatchChannel, StdChannel, CrossbeamChannel, FuturesChannel, KanalChannel], 237 | consts = [1, 10, 100], 238 | )] 239 | fn batch_size_rx_first(bencher: divan::Bencher) 240 | where 241 | UC: UnboundedChannel + Send, 242 | { 243 | let iteration_count = 100; 244 | bencher 245 | .counter(divan::counter::ItemsCount::new(N * iteration_count)) 246 | .with_inputs(|| { 247 | let pool = LocalPool::new(); 248 | let spawner = pool.spawner(); 249 | let (tx, rx) = UC::new(); 250 | () = spawner.spawn(receiver::(rx, N)).unwrap(); 251 | () = spawner.spawn(sender::(tx, iteration_count, N)).unwrap(); 252 | pool 253 | }) 254 | .bench_local_values(|mut pool| { 255 | pool.run_until_stalled(); 256 | }) 257 | } 258 | 259 | fn main() { 260 | divan::main() 261 | } 262 | -------------------------------------------------------------------------------- /benches/throughput.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use clap::Subcommand; 3 | use futures::future::BoxFuture; 4 | use futures::FutureExt; 5 | use itertools::Itertools; 6 | use lazy_static::lazy_static; 7 | use std::future::Future; 8 | use std::time::Duration; 9 | use std::time::Instant; 10 | 11 | trait Channel { 12 | const HAS_BATCH: bool; 13 | 14 | type Sender: ChannelSender + 'static; 15 | type Receiver: ChannelReceiver + 'static; 16 | 17 | fn bounded(capacity: usize) -> (Self::Sender, Self::Receiver); 18 | } 19 | 20 | trait ChannelSync { 21 | const HAS_BATCH: bool; 22 | 23 | type SyncSender: ChannelSyncSender + 'static; 24 | type SyncReceiver: ChannelSyncReceiver + 'static; 25 | 26 | fn bounded_sync( 27 | capacity: usize, 28 | ) -> (Self::SyncSender, Self::SyncReceiver); 29 | } 30 | 31 | trait ChannelSender: Clone + Send { 32 | type BatchSender: ChannelBatchSender; 33 | 34 | fn autobatch(self, batch_limit: usize, f: F) -> impl Future + Send 35 | where 36 | for<'a> F: (FnOnce(&'a mut Self::BatchSender) -> BoxFuture<'a, ()>) + Send + 'static; 37 | } 38 | 39 | trait ChannelBatchSender: Send { 40 | fn send(&mut self, value: T) -> impl Future + Send; 41 | } 42 | 43 | trait ChannelReceiver: Clone + Send { 44 | fn recv_vec<'a>( 45 | &'a self, 46 | element_limit: usize, 47 | vec: &'a mut Vec, 48 | ) -> impl Future + Send; 49 | } 50 | 51 | trait ChannelSyncSender: Clone + Send { 52 | type BatchSenderSync<'a>: ChannelBatchSenderSync 53 | where 54 | T: 'a; 55 | 56 | fn send(&mut self, value: T); 57 | 58 | fn autobatch<'a, F>(&'a mut self, batch_limit: usize, f: F) 59 | where 60 | F: FnOnce(&mut Self::BatchSenderSync<'a>); 61 | } 62 | 63 | trait ChannelBatchSenderSync: Send { 64 | fn send(&mut self, value: T); 65 | } 66 | 67 | trait ChannelSyncReceiver: Clone + Send { 68 | fn recv_vec(&self, element_limit: usize, vec: &mut Vec); 69 | } 70 | 71 | // batch-channel, this crate 72 | 73 | struct BatchChannel; 74 | 75 | impl Channel for BatchChannel { 76 | const HAS_BATCH: bool = true; 77 | 78 | type Sender = batch_channel::Sender; 79 | type Receiver = batch_channel::Receiver; 80 | 81 | fn bounded(capacity: usize) -> (Self::Sender, Self::Receiver) { 82 | batch_channel::bounded(capacity) 83 | } 84 | } 85 | 86 | impl ChannelSync for BatchChannel { 87 | const HAS_BATCH: bool = true; 88 | 89 | type SyncSender = batch_channel::SyncSender; 90 | type SyncReceiver = batch_channel::SyncReceiver; 91 | 92 | fn bounded_sync( 93 | capacity: usize, 94 | ) -> (Self::SyncSender, Self::SyncReceiver) { 95 | batch_channel::bounded_sync(capacity) 96 | } 97 | } 98 | 99 | impl ChannelSender for batch_channel::Sender { 100 | type BatchSender = batch_channel::BatchSender; 101 | 102 | fn autobatch(self, batch_limit: usize, f: F) -> impl Future + Send 103 | where 104 | for<'a> F: (FnOnce(&'a mut Self::BatchSender) -> BoxFuture<'a, ()>) + Send + 'static, 105 | { 106 | async move { 107 | batch_channel::Sender::autobatch(self, batch_limit, |tx| { 108 | async move { 109 | () = f(tx).await; 110 | Ok(()) 111 | } 112 | .boxed() 113 | }) 114 | .await 115 | .expect("in this benchmark, receiver never drops") 116 | } 117 | } 118 | } 119 | 120 | impl ChannelBatchSender for batch_channel::BatchSender { 121 | fn send(&mut self, value: T) -> impl Future + Send { 122 | async move { 123 | batch_channel::BatchSender::send(self, value) 124 | .await 125 | .expect("in this benchmark, receiver never drops") 126 | } 127 | } 128 | } 129 | 130 | impl ChannelReceiver for batch_channel::Receiver { 131 | fn recv_vec<'a>( 132 | &'a self, 133 | element_limit: usize, 134 | vec: &'a mut Vec, 135 | ) -> impl Future + Send { 136 | batch_channel::Receiver::recv_vec(self, element_limit, vec) 137 | } 138 | } 139 | 140 | impl ChannelSyncSender for batch_channel::SyncSender { 141 | type BatchSenderSync<'a> 142 | = batch_channel::SyncBatchSender<'a, T> 143 | where 144 | T: 'a; 145 | 146 | fn send(&mut self, value: T) { 147 | let Ok(()) = batch_channel::SyncSender::send(self, value) else { 148 | panic!("in this benchmark, receiver never drops"); 149 | }; 150 | } 151 | 152 | fn autobatch<'a, F>(&'a mut self, batch_limit: usize, f: F) 153 | where 154 | F: FnOnce(&mut Self::BatchSenderSync<'a>), 155 | { 156 | batch_channel::SyncSender::autobatch(self, batch_limit, |tx| { 157 | f(tx); 158 | Ok(()) 159 | }) 160 | .expect("in this benchmark, receiver never drops") 161 | } 162 | } 163 | 164 | impl<'a, T: Send> ChannelBatchSenderSync for batch_channel::SyncBatchSender<'a, T> { 165 | fn send(&mut self, value: T) { 166 | batch_channel::SyncBatchSender::send(self, value) 167 | .expect("in this benchmark, receiver never drops") 168 | } 169 | } 170 | 171 | impl ChannelSyncReceiver for batch_channel::SyncReceiver { 172 | fn recv_vec(&self, element_limit: usize, vec: &mut Vec) { 173 | batch_channel::SyncReceiver::recv_vec(self, element_limit, vec) 174 | } 175 | } 176 | 177 | // Kanal 178 | 179 | struct KanalChannel; 180 | 181 | impl Channel for KanalChannel { 182 | const HAS_BATCH: bool = false; 183 | 184 | type Sender = kanal::AsyncSender; 185 | type Receiver = kanal::AsyncReceiver; 186 | 187 | fn bounded(capacity: usize) -> (Self::Sender, Self::Receiver) { 188 | kanal::bounded_async(capacity) 189 | } 190 | } 191 | 192 | impl ChannelSender for kanal::AsyncSender { 193 | type BatchSender = kanal::AsyncSender; 194 | 195 | fn autobatch(mut self, _batch_limit: usize, f: F) -> impl Future + Send 196 | where 197 | for<'a> F: (FnOnce(&'a mut Self::BatchSender) -> BoxFuture<'a, ()>) + Send + 'static, 198 | { 199 | async move { 200 | f(&mut self).await; 201 | } 202 | } 203 | } 204 | 205 | impl ChannelBatchSender for kanal::AsyncSender { 206 | fn send(&mut self, value: T) -> impl Future + Send { 207 | async move { 208 | kanal::AsyncSender::send(self, value) 209 | .await 210 | .expect("in this benchmark, receiver never drops") 211 | } 212 | } 213 | } 214 | 215 | impl ChannelReceiver for kanal::AsyncReceiver { 216 | fn recv_vec<'a>( 217 | &'a self, 218 | element_limit: usize, 219 | vec: &'a mut Vec, 220 | ) -> impl Future + Send { 221 | async move { 222 | let Ok(value) = self.recv().await else { 223 | return; 224 | }; 225 | vec.push(value); 226 | // Now try to read the rest. 227 | for _ in 0..element_limit { 228 | let Ok(Some(value)) = self.try_recv() else { 229 | return; 230 | }; 231 | vec.push(value); 232 | } 233 | } 234 | } 235 | } 236 | 237 | impl ChannelSync for KanalChannel { 238 | const HAS_BATCH: bool = false; 239 | 240 | type SyncSender = kanal::Sender; 241 | type SyncReceiver = kanal::Receiver; 242 | 243 | fn bounded_sync( 244 | capacity: usize, 245 | ) -> (Self::SyncSender, Self::SyncReceiver) { 246 | kanal::bounded(capacity) 247 | } 248 | } 249 | 250 | impl ChannelSyncSender for kanal::Sender { 251 | type BatchSenderSync<'a> 252 | = kanal::Sender 253 | where 254 | T: 'a; 255 | 256 | fn send(&mut self, value: T) { 257 | let Ok(()) = kanal::Sender::send(self, value) else { 258 | panic!("in this benchmark, receiver never drops"); 259 | }; 260 | } 261 | 262 | fn autobatch<'a, F>(&'a mut self, _batch_limit: usize, f: F) 263 | where 264 | F: FnOnce(&mut Self::BatchSenderSync<'a>), 265 | { 266 | f(self); 267 | } 268 | } 269 | 270 | impl ChannelBatchSenderSync for kanal::Sender { 271 | fn send(&mut self, value: T) { 272 | kanal::Sender::send(self, value).expect("in this benchmark, receiver never drops") 273 | } 274 | } 275 | 276 | impl ChannelSyncReceiver for kanal::Receiver { 277 | fn recv_vec(&self, element_limit: usize, vec: &mut Vec) { 278 | let Ok(value) = self.recv() else { 279 | return; 280 | }; 281 | vec.push(value); 282 | // Now try to read the rest. 283 | for _ in 1..element_limit { 284 | let Ok(Some(value)) = self.try_recv() else { 285 | return; 286 | }; 287 | vec.push(value); 288 | } 289 | } 290 | } 291 | 292 | // Crossbeam 293 | 294 | struct CrossbeamChannel; 295 | 296 | impl ChannelSync for CrossbeamChannel { 297 | const HAS_BATCH: bool = false; 298 | 299 | type SyncSender = crossbeam::channel::Sender; 300 | type SyncReceiver = crossbeam::channel::Receiver; 301 | 302 | fn bounded_sync( 303 | capacity: usize, 304 | ) -> (Self::SyncSender, Self::SyncReceiver) { 305 | crossbeam::channel::bounded(capacity) 306 | } 307 | } 308 | 309 | impl ChannelSyncSender for crossbeam::channel::Sender { 310 | type BatchSenderSync<'a> 311 | = crossbeam::channel::Sender 312 | where 313 | T: 'a; 314 | 315 | fn send(&mut self, value: T) { 316 | let Ok(()) = crossbeam::channel::Sender::send(self, value) else { 317 | panic!("in this benchmark, receiver never drops"); 318 | }; 319 | } 320 | 321 | fn autobatch<'a, F>(&'a mut self, _batch_limit: usize, f: F) 322 | where 323 | F: FnOnce(&mut Self::BatchSenderSync<'a>), 324 | { 325 | f(self); 326 | } 327 | } 328 | 329 | impl ChannelBatchSenderSync for crossbeam::channel::Sender { 330 | fn send(&mut self, value: T) { 331 | crossbeam::channel::Sender::send(self, value) 332 | .expect("in this benchmark, receiver never drops") 333 | } 334 | } 335 | 336 | impl ChannelSyncReceiver for crossbeam::channel::Receiver { 337 | fn recv_vec(&self, element_limit: usize, vec: &mut Vec) { 338 | let Ok(value) = self.recv() else { 339 | return; 340 | }; 341 | vec.push(value); 342 | // Now try to read the rest. 343 | for _ in 1..element_limit { 344 | let Ok(value) = self.try_recv() else { 345 | return; 346 | }; 347 | vec.push(value); 348 | } 349 | } 350 | } 351 | 352 | // async-channel 353 | 354 | struct AsyncChannel; 355 | 356 | impl Channel for AsyncChannel { 357 | const HAS_BATCH: bool = false; 358 | 359 | type Sender = async_channel::Sender; 360 | type Receiver = async_channel::Receiver; 361 | 362 | fn bounded(capacity: usize) -> (Self::Sender, Self::Receiver) { 363 | async_channel::bounded(capacity) 364 | } 365 | } 366 | 367 | impl ChannelSender for async_channel::Sender { 368 | type BatchSender = async_channel::Sender; 369 | 370 | fn autobatch(mut self, _batch_limit: usize, f: F) -> impl Future + Send 371 | where 372 | for<'a> F: (FnOnce(&'a mut Self::BatchSender) -> BoxFuture<'a, ()>) + Send + 'static, 373 | { 374 | async move { 375 | f(&mut self).await; 376 | } 377 | } 378 | } 379 | 380 | impl ChannelBatchSender for async_channel::Sender { 381 | fn send(&mut self, value: T) -> impl Future + Send { 382 | async move { 383 | async_channel::Sender::send(self, value) 384 | .await 385 | .expect("in this benchmark, receiver never drops") 386 | } 387 | } 388 | } 389 | 390 | impl ChannelReceiver for async_channel::Receiver { 391 | fn recv_vec<'a>( 392 | &'a self, 393 | element_limit: usize, 394 | vec: &'a mut Vec, 395 | ) -> impl Future + Send { 396 | async move { 397 | let Ok(value) = self.recv().await else { 398 | return; 399 | }; 400 | vec.push(value); 401 | // Now try to read the rest. 402 | for _ in 1..element_limit { 403 | let Ok(value) = self.try_recv() else { 404 | return; 405 | }; 406 | vec.push(value); 407 | } 408 | } 409 | } 410 | } 411 | 412 | // Benchmark 413 | 414 | #[derive(Copy, Clone)] 415 | struct Options { 416 | tx_batch_size: usize, 417 | rx_batch_size: usize, 418 | tx_count: usize, 419 | rx_count: usize, 420 | } 421 | 422 | struct Timings { 423 | total: Duration, 424 | per_item: Duration, 425 | } 426 | 427 | impl Timings { 428 | fn print(&self) { 429 | println!( 430 | "{:?}, {:?} per item, {:.2e} items/s", 431 | self.total, 432 | self.per_item, 433 | 1f64 / self.per_item.as_secs_f64() 434 | ) 435 | } 436 | } 437 | 438 | async fn benchmark_throughput_async(_: C, options: Options) -> Timings { 439 | const CAPACITY: usize = 65536; 440 | let send_count: usize = 2 * 1024 * 1024; 441 | let total_items = send_count * options.tx_count; 442 | 443 | let mut senders = Vec::with_capacity(options.tx_count); 444 | let mut receivers = Vec::with_capacity(options.rx_count); 445 | 446 | let now = Instant::now(); 447 | 448 | let (tx, rx) = C::bounded(CAPACITY); 449 | for task_id in 0..options.tx_count { 450 | let tx = tx.clone(); 451 | senders.push(tokio::spawn( 452 | async move { 453 | tx.autobatch(options.tx_batch_size, move |tx| { 454 | async move { 455 | for i in 0..send_count { 456 | tx.send((task_id, i)).await; 457 | } 458 | } 459 | .boxed() 460 | }) 461 | .await; 462 | } 463 | .boxed(), 464 | )); 465 | } 466 | drop(tx); 467 | for _ in 0..options.rx_count { 468 | let rx = rx.clone(); 469 | receivers.push(tokio::spawn( 470 | async move { 471 | let mut batch = Vec::with_capacity(options.rx_batch_size); 472 | loop { 473 | batch.clear(); 474 | rx.recv_vec(options.rx_batch_size, &mut batch).await; 475 | if batch.is_empty() { 476 | break; 477 | } 478 | } 479 | } 480 | .boxed(), 481 | )); 482 | } 483 | drop(rx); 484 | 485 | for r in receivers { 486 | () = r.await.expect("task panicked"); 487 | } 488 | for s in senders { 489 | () = s.await.expect("task panicked"); 490 | } 491 | 492 | let elapsed = now.elapsed(); 493 | Timings { 494 | total: elapsed, 495 | per_item: elapsed / (total_items as u32), 496 | } 497 | } 498 | 499 | fn benchmark_throughput_sync(_: C, options: Options) -> Timings { 500 | const CAPACITY: usize = 65536; 501 | let send_count: usize = 1 * 1024 * 1024; 502 | let total_items = send_count * options.tx_count; 503 | 504 | let mut senders = Vec::with_capacity(options.tx_count); 505 | let mut receivers = Vec::with_capacity(options.rx_count); 506 | 507 | let now = Instant::now(); 508 | 509 | let (tx, rx) = C::bounded_sync(CAPACITY); 510 | for task_id in 0..options.tx_count { 511 | let mut tx = tx.clone(); 512 | senders.push(std::thread::spawn(move || { 513 | if options.tx_batch_size == 1 { 514 | for i in 0..send_count { 515 | tx.send((task_id, i)); 516 | } 517 | } else { 518 | tx.autobatch(options.tx_batch_size, move |tx| { 519 | for i in 0..send_count { 520 | tx.send((task_id, i)); 521 | } 522 | }) 523 | } 524 | })); 525 | } 526 | drop(tx); 527 | for _ in 0..options.rx_count { 528 | let rx = rx.clone(); 529 | receivers.push(std::thread::spawn(move || { 530 | let mut batch = Vec::with_capacity(options.rx_batch_size); 531 | loop { 532 | batch.clear(); 533 | rx.recv_vec(options.rx_batch_size, &mut batch); 534 | if batch.is_empty() { 535 | break; 536 | } 537 | } 538 | })); 539 | } 540 | drop(rx); 541 | 542 | for r in receivers { 543 | () = r.join().expect("thread panicked"); 544 | } 545 | for s in senders { 546 | () = s.join().expect("thread panicked"); 547 | } 548 | 549 | let elapsed = now.elapsed(); 550 | Timings { 551 | total: elapsed, 552 | per_item: elapsed / (total_items as u32), 553 | } 554 | } 555 | 556 | // These exist to allow `cargo bench` to run this benchmark while 557 | // selecting filters from other 558 | #[derive(Debug, Subcommand)] 559 | enum Commands { 560 | Throughput { 561 | #[arg(long)] 562 | bench: bool, 563 | }, 564 | Alloc { 565 | #[arg(long)] 566 | bench: bool, 567 | }, 568 | Async { 569 | #[arg(long)] 570 | bench: bool, 571 | }, 572 | Uncontended { 573 | #[arg(long)] 574 | bench: bool, 575 | }, 576 | } 577 | 578 | #[derive(Parser, Debug)] 579 | struct Args { 580 | #[arg(long)] 581 | bench: bool, 582 | 583 | #[arg(long)] 584 | csv: bool, 585 | 586 | #[arg(long)] 587 | threads: Option, 588 | 589 | #[arg(long, action=clap::ArgAction::Set, default_value_t=true)] 590 | sync: bool, 591 | 592 | #[arg(long, action=clap::ArgAction::Set, default_value_t=true)] 593 | r#async: bool, 594 | 595 | #[arg(long)] 596 | txs: Option>, 597 | 598 | #[arg(long)] 599 | rxs: Option>, 600 | 601 | #[arg(long)] 602 | tx_batch: Option>, 603 | 604 | #[arg(long)] 605 | rx_batch: Option>, 606 | 607 | #[command(subcommand)] 608 | command: Option, 609 | } 610 | 611 | lazy_static! { 612 | static ref ARGS: Args = Args::parse(); 613 | } 614 | 615 | const DEFAULT_TASK_COUNTS: &[usize] = &[1, 4]; 616 | 617 | const DEFAULT_BATCH_SIZES: &[usize] = &[1, 2, 4, 8, 16, 32, 64, 128, 256]; 618 | 619 | fn main() -> anyhow::Result<()> { 620 | match ARGS.command { 621 | Some(Commands::Throughput { .. }) => (), 622 | None => (), 623 | _ => { 624 | return Ok(()); 625 | } 626 | } 627 | 628 | let thread_count = ARGS 629 | .threads 630 | .unwrap_or(std::thread::available_parallelism()?.get()); 631 | 632 | let runtime = tokio::runtime::Builder::new_multi_thread() 633 | .worker_threads(thread_count) 634 | .build() 635 | .expect("failed to create tokio runtime"); 636 | 637 | let task_counts: Vec<(usize, usize)> = match (&ARGS.txs, &ARGS.rxs) { 638 | (Some(tx), Some(rx)) => tx 639 | .iter() 640 | .copied() 641 | .cartesian_product(rx.iter().copied()) 642 | .collect(), 643 | (Some(tx), None) => tx 644 | .iter() 645 | .copied() 646 | .cartesian_product(DEFAULT_TASK_COUNTS.iter().copied()) 647 | .collect(), 648 | (None, Some(rx)) => DEFAULT_TASK_COUNTS 649 | .iter() 650 | .copied() 651 | .cartesian_product(rx.iter().copied()) 652 | .collect(), 653 | (None, None) => DEFAULT_TASK_COUNTS 654 | .iter() 655 | .copied() 656 | .cartesian_product(DEFAULT_TASK_COUNTS.iter().copied()) 657 | .collect(), 658 | }; 659 | 660 | let batch_sizes: Vec<(usize, usize)> = match (&ARGS.tx_batch, &ARGS.rx_batch) { 661 | (Some(tx_batch), Some(rx_batch)) => tx_batch 662 | .iter() 663 | .copied() 664 | .cartesian_product(rx_batch.iter().copied()) 665 | .collect(), 666 | (Some(tx_batch), None) => tx_batch 667 | .iter() 668 | .copied() 669 | .cartesian_product(DEFAULT_BATCH_SIZES.iter().copied()) 670 | .collect(), 671 | (None, Some(rx_batch)) => DEFAULT_BATCH_SIZES 672 | .iter() 673 | .copied() 674 | .cartesian_product(rx_batch.iter().copied()) 675 | .collect(), 676 | (None, None) => DEFAULT_BATCH_SIZES 677 | .iter() 678 | .copied() 679 | .map(|s| (s, s)) 680 | .collect(), 681 | }; 682 | 683 | async fn bench_async(name: &str, options: Options, channel: C) { 684 | if !ARGS.csv { 685 | print!(" {: <13}: ", name); 686 | } 687 | let timings = benchmark_throughput_async(channel, options).await; 688 | if ARGS.csv { 689 | println!( 690 | "async,{},{},{},{},{},{},{}", 691 | name, 692 | options.tx_count, 693 | options.rx_count, 694 | options.tx_batch_size, 695 | options.rx_batch_size, 696 | timings.total.as_nanos(), 697 | timings.per_item.as_nanos() 698 | ); 699 | } else { 700 | timings.print(); 701 | } 702 | } 703 | 704 | fn bench_sync(name: &str, options: Options, channel: C) { 705 | if !ARGS.csv { 706 | print!(" {: <13}: ", name); 707 | } 708 | let timings = benchmark_throughput_sync(channel, options); 709 | if ARGS.csv { 710 | println!( 711 | "sync,{},{},{},{},{},{},{}", 712 | name, 713 | options.tx_count, 714 | options.rx_count, 715 | options.tx_batch_size, 716 | options.rx_batch_size, 717 | timings.total.as_nanos(), 718 | timings.per_item.as_nanos() 719 | ); 720 | } else { 721 | timings.print(); 722 | } 723 | } 724 | 725 | let run_batch_async_with_options = |options| { 726 | runtime.block_on(bench_async("batch-channel", options, BatchChannel)); 727 | runtime.block_on(bench_async("kanal", options, KanalChannel)); 728 | runtime.block_on(bench_async("async-channel", options, AsyncChannel)); 729 | }; 730 | 731 | let run_batch_sync_with_options = |options| { 732 | bench_sync("batch-channel", options, BatchChannel); 733 | bench_sync("kanal", options, KanalChannel); 734 | bench_sync("crossbeam", options, CrossbeamChannel); 735 | }; 736 | 737 | if ARGS.csv { 738 | println!("mode,channel,tx,rx,tx_batch_size,rx_batch_size,total_ns,per_item_ns"); 739 | } 740 | 741 | for (tx_count, rx_count) in task_counts.iter().copied() { 742 | if ARGS.r#async { 743 | if !ARGS.csv { 744 | println!(); 745 | println!("throughput async (tx={} rx={})", tx_count, rx_count); 746 | } 747 | for (tx_batch_size, rx_batch_size) in batch_sizes.iter().copied() { 748 | if !ARGS.csv { 749 | println!(" tx_batch={tx_batch_size}, rx_batch={rx_batch_size}"); 750 | } 751 | 752 | run_batch_async_with_options(Options { 753 | tx_batch_size, 754 | rx_batch_size, 755 | tx_count, 756 | rx_count, 757 | }); 758 | } 759 | } 760 | 761 | if ARGS.sync { 762 | if !ARGS.csv { 763 | println!(); 764 | println!("throughput sync (tx={} rx={})", tx_count, rx_count); 765 | } 766 | for (tx_batch_size, rx_batch_size) in batch_sizes.iter().copied() { 767 | if !ARGS.csv { 768 | println!(" tx_batch={tx_batch_size}, rx_batch={rx_batch_size}"); 769 | } 770 | 771 | run_batch_sync_with_options(Options { 772 | tx_batch_size, 773 | rx_batch_size, 774 | tx_count, 775 | rx_count, 776 | }); 777 | } 778 | } 779 | } 780 | 781 | Ok(()) 782 | } 783 | -------------------------------------------------------------------------------- /benches/uncontended.rs: -------------------------------------------------------------------------------- 1 | use divan::Bencher; 2 | 3 | #[divan::bench] 4 | fn batch_channel(bencher: Bencher) { 5 | let item_count = 1000000usize; 6 | bencher 7 | .counter(divan::counter::ItemsCount::new(item_count)) 8 | .with_inputs(|| batch_channel::bounded_sync(item_count)) 9 | .bench_local_values(|(tx, rx)| { 10 | for i in 0..item_count { 11 | tx.send(i).unwrap(); 12 | } 13 | drop(tx); 14 | for i in 0..item_count { 15 | assert_eq!(Some(i), rx.recv()); 16 | } 17 | assert_eq!(None, rx.recv()); 18 | }); 19 | } 20 | 21 | #[divan::bench] 22 | fn kanal(bencher: Bencher) { 23 | let item_count = 1000000usize; 24 | bencher 25 | .counter(divan::counter::ItemsCount::new(item_count)) 26 | .with_inputs(|| kanal::bounded(item_count)) 27 | .bench_local_values(|(tx, rx)| { 28 | for i in 0..item_count { 29 | tx.send(i).unwrap(); 30 | } 31 | drop(tx); 32 | for i in 0..item_count { 33 | assert_eq!(Ok(i), rx.recv()); 34 | } 35 | assert!(rx.recv().is_err()); 36 | }); 37 | } 38 | 39 | #[divan::bench] 40 | fn crossbeam(bencher: Bencher) { 41 | let item_count = 1000000usize; 42 | bencher 43 | .counter(divan::counter::ItemsCount::new(item_count)) 44 | .with_inputs(|| crossbeam::channel::bounded(item_count)) 45 | .bench_local_values(|(tx, rx)| { 46 | for i in 0..item_count { 47 | tx.send(i).unwrap(); 48 | } 49 | drop(tx); 50 | for i in 0..item_count { 51 | assert_eq!(Ok(i), rx.recv()); 52 | } 53 | assert!(rx.recv().is_err()); 54 | }); 55 | } 56 | 57 | fn main() { 58 | divan::main() 59 | } 60 | -------------------------------------------------------------------------------- /results/9343.csv: -------------------------------------------------------------------------------- 1 | mode,channel,tx,rx,tx_batch_size,rx_batch_size,total_ns,per_item_ns 2 | async,batch-channel,1,1,1,1,293729000,140 3 | async,kanal,1,1,1,1,173061800,82 4 | async,async-channel,1,1,1,1,120870900,57 5 | async,batch-channel,1,1,2,2,258277800,61 6 | async,kanal,1,1,2,2,338809000,161 7 | async,async-channel,1,1,2,2,194214700,92 8 | async,batch-channel,1,1,4,4,471772400,56 9 | async,kanal,1,1,4,4,114366000,54 10 | async,async-channel,1,1,4,4,129812000,61 11 | async,batch-channel,1,1,8,8,407857300,24 12 | async,kanal,1,1,8,8,94812200,45 13 | async,async-channel,1,1,8,8,123880100,59 14 | async,batch-channel,1,1,16,16,616699800,18 15 | async,kanal,1,1,16,16,830374800,395 16 | async,async-channel,1,1,16,16,274441500,130 17 | async,batch-channel,1,1,32,32,2389332800,35 18 | async,kanal,1,1,32,32,106831500,50 19 | async,async-channel,1,1,32,32,147961400,70 20 | async,batch-channel,1,1,64,64,5222667200,38 21 | async,kanal,1,1,64,64,209541200,99 22 | async,async-channel,1,1,64,64,208713700,99 23 | async,batch-channel,1,1,128,128,4341713100,16 24 | async,kanal,1,1,128,128,111785600,53 25 | async,async-channel,1,1,128,128,149286400,71 26 | async,batch-channel,1,1,256,256,14870303400,27 27 | async,kanal,1,1,256,256,140646200,67 28 | async,async-channel,1,1,256,256,198254700,94 29 | sync,batch-channel,1,1,1,1,136244800,129 30 | sync,kanal,1,1,1,1,241875100,230 31 | sync,crossbeam,1,1,1,1,191418700,182 32 | sync,batch-channel,1,1,2,2,205745000,98 33 | sync,kanal,1,1,2,2,132505000,126 34 | sync,crossbeam,1,1,2,2,74982300,71 35 | sync,batch-channel,1,1,4,4,210593000,50 36 | sync,kanal,1,1,4,4,84204400,80 37 | sync,crossbeam,1,1,4,4,30765800,29 38 | sync,batch-channel,1,1,8,8,240633100,28 39 | sync,kanal,1,1,8,8,109449600,104 40 | sync,crossbeam,1,1,8,8,32773600,31 41 | sync,batch-channel,1,1,16,16,567527400,33 42 | sync,kanal,1,1,16,16,95426300,91 43 | sync,crossbeam,1,1,16,16,35911300,34 44 | sync,batch-channel,1,1,32,32,1019082300,30 45 | sync,kanal,1,1,32,32,88134100,84 46 | sync,crossbeam,1,1,32,32,34094500,32 47 | sync,batch-channel,1,1,64,64,1553422400,23 48 | sync,kanal,1,1,64,64,103879700,99 49 | sync,crossbeam,1,1,64,64,38771200,36 50 | sync,batch-channel,1,1,128,128,2000601900,14 51 | sync,kanal,1,1,128,128,98605400,94 52 | sync,crossbeam,1,1,128,128,69863900,66 53 | sync,batch-channel,1,1,256,256,3079780300,11 54 | sync,kanal,1,1,256,256,90381600,86 55 | sync,crossbeam,1,1,256,256,64312800,61 56 | async,batch-channel,4,1,1,1,1791692100,213 57 | async,kanal,4,1,1,1,2230471700,265 58 | async,async-channel,4,1,1,1,1094990000,130 59 | async,batch-channel,4,1,2,2,2742210100,163 60 | async,kanal,4,1,2,2,1241671200,148 61 | async,async-channel,4,1,2,2,1233547600,147 62 | async,batch-channel,4,1,4,4,3321390200,98 63 | async,kanal,4,1,4,4,920749100,109 64 | async,async-channel,4,1,4,4,1087539200,129 65 | async,batch-channel,4,1,8,8,4930515700,73 66 | async,kanal,4,1,8,8,1136744700,135 67 | async,async-channel,4,1,8,8,1320890900,157 68 | async,batch-channel,4,1,16,16,7598401300,56 69 | async,kanal,4,1,16,16,925289200,110 70 | async,async-channel,4,1,16,16,1173138700,139 71 | async,batch-channel,4,1,32,32,8787526700,32 72 | async,kanal,4,1,32,32,803157100,95 73 | async,async-channel,4,1,32,32,1140032500,135 74 | async,batch-channel,4,1,64,64,12373642400,23 75 | async,kanal,4,1,64,64,2248372400,268 76 | async,async-channel,4,1,64,64,1244911200,148 77 | async,batch-channel,4,1,128,128,17300787900,16 78 | async,kanal,4,1,128,128,724835800,86 79 | async,async-channel,4,1,128,128,1117397900,133 80 | async,batch-channel,4,1,256,256,33371743900,15 81 | async,kanal,4,1,256,256,900468000,107 82 | async,async-channel,4,1,256,256,1094779000,130 83 | sync,batch-channel,4,1,1,1,1236141500,294 84 | sync,kanal,4,1,1,1,602282200,143 85 | sync,crossbeam,4,1,1,1,312850100,74 86 | sync,batch-channel,4,1,2,2,912420500,108 87 | sync,kanal,4,1,2,2,796164100,189 88 | sync,crossbeam,4,1,2,2,262339000,62 89 | sync,batch-channel,4,1,4,4,1222602500,72 90 | sync,kanal,4,1,4,4,733726100,174 91 | sync,crossbeam,4,1,4,4,226728200,54 92 | sync,batch-channel,4,1,8,8,1558888200,46 93 | sync,kanal,4,1,8,8,593727400,141 94 | sync,crossbeam,4,1,8,8,730225100,174 95 | sync,batch-channel,4,1,16,16,2341090500,34 96 | sync,kanal,4,1,16,16,258889400,61 97 | sync,crossbeam,4,1,16,16,143701800,34 98 | sync,batch-channel,4,1,32,32,4116482000,30 99 | sync,kanal,4,1,32,32,838506400,199 100 | sync,crossbeam,4,1,32,32,204922200,48 101 | sync,batch-channel,4,1,64,64,6434242100,23 102 | sync,kanal,4,1,64,64,774443500,184 103 | sync,crossbeam,4,1,64,64,224483000,53 104 | sync,batch-channel,4,1,128,128,9427989400,17 105 | sync,kanal,4,1,128,128,590198000,140 106 | sync,crossbeam,4,1,128,128,223628800,53 107 | sync,batch-channel,4,1,256,256,14580671700,13 108 | sync,kanal,4,1,256,256,599723100,142 109 | sync,crossbeam,4,1,256,256,235570400,56 110 | async,batch-channel,4,4,1,1,1820964300,217 111 | async,kanal,4,4,1,1,661828600,78 112 | async,async-channel,4,4,1,1,976363600,116 113 | async,batch-channel,4,4,2,2,2239097800,133 114 | async,kanal,4,4,2,2,613312700,73 115 | async,async-channel,4,4,2,2,860084600,102 116 | async,batch-channel,4,4,4,4,2633715800,78 117 | async,kanal,4,4,4,4,633737200,75 118 | async,async-channel,4,4,4,4,1319665700,157 119 | async,batch-channel,4,4,8,8,4411549100,65 120 | async,kanal,4,4,8,8,661524600,78 121 | async,async-channel,4,4,8,8,1176735900,140 122 | async,batch-channel,4,4,16,16,5080586600,37 123 | async,kanal,4,4,16,16,596589300,71 124 | async,async-channel,4,4,16,16,1027732800,122 125 | async,batch-channel,4,4,32,32,8900932700,33 126 | async,kanal,4,4,32,32,583158300,69 127 | async,async-channel,4,4,32,32,733552900,87 128 | async,batch-channel,4,4,64,64,12675716800,23 129 | async,kanal,4,4,64,64,703858400,83 130 | async,async-channel,4,4,64,64,1169458800,139 131 | async,batch-channel,4,4,128,128,18276920700,17 132 | async,kanal,4,4,128,128,582709700,69 133 | async,async-channel,4,4,128,128,1220502100,145 134 | async,batch-channel,4,4,256,256,29043948200,13 135 | async,kanal,4,4,256,256,632540800,75 136 | async,async-channel,4,4,256,256,950575700,113 137 | sync,batch-channel,4,4,1,1,1737304500,414 138 | sync,kanal,4,4,1,1,407323400,97 139 | sync,crossbeam,4,4,1,1,156835600,37 140 | sync,batch-channel,4,4,2,2,1632066500,194 141 | sync,kanal,4,4,2,2,589944800,140 142 | sync,crossbeam,4,4,2,2,264562500,63 143 | sync,batch-channel,4,4,4,4,1930108300,115 144 | sync,kanal,4,4,4,4,351194400,83 145 | sync,crossbeam,4,4,4,4,164517900,39 146 | sync,batch-channel,4,4,8,8,3139597500,93 147 | sync,kanal,4,4,8,8,439842100,104 148 | sync,crossbeam,4,4,8,8,193095500,46 149 | sync,batch-channel,4,4,16,16,3706421800,55 150 | sync,kanal,4,4,16,16,366708000,87 151 | sync,crossbeam,4,4,16,16,207307300,49 152 | sync,batch-channel,4,4,32,32,6203092600,46 153 | sync,kanal,4,4,32,32,343015900,81 154 | sync,crossbeam,4,4,32,32,191409100,45 155 | sync,batch-channel,4,4,64,64,8617355700,32 156 | sync,kanal,4,4,64,64,520973000,124 157 | sync,crossbeam,4,4,64,64,203679400,48 158 | sync,batch-channel,4,4,128,128,11778920100,21 159 | sync,kanal,4,4,128,128,299382400,71 160 | sync,crossbeam,4,4,128,128,169988500,40 161 | sync,batch-channel,4,4,256,256,15533465100,14 162 | sync,kanal,4,4,256,256,352828300,84 163 | sync,crossbeam,4,4,256,256,169083600,40 164 | -------------------------------------------------------------------------------- /results/apple-m2-air.csv: -------------------------------------------------------------------------------- 1 | mode,channel,tx,rx,batch_size,total_ns,per_item_ns 2 | async,batch-channel,1,1,1,84242667,40 3 | async,kanal,1,1,1,50990625,24 4 | async,async-channel,1,1,1,24786292,11 5 | async,batch-channel,1,1,2,103030333,24 6 | async,kanal,1,1,2,50250541,23 7 | async,async-channel,1,1,2,33060417,15 8 | async,batch-channel,1,1,4,118652625,14 9 | async,kanal,1,1,4,43971084,20 10 | async,async-channel,1,1,4,25749792,12 11 | async,batch-channel,1,1,8,155258416,9 12 | async,kanal,1,1,8,46123292,21 13 | async,async-channel,1,1,8,25544291,12 14 | async,batch-channel,1,1,16,238009667,7 15 | async,kanal,1,1,16,36220625,17 16 | async,async-channel,1,1,16,28305625,13 17 | async,batch-channel,1,1,32,412830375,6 18 | async,kanal,1,1,32,62586417,29 19 | async,async-channel,1,1,32,29527667,14 20 | async,batch-channel,1,1,64,774246584,5 21 | async,kanal,1,1,64,39864958,19 22 | async,async-channel,1,1,64,44238500,21 23 | async,batch-channel,1,1,128,1502588875,5 24 | async,kanal,1,1,128,38598292,18 25 | async,async-channel,1,1,128,41932084,19 26 | async,batch-channel,1,1,256,2961883959,5 27 | async,kanal,1,1,256,53904250,25 28 | async,async-channel,1,1,256,31902167,15 29 | sync,batch-channel,1,1,1,83783583,79 30 | sync,kanal,1,1,1,64609208,61 31 | sync,crossbeam,1,1,1,16825000,16 32 | sync,batch-channel,1,1,2,86287583,41 33 | sync,kanal,1,1,2,90802417,86 34 | sync,crossbeam,1,1,2,16260541,15 35 | sync,batch-channel,1,1,4,108837625,25 36 | sync,kanal,1,1,4,73388167,69 37 | sync,crossbeam,1,1,4,8972958,8 38 | sync,batch-channel,1,1,8,108396291,12 39 | sync,kanal,1,1,8,79632458,75 40 | sync,crossbeam,1,1,8,8925541,8 41 | sync,batch-channel,1,1,16,148787000,8 42 | sync,kanal,1,1,16,73229292,69 43 | sync,crossbeam,1,1,16,13799292,13 44 | sync,batch-channel,1,1,32,215300000,6 45 | sync,kanal,1,1,32,71524292,68 46 | sync,crossbeam,1,1,32,17371250,16 47 | sync,batch-channel,1,1,64,376816334,5 48 | sync,kanal,1,1,64,85774667,81 49 | sync,crossbeam,1,1,64,13409292,12 50 | sync,batch-channel,1,1,128,688416625,5 51 | sync,kanal,1,1,128,74926333,71 52 | sync,crossbeam,1,1,128,15401542,14 53 | sync,batch-channel,1,1,256,1340053834,4 54 | sync,kanal,1,1,256,78441042,74 55 | sync,crossbeam,1,1,256,13013208,12 56 | async,batch-channel,4,1,1,897828791,107 57 | async,kanal,4,1,1,362521083,43 58 | async,async-channel,4,1,1,683058375,81 59 | async,batch-channel,4,1,2,964387292,57 60 | async,kanal,4,1,2,288131458,34 61 | async,async-channel,4,1,2,659434083,78 62 | async,batch-channel,4,1,4,1114091166,33 63 | async,kanal,4,1,4,334584084,39 64 | async,async-channel,4,1,4,752022875,89 65 | async,batch-channel,4,1,8,1716336375,25 66 | async,kanal,4,1,8,305628375,36 67 | async,async-channel,4,1,8,706286833,84 68 | async,batch-channel,4,1,16,2041142500,15 69 | async,kanal,4,1,16,310395416,37 70 | async,async-channel,4,1,16,733600917,87 71 | async,batch-channel,4,1,32,3609892334,13 72 | async,kanal,4,1,32,305799125,36 73 | async,async-channel,4,1,32,708308375,84 74 | async,batch-channel,4,1,64,7201179083,13 75 | async,kanal,4,1,64,307621958,36 76 | async,async-channel,4,1,64,696694208,83 77 | async,batch-channel,4,1,128,12088310584,11 78 | async,kanal,4,1,128,341155208,40 79 | async,async-channel,4,1,128,695088542,82 80 | async,batch-channel,4,1,256,19687450708,9 81 | async,kanal,4,1,256,337148000,40 82 | async,async-channel,4,1,256,687206833,81 83 | sync,batch-channel,4,1,1,327293250,78 84 | sync,kanal,4,1,1,754454000,179 85 | sync,crossbeam,4,1,1,128304250,30 86 | sync,batch-channel,4,1,2,356866167,42 87 | sync,kanal,4,1,2,782340000,186 88 | sync,crossbeam,4,1,2,151775667,36 89 | sync,batch-channel,4,1,4,422482042,25 90 | sync,kanal,4,1,4,693232083,165 91 | sync,crossbeam,4,1,4,161908166,38 92 | sync,batch-channel,4,1,8,579511916,17 93 | sync,kanal,4,1,8,634167708,151 94 | sync,crossbeam,4,1,8,160575291,38 95 | sync,batch-channel,4,1,16,1005694500,14 96 | sync,kanal,4,1,16,703045333,167 97 | sync,crossbeam,4,1,16,180555750,43 98 | sync,batch-channel,4,1,32,1450845708,10 99 | sync,kanal,4,1,32,607637458,144 100 | sync,crossbeam,4,1,32,168285875,40 101 | sync,batch-channel,4,1,64,2468876375,9 102 | sync,kanal,4,1,64,675661166,161 103 | sync,crossbeam,4,1,64,166182875,39 104 | sync,batch-channel,4,1,128,4219711041,7 105 | sync,kanal,4,1,128,663351541,158 106 | sync,crossbeam,4,1,128,176367750,42 107 | sync,batch-channel,4,1,256,6399856625,5 108 | sync,kanal,4,1,256,765849583,182 109 | sync,crossbeam,4,1,256,180818000,43 110 | async,batch-channel,4,4,1,859459125,102 111 | async,kanal,4,4,1,271379167,32 112 | async,async-channel,4,4,1,620180333,73 113 | async,batch-channel,4,4,2,972508667,57 114 | async,kanal,4,4,2,249107666,29 115 | async,async-channel,4,4,2,990432458,118 116 | async,batch-channel,4,4,4,1167422584,34 117 | async,kanal,4,4,4,231966375,27 118 | async,async-channel,4,4,4,1086927791,129 119 | async,batch-channel,4,4,8,1744363458,25 120 | async,kanal,4,4,8,220673334,26 121 | async,async-channel,4,4,8,640310666,76 122 | async,batch-channel,4,4,16,2774650541,20 123 | async,kanal,4,4,16,220650542,26 124 | async,async-channel,4,4,16,685391167,81 125 | async,batch-channel,4,4,32,4578826167,17 126 | async,kanal,4,4,32,247365333,29 127 | async,async-channel,4,4,32,573330250,68 128 | async,batch-channel,4,4,64,7421637958,13 129 | async,kanal,4,4,64,353788542,42 130 | async,async-channel,4,4,64,591742542,70 131 | async,batch-channel,4,4,128,11100216625,10 132 | async,kanal,4,4,128,291384667,34 133 | async,async-channel,4,4,128,780492667,93 134 | async,batch-channel,4,4,256,16868645625,7 135 | async,kanal,4,4,256,307747042,36 136 | async,async-channel,4,4,256,927770083,110 137 | sync,batch-channel,4,4,1,573790834,136 138 | sync,kanal,4,4,1,484250041,115 139 | sync,crossbeam,4,4,1,134533458,32 140 | sync,batch-channel,4,4,2,587539458,70 141 | sync,kanal,4,4,2,381239208,90 142 | sync,crossbeam,4,4,2,90225584,21 143 | sync,batch-channel,4,4,4,605478167,36 144 | sync,kanal,4,4,4,389572750,92 145 | sync,crossbeam,4,4,4,72210292,17 146 | sync,batch-channel,4,4,8,660036292,19 147 | sync,kanal,4,4,8,297565042,70 148 | sync,crossbeam,4,4,8,74246750,17 149 | sync,batch-channel,4,4,16,1044076709,15 150 | sync,kanal,4,4,16,305030083,72 151 | sync,crossbeam,4,4,16,69908792,16 152 | sync,batch-channel,4,4,32,1465950125,10 153 | sync,kanal,4,4,32,462919167,110 154 | sync,crossbeam,4,4,32,92395708,22 155 | sync,batch-channel,4,4,64,2581077708,9 156 | sync,kanal,4,4,64,364957333,87 157 | sync,crossbeam,4,4,64,75018875,17 158 | sync,batch-channel,4,4,128,4118801667,7 159 | sync,kanal,4,4,128,371839041,88 160 | sync,crossbeam,4,4,128,72983042,17 161 | sync,batch-channel,4,4,256,7183787125,6 162 | sync,kanal,4,4,256,365687083,87 163 | sync,crossbeam,4,4,256,53375208,12 164 | -------------------------------------------------------------------------------- /results/atom-d2700.csv: -------------------------------------------------------------------------------- 1 | mode,channel,tx,rx,tx_batch_size,rx_batch_size,total_ns,per_item_ns 2 | async,batch-channel,1,1,1,1,543748393,259 3 | async,kanal,1,1,1,1,223751092,106 4 | async,async-channel,1,1,1,1,202271880,96 5 | async,batch-channel,1,1,2,2,633672586,151 6 | async,kanal,1,1,2,2,189066819,90 7 | async,async-channel,1,1,2,2,226697992,108 8 | async,batch-channel,1,1,4,4,858721169,102 9 | async,kanal,1,1,4,4,183978142,87 10 | async,async-channel,1,1,4,4,293482170,139 11 | async,batch-channel,1,1,8,8,1353602142,80 12 | async,kanal,1,1,8,8,194560335,92 13 | async,async-channel,1,1,8,8,390808049,186 14 | async,batch-channel,1,1,16,16,2270377052,67 15 | async,kanal,1,1,16,16,228677393,109 16 | async,async-channel,1,1,16,16,365628920,174 17 | async,batch-channel,1,1,32,32,4015036833,59 18 | async,kanal,1,1,32,32,161758396,77 19 | async,async-channel,1,1,32,32,275241253,131 20 | async,batch-channel,1,1,64,64,7350764490,54 21 | async,kanal,1,1,64,64,240950970,114 22 | async,async-channel,1,1,64,64,403804170,192 23 | async,batch-channel,1,1,128,128,14132538885,52 24 | async,kanal,1,1,128,128,169705771,80 25 | async,async-channel,1,1,128,128,415660703,198 26 | async,batch-channel,1,1,256,256,28384097669,52 27 | async,kanal,1,1,256,256,208683410,99 28 | async,async-channel,1,1,256,256,410143613,195 29 | sync,batch-channel,1,1,1,1,374724305,357 30 | sync,kanal,1,1,1,1,1281034634,1221 31 | sync,crossbeam,1,1,1,1,299238461,285 32 | sync,batch-channel,1,1,2,2,491980132,234 33 | sync,kanal,1,1,2,2,1389480093,1325 34 | sync,crossbeam,1,1,2,2,291424385,277 35 | sync,batch-channel,1,1,4,4,683677639,163 36 | sync,kanal,1,1,4,4,1044506857,996 37 | sync,crossbeam,1,1,4,4,279737219,266 38 | sync,batch-channel,1,1,8,8,1136342865,135 39 | sync,kanal,1,1,8,8,1388263312,1323 40 | sync,crossbeam,1,1,8,8,266595243,254 41 | sync,batch-channel,1,1,16,16,1909012233,113 42 | sync,kanal,1,1,16,16,1132607989,1080 43 | sync,crossbeam,1,1,16,16,267654640,255 44 | sync,batch-channel,1,1,32,32,3392141292,101 45 | sync,kanal,1,1,32,32,1346900699,1284 46 | sync,crossbeam,1,1,32,32,265878633,253 47 | sync,batch-channel,1,1,64,64,4828730645,71 48 | sync,kanal,1,1,64,64,1245172803,1187 49 | sync,crossbeam,1,1,64,64,245829605,234 50 | sync,batch-channel,1,1,128,128,9568247239,71 51 | sync,kanal,1,1,128,128,1343116866,1280 52 | sync,crossbeam,1,1,128,128,255452457,243 53 | sync,batch-channel,1,1,256,256,20065363684,74 54 | sync,kanal,1,1,256,256,1173575918,1119 55 | sync,crossbeam,1,1,256,256,246465262,235 56 | async,batch-channel,4,1,1,1,3593196397,428 57 | async,kanal,4,1,1,1,1940689264,231 58 | async,async-channel,4,1,1,1,1291406671,153 59 | async,batch-channel,4,1,2,2,4270607749,254 60 | async,kanal,4,1,2,2,2068444756,246 61 | async,async-channel,4,1,2,2,1231939076,146 62 | async,batch-channel,4,1,4,4,5797972428,172 63 | async,kanal,4,1,4,4,1517289357,180 64 | async,async-channel,4,1,4,4,1176794703,140 65 | async,batch-channel,4,1,8,8,8467450451,126 66 | async,kanal,4,1,8,8,1091306218,130 67 | async,async-channel,4,1,8,8,1374423162,163 68 | async,batch-channel,4,1,16,16,14147034355,105 69 | async,kanal,4,1,16,16,1129976596,134 70 | async,async-channel,4,1,16,16,1290123628,153 71 | async,batch-channel,4,1,32,32,24766482055,92 72 | async,kanal,4,1,32,32,1150377220,137 73 | async,async-channel,4,1,32,32,1195471290,142 74 | async,batch-channel,4,1,64,64,46187783859,86 75 | async,kanal,4,1,64,64,1224950760,146 76 | async,async-channel,4,1,64,64,1484300533,176 77 | async,batch-channel,4,1,128,128,94299281850,87 78 | async,kanal,4,1,128,128,1111394202,132 79 | async,async-channel,4,1,128,128,1311276599,156 80 | async,batch-channel,4,1,256,256,171539856324,79 81 | async,kanal,4,1,256,256,1074045759,128 82 | async,async-channel,4,1,256,256,1278002798,152 83 | sync,batch-channel,4,1,1,1,1765724877,420 84 | sync,kanal,4,1,1,1,745125743,177 85 | sync,crossbeam,4,1,1,1,563090579,134 86 | sync,batch-channel,4,1,2,2,2293964829,273 87 | sync,kanal,4,1,2,2,554724213,132 88 | sync,crossbeam,4,1,2,2,482831225,115 89 | sync,batch-channel,4,1,4,4,2913620376,173 90 | sync,kanal,4,1,4,4,738349862,176 91 | sync,crossbeam,4,1,4,4,595596131,142 92 | sync,batch-channel,4,1,8,8,4227136370,125 93 | sync,kanal,4,1,8,8,789634256,188 94 | sync,crossbeam,4,1,8,8,628858158,149 95 | sync,batch-channel,4,1,16,16,7156278558,106 96 | sync,kanal,4,1,16,16,937844883,223 97 | sync,crossbeam,4,1,16,16,650647905,155 98 | sync,batch-channel,4,1,32,32,12273117795,91 99 | sync,kanal,4,1,32,32,636158829,151 100 | sync,crossbeam,4,1,32,32,558266254,133 101 | sync,batch-channel,4,1,64,64,23043542695,85 102 | sync,kanal,4,1,64,64,1163128649,277 103 | sync,crossbeam,4,1,64,64,568298165,135 104 | sync,batch-channel,4,1,128,128,57036040037,106 105 | sync,kanal,4,1,128,128,669178796,159 106 | sync,crossbeam,4,1,128,128,567371641,135 107 | sync,batch-channel,4,1,256,256,118831905328,110 108 | sync,kanal,4,1,256,256,916237493,218 109 | sync,crossbeam,4,1,256,256,622188730,148 110 | async,batch-channel,4,4,1,1,3805370507,453 111 | async,kanal,4,4,1,1,850217382,101 112 | async,async-channel,4,4,1,1,1057876426,126 113 | async,batch-channel,4,4,2,2,4308503654,256 114 | async,kanal,4,4,2,2,846909140,100 115 | async,async-channel,4,4,2,2,1245620602,148 116 | async,batch-channel,4,4,4,4,5940134343,177 117 | async,kanal,4,4,4,4,772940992,92 118 | async,async-channel,4,4,4,4,1182808378,141 119 | async,batch-channel,4,4,8,8,8924664556,132 120 | async,kanal,4,4,8,8,754852163,89 121 | async,async-channel,4,4,8,8,1279647389,152 122 | async,batch-channel,4,4,16,16,14301124650,106 123 | async,kanal,4,4,16,16,761740223,90 124 | async,async-channel,4,4,16,16,1350394605,160 125 | async,batch-channel,4,4,32,32,25667301503,95 126 | async,kanal,4,4,32,32,741743671,88 127 | async,async-channel,4,4,32,32,1347712242,160 128 | async,batch-channel,4,4,64,64,46817187590,87 129 | async,kanal,4,4,64,64,732538939,87 130 | async,async-channel,4,4,64,64,1383570174,164 131 | async,batch-channel,4,4,128,128,95533823106,88 132 | async,kanal,4,4,128,128,773731118,92 133 | async,async-channel,4,4,128,128,1226936455,146 134 | async,batch-channel,4,4,256,256,186112457169,86 135 | async,kanal,4,4,256,256,747535655,89 136 | async,async-channel,4,4,256,256,1431442828,170 137 | sync,batch-channel,4,4,1,1,2885034258,687 138 | sync,kanal,4,4,1,1,351175398,83 139 | sync,crossbeam,4,4,1,1,659005296,157 140 | sync,batch-channel,4,4,2,2,3541131172,422 141 | sync,kanal,4,4,2,2,336742829,80 142 | sync,crossbeam,4,4,2,2,694450652,165 143 | sync,batch-channel,4,4,4,4,3796033894,226 144 | sync,kanal,4,4,4,4,347434654,82 145 | sync,crossbeam,4,4,4,4,516688917,123 146 | sync,batch-channel,4,4,8,8,4921695541,146 147 | sync,kanal,4,4,8,8,356044595,84 148 | sync,crossbeam,4,4,8,8,496023143,118 149 | sync,batch-channel,4,4,16,16,9199498408,137 150 | sync,kanal,4,4,16,16,362868809,86 151 | sync,crossbeam,4,4,16,16,552266228,131 152 | sync,batch-channel,4,4,32,32,15859324857,118 153 | sync,kanal,4,4,32,32,307233289,73 154 | sync,crossbeam,4,4,32,32,605441585,144 155 | sync,batch-channel,4,4,64,64,29099181217,108 156 | sync,kanal,4,4,64,64,304817358,72 157 | sync,crossbeam,4,4,64,64,489141449,116 158 | sync,batch-channel,4,4,128,128,56309410499,104 159 | sync,kanal,4,4,128,128,310188481,73 160 | sync,crossbeam,4,4,128,128,473965780,113 161 | sync,batch-channel,4,4,256,256,87646310089,81 162 | sync,kanal,4,4,256,256,337301212,80 163 | sync,crossbeam,4,4,256,256,526915946,125 164 | -------------------------------------------------------------------------------- /results/tiamat.csv: -------------------------------------------------------------------------------- 1 | mode,channel,tx,rx,batch_size,total_ns,per_item_ns 2 | async,batch-channel,1,1,1,163514500,77 3 | async,kanal,1,1,1,87743500,41 4 | async,async-channel,1,1,1,84149300,40 5 | async,batch-channel,1,1,2,173508800,41 6 | async,kanal,1,1,2,83296300,39 7 | async,async-channel,1,1,2,82390000,39 8 | async,batch-channel,1,1,4,214712000,25 9 | async,kanal,1,1,4,468930900,223 10 | async,async-channel,1,1,4,117201700,55 11 | async,batch-channel,1,1,8,300231300,17 12 | async,kanal,1,1,8,508929000,242 13 | async,async-channel,1,1,8,107897200,51 14 | async,batch-channel,1,1,16,606546200,18 15 | async,kanal,1,1,16,67709800,32 16 | async,async-channel,1,1,16,93676400,44 17 | async,batch-channel,1,1,32,1001731000,14 18 | async,kanal,1,1,32,67160200,32 19 | async,async-channel,1,1,32,88055900,41 20 | async,batch-channel,1,1,64,1626422200,12 21 | async,kanal,1,1,64,1526009100,727 22 | async,async-channel,1,1,64,158652300,75 23 | async,batch-channel,1,1,128,2950729000,10 24 | async,kanal,1,1,128,65158300,31 25 | async,async-channel,1,1,128,106837100,50 26 | async,batch-channel,1,1,256,5733549300,10 27 | async,kanal,1,1,256,66437600,31 28 | async,async-channel,1,1,256,97770600,46 29 | sync,batch-channel,1,1,1,75406200,71 30 | sync,kanal,1,1,1,155292100,148 31 | sync,crossbeam,1,1,1,59072300,56 32 | sync,batch-channel,1,1,2,90148200,42 33 | sync,kanal,1,1,2,76683000,73 34 | sync,crossbeam,1,1,2,54126900,51 35 | sync,batch-channel,1,1,4,122306700,29 36 | sync,kanal,1,1,4,167403800,159 37 | sync,crossbeam,1,1,4,16193800,15 38 | sync,batch-channel,1,1,8,128398300,15 39 | sync,kanal,1,1,8,130961300,124 40 | sync,crossbeam,1,1,8,24304400,23 41 | sync,batch-channel,1,1,16,161144800,9 42 | sync,kanal,1,1,16,140353000,133 43 | sync,crossbeam,1,1,16,31298500,29 44 | sync,batch-channel,1,1,32,476296500,14 45 | sync,kanal,1,1,32,184645200,176 46 | sync,crossbeam,1,1,32,14876000,14 47 | sync,batch-channel,1,1,64,1155580900,17 48 | sync,kanal,1,1,64,146653500,139 49 | sync,crossbeam,1,1,64,19124600,18 50 | sync,batch-channel,1,1,128,1386459100,10 51 | sync,kanal,1,1,128,139999700,133 52 | sync,crossbeam,1,1,128,20831700,19 53 | sync,batch-channel,1,1,256,1859273400,6 54 | sync,kanal,1,1,256,220538600,210 55 | sync,crossbeam,1,1,256,15587200,14 56 | async,batch-channel,4,1,1,715684500,85 57 | async,kanal,4,1,1,520116700,62 58 | async,async-channel,4,1,1,580561800,69 59 | async,batch-channel,4,1,2,767698700,45 60 | async,kanal,4,1,2,745703700,88 61 | async,async-channel,4,1,2,608865900,72 62 | async,batch-channel,4,1,4,1169031800,34 63 | async,kanal,4,1,4,585142000,69 64 | async,async-channel,4,1,4,594156200,70 65 | async,batch-channel,4,1,8,1824212500,27 66 | async,kanal,4,1,8,502717900,59 67 | async,async-channel,4,1,8,613378200,73 68 | async,batch-channel,4,1,16,2818525800,20 69 | async,kanal,4,1,16,443808400,52 70 | async,async-channel,4,1,16,595604800,71 71 | async,batch-channel,4,1,32,4753743200,17 72 | async,kanal,4,1,32,416284600,49 73 | async,async-channel,4,1,32,613352300,73 74 | async,batch-channel,4,1,64,8174686700,15 75 | async,kanal,4,1,64,433626000,51 76 | async,async-channel,4,1,64,591701700,70 77 | async,batch-channel,4,1,128,14168135600,13 78 | async,kanal,4,1,128,435864600,51 79 | async,async-channel,4,1,128,599707000,71 80 | async,batch-channel,4,1,256,24208301200,11 81 | async,kanal,4,1,256,424188600,50 82 | async,async-channel,4,1,256,629116600,74 83 | sync,batch-channel,4,1,1,335506000,79 84 | sync,kanal,4,1,1,665546700,158 85 | sync,crossbeam,4,1,1,220604800,52 86 | sync,batch-channel,4,1,2,367105700,43 87 | sync,kanal,4,1,2,866831900,206 88 | sync,crossbeam,4,1,2,132171100,31 89 | sync,batch-channel,4,1,4,507745200,30 90 | sync,kanal,4,1,4,859033000,204 91 | sync,crossbeam,4,1,4,164813000,39 92 | sync,batch-channel,4,1,8,494507800,14 93 | sync,kanal,4,1,8,759419200,181 94 | sync,crossbeam,4,1,8,144341600,34 95 | sync,batch-channel,4,1,16,1631953500,24 96 | sync,kanal,4,1,16,507490900,120 97 | sync,crossbeam,4,1,16,120751700,28 98 | sync,batch-channel,4,1,32,1699475100,12 99 | sync,kanal,4,1,32,839626900,200 100 | sync,crossbeam,4,1,32,130997700,31 101 | sync,batch-channel,4,1,64,2992100000,11 102 | sync,kanal,4,1,64,567876800,135 103 | sync,crossbeam,4,1,64,117679600,28 104 | sync,batch-channel,4,1,128,4299919700,8 105 | sync,kanal,4,1,128,740739500,176 106 | sync,crossbeam,4,1,128,144078200,34 107 | sync,batch-channel,4,1,256,7403147500,6 108 | sync,kanal,4,1,256,558295700,133 109 | sync,crossbeam,4,1,256,150765200,35 110 | async,batch-channel,4,4,1,812375600,96 111 | async,kanal,4,4,1,507841200,60 112 | async,async-channel,4,4,1,616978700,73 113 | async,batch-channel,4,4,2,839473800,50 114 | async,kanal,4,4,2,694855600,82 115 | async,async-channel,4,4,2,626380200,74 116 | async,batch-channel,4,4,4,1281177500,38 117 | async,kanal,4,4,4,568440100,67 118 | async,async-channel,4,4,4,670371700,79 119 | async,batch-channel,4,4,8,1954688900,29 120 | async,kanal,4,4,8,707210600,84 121 | async,async-channel,4,4,8,665353100,79 122 | async,batch-channel,4,4,16,2782529100,20 123 | async,kanal,4,4,16,831138600,99 124 | async,async-channel,4,4,16,662800900,79 125 | async,batch-channel,4,4,32,5312892400,19 126 | async,kanal,4,4,32,1546357300,184 127 | async,async-channel,4,4,32,700113600,83 128 | async,batch-channel,4,4,64,7932643200,14 129 | async,kanal,4,4,64,872695400,104 130 | async,async-channel,4,4,64,637027200,75 131 | async,batch-channel,4,4,128,15012494200,13 132 | async,kanal,4,4,128,1206935100,143 133 | async,async-channel,4,4,128,715112200,85 134 | async,batch-channel,4,4,256,29065310400,13 135 | async,kanal,4,4,256,1023531300,122 136 | async,async-channel,4,4,256,675924300,80 137 | sync,batch-channel,4,4,1,565521600,134 138 | sync,kanal,4,4,1,361631200,86 139 | sync,crossbeam,4,4,1,250397200,59 140 | sync,batch-channel,4,4,2,446762400,53 141 | sync,kanal,4,4,2,401435800,95 142 | sync,crossbeam,4,4,2,273293700,65 143 | sync,batch-channel,4,4,4,588798000,35 144 | sync,kanal,4,4,4,257296600,61 145 | sync,crossbeam,4,4,4,196414800,46 146 | sync,batch-channel,4,4,8,1035486700,30 147 | sync,kanal,4,4,8,319616100,76 148 | sync,crossbeam,4,4,8,225444900,53 149 | sync,batch-channel,4,4,16,1629493400,24 150 | sync,kanal,4,4,16,281022100,67 151 | sync,crossbeam,4,4,16,189472400,45 152 | sync,batch-channel,4,4,32,2911421000,21 153 | sync,kanal,4,4,32,331999400,79 154 | sync,crossbeam,4,4,32,210570500,50 155 | sync,batch-channel,4,4,64,4409345500,16 156 | sync,kanal,4,4,64,274153800,65 157 | sync,crossbeam,4,4,64,212045600,50 158 | sync,batch-channel,4,4,128,6317476400,11 159 | sync,kanal,4,4,128,271784400,64 160 | sync,crossbeam,4,4,128,198836000,47 161 | sync,batch-channel,4,4,256,13657659900,12 162 | sync,kanal,4,4,256,295232400,70 163 | sync,crossbeam,4,4,256,205000700,48 164 | -------------------------------------------------------------------------------- /s/check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd "${BASH_SOURCE%/*}"/.. 4 | 5 | cargo check --all-targets 6 | -------------------------------------------------------------------------------- /s/ci: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd "${BASH_SOURCE%/*}"/.. 4 | 5 | cargo check --all-targets 6 | cargo check --all-targets -F parking_lot 7 | cargo test 8 | cargo test -F parking_lot 9 | #cargo bench 10 | cargo doc 11 | 12 | -------------------------------------------------------------------------------- /s/doc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd "${BASH_SOURCE%/*}"/.. 4 | 5 | cargo +nightly docs-rs 6 | -------------------------------------------------------------------------------- /s/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd "${BASH_SOURCE%/*}"/.. 4 | 5 | cargo +nightly fmt 6 | cargo clippy 7 | -------------------------------------------------------------------------------- /s/miri: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd "${BASH_SOURCE%/*}"/.. 4 | 5 | cargo +nightly miri test "$@" 6 | -------------------------------------------------------------------------------- /s/publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd "${BASH_SOURCE%/*}"/.. 4 | set -o pipefail 5 | 6 | if ! git diff --exit-code HEAD ; then 7 | echo "please commit uncommitted changes" 8 | exit 1 9 | fi 10 | 11 | VERSION=$(cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "batch-channel") | .version') 12 | 13 | if [[ "$VERSION" == "" ]]; then 14 | echo "no version found" 15 | exit 1 16 | fi 17 | 18 | echo "publishing version $VERSION" 19 | 20 | TAGNAME="v$VERSION" 21 | 22 | git tag "$TAGNAME" 23 | 24 | cargo publish 25 | 26 | git push origin "$TAGNAME" 27 | -------------------------------------------------------------------------------- /src/example.md: -------------------------------------------------------------------------------- 1 | ```rust 2 | # futures::executor::block_on(async { 3 | let (tx, rx) = batch_channel::unbounded(); 4 | let tx = tx.into_sync(); 5 | # let value = 8675309; 6 | # let v1 = 1; 7 | # let v2 = 2; 8 | # let v3 = 3; 9 | 10 | tx.send(value).unwrap(); 11 | tx.send_iter([v1, v2, v3]).unwrap(); 12 | 13 | match rx.recv().await { 14 | Some(value) => println!("single {value}"), 15 | None => return "sender closed", 16 | } 17 | 18 | let batch = rx.recv_batch(100).await; 19 | // batch.is_empty() means sender closed 20 | # "" 21 | # }); 22 | ``` 23 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![doc = include_str!("example.md")] 3 | 4 | use futures_core::future::BoxFuture; 5 | use mutex::PinnedCondvar as Condvar; 6 | use mutex::PinnedMutex as Mutex; 7 | use mutex::PinnedMutexGuard as MutexGuard; 8 | use pin_project::pin_project; 9 | use pin_project::pinned_drop; 10 | #[cfg(feature = "parking_lot")] 11 | use pinned_mutex::parking_lot as mutex; 12 | #[cfg(not(feature = "parking_lot"))] 13 | use pinned_mutex::std as mutex; 14 | use std::cmp::min; 15 | use std::collections::VecDeque; 16 | use std::fmt; 17 | use std::future::Future; 18 | use std::iter::Peekable; 19 | use std::pin::Pin; 20 | use std::sync::OnceLock; 21 | use std::task::Context; 22 | use std::task::Poll; 23 | use wakerset::WakerList; 24 | use wakerset::WakerSlot; 25 | 26 | const UNBOUNDED_CAPACITY: usize = usize::MAX; 27 | 28 | macro_rules! derive_clone { 29 | ($t:ident) => { 30 | impl Clone for $t { 31 | fn clone(&self) -> Self { 32 | Self { 33 | core: self.core.clone(), 34 | } 35 | } 36 | } 37 | }; 38 | } 39 | 40 | #[derive(Debug)] 41 | #[pin_project] 42 | struct StateBase { 43 | capacity: usize, 44 | closed: bool, 45 | #[pin] 46 | tx_wakers: WakerList, 47 | #[pin] 48 | rx_wakers: WakerList, 49 | } 50 | 51 | impl StateBase { 52 | fn target_capacity(&self) -> usize { 53 | // TODO: We could offer an option to use queue.capacity 54 | // instead. 55 | self.capacity 56 | } 57 | 58 | fn pending_tx( 59 | self: Pin<&mut StateBase>, 60 | slot: Pin<&mut WakerSlot>, 61 | cx: &mut Context, 62 | ) -> Poll { 63 | // This may allocate, but only when the sender is about to 64 | // block, which is already expensive. 65 | self.project().tx_wakers.link(slot, cx.waker().clone()); 66 | Poll::Pending 67 | } 68 | 69 | fn pending_rx( 70 | self: Pin<&mut StateBase>, 71 | slot: Pin<&mut WakerSlot>, 72 | cx: &mut Context, 73 | ) -> Poll { 74 | // This may allocate, but only when the receiver is about to 75 | // block, which is already expensive. 76 | self.project().rx_wakers.link(slot, cx.waker().clone()); 77 | Poll::Pending 78 | } 79 | } 80 | 81 | #[derive(Debug)] 82 | #[pin_project] 83 | struct State { 84 | #[pin] 85 | base: StateBase, 86 | queue: VecDeque, 87 | } 88 | 89 | impl State { 90 | fn has_capacity(&self) -> bool { 91 | self.queue.len() < self.target_capacity() 92 | } 93 | 94 | fn base(self: Pin<&mut Self>) -> Pin<&mut StateBase> { 95 | self.project().base 96 | } 97 | } 98 | 99 | impl std::ops::Deref for State { 100 | type Target = StateBase; 101 | 102 | fn deref(&self) -> &Self::Target { 103 | &self.base 104 | } 105 | } 106 | 107 | impl std::ops::DerefMut for State { 108 | fn deref_mut(&mut self) -> &mut Self::Target { 109 | &mut self.base 110 | } 111 | } 112 | 113 | #[derive(Debug)] 114 | #[pin_project] 115 | struct Core { 116 | #[pin] 117 | state: Mutex>, 118 | // OnceLock ensures Core is Sync and Arc is Send. But it is 119 | // not strictly necessary, as these condition variables are only 120 | // accessed while the lock is held. Alas, Rust does not allow 121 | // Condvar to be stored under the Mutex. 122 | not_empty: OnceLock, 123 | not_full: OnceLock, 124 | } 125 | 126 | impl Core { 127 | /// Returns when there is a value or there are no values and all 128 | /// senders are dropped. 129 | fn block_until_not_empty(self: Pin<&Self>) -> MutexGuard<'_, State> { 130 | fn condition(s: Pin<&mut State>) -> bool { 131 | !s.closed && s.queue.is_empty() 132 | } 133 | 134 | let mut state = self.project_ref().state.lock(); 135 | if !condition(state.as_mut()) { 136 | return state; 137 | } 138 | // Initialize the condvar while the lock is held. Thus, the 139 | // caller can, while the lock is held, check whether the 140 | // condvar must be notified. 141 | let not_empty = self.not_empty.get_or_init(Default::default); 142 | not_empty.wait_while(state, condition) 143 | } 144 | 145 | /// Returns when there is either room in the queue or all receivers 146 | /// are dropped. 147 | fn block_until_not_full(self: Pin<&Self>) -> MutexGuard<'_, State> { 148 | fn condition(s: Pin<&mut State>) -> bool { 149 | !s.closed && !s.has_capacity() 150 | } 151 | 152 | let mut state = self.project_ref().state.lock(); 153 | if !condition(state.as_mut()) { 154 | return state; 155 | } 156 | // Initialize the condvar while the lock is held. Thus, the 157 | // caller can, while the lock is held, check whether the 158 | // condvar must be notified. 159 | let not_full = self.not_full.get_or_init(Default::default); 160 | not_full.wait_while(state, condition) 161 | } 162 | 163 | /// Returns when there is either room in the queue or all receivers 164 | /// are dropped. 165 | fn wake_rx_and_block_while_full<'a>( 166 | self: Pin<&'a Self>, 167 | mut state: MutexGuard<'a, State>, 168 | ) -> MutexGuard<'a, State> { 169 | // The lock is held. Therefore, we know whether a Condvar must 170 | // be notified or not. 171 | let cvar = self.not_empty.get(); 172 | // We should not wake Wakers while a lock is held. Therefore, 173 | // we must release the lock and reacquire it to wait. 174 | let mut wakers = state 175 | .as_mut() 176 | .project() 177 | .base 178 | .project() 179 | .rx_wakers 180 | .extract_some_wakers(); 181 | 182 | // TODO: Avoid unlocking and locking again when there's no 183 | // waker or condition variable. 184 | 185 | drop(state); 186 | if let Some(cvar) = cvar { 187 | // TODO: There are situations where we may know that we 188 | // can get away with notify_one(). 189 | cvar.notify_all(); 190 | } 191 | // There is no guarantee that the highest-priority waker will 192 | // actually call poll() again. Therefore, the best we can do 193 | // is wake everyone. 194 | while wakers.wake_all() { 195 | let mut state = self.project_ref().state.lock(); 196 | wakers.extract_more(state.as_mut().base().project().rx_wakers); 197 | } 198 | 199 | // Lock again to block while full. 200 | let state = self.project_ref().state.lock(); 201 | 202 | // Initialize the condvar while the lock is held. Thus, the 203 | // caller can, while the lock is held, check whether the 204 | // condvar must be notified. 205 | let not_full = self.not_full.get_or_init(Default::default); 206 | not_full.wait_while(state, |s| !s.closed && !s.has_capacity()) 207 | } 208 | 209 | fn wake_all_tx(self: Pin<&Self>, mut state: MutexGuard>) { 210 | // The lock is held. Therefore, we know whether a Condvar must be notified or not. 211 | let cvar = self.not_full.get(); 212 | let mut wakers = state 213 | .as_mut() 214 | .project() 215 | .base 216 | .project() 217 | .tx_wakers 218 | .extract_some_wakers(); 219 | drop(state); 220 | if let Some(cvar) = cvar { 221 | // TODO: There are situations where we may know that we 222 | // can get away with notify_one(). 223 | cvar.notify_all(); 224 | } 225 | // There is no guarantee that the highest-priority waker will 226 | // actually call poll() again. Therefore, the best we can do 227 | // is wake everyone. 228 | while wakers.wake_all() { 229 | let mut state = self.project_ref().state.lock(); 230 | wakers.extract_more(state.as_mut().project().base.project().tx_wakers); 231 | } 232 | } 233 | 234 | fn wake_one_rx(self: Pin<&Self>, mut state: MutexGuard>) { 235 | // The lock is held. Therefore, we know whether a Condvar must be notified or not. 236 | let cvar = self.not_empty.get(); 237 | let mut wakers = state 238 | .as_mut() 239 | .project() 240 | .base 241 | .project() 242 | .rx_wakers 243 | .extract_some_wakers(); 244 | drop(state); 245 | if let Some(cvar) = cvar { 246 | cvar.notify_one(); 247 | } 248 | // There is no guarantee that the highest-priority waker will 249 | // actually call poll() again. Therefore, the best we can do 250 | // is wake everyone. 251 | while wakers.wake_all() { 252 | let mut state = self.project_ref().state.lock(); 253 | wakers.extract_more(state.as_mut().project().base.project().rx_wakers); 254 | } 255 | } 256 | 257 | fn wake_all_rx(self: Pin<&Self>, mut state: MutexGuard>) { 258 | // The lock is held. Therefore, we know whether a Condvar must be notified or not. 259 | let cvar = self.not_empty.get(); 260 | let mut wakers = state 261 | .as_mut() 262 | .project() 263 | .base 264 | .project() 265 | .rx_wakers 266 | .extract_some_wakers(); 267 | drop(state); 268 | if let Some(cvar) = cvar { 269 | cvar.notify_all(); 270 | } 271 | // There is no guarantee that the highest-priority waker will 272 | // actually call poll() again. Therefore, the best we can do 273 | // is wake everyone. 274 | while wakers.wake_all() { 275 | let mut state = self.project_ref().state.lock(); 276 | wakers.extract_more(state.as_mut().project().base.project().rx_wakers); 277 | } 278 | } 279 | } 280 | 281 | impl splitrc::Notify for Core { 282 | fn last_tx_did_drop_pinned(self: Pin<&Self>) { 283 | let mut state = self.project_ref().state.lock(); 284 | *state.as_mut().base().project().closed = true; 285 | // We cannot deallocate the queue, as remaining receivers can 286 | // drain it. 287 | self.wake_all_rx(state); 288 | } 289 | 290 | fn last_rx_did_drop_pinned(self: Pin<&Self>) { 291 | let mut state = self.project_ref().state.lock(); 292 | *state.as_mut().base().project().closed = true; 293 | // TODO: deallocate 294 | state.as_mut().project().queue.clear(); 295 | self.wake_all_tx(state); 296 | } 297 | } 298 | 299 | // SendError 300 | 301 | /// An error returned from [Sender::send] when all [Receiver]s are 302 | /// dropped. 303 | /// 304 | /// The unsent value is returned. 305 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 306 | pub struct SendError(pub T); 307 | 308 | impl fmt::Display for SendError { 309 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 310 | write!(f, "failed to send value on channel") 311 | } 312 | } 313 | 314 | impl std::error::Error for SendError {} 315 | 316 | // SyncSender 317 | 318 | /// The sending half of a channel. 319 | #[derive(Debug)] 320 | pub struct SyncSender { 321 | core: Pin>>, 322 | } 323 | 324 | derive_clone!(SyncSender); 325 | 326 | impl SyncSender { 327 | /// Converts `SyncSender` to asynchronous `Sender`. 328 | pub fn into_async(self) -> Sender { 329 | Sender { core: self.core } 330 | } 331 | 332 | /// Send a single value. 333 | /// 334 | /// Returns [SendError] if all receivers are dropped. 335 | pub fn send(&self, value: T) -> Result<(), SendError> { 336 | let mut state = self.core.as_ref().block_until_not_full(); 337 | if state.closed { 338 | assert!(state.as_ref().project_ref().queue.is_empty()); 339 | return Err(SendError(value)); 340 | } 341 | 342 | state.as_mut().project().queue.push_back(value); 343 | 344 | self.core.as_ref().wake_one_rx(state); 345 | Ok(()) 346 | } 347 | 348 | /// Send multiple values. 349 | /// 350 | /// If all receivers are dropped, SendError is returned. The 351 | /// values cannot be returned, as they may have been partially 352 | /// sent when the channel is closed. 353 | pub fn send_iter(&self, values: I) -> Result<(), SendError<()>> 354 | where 355 | I: IntoIterator, 356 | { 357 | let mut values = values.into_iter(); 358 | 359 | // If the iterator is empty, we can avoid acquiring the lock. 360 | let Some(mut value) = values.next() else { 361 | return Ok(()); 362 | }; 363 | 364 | let mut sent_count = 0usize; 365 | 366 | let mut state = self.core.as_ref().block_until_not_full(); 367 | 'outer: loop { 368 | if state.closed { 369 | // We may have sent some values, but the receivers are 370 | // all dropped, and that cleared the queue. 371 | assert!(state.queue.is_empty()); 372 | return Err(SendError(())); 373 | } 374 | 375 | debug_assert!(state.has_capacity()); 376 | state.as_mut().project().queue.push_back(value); 377 | sent_count += 1; 378 | loop { 379 | match values.next() { 380 | Some(v) => { 381 | if state.has_capacity() { 382 | state.as_mut().project().queue.push_back(v); 383 | sent_count += 1; 384 | } else { 385 | value = v; 386 | // We're about to block, but we know we 387 | // sent at least one value, so wake any 388 | // waiters. 389 | state = self.core.as_ref().wake_rx_and_block_while_full(state); 390 | continue 'outer; 391 | } 392 | } 393 | None => { 394 | // Done pulling from the iterator and know we 395 | // sent at least one value. 396 | if sent_count == 1 { 397 | self.core.as_ref().wake_one_rx(state); 398 | } else { 399 | self.core.as_ref().wake_all_rx(state); 400 | } 401 | return Ok(()); 402 | } 403 | } 404 | } 405 | } 406 | } 407 | 408 | /// Drain a [Vec] into the channel without deallocating it. 409 | /// 410 | /// This is a convenience method for allocation-free batched 411 | /// sends. The `values` vector is drained, and then returned with 412 | /// the same capacity it had. 413 | pub fn send_vec(&self, mut values: Vec) -> Result, SendError>> { 414 | match self.send_iter(values.drain(..)) { 415 | Ok(_) => Ok(values), 416 | Err(_) => Err(SendError(values)), 417 | } 418 | } 419 | 420 | /// Automatically accumulate sends into a buffer of size `batch_limit` 421 | /// and send when full. 422 | pub fn autobatch<'a, F, R>(&'a mut self, batch_limit: usize, f: F) -> Result> 423 | where 424 | F: (FnOnce(&mut SyncBatchSender<'a, T>) -> Result>), 425 | { 426 | let mut tx = SyncBatchSender { 427 | sender: self, 428 | capacity: batch_limit, 429 | buffer: Vec::with_capacity(batch_limit), 430 | }; 431 | let r = f(&mut tx)?; 432 | tx.drain()?; 433 | Ok(r) 434 | } 435 | } 436 | 437 | // SyncBatchSender 438 | 439 | /// Automatically batches up values and sends them when a batch is full. 440 | #[derive(Debug)] 441 | pub struct SyncBatchSender<'a, T> { 442 | sender: &'a mut SyncSender, 443 | capacity: usize, 444 | buffer: Vec, 445 | } 446 | 447 | impl SyncBatchSender<'_, T> { 448 | /// Buffers a single value to be sent on the channel. 449 | /// 450 | /// Sends the batch if the buffer is full. 451 | pub fn send(&mut self, value: T) -> Result<(), SendError<()>> { 452 | self.buffer.push(value); 453 | // TODO: consider using the full capacity if Vec overallocated. 454 | if self.buffer.len() == self.capacity { 455 | self.drain() 456 | } else { 457 | Ok(()) 458 | } 459 | } 460 | 461 | /// Buffers multiple values, sending batches as the internal 462 | /// buffer reaches capacity. 463 | pub fn send_iter>(&mut self, values: I) -> Result<(), SendError<()>> { 464 | // TODO: We could return the remainder of I under cancellation. 465 | for value in values.into_iter() { 466 | self.send(value)?; 467 | } 468 | Ok(()) 469 | } 470 | 471 | /// Sends any buffered values, clearing the current batch. 472 | pub fn drain(&mut self) -> Result<(), SendError<()>> { 473 | // TODO: send_iter 474 | match self.sender.send_vec(std::mem::take(&mut self.buffer)) { 475 | Ok(drained_vec) => { 476 | self.buffer = drained_vec; 477 | Ok(()) 478 | } 479 | Err(_) => Err(SendError(())), 480 | } 481 | } 482 | } 483 | 484 | // Sender 485 | 486 | /// The asynchronous sending half of a channel. 487 | #[derive(Debug)] 488 | pub struct Sender { 489 | core: Pin>>, 490 | } 491 | 492 | derive_clone!(Sender); 493 | 494 | impl Sender { 495 | /// Converts asynchronous `Sender` to `SyncSender`. 496 | pub fn into_sync(self) -> SyncSender { 497 | SyncSender { core: self.core } 498 | } 499 | 500 | /// Send a single value. 501 | /// 502 | /// Returns [SendError] if all receivers are dropped. 503 | pub fn send(&self, value: T) -> impl Future>> + '_ { 504 | Send { 505 | sender: self, 506 | value: Some(value), 507 | waker: WakerSlot::new(), 508 | } 509 | } 510 | 511 | /// Send multiple values. 512 | /// 513 | /// If all receivers are dropped, SendError is returned and unsent 514 | /// values are dropped. 515 | pub fn send_iter<'a, I>( 516 | &'a self, 517 | values: I, 518 | ) -> impl Future>> + 'a 519 | where 520 | I: IntoIterator + 'a, 521 | { 522 | SendIter { 523 | sender: self, 524 | values: Some(values.into_iter().peekable()), 525 | waker: WakerSlot::new(), 526 | } 527 | } 528 | 529 | /// Automatically accumulate sends into a buffer of size `batch_limit` 530 | /// and send when full. 531 | /// 532 | /// The callback's future must be boxed to work around [type 533 | /// system limitations in 534 | /// Rust](https://smallcultfollowing.com/babysteps/blog/2023/03/29/thoughts-on-async-closures/). 535 | /// 536 | /// There is [a git 537 | /// branch](https://github.com/chadaustin/batch-channel/tree/async-closure-autobatch) 538 | /// that shows what an API based on async closures would look 539 | /// like. 540 | pub async fn autobatch(self, batch_limit: usize, f: F) -> Result> 541 | where 542 | for<'a> F: (FnOnce(&'a mut BatchSender) -> BoxFuture<'a, Result>>), 543 | { 544 | let mut tx = BatchSender { 545 | sender: self, 546 | batch_limit, 547 | buffer: Vec::with_capacity(batch_limit), 548 | }; 549 | let r = f(&mut tx).await?; 550 | tx.drain().await?; 551 | Ok(r) 552 | } 553 | 554 | /// Same as [Sender::autobatch] except that it immediately returns 555 | /// `()` when `f` returns [SendError]. This is a convenience 556 | /// wrapper for the common case that the future is passed to a 557 | /// spawn function and the receiver being dropped (i.e. 558 | /// [SendError]) is considered a clean cancellation. 559 | pub async fn autobatch_or_cancel(self, capacity: usize, f: F) 560 | where 561 | for<'a> F: (FnOnce(&'a mut BatchSender) -> BoxFuture<'a, Result<(), SendError<()>>>), 562 | { 563 | self.autobatch(capacity, f).await.unwrap_or(()) 564 | } 565 | } 566 | 567 | #[must_use = "futures do nothing unless you `.await` or poll them"] 568 | #[pin_project(PinnedDrop)] 569 | struct Send<'a, T> { 570 | sender: &'a Sender, 571 | value: Option, 572 | #[pin] 573 | waker: WakerSlot, 574 | } 575 | 576 | #[pinned_drop] 577 | impl PinnedDrop for Send<'_, T> { 578 | fn drop(mut self: Pin<&mut Self>) { 579 | if self.waker.is_linked() { 580 | let mut state = self.sender.core.as_ref().project_ref().state.lock(); 581 | state 582 | .as_mut() 583 | .base() 584 | .project() 585 | .tx_wakers 586 | .unlink(self.project().waker); 587 | } 588 | } 589 | } 590 | 591 | impl Future for Send<'_, T> { 592 | type Output = Result<(), SendError>; 593 | 594 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 595 | let mut state = self.sender.core.as_ref().project_ref().state.lock(); 596 | if state.closed { 597 | return Poll::Ready(Err(SendError(self.project().value.take().unwrap()))); 598 | } 599 | if state.has_capacity() { 600 | state 601 | .as_mut() 602 | .project() 603 | .queue 604 | .push_back(self.as_mut().project().value.take().unwrap()); 605 | self.project().sender.core.as_ref().wake_one_rx(state); 606 | Poll::Ready(Ok(())) 607 | } else { 608 | state.as_mut().base().pending_tx(self.project().waker, cx) 609 | } 610 | } 611 | } 612 | 613 | #[must_use = "futures do nothing unless you `.await` or poll them"] 614 | #[pin_project(PinnedDrop)] 615 | struct SendIter<'a, T, I: Iterator> { 616 | sender: &'a Sender, 617 | values: Option>, 618 | #[pin] 619 | waker: WakerSlot, 620 | } 621 | 622 | #[pinned_drop] 623 | impl> PinnedDrop for SendIter<'_, T, I> { 624 | fn drop(mut self: Pin<&mut Self>) { 625 | if self.waker.is_linked() { 626 | let mut state = self.sender.core.as_ref().project_ref().state.lock(); 627 | state 628 | .as_mut() 629 | .base() 630 | .project() 631 | .tx_wakers 632 | .unlink(self.project().waker); 633 | } 634 | } 635 | } 636 | 637 | impl> Future for SendIter<'_, T, I> { 638 | type Output = Result<(), SendError<()>>; 639 | 640 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 641 | // Optimize the case that send_iter was called with an empty 642 | // iterator, and don't even acquire the lock. 643 | { 644 | let pi = self.as_mut().project().values.as_mut().unwrap(); 645 | if pi.peek().is_none() { 646 | return Poll::Ready(Ok(())); 647 | } 648 | // Satisfy borrow checker: we cannot hold a mut reference to 649 | // self through pi before acquiring the lock below. 650 | } 651 | 652 | let mut state = self.sender.core.as_ref().project_ref().state.lock(); 653 | 654 | // There is an awkward set of constraints here. 655 | // 1. To check whether an iterator contains an item, one must be popped. 656 | // 2. If the receivers are cancelled, we'd like to return the iterator whole. 657 | // 3. If we don't know whether there are any remaining items, we must block 658 | // if the queue is at capacity. 659 | // We relax constraint #2 because #3 is preferable. 660 | // TODO: We could return Peekable instead. 661 | 662 | let pi = self.as_mut().project().values.as_mut().unwrap(); 663 | // We already checked above. 664 | debug_assert!(pi.peek().is_some()); 665 | if state.closed { 666 | Poll::Ready(Err(SendError(()))) 667 | } else if !state.has_capacity() { 668 | // We know we have a value to send, but there is no room. 669 | state.as_mut().base().pending_tx(self.project().waker, cx) 670 | } else { 671 | debug_assert!(state.has_capacity()); 672 | state.as_mut().project().queue.push_back(pi.next().unwrap()); 673 | while state.has_capacity() { 674 | match pi.next() { 675 | Some(value) => { 676 | state.as_mut().project().queue.push_back(value); 677 | } 678 | None => { 679 | // Done pulling from the iterator and still 680 | // have capacity, so we're done. 681 | // TODO: wake_one_rx if we only queued one. 682 | self.sender.core.as_ref().wake_all_rx(state); 683 | return Poll::Ready(Ok(())); 684 | } 685 | } 686 | } 687 | // We're out of capacity, and might still have items to 688 | // send. To avoid a round-trip through the scheduler, peek 689 | // ahead. 690 | if pi.peek().is_none() { 691 | self.sender.core.as_ref().wake_all_rx(state); 692 | return Poll::Ready(Ok(())); 693 | } 694 | 695 | // Unconditionally returns Poll::Pending 696 | let pending = state 697 | .as_mut() 698 | .base() 699 | .pending_tx(self.as_mut().project().waker, cx); 700 | self.sender.core.as_ref().wake_all_rx(state); 701 | pending 702 | } 703 | } 704 | } 705 | 706 | // BatchSender 707 | 708 | /// The internal send handle used by [Sender::autobatch]. 709 | /// Builds a buffer of size `batch_limit` and flushes when it's full. 710 | pub struct BatchSender { 711 | sender: Sender, 712 | batch_limit: usize, 713 | buffer: Vec, 714 | } 715 | 716 | impl BatchSender { 717 | /// Adds a value to the internal buffer and flushes it into the 718 | /// queue when the buffer fills. 719 | pub async fn send(&mut self, value: T) -> Result<(), SendError<()>> { 720 | self.buffer.push(value); 721 | if self.buffer.len() == self.batch_limit { 722 | self.drain().await?; 723 | } 724 | Ok(()) 725 | } 726 | 727 | async fn drain(&mut self) -> Result<(), SendError<()>> { 728 | self.sender.send_iter(self.buffer.drain(..)).await?; 729 | assert!(self.buffer.is_empty()); 730 | Ok(()) 731 | } 732 | } 733 | 734 | // Receiver 735 | 736 | /// The receiving half of a channel. Reads are asynchronous. 737 | #[derive(Debug)] 738 | pub struct Receiver { 739 | core: Pin>>, 740 | } 741 | 742 | derive_clone!(Receiver); 743 | 744 | #[must_use = "futures do nothing unless you `.await` or poll them"] 745 | #[pin_project(PinnedDrop)] 746 | struct Recv<'a, T> { 747 | receiver: &'a Receiver, 748 | #[pin] 749 | waker: WakerSlot, 750 | } 751 | 752 | #[pinned_drop] 753 | impl PinnedDrop for Recv<'_, T> { 754 | fn drop(mut self: Pin<&mut Self>) { 755 | if self.waker.is_linked() { 756 | let mut state = self.receiver.core.as_ref().project_ref().state.lock(); 757 | state 758 | .as_mut() 759 | .base() 760 | .project() 761 | .rx_wakers 762 | .unlink(self.project().waker); 763 | } 764 | } 765 | } 766 | 767 | impl Future for Recv<'_, T> { 768 | type Output = Option; 769 | 770 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 771 | let mut state = self.receiver.core.as_ref().project_ref().state.lock(); 772 | match state.as_mut().project().queue.pop_front() { 773 | Some(value) => { 774 | self.receiver.core.as_ref().wake_all_tx(state); 775 | Poll::Ready(Some(value)) 776 | } 777 | None => { 778 | if state.closed { 779 | Poll::Ready(None) 780 | } else { 781 | state.as_mut().base().pending_rx(self.project().waker, cx) 782 | } 783 | } 784 | } 785 | } 786 | } 787 | 788 | #[must_use = "futures do nothing unless you .await or poll them"] 789 | #[pin_project(PinnedDrop)] 790 | struct RecvBatch<'a, T> { 791 | receiver: &'a Receiver, 792 | element_limit: usize, 793 | #[pin] 794 | waker: WakerSlot, 795 | } 796 | 797 | #[pinned_drop] 798 | impl PinnedDrop for RecvBatch<'_, T> { 799 | fn drop(mut self: Pin<&mut Self>) { 800 | if self.waker.is_linked() { 801 | let mut state = self.receiver.core.as_ref().project_ref().state.lock(); 802 | state 803 | .as_mut() 804 | .base() 805 | .project() 806 | .rx_wakers 807 | .unlink(self.project().waker); 808 | } 809 | } 810 | } 811 | 812 | impl Future for RecvBatch<'_, T> { 813 | type Output = Vec; 814 | 815 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 816 | let mut state = self.receiver.core.as_ref().project_ref().state.lock(); 817 | let q = &mut state.as_mut().project().queue; 818 | let q_len = q.len(); 819 | if q_len == 0 { 820 | if state.closed { 821 | return Poll::Ready(Vec::new()); 822 | } else { 823 | return state.as_mut().base().pending_rx(self.project().waker, cx); 824 | } 825 | } 826 | 827 | let capacity = min(q_len, self.element_limit); 828 | let v = Vec::from_iter(q.drain(..capacity)); 829 | self.receiver.core.as_ref().wake_all_tx(state); 830 | Poll::Ready(v) 831 | } 832 | } 833 | 834 | #[must_use = "futures do nothing unless you .await or poll them"] 835 | #[pin_project(PinnedDrop)] 836 | struct RecvVec<'a, T> { 837 | receiver: &'a Receiver, 838 | element_limit: usize, 839 | vec: &'a mut Vec, 840 | #[pin] 841 | waker: WakerSlot, 842 | } 843 | 844 | #[pinned_drop] 845 | impl PinnedDrop for RecvVec<'_, T> { 846 | fn drop(mut self: Pin<&mut Self>) { 847 | if self.waker.is_linked() { 848 | let mut state = self.receiver.core.as_ref().project_ref().state.lock(); 849 | state 850 | .as_mut() 851 | .base() 852 | .project() 853 | .rx_wakers 854 | .unlink(self.project().waker); 855 | } 856 | } 857 | } 858 | 859 | impl Future for RecvVec<'_, T> { 860 | type Output = (); 861 | 862 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 863 | let mut state = self.receiver.core.as_ref().project_ref().state.lock(); 864 | let q = &mut state.as_mut().project().queue; 865 | let q_len = q.len(); 866 | if q_len == 0 { 867 | if state.closed { 868 | assert!(self.vec.is_empty()); 869 | return Poll::Ready(()); 870 | } else { 871 | return state.as_mut().base().pending_rx(self.project().waker, cx); 872 | } 873 | } 874 | 875 | let capacity = min(q_len, self.element_limit); 876 | self.as_mut().project().vec.extend(q.drain(..capacity)); 877 | self.project().receiver.core.as_ref().wake_all_tx(state); 878 | Poll::Ready(()) 879 | } 880 | } 881 | 882 | impl Receiver { 883 | /// Converts asynchronous `Receiver` to `SyncReceiver`. 884 | pub fn into_sync(self) -> SyncReceiver { 885 | SyncReceiver { core: self.core } 886 | } 887 | 888 | /// Wait for a single value from the channel. 889 | /// 890 | /// Returns [None] if all [Sender]s are dropped. 891 | pub fn recv(&self) -> impl Future> + '_ { 892 | Recv { 893 | receiver: self, 894 | waker: WakerSlot::new(), 895 | } 896 | } 897 | 898 | // TODO: try_recv 899 | 900 | /// Wait for up to `element_limit` values from the channel. 901 | /// 902 | /// Up to `element_limit` values are returned if they're already 903 | /// available. Otherwise, waits for any values to be available. 904 | /// 905 | /// Returns an empty [Vec] if all [Sender]s are dropped. 906 | pub fn recv_batch(&self, element_limit: usize) -> impl Future> + '_ { 907 | RecvBatch { 908 | receiver: self, 909 | element_limit, 910 | waker: WakerSlot::new(), 911 | } 912 | } 913 | 914 | // TODO: try_recv_batch 915 | 916 | /// Wait for up to `element_limit` values from the channel and 917 | /// store them in `vec`. 918 | /// 919 | /// `vec` should be empty when passed in. Nevertheless, `recv_vec` 920 | /// will clear it before adding values. The intent of `recv_vec` 921 | /// is that batches can be repeatedly read by workers without new 922 | /// allocations. 923 | /// 924 | /// It's not required, but `vec`'s capacity should be greater than 925 | /// or equal to element_limit to avoid reallocation. 926 | pub fn recv_vec<'a>( 927 | &'a self, 928 | element_limit: usize, 929 | vec: &'a mut Vec, 930 | ) -> impl Future + 'a { 931 | vec.clear(); 932 | RecvVec { 933 | receiver: self, 934 | element_limit, 935 | vec, 936 | waker: WakerSlot::new(), 937 | } 938 | } 939 | 940 | // TODO: try_recv_vec 941 | } 942 | 943 | // SyncReceiver 944 | 945 | /// The synchronous receiving half of a channel. 946 | #[derive(Debug)] 947 | pub struct SyncReceiver { 948 | core: Pin>>, 949 | } 950 | 951 | derive_clone!(SyncReceiver); 952 | 953 | impl SyncReceiver { 954 | /// Converts `SyncReceiver` to asynchronous `Receiver`. 955 | pub fn into_async(self) -> Receiver { 956 | Receiver { core: self.core } 957 | } 958 | 959 | /// Block waiting for a single value from the channel. 960 | /// 961 | /// Returns [None] if all [Sender]s are dropped. 962 | pub fn recv(&self) -> Option { 963 | let mut state = self.core.as_ref().block_until_not_empty(); 964 | match state.as_mut().project().queue.pop_front() { 965 | Some(value) => { 966 | self.core.as_ref().wake_all_tx(state); 967 | Some(value) 968 | } 969 | None => { 970 | assert!(state.closed); 971 | None 972 | } 973 | } 974 | } 975 | 976 | /// Block waiting for values from the channel. 977 | /// 978 | /// Up to `element_limit` values are returned if they're already 979 | /// available. Otherwise, waits for any values to be available. 980 | /// 981 | /// Returns an empty [Vec] if all [Sender]s are dropped. 982 | pub fn recv_batch(&self, element_limit: usize) -> Vec { 983 | let mut state = self.core.as_ref().block_until_not_empty(); 984 | 985 | let q = &mut state.as_mut().project().queue; 986 | let q_len = q.len(); 987 | if q_len == 0 { 988 | assert!(state.closed); 989 | return Vec::new(); 990 | } 991 | 992 | let capacity = min(q_len, element_limit); 993 | let v = Vec::from_iter(q.drain(..capacity)); 994 | self.core.as_ref().wake_all_tx(state); 995 | v 996 | } 997 | 998 | /// Wait for up to `element_limit` values from the channel and 999 | /// store them in `vec`. 1000 | /// 1001 | /// `vec` should be empty when passed in. Nevertheless, `recv_vec` 1002 | /// will clear it before adding values. The intent of `recv_vec` 1003 | /// is that batches can be repeatedly read by workers without new 1004 | /// allocations. 1005 | /// 1006 | /// It's not required, but `vec`'s capacity should be greater than 1007 | /// or equal to element_limit to avoid reallocation. 1008 | pub fn recv_vec(&self, element_limit: usize, vec: &mut Vec) { 1009 | vec.clear(); 1010 | 1011 | let mut state = self.core.as_ref().block_until_not_empty(); 1012 | let q = &mut state.as_mut().project().queue; 1013 | let q_len = q.len(); 1014 | if q_len == 0 { 1015 | assert!(state.closed); 1016 | // The result vector is already cleared. 1017 | return; 1018 | } 1019 | 1020 | let capacity = min(q_len, element_limit); 1021 | vec.extend(q.drain(..capacity)); 1022 | self.core.as_ref().wake_all_tx(state); 1023 | } 1024 | } 1025 | 1026 | // Constructors 1027 | 1028 | /// Allocates a bounded channel and returns the sender, receiver 1029 | /// pair. 1030 | /// 1031 | /// Rust async is polling, so unbuffered channels are not supported. 1032 | /// Therefore, a capacity of 0 is rounded up to 1. 1033 | pub fn bounded(capacity: usize) -> (Sender, Receiver) { 1034 | let capacity = capacity.max(1); 1035 | let core = Core { 1036 | state: Mutex::new(State { 1037 | base: StateBase { 1038 | capacity, 1039 | closed: false, 1040 | tx_wakers: WakerList::new(), 1041 | rx_wakers: WakerList::new(), 1042 | }, 1043 | queue: VecDeque::new(), 1044 | }), 1045 | not_empty: OnceLock::new(), 1046 | not_full: OnceLock::new(), 1047 | }; 1048 | let (core_tx, core_rx) = splitrc::pin(core); 1049 | (Sender { core: core_tx }, Receiver { core: core_rx }) 1050 | } 1051 | 1052 | /// Allocates a bounded channel and returns the synchronous handles as 1053 | /// a sender, receiver pair. 1054 | /// 1055 | /// Because handles can be converted freely between sync and async, 1056 | /// and Rust async is polling, unbuffered channels are not 1057 | /// supported. A capacity of 0 is rounded up to 1. 1058 | pub fn bounded_sync(capacity: usize) -> (SyncSender, SyncReceiver) { 1059 | let (tx, rx) = bounded(capacity); 1060 | (tx.into_sync(), rx.into_sync()) 1061 | } 1062 | 1063 | /// Allocates an unbounded channel and returns the sender, 1064 | /// receiver pair. 1065 | pub fn unbounded() -> (Sender, Receiver) { 1066 | let core = Core { 1067 | state: Mutex::new(State { 1068 | base: StateBase { 1069 | capacity: UNBOUNDED_CAPACITY, 1070 | closed: false, 1071 | tx_wakers: WakerList::new(), 1072 | rx_wakers: WakerList::new(), 1073 | }, 1074 | queue: VecDeque::new(), 1075 | }), 1076 | not_empty: OnceLock::new(), 1077 | not_full: OnceLock::new(), 1078 | }; 1079 | let (core_tx, core_rx) = splitrc::pin(core); 1080 | (Sender { core: core_tx }, Receiver { core: core_rx }) 1081 | } 1082 | 1083 | /// Allocates an unbounded channel and returns the synchronous handles 1084 | /// as a sender, receiver pair. 1085 | pub fn unbounded_sync() -> (SyncSender, SyncReceiver) { 1086 | let (tx, rx) = unbounded(); 1087 | (tx.into_sync(), rx.into_sync()) 1088 | } 1089 | -------------------------------------------------------------------------------- /tests/api.rs: -------------------------------------------------------------------------------- 1 | use futures::pin_mut; 2 | use std::future::Future; 3 | use std::sync::atomic::AtomicUsize; 4 | use std::sync::atomic::Ordering; 5 | use std::sync::Arc; 6 | use std::sync::Mutex; 7 | use std::task::Poll; 8 | use std::task::Wake; 9 | 10 | #[test] 11 | fn sync_to_async_and_back() { 12 | let (tx, rx) = batch_channel::bounded::<()>(1); 13 | 14 | let tx = tx.into_sync(); 15 | let rx = rx.into_sync(); 16 | 17 | let tx = tx.into_async(); 18 | let rx = rx.into_async(); 19 | 20 | _ = (tx, rx); 21 | } 22 | 23 | static COUNT: AtomicUsize = AtomicUsize::new(0); 24 | 25 | #[derive(Debug)] 26 | struct Counter { 27 | inc: usize, 28 | } 29 | 30 | impl Counter { 31 | fn new() -> Counter { 32 | let inc = 1; 33 | COUNT.fetch_add(inc, Ordering::AcqRel); 34 | Counter { inc } 35 | } 36 | } 37 | 38 | impl Drop for Counter { 39 | fn drop(&mut self) { 40 | COUNT.fetch_sub(self.inc, Ordering::AcqRel); 41 | } 42 | } 43 | 44 | // cargo test runs threads multithreaded by default. crazy. 45 | static COUNTER_TEST_MUTEX: Mutex<()> = Mutex::new(()); 46 | 47 | #[test] 48 | fn counter_instances_count() { 49 | let _l = COUNTER_TEST_MUTEX.lock().unwrap(); 50 | assert_eq!(0, COUNT.load(Ordering::Acquire)); 51 | let c1 = Counter::new(); 52 | assert_eq!(1, COUNT.load(Ordering::Acquire)); 53 | let c2 = Counter::new(); 54 | assert_eq!(2, COUNT.load(Ordering::Acquire)); 55 | drop(c1); 56 | assert_eq!(1, COUNT.load(Ordering::Acquire)); 57 | drop(c2); 58 | assert_eq!(0, COUNT.load(Ordering::Acquire)); 59 | } 60 | 61 | #[test] 62 | fn closing_rx_drops_elements() { 63 | let _l = COUNTER_TEST_MUTEX.lock().unwrap(); 64 | let (tx, rx) = batch_channel::bounded_sync(2); 65 | tx.send(Counter::new()).unwrap(); 66 | assert_eq!(1, COUNT.load(Ordering::Acquire)); 67 | tx.send(Counter::new()).unwrap(); 68 | assert_eq!(2, COUNT.load(Ordering::Acquire)); 69 | drop(rx); 70 | assert_eq!(0, COUNT.load(Ordering::Acquire)); 71 | } 72 | 73 | struct TestWake; 74 | 75 | impl Wake for TestWake { 76 | fn wake(self: Arc) {} 77 | } 78 | 79 | #[test] 80 | fn cancel_send() { 81 | let waker = Arc::new(TestWake).into(); 82 | let mut cx = std::task::Context::from_waker(&waker); 83 | 84 | let (tx, _rx) = batch_channel::bounded(1); 85 | let send_fut = tx.send('a'); 86 | pin_mut!(send_fut); 87 | assert_eq!(Poll::Ready(Ok(())), send_fut.poll(&mut cx)); 88 | 89 | let send_fut = tx.send('b'); 90 | pin_mut!(send_fut); 91 | assert_eq!(Poll::Pending, send_fut.as_mut().poll(&mut cx)); 92 | drop(send_fut); 93 | } 94 | 95 | #[test] 96 | fn cancel_send_iter() { 97 | let waker = Arc::new(TestWake).into(); 98 | let mut cx = std::task::Context::from_waker(&waker); 99 | 100 | let (tx, _rx) = batch_channel::bounded(1); 101 | let send_fut = tx.send_iter(vec!['a', 'b']); 102 | pin_mut!(send_fut); 103 | assert_eq!(Poll::Pending, send_fut.as_mut().poll(&mut cx)); 104 | drop(send_fut); 105 | } 106 | 107 | #[test] 108 | fn cancel_recv() { 109 | let waker = Arc::new(TestWake).into(); 110 | let mut cx = std::task::Context::from_waker(&waker); 111 | 112 | let (_tx, rx) = batch_channel::bounded::(1); 113 | let recv_fut = rx.recv(); 114 | pin_mut!(recv_fut); 115 | assert_eq!(Poll::Pending, recv_fut.as_mut().poll(&mut cx)); 116 | drop(recv_fut); 117 | } 118 | 119 | #[test] 120 | fn cancel_recv_batch() { 121 | let waker = Arc::new(TestWake).into(); 122 | let mut cx = std::task::Context::from_waker(&waker); 123 | 124 | let (_tx, rx) = batch_channel::bounded::(1); 125 | let recv_fut = rx.recv_batch(100); 126 | pin_mut!(recv_fut); 127 | assert_eq!(Poll::Pending, recv_fut.as_mut().poll(&mut cx)); 128 | drop(recv_fut); 129 | } 130 | 131 | #[test] 132 | fn cancel_recv_vec() { 133 | let waker = Arc::new(TestWake).into(); 134 | let mut cx = std::task::Context::from_waker(&waker); 135 | 136 | let (_tx, rx) = batch_channel::bounded::(1); 137 | let mut v = Vec::with_capacity(100); 138 | let recv_fut = rx.recv_vec(v.capacity(), &mut v); 139 | pin_mut!(recv_fut); 140 | assert_eq!(Poll::Pending, recv_fut.as_mut().poll(&mut cx)); 141 | drop(recv_fut); 142 | } 143 | -------------------------------------------------------------------------------- /tests/bounded.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use batch_channel::SendError; 4 | use futures::FutureExt; 5 | 6 | mod fixture; 7 | use fixture::*; 8 | 9 | #[test] 10 | fn zero_capacity_rounds_up_to_one() { 11 | let (tx, rx) = batch_channel::bounded(1); 12 | 13 | block_on(async move { 14 | tx.send(10).await.unwrap(); 15 | assert_eq!(Some(10), rx.recv().await); 16 | }); 17 | } 18 | 19 | #[test] 20 | fn send_returns_SendError_if_receivers_dropped() { 21 | let (tx, rx) = batch_channel::bounded(1); 22 | drop(rx); 23 | assert_eq!(Err(SendError("x")), block_on(tx.send("x"))); 24 | } 25 | 26 | #[test] 27 | fn recv_unblocks_send() { 28 | let mut pool = LocalPool::new(); 29 | let (tx, rx) = batch_channel::bounded(1); 30 | 31 | let state = StateVar::new(""); 32 | 33 | pool.spawn({ 34 | let state = state.clone(); 35 | async move { 36 | state.set("sending"); 37 | tx.send(10).await.unwrap(); 38 | state.set("sent 1"); 39 | tx.send(20).await.unwrap(); 40 | state.set("sent 2"); 41 | } 42 | }); 43 | 44 | pool.run_until_stalled(); 45 | assert_eq!("sent 1", state.get()); 46 | assert_eq!(Some(10), block_on(rx.recv())); 47 | 48 | pool.run_until_stalled(); 49 | assert_eq!("sent 2", state.get()); 50 | assert_eq!(Some(20), block_on(rx.recv())); 51 | 52 | pool.run(); 53 | } 54 | 55 | #[test] 56 | fn recv_batch_unblocks_send() { 57 | let mut pool = LocalPool::new(); 58 | let (tx, rx) = batch_channel::bounded(1); 59 | 60 | let state = StateVar::new(""); 61 | 62 | pool.spawn({ 63 | let state = state.clone(); 64 | async move { 65 | state.set("sending"); 66 | tx.send(10).await.unwrap(); 67 | state.set("sent 1"); 68 | tx.send(20).await.unwrap(); 69 | state.set("sent 2"); 70 | } 71 | }); 72 | 73 | pool.run_until_stalled(); 74 | assert_eq!("sent 1", state.get()); 75 | assert_eq!(vec![10], block_on(rx.recv_batch(5))); 76 | 77 | pool.run_until_stalled(); 78 | assert_eq!("sent 2", state.get()); 79 | assert_eq!(vec![20], block_on(rx.recv_batch(5))); 80 | 81 | pool.run(); 82 | } 83 | 84 | #[test] 85 | fn recv_vec_unblocks_send() { 86 | let mut pool = LocalPool::new(); 87 | let (tx, rx) = batch_channel::bounded(1); 88 | 89 | let state = StateVar::new(""); 90 | 91 | pool.spawn({ 92 | let state = state.clone(); 93 | async move { 94 | state.set("sending"); 95 | tx.send(10).await.unwrap(); 96 | state.set("sent 1"); 97 | tx.send(20).await.unwrap(); 98 | state.set("sent 2"); 99 | } 100 | }); 101 | 102 | pool.run_until_stalled(); 103 | assert_eq!("sent 1", state.get()); 104 | let mut batch = Vec::new(); 105 | block_on(rx.recv_vec(5, &mut batch)); 106 | assert_eq!(vec![10], batch); 107 | 108 | pool.run_until_stalled(); 109 | assert_eq!("sent 2", state.get()); 110 | block_on(rx.recv_vec(5, &mut batch)); 111 | assert_eq!(vec![20], batch); 112 | 113 | pool.run(); 114 | } 115 | 116 | #[test] 117 | fn recv_batch_returning_all() { 118 | let (tx, rx) = batch_channel::bounded(3); 119 | 120 | block_on(async move { 121 | tx.send_iter([10, 20, 30]).await.unwrap(); 122 | assert_eq!(vec![10, 20, 30], rx.recv_batch(100).await); 123 | }) 124 | } 125 | 126 | #[test] 127 | fn send_batch_blocks_as_needed() { 128 | let mut pool = LocalPool::new(); 129 | let (tx, rx) = batch_channel::bounded(1); 130 | 131 | pool.spawn(async move { 132 | tx.send_iter([1, 2, 3, 4]).await.unwrap(); 133 | }); 134 | 135 | pool.block_on(async move { 136 | assert_eq!(Some(1), rx.recv().await); 137 | assert_eq!(Some(2), rx.recv().await); 138 | assert_eq!(Some(3), rx.recv().await); 139 | assert_eq!(Some(4), rx.recv().await); 140 | assert_eq!(None, rx.recv().await); 141 | }); 142 | 143 | pool.run(); 144 | } 145 | 146 | #[test] 147 | fn autobatch_batches() { 148 | let mut pool = LocalPool::new(); 149 | let state = AtomicVar::new(""); 150 | 151 | let (tx, rx) = batch_channel::bounded(1); 152 | let inner = state.clone(); 153 | pool.spawn(async move { 154 | tx.autobatch(2, move |tx| { 155 | async move { 156 | inner.set("0"); 157 | tx.send(1).await?; 158 | inner.set("1"); 159 | tx.send(2).await?; 160 | inner.set("2"); 161 | tx.send(3).await?; 162 | inner.set("3"); 163 | tx.send(4).await?; 164 | inner.set("4"); 165 | Ok(()) 166 | } 167 | .boxed() 168 | }) 169 | .await 170 | .unwrap() 171 | }); 172 | 173 | pool.run_until_stalled(); 174 | assert_eq!("1", state.get()); 175 | assert_eq!(Some(1), pool.run_until(rx.recv())); 176 | assert_eq!("1", state.get()); 177 | assert_eq!(Some(2), pool.run_until(rx.recv())); 178 | assert_eq!("3", state.get()); 179 | assert_eq!(Some(3), pool.run_until(rx.recv())); 180 | assert_eq!("3", state.get()); 181 | assert_eq!(Some(4), pool.run_until(rx.recv())); 182 | assert_eq!("4", state.get()); 183 | assert_eq!(None, pool.run_until(rx.recv())); 184 | } 185 | 186 | #[test] 187 | fn autobatch_or_cancel_stops_if_receiver_is_dropped() { 188 | let mut pool = LocalPool::new(); 189 | let state = AtomicVar::new(""); 190 | 191 | let (tx, rx) = batch_channel::bounded(1); 192 | let inner = state.clone(); 193 | pool.spawn(tx.autobatch_or_cancel(2, move |tx| { 194 | async move { 195 | inner.set("0"); 196 | tx.send(1).await?; 197 | inner.set("1"); 198 | tx.send(2).await?; 199 | inner.set("2"); 200 | tx.send(3).await?; 201 | inner.set("3"); 202 | tx.send(4).await?; 203 | inner.set("4"); 204 | Ok(()) 205 | } 206 | .boxed() 207 | })); 208 | 209 | pool.run_until_stalled(); 210 | assert_eq!("1", state.get()); 211 | assert_eq!(Some(1), pool.run_until(rx.recv())); 212 | assert_eq!("1", state.get()); 213 | drop(rx); 214 | pool.run_until_stalled(); 215 | assert_eq!("1", state.get()); 216 | } 217 | 218 | #[test] 219 | fn clone_bounded_sender() { 220 | let mut pool = LocalPool::new(); 221 | let (tx1, rx) = batch_channel::bounded::<()>(1); 222 | let tx2 = tx1.clone(); 223 | drop(tx1); 224 | let tx3 = tx2.clone(); 225 | drop(tx2); 226 | drop(tx3); 227 | assert_eq!(None, pool.run_until(rx.recv())); 228 | } 229 | 230 | #[test] 231 | fn send_empty_iter_immediately_returns() { 232 | let mut pool = LocalPool::new(); 233 | let state = AtomicVar::new(""); 234 | let (tx, rx) = batch_channel::bounded::<()>(1); 235 | pool.spawn({ 236 | let state = state.clone(); 237 | async move { 238 | state.set("1"); 239 | tx.send_iter([]).await.unwrap(); 240 | state.set("2"); 241 | } 242 | }); 243 | 244 | pool.spawn({ 245 | let state = state.clone(); 246 | async move { 247 | assert_eq!("2", state.get()); 248 | assert_eq!(None, rx.recv().await); 249 | state.set("3"); 250 | } 251 | }); 252 | 253 | pool.run(); 254 | assert_eq!("3", state.get()); 255 | } 256 | 257 | #[test] 258 | fn send_empty_iter_immediately_returns_even_if_rx_is_dropped() { 259 | let mut pool = LocalPool::new(); 260 | let (tx, rx) = batch_channel::bounded::<()>(1); 261 | drop(rx); 262 | pool.block_on(async move { 263 | assert_eq!(Ok(()), tx.send_iter([]).await); 264 | }); 265 | } 266 | 267 | #[test] 268 | fn send_iter_completes_if_there_is_just_enough_capacity() { 269 | let mut pool = LocalPool::new(); 270 | let state = AtomicVar::new(""); 271 | let (tx, rx) = batch_channel::bounded(2); 272 | pool.spawn({ 273 | let state = state.clone(); 274 | async move { 275 | state.set("1"); 276 | tx.send_iter([1, 2]).await.unwrap(); 277 | state.set("2"); 278 | } 279 | }); 280 | 281 | pool.spawn({ 282 | let state = state.clone(); 283 | async move { 284 | assert_eq!("2", state.get()); 285 | assert_eq!(Some(1), rx.recv().await); 286 | assert_eq!(Some(2), rx.recv().await); 287 | state.set("3"); 288 | } 289 | }); 290 | 291 | pool.run(); 292 | assert_eq!("3", state.get()); 293 | } 294 | 295 | #[test] 296 | fn send_iter_wakes_receivers_if_it_hits_capacity() { 297 | let mut pool = LocalPool::new(); 298 | let reader_state = AtomicVar::new(""); 299 | let writer_state = AtomicVar::new(""); 300 | let (tx, rx) = batch_channel::bounded(2); 301 | pool.spawn({ 302 | let reader_state = reader_state.clone(); 303 | async move { 304 | reader_state.set("a"); 305 | assert_eq!(Some(1), rx.recv().await); 306 | reader_state.set("b"); 307 | assert_eq!(Some(2), rx.recv().await); 308 | reader_state.set("c"); 309 | assert_eq!(Some(3), rx.recv().await); 310 | reader_state.set("d"); 311 | assert_eq!(None, rx.recv().await); 312 | reader_state.set("e"); 313 | } 314 | }); 315 | pool.spawn({ 316 | let writer_state = writer_state.clone(); 317 | async move { 318 | writer_state.set("1"); 319 | tx.send_iter([1, 2, 3]).await.unwrap(); 320 | writer_state.set("2"); 321 | } 322 | }); 323 | 324 | pool.run(); 325 | assert_eq!("e", reader_state.get()); 326 | assert_eq!("2", writer_state.get()); 327 | } 328 | 329 | #[test] 330 | fn sender_and_receiver_of_noncloneable_can_clone() { 331 | struct NoClone; 332 | let (tx, rx) = batch_channel::bounded::(1); 333 | _ = tx.clone(); 334 | _ = rx.clone(); 335 | } 336 | 337 | #[test] 338 | fn recv_vec_blocking() { 339 | const CAPACITY: usize = 3; 340 | let (tx, rx) = batch_channel::bounded_sync(CAPACITY); 341 | tx.send_iter([10, 20]).unwrap(); 342 | let mut vec = Vec::with_capacity(CAPACITY); 343 | rx.recv_vec(CAPACITY, &mut vec); 344 | assert_eq!(vec![10, 20], vec); 345 | } 346 | -------------------------------------------------------------------------------- /tests/fixture.rs: -------------------------------------------------------------------------------- 1 | pub use futures::executor::block_on; 2 | pub use futures::executor::LocalPool; 3 | pub use futures::task::LocalSpawnExt; 4 | use futures::Future; 5 | use std::cell::RefCell; 6 | use std::rc::Rc; 7 | use std::sync::Arc; 8 | use std::sync::Mutex; 9 | 10 | pub trait TestPool { 11 | fn spawn + 'static>(&self, future: F); 12 | 13 | #[allow(dead_code)] 14 | fn block_on + 'static>(&mut self, future: F) -> T; 15 | } 16 | 17 | impl TestPool for LocalPool { 18 | fn spawn(&self, future: F) 19 | where 20 | F: Future + 'static, 21 | { 22 | self.spawner().spawn_local(future).unwrap(); 23 | } 24 | 25 | fn block_on(&mut self, future: F) -> T 26 | where 27 | F: Future + 'static, 28 | { 29 | self.run_until(future) 30 | } 31 | } 32 | 33 | /// A mutable slot so asynchronous single-threaded tests can assert 34 | /// progress. 35 | #[derive(Clone, Debug)] 36 | pub struct StateVar(Rc>); 37 | 38 | #[allow(dead_code)] 39 | impl StateVar { 40 | pub fn new(init: T) -> Self { 41 | Self(Rc::new(RefCell::new(init))) 42 | } 43 | 44 | pub fn set(&self, value: T) { 45 | *self.0.borrow_mut() = value; 46 | } 47 | 48 | pub fn borrow(&self) -> std::cell::Ref<'_, T> { 49 | self.0.borrow() 50 | } 51 | 52 | pub fn borrow_mut(&self) -> std::cell::RefMut<'_, T> { 53 | self.0.borrow_mut() 54 | } 55 | } 56 | 57 | #[allow(dead_code)] 58 | impl StateVar { 59 | pub fn get(&self) -> T { 60 | self.0.borrow().clone() 61 | } 62 | } 63 | 64 | #[allow(dead_code)] 65 | impl StateVar { 66 | pub fn default() -> Self { 67 | Self::new(Default::default()) 68 | } 69 | } 70 | 71 | /// A mutable, atomic slot so asynchronous tests can assert progress. 72 | #[derive(Clone, Debug)] 73 | // Could use crossbeam::AtomicCell 74 | pub struct AtomicVar(Arc>); 75 | 76 | #[allow(dead_code)] 77 | impl AtomicVar { 78 | pub fn new(init: T) -> Self { 79 | Self(Arc::new(Mutex::new(init))) 80 | } 81 | 82 | pub fn set(&self, value: T) { 83 | *self.0.lock().unwrap() = value; 84 | } 85 | } 86 | 87 | #[allow(dead_code)] 88 | impl AtomicVar { 89 | pub fn get(&self) -> T { 90 | self.0.lock().unwrap().clone() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/unbounded.rs: -------------------------------------------------------------------------------- 1 | mod fixture; 2 | use fixture::*; 3 | 4 | #[test] 5 | fn send_and_recv() { 6 | block_on(async move { 7 | let (tx, rx) = batch_channel::unbounded(); 8 | tx.into_sync().send(10).unwrap(); 9 | assert_eq!(Some(10), rx.recv().await); 10 | }) 11 | } 12 | 13 | #[test] 14 | fn recv_returns_none_if_sender_dropped() { 15 | block_on(async move { 16 | let (tx, rx) = batch_channel::unbounded(); 17 | drop(tx); 18 | assert_eq!(None as Option<()>, rx.recv().await); 19 | }) 20 | } 21 | 22 | #[test] 23 | fn recv_returns_value_if_sender_sent_before_dropping() { 24 | block_on(async move { 25 | let (tx, rx) = batch_channel::unbounded(); 26 | tx.into_sync().send(10).unwrap(); 27 | assert_eq!(Some(10), rx.recv().await); 28 | }) 29 | } 30 | 31 | #[test] 32 | fn recv_wakes_when_sender_sends() { 33 | let (tx, rx) = batch_channel::unbounded(); 34 | 35 | let mut pool = LocalPool::new(); 36 | pool.spawn(async move { 37 | assert_eq!(Some(()), rx.recv().await); 38 | }); 39 | 40 | tx.into_sync().send(()).unwrap(); 41 | 42 | pool.run(); 43 | } 44 | 45 | #[test] 46 | fn recv_wakes_when_sender_drops() { 47 | let (tx, rx) = batch_channel::unbounded(); 48 | 49 | let mut pool = LocalPool::new(); 50 | pool.spawn(async move { 51 | assert_eq!(None as Option<()>, rx.recv().await); 52 | }); 53 | 54 | pool.spawn(async move { 55 | drop(tx); 56 | }); 57 | 58 | pool.run(); 59 | } 60 | 61 | #[test] 62 | fn send_fails_when_receiver_drops() { 63 | block_on(async move { 64 | let (tx, rx) = batch_channel::unbounded_sync(); 65 | drop(rx); 66 | assert_eq!(Err(batch_channel::SendError(())), tx.send(())); 67 | }) 68 | } 69 | 70 | #[test] 71 | fn two_receivers_and_two_senders() { 72 | block_on(async move { 73 | let (tx1, rx1) = batch_channel::unbounded(); 74 | let tx2 = tx1.clone(); 75 | let rx2 = rx1.clone(); 76 | tx1.send(1).await.unwrap(); 77 | tx2.send(2).await.unwrap(); 78 | assert_eq!(Some(1), rx1.recv().await); 79 | assert_eq!(Some(2), rx2.recv().await); 80 | }) 81 | } 82 | 83 | #[test] 84 | fn two_reads_from_one_push() { 85 | let mut pool = LocalPool::new(); 86 | 87 | let (tx, rx1) = batch_channel::unbounded(); 88 | let tx = tx.into_sync(); 89 | let rx2 = rx1.clone(); 90 | 91 | pool.spawn(async move { 92 | assert_eq!(Some(10), rx1.recv().await); 93 | }); 94 | pool.spawn(async move { 95 | assert_eq!(Some(20), rx2.recv().await); 96 | }); 97 | tx.send_iter([10, 20]).unwrap(); 98 | 99 | pool.run() 100 | } 101 | 102 | #[test] 103 | fn send_batch_wakes_both() { 104 | let mut pool = LocalPool::new(); 105 | 106 | let (tx, rx1) = batch_channel::unbounded(); 107 | let tx = tx.into_sync(); 108 | let rx2 = rx1.clone(); 109 | 110 | pool.spawn(async move { 111 | assert_eq!(Some(10), rx1.recv().await); 112 | }); 113 | pool.spawn(async move { 114 | assert_eq!(Some(20), rx2.recv().await); 115 | }); 116 | pool.spawn(async move { 117 | tx.send_iter([10, 20]).unwrap(); 118 | }); 119 | 120 | pool.run() 121 | } 122 | 123 | #[test] 124 | fn send_iter_array() { 125 | let (tx, rx) = batch_channel::unbounded(); 126 | let tx = tx.into_sync(); 127 | tx.send_iter(["foo", "bar", "baz"]).unwrap(); 128 | drop(tx); 129 | 130 | block_on(async move { 131 | assert_eq!(vec!["foo", "bar", "baz"], rx.recv_batch(4).await); 132 | }); 133 | 134 | let (tx, rx) = batch_channel::unbounded(); 135 | let tx = tx.into_sync(); 136 | drop(rx); 137 | assert_eq!( 138 | Err(batch_channel::SendError(())), 139 | tx.send_iter(["foo", "bar", "baz"]) 140 | ); 141 | } 142 | 143 | #[test] 144 | fn send_iter_vec() { 145 | let (tx, rx) = batch_channel::unbounded(); 146 | tx.into_sync().send_iter(vec!["foo", "bar", "baz"]).unwrap(); 147 | 148 | block_on(async move { 149 | assert_eq!(vec!["foo", "bar", "baz"], rx.recv_batch(4).await); 150 | }); 151 | } 152 | 153 | #[test] 154 | fn recv_batch_returning_all() { 155 | let (tx, rx) = batch_channel::unbounded(); 156 | 157 | tx.into_sync().send_iter([10, 20, 30]).unwrap(); 158 | block_on(async move { 159 | assert_eq!(vec![10, 20, 30], rx.recv_batch(100).await); 160 | }) 161 | } 162 | 163 | #[test] 164 | fn recv_batch_returning_some() { 165 | let (tx, rx) = batch_channel::unbounded(); 166 | 167 | tx.into_sync().send_iter([10, 20, 30]).unwrap(); 168 | block_on(async move { 169 | assert_eq!(vec![10, 20], rx.recv_batch(2).await); 170 | assert_eq!(vec![30], rx.recv_batch(2).await); 171 | }) 172 | } 173 | 174 | #[test] 175 | fn recv_vec_returning_some() { 176 | let (tx, rx) = batch_channel::unbounded(); 177 | 178 | tx.into_sync().send_iter([10, 20, 30]).unwrap(); 179 | block_on(async move { 180 | let mut vec = Vec::new(); 181 | () = rx.recv_vec(2, &mut vec).await; 182 | assert_eq!(vec![10, 20], vec); 183 | () = rx.recv_vec(2, &mut vec).await; 184 | assert_eq!(vec![30], vec); 185 | }); 186 | } 187 | 188 | #[test] 189 | fn recv_batch_returns_empty_when_no_tx() { 190 | let (tx, rx) = batch_channel::unbounded(); 191 | drop(tx); 192 | 193 | block_on(async move { 194 | assert_eq!(Vec::<()>::new(), rx.recv_batch(2).await); 195 | }) 196 | } 197 | 198 | #[test] 199 | fn batch_locally_accumulates() { 200 | let mut pool = LocalPool::new(); 201 | 202 | let (tx, rx) = batch_channel::unbounded(); 203 | let read_values: StateVar> = StateVar::default(); 204 | 205 | pool.spawn({ 206 | let read_values = read_values.clone(); 207 | async move { 208 | while let Some(v) = rx.recv().await { 209 | read_values.borrow_mut().push(v); 210 | } 211 | } 212 | }); 213 | 214 | tx.into_sync() 215 | .autobatch(2, |tx| { 216 | assert_eq!(Ok(()), tx.send(1)); 217 | pool.run_until_stalled(); 218 | assert_eq!(0, read_values.borrow().len()); 219 | 220 | assert_eq!(Ok(()), tx.send(2)); 221 | pool.run_until_stalled(); 222 | assert_eq!(vec![1, 2], read_values.get()); 223 | 224 | assert_eq!(Ok(()), tx.send(3)); 225 | pool.run_until_stalled(); 226 | assert_eq!(vec![1, 2], read_values.get()); 227 | Ok(()) 228 | }) 229 | .unwrap(); 230 | 231 | pool.run(); 232 | assert_eq!(vec![1, 2, 3], read_values.get()); 233 | } 234 | 235 | #[test] 236 | fn autobatch_locally_accumulates() { 237 | let mut pool = LocalPool::new(); 238 | 239 | let (tx, rx) = batch_channel::unbounded(); 240 | let read_values: StateVar> = StateVar::default(); 241 | 242 | pool.spawn({ 243 | let read_values = read_values.clone(); 244 | async move { 245 | while let Some(v) = rx.recv().await { 246 | read_values.borrow_mut().push(v); 247 | } 248 | } 249 | }); 250 | 251 | tx.into_sync() 252 | .autobatch(2, |tx| { 253 | assert_eq!(Ok(()), tx.send(1)); 254 | pool.run_until_stalled(); 255 | assert_eq!(0, read_values.borrow().len()); 256 | 257 | assert_eq!(Ok(()), tx.send(2)); 258 | pool.run_until_stalled(); 259 | assert_eq!(vec![1, 2], read_values.get()); 260 | 261 | assert_eq!(Ok(()), tx.send(3)); 262 | pool.run_until_stalled(); 263 | assert_eq!(vec![1, 2], read_values.get()); 264 | 265 | Ok(()) 266 | }) 267 | .expect("receiver never dropped"); 268 | 269 | // autobatch drained 270 | pool.run(); 271 | assert_eq!(vec![1, 2, 3], read_values.get()); 272 | } 273 | 274 | #[test] 275 | fn batch_can_be_drained() { 276 | let mut pool = LocalPool::new(); 277 | 278 | let (tx, rx) = batch_channel::unbounded(); 279 | let read_values: StateVar> = StateVar::default(); 280 | 281 | pool.spawn({ 282 | let read_values = read_values.clone(); 283 | async move { 284 | while let Some(v) = rx.recv().await { 285 | read_values.borrow_mut().push(v); 286 | } 287 | } 288 | }); 289 | 290 | tx.into_sync() 291 | .autobatch(3, |tx| { 292 | assert_eq!(Ok(()), tx.send(1)); 293 | pool.run_until_stalled(); 294 | assert_eq!(0, read_values.borrow().len()); 295 | 296 | assert_eq!(Ok(()), tx.send(2)); 297 | pool.run_until_stalled(); 298 | assert_eq!(0, read_values.borrow().len()); 299 | 300 | assert_eq!(Ok(()), tx.drain()); 301 | pool.run_until_stalled(); 302 | assert_eq!(vec![1, 2], read_values.get()); 303 | 304 | assert_eq!(Ok(()), tx.send(3)); 305 | pool.run_until_stalled(); 306 | assert_eq!(2, read_values.borrow().len()); 307 | 308 | Ok(()) 309 | }) 310 | .unwrap(); 311 | 312 | pool.run(); 313 | assert_eq!(vec![1, 2, 3], read_values.get()); 314 | } 315 | 316 | #[test] 317 | fn sender_and_receiver_of_noncloneable_can_clone() { 318 | struct NoClone; 319 | let (tx, rx) = batch_channel::unbounded::(); 320 | _ = tx.clone(); 321 | _ = rx.clone(); 322 | } 323 | 324 | #[test] 325 | fn blocking_recv_stress_condvar() { 326 | use std::sync::Arc; 327 | use std::sync::Barrier; 328 | use std::thread; 329 | 330 | const N: usize = 100; 331 | const I: usize = 20; 332 | let mut handles = Vec::with_capacity(N * 2); 333 | let barrier = Arc::new(Barrier::new(N * 2)); 334 | for _ in 0..N { 335 | let tx_barrier = Arc::clone(&barrier); 336 | let rx_barrier = Arc::clone(&barrier); 337 | let (tx, rx) = batch_channel::unbounded_sync(); 338 | handles.push(thread::spawn(move || { 339 | tx_barrier.wait(); 340 | for i in 0..I { 341 | tx.send(i).unwrap(); 342 | } 343 | })); 344 | handles.push(thread::spawn(move || { 345 | rx_barrier.wait(); 346 | for i in 0..I { 347 | assert_eq!(Some(i), rx.recv()); 348 | } 349 | assert_eq!(None, rx.recv()); 350 | })); 351 | } 352 | 353 | for handle in handles { 354 | handle.join().unwrap(); 355 | } 356 | } 357 | 358 | #[test] 359 | fn recv_batch_blocking() { 360 | let (tx, rx) = batch_channel::unbounded_sync(); 361 | tx.send(10).unwrap(); 362 | tx.send(20).unwrap(); 363 | assert_eq!(vec![10, 20], rx.recv_batch(4)); 364 | } 365 | --------------------------------------------------------------------------------