├── .github ├── FUNDING.yml └── workflows │ ├── build-and-test.yaml │ ├── lint.yaml │ ├── security.yaml │ └── semver.yaml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── config.rs ├── executor.rs ├── init.rs ├── lib.rs ├── reactor.rs ├── threading.rs └── tokio.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Keruspe 2 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yaml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 12 * * 1" 8 | 9 | jobs: 10 | build_and_test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-latest, windows-latest, macos-latest] 16 | rust: [nightly, beta, stable, 1.85.0] 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install latest ${{ matrix.rust }} 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: ${{ matrix.rust }} 24 | profile: minimal 25 | override: true 26 | 27 | - name: Run cargo check 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: check 31 | args: --all --bins --examples --tests --all-features 32 | 33 | - name: Run cargo check (without dev-dependencies to catch missing feature flags) 34 | if: startsWith(matrix.rust, 'nightly') 35 | uses: actions-rs/cargo@v1 36 | with: 37 | command: check 38 | args: -Z features=dev_dep 39 | 40 | - name: Run cargo test 41 | uses: actions-rs/cargo@v1 42 | with: 43 | command: test 44 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | clippy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: stable 16 | profile: minimal 17 | components: clippy 18 | - uses: actions-rs/clippy-check@v1 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | args: --all-features -- -W clippy::all 22 | 23 | rustfmt: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: stable 31 | profile: minimal 32 | components: rustfmt 33 | - uses: actions-rs/cargo@v1 34 | with: 35 | command: fmt 36 | args: --all -- --check 37 | -------------------------------------------------------------------------------- /.github/workflows/security.yaml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | security_audit: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - uses: rustsec/audit-check@v2 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/semver.yaml: -------------------------------------------------------------------------------- 1 | name: Semver compliance check 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | semver_check: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: obi1kenobi/cargo-semver-checks-action@v2 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | style_edition = "2024" 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 3.1.0 2 | 3 | - Fix tokio feature 4 | - Drop `once_cell` dependency 5 | - Use `async_executor::StaticExecutor` 6 | - Update MSRV to 1.80 7 | 8 | # Version 3.0.2 9 | 10 | - README update 11 | 12 | # Version 3.0.1 13 | 14 | - README update 15 | 16 | # Version 3.0.0 17 | 18 | - Drop support for tokio < 1.0 19 | 20 | # Version 2.4.1 21 | 22 | - Update to events-listener 4.0 23 | 24 | # Version 2.4.0 25 | 26 | - Update to async-io 2.0 27 | 28 | # Version 2.3.1 29 | 30 | - Update blocking dependency 31 | 32 | # Version 2.3.0 33 | 34 | - Switch back to edition 2021 and MSRV 1.59, dropping num-cups dependency 35 | 36 | # Version 2.2.0 37 | 38 | - Revert back to edition 2018 and MSRV 1.49 39 | 40 | # Version 2.1.0 41 | 42 | - Switch from async-mutex to async-lock 43 | - Switch from num-cpus to Use `std::thread::available_parallelism` 44 | - Update MSRV to 1.59 45 | - Update to edition 2021 46 | 47 | # Version 2.0.4 48 | 49 | - Return concrete type Task from `spawn_blocking` 50 | 51 | # Version 2.0.3 52 | 53 | - Documentation updates 54 | 55 | # Version 2.0.2 56 | 57 | - Documentation updates 58 | 59 | # Version 2.0.1 60 | 61 | - fix build without default features 62 | 63 | # Version 2.0.0 64 | 65 | - add tokio 1.0 integration 66 | - rework configuration 67 | - add a way to update the number of threads at runtime within configured bounds 68 | 69 | # Version 1.4.3 70 | 71 | - switch to multi threaded tokio schedulers when enabled 72 | 73 | # Version 1.4.2 74 | 75 | - Drop an Arc 76 | 77 | # Version 1.4.1 78 | 79 | - switch back to manual implementation for tokio02 integration 80 | 81 | # Version 1.4.0 82 | 83 | - add tokio03 integration 84 | 85 | # Version 1.3.0 86 | 87 | - use async-compat for tokio02 integration 88 | 89 | # Version 1.2.1 90 | 91 | - tokio02 fix 92 | 93 | # Version 1.2.0 94 | 95 | - Add tokio02 feature 96 | 97 | # Version 1.1.1 98 | 99 | - Update `async-executor`. 100 | 101 | # Version 1.1.0 102 | 103 | - Update async-executor 104 | 105 | # Version 1.0.2 106 | 107 | - Do not run global tasks in `block_on()` 108 | 109 | # Version 1.0.1 110 | 111 | - Update dependencies 112 | 113 | # Version 1.0.0 114 | 115 | - Update dependencies 116 | - Make async-io support optional 117 | 118 | # Version 0.2.3 119 | 120 | - Change license to MIT or Apache-2.0 121 | 122 | # Version 0.2.2 123 | 124 | - Reexport `async_executor::Task` 125 | 126 | # Version 0.2.1 127 | 128 | - Make sure we spawn at least one thread 129 | 130 | # Version 0.2.0 131 | 132 | - Rename `run` to `block_on` and drop `'static` requirement 133 | - Add `GlobalExecutorConfig::with_thread_name` 134 | 135 | # Version 0.1.4 136 | 137 | - Add init functions 138 | 139 | # Version 0.1.3 140 | 141 | - `run`: do not require `Future` to be `Send` 142 | 143 | # Version 0.1.2 144 | 145 | - Adjust dependencies 146 | 147 | # Versio 0.1.1 148 | 149 | - Fix the number of spawned threads 150 | 151 | # Version 0.1.0 152 | 153 | - Initial release 154 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 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 = "async-channel" 22 | version = "2.3.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" 25 | dependencies = [ 26 | "concurrent-queue", 27 | "event-listener-strategy", 28 | "futures-core", 29 | "pin-project-lite", 30 | ] 31 | 32 | [[package]] 33 | name = "async-executor" 34 | version = "1.13.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" 37 | dependencies = [ 38 | "async-task", 39 | "concurrent-queue", 40 | "fastrand", 41 | "futures-lite", 42 | "slab", 43 | ] 44 | 45 | [[package]] 46 | name = "async-global-executor" 47 | version = "3.1.0" 48 | dependencies = [ 49 | "async-channel", 50 | "async-executor", 51 | "async-io", 52 | "async-lock", 53 | "blocking", 54 | "doc-comment", 55 | "futures-lite", 56 | "tokio", 57 | ] 58 | 59 | [[package]] 60 | name = "async-io" 61 | version = "2.4.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" 64 | dependencies = [ 65 | "async-lock", 66 | "cfg-if", 67 | "concurrent-queue", 68 | "futures-io", 69 | "futures-lite", 70 | "parking", 71 | "polling", 72 | "rustix", 73 | "slab", 74 | "tracing", 75 | "windows-sys", 76 | ] 77 | 78 | [[package]] 79 | name = "async-lock" 80 | version = "3.4.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" 83 | dependencies = [ 84 | "event-listener", 85 | "event-listener-strategy", 86 | "pin-project-lite", 87 | ] 88 | 89 | [[package]] 90 | name = "async-task" 91 | version = "4.7.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 94 | 95 | [[package]] 96 | name = "atomic-waker" 97 | version = "1.1.2" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 100 | 101 | [[package]] 102 | name = "autocfg" 103 | version = "1.4.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 106 | 107 | [[package]] 108 | name = "backtrace" 109 | version = "0.3.74" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 112 | dependencies = [ 113 | "addr2line", 114 | "cfg-if", 115 | "libc", 116 | "miniz_oxide", 117 | "object", 118 | "rustc-demangle", 119 | "windows-targets", 120 | ] 121 | 122 | [[package]] 123 | name = "bitflags" 124 | version = "2.9.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 127 | 128 | [[package]] 129 | name = "blocking" 130 | version = "1.6.1" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" 133 | dependencies = [ 134 | "async-channel", 135 | "async-task", 136 | "futures-io", 137 | "futures-lite", 138 | "piper", 139 | ] 140 | 141 | [[package]] 142 | name = "cfg-if" 143 | version = "1.0.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 146 | 147 | [[package]] 148 | name = "concurrent-queue" 149 | version = "2.5.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 152 | dependencies = [ 153 | "crossbeam-utils", 154 | ] 155 | 156 | [[package]] 157 | name = "crossbeam-utils" 158 | version = "0.8.21" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 161 | 162 | [[package]] 163 | name = "doc-comment" 164 | version = "0.3.3" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 167 | 168 | [[package]] 169 | name = "errno" 170 | version = "0.3.10" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 173 | dependencies = [ 174 | "libc", 175 | "windows-sys", 176 | ] 177 | 178 | [[package]] 179 | name = "event-listener" 180 | version = "5.4.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 183 | dependencies = [ 184 | "concurrent-queue", 185 | "parking", 186 | "pin-project-lite", 187 | ] 188 | 189 | [[package]] 190 | name = "event-listener-strategy" 191 | version = "0.5.3" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" 194 | dependencies = [ 195 | "event-listener", 196 | "pin-project-lite", 197 | ] 198 | 199 | [[package]] 200 | name = "fastrand" 201 | version = "2.3.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 204 | 205 | [[package]] 206 | name = "futures-core" 207 | version = "0.3.31" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 210 | 211 | [[package]] 212 | name = "futures-io" 213 | version = "0.3.31" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 216 | 217 | [[package]] 218 | name = "futures-lite" 219 | version = "2.6.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" 222 | dependencies = [ 223 | "fastrand", 224 | "futures-core", 225 | "futures-io", 226 | "parking", 227 | "pin-project-lite", 228 | ] 229 | 230 | [[package]] 231 | name = "gimli" 232 | version = "0.31.1" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 235 | 236 | [[package]] 237 | name = "hermit-abi" 238 | version = "0.4.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 241 | 242 | [[package]] 243 | name = "libc" 244 | version = "0.2.171" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 247 | 248 | [[package]] 249 | name = "linux-raw-sys" 250 | version = "0.4.15" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 253 | 254 | [[package]] 255 | name = "memchr" 256 | version = "2.7.4" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 259 | 260 | [[package]] 261 | name = "miniz_oxide" 262 | version = "0.8.5" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" 265 | dependencies = [ 266 | "adler2", 267 | ] 268 | 269 | [[package]] 270 | name = "object" 271 | version = "0.36.7" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 274 | dependencies = [ 275 | "memchr", 276 | ] 277 | 278 | [[package]] 279 | name = "parking" 280 | version = "2.2.1" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 283 | 284 | [[package]] 285 | name = "pin-project-lite" 286 | version = "0.2.16" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 289 | 290 | [[package]] 291 | name = "piper" 292 | version = "0.2.4" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 295 | dependencies = [ 296 | "atomic-waker", 297 | "fastrand", 298 | "futures-io", 299 | ] 300 | 301 | [[package]] 302 | name = "polling" 303 | version = "3.7.4" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" 306 | dependencies = [ 307 | "cfg-if", 308 | "concurrent-queue", 309 | "hermit-abi", 310 | "pin-project-lite", 311 | "rustix", 312 | "tracing", 313 | "windows-sys", 314 | ] 315 | 316 | [[package]] 317 | name = "rustc-demangle" 318 | version = "0.1.24" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 321 | 322 | [[package]] 323 | name = "rustix" 324 | version = "0.38.44" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 327 | dependencies = [ 328 | "bitflags", 329 | "errno", 330 | "libc", 331 | "linux-raw-sys", 332 | "windows-sys", 333 | ] 334 | 335 | [[package]] 336 | name = "slab" 337 | version = "0.4.9" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 340 | dependencies = [ 341 | "autocfg", 342 | ] 343 | 344 | [[package]] 345 | name = "tokio" 346 | version = "1.44.1" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" 349 | dependencies = [ 350 | "backtrace", 351 | "pin-project-lite", 352 | ] 353 | 354 | [[package]] 355 | name = "tracing" 356 | version = "0.1.41" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 359 | dependencies = [ 360 | "pin-project-lite", 361 | "tracing-core", 362 | ] 363 | 364 | [[package]] 365 | name = "tracing-core" 366 | version = "0.1.33" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 369 | 370 | [[package]] 371 | name = "windows-sys" 372 | version = "0.59.0" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 375 | dependencies = [ 376 | "windows-targets", 377 | ] 378 | 379 | [[package]] 380 | name = "windows-targets" 381 | version = "0.52.6" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 384 | dependencies = [ 385 | "windows_aarch64_gnullvm", 386 | "windows_aarch64_msvc", 387 | "windows_i686_gnu", 388 | "windows_i686_gnullvm", 389 | "windows_i686_msvc", 390 | "windows_x86_64_gnu", 391 | "windows_x86_64_gnullvm", 392 | "windows_x86_64_msvc", 393 | ] 394 | 395 | [[package]] 396 | name = "windows_aarch64_gnullvm" 397 | version = "0.52.6" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 400 | 401 | [[package]] 402 | name = "windows_aarch64_msvc" 403 | version = "0.52.6" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 406 | 407 | [[package]] 408 | name = "windows_i686_gnu" 409 | version = "0.52.6" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 412 | 413 | [[package]] 414 | name = "windows_i686_gnullvm" 415 | version = "0.52.6" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 418 | 419 | [[package]] 420 | name = "windows_i686_msvc" 421 | version = "0.52.6" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 424 | 425 | [[package]] 426 | name = "windows_x86_64_gnu" 427 | version = "0.52.6" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 430 | 431 | [[package]] 432 | name = "windows_x86_64_gnullvm" 433 | version = "0.52.6" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 436 | 437 | [[package]] 438 | name = "windows_x86_64_msvc" 439 | version = "0.52.6" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 442 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-global-executor" 3 | version = "3.1.0" 4 | authors = ["Marc-Antoine Perennou "] 5 | description = "A global executor built on top of async-executor and async-io" 6 | edition = "2024" 7 | license = "Apache-2.0 OR MIT" 8 | repository = "https://github.com/Keruspe/async-global-executor" 9 | homepage = "https://github.com/Keruspe/async-global-executor" 10 | documentation = "https://docs.rs/async-global-executor" 11 | keywords = ["async", "await", "future", "executor"] 12 | categories = ["asynchronous", "concurrency"] 13 | readme = "README.md" 14 | rust-version = "1.85" 15 | 16 | [features] 17 | default = ["async-io"] 18 | async-io = ["dep:async-io"] 19 | tokio = ["dep:tokio"] 20 | 21 | [dependencies] 22 | async-channel = "^2.1.1" 23 | async-lock = "^3.2" 24 | blocking = "^1.5" 25 | futures-lite = "^2.0" 26 | 27 | [dependencies.async-executor] 28 | version = "^1.12" 29 | features = ["static"] 30 | 31 | [dependencies.async-io] 32 | version = "^2.2.1" 33 | optional = true 34 | 35 | [dependencies.tokio] 36 | version = "^1.0" 37 | optional = true 38 | default-features = false 39 | features = ["rt", "rt-multi-thread"] 40 | 41 | [dev-dependencies] 42 | doc-comment = "^0.3" 43 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-global-executor 2 | 3 | [![API Docs](https://docs.rs/async-global-executor/badge.svg)](https://docs.rs/async-global-executor) 4 | [![Build status](https://github.com/Keruspe/async-global-executor/workflows/Build%20and%20test/badge.svg)](https://github.com/Keruspe/async-global-executor/actions) 5 | [![Downloads](https://img.shields.io/crates/d/async-global-executor.svg)](https://crates.io/crates/async-global-executor) 6 | 7 | A global executor built on top of async-executor and async-io 8 | 9 | # Features 10 | 11 | * `async-io`: if enabled, `async-global-executor` will use `async_io::block_on` instead of 12 | `futures_lite::future::block_on` internally. this is preferred if your application also uses `async-io`. 13 | * `tokio`: if enabled, `async-global-executor` will ensure that all tasks that you will spawn run in the context of a 14 | tokio 1.0 runtime, spawning a new one if required. 15 | 16 | # Examples 17 | 18 | ``` 19 | # use futures_lite::future; 20 | 21 | // spawn a task on the multi-threaded executor 22 | let task1 = async_global_executor::spawn(async { 23 | 1 + 2 24 | }); 25 | // spawn a task on the local executor (same thread) 26 | let task2 = async_global_executor::spawn_local(async { 27 | 3 + 4 28 | }); 29 | let task = future::zip(task1, task2); 30 | 31 | // run the executor 32 | async_global_executor::block_on(async { 33 | assert_eq!(task.await, (3, 7)); 34 | }); 35 | ``` 36 | 37 | ## License 38 | 39 | Licensed under either of 40 | 41 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 42 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 43 | 44 | at your option. 45 | 46 | #### Contribution 47 | 48 | Unless you explicitly state otherwise, any contribution intentionally submitted 49 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 50 | dual licensed as above, without any additional terms or conditions. 51 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | sync::{ 4 | OnceLock, 5 | atomic::{AtomicUsize, Ordering}, 6 | }, 7 | }; 8 | 9 | pub(crate) static GLOBAL_EXECUTOR_CONFIG: OnceLock = OnceLock::new(); 10 | 11 | /// Configuration to init the thread pool for the multi-threaded global executor. 12 | #[derive(Default)] 13 | pub struct GlobalExecutorConfig { 14 | /// The environment variable from which we'll try to parse the number of threads to spawn. 15 | env_var: Option<&'static str>, 16 | /// The minimum number of threads to spawn. 17 | min_threads: Option, 18 | /// The maximum number of threads to spawn. 19 | max_threads: Option, 20 | /// The closure function used to get the name of the thread. The name can be used for identification in panic messages. 21 | thread_name_fn: Option String + Send + Sync>>, 22 | } 23 | 24 | impl fmt::Debug for GlobalExecutorConfig { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | f.debug_struct("GlobalExecutorConfig") 27 | .field("env_var", &self.env_var) 28 | .field("min_threads", &self.min_threads) 29 | .field("max_threads", &self.max_threads) 30 | .finish() 31 | } 32 | } 33 | 34 | impl GlobalExecutorConfig { 35 | /// Use the specified environment variable to find the number of threads to spawn. 36 | pub fn with_env_var(mut self, env_var: &'static str) -> Self { 37 | self.env_var = Some(env_var); 38 | self 39 | } 40 | 41 | /// Use the specified value as the minimum number of threads. 42 | pub fn with_min_threads(mut self, min_threads: usize) -> Self { 43 | self.min_threads = Some(min_threads); 44 | self 45 | } 46 | 47 | /// Use the specified value as the maximum number of threads for async tasks. 48 | /// To limit the maximum number of threads for blocking tasks, please use the 49 | /// `BLOCKING_MAX_THREADS` environment variable. 50 | pub fn with_max_threads(mut self, max_threads: usize) -> Self { 51 | self.max_threads = Some(max_threads); 52 | self 53 | } 54 | 55 | /// Use the specified prefix to name the threads. 56 | pub fn with_thread_name_fn( 57 | mut self, 58 | thread_name_fn: impl Fn() -> String + Send + Sync + 'static, 59 | ) -> Self { 60 | self.thread_name_fn = Some(Box::new(thread_name_fn)); 61 | self 62 | } 63 | 64 | pub(crate) fn seal(self) -> Config { 65 | let min_threads = std::env::var(self.env_var.unwrap_or("ASYNC_GLOBAL_EXECUTOR_THREADS")) 66 | .ok() 67 | .and_then(|threads| threads.parse().ok()) 68 | .or(self.min_threads) 69 | .unwrap_or_else(|| std::thread::available_parallelism().map_or(1, usize::from)) 70 | .max(1); 71 | let max_threads = self.max_threads.unwrap_or(min_threads * 4).max(min_threads); 72 | Config { 73 | min_threads, 74 | max_threads, 75 | thread_name_fn: self.thread_name_fn.unwrap_or_else(|| { 76 | Box::new(|| { 77 | static GLOBAL_EXECUTOR_NEXT_THREAD: AtomicUsize = AtomicUsize::new(1); 78 | format!( 79 | "async-global-executor-{}", 80 | GLOBAL_EXECUTOR_NEXT_THREAD.fetch_add(1, Ordering::SeqCst) 81 | ) 82 | }) 83 | }), 84 | } 85 | } 86 | } 87 | 88 | // The actual configuration, computed from the given GlobalExecutorConfig 89 | pub(crate) struct Config { 90 | pub(crate) min_threads: usize, 91 | pub(crate) max_threads: usize, 92 | pub(crate) thread_name_fn: Box String + Send + Sync>, 93 | } 94 | 95 | impl Default for Config { 96 | fn default() -> Self { 97 | GlobalExecutorConfig::default().seal() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/executor.rs: -------------------------------------------------------------------------------- 1 | use crate::Task; 2 | use async_executor::{LocalExecutor, StaticExecutor}; 3 | use std::future::Future; 4 | 5 | pub(crate) static GLOBAL_EXECUTOR: StaticExecutor = StaticExecutor::new(); 6 | 7 | thread_local! { 8 | pub(crate) static LOCAL_EXECUTOR: LocalExecutor<'static> = const { LocalExecutor::new() }; 9 | } 10 | 11 | /// Runs the global and the local executor on the current thread 12 | /// 13 | /// Note: this calls `async_io::block_on` underneath. 14 | /// 15 | /// # Examples 16 | /// 17 | /// ``` 18 | /// let task = async_global_executor::spawn(async { 19 | /// 1 + 2 20 | /// }); 21 | /// async_global_executor::block_on(async { 22 | /// assert_eq!(task.await, 3); 23 | /// }); 24 | /// ``` 25 | pub fn block_on, T>(future: F) -> T { 26 | LOCAL_EXECUTOR.with(|executor| crate::reactor::block_on(executor.run(future))) 27 | } 28 | 29 | /// Spawns a task onto the multi-threaded global executor. 30 | /// 31 | /// # Examples 32 | /// 33 | /// ``` 34 | /// # use futures_lite::future; 35 | /// 36 | /// let task1 = async_global_executor::spawn(async { 37 | /// 1 + 2 38 | /// }); 39 | /// let task2 = async_global_executor::spawn(async { 40 | /// 3 + 4 41 | /// }); 42 | /// let task = future::zip(task1, task2); 43 | /// 44 | /// async_global_executor::block_on(async { 45 | /// assert_eq!(task.await, (3, 7)); 46 | /// }); 47 | /// ``` 48 | pub fn spawn + Send + 'static, T: Send + 'static>(future: F) -> Task { 49 | crate::init(); 50 | GLOBAL_EXECUTOR.spawn(future) 51 | } 52 | 53 | /// Spawns a task onto the local executor. 54 | /// 55 | /// 56 | /// The task does not need to be `Send` as it will be spawned on the same thread. 57 | /// 58 | /// # Examples 59 | /// 60 | /// ``` 61 | /// # use futures_lite::future; 62 | /// 63 | /// let task1 = async_global_executor::spawn_local(async { 64 | /// 1 + 2 65 | /// }); 66 | /// let task2 = async_global_executor::spawn_local(async { 67 | /// 3 + 4 68 | /// }); 69 | /// let task = future::zip(task1, task2); 70 | /// 71 | /// async_global_executor::block_on(async { 72 | /// assert_eq!(task.await, (3, 7)); 73 | /// }); 74 | /// ``` 75 | pub fn spawn_local + 'static, T: 'static>(future: F) -> Task { 76 | LOCAL_EXECUTOR.with(|executor| executor.spawn(future)) 77 | } 78 | 79 | /// Runs blocking code on a thread pool. 80 | /// 81 | /// # Examples 82 | /// 83 | /// Read the contents of a file: 84 | /// 85 | /// ```no_run 86 | /// # async_global_executor::block_on(async { 87 | /// let contents = async_global_executor::spawn_blocking(|| std::fs::read_to_string("file.txt")).await?; 88 | /// # std::io::Result::Ok(()) }); 89 | /// ``` 90 | /// 91 | /// Spawn a process: 92 | /// 93 | /// ```no_run 94 | /// use std::process::Command; 95 | /// 96 | /// # async_global_executor::block_on(async { 97 | /// let out = async_global_executor::spawn_blocking(|| Command::new("dir").output()).await?; 98 | /// # std::io::Result::Ok(()) }); 99 | /// ``` 100 | pub fn spawn_blocking T + Send + 'static, T: Send + 'static>(f: F) -> Task { 101 | blocking::unblock(f) 102 | } 103 | -------------------------------------------------------------------------------- /src/init.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | 3 | /// Init the global executor, spawning as many threads as specified or 4 | /// the value specified by the specified environment variable. 5 | /// 6 | /// # Examples 7 | /// 8 | /// ``` 9 | /// async_global_executor::init_with_config( 10 | /// async_global_executor::GlobalExecutorConfig::default() 11 | /// .with_env_var("NUMBER_OF_THREADS") 12 | /// .with_min_threads(4) 13 | /// .with_max_threads(6) 14 | /// .with_thread_name_fn(Box::new(|| "worker".to_string())) 15 | /// ); 16 | /// ``` 17 | pub fn init_with_config(config: crate::config::GlobalExecutorConfig) { 18 | let _ = crate::config::GLOBAL_EXECUTOR_CONFIG.set(config.seal()); 19 | init(); 20 | } 21 | 22 | /// Init the global executor, spawning as many threads as the number or cpus or 23 | /// the value specified by the `ASYNC_GLOBAL_EXECUTOR_THREADS` environment variable 24 | /// if specified. 25 | /// 26 | /// # Examples 27 | /// 28 | /// ``` 29 | /// async_global_executor::init(); 30 | /// ``` 31 | pub fn init() { 32 | static INIT_DONE: AtomicBool = AtomicBool::new(false); 33 | if !INIT_DONE.swap(true, Ordering::SeqCst) { 34 | let config = 35 | crate::config::GLOBAL_EXECUTOR_CONFIG.get_or_init(crate::config::Config::default); 36 | crate::reactor::block_on(async { 37 | crate::threading::spawn_more_threads(config.min_threads) 38 | .await 39 | .expect("cannot spawn executor threads"); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A global executor built on top of async-executor and async_io 2 | //! 3 | //! The global executor is lazily spawned on first use. It spawns as many threads 4 | //! as the number of cpus by default. You can override this using the 5 | //! `ASYNC_GLOBAL_EXECUTOR_THREADS` environment variable. 6 | //! 7 | //! # Examples 8 | //! 9 | //! ``` 10 | //! # use futures_lite::future; 11 | //! 12 | //! // spawn a task on the multi-threaded executor 13 | //! let task1 = async_global_executor::spawn(async { 14 | //! 1 + 2 15 | //! }); 16 | //! // spawn a task on the local executor (same thread) 17 | //! let task2 = async_global_executor::spawn_local(async { 18 | //! 3 + 4 19 | //! }); 20 | //! let task = future::zip(task1, task2); 21 | //! 22 | //! // run the executor 23 | //! async_global_executor::block_on(async { 24 | //! assert_eq!(task.await, (3, 7)); 25 | //! }); 26 | //! ``` 27 | 28 | #![forbid(unsafe_code)] 29 | #![warn(missing_docs, missing_debug_implementations)] 30 | 31 | #[cfg(doctest)] 32 | doc_comment::doctest!("../README.md"); 33 | 34 | pub use async_executor::Task; 35 | pub use config::GlobalExecutorConfig; 36 | pub use executor::{block_on, spawn, spawn_blocking, spawn_local}; 37 | pub use init::{init, init_with_config}; 38 | pub use threading::{spawn_more_threads, stop_current_thread, stop_thread}; 39 | 40 | mod config; 41 | mod executor; 42 | mod init; 43 | mod reactor; 44 | mod threading; 45 | 46 | #[cfg(feature = "tokio")] 47 | mod tokio; 48 | -------------------------------------------------------------------------------- /src/reactor.rs: -------------------------------------------------------------------------------- 1 | pub(crate) fn block_on, T>(future: F) -> T { 2 | #[cfg(feature = "async-io")] 3 | let run = || async_io::block_on(future); 4 | #[cfg(not(feature = "async-io"))] 5 | let run = || futures_lite::future::block_on(future); 6 | #[cfg(feature = "tokio")] 7 | let _tokio_enter = crate::tokio::enter(); 8 | run() 9 | } 10 | -------------------------------------------------------------------------------- /src/threading.rs: -------------------------------------------------------------------------------- 1 | use crate::Task; 2 | use async_channel::{Receiver, Sender}; 3 | use async_lock::Mutex; 4 | use futures_lite::future; 5 | use std::{cell::OnceCell, io, thread}; 6 | 7 | // The current number of threads (some might be shutting down and not in the pool anymore) 8 | static GLOBAL_EXECUTOR_THREADS_NUMBER: Mutex = Mutex::new(0); 9 | // The expected number of threads (excluding the one that are shutting down) 10 | static GLOBAL_EXECUTOR_EXPECTED_THREADS_NUMBER: Mutex = Mutex::new(0); 11 | 12 | thread_local! { 13 | // Used to shutdown a thread when we receive a message from the Sender. 14 | // We send an ack using to the Receiver once we're finished shutting down. 15 | static THREAD_SHUTDOWN: OnceCell<(Sender<()>, Receiver<()>)> = const { OnceCell::new() }; 16 | } 17 | 18 | /// Spawn more executor threads, up to configured max value. 19 | /// 20 | /// Returns how many threads we spawned. 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// async_global_executor::spawn_more_threads(2); 26 | /// ``` 27 | pub async fn spawn_more_threads(count: usize) -> io::Result { 28 | // Get the current configuration, or initialize the thread pool. 29 | let config = crate::config::GLOBAL_EXECUTOR_CONFIG 30 | .get() 31 | .unwrap_or_else(|| { 32 | crate::init(); 33 | crate::config::GLOBAL_EXECUTOR_CONFIG.get().unwrap() 34 | }); 35 | // How many threads do we have (including shutting down) 36 | let mut threads_number = GLOBAL_EXECUTOR_THREADS_NUMBER.lock().await; 37 | // How many threads are we supposed to have (when all shutdowns are complete) 38 | let mut expected_threads_number = GLOBAL_EXECUTOR_EXPECTED_THREADS_NUMBER.lock().await; 39 | // Ensure we don't exceed configured max threads (including shutting down) 40 | let count = count.min(config.max_threads - *threads_number); 41 | for _ in 0..count { 42 | thread::Builder::new() 43 | .name((config.thread_name_fn)()) 44 | .spawn(thread_main_loop)?; 45 | *threads_number += 1; 46 | *expected_threads_number += 1; 47 | } 48 | Ok(count) 49 | } 50 | 51 | /// Stop one of the executor threads, down to configured min value 52 | /// 53 | /// Returns whether a thread has been stopped. 54 | /// 55 | /// # Examples 56 | /// 57 | /// ``` 58 | /// async_global_executor::stop_thread(); 59 | /// ``` 60 | pub fn stop_thread() -> Task { 61 | crate::spawn(stop_current_executor_thread()) 62 | } 63 | 64 | /// Stop the current executor thread, if we exceed the configured min value 65 | /// 66 | /// Returns whether the thread has been stopped. 67 | /// 68 | /// # Examples 69 | /// 70 | /// ``` 71 | /// async_global_executor::stop_current_thread(); 72 | /// ``` 73 | pub fn stop_current_thread() -> Task { 74 | crate::spawn_local(stop_current_executor_thread()) 75 | } 76 | 77 | fn thread_main_loop() { 78 | // This will be used to ask for shutdown. 79 | let (s, r) = async_channel::bounded(1); 80 | // This wil be used to ack once shutdown is complete. 81 | let (s_ack, r_ack) = async_channel::bounded(1); 82 | THREAD_SHUTDOWN.with(|thread_shutdown| drop(thread_shutdown.set((s, r_ack)))); 83 | 84 | // Main loop 85 | loop { 86 | #[allow(clippy::blocks_in_conditions)] 87 | if std::panic::catch_unwind(|| { 88 | crate::executor::LOCAL_EXECUTOR.with(|executor| { 89 | let local = executor.run(async { 90 | // Wait until we're asked to shutdown. 91 | let _ = r.recv().await; 92 | }); 93 | let global = crate::executor::GLOBAL_EXECUTOR.run(future::pending::<()>()); 94 | crate::reactor::block_on(future::or(local, global)); 95 | }); 96 | }) 97 | .is_ok() 98 | { 99 | break; 100 | } 101 | } 102 | 103 | wait_for_local_executor_completion(); 104 | 105 | // Ack that we're done shutting down. 106 | crate::reactor::block_on(async { 107 | let _ = s_ack.send(()).await; 108 | }); 109 | } 110 | 111 | fn wait_for_local_executor_completion() { 112 | loop { 113 | #[allow(clippy::blocks_in_conditions)] 114 | if std::panic::catch_unwind(|| { 115 | crate::executor::LOCAL_EXECUTOR.with(|executor| { 116 | crate::reactor::block_on(async { 117 | // Wait for spawned tasks completion 118 | while !executor.is_empty() { 119 | executor.tick().await; 120 | } 121 | }); 122 | }); 123 | }) 124 | .is_ok() 125 | { 126 | break; 127 | } 128 | } 129 | } 130 | 131 | async fn stop_current_executor_thread() -> bool { 132 | // How many threads are we supposed to have (when all shutdowns are complete) 133 | let mut expected_threads_number = GLOBAL_EXECUTOR_EXPECTED_THREADS_NUMBER.lock().await; 134 | // Ensure we don't go below the configured min_threads (ignoring shutting down) 135 | if *expected_threads_number 136 | > crate::config::GLOBAL_EXECUTOR_CONFIG 137 | .get() 138 | .unwrap() 139 | .min_threads 140 | { 141 | let (s, r_ack) = 142 | THREAD_SHUTDOWN.with(|thread_shutdown| thread_shutdown.get().unwrap().clone()); 143 | let _ = s.send(()).await; 144 | // We now expect to have one less thread (this one is shutting down) 145 | *expected_threads_number -= 1; 146 | // Unlock the Mutex 147 | drop(expected_threads_number); 148 | let _ = r_ack.recv().await; 149 | // This thread is done shutting down 150 | *GLOBAL_EXECUTOR_THREADS_NUMBER.lock().await -= 1; 151 | true 152 | } else { 153 | false 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/tokio.rs: -------------------------------------------------------------------------------- 1 | use std::sync::LazyLock; 2 | 3 | pub(crate) fn enter() -> tokio::runtime::EnterGuard<'static> { 4 | RUNTIME.enter() 5 | } 6 | 7 | static RUNTIME: LazyLock = LazyLock::new(|| { 8 | tokio::runtime::Handle::try_current().unwrap_or_else(|_| { 9 | let rt = tokio::runtime::Runtime::new().expect("failed to build tokio runtime"); 10 | let handle = rt.handle().clone(); 11 | std::thread::Builder::new() 12 | .name("async-global-executor/tokio".to_string()) 13 | .spawn(move || { 14 | rt.block_on(futures_lite::future::pending::<()>()); 15 | }) 16 | .expect("failed to spawn tokio driver thread"); 17 | handle 18 | }) 19 | }); 20 | 21 | #[cfg(test)] 22 | mod test { 23 | async fn compute() -> u8 { 24 | tokio::spawn(async { 1 + 2 }).await.unwrap() 25 | } 26 | 27 | #[test] 28 | fn spawn_tokio() { 29 | crate::block_on(async { 30 | assert_eq!( 31 | crate::spawn(compute()).await 32 | + crate::spawn_local(compute()).await 33 | + tokio::spawn(compute()).await.unwrap(), 34 | 9 35 | ); 36 | }); 37 | } 38 | } 39 | --------------------------------------------------------------------------------