Compare changes

Choose any two refs to compare.

Changed files
+2153 -714
futures
futures-combinators
futures-compat
futures-core
futures-derive
src
futures-util
lifetime-guard
+2
.gitignore
··· 13 13 !/futures/** 14 14 !/futures-*/ 15 15 !/futures-*/** 16 + !/lifetime-guard/ 17 + !/lifetime-guard/**
+419 -5
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "aho-corasick" 7 + version = "1.1.3" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 + dependencies = [ 11 + "memchr", 12 + ] 13 + 14 + [[package]] 15 + name = "cc" 16 + version = "1.2.33" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" 19 + dependencies = [ 20 + "shlex", 21 + ] 22 + 23 + [[package]] 24 + name = "cfg-if" 25 + version = "1.0.3" 26 + source = "registry+https://github.com/rust-lang/crates.io-index" 27 + checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 28 + 29 + [[package]] 30 + name = "critical-section" 31 + version = "1.2.0" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 34 + 35 + [[package]] 36 + name = "futures" 37 + version = "0.0.2" 38 + dependencies = [ 39 + "futures-combinators", 40 + "futures-compat", 41 + "futures-core", 42 + "futures-derive", 43 + ] 44 + 45 + [[package]] 6 46 name = "futures-combinators" 7 - version = "0.1.0" 47 + version = "0.0.2" 8 48 dependencies = [ 49 + "futures-compat", 9 50 "futures-core", 10 51 "futures-util", 52 + "lifetime-guard", 11 53 ] 12 54 13 55 [[package]] 14 56 name = "futures-compat" 15 - version = "0.1.0" 57 + version = "0.0.2" 16 58 dependencies = [ 17 59 "futures-core", 60 + "lifetime-guard", 18 61 ] 19 62 20 63 [[package]] 21 64 name = "futures-core" 22 - version = "0.1.0" 65 + version = "0.0.2" 23 66 24 67 [[package]] 25 68 name = "futures-derive" 26 - version = "0.1.0" 69 + version = "0.0.2" 27 70 dependencies = [ 28 71 "proc-macro2", 29 72 "quote", ··· 32 75 33 76 [[package]] 34 77 name = "futures-util" 35 - version = "0.1.0" 78 + version = "0.0.2" 36 79 dependencies = [ 37 80 "futures-core", 81 + "lifetime-guard", 38 82 ] 39 83 40 84 [[package]] 85 + name = "generator" 86 + version = "0.8.5" 87 + source = "registry+https://github.com/rust-lang/crates.io-index" 88 + checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" 89 + dependencies = [ 90 + "cc", 91 + "cfg-if", 92 + "libc", 93 + "log", 94 + "rustversion", 95 + "windows", 96 + ] 97 + 98 + [[package]] 99 + name = "lazy_static" 100 + version = "1.5.0" 101 + source = "registry+https://github.com/rust-lang/crates.io-index" 102 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 103 + 104 + [[package]] 105 + name = "libc" 106 + version = "0.2.175" 107 + source = "registry+https://github.com/rust-lang/crates.io-index" 108 + checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 109 + 110 + [[package]] 111 + name = "lifetime-guard" 112 + version = "0.0.2" 113 + dependencies = [ 114 + "critical-section", 115 + "loom", 116 + ] 117 + 118 + [[package]] 119 + name = "log" 120 + version = "0.4.27" 121 + source = "registry+https://github.com/rust-lang/crates.io-index" 122 + checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 123 + 124 + [[package]] 125 + name = "loom" 126 + version = "0.7.2" 127 + source = "registry+https://github.com/rust-lang/crates.io-index" 128 + checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 129 + dependencies = [ 130 + "cfg-if", 131 + "generator", 132 + "scoped-tls", 133 + "tracing", 134 + "tracing-subscriber", 135 + ] 136 + 137 + [[package]] 138 + name = "matchers" 139 + version = "0.1.0" 140 + source = "registry+https://github.com/rust-lang/crates.io-index" 141 + checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 142 + dependencies = [ 143 + "regex-automata 0.1.10", 144 + ] 145 + 146 + [[package]] 147 + name = "memchr" 148 + version = "2.7.5" 149 + source = "registry+https://github.com/rust-lang/crates.io-index" 150 + checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 151 + 152 + [[package]] 153 + name = "nu-ansi-term" 154 + version = "0.46.0" 155 + source = "registry+https://github.com/rust-lang/crates.io-index" 156 + checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 157 + dependencies = [ 158 + "overload", 159 + "winapi", 160 + ] 161 + 162 + [[package]] 163 + name = "once_cell" 164 + version = "1.21.3" 165 + source = "registry+https://github.com/rust-lang/crates.io-index" 166 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 167 + 168 + [[package]] 169 + name = "overload" 170 + version = "0.1.1" 171 + source = "registry+https://github.com/rust-lang/crates.io-index" 172 + checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 173 + 174 + [[package]] 175 + name = "pin-project-lite" 176 + version = "0.2.16" 177 + source = "registry+https://github.com/rust-lang/crates.io-index" 178 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 179 + 180 + [[package]] 41 181 name = "proc-macro2" 42 182 version = "1.0.95" 43 183 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 56 196 ] 57 197 58 198 [[package]] 199 + name = "regex" 200 + version = "1.11.1" 201 + source = "registry+https://github.com/rust-lang/crates.io-index" 202 + checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 203 + dependencies = [ 204 + "aho-corasick", 205 + "memchr", 206 + "regex-automata 0.4.9", 207 + "regex-syntax 0.8.5", 208 + ] 209 + 210 + [[package]] 211 + name = "regex-automata" 212 + version = "0.1.10" 213 + source = "registry+https://github.com/rust-lang/crates.io-index" 214 + checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 215 + dependencies = [ 216 + "regex-syntax 0.6.29", 217 + ] 218 + 219 + [[package]] 220 + name = "regex-automata" 221 + version = "0.4.9" 222 + source = "registry+https://github.com/rust-lang/crates.io-index" 223 + checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 224 + dependencies = [ 225 + "aho-corasick", 226 + "memchr", 227 + "regex-syntax 0.8.5", 228 + ] 229 + 230 + [[package]] 231 + name = "regex-syntax" 232 + version = "0.6.29" 233 + source = "registry+https://github.com/rust-lang/crates.io-index" 234 + checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 235 + 236 + [[package]] 237 + name = "regex-syntax" 238 + version = "0.8.5" 239 + source = "registry+https://github.com/rust-lang/crates.io-index" 240 + checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 241 + 242 + [[package]] 243 + name = "rustversion" 244 + version = "1.0.22" 245 + source = "registry+https://github.com/rust-lang/crates.io-index" 246 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 247 + 248 + [[package]] 249 + name = "scoped-tls" 250 + version = "1.0.1" 251 + source = "registry+https://github.com/rust-lang/crates.io-index" 252 + checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 253 + 254 + [[package]] 255 + name = "sharded-slab" 256 + version = "0.1.7" 257 + source = "registry+https://github.com/rust-lang/crates.io-index" 258 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 259 + dependencies = [ 260 + "lazy_static", 261 + ] 262 + 263 + [[package]] 264 + name = "shlex" 265 + version = "1.3.0" 266 + source = "registry+https://github.com/rust-lang/crates.io-index" 267 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 268 + 269 + [[package]] 270 + name = "smallvec" 271 + version = "1.15.1" 272 + source = "registry+https://github.com/rust-lang/crates.io-index" 273 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 274 + 275 + [[package]] 59 276 name = "syn" 60 277 version = "2.0.104" 61 278 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 67 284 ] 68 285 69 286 [[package]] 287 + name = "thread_local" 288 + version = "1.1.9" 289 + source = "registry+https://github.com/rust-lang/crates.io-index" 290 + checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 291 + dependencies = [ 292 + "cfg-if", 293 + ] 294 + 295 + [[package]] 296 + name = "tracing" 297 + version = "0.1.41" 298 + source = "registry+https://github.com/rust-lang/crates.io-index" 299 + checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 300 + dependencies = [ 301 + "pin-project-lite", 302 + "tracing-core", 303 + ] 304 + 305 + [[package]] 306 + name = "tracing-core" 307 + version = "0.1.34" 308 + source = "registry+https://github.com/rust-lang/crates.io-index" 309 + checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 310 + dependencies = [ 311 + "once_cell", 312 + "valuable", 313 + ] 314 + 315 + [[package]] 316 + name = "tracing-log" 317 + version = "0.2.0" 318 + source = "registry+https://github.com/rust-lang/crates.io-index" 319 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 320 + dependencies = [ 321 + "log", 322 + "once_cell", 323 + "tracing-core", 324 + ] 325 + 326 + [[package]] 327 + name = "tracing-subscriber" 328 + version = "0.3.19" 329 + source = "registry+https://github.com/rust-lang/crates.io-index" 330 + checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 331 + dependencies = [ 332 + "matchers", 333 + "nu-ansi-term", 334 + "once_cell", 335 + "regex", 336 + "sharded-slab", 337 + "smallvec", 338 + "thread_local", 339 + "tracing", 340 + "tracing-core", 341 + "tracing-log", 342 + ] 343 + 344 + [[package]] 70 345 name = "unicode-ident" 71 346 version = "1.0.18" 72 347 source = "registry+https://github.com/rust-lang/crates.io-index" 73 348 checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 349 + 350 + [[package]] 351 + name = "valuable" 352 + version = "0.1.1" 353 + source = "registry+https://github.com/rust-lang/crates.io-index" 354 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 355 + 356 + [[package]] 357 + name = "winapi" 358 + version = "0.3.9" 359 + source = "registry+https://github.com/rust-lang/crates.io-index" 360 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 361 + dependencies = [ 362 + "winapi-i686-pc-windows-gnu", 363 + "winapi-x86_64-pc-windows-gnu", 364 + ] 365 + 366 + [[package]] 367 + name = "winapi-i686-pc-windows-gnu" 368 + version = "0.4.0" 369 + source = "registry+https://github.com/rust-lang/crates.io-index" 370 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 371 + 372 + [[package]] 373 + name = "winapi-x86_64-pc-windows-gnu" 374 + version = "0.4.0" 375 + source = "registry+https://github.com/rust-lang/crates.io-index" 376 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 377 + 378 + [[package]] 379 + name = "windows" 380 + version = "0.61.3" 381 + source = "registry+https://github.com/rust-lang/crates.io-index" 382 + checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 383 + dependencies = [ 384 + "windows-collections", 385 + "windows-core", 386 + "windows-future", 387 + "windows-link", 388 + "windows-numerics", 389 + ] 390 + 391 + [[package]] 392 + name = "windows-collections" 393 + version = "0.2.0" 394 + source = "registry+https://github.com/rust-lang/crates.io-index" 395 + checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 396 + dependencies = [ 397 + "windows-core", 398 + ] 399 + 400 + [[package]] 401 + name = "windows-core" 402 + version = "0.61.2" 403 + source = "registry+https://github.com/rust-lang/crates.io-index" 404 + checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 405 + dependencies = [ 406 + "windows-implement", 407 + "windows-interface", 408 + "windows-link", 409 + "windows-result", 410 + "windows-strings", 411 + ] 412 + 413 + [[package]] 414 + name = "windows-future" 415 + version = "0.2.1" 416 + source = "registry+https://github.com/rust-lang/crates.io-index" 417 + checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 418 + dependencies = [ 419 + "windows-core", 420 + "windows-link", 421 + "windows-threading", 422 + ] 423 + 424 + [[package]] 425 + name = "windows-implement" 426 + version = "0.60.0" 427 + source = "registry+https://github.com/rust-lang/crates.io-index" 428 + checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 429 + dependencies = [ 430 + "proc-macro2", 431 + "quote", 432 + "syn", 433 + ] 434 + 435 + [[package]] 436 + name = "windows-interface" 437 + version = "0.59.1" 438 + source = "registry+https://github.com/rust-lang/crates.io-index" 439 + checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 440 + dependencies = [ 441 + "proc-macro2", 442 + "quote", 443 + "syn", 444 + ] 445 + 446 + [[package]] 447 + name = "windows-link" 448 + version = "0.1.3" 449 + source = "registry+https://github.com/rust-lang/crates.io-index" 450 + checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 451 + 452 + [[package]] 453 + name = "windows-numerics" 454 + version = "0.2.0" 455 + source = "registry+https://github.com/rust-lang/crates.io-index" 456 + checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 457 + dependencies = [ 458 + "windows-core", 459 + "windows-link", 460 + ] 461 + 462 + [[package]] 463 + name = "windows-result" 464 + version = "0.3.4" 465 + source = "registry+https://github.com/rust-lang/crates.io-index" 466 + checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 467 + dependencies = [ 468 + "windows-link", 469 + ] 470 + 471 + [[package]] 472 + name = "windows-strings" 473 + version = "0.4.2" 474 + source = "registry+https://github.com/rust-lang/crates.io-index" 475 + checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 476 + dependencies = [ 477 + "windows-link", 478 + ] 479 + 480 + [[package]] 481 + name = "windows-threading" 482 + version = "0.1.0" 483 + source = "registry+https://github.com/rust-lang/crates.io-index" 484 + checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 485 + dependencies = [ 486 + "windows-link", 487 + ]
+10 -2
Cargo.toml
··· 1 1 [workspace] 2 2 resolver = "3" 3 - members = [ "futures-core", "futures-combinators", "futures-compat", "futures-derive", "futures-util"] 3 + members = [ "futures", "futures-combinators", "futures-compat", "futures-core", "futures-derive", "futures-util", "lifetime-guard"] 4 4 5 5 [workspace.package] 6 - version = "0.1.0" 6 + version = "0.0.2" 7 7 rust-version = "1.87" 8 8 edition = "2024" 9 9 license = "MIT OR Apache-2.0" ··· 11 11 repository = "https://github.com/AngleSideAngle/bcsc" 12 12 homepage = "https://github.com/AngleSideAngle/bcsc" 13 13 14 + [workspace.dependencies] 15 + # futures = { path = "futures", version = "0.0.2" } 16 + futures-combinators = { path = "futures-combinators", version = "0.0.2" } 17 + futures-compat = { path = "futures-compat", version = "0.0.2" } 18 + futures-core = { path = "futures-core", version = "0.0.2" } 19 + futures-derive = { path = "futures-derive", version = "0.0.2" } 20 + futures-util = { path = "futures-util", version = "0.0.2" } 21 + lifetime-guard = { path = "lifetime-guard", version = "0.0.2" }
+162 -9
README.md
··· 82 82 83 83 New async primitives that disallow intra-task concurrency, clone of `futures` and `futures-concurrency` for the new primitives. 84 84 85 - ## TODO: 86 - - [x] ScopedFuture 87 - - [ ] static combinators (Join Race etc), see futures-concurrency 88 - - [ ] `#[bsync]` or some compiler ScopedFuture generation 89 - - [ ] growable combinators (eg. `FutureGroup`, `FuturesUnordered`) (require alloc?) 90 - - [ ] unsound (needs `Forget`) multithreading 91 - - [ ] "rethinking async rust" 92 - - [ ] all of the above for streams 93 - - [ ] rfc? 94 85 95 86 channels: need lifetimed receievers, probably needs `Forget` (arc-like channels would be unsafe) 96 87 ··· 141 132 - need tons of interior mutability, since immutable/can't move means `poll` cannot take `&mut self`, cells everywhere 142 133 - nvm lots of unsafe code, but nothing really unsound 143 134 - potentially bad error messages? stuff like `join!` will have to output code that manually sets up the waker self ref 135 + 136 + ## TODO: 137 + - [x] ScopedFuture 138 + - [x] static combinators (Join Race etc), see futures-concurrency 139 + - [x] `#[async_scoped]` or some compiler ScopedFuture generation 140 + - [ ] doubly linked list waker registration 141 + - [ ] repeating static time reactors - eg. make event poll every N seconds 142 + - [ ] io uring reactors 143 + - [ ] growable combinators (eg. `FutureGroup`, `FuturesUnordered`) (require alloc?) 144 + - [ ] unsound (needs `Forget`) multithreading 145 + - [ ] "rethinking async rust" 146 + - [ ] all of the above for streams 147 + - [ ] rfc? 148 + 149 + # Chapter 3 150 + 151 + man this really sucks 152 + 153 + i need better things to do 154 + 155 + issues from ch 2 156 + - works great[*] 157 + - *: incompatible with event loop - something still has to poll 158 + - we're back to doubly linked list of waker registration in event loop 159 + - this requires Forget 160 + - ScopedFuture - Future interop sucks 161 + 162 + 163 + structured concurrency with regular combinators: 164 + - scope holds tasks 165 + - scope cancels tasks when dropped 166 + - tasks are ran by central executor 167 + 168 + pure structured concurrency with borrow checker: 169 + - high level block_on(), any task wake wakes every level up 170 + - tasks have events 171 + 172 + how do the tasks register to an event loop? they don't fuck 173 + 174 + 175 + ```rust 176 + struct Task<F: Future> { 177 + inner: F, 178 + prev: *const Task, 179 + next: *const Task, 180 + waker: Waker, 181 + } 182 + ``` 183 + 184 + &waker is passed into all sub-tasks, calling wake clone etc panics!!! 185 + this is pretty jank 186 + 187 + also waker doesn't have a lifetime so a safe code could easily register to external source that outlives the task 188 + this is unsound 189 + 190 + we need 191 + ```rust 192 + struct WakerRegister { 193 + prev: *const WakerRegister, 194 + next: *const WakerRegister, 195 + } 196 + ``` 197 + 198 + # Borrow Checked Structured Concurrency 199 + 200 + An async system needs to have the following components: 201 + 202 + - event loop : polls events and schedules tasks when they are ready 203 + - tasks : state machines that progress and can await events from the event loop 204 + - task combinators : tasks that compose other tasks into useful logic structures 205 + 206 + Tasks will register themselves to events on the event loop, which will need to outlive tasks and register pointers to wake the tasks, so that they can again be polled. 207 + This is incompatible with the borrow checker because the task pointers (wakers) are being stored inside an event loop with a lifetime that may exceed the tasks'. 208 + 209 + `Waker` is effectively a `*const dyn Wake`. It is implemented using a custom `RawWakerVTable` rather than a `dyn` ptr to allow for `Wake::wake(self)`, which is not object safe. 210 + This method is necessary for runtime implementations that rely on the wakers to be effectively `Arc<Task>`, since `wake(self)` consumes `self` and decrements the reference count. 211 + 212 + There are two types of sound async runtimes that currently exist in rust: 213 + 214 + [tokio](https://github.com/tokio-rs) and [smol](https://github.com/smol-rs) work using the afformentioned reference counting system to ensure wakers aren't dangling pointers to tasks that no longer exist. 215 + 216 + [embassy](https://github.com/embassy-rs/embassy) and [rtic](https://github.com/rtic-rs/rtic) work by ensuring tasks are stored in `static` task pools for `N` tasks. Scheduled tasks are represented by an intrusively linked list to avoid allocation, and wakers can't be dangling pointers because completed tasks will refuse to add themselves back to the linked list, or will be replaced by a new task. This is useful in environments where it is desirable to avoid heap allocation, but requires the user annotate the maximum number of a specific task that can exist at one time, and fails to spawn tasks when they exceed that limit. 217 + 218 + An async runtime where futures are allocated to the stack cannot be sound under this model because `Future::poll` allows any safe `Future` implementation to store or clone wakers wherever they want, which become dangling pointers after the stack allocated future goes out of scope. In order to prevent this, we must have a circular reference, where the task (`Task<'scope>: Wake`) contains a `&'scope Waker` and the `Waker` contains `*const dyn Wake`. For that to be safe, the `Waker` must never be moved. This cannot be possible because something needs to register the waker: 219 + 220 + ```rust 221 + // waker is moved 222 + poll(self: Pin<&mut Self>, cx: &mut Context<'_> /* '_ is the duration of the poll fn */) -> Poll<Self::Output> { 223 + // waker is moved! 224 + // can't register &Waker bc we run into the same problem, 225 + // and the waker only lives for '_ 226 + store.register(cx.waker()) 227 + } 228 + ``` 229 + 230 + Effectively, by saying our waker can't move, we are saying it must be stored by the task, which means it can't be a useful waker. Instead, what we could do is have a waker-register (verb, not noun) that facilitates the binding of an immovable waker to an immovable task, where the waker is guaranteed to outlive the task: 231 + 232 + ```rust 233 + pub trait Wake<'task> { 234 + fn wake(&self); 235 + fn register_waker(&mut self, waker: &'task Waker); 236 + } 237 + 238 + pub struct Waker { 239 + task: *const dyn Wake, 240 + valid: bool, // task is in charge of invalidating when it goes out of scope 241 + } 242 + 243 + pub struct WakerRegistration<'poll> { 244 + task: &'poll mut dyn Wake, 245 + } 246 + 247 + impl<'poll> WakerRegistration<'poll> { 248 + pub fn register<'task>(self, slot: &'task Waker) 249 + where 250 + 'task: 'poll, 251 + Self: 'task 252 + { 253 + *slot = Waker::new(self.task as *const dyn Wake); 254 + *task.register_waker(slot) 255 + } 256 + } 257 + ``` 258 + 259 + This system works better because `WakerRegistration` only lives 260 + 261 + 262 + 263 + Experienced rust programmers might be reading this and thinking I am stupid (true) because `Forget` 264 + 265 + An astute (soon to be disappointed) reader might be thinking, as I did when I first learned about this sytem, "what if we ensured that there was only one `Waker` per `Task`, and gave the task a pointer to the waker, so that it could disable the waker when dropped?" 266 + 267 + Unfortunately, there are a multitude of issues with this system 268 + 269 + - In order to hold a pointer to the Waker from the 270 + - Preventing a `Waker` from moving means panicking on the 271 + 272 + Even if it was guaranteed that wakers could not be moved or `cloned` (by panicking on `clone`), and registration occured via `&Waker`, the task would still be unable to 273 + 274 + https://conradludgate.com/posts/async-stack 275 + 276 + ## Structured Concurrency 277 + 278 + https://trio.discourse.group/t/discussion-notes-on-structured-concurrency-or-go-statement-considered-harmful/25 279 + https://kotlinlang.org/docs/coroutines-basics.html 280 + 281 + Notably, the structured concurrency pattern fits very nicely with our hypothetical unsound stack based async runtime. 282 + 283 + ## WeakCell Pattern & Forget trait 284 + 285 + https://github.com/rust-lang/rfcs/pull/3782 286 + 287 + 288 + There are two solutions: 289 + 290 + - `Wakers` panic on `clone()` 291 + 292 + ## Waker allocation problem & intra task concurrency 293 + 294 + we can't do intra task concurrency because WeakRegistrations 295 + 296 +
+22 -66
flake.lock
··· 1 1 { 2 2 "nodes": { 3 - "flake-utils": { 4 - "inputs": { 5 - "systems": "systems" 6 - }, 7 - "locked": { 8 - "lastModified": 1731533236, 9 - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 - "owner": "numtide", 11 - "repo": "flake-utils", 12 - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 - "type": "github" 14 - }, 15 - "original": { 16 - "owner": "numtide", 17 - "repo": "flake-utils", 18 - "type": "github" 19 - } 20 - }, 21 3 "naersk": { 22 4 "inputs": { 23 - "nixpkgs": "nixpkgs" 5 + "nixpkgs": [ 6 + "nixpkgs" 7 + ] 24 8 }, 25 9 "locked": { 26 10 "lastModified": 1752249768, ··· 38 22 }, 39 23 "nixpkgs": { 40 24 "locked": { 41 - "lastModified": 1752077645, 42 - "narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=", 43 - "owner": "NixOS", 44 - "repo": "nixpkgs", 45 - "rev": "be9e214982e20b8310878ac2baa063a961c1bdf6", 46 - "type": "github" 47 - }, 48 - "original": { 49 - "owner": "NixOS", 50 - "ref": "nixpkgs-unstable", 51 - "repo": "nixpkgs", 52 - "type": "github" 53 - } 54 - }, 55 - "nixpkgs-mozilla": { 56 - "flake": false, 57 - "locked": { 58 - "lastModified": 1744624473, 59 - "narHash": "sha256-S6zT/w5SyAkJ//dYdjbrXgm+6Vkd/k7qqUl4WgZ6jjk=", 60 - "owner": "mozilla", 61 - "repo": "nixpkgs-mozilla", 62 - "rev": "2292d4b35aa854e312ad2e95c4bb5c293656f21a", 63 - "type": "github" 64 - }, 65 - "original": { 66 - "owner": "mozilla", 67 - "repo": "nixpkgs-mozilla", 68 - "type": "github" 69 - } 70 - }, 71 - "nixpkgs_2": { 72 - "locked": { 73 - "lastModified": 1752077645, 74 - "narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=", 25 + "lastModified": 1756787288, 26 + "narHash": "sha256-rw/PHa1cqiePdBxhF66V7R+WAP8WekQ0mCDG4CFqT8Y=", 75 27 "owner": "NixOS", 76 28 "repo": "nixpkgs", 77 - "rev": "be9e214982e20b8310878ac2baa063a961c1bdf6", 29 + "rev": "d0fc30899600b9b3466ddb260fd83deb486c32f1", 78 30 "type": "github" 79 31 }, 80 32 "original": { 81 33 "owner": "NixOS", 82 - "ref": "nixpkgs-unstable", 34 + "ref": "nixos-unstable", 83 35 "repo": "nixpkgs", 84 36 "type": "github" 85 37 } 86 38 }, 87 39 "root": { 88 40 "inputs": { 89 - "flake-utils": "flake-utils", 90 41 "naersk": "naersk", 91 - "nixpkgs": "nixpkgs_2", 92 - "nixpkgs-mozilla": "nixpkgs-mozilla" 42 + "nixpkgs": "nixpkgs", 43 + "rust-overlay": "rust-overlay" 93 44 } 94 45 }, 95 - "systems": { 46 + "rust-overlay": { 47 + "inputs": { 48 + "nixpkgs": [ 49 + "nixpkgs" 50 + ] 51 + }, 96 52 "locked": { 97 - "lastModified": 1681028828, 98 - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 99 - "owner": "nix-systems", 100 - "repo": "default", 101 - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 53 + "lastModified": 1757039615, 54 + "narHash": "sha256-qm53+EUFfzyF8F0MEscHGqf9tx462GV3/zUZrn9wiQU=", 55 + "owner": "oxalica", 56 + "repo": "rust-overlay", 57 + "rev": "4486e04adbb4b0e39f593767f2c36e2211003d01", 102 58 "type": "github" 103 59 }, 104 60 "original": { 105 - "owner": "nix-systems", 106 - "repo": "default", 61 + "owner": "oxalica", 62 + "repo": "rust-overlay", 107 63 "type": "github" 108 64 } 109 65 }
+127 -27
flake.nix
··· 1 1 { 2 + description = "Stack allocated structured concurrency"; 3 + 2 4 inputs = { 3 - flake-utils.url = "github:numtide/flake-utils"; 4 - naersk.url = "github:nix-community/naersk"; 5 - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 - nixpkgs-mozilla = { 7 - url = "github:mozilla/nixpkgs-mozilla"; 8 - flake = false; 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + 7 + rust-overlay = { 8 + url = "github:oxalica/rust-overlay"; 9 + inputs.nixpkgs.follows = "nixpkgs"; 10 + }; 11 + 12 + naersk = { 13 + url = "github:nix-community/naersk"; 14 + inputs.nixpkgs.follows = "nixpkgs"; 9 15 }; 10 16 }; 11 17 12 - outputs = { self, flake-utils, naersk, nixpkgs, nixpkgs-mozilla }: 13 - flake-utils.lib.eachDefaultSystem (system: 14 - let 15 - pkgs = (import nixpkgs) { 16 - inherit system; 17 - overlays = [ 18 - (import nixpkgs-mozilla) 19 - ]; 20 - }; 18 + outputs = { nixpkgs, rust-overlay, naersk, ... }: 19 + let 20 + forAllSystems = function: 21 + nixpkgs.lib.genAttrs [ 22 + "x86_64-linux" 23 + "aarch64-linux" 24 + "riscv64-linux" 25 + "aarch64-darwin" 26 + ] (system: function nixpkgs.legacyPackages.${system}); 27 + 28 + forEachSystem = forAllSystems (pkgs: 29 + let 30 + pkgs' = pkgs.extend rust-overlay.overlays.default; 31 + 32 + rust-stable = pkgs'.rust-bin.stable.latest.default.override { 33 + extensions = [ "rust-src" "clippy" "rustfmt" ]; 34 + }; 35 + 36 + rust-nightly = pkgs'.rust-bin.nightly.latest.default.override { 37 + extensions = [ "rust-src" "clippy" "rustfmt" "miri" ]; 38 + }; 21 39 22 - naersk' = pkgs.callPackage naersk {}; 40 + naersk-stable = pkgs'.callPackage naersk { 41 + cargo = rust-stable; 42 + rustc = rust-stable; 43 + }; 23 44 24 - in { 25 - # For `nix build` & `nix run`: 26 - defaultPackage = naersk'.buildPackage { 27 - src = ./.; 28 - }; 45 + naersk-nightly = pkgs'.callPackage naersk { 46 + cargo = rust-nightly; 47 + rustc = rust-nightly; 48 + }; 29 49 30 - # For `nix develop`: 31 - devShell = pkgs.mkShell { 32 - nativeBuildInputs = with pkgs; [ rustc cargo clippy rustfmt rust-analyzer cargo-expand ]; 33 - }; 34 - } 35 - ); 50 + buildInputs = []; 51 + nativeBuildInputs = []; 52 + 53 + # Production package built with stable Rust 54 + package = naersk-stable.buildPackage { 55 + src = ./.; 56 + inherit buildInputs nativeBuildInputs; 57 + }; 58 + 59 + # Development package built with nightly (for Miri compatibility) 60 + packageNightly = naersk-nightly.buildPackage { 61 + src = ./.; 62 + inherit buildInputs nativeBuildInputs; 63 + }; 64 + 65 + in 66 + { 67 + packages = { 68 + default = package; 69 + stable = package; 70 + nightly = packageNightly; 71 + }; 72 + 73 + devShells = { 74 + default = pkgs'.mkShell { 75 + buildInputs = buildInputs ++ [ 76 + rust-stable 77 + pkgs'.rust-analyzer 78 + ]; 79 + nativeBuildInputs = nativeBuildInputs; 80 + 81 + # RUST_SRC_PATH = "${rust-stable}/lib/rustlib/src/rust/library"; 82 + }; 83 + 84 + nightly = pkgs'.mkShell { 85 + buildInputs = buildInputs ++ [ 86 + rust-nightly 87 + # pkgs'.rust-analyzer 88 + ]; 89 + nativeBuildInputs = nativeBuildInputs; 90 + 91 + # RUST_SRC_PATH = "${rust-nightly}/lib/rustlib/src/rust/library"; 92 + MIRIFLAGS = "-Zmiri-strict-provenance"; 93 + 94 + shellHook = '' 95 + echo "Nightly Rust environment with Miri" 96 + ''; 97 + }; 98 + }; 99 + 100 + checks = { 101 + test = pkgs'.runCommand "cargo-test" { 102 + buildInputs = [ rust-stable ] ++ buildInputs; 103 + nativeBuildInputs = nativeBuildInputs; 104 + } '' 105 + cargo test --release 106 + ''; 107 + 108 + miri = pkgs'.runCommand "cargo-miri-test" { 109 + buildInputs = [ rust-nightly ] ++ buildInputs; 110 + nativeBuildInputs = nativeBuildInputs; 111 + } '' 112 + cargo miri test --release 113 + ''; 114 + 115 + clippy = pkgs'.runCommand "cargo-clippy" { 116 + buildInputs = [ rust-stable ] ++ buildInputs; 117 + nativeBuildInputs = nativeBuildInputs; 118 + } '' 119 + cargo clippy --deny 120 + ''; 121 + 122 + fmt = pkgs'.runCommand "cargo-fmt" { 123 + buildInputs = [ rust-stable ]; 124 + } '' 125 + cargo fmt --all --check 126 + ''; 127 + }; 128 + } 129 + ); 130 + in 131 + { 132 + packages = forAllSystems (pkgs: forEachSystem.${pkgs.system}.packages); 133 + devShells = forAllSystems (pkgs: forEachSystem.${pkgs.system}.devShells); 134 + checks = forAllSystems (pkgs: forEachSystem.${pkgs.system}.checks); 135 + }; 36 136 }
+4 -5
futures/Cargo.toml
··· 9 9 homepage.workspace = true 10 10 11 11 [dependencies] 12 - futures-core = { path = "../futures-core" } 13 - futures-combinators = { path = "../futures-combinators" } 14 - futures-compat = { path = "../futures-compat" } 15 - futures-derive = { path = "../futures-derive" } 16 - futures-util = { path = "../futures-util" } 12 + futures-core = { workspace = true } 13 + futures-combinators = { workspace = true } 14 + futures-compat = { workspace = true } 15 + futures-derive = { workspace = true }
+19 -1
futures/src/lib.rs
··· 1 - fn test() {} 1 + #![no_std] 2 + 3 + use futures_compat::LocalWaker; 4 + use futures_derive::async_function; 5 + 6 + async fn evil() {} 7 + 8 + #[async_function] 9 + fn inner(a: i32, b: &i32) -> i32 { 10 + // evil().await; 11 + 1 12 + } 13 + 14 + #[async_function] 15 + fn test(a: i32, b: &i32) -> i32 { 16 + futures_derive::async_block! { let _ = 1 + *b; 2 }.await 17 + } 18 + 19 + // fn test2<'a>(a: i32) {}
+4 -2
futures-combinators/Cargo.toml
··· 9 9 homepage.workspace = true 10 10 11 11 [dependencies] 12 - futures-core = { path = "../futures-core" } 13 - futures-util = { path = "../futures-util" } 12 + futures-core = { workspace = true } 13 + futures-compat = { workspace = true } 14 + futures-util = { workspace = true } 15 + lifetime-guard = { workspace = true }
+77 -78
futures-combinators/src/join.rs
··· 1 - use futures_core::{ScopedFuture, Wake}; 2 - use futures_util::WakeStore; 3 - use futures_util::{MaybeDone, maybe_done}; 4 - use std::{cell::Cell, task::Poll}; 1 + use crate::wake::WakeArray; 2 + use futures_compat::LocalWaker; 3 + use futures_core::FusedFuture; 4 + use futures_util::maybe_done::MaybeDone; 5 + use futures_util::maybe_done::maybe_done; 6 + use std::pin::Pin; 7 + use std::task::Poll; 5 8 6 9 /// from [futures-concurrency](https://github.com/yoshuawuyts/futures-concurrency/tree/main) 7 10 /// Wait for all futures to complete. 8 11 /// 9 12 /// Awaits multiple futures simultaneously, returning the output of the futures 10 13 /// in the same container type they were created once all complete. 11 - pub trait Join<'scope> { 14 + pub trait Join { 12 15 /// The resulting output type. 13 16 type Output; 14 17 15 18 /// The [`ScopedFuture`] implementation returned by this method. 16 - type Future: ScopedFuture<'scope, Output = Self::Output>; 19 + type Future: futures_core::Future<LocalWaker, Output = Self::Output>; 17 20 18 21 /// Waits for multiple futures to complete. 19 22 /// ··· 24 27 fn join(self) -> Self::Future; 25 28 } 26 29 27 - pub trait JoinExt<'scope> { 28 - fn along_with<Fut>(self, other: Fut) -> Join2<'scope, Self, Fut> 30 + pub trait JoinExt { 31 + fn along_with<Fut>(self, other: Fut) -> Join2<Self, Fut> 29 32 where 30 - Self: Sized + ScopedFuture<'scope>, 31 - Fut: ScopedFuture<'scope>, 33 + Self: Sized + futures_core::Future<LocalWaker>, 34 + Fut: futures_core::Future<LocalWaker>, 32 35 { 33 36 (self, other).join() 34 37 } 35 38 } 36 39 37 - impl<'scope, T> JoinExt<'scope> for T where T: ScopedFuture<'scope> {} 40 + impl<T> JoinExt for T where T: futures_core::Future<LocalWaker> {} 38 41 39 42 macro_rules! impl_join_tuple { 40 43 ($namespace:ident $StructName:ident $($F:ident)+) => { 41 44 mod $namespace { 42 - use super::*; 43 - 44 - #[allow(non_snake_case)] 45 - pub struct Wakers<'scope> { 46 - $(pub $F: WakeStore<'scope>,)* 47 - } 48 - 49 - #[allow(non_snake_case)] 50 - pub struct WakerRefs<'scope> { 51 - $(pub $F: Cell<Option<&'scope dyn Wake<'scope>>>,)* 52 - } 45 + #[repr(u8)] 46 + pub(super) enum Indexes { $($F,)+ } 47 + pub(super) const LEN: usize = [$(Indexes::$F,)+].len(); 53 48 } 54 49 55 50 #[allow(non_snake_case)] 56 51 #[must_use = "futures do nothing unless you `.await` or poll them"] 57 - pub struct $StructName<'scope, $($F: ScopedFuture<'scope>),+> { 58 - $($F: MaybeDone<'scope, $F>,)* 59 - wakers: $namespace::Wakers<'scope>, 60 - refs: $namespace::WakerRefs<'scope>, 52 + pub struct $StructName<$($F: futures_core::Future<LocalWaker>),+> { 53 + $($F: MaybeDone<$F>,)* 54 + wake_array: WakeArray<{$namespace::LEN}>, 61 55 } 62 56 63 - impl<'scope, $($F: ScopedFuture<'scope>),+> ScopedFuture<'scope> 64 - for $StructName<'scope, $($F),+> 57 + impl<$($F: futures_core::Future<LocalWaker>),+> futures_core::Future<LocalWaker> for $StructName<$($F),+> 65 58 { 66 59 type Output = ($($F::Output),+); 67 60 68 - fn poll(&'scope self, wake: &'scope dyn Wake<'scope>) -> Poll<Self::Output> { 61 + #[allow(non_snake_case)] 62 + fn poll(self: Pin<&mut Self>, waker: Pin<&LocalWaker>) -> Poll<Self::Output> { 63 + let this = unsafe { self.get_unchecked_mut() }; 64 + 65 + let wake_array = unsafe { Pin::new_unchecked(&this.wake_array) }; 66 + $( 67 + // TODO debug_assert_matches is nightly https://github.com/rust-lang/rust/issues/82775 68 + debug_assert!(!matches!(this.$F, MaybeDone::Gone), "do not poll futures after they return Poll::Ready"); 69 + let mut $F = unsafe { Pin::new_unchecked(&mut this.$F) }; 70 + )+ 71 + 72 + wake_array.register_parent(waker); 73 + 69 74 let mut ready = true; 70 75 71 76 $( 72 - self.wakers.$F.set_parent(wake); 73 - self.refs.$F.replace(Some(&self.wakers.$F)); 77 + let index = $namespace::Indexes::$F as usize; 78 + let waker = unsafe { wake_array.child_guard_ptr(index).unwrap_unchecked() }; 74 79 75 - if !self.$F.is_done() { 76 - ready &= if self.wakers.$F.take_ready() { 77 - // By polling the future, we create our self-referential structure for lifetime `'scope`. 78 - // 79 - // SAFETY: 80 - // `unwrap_unchecked` is safe because we just inserted `Some` into `refs.$F`, 81 - // so it is guaranteed to be `Some`. 82 - self.$F 83 - .poll(unsafe { (&self.refs.$F.get()).unwrap_unchecked() }) 84 - .is_ready() 85 - } else { 86 - false 87 - }; 88 - } 80 + // ready if MaybeDone is Done or just completed (converted to Done) 81 + // unsafe / against Future api contract to poll after Gone/Future is finished 82 + ready &= if unsafe { dbg!(wake_array.take_woken(index).unwrap_unchecked()) } { 83 + $F.as_mut().poll(waker).is_ready() 84 + } else { 85 + $F.is_terminated() 86 + }; 89 87 )+ 90 88 91 89 if ready { ··· 96 94 // Once a future is not `MaybeDoneState::Future`, it transitions to `Done`, 97 95 // so we know the result of `take_output` must be `Some`. 98 96 unsafe { 99 - self.$F.take_output().unwrap_unchecked() 97 + $F.take_output().unwrap_unchecked() 100 98 }, 101 99 )* 102 100 )) ··· 106 104 } 107 105 } 108 106 109 - impl<'scope, $($F: ScopedFuture<'scope>),+> Join<'scope> for ($($F),+) { 107 + impl<$($F: futures_core::Future<LocalWaker>),+> Join for ($($F),+) { 110 108 type Output = ($($F::Output),*); 111 - type Future = $StructName<'scope, $($F),+>; 109 + type Future = $StructName<$($F),+>; 112 110 113 111 #[allow(non_snake_case)] 114 112 fn join(self) -> Self::Future { ··· 116 114 117 115 $StructName { 118 116 $($F: maybe_done($F),)* 119 - wakers: $namespace::Wakers { 120 - $($F: WakeStore::new(),)* 121 - }, 122 - refs: $namespace::WakerRefs { 123 - $($F: Option::None.into(),)* 124 - }, 117 + wake_array: WakeArray::new(), 125 118 } 126 119 } 127 120 } ··· 144 137 mod tests { 145 138 #![no_std] 146 139 147 - use futures_util::{noop_wake, poll_fn}; 140 + use futures_core::Future; 141 + use futures_util::{dummy_guard, poll_fn}; 142 + 143 + use crate::wake::local_wake; 148 144 149 145 use super::*; 150 146 147 + use std::pin; 148 + 151 149 #[test] 152 150 fn counters() { 153 - let x1 = Cell::new(0); 154 - let x2 = Cell::new(0); 155 - let f1 = poll_fn(|wake| { 156 - wake.wake(); 157 - x1.set(x1.get() + 1); 158 - if x1.get() == 4 { 159 - Poll::Ready(x1.get()) 151 + let mut x1 = 0; 152 + let mut x2 = 0; 153 + let f1 = poll_fn(|waker| { 154 + local_wake(waker); 155 + x1 += 1; 156 + if x1 == 4 { 157 + Poll::Ready(x1) 160 158 } else { 161 159 Poll::Pending 162 160 } 163 161 }); 164 - let f2 = poll_fn(|wake| { 165 - wake.wake(); 166 - x2.set(x2.get() + 1); 167 - if x2.get() == 5 { 168 - Poll::Ready(x2.get()) 162 + let f2 = poll_fn(|waker| { 163 + local_wake(waker); 164 + x2 += 1; 165 + if x2 == 5 { 166 + Poll::Ready(x2) 169 167 } else { 170 168 Poll::Pending 171 169 } 172 170 }); 173 - let dummy_waker = noop_wake(); 174 - let join = (f1, f2).join(); 171 + let guard = pin::pin!(dummy_guard()); 172 + let mut join = pin::pin!((f1, f2).join()); 175 173 for _ in 0..4 { 176 - assert_eq!(join.poll(&dummy_waker), Poll::Pending); 174 + assert_eq!(join.as_mut().poll(guard.as_ref()), Poll::Pending); 177 175 } 178 - assert_eq!(join.poll(&dummy_waker), Poll::Ready((4, 5))); 176 + assert_eq!(join.poll(guard.as_ref()), Poll::Ready((4, 5))); 179 177 } 180 178 181 179 #[test] 182 180 fn never_wake() { 183 - let f1 = poll_fn(|_| Poll::<i32>::Pending); 181 + let f1 = poll_fn(|_| Poll::<i32>::Ready(0)); 184 182 let f2 = poll_fn(|_| Poll::<i32>::Pending); 185 - let dummy_waker = noop_wake(); 186 - let join = (f1, f2).join(); 183 + let guard = pin::pin!(dummy_guard()); 184 + let mut join = pin::pin!((f1, f2).join()); 187 185 for _ in 0..10 { 188 - assert_eq!(join.poll(&dummy_waker), Poll::Pending); 186 + assert_eq!(join.as_mut().poll(guard.as_ref()), Poll::Pending); 189 187 } 190 188 } 191 189 192 190 #[test] 193 - fn basic() { 191 + fn immediate() { 194 192 let f1 = poll_fn(|_| Poll::Ready(1)); 195 193 let f2 = poll_fn(|_| Poll::Ready(2)); 196 - let dummy_waker = noop_wake(); 197 - assert_eq!(f1.along_with(f2).poll(&dummy_waker), Poll::Ready((1, 2))); 194 + let join = pin::pin!(f1.along_with(f2)); 195 + let guard = pin::pin!(dummy_guard()); 196 + assert_eq!(join.poll(guard.as_ref()), Poll::Ready((1, 2))); 198 197 } 199 198 }
+3 -2
futures-combinators/src/lib.rs
··· 1 - mod join; 2 - mod race; 1 + pub mod join; 2 + pub mod race; 3 + mod wake; 3 4 4 5 use join::*; 5 6 use race::*;
+65 -68
futures-combinators/src/race.rs
··· 1 - use futures_core::{ScopedFuture, Wake}; 2 - use futures_util::WakeStore; 3 - use std::{cell::Cell, task::Poll}; 1 + use futures_util::LocalWaker; 2 + 3 + use crate::wake::WakeArray; 4 + use std::pin::Pin; 5 + use std::task::Poll; 4 6 5 7 /// from [futures-concurrency](https://github.com/yoshuawuyts/futures-concurrency/tree/main) 6 8 /// Wait for the first future to complete. 7 9 /// 8 10 /// Awaits multiple future at once, returning as soon as one completes. The 9 11 /// other futures are cancelled. 10 - pub trait Race<'scope> { 12 + pub trait Race { 11 13 /// The resulting output type. 12 14 type Output; 13 15 14 16 /// The [`ScopedFuture`] implementation returned by this method. 15 - type Future: ScopedFuture<'scope, Output = Self::Output>; 17 + type Future: futures_core::Future<LocalWaker, Output = Self::Output>; 16 18 17 19 /// Wait for the first future to complete. 18 20 /// ··· 24 26 } 25 27 26 28 pub trait RaceExt<'scope> { 27 - fn race_with<Fut>(self, other: Fut) -> Race2<'scope, Self, Fut> 29 + fn race_with<Fut>(self, other: Fut) -> Race2<Self, Fut> 28 30 where 29 - Self: Sized + 'scope + ScopedFuture<'scope>, 30 - Fut: ScopedFuture<'scope> + 'scope, 31 + Self: Sized + futures_core::Future<LocalWaker>, 32 + Fut: futures_core::Future<LocalWaker>, 31 33 { 32 34 (self, other).race() 33 35 } 34 36 } 35 37 36 - impl<'scope, T> RaceExt<'scope> for T where T: ScopedFuture<'scope> {} 38 + impl<'scope, T> RaceExt<'scope> for T where T: futures_core::Future<LocalWaker> {} 37 39 38 40 macro_rules! impl_race_tuple { 39 41 ($namespace:ident $StructName:ident $OutputsName:ident $($F:ident)+) => { 40 42 mod $namespace { 41 - use super::*; 42 - 43 - #[allow(non_snake_case)] 44 - pub struct Wakers<'scope> { 45 - $(pub $F: WakeStore<'scope>,)* 46 - } 47 - 48 - #[allow(non_snake_case)] 49 - pub struct WakerRefs<'scope> { 50 - $(pub $F: Cell<Option<&'scope dyn Wake<'scope>>>,)* 51 - } 43 + #[repr(u8)] 44 + pub(super) enum Indexes { $($F,)+ } 45 + pub(super) const LEN: usize = [$(Indexes::$F,)+].len(); 52 46 } 53 47 54 48 pub enum $OutputsName<$($F,)+> { ··· 77 71 78 72 #[allow(non_snake_case)] 79 73 #[must_use = "futures do nothing unless you `.await` or poll them"] 80 - pub struct $StructName<'scope, $($F: ScopedFuture<'scope>),+> { 74 + pub struct $StructName<$($F: futures_core::Future<LocalWaker>),+> { 81 75 $($F: $F,)* 82 - wakers: $namespace::Wakers<'scope>, 83 - refs: $namespace::WakerRefs<'scope>, 76 + wake_array: WakeArray<{$namespace::LEN}>, 84 77 } 85 78 86 - impl<'scope, $($F: ScopedFuture<'scope> + 'scope),+> ScopedFuture<'scope> 87 - for $StructName<'scope, $($F),+> 79 + impl<'scope, $($F: futures_core::Future<LocalWaker>),+> futures_core::Future<LocalWaker> 80 + for $StructName<$($F),+> 88 81 { 89 82 type Output = $OutputsName<$($F::Output,)+>; 90 83 91 - fn poll(&'scope self, wake: &'scope dyn Wake<'scope>) -> Poll<Self::Output> { 84 + #[allow(non_snake_case)] 85 + fn poll(self: Pin<&mut Self>, waker: Pin<&LocalWaker>) -> Poll<Self::Output> { 86 + let this = unsafe { self.get_unchecked_mut() }; 87 + 88 + let wake_array = unsafe { Pin::new_unchecked(&this.wake_array) }; 89 + $( 90 + let mut $F = unsafe { Pin::new_unchecked(&mut this.$F) }; 91 + )+ 92 + 93 + wake_array.register_parent(waker); 94 + 92 95 $( 93 - self.wakers.$F.set_parent(wake); 94 - self.refs.$F.replace(Some(&self.wakers.$F)); 96 + let index = $namespace::Indexes::$F as usize; 97 + let waker = unsafe { wake_array.child_guard_ptr(index).unwrap_unchecked() }; 95 98 96 - if self.wakers.$F.take_ready() { 97 - // By polling the future, we create our self-referential structure for lifetime `'scope`. 98 - // 99 - // SAFETY: 100 - // `unwrap_unchecked` is safe because we just inserted `Some` into `refs.$F`, 101 - // so it is guaranteed to be `Some`. 102 - if let Poll::Ready(res) = self.$F.poll(unsafe { (&self.refs.$F.get()).unwrap_unchecked() }) { 99 + // this is safe because we know index < LEN 100 + if unsafe { wake_array.take_woken(index).unwrap_unchecked() } { 101 + if let Poll::Ready(res) = $F.as_mut().poll(waker) { 103 102 return Poll::Ready($OutputsName::$F(res)); 104 103 } 105 104 } ··· 109 108 } 110 109 } 111 110 112 - impl<'scope, $($F: ScopedFuture<'scope> + 'scope),+> Race<'scope> for ($($F),+) { 111 + impl<'scope, $($F: futures_core::Future<LocalWaker>),+> Race for ($($F),+) { 113 112 type Output = $OutputsName<$($F::Output),*>; 114 - type Future = $StructName<'scope, $($F),+>; 113 + type Future = $StructName<$($F),+>; 115 114 116 115 #[allow(non_snake_case)] 117 116 fn race(self) -> Self::Future { ··· 119 118 120 119 $StructName { 121 120 $($F: $F,)* 122 - wakers: $namespace::Wakers { 123 - $($F: WakeStore::new(),)* 124 - }, 125 - refs: $namespace::WakerRefs { 126 - $($F: Option::None.into(),)* 127 - }, 121 + wake_array: WakeArray::new(), 128 122 } 129 123 } 130 124 } ··· 147 141 mod tests { 148 142 #![no_std] 149 143 150 - use futures_util::{noop_wake, poll_fn}; 144 + use std::pin; 145 + 146 + use futures_core::Future; 147 + use futures_util::{dummy_guard, poll_fn}; 148 + 149 + use crate::wake::local_wake; 151 150 152 151 use super::*; 153 152 154 153 #[test] 155 154 fn counters() { 156 - let x1 = Cell::new(0); 157 - let x2 = Cell::new(0); 158 - let f1 = poll_fn(|wake| { 159 - wake.wake(); 160 - x1.set(x1.get() + 1); 161 - if x1.get() == 4 { 162 - Poll::Ready(x1.get()) 155 + let mut x1 = 0; 156 + let mut x2 = 0; 157 + let f1 = poll_fn(|waker| { 158 + local_wake(waker); 159 + x1 += 1; 160 + if x1 == 4 { 161 + Poll::Ready(x1) 163 162 } else { 164 163 Poll::Pending 165 164 } 166 165 }); 167 - let f2 = poll_fn(|wake| { 168 - wake.wake(); 169 - x2.set(x2.get() + 1); 170 - if x2.get() == 2 { 171 - Poll::Ready(x2.get()) 166 + let f2 = poll_fn(|waker| { 167 + local_wake(waker); 168 + x2 += 1; 169 + if x2 == 2 { 170 + Poll::Ready(x2) 172 171 } else { 173 172 Poll::Pending 174 173 } 175 174 }); 176 - let dummy_waker = noop_wake(); 177 - let join = (f1, f2).race(); 178 - assert_eq!(join.poll(&dummy_waker), Poll::Pending); 179 - assert_eq!(join.poll(&dummy_waker), Poll::Ready(RaceOutputs2::B(2))); 175 + let guard = pin::pin!(dummy_guard()); 176 + let mut race = pin::pin!((f1, f2).race()); 177 + assert_eq!(race.as_mut().poll(guard.as_ref()), Poll::Pending); 178 + assert_eq!(race.poll(guard.as_ref()), Poll::Ready(RaceOutputs2::B(2))); 180 179 } 181 180 182 181 #[test] 183 182 fn never_wake() { 184 183 let f1 = poll_fn(|_| Poll::<i32>::Pending); 185 184 let f2 = poll_fn(|_| Poll::<i32>::Pending); 186 - let dummy_waker = noop_wake(); 187 - let join = (f1, f2).race(); 185 + let mut race = pin::pin!((f1, f2).race()); 186 + let guard = pin::pin!(dummy_guard()); 188 187 for _ in 0..10 { 189 - assert_eq!(join.poll(&dummy_waker), Poll::Pending); 188 + assert_eq!(race.as_mut().poll(guard.as_ref()), Poll::Pending); 190 189 } 191 190 } 192 191 ··· 194 193 fn basic() { 195 194 let f1 = poll_fn(|_| Poll::Ready(1)); 196 195 let f2 = poll_fn(|_| Poll::Ready(2)); 197 - let dummy_waker = noop_wake(); 198 - assert_eq!( 199 - f1.race_with(f2).poll(&dummy_waker), 200 - Poll::Ready(RaceOutputs2::A(1)) 201 - ); 196 + let race = pin::pin!(f1.race_with(f2)); 197 + let guard = pin::pin!(dummy_guard()); 198 + assert_eq!(race.poll(guard.as_ref()), Poll::Ready(RaceOutputs2::A(1))); 202 199 } 203 200 }
+114
futures-combinators/src/wake.rs
··· 1 + use std::{array, cell::Cell, marker::PhantomPinned, pin::Pin, ptr::NonNull}; 2 + 3 + use futures_compat::{LocalWaker, WakePtr}; 4 + use futures_core::Wake; 5 + use lifetime_guard::{guard::RefGuard, guard::ValueGuard}; 6 + 7 + pub struct WakeArray<const N: usize> { 8 + parent: RefGuard<WakePtr>, 9 + children: [ValueGuard<WakePtr>; N], 10 + stores: [WakeStore; N], 11 + _marker: PhantomPinned, 12 + } 13 + 14 + impl<const N: usize> WakeArray<N> { 15 + pub fn new() -> Self { 16 + Self { 17 + parent: RefGuard::new(), 18 + children: array::from_fn(|_| ValueGuard::new(None)), 19 + stores: array::from_fn(|_| WakeStore::new()), 20 + _marker: PhantomPinned, 21 + } 22 + } 23 + 24 + pub fn register_parent( 25 + self: Pin<&Self>, 26 + parent: Pin<&ValueGuard<WakePtr>>, 27 + ) { 28 + unsafe { Pin::new_unchecked(&self.parent) }.register(parent); 29 + } 30 + 31 + /// Returns pinned reference to child ValueGuard 32 + /// returns None if n is not in 0..N 33 + pub fn child_guard_ptr( 34 + self: Pin<&Self>, 35 + index: usize, 36 + ) -> Option<Pin<&ValueGuard<WakePtr>>> { 37 + // TODO remove bounds checking, break api when https://github.com/rust-lang/rust/issues/123646 38 + if index >= N { 39 + return None; 40 + } 41 + 42 + let wake_store = unsafe { self.stores.get(index).unwrap_unchecked() }; 43 + wake_store.set_parent(&self.parent); 44 + 45 + let wake_store = unsafe { 46 + NonNull::new_unchecked( 47 + wake_store as *const dyn Wake as *mut dyn Wake, 48 + ) 49 + }; 50 + 51 + let child_guard = 52 + unsafe { self.get_ref().children.get(index).unwrap_unchecked() }; 53 + child_guard.set(Some(wake_store)); 54 + 55 + Some(unsafe { Pin::new_unchecked(child_guard) }) 56 + } 57 + 58 + pub fn take_woken(self: Pin<&Self>, index: usize) -> Option<bool> { 59 + self.stores.get(index).map(|store| store.take_woken()) 60 + } 61 + } 62 + 63 + pub struct WakeStore { 64 + wake_parent: Cell<Option<NonNull<RefGuard<WakePtr>>>>, 65 + activated: Cell<bool>, 66 + } 67 + 68 + impl WakeStore { 69 + pub fn new() -> Self { 70 + Self { 71 + wake_parent: Cell::new(None), 72 + activated: Cell::new(true), 73 + } 74 + } 75 + 76 + pub fn set_parent(&self, parent: &RefGuard<WakePtr>) { 77 + self.wake_parent.set(Some(parent.into())); 78 + } 79 + 80 + pub fn take_woken(&self) -> bool { 81 + self.activated.replace(false) 82 + } 83 + } 84 + 85 + impl Wake for WakeStore { 86 + fn wake(&self) { 87 + dbg!("awake?"); 88 + self.activated.set(true); 89 + if let Some(parent) = self 90 + .wake_parent 91 + .get() 92 + .map(|guard_ptr| unsafe { &*guard_ptr.as_ptr() }) 93 + .and_then(|guard| guard.get()) 94 + .flatten() 95 + { 96 + unsafe { &*parent.as_ptr() }.wake(); 97 + } 98 + } 99 + } 100 + 101 + pub fn local_wake(guard: &LocalWaker) { 102 + if let Some(wake) = guard.get() { 103 + unsafe { (*wake.as_ptr()).wake() } 104 + } 105 + } 106 + 107 + // pub unsafe fn wake_bespoke_waker(waker: &std::task::Waker) { 108 + // unsafe { 109 + // let guard = futures_compat::waker_to_guard(waker); 110 + // if let Some(wake) = guard.get() { 111 + // (*wake.as_ptr()).wake(); 112 + // } 113 + // } 114 + // }
+2 -1
futures-compat/Cargo.toml
··· 9 9 homepage.workspace = true 10 10 11 11 [dependencies] 12 - futures-core = { path = "../futures-core" } 12 + futures-core = { workspace = true } 13 + lifetime-guard = { workspace = true }
+112 -127
futures-compat/src/lib.rs
··· 1 - //! Any interaction between the real Future ecosystem and ScopedFuture 2 - //! ecosystem is strictly unsound 3 - //! 4 - //! ScopedFutures cannot poll Futures because they can't guarantee they 5 - //! will outlive *const () ptrs they supply to the futures, leading to 6 - //! dangling pointers if the futures register the waker with something that 7 - //! assumes assumes it is valid for 'static and then the ScopedFuture goes 8 - //! out of scope 9 - //! 10 - //! Futures cannot poll ScopedFutures because they cannot guarantee their 11 - //! Waker will be valid for 'scope (due to lack of real borrowing), leading to 12 - //! unsoundness if a ScopedFuture internally registers the waker with something 13 - //! that expects it to live for 'scope, and then the ScopedFutureWrapper is 14 - //! dropped 15 - //! 16 - //! 17 - //! ## TRIGGER WARNING 18 - //! 19 - //! This code is not for the faint of heart. Read at your own risk. 1 + //! Any interaction between an executor/reactor intended for task::Future 2 + //! with an executor/reactor intended for bcsc::Future is strictly unsound. 20 3 21 4 use std::{ 22 - cell::UnsafeCell, 23 - marker::PhantomData, 24 - mem, 5 + hint::unreachable_unchecked, 6 + mem::ManuallyDrop, 25 7 pin::Pin, 26 - task::{Context, Poll, Waker}, 8 + ptr::NonNull, 9 + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, 27 10 }; 28 11 29 - use futures_core::{ScopedFuture, Wake}; 12 + use futures_core::Wake; 13 + use lifetime_guard::{atomic_guard::AtomicValueGuard, guard::ValueGuard}; 30 14 31 - /// RawWaker: fat ptr (*const (), &'static RawWakerVTable) 32 - /// &'scope dyn Wake fat ptr: (&'scope (), &'scope WakeVTable) 15 + pub type WakePtr = Option<NonNull<dyn Wake>>; 16 + pub type LocalWaker = ValueGuard<WakePtr>; 17 + pub type AtomicWaker = AtomicValueGuard<WakePtr>; 18 + 19 + static EVIL_VTABLE: RawWakerVTable = unsafe { 20 + RawWakerVTable::new( 21 + |_| unreachable_unchecked(), 22 + |_| unreachable_unchecked(), 23 + |_| unreachable_unchecked(), 24 + |_| unreachable_unchecked(), 25 + ) 26 + }; 27 + 28 + /// Coerces a pinned `ValueGuard` reference to a `Waker` for use in 29 + /// `core::future::Future` 33 30 /// 34 - /// can transmute between them, but the waker will be completely invalid! 31 + /// Any usage or storage of the resulting `Waker` is undefined behavior. 32 + pub unsafe fn guard_to_waker(guard: Pin<&LocalWaker>) -> ManuallyDrop<Waker> { 33 + ManuallyDrop::new(unsafe { 34 + Waker::from_raw(RawWaker::new( 35 + guard.get_ref() as *const ValueGuard<WakePtr> as *const (), 36 + &EVIL_VTABLE, 37 + )) 38 + }) 39 + } 35 40 36 - /// wraps an internal ScopedFuture, implements Future 37 - pub struct ScopedFutureWrapper<'scope, F: ScopedFuture<'scope> + 'scope> { 38 - inner: UnsafeCell<F>, 39 - marker: PhantomData<&'scope ()>, 41 + pub unsafe fn atomic_guard_to_waker( 42 + guard: Pin<&AtomicWaker>, 43 + ) -> ManuallyDrop<Waker> { 44 + ManuallyDrop::new(unsafe { 45 + Waker::from_raw(RawWaker::new( 46 + guard.get_ref() as *const AtomicValueGuard<WakePtr> as *const (), 47 + &EVIL_VTABLE, 48 + )) 49 + }) 40 50 } 41 51 42 - impl<'scope, F: ScopedFuture<'scope> + 'scope> Future 43 - for ScopedFutureWrapper<'scope, F> 44 - { 45 - type Output = F::Output; 52 + /// Coerces a `Waker` into a pinned `AtomicValueGuard` reference. 53 + /// 54 + /// This should only be used to undo the work of `guard_to_waker`. 55 + pub unsafe fn waker_to_guard<'a>(waker: &Waker) -> Pin<&LocalWaker> { 56 + unsafe { 57 + Pin::new_unchecked(&*(waker.data() as *const ValueGuard<WakePtr>)) 58 + } 59 + } 46 60 47 - fn poll( 48 - self: std::pin::Pin<&mut Self>, 49 - cx: &mut std::task::Context<'_>, 50 - ) -> Poll<Self::Output> { 51 - // # Safety 52 - // 53 - // Transmutes `Waker` into `&'scope dyn Wake`. 54 - // This is possible because Waker (internally just RawWaker) contains 55 - // (*const (), &'static RawWakerVTable), and the fat ptr `&dyn Wake` 56 - // internally is (*const (), *const WakeVTable). 57 - // 58 - // For this to be sound, the input waker from `cx` must be an invalid 59 - // waker (using the waker as intended would be UB) that has the form of 60 - // a `&dyn Wake` fat ptr, as generated in `UnscopedFutureWrapper`. 61 - // 62 - // This is only sound because it is paired with the transmute in 63 - // `UnscopedFutureWrapper` 64 - // 65 - // This conversion is necessary to piggyback off rustc's expansion 66 - // of `async` blocks into state machines implementing `Future`. 67 - // 68 - // The unpinning is safe because inner (a `ScopedFuture`) cannot be 69 - // moved after a self reference is established on its first `poll`. 70 - // 71 - // The use of the `UnsafeCell` is sound and necessary to get around 72 - // the afformentioned immutable self reference (since `Future::poll`) 73 - // requires a `&mut Self`. It is sound because we never take a 74 - // `&mut self.inner`. 75 - unsafe { 76 - let this = self.get_unchecked_mut(); 77 - let wake: &'scope dyn Wake = mem::transmute::< 78 - Waker, 79 - &'scope dyn Wake, 80 - >(cx.waker().to_owned()); 81 - (&*this.inner.get()).poll(wake) 82 - } 61 + pub unsafe fn waker_to_atomic_guard<'a>(waker: &Waker) -> Pin<&AtomicWaker> { 62 + unsafe { 63 + Pin::new_unchecked(&*(waker.data() as *const AtomicValueGuard<WakePtr>)) 83 64 } 84 65 } 85 66 86 - impl<'scope, F: ScopedFuture<'scope> + 'scope> ScopedFutureWrapper<'scope, F> { 87 - pub unsafe fn from_scoped(f: F) -> Self { 88 - Self { 89 - inner: f.into(), 90 - marker: PhantomData, 91 - } 92 - } 67 + pub unsafe fn std_future_to_bespoke<F: core::future::Future>( 68 + future: F, 69 + ) -> impl futures_core::Future<LocalWaker, Output = F::Output> { 70 + NormalFutureWrapper(future) 93 71 } 94 72 95 - /// wraps an internal Future, implements ScopedFuture 96 - /// this is fundamentally unsafe and relies on the future not registering its waker 97 - /// in any reactor that lives beyond this wrapper, otherwise there will be a dangling pointer 98 - /// 99 - /// it is safe to use only with the #[async_scoped] macro, which guarantees that, internally, every futures is a ScopedFutureWrapper 100 - pub struct UnscopedFutureWrapper<'scope, F: Future> { 101 - inner: UnsafeCell<F>, 102 - marker: PhantomData<&'scope ()>, 73 + pub unsafe fn bespoke_future_to_std<F: futures_core::Future<LocalWaker>>( 74 + future: F, 75 + ) -> impl core::future::Future<Output = F::Output> { 76 + BespokeFutureWrapper(future) 103 77 } 104 78 105 - impl<'scope, F: Future> ScopedFuture<'scope> 106 - for UnscopedFutureWrapper<'scope, F> 79 + /// wraps `core::future::Future` in impl of `bcsc:Future` 80 + #[repr(transparent)] 81 + pub struct NormalFutureWrapper<F: core::future::Future>(F); 82 + 83 + impl<F: core::future::Future> futures_core::Future<LocalWaker> 84 + for NormalFutureWrapper<F> 107 85 { 108 86 type Output = F::Output; 109 87 110 88 fn poll( 111 - self: &'scope Self, 112 - wake: &'scope dyn Wake<'scope>, 89 + self: Pin<&mut Self>, 90 + waker: Pin<&LocalWaker>, 113 91 ) -> Poll<Self::Output> { 114 - // # Safety 115 - // 116 - // Transmutes `&'scope dyn Wake` into a Waker. 117 - // This is possible because Waker (internally just RawWaker) contains 118 - // (*const (), &'static RawWakerVTable), and the fat ptr `&dyn Wake` 119 - // internally is (*const (), *const WakeVTable). 120 - // 121 - // Using the resulting Waker is UB, which is why UnscopedFutureWrapper 122 - // can only be used in pair with ScopedFutureWrapper, which transmutes 123 - // the invalid `Waker` back to a `&dyn Wake`. 124 - // 125 - // This conversion is necessary to piggyback off rustc's expansion 126 - // of `async` blocks into state machines implementing `Future`. 127 - let waker: Waker = 128 - unsafe { mem::transmute::<&'scope dyn Wake<'scope>, Waker>(wake) }; 129 - // # Safety 130 - // 131 - // Once any ScopedFuture is first polled and stores a waker, it becomes 132 - // immutable and immovable because it has an immutable self reference. 133 - // 134 - // The Pin::new_unchecked is necessary to be compatible with 135 - // `task::Future` 136 - let pinned_future = 137 - unsafe { Pin::new_unchecked(&mut *self.inner.get()) }; 138 - 139 - pinned_future.poll(&mut Context::from_waker(&waker)) 92 + unsafe { 93 + self.map_unchecked_mut(|this| &mut this.0) 94 + .poll(&mut Context::from_waker(&guard_to_waker(waker))) 95 + } 140 96 } 141 97 } 142 98 143 - impl<'scope, F: Future> UnscopedFutureWrapper<'scope, F> { 144 - pub unsafe fn from_future(f: F) -> Self { 145 - Self { 146 - inner: f.into(), 147 - marker: PhantomData, 99 + /// wraps custom `bcsc::Future` in impl of `core::future::Future` 100 + #[repr(transparent)] 101 + pub struct BespokeFutureWrapper<F>(F) 102 + where 103 + F: futures_core::Future<LocalWaker>; 104 + 105 + impl<F> core::future::Future for BespokeFutureWrapper<F> 106 + where 107 + F: futures_core::Future<LocalWaker>, 108 + { 109 + type Output = F::Output; 110 + 111 + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { 112 + unsafe { 113 + self.map_unchecked_mut(|this| &mut this.0) 114 + .poll(waker_to_guard(cx.waker())) 148 115 } 149 116 } 150 117 } 151 118 152 - fn test(a: &i32) -> impl ScopedFuture<'_> + '_ { 153 - async fn inner(a: &i32) -> i32 { 154 - a + 1 119 + #[cfg(test)] 120 + mod test { 121 + use std::pin; 122 + 123 + use super::*; 124 + use futures_core::Wake; 125 + 126 + #[derive(Debug)] 127 + struct DummyWake; 128 + impl Wake for DummyWake { 129 + fn wake(&self) {} 155 130 } 156 131 157 - let x = inner(a); 158 - 159 - unsafe { UnscopedFutureWrapper::from_future(inner(a)) } 132 + #[test] 133 + fn waker_conversion() { 134 + let dummy = DummyWake; 135 + let guard = pin::pin!(ValueGuard::new(NonNull::new( 136 + &dummy as *const dyn Wake as *mut dyn Wake 137 + ))); 138 + let waker = unsafe { guard_to_waker(guard.as_ref()) }; 139 + let guard = unsafe { waker_to_guard(&waker) }; 140 + assert_eq!( 141 + guard.get().unwrap().as_ptr() as *const () as usize, 142 + &dummy as *const _ as *const () as usize 143 + ); 144 + } 160 145 }
+149 -35
futures-core/src/lib.rs
··· 1 - use std::task::Poll; 2 - 3 - mod task; 1 + //! Redefinitions of task::Future to be incompatible with them 4 2 5 - pub use crate::task::Wake; 3 + use std::{ 4 + ops::{self, DerefMut}, 5 + pin::Pin, 6 + task::Poll, 7 + }; 6 8 7 - /// ScopedFuture represents a unit of asynchronous computation that must be 8 - /// polled by an external actor. 9 + /// A future represents an asynchronous computation obtained by use of `async`. 9 10 /// 10 - /// Implementations access a context (`cx: &'scope dyn Wake`) to signal 11 - /// they are ready to resume execution. 12 - /// 13 - /// A notable difference between `bcsc::ScopedFuture` and `core::task::Future` 14 - /// is the latter cannot safetly ran as a task by an executor without having a 15 - /// 'static lifetime. This is because there is no way for the compiler to 16 - /// guarantee the task doesn't outlive any data, as the executor is free to 17 - /// cancel it (or refuse to) whenever it wants. 11 + /// This future assumes a nonstandard Context, which is incompatible with 12 + /// executors or reactors made for `core::future::Future`. In the interest of 13 + /// safety, it has a dedicated type. 18 14 /// 19 - /// Additionally, because raw/unsafe implementations of `core::task::Waker` 20 - /// effectively do lifetime-erasure, stack-allocated futures cannot prevent 21 - /// unsound behavior from wakers outliving them (even `Forget` would not 22 - /// entirely fix this due to the api). 15 + /// A future is a value that might not have finished computing yet. This kind of 16 + /// "asynchronous value" makes it possible for a thread to continue doing useful 17 + /// work while it waits for the value to become available. 23 18 /// 24 - /// In order to avoid unsound behavior, executors must either use Weak<Wake> 25 - /// for safetly losing access to tasks or enforce tasks being stored in 26 - /// `static` pools of memory. 19 + /// # The `poll` method 27 20 /// 28 - /// `ScopedFuture` instead leverages the borrow checker to allow for (less 29 - /// powerful) stack based async execution. 21 + /// The core method of future, `poll`, *attempts* to resolve the future into a 22 + /// final value. This method does not block if the value is not ready. Instead, 23 + /// the current task is scheduled to be woken up when it's possible to make 24 + /// further progress by `poll`ing again. The `context` passed to the `poll` 25 + /// method can provide a [`Waker`], which is a handle for waking up the current 26 + /// task. 30 27 /// 31 - /// some more: 32 - /// what occurs in `core::task::Future::poll()` is that the ref to a cx.waker 33 - /// is cloned and stored by a reactor via some method. 28 + /// When using a future, you generally won't call `poll` directly, but instead 29 + /// `.await` the value. 34 30 /// 35 - /// The waker is no longer tied to the actual future's lifetime, making it 36 - /// unsound to not have either static tasks or reference counting. 37 - /// To avoid this, we want to use a &'scope waker instead, with 1 waker / task. 38 - pub trait ScopedFuture<'scope> { 31 + /// [`Waker`]: crate::task::Waker 32 + #[must_use = "futures do nothing unless you `.await` or poll them"] 33 + #[diagnostic::on_unimplemented( 34 + label = "`{Self}` is not a `bcsc::Future`", 35 + message = "`{Self}` is not a `bcsc::Future`", 36 + note = "If you are trying to await a `core::future::Future` from within a `bcsc::Future`, note that the systems are incompatible." 37 + )] 38 + pub trait Future<Waker> { 39 + /// The type of value produced on completion. 39 40 type Output; 40 41 41 - /// as soon as poll is called, the struct becomes self-referential, 42 - /// effectively pinned until dropped (or forgotten....D; ) 42 + /// Attempts to resolve the future to a final value, registering 43 + /// the current task for wakeup if the value is not yet available. 44 + /// 45 + /// # Return value 46 + /// 47 + /// This function returns: 48 + /// 49 + /// - [`Poll::Pending`] if the future is not ready yet 50 + /// - [`Poll::Ready(val)`] with the result `val` of this future if it 51 + /// finished successfully. 52 + /// 53 + /// Once a future has finished, clients should not `poll` it again. 54 + /// 55 + /// When a future is not ready yet, `poll` returns `Poll::Pending` and 56 + /// stores a clone of the [`Waker`] copied from the current [`Context`]. 57 + /// This [`Waker`] is then woken once the future can make progress. 58 + /// For example, a future waiting for a socket to become 59 + /// readable would call `.clone()` on the [`Waker`] and store it. 60 + /// When a signal arrives elsewhere indicating that the socket is readable, 61 + /// [`Waker::wake`] is called and the socket future's task is awoken. 62 + /// Once a task has been woken up, it should attempt to `poll` the future 63 + /// again, which may or may not produce a final value. 64 + /// 65 + /// Note that on multiple calls to `poll`, only the [`Waker`] from the 66 + /// [`Context`] passed to the most recent call should be scheduled to 67 + /// receive a wakeup. 68 + /// 69 + /// # Runtime characteristics 70 + /// 71 + /// Futures alone are *inert*; they must be *actively* `poll`ed to make 72 + /// progress, meaning that each time the current task is woken up, it should 73 + /// actively re-`poll` pending futures that it still has an interest in. 74 + /// 75 + /// The `poll` function is not called repeatedly in a tight loop -- instead, 76 + /// it should only be called when the future indicates that it is ready to 77 + /// make progress (by calling `wake()`). If you're familiar with the 78 + /// `poll(2)` or `select(2)` syscalls on Unix it's worth noting that futures 79 + /// typically do *not* suffer the same problems of "all wakeups must poll 80 + /// all events"; they are more like `epoll(4)`. 81 + /// 82 + /// An implementation of `poll` should strive to return quickly, and should 83 + /// not block. Returning quickly prevents unnecessarily clogging up 84 + /// threads or event loops. If it is known ahead of time that a call to 85 + /// `poll` may end up taking a while, the work should be offloaded to a 86 + /// thread pool (or something similar) to ensure that `poll` can return 87 + /// quickly. 88 + /// 89 + /// # Panics 90 + /// 91 + /// Once a future has completed (returned `Ready` from `poll`), calling its 92 + /// `poll` method again may panic, block forever, or cause other kinds of 93 + /// problems; the `Future` trait places no requirements on the effects of 94 + /// such a call. However, as the `poll` method is not marked `unsafe`, 95 + /// Rust's usual rules apply: calls must never cause undefined behavior 96 + /// (memory corruption, incorrect use of `unsafe` functions, or the like), 97 + /// regardless of the future's state. 98 + /// 99 + /// [`Poll::Ready(val)`]: Poll::Ready 100 + /// [`Waker`]: crate::task::Waker 101 + /// [`Waker::wake`]: crate::task::Waker::wake 102 + fn poll(self: Pin<&mut Self>, waker: Pin<&Waker>) -> Poll<Self::Output>; 103 + } 104 + 105 + impl<Waker, F: ?Sized + Future<Waker> + Unpin> Future<Waker> for &mut F { 106 + type Output = F::Output; 107 + 43 108 fn poll( 44 - self: &'scope Self, 45 - wake: &'scope dyn Wake<'scope>, 46 - ) -> Poll<Self::Output>; 109 + mut self: Pin<&mut Self>, 110 + waker: Pin<&Waker>, 111 + ) -> Poll<Self::Output> { 112 + F::poll(Pin::new(&mut **self), waker) 113 + } 114 + } 115 + 116 + impl<Waker, P> Future<Waker> for Pin<P> 117 + where 118 + P: ops::DerefMut<Target: Future<Waker>>, 119 + { 120 + type Output = <<P as ops::Deref>::Target as Future<Waker>>::Output; 121 + 122 + fn poll(self: Pin<&mut Self>, waker: Pin<&Waker>) -> Poll<Self::Output> { 123 + <P::Target as Future<Waker>>::poll(self.as_deref_mut(), waker) 124 + } 125 + } 126 + 127 + /// A future which tracks whether or not the underlying future 128 + /// should no longer be polled. 129 + /// 130 + /// `is_terminated` will return `true` if a future should no longer be polled. 131 + /// Usually, this state occurs after `poll` (or `try_poll`) returned 132 + /// `Poll::Ready`. However, `is_terminated` may also return `true` if a future 133 + /// has become inactive and can no longer make progress and should be ignored 134 + /// or dropped rather than being `poll`ed again. 135 + pub trait FusedFuture<Waker>: Future<Waker> { 136 + /// Returns `true` if the underlying future should no longer be polled. 137 + fn is_terminated(&self) -> bool; 138 + } 139 + 140 + impl<Waker, F: FusedFuture<Waker> + ?Sized + Unpin> FusedFuture<Waker> 141 + for &mut F 142 + { 143 + fn is_terminated(&self) -> bool { 144 + <F as FusedFuture<Waker>>::is_terminated(&**self) 145 + } 146 + } 147 + 148 + impl<Waker, P> FusedFuture<Waker> for Pin<P> 149 + where 150 + P: DerefMut + Unpin, 151 + P::Target: FusedFuture<Waker>, 152 + { 153 + fn is_terminated(&self) -> bool { 154 + <P::Target as FusedFuture<Waker>>::is_terminated(&**self) 155 + } 156 + } 157 + 158 + /// temporary trait until Fn::call is stabilized 159 + pub trait Wake { 160 + fn wake(&self); 47 161 }
-7
futures-core/src/task.rs
··· 1 - /// A task that can be woken. 2 - /// 3 - /// This acts as a handle for a reactor to indicate when a `ScopedFuture` is 4 - /// once again ready to be polled. 5 - pub trait Wake<'scope> { 6 - fn wake(&self); 7 - }
+163 -54
futures-derive/src/lib.rs
··· 1 1 use proc_macro::TokenStream; 2 - use quote::quote; 2 + use quote::{ToTokens, quote}; 3 3 use syn::{ 4 - Expr, ExprAwait, FnArg, ItemFn, Pat, ReturnType, Signature, 5 - parse_macro_input, visit_mut::VisitMut, 4 + Expr, ExprAwait, ItemFn, ReturnType, parse_macro_input, parse_quote, 5 + parse2, visit_mut::VisitMut, 6 6 }; 7 7 8 + /// Takes async fn that returns anonymous `Future` impl. 9 + /// Generates fn that returns `UnscopedFutureWrapper` wrapper for the the anonymous `Future` impl. 10 + /// 11 + /// ```rust,ignore 12 + /// fn my_func<'a, 'b>(a: &'a A, b: &'b B) -> impl ScopedFuture<LifetimeGuard, Output = Output> { 13 + /// let output = async move { [body] } // compilers turns this into -> impl Future<Output = T> + 'a + 'b 14 + /// unsafe { futures_compat::std_future_to_bespoke(output) } 15 + /// } 16 + /// ``` 17 + /// 18 + /// see https://rust-lang.github.io/rfcs/2394-async_await.html#lifetime-capture-in-the-anonymous-future 19 + /// for more context on lifetime capture 20 + /// - resulting ScopedFuture needs to be constrained to not outlive the lifetimes of any references 21 + /// 22 + /// to actually implement this (capture all lifetimes) we use `ScopedFuture<'_> + '_` so the compiler can infer 23 + /// lifetimes from the anonymous future impl returned by the actual inner async fn 8 24 #[proc_macro_attribute] 9 - pub fn async_scoped(_: TokenStream, item: TokenStream) -> TokenStream { 10 - let mut input = parse_macro_input!(item as ItemFn); 11 - 25 + pub fn async_function(_: TokenStream, item: TokenStream) -> TokenStream { 26 + let mut item_fn = parse_macro_input!(item as ItemFn); 12 27 // Wraps *every* async expression within the function block with 13 - // `ScopedFutureWrapper`, allowing them to be treated as regular `Future` 28 + // `BespokeFutureWrapper`, allowing them to be treated as regular `Future` 14 29 // impls. 15 30 // 16 31 // This will cause a compiler error if any expression being awaited is not 17 32 // a `ScopedFuture`, which is intentional because the `Future` and 18 33 // `ScopedFuture` systems are incompatible. 19 - ScopedFutureWrappingVisitor.visit_item_fn_mut(&mut input); 34 + BespokeFutureWrappingVisitor.visit_item_fn_mut(&mut item_fn); 35 + 36 + // disable async since it is moved to the block 37 + item_fn.sig.asyncness = None; 38 + 39 + // wrap block with UnscopedFutureWrapper 40 + let block = *item_fn.block; 41 + *item_fn.block = parse_quote! { 42 + { 43 + let future = async move #block; 44 + unsafe { futures_compat::std_future_to_bespoke(future) } 45 + } 46 + }; 47 + 48 + let output_type = match &item_fn.sig.output { 49 + ReturnType::Default => quote! { () }, 50 + ReturnType::Type(_, ty) => quote! { #ty }, 51 + }; 52 + 53 + item_fn.sig.output = parse_quote! { -> impl futures_core::Future<LocalWaker, Output = #output_type> }; 54 + 55 + // let has_lifetime_dependency = 56 + // item_fn.sig.inputs.iter().any(|param| match param { 57 + // FnArg::Receiver(receiver) => receiver.reference.is_some(), 58 + // FnArg::Typed(pat) => has_lifetime_dependency(&pat.ty), 59 + // }); 20 60 21 - // Wrap the function with `UnscopedFutureWrapper` to convert it back into 22 - // a `ScopedFuture`. 23 - wrap_async_with_scoped(&input).into() 61 + // // set outer fn output to ScopedFuture<'_/'static, Output = #output> 62 + // item_fn.sig.output = if has_lifetime_dependency { 63 + // parse_quote! { -> impl futures_core::ScopedFuture<'_, Output = #output> + '_ } 64 + // } else { 65 + // parse_quote! { -> impl futures_core::ScopedFuture<'static, Output = #output> } 66 + // }; 67 + 68 + item_fn.to_token_stream().into() 24 69 } 25 70 71 + /// This currently is impossible to do the `futures_compat` workarounds not 72 + /// being compatible with closures. 73 + /// 26 74 /// Takes async fn that returns anonymous `Future` impl. 27 75 /// Generates fn that returns `UnscopedFutureWrapper` wrapper for the the anonymous `Future` impl. 28 76 /// 29 - /// ```rust 77 + /// ```rust,ignore 30 78 /// fn [original name]<'a, 'b>(a: &'a A, b: &'b B) -> impl ScopedFuture<'a + 'b, Output = T> + 'a + 'b { 31 79 /// async fn [__inner]<'a, 'b>(a: &'a A, b: &'b B) -> T { [body] } // compilers turns this into -> impl Future<Output = T> + 'a + 'b 32 80 /// unsafe { UnscopedFutureWrapper::from_future(__inner()) } ··· 39 87 /// 40 88 /// to actually implement this (capture all lifetimes) we use `ScopedFuture<'_> + '_` so the compiler can infer 41 89 /// lifetimes from the anonymous future impl returned by the actual inner async fn 42 - fn wrap_async_with_scoped( 43 - ItemFn { 44 - attrs, 45 - vis, 46 - sig: 47 - Signature { 48 - constness, 49 - unsafety, 50 - ident, 51 - generics, 52 - inputs, 53 - output, 54 - .. 55 - }, 56 - block, 57 - }: &ItemFn, 58 - ) -> proc_macro2::TokenStream { 59 - let output = match output { 60 - ReturnType::Default => quote! { () }, 61 - ReturnType::Type(_, ty) => quote! { #ty }, 62 - }; 90 + // #[proc_macro] 91 + // pub fn closure(input: TokenStream) -> TokenStream { 92 + // // let ExprClosure { 93 + // // attrs, 94 + // // lifetimes, 95 + // // constness, 96 + // // movability, 97 + // // capture, 98 + // // inputs, 99 + // // output, 100 + // // body, 101 + // // .. 102 + // // } = parse_macro_input!(input as ExprClosure); 103 + // let mut closure = parse_macro_input!(input as ExprClosure); 104 + // // disable async because we move it to inner 105 + // closure.asyncness = None; 106 + // let body = closure.body; 107 + 108 + // // let output = match closure.output { 109 + // // ReturnType::Default => parse_quote! { () }, 110 + // // ReturnType::Type(_, ty) => parse_quote! { #ty }, 111 + // // }; 112 + 113 + // // let outer_output = 114 + // // parse_quote! { futures_core::ScopedFuture<'_, Output = #output> + '_ }; 115 + 116 + // closure.body = parse_quote! {{ 117 + // let output = async move { #body }; 118 + // unsafe { futures_compat::UnscopedFutureWrapper::from_future(output) } 119 + // }}; 120 + // // closure.output = outer_output; 121 + // closure.to_token_stream().into() 122 + // } 63 123 64 - let inner_args: Vec<syn::Ident> = inputs 65 - .iter() 66 - .filter_map(|param| match param { 67 - FnArg::Receiver(_) => Some(quote::format_ident!("self")), 68 - FnArg::Typed(typed) => { 69 - if let Pat::Ident(ident) = &*typed.pat { 70 - Some(ident.ident.to_owned()) 71 - } else { 72 - None 73 - } 74 - } 75 - }) 76 - .collect(); 124 + /// Wraps a block of optionally async statements and expressions in an anonymous `ScopedFuture` impl. 125 + /// 126 + /// This generates a modified block of the form: 127 + /// 128 + /// ```rust,ignore 129 + /// { 130 + /// let output = async { <original block, mapped to convert all `ScopedFuture` to `Future`> }; 131 + /// unsafe { futures_compat::UnscopedFutureWrapper::from_future(output) } 132 + /// } 133 + /// ``` 134 + #[proc_macro] 135 + pub fn async_block(input: TokenStream) -> TokenStream { 136 + // block is formed { **expr/stmt }, so we need to surround the inputs in {} 137 + let input = proc_macro2::TokenStream::from(input); 138 + let block_input = quote! { { #input } }; 77 139 78 - quote! { 79 - #(#attrs)* #vis #constness #unsafety fn #ident #generics (#inputs) -> impl ScopedFuture<'_, Output = #output> + '_ { 80 - async fn #constness #unsafety fn __inner (#inputs) -> #output #block 140 + let mut block = parse2(block_input).expect("Failed to parse as block."); 81 141 82 - let future = __inner(#(#inner_args),*); 142 + BespokeFutureWrappingVisitor.visit_block_mut(&mut block); 83 143 84 - unsafe { futures_compat::UnscopedFutureWrapper::from_future(future) } 144 + quote! { 145 + { 146 + let output = async #block; 147 + unsafe { futures_compat::std_future_to_bespoke(output) } 85 148 } 86 149 } 150 + .into() 87 151 } 88 152 153 + /// Determines if typed pattern contains a reference or dependency on a 154 + /// lifetime (used for deciding between '_ and 'static ScopedFuture). 155 + // fn has_lifetime_dependency(ty: &syn::Type) -> bool { 156 + // match ty { 157 + // syn::Type::Reference(_) => true, 158 + // syn::Type::Path(type_path) => { 159 + // type_path.path.segments.iter().any(|segment| { 160 + // if let syn::PathArguments::AngleBracketed(args) = 161 + // &segment.arguments 162 + // { 163 + // args.args.iter().any(|arg| match arg { 164 + // GenericArgument::Type(ty) => { 165 + // has_lifetime_dependency(ty) 166 + // } 167 + // syn::GenericArgument::Lifetime(_) => true, 168 + // _ => false, 169 + // }) 170 + // } else { 171 + // false 172 + // } 173 + // }) 174 + // } 175 + // syn::Type::Tuple(tuple) => { 176 + // tuple.elems.iter().any(has_lifetime_dependency) 177 + // } 178 + // syn::Type::Slice(slice) => has_lifetime_dependency(&slice.elem), 179 + // syn::Type::Array(array) => has_lifetime_dependency(&array.elem), 180 + // syn::Type::Ptr(ptr) => has_lifetime_dependency(&ptr.elem), 181 + // syn::Type::Group(group) => has_lifetime_dependency(&group.elem), 182 + // syn::Type::Paren(paren) => has_lifetime_dependency(&paren.elem), 183 + // syn::Type::BareFn(bare_fn) => { 184 + // bare_fn 185 + // .inputs 186 + // .iter() 187 + // .any(|input| has_lifetime_dependency(&input.ty)) 188 + // || match &bare_fn.output { 189 + // ReturnType::Default => false, 190 + // ReturnType::Type(_, ty) => has_lifetime_dependency(ty), 191 + // } 192 + // } 193 + 194 + // _ => false, 195 + // } 196 + // } 197 + 89 198 /// Uses the `syn::visit_mut` api to wrap every `.await` expression in 90 199 /// `ScopedFutureWrapper`. 91 - struct ScopedFutureWrappingVisitor; 200 + struct BespokeFutureWrappingVisitor; 92 201 93 - impl VisitMut for ScopedFutureWrappingVisitor { 202 + impl VisitMut for BespokeFutureWrappingVisitor { 94 203 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { 95 204 if let Expr::Await(ExprAwait { attrs, base, .. }) = expr { 96 205 *expr = syn::parse_quote! { 97 - unsafe { futures_compat::ScopedFutureWrapper::from_scoped(#(#attrs)* #base) }.await 206 + unsafe { futures_compat::bespoke_future_to_std(#(#attrs)* #base) }.await 98 207 }; 99 208 } 100 209
+2 -1
futures-util/Cargo.toml
··· 9 9 homepage.workspace = true 10 10 11 11 [dependencies] 12 - futures-core = { path = "../futures-core" } 12 + futures-core = { workspace = true } 13 + lifetime-guard = { workspace = true }
+17
futures-util/src/block_on.rs
··· 1 + use std::{ 2 + pin::{self, Pin}, 3 + task::Poll, 4 + }; 5 + 6 + use crate::{LocalWaker, dummy_guard}; 7 + 8 + pub fn block_on<F: futures_core::Future<LocalWaker>>( 9 + mut f: Pin<&mut F>, 10 + ) -> F::Output { 11 + let dummy_guard = pin::pin!(dummy_guard()); 12 + loop { 13 + if let Poll::Ready(out) = f.as_mut().poll(dummy_guard.as_ref()) { 14 + return out; 15 + } 16 + } 17 + }
-14
futures-util/src/ext.rs
··· 1 - pub trait FutureExt: Future { 2 - fn along_with(self, other: impl Future) 3 - where 4 - Self: Sized, 5 - { 6 - // Join2 7 - } 8 - // fn then(self, other: impl Future) 9 - // where 10 - // Self: Sized, 11 - // { 12 - 13 - // } 14 - }
+49 -12
futures-util/src/lib.rs
··· 1 - mod ext; 2 - mod maybe_done; 3 - mod poll_fn; 4 - mod wakers; 1 + use std::{pin::Pin, ptr::NonNull, task::Poll}; 5 2 6 - use futures_core::ScopedFuture; 7 - pub use maybe_done::*; 8 - pub use poll_fn::poll_fn; 9 - pub use wakers::*; 3 + use futures_core::{Future, Wake}; 4 + use lifetime_guard::{atomic_guard::AtomicValueGuard, guard::ValueGuard}; 5 + 6 + pub mod block_on; 7 + pub mod maybe_done; 8 + 9 + pub type WakePtr = Option<NonNull<dyn Wake>>; 10 + pub type LocalWaker = ValueGuard<WakePtr>; 11 + pub type AtomicWaker = AtomicValueGuard<WakePtr>; 10 12 11 - // Just a helper function to ensure the futures we're returning all have the 12 - // right implementations. 13 - pub(crate) fn assert_future<'scope, T, F>(future: F) -> F 13 + pub(crate) fn assert_future<T, F>(future: F) -> F 14 14 where 15 - F: ScopedFuture<'scope, Output = T>, 15 + F: Future<LocalWaker, Output = T>, 16 16 { 17 17 future 18 18 } 19 + 20 + pub struct PollFn<F, T>(F) 21 + where 22 + F: FnMut(&LocalWaker) -> Poll<T>; 23 + 24 + impl<F, T> futures_core::Future<LocalWaker> for PollFn<F, T> 25 + where 26 + F: FnMut(&LocalWaker) -> Poll<T>, 27 + { 28 + type Output = T; 29 + 30 + fn poll( 31 + self: Pin<&mut Self>, 32 + waker: Pin<&LocalWaker>, 33 + ) -> Poll<Self::Output> { 34 + (unsafe { &mut self.get_unchecked_mut().0 })(&waker) 35 + } 36 + } 37 + 38 + pub fn poll_fn<F, T>(f: F) -> impl futures_core::Future<LocalWaker, Output = T> 39 + where 40 + F: FnMut(&LocalWaker) -> Poll<T>, 41 + { 42 + PollFn(f) 43 + } 44 + 45 + pub struct DummyWaker; 46 + 47 + impl Wake for DummyWaker { 48 + fn wake(&self) { 49 + dbg!("awake!"); 50 + } 51 + } 52 + 53 + pub fn dummy_guard() -> ValueGuard<WakePtr> { 54 + ValueGuard::new(NonNull::new(&mut DummyWaker as *mut dyn Wake)) 55 + }
+59 -107
futures-util/src/maybe_done.rs
··· 1 1 //! Definition of the MaybeDone combinator 2 2 3 - use futures_core::{ScopedFuture, Wake}; 3 + use futures_core::FusedFuture; 4 + 5 + use crate::LocalWaker; 4 6 5 7 use super::assert_future; 6 - use std::{ 7 - cell::UnsafeCell, 8 - task::{Poll, ready}, 9 - }; 10 - 11 - #[must_use = "futures do nothing unless you `.await` or poll them"] 12 - pub struct MaybeDone<'scope, Fut: ScopedFuture<'scope>> { 13 - state: UnsafeCell<MaybeDoneState<'scope, Fut>>, 14 - } 8 + use core::mem; 9 + use core::pin::Pin; 10 + use futures_core::Future; 11 + use std::task::Poll; 12 + use std::task::ready; 15 13 16 14 /// A future that may have completed. 17 15 /// 18 16 /// This is created by the [`maybe_done()`] function. 19 17 #[derive(Debug)] 20 - pub enum MaybeDoneState<'scope, Fut: ScopedFuture<'scope>> { 18 + pub enum MaybeDone<Fut: futures_core::Future<LocalWaker>> { 21 19 /// A not-yet-completed future 22 - Future(Fut), 20 + Future(/* #[pin] */ Fut), 23 21 /// The output of the completed future 24 22 Done(Fut::Output), 25 23 /// The empty variant after the result of a [`MaybeDone`] has been ··· 27 25 Gone, 28 26 } 29 27 28 + impl<Fut: Future<LocalWaker> + Unpin> Unpin for MaybeDone<Fut> {} 29 + 30 30 /// Wraps a future into a `MaybeDone` 31 - /// 32 - /// 33 - pub fn maybe_done<'scope, Fut: ScopedFuture<'scope>>( 31 + pub fn maybe_done<Fut: futures_core::Future<LocalWaker>>( 34 32 future: Fut, 35 - ) -> MaybeDone<'scope, Fut> { 36 - assert_future::<(), _>(MaybeDone { 37 - state: MaybeDoneState::Future(future).into(), 38 - }) 33 + ) -> MaybeDone<Fut> { 34 + assert_future::<(), _>(MaybeDone::Future(future)) 39 35 } 40 36 41 - impl<'scope, Fut: ScopedFuture<'scope>> MaybeDone<'scope, Fut> { 37 + impl<Fut: Future<LocalWaker>> MaybeDone<Fut> { 38 + /// Returns an [`Option`] containing a mutable reference to the output of the future. 39 + /// The output of this method will be [`Some`] if and only if the inner 40 + /// future has been completed and [`take_output`](MaybeDone::take_output) 41 + /// has not yet been called. 42 + #[inline] 43 + pub fn output_mut(self: Pin<&mut Self>) -> Option<&mut Fut::Output> { 44 + unsafe { 45 + match self.get_unchecked_mut() { 46 + Self::Done(res) => Some(res), 47 + _ => None, 48 + } 49 + } 50 + } 51 + 42 52 /// Attempt to take the output of a `MaybeDone` without driving it 43 53 /// towards completion. 44 54 #[inline] 45 - pub fn take_output(&self) -> Option<Fut::Output> { 46 - match unsafe { &*self.state.get() } { 47 - MaybeDoneState::Done(_) => {} 48 - MaybeDoneState::Future(_) | MaybeDoneState::Gone => return None, 55 + pub fn take_output(self: Pin<&mut Self>) -> Option<Fut::Output> { 56 + match &*self { 57 + Self::Done(_) => {} 58 + Self::Future(_) | Self::Gone => return None, 49 59 } 50 - match unsafe { self.state.get().replace(MaybeDoneState::Gone) } { 51 - MaybeDoneState::Done(output) => Some(output), 52 - _ => unreachable!(), 60 + unsafe { 61 + match mem::replace(self.get_unchecked_mut(), Self::Gone) { 62 + Self::Done(output) => Some(output), 63 + _ => unreachable!(), 64 + } 53 65 } 54 66 } 55 - 56 - /// Returns an immutable reference to the internal state of this future 57 - /// 58 - /// # Safety 59 - /// You must not hold this reference past any use of any other methods of this struct 60 - pub unsafe fn get_state(&self) -> &MaybeDoneState<'scope, Fut> { 61 - unsafe { &*self.state.get() } 62 - } 67 + } 63 68 64 - pub fn is_done(&self) -> bool { 65 - match unsafe { &*self.state.get() } { 66 - MaybeDoneState::Future(_) => false, 67 - MaybeDoneState::Done(_) | MaybeDoneState::Gone => true, 69 + impl<Fut: Future<LocalWaker>> FusedFuture<LocalWaker> for MaybeDone<Fut> { 70 + fn is_terminated(&self) -> bool { 71 + match self { 72 + Self::Future(_) => false, 73 + Self::Done(_) | Self::Gone => true, 68 74 } 69 75 } 70 76 } 71 77 72 - impl<'scope, Fut: ScopedFuture<'scope>> ScopedFuture<'scope> 73 - for MaybeDone<'scope, Fut> 78 + impl<Fut: Future<LocalWaker>> futures_core::Future<LocalWaker> 79 + for MaybeDone<Fut> 74 80 { 75 81 type Output = (); 76 82 77 - fn poll(&'scope self, cx: &'scope dyn Wake<'scope>) -> Poll<Self::Output> { 78 - match unsafe { &*self.state.get() } { 79 - MaybeDoneState::Future(f) => { 80 - let res = ready!(f.poll(cx)); 81 - // this is fine because no immutable references currently exist 82 - unsafe { self.state.get().replace(MaybeDoneState::Done(res)) }; 83 - } 84 - MaybeDoneState::Done(_) => {} 85 - MaybeDoneState::Gone => { 86 - panic!("MaybeDone polled after value taken") 83 + fn poll( 84 + mut self: Pin<&mut Self>, 85 + waker: Pin<&LocalWaker>, 86 + ) -> Poll<Self::Output> { 87 + unsafe { 88 + match self.as_mut().get_unchecked_mut() { 89 + Self::Future(f) => { 90 + let res = ready!(Pin::new_unchecked(f).poll(waker)); 91 + self.set(Self::Done(res)); 92 + } 93 + Self::Done(_) => {} 94 + Self::Gone => panic!("MaybeDone polled after value taken"), 87 95 } 88 96 } 89 97 Poll::Ready(()) 90 98 } 91 99 } 92 - 93 - #[cfg(test)] 94 - mod tests { 95 - use std::cell::Cell; 96 - 97 - use crate::poll_fn; 98 - 99 - use crate::noop_wake; 100 - 101 - use super::*; 102 - 103 - #[test] 104 - fn immediate_return() { 105 - let immediate = poll_fn(|_| Poll::Ready(1)); 106 - let future = maybe_done(immediate); 107 - let wake = noop_wake(); 108 - match unsafe { future.get_state() } { 109 - MaybeDoneState::Future(_) => {} 110 - MaybeDoneState::Done(_) | MaybeDoneState::Gone => { 111 - panic!("should be MaybeDoneState::Future") 112 - } 113 - } 114 - assert_eq!(future.poll(&wake), Poll::Ready(())); 115 - match unsafe { future.get_state() } { 116 - MaybeDoneState::Done(_) => {} 117 - MaybeDoneState::Future(_) | MaybeDoneState::Gone => { 118 - panic!("should be MaybeDoneState::Done") 119 - } 120 - } 121 - assert_eq!(future.take_output(), Some(1)); 122 - assert_eq!(future.take_output(), None); 123 - } 124 - 125 - #[test] 126 - fn normal() { 127 - let x = Cell::new(0); 128 - let poll = poll_fn(|wake| { 129 - wake.wake(); 130 - x.set(x.get() + 1); 131 - if x.get() == 4 { 132 - Poll::Ready(x.get()) 133 - } else { 134 - Poll::Pending 135 - } 136 - }); 137 - let future = maybe_done(poll); 138 - let noop = noop_wake(); 139 - for _ in 0..3 { 140 - assert_eq!(future.poll(&noop), Poll::Pending); 141 - assert_eq!(future.take_output(), None); 142 - } 143 - assert_eq!(future.poll(&noop), Poll::Ready(())); 144 - assert_eq!(future.poll(&noop), Poll::Ready(())); 145 - assert_eq!(future.take_output(), Some(4)); 146 - } 147 - }
-39
futures-util/src/poll_fn.rs
··· 1 - use core::fmt; 2 - use std::task::Poll; 3 - 4 - use futures_core::{ScopedFuture, Wake}; 5 - 6 - use crate::assert_future; 7 - 8 - /// Future for the [`poll_fn`] function. 9 - #[must_use = "futures do nothing unless you `.await` or poll them"] 10 - pub struct PollFn<F> { 11 - f: F, 12 - } 13 - 14 - /// Creates a new future wrapping around a function returning [`Poll`]. 15 - /// 16 - /// Polling the returned future delegates to the wrapped function. 17 - pub fn poll_fn<'scope, T, F>(f: F) -> PollFn<F> 18 - where 19 - F: Fn(&'scope dyn Wake) -> Poll<T>, 20 - { 21 - assert_future::<T, _>(PollFn { f }) 22 - } 23 - 24 - impl<F> fmt::Debug for PollFn<F> { 25 - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 - f.debug_struct("PollFn").finish() 27 - } 28 - } 29 - 30 - impl<'scope, T, F> ScopedFuture<'scope> for PollFn<F> 31 - where 32 - F: Fn(&'scope dyn Wake) -> Poll<T>, 33 - { 34 - type Output = T; 35 - 36 - fn poll(&self, wake: &'scope dyn Wake) -> Poll<T> { 37 - (&self.f)(wake) 38 - } 39 - }
-52
futures-util/src/wakers.rs
··· 1 - use std::cell::Cell; 2 - 3 - use futures_core::Wake; 4 - 5 - pub struct WakeStore<'scope> { 6 - parent: Cell<Option<&'scope dyn Wake<'scope>>>, 7 - ready: Cell<bool>, 8 - } 9 - 10 - impl<'scope> WakeStore<'scope> { 11 - pub fn new() -> Self { 12 - Self { 13 - parent: Option::None.into(), 14 - ready: true.into(), 15 - } 16 - } 17 - 18 - pub fn set_parent(&self, parent: &'scope dyn Wake<'scope>) { 19 - self.parent.replace(Some(parent)); 20 - } 21 - 22 - pub fn take_ready(&self) -> bool { 23 - self.ready.replace(false) 24 - } 25 - } 26 - 27 - impl<'scope> Wake<'scope> for WakeStore<'scope> { 28 - fn wake(&self) { 29 - self.ready.replace(true); 30 - if let Some(parent) = &self.parent.get() { 31 - parent.wake(); 32 - } 33 - } 34 - } 35 - 36 - pub struct FnWake<F: Fn()>(F); 37 - 38 - impl<'scope, F: Fn()> Wake<'scope> for FnWake<F> { 39 - fn wake(&self) { 40 - self.0() 41 - } 42 - } 43 - 44 - impl<'scope, F: Fn()> From<F> for FnWake<F> { 45 - fn from(value: F) -> Self { 46 - FnWake(value) 47 - } 48 - } 49 - 50 - pub fn noop_wake() -> FnWake<fn()> { 51 - FnWake(|| {}) 52 - }
+22
lifetime-guard/Cargo.toml
··· 1 + [package] 2 + name = "lifetime-guard" 3 + version.workspace = true 4 + authors.workspace = true 5 + edition.workspace = true 6 + rust-version.workspace = true 7 + description = "create weak/strong reference pairs to interior mutable data on the stack" 8 + homepage.workspace = true 9 + repository.workspace = true 10 + license.workspace = true 11 + keywords = [ "weak_ptr", "value_guard", "no_std" ] 12 + categories = [ "data-structures", "no-std::no-alloc" ] 13 + 14 + [features] 15 + default = ["atomics"] 16 + atomics = ["dep:critical-section"] 17 + 18 + [target.'cfg(loom)'.dependencies] 19 + loom = "0.7" 20 + 21 + [dependencies] 22 + critical-section = { version = "1.1", features = ["std"], optional = true }
+50
lifetime-guard/README.md
··· 1 + # Lifetime Guard 2 + 3 + `lifetime-guard` provides `ValueGuard` and `RefGuard` structs to allow for 4 + weak references to interior mutable values, similar to a singular pair of 5 + `Rc` and `Weak`, but without heap allocation. 6 + 7 + For parallelism, it provides `AtomicValueGuard` and `AtomicRefGuard` that 8 + implement `Send`. 9 + 10 + ## Example Usage 11 + 12 + ```rust 13 + use std::pin; 14 + use lifetime_guard::guard::*; 15 + 16 + let weak = pin::pin!(RefGuard::new()); 17 + { 18 + let strong = pin::pin!(ValueGuard::new(0)); 19 + weak.as_ref().register(strong.as_ref()); 20 + 21 + assert_eq!(strong.get(), 0); 22 + assert_eq!(weak.get(), Some(0)); 23 + 24 + strong.as_ref().set(1); 25 + assert_eq!(strong.get(), 1); 26 + assert_eq!(weak.get(), Some(1)); 27 + } 28 + assert_eq!(weak.get(), None); 29 + ``` 30 + 31 + # Safety 32 + 33 + You *may not* leak any instance of either `ValueGuard` or `RefGuard` to the 34 + stack using `mem::forget()` or any other mechanism that causes their 35 + contents to be overwritten without `Drop::drop()` running. 36 + Doing so creates unsoundness that likely will lead to dereferencing a null 37 + pointer. 38 + 39 + Doing so creates unsoundness that likely will lead to dereferencing a null 40 + pointer. See the 41 + [Forget marker trait](https://github.com/rust-lang/rfcs/pull/3782) rfc for 42 + progress on making interfaces that rely on not being leaked sound. 43 + 44 + Note that it is sound to leak `ValueGuard` and `RefGuard` to the heap using 45 + methods including `Box::leak()` because heap allocated data will never be 46 + overwritten if it is never freed. 47 + 48 + The test cases for this library have been verified to not exhibit undefined 49 + behavior using [miri](https://github.com/rust-lang/miri). 50 +
+255
lifetime-guard/src/atomic_guard.rs
··· 1 + use core::{cell::Cell, marker::PhantomPinned, pin::Pin, ptr::NonNull}; 2 + 3 + use critical_section::Mutex; 4 + 5 + struct RawValueGuard<T> { 6 + /// Contains the value being immutably accessed by `RefGuard` and 7 + /// mutably accessed by `Self` 8 + /// 9 + /// This needs to be a cell so that the original immutable alias 10 + /// to `Self` (given to `RefGuard`) can continue to be referenced after 11 + /// invalidated by the creation of a mutable alias for `Self::set`. 12 + data: Cell<T>, 13 + /// A pointer to a `RefGuard` with read access to `data` to invalidate that 14 + /// `RefGuard` when `Self` is dropped. 15 + ref_guard: Cell<Option<NonNull<AtomicRefGuard<T>>>>, 16 + } 17 + 18 + /// Strong guard for granting read access to a single interior mutable value to 19 + /// [`RefGuard`](RefGuard). 20 + /// 21 + /// A `ValueGuard`:`RefGuard` relationship is exclusive, and behaves similarly 22 + /// to a single `Rc` and `Weak` pair, but notably does not require heap 23 + /// allocation. 24 + /// 25 + /// # Safety 26 + /// 27 + /// This struct *must* not be leaked to the stack using `mem::forget` or any 28 + /// other mechanism that causes the contents of `Self` to be overwritten 29 + /// without `Drop::drop()` running. 30 + /// Doing so creates unsoundness that likely will lead to dereferencing a null 31 + /// pointer. 32 + /// 33 + /// Note that it is sound to leak `Self` to the heap using methods including 34 + /// `Box::leak()` because heap allocated data will never be overwritten if it 35 + /// is never freed. 36 + pub struct AtomicValueGuard<T> { 37 + /// Mutex is unfortunately necessary because the replace operation requires 38 + /// confirming if the ptr is valid, meaning it's a two instruction process 39 + /// and can't be done with atomic compare-and-swap instructions 40 + mutex: Mutex<RawValueGuard<T>>, 41 + _marker: PhantomPinned, 42 + } 43 + 44 + impl<T> AtomicValueGuard<T> { 45 + /// Creates a new `ValueGuard` containing `data`. 46 + #[inline] 47 + pub fn new(data: T) -> Self { 48 + Self { 49 + mutex: Mutex::new(RawValueGuard { 50 + data: Cell::new(data), 51 + ref_guard: Cell::new(None), 52 + }), 53 + _marker: PhantomPinned, 54 + } 55 + } 56 + 57 + /// Sets the internal value stored by `Self`. 58 + #[inline] 59 + pub fn set(&self, value: T) { 60 + critical_section::with(|cs| self.mutex.borrow(cs).data.set(value)); 61 + } 62 + 63 + #[inline] 64 + fn invalidate_ref_guard(&self) { 65 + critical_section::with(|cs| self.mutex.borrow(cs).ref_guard.set(None)); 66 + } 67 + 68 + #[inline] 69 + fn replace_ref_guard(&self, ref_guard: Option<NonNull<AtomicRefGuard<T>>>) { 70 + critical_section::with(|cs| { 71 + if let Some(guard) = 72 + self.mutex.borrow(cs).ref_guard.replace(ref_guard) 73 + { 74 + unsafe { (*guard.as_ptr()).invalidate_value_guard() } 75 + } 76 + }); 77 + } 78 + } 79 + 80 + impl<T: Copy> AtomicValueGuard<T> { 81 + /// Gets a copy of the value stored inside this `ValueGuard`. 82 + #[inline] 83 + pub fn get(&self) -> T { 84 + critical_section::with(|cs| self.mutex.borrow(cs).data.get()) 85 + } 86 + } 87 + 88 + impl<T> Drop for AtomicValueGuard<T> { 89 + #[inline] 90 + fn drop(&mut self) { 91 + self.replace_ref_guard(None); 92 + } 93 + } 94 + 95 + /// Weak guard for acquiring read only access to a `ValueGuard`'s value. 96 + /// 97 + /// Provides [`WeakGuard::register()`](Self::register) to register a `ValueGuard` 98 + /// to `Self` and vice versa. 99 + /// 100 + /// # Safety 101 + /// 102 + /// This struct *must* not be leaked to the stack using `mem::forget` or any 103 + /// other mechanism that causes the contents of `Self` to be overwritten 104 + /// without `Drop::drop()` running. 105 + /// Doing so creates unsoundness that likely will lead to dereferencing a null 106 + /// pointer. 107 + /// 108 + /// Note that it is sound to leak `Self` to the heap using methods including 109 + /// `Box::leak()` because heap allocated data will never be overwritten if it 110 + /// is never freed. 111 + pub struct AtomicRefGuard<T> { 112 + value_guard: Cell<Option<NonNull<AtomicValueGuard<T>>>>, 113 + _marker: PhantomPinned, 114 + } 115 + 116 + impl<T> AtomicRefGuard<T> { 117 + /// Creates a new `RefGuard` with no reference to a `ValueGuard`. 118 + #[inline] 119 + pub fn new() -> Self { 120 + Self { 121 + value_guard: Cell::new(None), 122 + _marker: PhantomPinned, 123 + } 124 + } 125 + 126 + #[inline] 127 + fn invalidate_value_guard(&self) { 128 + self.value_guard.set(None); 129 + } 130 + 131 + #[inline] 132 + fn replace_value_guard( 133 + &self, 134 + value_guard: Option<NonNull<AtomicValueGuard<T>>>, 135 + ) { 136 + if let Some(guard) = self.value_guard.replace(value_guard) { 137 + unsafe { (*guard.as_ptr()).invalidate_ref_guard() } 138 + } 139 + } 140 + 141 + /// Binds a pinned `value_guard` to `self`. 142 + /// 143 + /// This means they will reference each other, and will invalidate their 144 + /// references to each other when dropped. 145 + /// 146 + /// This method also invalidates the existing references held by the 147 + /// now-replaced referencees of `self` and `value_guard` to avoid 148 + /// dangling pointers. 149 + #[inline] 150 + pub fn register<'a>( 151 + self: Pin<&'a AtomicRefGuard<T>>, 152 + value_guard: Pin<&'a AtomicValueGuard<T>>, 153 + ) { 154 + value_guard.replace_ref_guard(Some(self.get_ref().into())); 155 + self.replace_value_guard(Some(value_guard.get_ref().into())); 156 + } 157 + } 158 + 159 + impl<T: Copy> AtomicRefGuard<T> { 160 + /// Gets a copy of the value stored inside the `ValueGuard` this `RefGuard` 161 + /// references. 162 + #[inline] 163 + pub fn get(&self) -> Option<T> { 164 + self.value_guard 165 + .get() 166 + .map(|guard| unsafe { (*guard.as_ptr()).get() }) 167 + } 168 + } 169 + 170 + impl<T> Drop for AtomicRefGuard<T> { 171 + #[inline] 172 + fn drop(&mut self) { 173 + self.replace_value_guard(None); 174 + } 175 + } 176 + 177 + impl<T> Default for AtomicRefGuard<T> { 178 + #[inline] 179 + fn default() -> Self { 180 + Self::new() 181 + } 182 + } 183 + 184 + #[cfg(test)] 185 + mod test { 186 + use core::{mem, pin}; 187 + 188 + extern crate alloc; 189 + 190 + use super::*; 191 + 192 + #[test] 193 + fn basic() { 194 + let weak = pin::pin!(AtomicRefGuard::new()); 195 + { 196 + let strong = pin::pin!(AtomicValueGuard::new(2)); 197 + weak.as_ref().register(strong.as_ref()); 198 + 199 + assert_eq!(strong.get(), 2); 200 + assert_eq!(weak.get(), Some(2)); 201 + 202 + strong.as_ref().set(3); 203 + assert_eq!(strong.get(), 3); 204 + assert_eq!(weak.get(), Some(3)); 205 + } 206 + 207 + assert_eq!(weak.get(), None); 208 + } 209 + 210 + #[test] 211 + fn multiple_registrations() { 212 + let weak1 = pin::pin!(AtomicRefGuard::new()); 213 + let weak2 = pin::pin!(AtomicRefGuard::new()); 214 + { 215 + let strong = pin::pin!(AtomicValueGuard::new(2)); 216 + weak1.as_ref().register(strong.as_ref()); 217 + 218 + assert_eq!(strong.get(), 2); 219 + assert_eq!(weak1.get(), Some(2)); 220 + 221 + strong.as_ref().set(3); 222 + assert_eq!(strong.get(), 3); 223 + assert_eq!(weak1.get(), Some(3)); 224 + 225 + // register next ptr, should invalidate previous weak ref (weak1) 226 + weak2.as_ref().register(strong.as_ref()); 227 + assert_eq!(weak1.get(), None); 228 + assert_eq!(weak1.value_guard.get(), None); 229 + 230 + assert_eq!(strong.get(), 3); 231 + assert_eq!(weak2.get(), Some(3)); 232 + 233 + strong.as_ref().set(4); 234 + assert_eq!(strong.get(), 4); 235 + assert_eq!(weak2.get(), Some(4)); 236 + } 237 + 238 + assert_eq!(weak1.get(), None); 239 + assert_eq!(weak2.get(), None); 240 + } 241 + 242 + #[test] 243 + #[cfg_attr(miri, ignore)] 244 + fn safe_leak() { 245 + let strong = alloc::boxed::Box::pin(AtomicValueGuard::new(10)); 246 + let weak = pin::pin!(AtomicRefGuard::new()); 247 + weak.as_ref().register(strong.as_ref()); 248 + 249 + // strong is now a ValueGuard on the heap that will never be freed 250 + // this is sound because it will never be overwritten 251 + mem::forget(strong); 252 + 253 + assert_eq!(weak.get(), Some(10)); 254 + } 255 + }
+237
lifetime-guard/src/guard.rs
··· 1 + use core::{cell::Cell, marker::PhantomPinned, pin::Pin, ptr::NonNull}; 2 + 3 + /// Strong guard for granting read access to a single interior mutable value to 4 + /// [`RefGuard`](RefGuard). 5 + /// 6 + /// A `ValueGuard`:`RefGuard` relationship is exclusive, and behaves similarly 7 + /// to a single `Rc` and `Weak` pair, but notably does not require heap 8 + /// allocation. 9 + /// 10 + /// # Safety 11 + /// 12 + /// This struct *must* not be leaked to the stack using `mem::forget` or any 13 + /// other mechanism that causes the contents of `Self` to be overwritten 14 + /// without `Drop::drop()` running. 15 + /// Doing so creates unsoundness that likely will lead to dereferencing a null 16 + /// pointer. 17 + /// 18 + /// Note that it is sound to leak `Self` to the heap using methods including 19 + /// `Box::leak()` because heap allocated data will never be overwritten if it 20 + /// is never freed. 21 + pub struct ValueGuard<T> { 22 + /// Contains the value being immutably accessed by `RefGuard` and 23 + /// mutably accessed by `Self` 24 + /// 25 + /// This needs to be a cell so that the original immutable alias 26 + /// to `Self` (given to `RefGuard`) can continue to be referenced after 27 + /// invalidated by the creation of a mutable alias for `Self::set`. 28 + data: Cell<T>, 29 + /// A pointer to a `RefGuard` with read access to `data` to invalidate that 30 + /// `RefGuard` when `Self` is dropped. 31 + ref_guard: Cell<Option<NonNull<RefGuard<T>>>>, 32 + _marker: PhantomPinned, 33 + } 34 + 35 + impl<T> ValueGuard<T> { 36 + /// Creates a new `ValueGuard` containing `data`. 37 + #[inline] 38 + pub fn new(data: T) -> Self { 39 + Self { 40 + data: Cell::new(data), 41 + ref_guard: Cell::new(None), 42 + _marker: PhantomPinned, 43 + } 44 + } 45 + 46 + /// Sets the internal value stored by `Self`. 47 + #[inline] 48 + pub fn set(&self, value: T) { 49 + self.data.set(value); 50 + } 51 + 52 + #[inline] 53 + fn invalidate_ref_guard(&self) { 54 + self.ref_guard.set(None); 55 + } 56 + 57 + #[inline] 58 + fn replace_ref_guard(&self, ref_guard: Option<NonNull<RefGuard<T>>>) { 59 + if let Some(guard) = self.ref_guard.replace(ref_guard) { 60 + unsafe { (*guard.as_ptr()).invalidate_value_guard() }; 61 + } 62 + } 63 + } 64 + 65 + impl<T: Copy> ValueGuard<T> { 66 + /// Gets a copy of the value stored inside this `ValueGuard`. 67 + #[inline] 68 + pub fn get(&self) -> T { 69 + self.data.get() 70 + } 71 + } 72 + 73 + impl<T> Drop for ValueGuard<T> { 74 + #[inline] 75 + fn drop(&mut self) { 76 + self.replace_ref_guard(None); 77 + } 78 + } 79 + 80 + /// Weak guard for acquiring read only access to a `ValueGuard`'s value. 81 + /// 82 + /// Provides [`WeakGuard::register()`](Self::register) to register a `ValueGuard` 83 + /// to `Self` and vice versa. 84 + /// 85 + /// # Safety 86 + /// 87 + /// This struct *must* not be leaked to the stack using `mem::forget` or any 88 + /// other mechanism that causes the contents of `Self` to be overwritten 89 + /// without `Drop::drop()` running. 90 + /// Doing so creates unsoundness that likely will lead to dereferencing a null 91 + /// pointer. 92 + /// 93 + /// Note that it is sound to leak `Self` to the heap using methods including 94 + /// `Box::leak()` because heap allocated data will never be overwritten if it 95 + /// is never freed. 96 + pub struct RefGuard<T> { 97 + value_guard: Cell<Option<NonNull<ValueGuard<T>>>>, 98 + _marker: PhantomPinned, 99 + } 100 + 101 + impl<T> RefGuard<T> { 102 + /// Creates a new `RefGuard` with no reference to a `ValueGuard`. 103 + #[inline] 104 + pub fn new() -> Self { 105 + Self { 106 + value_guard: Cell::new(None), 107 + _marker: PhantomPinned, 108 + } 109 + } 110 + 111 + #[inline] 112 + fn invalidate_value_guard(&self) { 113 + self.value_guard.set(None); 114 + } 115 + 116 + #[inline] 117 + fn replace_value_guard(&self, value_guard: Option<NonNull<ValueGuard<T>>>) { 118 + if let Some(guard) = self.value_guard.replace(value_guard) { 119 + unsafe { (*guard.as_ptr()).invalidate_ref_guard() } 120 + } 121 + } 122 + 123 + /// Binds a pinned `value_guard` to `self`. 124 + /// 125 + /// This means they will reference each other, and will invalidate their 126 + /// references to each other when dropped. 127 + /// 128 + /// This method also invalidates the existing references held by the 129 + /// now-replaced referencees of `self` and `value_guard` to avoid 130 + /// dangling pointers. 131 + #[inline] 132 + pub fn register<'a>( 133 + self: Pin<&'a RefGuard<T>>, 134 + value_guard: Pin<&'a ValueGuard<T>>, 135 + ) { 136 + value_guard.replace_ref_guard(Some(self.get_ref().into())); 137 + self.replace_value_guard(Some(value_guard.get_ref().into())); 138 + } 139 + } 140 + 141 + impl<T: Copy> RefGuard<T> { 142 + /// Gets a copy of the value stored inside the `ValueGuard` this `RefGuard` 143 + /// references. 144 + #[inline] 145 + pub fn get(&self) -> Option<T> { 146 + self.value_guard 147 + .get() 148 + .map(|guard| unsafe { (*guard.as_ptr()).get() }) 149 + } 150 + } 151 + 152 + impl<T> Drop for RefGuard<T> { 153 + #[inline] 154 + fn drop(&mut self) { 155 + self.replace_value_guard(None); 156 + } 157 + } 158 + 159 + impl<T: Copy> Default for RefGuard<T> { 160 + #[inline] 161 + fn default() -> Self { 162 + Self::new() 163 + } 164 + } 165 + 166 + #[cfg(test)] 167 + mod test { 168 + use core::{mem, pin}; 169 + 170 + extern crate alloc; 171 + 172 + use super::*; 173 + 174 + #[test] 175 + fn basic() { 176 + let weak = pin::pin!(RefGuard::new()); 177 + { 178 + let strong = pin::pin!(ValueGuard::new(2)); 179 + weak.as_ref().register(strong.as_ref()); 180 + 181 + assert_eq!(strong.get(), 2); 182 + assert_eq!(weak.get(), Some(2)); 183 + 184 + strong.as_ref().set(3); 185 + assert_eq!(strong.get(), 3); 186 + assert_eq!(weak.get(), Some(3)); 187 + } 188 + 189 + assert_eq!(weak.get(), None); 190 + } 191 + 192 + #[test] 193 + fn multiple_registrations() { 194 + let weak1 = pin::pin!(RefGuard::new()); 195 + let weak2 = pin::pin!(RefGuard::new()); 196 + { 197 + let strong = pin::pin!(ValueGuard::new(2)); 198 + weak1.as_ref().register(strong.as_ref()); 199 + 200 + assert_eq!(strong.get(), 2); 201 + assert_eq!(weak1.get(), Some(2)); 202 + 203 + strong.as_ref().set(3); 204 + assert_eq!(strong.get(), 3); 205 + assert_eq!(weak1.get(), Some(3)); 206 + 207 + // register next ptr, should invalidate previous weak ref (weak1) 208 + weak2.as_ref().register(strong.as_ref()); 209 + assert_eq!(weak1.get(), None); 210 + assert_eq!(weak1.value_guard.get(), None); 211 + 212 + assert_eq!(strong.get(), 3); 213 + assert_eq!(weak2.get(), Some(3)); 214 + 215 + strong.as_ref().set(4); 216 + assert_eq!(strong.get(), 4); 217 + assert_eq!(weak2.get(), Some(4)); 218 + } 219 + 220 + assert_eq!(weak1.get(), None); 221 + assert_eq!(weak2.get(), None); 222 + } 223 + 224 + #[test] 225 + #[cfg_attr(miri, ignore)] 226 + fn safe_leak() { 227 + let strong = alloc::boxed::Box::pin(ValueGuard::new(10)); 228 + let weak = pin::pin!(RefGuard::new()); 229 + weak.as_ref().register(strong.as_ref()); 230 + 231 + // strong is now a ValueGuard on the heap that will never be freed 232 + // this is sound because it will never be overwritten 233 + mem::forget(strong); 234 + 235 + assert_eq!(weak.get(), Some(10)); 236 + } 237 + }
+8
lifetime-guard/src/lib.rs
··· 1 + #![doc = include_str!("../README.md")] 2 + #![no_std] 3 + 4 + // pub mod base; 5 + pub mod guard; 6 + 7 + #[cfg(feature = "atomics")] 8 + pub mod atomic_guard;