├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── futures.bs └── notes ├── future_continuation_concept.cpp ├── lexicon.md ├── meetings ├── 2018_04_25__futures__face_to_face__planning__lee_bryce.md ├── 2018_04_26__futures__face_to_face__attendees.md ├── 2018_05_03__rapperswil_2018__pre_mailing_deadline__lee_bryce.md ├── 2018_06_20__futures__weekly_us.md ├── 2018_07_09__futures__biweekly_non_us.md ├── 2018_07_11__futures__weekly_us.md ├── 2018_07_13__executors__weekly.md ├── 2018_07_13__executors__weekly │ ├── receiver__ask_0.cpp │ ├── receiver__ask_1.cpp │ ├── receiver__ask_2.cpp │ ├── receiver__ask_3.cpp │ ├── receiver__ask_4.cpp │ ├── receiver__ask_5.cpp │ ├── receiver__ask_6.cpp │ └── receiver__intro.cpp ├── 2018_07_18__futures__weekly_us.md ├── 2018_07_20__executors__weekly.md ├── 2018_07_25__futures__weekly_us.md ├── 2018_07_27__executors__weekly.md └── 2018_08_01__futures__weekly_us.md ├── rapperswil_2018__pre_mailing_deadline__outline.md └── resources.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | install: 5 | - git clone https://github.com/tabatkins/bikeshed.git 6 | - pip install --editable $PWD/bikeshed 7 | - pip install docutils 8 | - bikeshed update 9 | script: 10 | - mkdir out 11 | - bikeshed spec futures.bs out/futures.html 12 | deploy: 13 | provider: pages 14 | skip-cleanup: true 15 | github-token: $GITHUB_TOKEN 16 | on: 17 | branch: master 18 | local-dir: out 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILDDIR = build 2 | 3 | all: remote 4 | 5 | setup: 6 | mkdir -p $(BUILDDIR)/ 7 | 8 | remote: setup 9 | find . -name "*.bs" -type f | sed 's/\.bs$$//' | xargs -I{} -t -n 1 sh -c "curl https://api.csswg.org/bikeshed/ -F force=1 -F file=@{}.bs > $(BUILDDIR)/\`basename {}\`.html" 10 | 11 | local: setup 12 | find . -name "*.bs" -type f | sed 's/\.bs$$//' | xargs -I{} -t -n 1 sh -c "bikeshed -f spec {}.bs $(BUILDDIR)/\`basename {}\`.html" 13 | 14 | clean: 15 | rm $(BUILDDIR)/* 16 | rmdir $(BUILDDIR) 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # futures 2 | A proposal for a futures programming model for ISO C++. 3 | 4 | Go [here](https://api.csswg.org/bikeshed/?force=1&url=https://raw.githubusercontent.com/executors/futures/master/futures.bs) to see a rendered copy of the current draft. 5 | -------------------------------------------------------------------------------- /futures.bs: -------------------------------------------------------------------------------- 1 |
   2 | Title: A Unified Futures Proposal for C++
   3 | Abstract: Proposed composable future concepts that interoperate with executors.
   4 | Shortname: P1054
   5 | URL: wg21.link/P1054R0
   6 | Revision: 0
   7 | Audience: SG1
   8 | Status: D
   9 | Group: WG21
  10 | Issue Tracking: GitHub https://github.com/executors/futures/issues
  11 | !Source: github.com/executors/futures/blob/master/futures.bs
  12 | No Abstract: yes
  13 | Markup Shorthands: markdown yes
  14 | Markup Shorthands: biblio yes
  15 | Editor: Lee Howes, Facebook, lwh@fb.com
  16 | Editor: Bryce Adelstein Lelbach, NVIDIA, brycelelbach@gmail.com
  17 | Editor: David S. Hollman, Sandia National Labs, dshollm@sandia.gov
  18 | Editor: Michał Dominiak, Nokia, griwes@griwes.info
  19 | 
20 | 21 | 22 | 23 | Contributors: 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
Marshall ClineCarter EdwardsJay Feldblum
Andrii GrynenkoJared HoberockHartmut Kaiser
Chris KohlhoffChris MysenEric Niebler
Sean ParentCory PerryFelix Petriconi
Kirk ShoopMathias Stearn
52 | 53 | # Introduction # {#intro} 54 | 55 | This paper introduces a hierarchy of concepts for future types that are designed to: 56 | 57 | * *Interoperate With Executors:* The concepts should require the functionality needed by [executors](https://wg21.link/P0443). 58 | * *Compose With Each Other:* The concepts should require the types to be composable. 59 | * *Stay Skinny:* The concepts should require absolutely nothing else so that it is not burdensome to write future types. 60 | 61 | There are five concepts introduces in this paper: 62 | 63 | * `FutureContinuation`, invocable objects that are called with the value or exception of a future as an argument. 64 | * `SemiFuture`, which can be bound to an executor, an operation which produces a `ContinuableFuture` (`f = sf.via(exec)`). 65 | * `ContinuableFuture`, which refines `SemiFuture` and instances can have one `FutureContinuation` attached to them (`f.then(c)`), which is executed on the future's associated executor when the future becomes ready. 66 | * `SharedFuture`, which refines `ContinuableFuture` and instances can have multiple `FutureContinuation`s attached to them. 67 | * `Promise`, each of which is associated with a future and make the future ready with either a value or an exception. 68 | 69 | Or, described another way: 70 | 71 | ``` 72 | template 73 | struct FutureContinuation 74 | { 75 | // At least one of these two overloads exists: 76 | auto operator()(T value); 77 | auto operator()(exception_arg_t, exception_ptr exception); 78 | }; 79 | 80 | template 81 | struct SemiFuture 82 | { 83 | template 84 | ContinuableFuture via(Executor&& exec) &&; 85 | }; 86 | 87 | template 88 | struct ContinuableFuture 89 | { 90 | template 91 | ContinuableFuture via(RExecutor&& exec) &&; 92 | 93 | template 94 | ContinuableFuture then(Continuation&& c) &&; 95 | }; 96 | 97 | template 98 | struct SharedFuture 99 | { 100 | template 101 | ContinuableFuture via(RExecutor&& exec); 102 | 103 | template 104 | SharedFuture then(Continuation&& c); 105 | }; 106 | 107 | template 108 | struct Promise 109 | { 110 | void set_value(T value) &&; 111 | 112 | template 113 | void set_exception(Error exception) &&; 114 | bool valid() const; 115 | }; 116 | ``` 117 | 118 | In the following sections, we describe some of the key aspects of our proposed 119 | design. 120 | 121 | ## The Future/Promise Execution Model ## {#intro_execution_model} 122 | 123 | In the Concurrency TS v1, it is unspecified where a `.then` 124 | continuation will be run. 125 | There are a number of possible answers: 126 | 127 | * Consumer Side: The consumer execution agent always executes the continuation. 128 | `.then` blocks until the producer execution agent signals readiness. 129 | * Producer Side: The producer execution agent always executes the continuation. 130 | `.set_value` blocks until the consumer execution agent signals readiness. 131 | * `inline_executor` Semantics: If the shared state is ready when the 132 | continuation is set, the consumer thread executes the continuation. If the 133 | shared state is not ready when the continuation is set, the producer thread 134 | executes the continuation. 135 | * `thread_executor` Semantics: A new `std::thread` executes the 136 | continuation. 137 | 138 | This is a source of trouble ([[P0701r0]] and [[P0701r1]]). 139 | The first two answers are undesirable, as they would require blocking, which is 140 | not ideal for an asynchronous interface. 141 | The third and fourth are likewise distasteful, as they can be vague or 142 | inefficient (respectively). 143 | 144 | Executors, finally, give us at least a partial solution to this problem. 145 | The question changes to "where do we enqueue work into the executor"? 146 | The answer: work is always enqueued on the consumer side as if, but not 147 | necessarily, via `then_execute`. 148 | You can query executor properties to determine whether or not the executor's 149 | APIs will block, which tells you whether or not continuation [=attachment=] 150 | (consumer side) or future [=fulfillment=] (producer side) potentially blocks 151 | pending execution (e.g. `inline_executor` semantics). 152 | 153 | ## Interactions with Executors ## {#intro_executors} 154 | 155 | [The executors proposal](https://github.com/executors/executors) defines a collection of executor types intended 156 | for use managing the execution of tasks on resources. 157 | There are three fundamental executor categories that cover directionality and 158 | control of launch: 159 | * one-way 160 | * two-way 161 | * then 162 | 163 | The first two could be considered *immediately launched*. That is that once 164 | handed to the executor, they may start immediately, assuming the internal 165 | executor policies and resources allow it. This makes them very useful for 166 | lazy-launch scenarios. 167 | 168 | Lazy launch scenarios are common in callback-based code, and in a wide range of 169 | future library implementations such as HPX 170 | and [folly](https://github.com/facebook/folly/tree/master/folly/futures). 171 | In these designs, a callback is executed on completion of some asynchronous 172 | work, and that callback enqueues work into the executor. 173 | This means that work is enqueued only after all dependencies are satisfied. 174 | 175 | Then-executors, on the other hand, are intended for explicitly deferred work. 176 | Work can be handed to the executor dependent on prior work, before that prior 177 | work is completed. 178 | This design is fundamentally different, but offers scope for optimization by 179 | the executor of chains of dependencies that it can batch, without running 180 | additional code on completion of each. 181 | 182 | The current executor design is intentionally generic - it makes few requirements 183 | on the future types it can use as input dependencies for the `then_execute` and 184 | `bulk_then_execute` operations. 185 | We can assume that for a future returned by a previous call to `then_execute` 186 | or `bulk_then_execute`, the executor understands the implementation of the 187 | future can can perform whatever dependence tracking and optimization necessary. 188 | This then is an implementation detail. 189 | 190 | However, there will also be interactions where a task to run on one executor is dependent on one produced by another. For this to be practical, we need a standardised mechanism to tie the two executors together. 191 | This amounts to a standard API for triggering deferred work. 192 | 193 | To solve this we provide two things: 194 | * A promise concept, that allows setting of value and/or exception. 195 | * A mechanism to retrieve from an executor a pair of a promise and a future, such that the future is a valid input dependence for a call to `then_execute` or `bulk_then_execute`. 196 | 197 | The promise is a write-only concept. This simplifies the definition and improves flexibility. 198 | 199 | The future is not a full future in the sense of future concepts. It is merely a token that completes when the promise is satisfied. This means that it is useful only for connecting to `then_execute` or `bulk_then_execute` on the executor that provided the result. 200 | 201 | ## The Exception Handling Model ## {#intro_exception_model} 202 | 203 | Our proposal moves away from future continuations that take futures as an 204 | argument, the design used in the Concurrency TS v1. 205 | Instead, continuations take the value type of the future. 206 | This, however, requires a new exception handling model. 207 | Both the executors proposal and this paper adopt the same model, where users 208 | can provide callable objects with both a value type and an optional tag 209 | disambiguated exception handling overload. 210 | 211 | In the example below, let `f` and `g` be `ContinuableFuture`s and let `c` be a `FutureContinuation`. 212 | 213 | ``` 214 | g = f.then(c); 215 | ``` 216 | 217 | When `f` is fulfilled: 218 | 219 | * If `f` has a value `val`: 220 | * If `c(val)` is well-formed, it is called and the future `g` is fulfilled with the result or any exception thrown from the evaluation. 221 | * Otherwise, the future `g` is fulfilled with the value `val` (this is known as future value propagation - see [[P0701r0]] and [[P0701r1]]). 222 | * If `f` fails with exception `exc`: 223 | * If `c(exception_arg, exc)` is well-formed, it is called and the future `g` is fulfilled with the result or any exception thrown from the evaluation. 224 | * Otherwise, the future `g` is fulfilled with the exception `exc` (this is known as future exception propagation - see [[P0701r0]] and [[P0701r1]]). 225 | 226 | Note that if both overloads are defined, `decltype(G(exception_arg, err))` shall be convertible to `decltype(G(val))`. 227 | 228 | This paper defines some helper types which can be used to build callable 229 | arguments that meet the `FutureContinuation` arguments. 230 | For example, `on_value_or_error(f, g)` takes two callables (a on-value 231 | continuation and on-error continuation) and returns a single 232 | `FutureContinuation` object composed from the two. 233 | 234 | ## The Future Cancellation Model ## {#intro_cancellation} 235 | 236 | This paper proposes a way to hook a [=cancellation notifier=] into a future/promise pair, using `cancellable_promise_contract_t`. 237 | It allows for authors of future types to support cancellation if they want to, and to correctly hook into the cancellation mechanisms 238 | of other futures they interoperate with - such as inside `.via` and `.then`, where the implementation can create a future/promise pair that will, 239 | on cancellation, invoke a function that will cancel the current future. 240 | 241 | A sample implementation of `.via` and the corresponding `.then` could be as 242 | follows (assuming immediately ready futures to simplify the data model): 243 | 244 | ``` 245 | template 246 | class simple_ready_continuable_future { 247 | template 248 | auto via(NewExecutor && ex) && 249 | { 250 | // Convert this future to a new future carrying the passed executor 251 | return simple_ready_continuable_future{ 252 | std::move(data_), std::forward(ex)}; 253 | } 254 | 255 | template 256 | auto then(F&& func) { 257 | // Create the cancellation callback 258 | auto cancel = [this] { handle_cancellation(); }; 259 | // Construct the promise/future pair with the cancellation callback 260 | // so that the executor can propagate cancellation 261 | auto [promise, contract_future] = 262 | execution::query(ex, cancellable_promise_contract_t{ cancel }); 263 | // Create the dependent task and the new future representing it 264 | auto taskFuture = 265 | executor.then_execute(std::forward(func), std::move(contract_future)); 266 | // Satisfy the promise immediately because this is a ready future 267 | promise.set_value(std::move(data_)); 268 | 269 | return taskFuture; 270 | } 271 | 272 | private: 273 | T data_; 274 | Executor exec_; 275 | }; 276 | ``` 277 | 278 | We are currently not proposing a concrete cancellation API; we plan to do that in a future revision of the paper. At this time, there is 279 | no guarantee that a future honors a [=cancellation notifier=], and no requirements as to when it actually invokes it. 280 | 281 | ## `SemiFuture` and `.via` For Composition ## {#intro_semi_future_via} 282 | 283 | The `SemiFuture` concept and `.via` mechanism ([[P0783r0]] and [[P0904r0]]) gives 284 | us control of the transfer of execution ownership between executors and a way 285 | to convert between different future types. 286 | 287 | One problem that arises in the [executor model](https://github.com/executors/executors) is how 288 | a [=future=]-returning interface can dictate the executor that callers attach 289 | continuations to. 290 | 291 | `SemiFuture` and `.via` is mechanism that allows the caller to control the 292 | executor that subsequent chaining operations use. 293 | An interface may return a handle to its work, a future in the most abstract sense, 294 | that does not provide a means to chain more work. 295 | This future will complete on whatever executor that interface was using. 296 | This returned future satisfies the `SemiFuture` concept, and the caller is hence 297 | aware that to make use of it they must attach an executor, using `.via`, to 298 | transition from the interface's executor to the caller's executor. 299 | From that point on, the caller can use the future as necessary, safely enqueuing 300 | work onto a known executor, and protecting the interface from misuse. 301 | 302 | Additionally, since `ContinuableFuture` and `SharedFuture`s are also `SemiFuture`s, 303 | `.via` provides a way to convert (if possible) from one future type 304 | (associated with a particular executor type) to another "foreign" future type 305 | (associated with another executor type). 306 | 307 | As a simple example: 308 | 309 | ``` 310 | std::execution::semi_future async_api() { 311 | std::execution::continuable_future = 312 | doAsyncWork(); 313 | return std::move(f).then( 314 | [](SerializedDataType&& val){ return deserialize(val); }); 315 | } 316 | 317 | void caller() { 318 | LocalExecutor e; 319 | auto sf = async_api(); 320 | auto f = std::move(sf).via(e); 321 | std::move(f).then([](DataType&& data) { 322 | std::cout << "Name: " << data.name() << "\n"; 323 | }); 324 | } 325 | ``` 326 | 327 | There is a strict separation of control here between `caller` and `async_api`. 328 | 329 | ## Consuming Interfaces Are Rvalue Reference Qualified ## {#intro_rvalue_ref} 330 | 331 | In a change to the model used in `std::future`, where `std::future::get()` is a 332 | consuming operation but is l-value qualified, consuming operations in this 333 | proposal are r-value qualified. 334 | 335 | For free functions this should be obvious. If we had a free function 336 | `future_then` that takes a future and returns a value, we will likely r-value 337 | qualify it: 338 | ``` 339 | std::future future_then(std::future&& f, continuation&& c); 340 | ``` 341 | 342 | For consistent and safe use, the same should apply to the equivalent builtin 343 | methods. 344 | 345 | In chaining code, this falls out cleanly: 346 | ``` 347 | auto f2 = do_async_thing().then([](T val){return val;}).then([](T2 val){return val;}); 348 | ``` 349 | 350 | Occasionally we must be explicit: 351 | ``` 352 | auto f = do_async_thing(); 353 | auto f2 = std::move(f).then([](T val){return val;}).then([](T2 val){return val;}); 354 | ``` 355 | 356 | but this is a minor inconvenience given that it allows the tooling to warn on 357 | use-after-move and related misuses. 358 | 359 | ## Blocking Interfaces (`.get` and `.wait`) ## {#intro_blocking} 360 | 361 | The future concepts in this paper are intended to express the minimal requirements for executors interoperation and composition. 362 | Executors do not require a blocking interface, and futures do not require a blocking interface to compose. 363 | This paper proposes the addition of the free functions `std::this_thread::future_wait` and `std::this_thread::future_get`. 364 | These functions will block on any `SemiFuture` type, and are suitable for use within `std::thread` execution agents. 365 | Some individuals may feel that blocking interfaces are more fundamental for futures than continuation chaining interfaces - some may desire to not provide continuation chaining interfaces at all. 366 | We believe that this is a perfectly valid design; however, such non-continuable futures are outside of the scope of this work. 367 | 368 | * If you want to write a future that only has blocking interfaces and doesn't support continuations, that's fine! But that type of future cannot interoperate with executors. 369 | * If you want to write a future that only doesn't have blocking interfaces and does support continuations, that's fine! That type of future can interoperate with executors, so you should conform to the proposed concepts. 370 | * If you want to write a future that has blocking interfaces and supports continuations, that's fine! That type of future can interoperate with executors, so you should conform to the proposed concepts. 371 | 372 | [[P0701r0]] and [[P0701r1]] discuss some of the challenges relating to future/promise synchronization that motivated the current design. 373 | 374 | ## Future Work ## {#intro_future_work} 375 | 376 | There is plenty of future work on futures to be done. 377 | Here is a list of topics that are currently on our agenda: 378 | 379 | * Forward progress guarantees for futures and promises. 380 | * Requirements on synchronization for use of futures and promises from non-concurrent execution agents. 381 | * `std::future`/`std::promise` interoperability. 382 | * Future unwrapping, both `future>` and more advanced forms. 383 | * `when_all`/`when_any`/`when_n`. 384 | * `async`. 385 | 386 | ## Prior Work ## {#intro_prior_work} 387 | 388 | This work is unification of prior papers written individually by the authors: 389 | 390 | * [[P0701r0]] 391 | * [[P0701r1]] 392 | * [[P0783r0]] 393 | * [[P0904r0]] 394 | 395 | # Proposed New Wording # {#wording} 396 | 397 | The following wording is purely additive to [the executors proposal](https://github.com/executors/executors). 398 | 399 | ## The Future/Promise Execution Model ## {#wording_execution_model} 400 | 401 | 402 | 403 | 1. A future is an object that executes, on a executor, continuations that are passed an [=asynchronous result=] as a parameter. 404 | 405 | * A uniquely owned future moves from the [=asynchronous result=] and can have at most one [=continuation=] attached. 406 | * A non-uniquely owned future copies from the [=asynchronous result=] and can have one or more [=continuations=] attached. 407 | 408 | 2. A promise is an object that produces an [=asynchronous result=] and is associated with a [=future=]. [ *Note:* Although a [=promise=] is associated with one [=future=], 409 | multiple [=shared future=] objects may refer to the same underlying [=shared future=]. - *end note* ] 410 | 411 | 3. The status of a [=future=]: 412 | 413 | * Is either ready, not ready, or invalid. 414 | * Shall be [=ready=] if and only if the [=future=] holds a value or an exception ready for retrieval. 415 | 416 | 4. An asynchronous result is either a value or an exception. The result is created by the [=promise=] and accessed by the [=future=]. 417 | 418 | 5. A [=future=] may optionally have a cancellation notifier, which is a nullary callable object. 419 | 420 | 6. When a [=future=] is cancelled: 421 | 422 | * If the [=future=] has a [=cancellation notifier=], it is called in the calling thread. 423 | * Then, the [=future's=] status is set to [=invalid=]. 424 | 425 | 7. When a [=continuation=] is attached to a [=future=]: 426 | 427 | * First, the [=status=] of its [=future=] is checked. 428 | * Then, 429 | * If the [=status=] is [=ready=] or [=not ready=]: 430 | * The [=continuation=] is enqueued for execution pending readiness of [=future=] on the [=future's=] executor, with the [=asynchronous result=] as its parameter. 431 | * Finally, if the [=future=] is a [=uniquely owned future=], the status is set to [=invalid=]. 432 | * If the [=status=] is [=invalid=], the behavior is unspecified. 433 | 434 | 8. When a [=promise=] fulfills its associated [=future=] with a value or error, if the lifetime of the [=future=] has not ended: 435 | 436 | * First, the [=status=] of its [=future=] is checked. 437 | * Then, 438 | * If the [=status=] is [=not ready=]: 439 | * The [=status=] is set to [=ready=] 440 | * Finally, any execution agents that are blocking until the [=status=] is [=ready=] are unblocked. 441 | * If the [=status=] is [=ready=] or [=invalid=], the behavior is unspecified. 442 | 443 | If the lifetime of the [=future=] has ended, [=fulfillment=] has no effect. 444 | 445 | 9. [=Fulfillment=] of a [=future=] synchronizes with execution of [=continuations=] that were enqueued for execution by that [=future=]. 446 | 447 | 10. The lifetime of the [=continuations=] and the [=asynchronous result=] passed to the [=continuations=] shall last until execution of the [=continuations=] completes. 448 | [ *Note:* The [=future=] may move (if it is a [=uniquely owned future=]) or copy (if it is a [=non-uniquely owned future=]) from the result into a new object to ensure this behavior. - *End Note* ] 449 | [ *Note:* The [=future=] may move from [=continuations=] into new object to ensure this behavior. - *End Note* ] 450 | 451 | 11. The lifetime of the [=continuations=] and the [=asynchronous result=] passed to the [=continuations=] shall last until execution of the [=continuations=] completes. 452 | [ *Note:* The [=promise=] may move or copy (if the result is `CopyConstructible`) from the result into a new object to ensure this behavior. - *End Note* ] 453 | [ *Note:* The [=promise=] may move from continuations into new object to ensure this behavior. - *End Note* ] 454 | 455 | 12. Upon destruction of a [=promise=]: 456 | 457 | * First, the [=promise=] checks the [=status=] of its [=future=]. 458 | * Then, if the [=status=] is [=not ready=], the [=promise=] shall be [=fulfilled=]. 459 | 460 | 13. Setting the [=status=] of a [=future=] synchronizes with operations that check the [=future=]'s status. 461 | 462 | 14. Operations that modify the set of continuations stored in a [=future=] synchronize with each other. 463 | 464 | 15. Successful [=fulfillment=] of a [=future=] synchronizes with [=attachment=] of [=continuations=] to that future. 465 | 466 | 16. Successful [=attachment=] of a [=continuation=] to a [=future=] synchronizes with [=fulfillment=] of the promise associated with that [=future=]. 467 | 468 | 471 | 472 | 17. If a [=future=] has a [=cancellation notifier=], successful [=fulfillment=] of it's associated [=promise=] synchronizes with [=cancellation=] of the [=future=]. 473 | 474 | ## `FutureContinuation` Requirements ## {#wording_future_continuation} 475 | 476 | 477 | 478 | ``` 479 | namespace std { 480 | namespace execution { 481 | 482 | struct exception_arg_t { explicit exception_arg_t() = default; }; 483 | 484 | inline constexpr exception_arg_t exception_arg{}; 485 | 486 | template 487 | struct is_future_value_continuation; 488 | 489 | template 490 | inline constexpr bool is_future_value_continuation_v 491 | = is_future_value_continuation::value; 492 | 493 | template 494 | struct is_future_exception_continuation; 495 | 496 | template 497 | inline constexpr bool is_future_exception_continuation_v 498 | = is_future_exception_continuation::value; 499 | 500 | template 501 | struct is_future_continuation; 502 | 503 | template 504 | inline constexpr bool is_future_continuation_v 505 | = is_future_continuation::value; 506 | 507 | }} 508 | ``` 509 | 510 | 1. A future continuation is a callable object that consumes the value of a [=future=]. 511 | 512 | 513 | 2. The struct `exception_arg_t` is an empty structure type used as a unique type to disambiguate `FutureContinuation` overloads that are called when a [=future=] holds an exception from `FutureContinuation` overloads that are called when a [=future=] holds a value. 514 | 515 | 516 | 3. This sub-clause contains templates that may be used to determine at compile time whether a type meets the requirements of [=future continuations=] for a particular [=future=] value type. Each of these templates is a `BinaryTypeTrait` with a base characteristic of `true_type` if the corresponding condition is true, and `false_type` otherwise. 517 | 4. A `FutureContinuation` type shall meet the `MoveConstructible` requirements and the requirements described in the Tables below. 518 | 519 |
520 | 521 | **Type Property Queries** 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 536 | 537 | 538 | 539 | 540 | 546 | 547 | 548 | 549 | 550 | 556 | 557 | 558 | 559 |
TemplateConditionPreconditions
531 | ``` 532 | template 533 | struct is_future_value_continuation; 534 | ``` 535 | `is_move_constructible_v && is_invocable_v``T` is a complete type.
541 | ``` 542 | template 543 | struct is_future_exception_continuation; 544 | ``` 545 | `is_move_constructible_v && is_invocable_v``T` is a complete type.
551 | ``` 552 | template 553 | struct is_future_continuation; 554 | ``` 555 | `is_future_value_continuation_v || is_future_exception_continuation_v``T` is a complete type.
560 | 561 |
562 | 563 | ## `Promise` Requirements ## {#wording_promise} 564 | 565 | ``` 566 | namespace std { 567 | namespace execution { 568 | 569 | template struct promise_value; 570 | template 571 | using promise_value_t = promise_value::type; 572 | 573 | }} 574 | ``` 575 | 576 | 1. A `Promise` type for value type `T` shall meet the `MoveConstructible` requirements, the `MoveAssignable` requirements, and the requirements described in the Tables below. 577 | 578 | Descriptive Variable Definitions 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 |
VariableDefinition
`T` 588 | Either: 589 | * Any (possibly cv-qualified) object type that is not an array, or 590 | * (possibly cv-qualified) `void`. 591 |
ta value of a type contextually convertible to `T`
`e`a value of type contextually convertible to `exception_ptr`
`P`A [=promise=] type for value type `T`
`p`An rvalue of type `P`
610 | 611 |
612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 624 | 625 | 626 | 627 | 628 | 633 | 634 | 635 | 636 | 637 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 652 | 653 | 654 | 655 |
ExpressionReturn TypeOperational semantics
`promise_value_t>``T` 623 |
`p.set_value(t)``void` 629 | * *Requires:* `!is_void_v` 630 | * *Effects:* Completes the promise and associated future with `t`. 631 | 632 |
`p.set_value()``void` 638 | * *Requires:* `is_void_v` 639 | * *Effects:* Completes the promise and associated future. 640 | 641 |
`p.set_exception(e)``void`Completes the promise and associated future with the error `e`.
650 | `p.valid()` 651 | Contextually convertible to `bool`.`true` if the promise has an associated future that is incomplete, `false` otherwise.
656 | 657 | 658 | ### Promise Contract Executor Properties ### {#wording_promise_contracts} 659 | 660 | ```c++ 661 | template 662 | struct promise_contract_t 663 | { 664 | static constexpr bool is_requirable = false; 665 | static constexpr bool is_preferable = false; 666 | 667 | using polymorphic_query_result_type 668 | = std::pair, std::execution::semi_future>; 669 | }; 670 | 671 | template 672 | inline constexpr promise_contract = promise_contract_t{}; 673 | ``` 674 | 675 | The `promise_contract_t` property can be used only with query. 676 | 677 | The result of a query of the `promise_contract_t` property applied to a 678 | `ThenExecutor` or `BulkThenExecutor` is a `std::pair` consisting of a `Promise` 679 | and an implementation-defined token type that will be interpreted as a valid 680 | input future by calls to `then_execute` or `bulk_then_execute` and that is 681 | satisfied by calling `set_value` or `set_exception` on the promise. 682 | 683 | The value returned from `execution::query(e, promise_contract_t)`, where `e` 684 | is an executor and `T` is a type, should be unique for any given call. 685 | When `e` is a `ThenExecutor` or `BulkThenExecutor` the result of the query is a 686 | `std::pair` where first value is an instance of a type matching the `Promise` 687 | requirements and the second is a token type that `e` will interpret as a valid 688 | future parameter to calls to `then_execute` or `bulk_then_execute`. 689 | 690 | ``` 691 | template 692 | struct cancellable_promise_contract_t 693 | { 694 | static constexpr bool is_requirable = false; 695 | static constexpr bool is_preferable = false; 696 | 697 | using polymorphic_query_result_type 698 | = std::pair, std::execution::semi_future>; 699 | 700 | template 701 | static constexpr decltype(auto) static_query_v 702 | = Executor::query(promise_contract_t()); 703 | 704 | template 705 | friend std::pair, std::execution::semi_future> 706 | query(const Executor& ex, const cancellable_promise_contract_t&); 707 | 708 | CancellationNotifier cancellation_notifier; 709 | }; 710 | ``` 711 | 712 | The `cancellable_promise_contract_t` property can be used only with query. 713 | `cancellable_promise_contract_t` differs from `promise_contract_t` in that the 714 | query carries a cancellation callback, `cancellation_notifier`, that will be 715 | called as `std::invoke(cancellation_notifier)` by the `ThenExecutor` on 716 | cancellation of the task dependent on the future resulting from the 717 | `cancellable_promise_contract_t` query. 718 | 719 | The result of a query of the `cancellable_promise_contract_t` property applied 720 | to a `ThenExecutor` or `BulkThenExecutor` is a `std::pair` consisting of a 721 | `Promise` and an implementation-defined token type that will be interpreted as 722 | a valid input future by calls to `then_execute` or `bulk_then_execute`, that 723 | is satisfied by calling `set_value` or `set_exception` on the promise and that 724 | supports cancellation by the executor. 725 | 726 | The value returned from 727 | `execution::query(e, cancellable_promise_contract_t{cancellation_notifier})`, 728 | where `e` is an executor and `T` is a type, should be unique for any given call. 729 | When `e` is a `ThenExecutor` or `BulkThenExecutor` the result of the query is a 730 | `std::pair` where first value is an instance of a type matching the `Promise` 731 | requirements and the second is a token type that `e` will interpret as a valid 732 | future parameter to calls to `then_execute` or `bulk_then_execute`. 733 | 734 | ``` 735 | template 736 | friend std::pair, std::execution::semi_future> 737 | query(const Executor& ex, const cancellable_promise_contract_t&); 738 | ``` 739 | * *Returns:* A `std::pair` where the first value is a `std::execution::promise` and the 740 | second is a `std::execution::semi_future` such that the future was retrieved 741 | from the promise. 742 | 743 | * *Remarks:* This function shall not participate in overload resolution unless 744 | `executor_future_t` is `std::execution::semi_future`. 745 | 746 | 747 | ## `SemiFuture` Requirements ## {#wording_semi_future} 748 | 749 | 750 | 751 | ``` 752 | namespace std { 753 | namespace execution { 754 | 755 | template struct future_value; 756 | template 757 | using future_value_t = future_value::type; 758 | 759 | template struct future_exception; 760 | template 761 | using future_exception_t = future_exception::type; 762 | }} 763 | ``` 764 | 765 | 1. A semi future is an object that can be bound to an executor to produce a [=future=]. 766 | 767 | 2. A `SemiFuture` type for type `T` shall meet the `MoveConstructible` requirements, the `MoveAssignable` requirements, and the requirements described in the Tables below. 768 | 769 |
770 | 771 | **Descriptive Variable Definitions** 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 813 | 814 |
VariableDefinition
`T` 781 | Either: 782 | * Any (possibly cv-qualified) object type that is not an array, or 783 | * (possibly cv-qualified) `void`. 784 |
`SF`A `SemiFuture` type for value type `T`.
`sf`An rvalue of type `SF`.
`E` 797 | An executor type, either: 798 | * A `ThenExecutor` such that `execution::can_query_v> == true`, or 799 | * A `OneWayExecutor` otherwise. 800 |
`e`A value of type `E`.
`CF` 809 | A `ContinuableFuture` type for value type `T` and executor type `E`, either: 810 | * `decltype(execution::query(e, promise_contract_t()).second)` if `E` is a `ThenExecutor` and `execution::can_query_v> == true`, or 811 | * `continuable_future` otherwise. 812 |
815 | 816 | `SemiFuture` Requirements 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 846 | 847 |
ExpressionReturn TypeOperational Semantics
`future_value_t>``T`
`future_exception_t>`Implicitly convertible to `exception_ptr`.
`sf.via(e)`*implementation-defined* 838 | **Returns:** A `ContinuableFuture` for value type `T` that is bound to the executor `e` and will be made ready with the value or exception of `sf` when `sf` is made ready. 839 | 840 | 841 | 842 | **Throws:** If `!execution::query(e, promise_contract_t).second`, throws any exception thrown by `!execution::query(e, oneway_t)`. 843 | 844 | **Postconditions:** For a returned future `f`, `f.get_executor()` returns an executor that compares equal to `e`. 845 |
848 | 849 |
850 | 851 | ## `ContinuableFuture` Requirements ## {#wording_continuable_future} 852 | 853 | 854 | 855 | 1. A continuable future is a [=future=] that is bound to an executor and can have continuations attached to it. 856 | 857 | 2. A `ContinuableFuture` shall meet the `SemiFuture` requirements and the requirements described in the Tables below. 858 | 859 |
860 | 861 | **Descriptive Variable Definitions** 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 940 | 941 | 942 | 943 | 946 | 947 |
VariableDefinition
`E`An executor type.
`EI`An executor type.
`e`A value of type `E`.
`ei`A value of type `EI`.
`T` 887 | Either: 888 | * Any (possibly cv-qualified) object type that is not an array, or 889 | * (possibly cv-qualified) `void`. 890 |
`CF`A `ContinuableFuture` type for value type `T` and executor type `E`.
`cf`A value of type `CF`.
`rcf`An rvalue of type `CF`.
`val`The value contained within the successfully completed future `rcf`.
`ex`The exception contained within the exceptionally completed future `rcf`.
`G`Any type such that `is_future_continuation_v == true`.
`g`An object of type `G`.
`R` 922 | 923 | Either: 924 | * If `is_same_v, void> == true` and `invoke(g)` is well formed, `decltype(g())`. 925 | * If `is_same_v, void> != true` and `invoke(g, val)` is well formed, `decltype(g(val))`. 926 | * Otherwise, `T`. 927 |
`SF`A `SemiFuture` type for value type `T`.
`NORMAL` 936 | Either: 937 | * If `is_same_v, void> == true`, `DECAY_COPY(std::forward(g))()`, and 938 | * Otherwise, `DECAY_COPY(std::forward(g))(std::move(val))`. 939 |
`EXCEPTIONAL` 944 | The expression `DECAY_COPY(std::forward(g))(exception_arg, std::move(ex))`. 945 |
948 | 949 | `ContinuableFuture` Requirements 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 963 | 964 | 965 | 966 | 967 | 1010 | 1011 |
ExpressionReturn TypeOperational Semantics
`cf.get_executor()``E` 961 | **Returns:** A copy of the executor that the future is bound to. 962 |
`rcf.then(g)``CF` 968 | **Returns:** A `ContinuableFuture` that is bound to the executor `e` and 969 | that wraps the type returned by execution of either the value or exception 970 | operations implemented in the continuation. 971 | 972 | **Effects:** 973 | When `rcf` becomes nonexceptionally ready, and if `NORMAL` is a 974 | well-formed expression, creates an execution agent which invokes `NORMAL` 975 | at most once, with the call to `DECAY_COPY` being evaluated in the thread 976 | that called `.then`. 977 | 978 | Otherwise, when `rcf` becomes exceptionally ready, if `EXCEPTIONAL` is a 979 | well-formed expression, creates an execution agent which invokes 980 | `EXCEPTIONAL` at most once, with the call to `DECAY_COPY` being evaluated 981 | in the thread that called `.then`. 982 | 983 | If `NORMAL` and `EXCEPTIONAL` are both well-formed expressions, 984 | `decltype(EXCEPTIONAL)` shall be convertible to `R`. 985 | 986 | If `NORMAL` is not a well-formed expression and `EXCEPTIONAL` is a 987 | well-formed expression, `decltype(EXCEPTIONAL)` shall be convertible to 988 | `decltype(val)`. 989 | 990 | If neither `NORMAL` nor `EXCEPTIONAL` are well-formed expressions, the 991 | invocation of `.then` shall be ill-formed. 992 | 993 | May block pending completion of `NORMAL` or `EXCEPTIONAL`. 994 | 995 | The invocation of `.then` synchronizes with 996 | (C++Std [intro.multithread]) the invocation of `g`. 997 | 998 | [=Fulfills=] the `ContinuableFuture` with the result of the `NORMAL` or 999 | `EXCEPTIONAL` expression, or any exception thrown by either. 1000 | Otherwise, [=fulfills=] the `ContinuableFuture` with either `val` or `e`. 1001 | 1002 | 1003 | 1004 | **Synchronization:** The destruction of the continuation that generates 1005 | `rcf`'s value synchronizes with the invocation of `g` and with the destruction 1006 | of `g`. 1007 | 1008 | **Postconditions:** For a returned future `f`, `f.get_executor()` returns an executor that compares equal to that returned by `rcf.get_executor()` before the call to `.then`. 1009 |
1012 | 1013 |
1014 | 1015 | ## `SharedFuture` Requirements ## {#wording_shared_future} 1016 | 1017 | 1018 | 1019 | 1. A shared future is a [=non-uniquely owned future=] that is copyable, is bound to an executor and that allows one or more continuation to be attached to it. 1020 | 1021 | 2. A `SharedFuture` shall meet the `ContinuableFuture` requirements, the `CopyConstructible` requirements, the `CopyAssignable` requirements and the requirements described in the Tables below. 1022 | 1023 |
1024 | 1025 | Descriptive Variable Definitions 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1070 | 1071 | 1072 | 1073 | 1077 | 1078 |
VariableDefinition
`E`An executor type.
`EI`An executor type.
`e`A value of type `E`.
`ei`A value of type `EI`.
`T`Any (possibly cv-qualified) object type that is not an array.
`CF`A `ContinuableFuture` type for executor type `E` and value type `T`.
`SHF`A `SharedFuture` type for executor type `E` and value type `T`.
`shf`A value of type `SHF`.
`NORMAL` 1067 | The expression `DECAY_COPY(std::forward(g))(val)` if 1068 | `T` is non-void and `DECAY_COPY(std::forward(g))()` if `T` is void. 1069 |
`EXCEPTIONAL` 1074 | The expression 1075 | `DECAY_COPY(std::forward(g))(exception_arg, ex)`, 1076 |
1079 | 1080 | `SharedFuture` Requirements 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1100 | 1139 | 1140 | 1141 | 1142 | 1143 | 1158 | 1159 |
ExpressionReturn TypeOperational Semantics
`shf.then(g)` 1091 | **If** `T` is non-void and `INVOKE(declval(), declval())` or if `T` is 1092 | `void` and `INVOKE(declval())` is well-formed: 1093 | 1094 | `CF(), declval()))>` 1095 | 1096 | **Otherwise**: 1097 | 1098 | `CF` 1099 | 1101 | **Returns:** A `ContinuableFuture` that is bound to the executor `e` and 1102 | that wraps the type returned by execution of either the value or exception 1103 | operations implemented in the continuation. 1104 | 1105 | **Effects:** 1106 | When `shf` becomes nonexceptionally ready, and if `NORMAL` is a 1107 | well-formed expression, creates an execution agent which invokes `NORMAL` 1108 | at most once, with the call to `DECAY_COPY` being evaluated in the thread 1109 | that called `.then`. 1110 | 1111 | Otherwise, when `shf` becomes exceptionally ready, if `EXCEPTIONAL` is a 1112 | well-formed expression, creates an execution agent which invokes 1113 | `EXCEPTIONAL` at most once, with the call to `DECAY_COPY` being evaluated 1114 | in the thread that called `.then`. 1115 | 1116 | If `NORMAL` and `EXCEPTIONAL` are both well-formed expressions, 1117 | `decltype(EXCEPTIONAL)` shall be convertible to `R`. 1118 | 1119 | If `NORMAL` is not a well-formed expression and `EXCEPTIONAL` is a 1120 | well-formed expression, `decltype(EXCEPTIONAL)` shall be convertible to 1121 | `decltype(val)`. 1122 | 1123 | If neither `NORMAL` nor `EXCEPTIONAL` are well-formed expressions, the 1124 | invocation of `.then` shall be ill-formed. 1125 | 1126 | May block pending completion of `NORMAL` or `EXCEPTIONAL`. 1127 | 1128 | The invocation of `.then` synchronizes with 1129 | (C++Std [intro.multithread]) the invocation of `g`. 1130 | 1131 | [=Fulfills=] the `ContinuableFuture` with the result of the `NORMAL` or 1132 | `EXCEPTIONAL` expression, or any exception thrown by either. 1133 | Otherwise, [=fulfills=] the `ContinuableFuture` with either `val` or `e`. 1134 | 1135 | **Postconditions:** 1136 | * Has no observable affect on `sfh`. 1137 | * For a returned future `f`, `f.get_executor()` returns an executor that compares equal tothat returned by `rcf.get_executor()` before the call to `.then`. 1138 |
`shf.via(e)`Implementation-defined 1144 | **Returns:** A `ContinuableFuture` for type `T` that is bound to the executor `e`. 1145 | 1146 | **Effect:** Returns an implementation-defined `ContinuableFuture` onto which continuations can be attached that will run on `e`. 1147 | 1148 | **Success:** Succeeds if: 1149 | * `e` is a `ThenExecutor` where `execution::query(e, promise_contract_t{})` or `execution::query(e, cancellable_promise_contract_t{ cancel })` is well-formed for some function `cancel`. 1150 | * `e` is a `OnewayExecutor` or is convertible to a OnewayExecutor. 1151 | 1152 | Fails at compile-time otherwise. 1153 | 1154 | **Postconditions:** 1155 | * Has no observable affect on `sfh`. 1156 | * For a returned future `f`, `f.get_executor()` returns an executor that compares equal to `e`. 1157 |
1160 | 1161 |
1162 | 1163 | ## `std::execution::promise` ## {#wording_erasing_promise} 1164 | 1165 | ```c++ 1166 | template 1167 | class promise { 1168 | public: 1169 | using value_type = T; 1170 | 1171 | promise() noexcept; 1172 | promise(promise&&) noexcept; 1173 | template 1174 | explicit promise(Promise&& p); 1175 | 1176 | void set_value(/* see below */) &&; 1177 | 1178 | template 1179 | void set_exception(Error&& err) &&; 1180 | 1181 | bool valid() const noexcept; 1182 | explicit operator bool() const noexcept; 1183 | }; 1184 | ``` 1185 | 1186 | A `promise` refers to a [=promise=] and is associated with a [=future=], either through type-erasure or through construction of an underlying [=promise=] with an overload of `make_promise_contract()`. 1187 | 1188 |
1189 | 1190 | ``` 1191 | promise() noexcept; 1192 | ``` 1193 | 1194 | * *Effects:* Constructs a `promise` object that does not refer to a [=promise=] 1195 | 1196 | * *Postconditions:* 1197 | * `valid() == false`. 1198 | 1199 |
1200 | 1201 | ``` 1202 | promise(promise&& rhs) noexcept; 1203 | ``` 1204 | 1205 | * *Effects:* Move constructs a `promise` object from `rhs` that refers 1206 | to the same [=promise=] and is associated with the same [=future=] 1207 | (if `rhs` refers to a [=promise=]). 1208 | 1209 | * *Postconditions:* 1210 | * `valid()` returns the same value as `rhs.valid()` prior to the constructor 1211 | invocation. 1212 | * `rhs.valid() == false`. 1213 | 1214 |
1215 | 1216 | ``` 1217 | template 1218 | promise(Promise&& rhs) noexcept; 1219 | ``` 1220 | * *Requires:* 1221 | - `Promise` meets the requirements for `Promise` 1222 | - `!is_same_v, promise>` 1223 | - `!is_reference_v` *Note:* This constrains the parameter to be an rvalue reference rather than a forwarding reference *—end note*] 1224 | 1225 | * *Effects:* Constructs a `promise` object that refers to the [=promise=] `rhs` and is associated with the same [=future=] as `rhs`. 1226 | (if `rhs` is associated with a [=future=]). 1227 | 1228 | * *Postconditions:* 1229 | * `valid()` returns the same value as `rhs.valid()` prior to the constructor 1230 | invocation. 1231 | 1232 |
1233 | 1234 | 1235 | ``` 1236 | void promise::set_value(const T& val) &&; 1237 | void promise::set_value(T&& val) &&; 1238 | void promise::set_value() &&; 1239 | ``` 1240 | * *Effects:* 1241 | 1242 | - If `valid() == true`, 1243 | - For `promise::set_value(const T& val) &&`: equivalent to calling `set_value(val)` on the [=promise=] that `*this` refers to. 1244 | - For `promise::set_value(T&& val) &&`: equivalent to calling `set_value(std::move(val))` on the [=promise=] that `*this` refers to. 1245 | - For `promise::set_value() &&`: equivalent to calling `set_value()` on the [=promise=] that `*this` refers to. 1246 | - Otherwise, throws 1247 | 1248 | * *Throws:* `future_error` with error condition `no_state` if `valid() == false` 1249 | 1250 | * *Postconditions:* 1251 | - `valid() == false` 1252 | 1253 | * *Notes:* `promise::set_value(const T& val) &&` does not participate in overload resolution unless `is_copy_constructible_v>` 1254 | 1255 |
1256 | 1257 | ``` 1258 | template 1259 | void set_exception(Error&& err) &&; 1260 | ``` 1261 | * *Requires:* 1262 | - `Error` is contextually convertible to `exception_ptr` 1263 | 1264 | * *Effects:* 1265 | - If `valid() == true`, equivalent to calling `set_exception(exception_ptr{forward(err)})` on the [=promise=] that `*this` refers to. 1266 | - Otherwise, throws 1267 | 1268 | * *Throws:* `future_error` with error condition `no_state` if `valid() == false` 1269 | 1270 | * *Postconditions:* 1271 | - `valid() == false` 1272 | 1273 |
1274 | 1275 | ``` 1276 | bool valid() const noexcept; 1277 | explicit operator bool() const noexcept; 1278 | ``` 1279 | 1280 | * *Returns:* `true` if `*this` refers to a [=promise=] and the referenced promise is `valid()`, or `false` otherwise 1281 | 1282 | ## `std::execution::semi_future` ## {#wording_erasing_semi_future} 1283 | 1284 | ``` 1285 | template 1286 | class semi_future { 1287 | public: 1288 | using value_type = T; 1289 | 1290 | semi_future(semi_future&&) = default; 1291 | semi_future(const semi_future&) = delete; 1292 | 1293 | semi_future& operator=(const semi_future&) = delete; 1294 | semi_future& operator=(semi_future&&) = default; 1295 | 1296 | template 1297 | explicit semi_future(continuable_future&&); 1298 | 1299 | template 1300 | explicit semi_future(shared_future&&); 1301 | 1302 | template 1303 | continuable_future via(EI) &&; 1304 | }; 1305 | ``` 1306 | 1307 |
1308 | ``` 1309 | continuable_future(semi_future&& rhs); 1310 | ``` 1311 | 1312 | * *Effects:* Move constructs a future object from `rhs` that refers to the 1313 | same [=future=] and its associated with the same [=promise=] (if `rhs` 1314 | refers to a [=future=]). 1315 | 1316 | 1317 | * *Postconditions:* 1318 | * valid() returns the same value as rhs.valid() prior to the constructor 1319 | invocation. 1320 | * rhs.valid() == false. 1321 | 1322 |
1323 | ``` 1324 | semi_future(const semi_future& rhs); 1325 | ``` 1326 | 1327 | * *Effects:* Copy constructs a future object from `rhs` that refers to the 1328 | same [=future=] and its associated with the same [=promise=] (if `rhs` 1329 | refers to a [=future=]). 1330 | 1331 | * *Postconditions:* valid() returns the same value as rhs.valid() prior to the 1332 | constructor invocation. The validity of rhs does not change. 1333 | 1334 |
1335 | ``` 1336 | template 1337 | semi_future(continuable_future&& rhs); 1338 | ``` 1339 | 1340 | * *Effects:* Move constructs a future object from `rhs` that refers to the 1341 | same [=future=] and its associated with the same [=promise=] (if `rhs` 1342 | refers to a [=future=]). 1343 | 1344 | * *Postconditions:* 1345 | * valid() returns the same value as rhs.valid() prior to the constructor invocation. 1346 | * rhs.valid() == false. 1347 | 1348 | 1349 |
1350 | ``` 1351 | template 1352 | explicit semi_future(shared_future&& rhs); 1353 | 1354 | template 1355 | explicit semi_future(const shared_future& rhs); 1356 | ``` 1357 | 1358 | * *Effects:* Move constructs a future object from `rhs` that refers to the 1359 | same [=future=] and its associated with the same [=promise=] (if `rhs` 1360 | refers to a [=future=]). 1361 | 1362 | * *Postconditions:* 1363 | * valid() returns the same value as rhs.valid() prior to the constructor 1364 | invocation. 1365 | * rhs.valid() == false. 1366 | 1367 |
1368 | ``` 1369 | continuable_future via(EI ei) &&; 1370 | ``` 1371 | 1372 | * *Effects:* Returns a new future that will complete when this completes but 1373 | on which continuations will be enqueued to a new executor. 1374 | 1375 | * *Returns:* A continuable_future modified to carry executor ei of 1376 | type EI. 1377 | 1378 | * *Requires:* 1379 | * `ei` is a `ThenExecutor` where `execution::query(ei, promise_contract_t{})` or `execution::query(ei, cancellable_promise_contract_t{ cancel })` is well-formed for some function `cancel`. 1380 | * `ei` is a `OnewayExecutor` or is convertible to a OnewayExecutor. 1381 | 1382 | * *Postconditions:* valid() == false. 1383 | 1384 |
1385 | ``` 1386 | bool valid() const noexcept; 1387 | ``` 1388 | 1389 | * *Returns:* Returns true if this is a valid future. False otherwise. 1390 | 1391 | ## `std::execution::continuable_future` ## {#wording_erasing_continuable_future} 1392 | 1393 | ``` 1394 | template 1395 | class continuable_future { 1396 | public: 1397 | using value_type = T; 1398 | using executor_type = Ex; 1399 | using semi_future_type = semi_future; 1400 | 1401 | continuable_future(const continuable_future&) = delete; 1402 | continuable_future(continuable_future&&) = default; 1403 | 1404 | continuable_future& operator=(const continuable_future&) = delete; 1405 | continuable_future& operator=(continuable_future&&) = default; 1406 | 1407 | template 1408 | explicit continuable_future(shared_future&&); 1409 | template 1410 | explicit continuable_future(const shared_future&); 1411 | 1412 | template 1413 | ReturnFuture then(FutureContinuation&&) &&; 1414 | 1415 | template 1416 | continuable_future via(EI) &&; 1417 | 1418 | E get_executor() const; 1419 | semi_future semi() &&; 1420 | shared_future share() &&; 1421 | 1422 | bool valid() const 1423 | } 1424 | }; 1425 | ``` 1426 | 1427 |
1428 | ``` 1429 | continuable_future(continuable_future&& rhs); 1430 | ``` 1431 | 1432 | * *Effects:* Move constructs a future object from `rhs` that refers to the 1433 | same [=future=] and its associated with the same [=promise=] (if `rhs` 1434 | refers to a [=future=]). 1435 | 1436 | * *Postconditions:* 1437 | * valid() returns the same value as rhs.valid() prior to the constructor 1438 | invocation. 1439 | * rhs.valid() == false. 1440 | 1441 |
1442 | ``` 1443 | continuable_future(const continuable_future& rhs); 1444 | ``` 1445 | 1446 | * *Effects:* Copy constructs a future object from `rhs` that refers to the 1447 | same [=future=] and its associated with the same [=promise=] (if `rhs` 1448 | refers to a [=future=]). 1449 | 1450 | * *Postconditions:* valid() returns the same value as rhs.valid() prior to the 1451 | constructor invocation. The validity of rhs does not change. 1452 | 1453 |
1454 | ``` 1455 | template 1456 | explicit continuable_future(shared_future&& rhs); 1457 | ``` 1458 | 1459 | * *Effects:* Move constructs a future object from `rhs` that refers to the 1460 | same [=future=] and its associated with the same [=promise=] (if `rhs` 1461 | refers to a [=future=]). 1462 | 1463 | * *Postconditions:* 1464 | * valid() returns the same value as rhs.valid() prior to the constructor 1465 | invocation. 1466 | * rhs.valid() == false. 1467 | 1468 |
1469 | ``` 1470 | template 1471 | explicit continuable_future(const shared_future& rhs); 1472 | ``` 1473 | 1474 | * *Effects:* Copy constructs a future object from `rhs` that refers to the 1475 | same [=future=] and its associated with the same [=promise=] (if `rhs` 1476 | refers to a [=future=]). 1477 | 1478 | * *Postconditions:* valid() returns the same value as rhs.valid() prior to the 1479 | constructor invocation. The validity of rhs does not change. 1480 | 1481 |
1482 | ``` 1483 | template 1484 | ReturnFuture then(F&&) &&; 1485 | ``` 1486 | 1487 | * *Requires:* 1488 | F satisfies the requirements of FutureContinuation. 1489 | 1490 | * *Returns:* A `ContinuableFuture` that is bound to the executor `e` and 1491 | that wraps the type returned by execution of either the value or exception 1492 | operations implemented in the continuation. The type of the returned 1493 | `ContinuableFuture` is defined by `Executor` `E`. 1494 | 1495 | * *Effects:* 1496 | * For `NORMAL` defined as the expression `DECAY_COPY(std::forward(g))(val)` if 1497 | `T` is non-void and `DECAY_COPY(std::forward(g))()` if `T` is void. 1498 | 1499 | * For `EXCEPTIONAL` defined as the expression 1500 | `DECAY_COPY(std::forward(f))(exception_arg, ex)`, 1501 | 1502 | * When `*this` becomes nonexceptionally ready, and if `NORMAL` is a 1503 | well-formed expression, creates an execution agent which invokes `NORMAL` 1504 | at most once, with the call to `DECAY_COPY` being evaluated in the thread 1505 | that called `.then`. 1506 | 1507 | * Otherwise, when `*this` becomes exceptionally ready, if `EXCEPTIONAL` is a 1508 | well-formed expression, creates an execution agent which invokes 1509 | `EXCEPTIONAL` at most once, with the call to `DECAY_COPY` being evaluated 1510 | in the thread that called `.then`. 1511 | 1512 | * If `NORMAL` and `EXCEPTIONAL` are both well-formed expressions, 1513 | `decltype(EXCEPTIONAL)` shall be convertible to `R`. 1514 | 1515 | * If `NORMAL` is not a well-formed expression and `EXCEPTIONAL` is a 1516 | well-formed expression, `decltype(EXCEPTIONAL)` shall be convertible to 1517 | `decltype(val)`. 1518 | 1519 | * If neither `NORMAL` nor `EXCEPTIONAL` are well-formed expressions, the 1520 | invocation of `.then` shall be ill-formed. 1521 | 1522 | * May block pending completion of `NORMAL` or `EXCEPTIONAL`. 1523 | 1524 | * The invocation of `.then` synchronizes with 1525 | (C++Std [intro.multithread]) the invocation of `f`. 1526 | 1527 | * Stores the result of either the `NORMAL` or `EXCEPTIONAL` expression, or 1528 | any exception thrown by either, in the associated shared state of the 1529 | resulting `ContinuableFuture`. Otherwise, stores either `val` or `e` in 1530 | the associated shared state of the resulting `ContinuableFuture`. 1531 | 1532 | * *Postconditions:* valid() == false. 1533 | 1534 |
1535 | ``` 1536 | continuable_future via(EI ex) &&; 1537 | ``` 1538 | 1539 | * *Effects:* Returns a new future that will complete when this completes but 1540 | on which continuations will be enqueued to a new executor. 1541 | 1542 | * *Returns:* A continuable_future modified to carry executor ex of 1543 | type EI. 1544 | 1545 | * *Requires:* 1546 | * `e` is a `ThenExecutor` where `execution::query(ex, promise_contract_t{})` or `execution::query(ex, cancellable_promise_contract_t{ cancel })` is well-formed for some function `cancel`. 1547 | * `e` is a `OnewayExecutor` or is convertible to a OnewayExecutor. 1548 | 1549 | * *Postconditions:* valid() == false. 1550 | 1551 |
1552 | ``` 1553 | E get_executor() const; 1554 | ``` 1555 | * *Returns:* If is_valid() is true, returns the executor contained within the 1556 | future. Otherwise throws std::future_error. 1557 | 1558 |
1559 | ``` 1560 | semi_future semi() &&; 1561 | ``` 1562 | * *Returns:* 1563 | Returns a semi_future of the same value type as \*this and that 1564 | completes when this completes, but that erases the executor. is_valid() on the 1565 | returned semi_future will return the same value as is_valid() on 1566 | \*this. 1567 | 1568 |
1569 | ``` 1570 | bool valid() const noexcept; 1571 | ``` 1572 | 1573 | * *Returns:* Returns true if this is a valid future. False otherwise. 1574 | 1575 |
1576 | ``` 1577 | shared_future share() &&; 1578 | ``` 1579 | 1580 | * *Returns:* shared_future(std::move(\*this)). 1581 | 1582 | * *Postconditions:* valid() == false. 1583 | 1584 | ## `std::execution::shared_future` ## {#wording_erasing_shared_future} 1585 | 1586 | ``` 1587 | namespace std::execution { 1588 | template 1589 | class shared_future { 1590 | public: 1591 | using value_type = T; 1592 | using executor_type = Ex; 1593 | using semi_future_type = semi_future; 1594 | 1595 | shared_future(const shared_future&) = default; 1596 | shared_future(shared_future&&) = default; 1597 | 1598 | shared_future& operator=(const shared_future&) = default 1599 | shared_future& operator=(shared_future&&) = default; 1600 | 1601 | template 1602 | explicit shared_future(continuable_future&&); 1603 | 1604 | template 1605 | ReturnFuture then(FutureContinuation&&); 1606 | 1607 | template 1608 | shared_future via(EI); 1609 | 1610 | E get_executor() const; 1611 | semi_future semi(); 1612 | 1613 | bool valid() const; 1614 | } 1615 | }; 1616 | } 1617 | ``` 1618 | 1619 |
1620 | ``` 1621 | shared_future(shared_future&& rhs); 1622 | ``` 1623 | 1624 | * *Effects:* Move constructs a future object that refers to the shared state that 1625 | was originally referred to by rhs (if any). 1626 | 1627 | * *Postconditions:* 1628 | * valid() returns the same value as rhs.valid() prior to the constructor invocation. 1629 | * rhs.valid() == false. 1630 | 1631 |
1632 | ``` 1633 | shared_future(const shared_future& rhs); 1634 | ``` 1635 | 1636 | * *Effects:* Copy constructs a future object that refers to the shared state that was originally referred to by rhs (if any). 1637 | 1638 | * *Postconditions:* valid() returns the same value as rhs.valid() prior to the constructor invocation. The validity of rhs does not change. 1639 | 1640 |
1641 | ``` 1642 | explicit shared_future(continuable_future&& rhs); 1643 | ``` 1644 | 1645 | * *Effects:* Move constructs a future object that refers to the shared state that 1646 | was originally referred to by rhs (if any). 1647 | 1648 | * *Postconditions:* 1649 | * valid() returns the same value as rhs.valid() prior to the constructor invocation. 1650 | * rhs.valid() == false. 1651 | 1652 |
1653 | ``` 1654 | template 1655 | ReturnFuture then(F&&); 1656 | ``` 1657 | 1658 | * *Requires:* F satisfies the requirements of FutureContinuation. 1659 | 1660 | * *Returns:* A `ContinuableFuture` that is bound to the executor `e` and 1661 | that wraps the type returned by execution of either the value or exception 1662 | operations implemented in the continuation. The type of the returned 1663 | `ContinuableFuture` is defined by `Executor` `E`. 1664 | 1665 | * *Effects:* 1666 | * For `NORMAL` defined as the expression `DECAY_COPY(std::forward(g))(val)` if 1667 | `T` is non-void and `DECAY_COPY(std::forward(g))()` if `T` is void. 1668 | 1669 | * For `EXCEPTIONAL` defined as the expression 1670 | `DECAY_COPY(std::forward(f))(exception_arg, ex)`, 1671 | 1672 | * When `*this` becomes nonexceptionally ready, and if `NORMAL` is a 1673 | well-formed expression, creates an execution agent which invokes `NORMAL` 1674 | at most once, with the call to `DECAY_COPY` being evaluated in the thread 1675 | that called `.then`. 1676 | 1677 | * Otherwise, when `*this` becomes exceptionally ready, if `EXCEPTIONAL` is a 1678 | well-formed expression, creates an execution agent which invokes 1679 | `EXCEPTIONAL` at most once, with the call to `DECAY_COPY` being evaluated 1680 | in the thread that called `.then`. 1681 | 1682 | * If `NORMAL` and `EXCEPTIONAL` are both well-formed expressions, 1683 | `decltype(EXCEPTIONAL)` shall be convertible to `R`. 1684 | 1685 | * If `NORMAL` is not a well-formed expression and `EXCEPTIONAL` is a 1686 | well-formed expression, `decltype(EXCEPTIONAL)` shall be convertible to 1687 | `decltype(val)`. 1688 | 1689 | * If neither `NORMAL` nor `EXCEPTIONAL` are well-formed expressions, the 1690 | invocation of `.then` shall be ill-formed. 1691 | 1692 | * May block pending completion of `NORMAL` or `EXCEPTIONAL`. 1693 | 1694 | * The invocation of `.then` synchronizes with 1695 | (C++Std [intro.multithread]) the invocation of `f`. 1696 | 1697 | * Stores the result of either the `NORMAL` or `EXCEPTIONAL` expression, or 1698 | any exception thrown by either, in the associated shared state of the 1699 | resulting `ContinuableFuture`. Otherwise, stores either `val` or `e` in 1700 | the associated shared state of the resulting `ContinuableFuture`. 1701 | 1702 | * *Postconditions:* No observable change to \*this. 1703 | 1704 | 1705 |
1706 | ``` 1707 | shared_future via(EI ei); 1708 | ``` 1709 | 1710 | * *Effects:* Returns a new future that will complete when this completes but 1711 | on which continuations will be enqueued to a new executor. 1712 | 1713 | * *Returns:* A shared_future modified to carry executor ei of type EI. 1714 | 1715 | * *Requires:* 1716 | * `ei` is a `ThenExecutor` where `execution::query(ei, promise_contract_t{})` or `execution::query(ei, cancellable_promise_contract_t{ cancel })` is well-formed for some function `cancel`. 1717 | * `ei` is a `OnewayExecutor` or is convertible to a OnewayExecutor. 1718 | 1719 | * *Postconditions:* No observable change to \*this. 1720 | 1721 |
1722 | ``` 1723 | E get_executor() const; 1724 | ``` 1725 | * *Returns:* If is_valid() is true, returns the executor contained within the 1726 | future. Otherwise throws std::future_error. 1727 | 1728 |
1729 | ``` 1730 | semi_future semi(); 1731 | ``` 1732 | * *Returns:* 1733 | Returns a semi_future of the same value type as \*this and that 1734 | completes when this completes, but that erases the executor. is_valid() on the 1735 | returned semi_future will return the same value as is_valid() on 1736 | \*this. 1737 | 1738 |
1739 | ``` 1740 | bool valid() const noexcept; 1741 | ``` 1742 | * *Returns:* Returns true if this is a valid future. False otherwise. 1743 | 1744 | 1745 | ## `std::execution::make_promise_contract` ## {#wording_make_promise_contract} 1746 | 1747 | ```c++ 1748 | template 1749 | /* see below */ 1750 | make_promise_contract(const Executor& ex) 1751 | requires execution::is_then_executor_v && execution::can_query_v> 1752 | ``` 1753 | 1754 | * *Effects:* equivalent to `execution::query(ex, promise_contract_t{})` 1755 | * *Returns:* same as `execution::query(ex, promise_contract_t{})` 1756 | 1757 |
1758 | 1759 | ```c++ 1760 | template 1761 | pair, continuable_future> 1762 | make_promise_contract(const Executor& ex) 1763 | requires execution::is_one_way_executor_v 1764 | ``` 1765 | 1766 | * *Returns:* A pair of: 1767 | * a `promise` that refers to a [=promise=] that is associated with the [=future=] in the pair 1768 | * a `continuable_future` that is associated with the [=promise=] and is bound to `ex`. 1769 | 1770 |
1771 | 1772 | ```c++ 1773 | template 1774 | pair, semi_future> 1775 | make_promise_contract() 1776 | ``` 1777 | 1778 | * *Returns:* A pair of: 1779 | * a `promise` that refers to a [=promise=] that is associated with the [=future=] in the pair 1780 | * a `semi_future` that is associated with the [=promise=]. 1781 | 1782 | ## Generic Future Blocking Functions ## {#wording_blocking_functions} 1783 | 1784 | In [**thread.syn**] and [**thread.thread.this**] add: 1785 | 1786 | ``` 1787 | namespace std::this_thread { 1788 | 1789 | template 1790 | void future_wait(Future& f) noexcept; 1791 | 1792 | template 1793 | future_value_t> future_get(Future&& f); 1794 | 1795 | } 1796 | ``` 1797 | 1798 | In [**thread.thread.this**] add: 1799 | 1800 | ``` 1801 | template 1802 | void future_wait(Future& f) noexcept; 1803 | ``` 1804 | 1805 | * *Requires:* `Future` meets the requirements of `SemiFuture`. 1806 | 1807 | * *Effects:* Blocks the calling thread until `f` becomes ready. 1808 | 1809 | * *Synchronization:* The destruction of the continuation that [=fulfills=] 1810 | `f`'s synchronizes with 1811 | `future_wait` calls blocking until `f` becomes ready. 1812 | 1813 | ``` 1814 | template 1815 | future_value_t> future_get(Future&& f); 1816 | ``` 1817 | 1818 | * *Requires:* 1819 | * `decay_t` shall meet the `SemiFuture` requirements. 1820 | * `!is_reference_v` 1821 | [ *Note:* 1822 | This constrains the parameter to be an rvalue reference rather than a forwarding reference. 1823 | — *end note* ] 1824 | 1825 | * *Effects:* 1826 | * Blocks the calling thread until `f` becomes ready. 1827 | * Retrieves the [=asynchronous result=]. 1828 | 1829 | * *Returns:* 1830 | * If `f` becomes [=ready=] with a value, moves the value from `f` and returns it 1831 | to the caller. 1832 | 1833 | * *Postconditions:* `f` is [=invalid=]. 1834 | 1835 | * *Synchronization:* The destruction of the continuation that generates `f`'s 1836 | value synchronizes with 1837 | `future_get` calls blocking until `f` becomes ready. 1838 | 1839 | * *Throws:* If `f` becomes [=ready=] with an exception, that exception is rethrown. 1840 | 1841 | ## `FutureContinuation` Helper Functions ## {#wording_future_continuation_helper_functions} 1842 | 1843 | ``` 1844 | namespace std::execution { 1845 | 1846 | template 1847 | /* see below */ on_value(F&& f); 1848 | 1849 | template 1850 | /* see below */ on_error(F&& f); 1851 | 1852 | template 1853 | /* see below */ on_value_or_error(F&& f, G&& g); 1854 | 1855 | } 1856 | ``` 1857 | 1858 | ``` 1859 | template 1860 | /* see below */ on_value(F&& f); 1861 | ``` 1862 | 1863 | **Requires:** 1864 | * For any (possibly cv-qualified) object type `T`, 1865 | `invoke(declval(), declval())` shall be well-formed. 1866 | * `F` shall meet the `MoveConstructible` requirements. 1867 | 1868 | **Returns:** 1869 | A `FutureContinuation` object, `fc`, of implementation-defined type such that: 1870 | 1871 | * `fc(t)` is well-formed for objects `t` of type `T` and has the same effects as 1872 | `invoke(ff, t)`, where `ff` is an instance of `F` move-constructed from 1873 | `forward(f)`. 1874 | The object `ff` is constructed before the return of `on_value()` and 1875 | destroyed when `fc` is destroyed. 1876 | * `fc(exception_arg, exception_ptr{})` is ill-formed. 1877 | 1878 | 1879 | 1880 | ``` 1881 | template 1882 | /* see below */ on_error(F&& f); 1883 | ``` 1884 | 1885 | **Requires:** 1886 | * `invoke(declval(), declval())` shall be well-formed. 1887 | * `F` shall meet the `MoveConstructible` requirements. 1888 | 1889 | **Returns:** 1890 | A `FutureContinuation` object, `fc`, of implementation-defined type such that: 1891 | * `fc(exception_tag, e)` is well-formed for objects `e` of type `exception_ptr` and 1892 | has the same effects as `invoke(ff, e)`, where `ff` is an object of type 1893 | `F` move-constructed from `forward(f)`. 1894 | The object `ff` is constructed before the return of `on_error` and 1895 | destroyed when `fc` is destroyed. 1896 | * `fc(t)` is ill-formed for any type `T`. 1897 | 1898 | 1922 | 1923 | 1924 | 1925 | ``` 1926 | template 1927 | /* see below */ on_value_or_error(F&& f, G&& g); 1928 | ``` 1929 | 1930 | **Requires:** 1931 | 1932 | * For any (possibly cv-qualified) object type `T`, 1933 | `invoke(declval(), declval())` and 1934 | `invoke(declval(), declval())` shall be well-formed. 1935 | * `F` and `G` shall meet the `MoveConstructible` requirements. 1936 | 1937 | **Returns:** 1938 | A `FutureContinuation` object, `fc`, of implementation-defined type such that: 1939 | * `fc(t)` is well-formed for objects `t` of type `T` and has the same effects 1940 | as `invoke(ff, t)`, where `ff` is an object of type `F` move-constructed 1941 | from `forward(f)`. 1942 | The object `ff` is constructed before the return of `on_value_or_error` 1943 | and destroyed when `fc` is destroyed. 1944 | * `fc(exception_tag, e)` is well-formed for objects `e` of type `exception_ptr` and 1945 | has the same effects as `invoke(gg, e)`, where `gg` is an object of type of 1946 | `G` move-constructed from `forward(g)`. 1947 | The object `gg` is constructed before the return of `on_value_or_error` and 1948 | destroyed when `fc` is destroyed. 1949 | 1950 | ## Proposed Modifications to Executors ## {#wording_p0443} 1951 | 1952 | ### `Future` Requirements ### {#wording_p0443_future} 1953 | 1954 | Remove this section. 1955 | 1956 | ### `TwoWayExecutor` Requirements ### {#wording_p0443_two_way_executor} 1957 | 1958 | In the *Return Type* column: 1959 | 1960 | **Replace:** 1961 | 1962 |
1963 | A type that satisfies the `Future` requirements for the value type `R`. 1964 |
1965 | 1966 | **With:** 1967 | 1968 |
1969 | A type that satisfies the `ContinuableFuture` requirements for the value type `R`. 1970 |
1971 | 1972 | 1973 | In the *Operational Semantics* column: 1974 | 1975 | **Replace:** 1976 | 1977 |
1978 | in the associated shared state of the resulting `Future`. 1979 |
1980 | 1981 | **With:** 1982 | 1983 |
1984 | in the resulting `ContinuableFuture`. 1985 |
1986 | 1987 | ### `ThenExecutor` Requirements ### {#wording_p0443_then_executor} 1988 | 1989 | In the type requirements list: 1990 | 1991 | **Replace:** 1992 | 1993 |
1994 | * `fut` denotes a future object satisfying the `Future` requirements, 1995 | 1996 |
1997 | 1998 | **With:** 1999 | 2000 |
2001 | * `fut` denotes a future object that: 2002 | * was returned by a call to `x.twoway_execute`, `x.bulk_twoway_execute`, 2003 | `x.then_execute`, or `x.bulk_then_execute` and meets the 2004 | `ContinuableFuture` requirements. 2005 | * was returned by a call to `execution::query(x, promise_contract_t)` or 2006 | `execution::query(x, cancellable_promise_contract_t)`. 2007 | 2008 |
2009 | 2010 | In the *Return Type* column: 2011 | 2012 | **Replace:** 2013 | 2014 |
2015 | A type that satisfies the `Future` requirements for the value type `R`. 2016 |
2017 | 2018 | **With:** 2019 | 2020 |
2021 | A type that satisfies the `ContinuableFuture` requirements for the value type `R`. 2022 |
2023 | 2024 | In the *Operational Semantics* column: 2025 | 2026 | **Replace:** 2027 | 2028 |
2029 | in the associated shared state of the resulting `Future`. 2030 |
2031 | 2032 | **With:** 2033 | 2034 |
2035 | in the resulting `ContinuableFuture`. 2036 |
2037 | 2038 | ### `BulkTwoWayExecutor` Requirements ### {#wording_p0443_bulk_two_way_executor} 2039 | 2040 | In the *Return Type* column: 2041 | 2042 | **Replace:** 2043 | 2044 |
2045 | A type that satisfies the `Future` requirements for the value type `R`. 2046 |
2047 | 2048 | **With:** 2049 |
2050 | A type that satisfies the `ContinuableFuture` requirements for the value type `R`. 2051 |
2052 | 2053 | In the *Operational Semantics* column: 2054 | 2055 | **Replace:** 2056 | 2057 |
2058 | in the associated shared state of the resulting `Future`. 2059 |
2060 | 2061 | **With:** 2062 | 2063 |
2064 | in the resulting `ContinuableFuture`. 2065 |
2066 | 2067 | ### `BulkThenExecutor` Requirements ### {#wording_p0443_bulk_then_executor} 2068 | 2069 | In the type requirements list: 2070 | 2071 | **Replace:** 2072 | 2073 |
2074 | * fut denotes a future object satisfying the Future requirements, 2075 | 2076 |
2077 | 2078 | **With:** 2079 | 2080 |
2081 | * fut denotes a future object that: 2082 | * was returned by a call to `x.twoway_execute`, `x.bulk_twoway_execute`, 2083 | `x.then_execute`, or `x.bulk_then_execute` and meets the 2084 | `ContinuableFuture` requirements. 2085 | * was returned by a call to `execution::query(x, promise_contract_t)` or 2086 | `execution::query(x, cancellable_promise_contract_t)`. 2087 | 2088 |
2089 | 2090 | In the *Return Type* column: 2091 | 2092 | **Replace:** 2093 | 2094 |
2095 | A type that satisfies the `Future` requirements for the value type `R`. 2096 |
2097 | 2098 | **With:** 2099 | 2100 |
2101 | A type that satisfies the `ContinuableFuture` requirements for the value type `R` 2102 |
2103 | 2104 | In the *Operational Semantics* column: 2105 | 2106 | **Replace:** 2107 | 2108 |
2109 | in the associated shared state of the resulting Future. 2110 |
2111 | 2112 | **With:** 2113 | 2114 |
2115 | in the resulting ContinuableFuture. 2116 |
2117 | 2118 | ### `twoway_t` Customization Points ### {#wording_p0443_twoway_t} 2119 | 2120 | **Replace:** 2121 | 2122 |
2123 | it is `std::experimental::future` 2124 |
2125 | 2126 | **With:** 2127 | 2128 |
2129 | it is a `execution::continuable_future` 2130 |
2131 | 2132 | ### `single_t` Customization Points ### {#wording_p0443_single_t} 2133 | 2134 | **Replace:** 2135 | 2136 |
2137 | it is `std::experimental::future` 2138 |
2139 | 2140 | **With:** 2141 | 2142 |
2143 | it is `execution::continuable_future` 2144 |
2145 | 2146 | ### Properties To Indicate If Blocking And Directionality May Be Adapted ### {#wording_p0443_blocking_and_directionality_properties} 2147 | 2148 | Remove `twoway_t` from the **Requirements** column of the Table. 2149 | 2150 | ### Class Template `executor` ### {#wording_p0443_erasing_executor} 2151 | 2152 | **Replace:** 2153 | 2154 |
2155 | ``` 2156 | template 2157 | std::experimental::future()>> 2158 | twoway_execute(Function&& f) const 2159 | ``` 2160 |
2161 | 2162 | **With:** 2163 | 2164 |
2165 | ``` 2166 | template 2167 | execution::semi_future()>> 2168 | twoway_execute(Function&& f) const 2169 | ``` 2170 |
2171 | 2172 | **Replace:** 2173 | 2174 |
2175 | ``` 2176 | template 2177 | std::experimental::future()>> 2178 | bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const; 2179 | ``` 2180 |
2181 | 2182 | **With:** 2183 | 2184 |
2185 | ``` 2186 | template 2187 | execution::semi_future()>> 2188 | bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const; 2189 | ``` 2190 |
2191 | 2192 | 2193 | 2194 | ### `executor` Operations ### {#wording_p0443_erasing_executor_operations} 2195 | 2196 | **Replace:** 2197 | 2198 |
2199 | ``` 2200 | template 2201 | std::experimental::future()>> 2202 | twoway_execute(Function&& f) const 2203 | ``` 2204 |
2205 | 2206 | **With:** 2207 | 2208 |
2209 | ``` 2210 | template 2211 | /* implementation-defined future type */ 2212 | twoway_execute(Function&& f) const 2213 | ``` 2214 |
2215 | 2216 | **Replace:** 2217 | 2218 |
2219 | *Returns:* 2220 | A future, whose shared state is made ready when the future returned by 2221 | `e.twoway_execute(f2)` is made ready, containing the result of `f1()` or any 2222 | exception thrown by `f1()`. 2223 | [ *Note:* 2224 | `e2.twoway_execute(f2)` may return any future type that satisfies the `Future` requirements, and not necessarily 2225 | One possible implementation approach is for the polymorphic wrapper to attach a 2226 | continuation to the inner future via that object's `then()` member function. 2227 | When invoked, this continuation stores the result in the outer future's 2228 | associated shared state and makes that shared state ready. 2229 | — *end note* ] 2230 |
2231 | 2232 | **With:** 2233 | 2234 |
2235 | *Returns:* 2236 | A value whose type satisfies the `ContinuableFuture` requirements. 2237 | The returned future is [=fulfilled=] when `f1()` completes execution, with 2238 | the result of `f1()` (if `decltype(f1())` is non-void), valueless completion 2239 | (if `decltype(f1())` is `void`), or any exception thrown by `f1()`. 2240 |
2241 | 2242 | **Replace:** 2243 | 2244 |
2245 | ``` 2246 | template 2247 | std::experimental::future()>> 2248 | void bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const; 2249 | ``` 2250 |
2251 | 2252 | **With:** 2253 | 2254 |
2255 | ``` 2256 | template 2257 | /* implementation-defined future type */ 2258 | bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const; 2259 | ``` 2260 |
2261 | 2262 | **Replace:** 2263 | 2264 |
2265 | *Returns:* 2266 | A future, whose shared state is made ready when the future returned 2267 | by e.bulk_twoway_execute(f2, n, rf2, sf2) is made ready, containing the result 2268 | in r1 (if decltype(rf1()) is non-void) or any exception thrown by an 2269 | invocation `f1`. 2270 | [ *Note:* 2271 | `e.bulk_twoway_execute(f2)` may return any future type that satisfies the 2272 | `Future` requirements, and not necessarily `std::experimental::future`. 2273 | One possible implementation approach is for the polymorphic wrapper to attach a 2274 | continuation to the inner future via that object's `then()` member function. 2275 | When invoked, this continuation stores the result in the outer future's 2276 | associated shared state and makes that shared state ready. 2277 | — *end note* ] 2278 |
2279 | 2280 | **With:** 2281 | 2282 | *Returns:* 2283 | 2284 |
2285 | A value whose type satisfies the `ContinuableFuture` requirements. 2286 | The returned future is [=fulfilled=] when `f1()` completes execution, with 2287 | the result in `r1` (if `decltype(rf1())` is non-void), valueless completion 2288 | (if `decltype(rf1())` is void), or any exception thrown by an invocation `f1`. 2289 |
2290 | 2291 | ### `static_thread_pool` Executor Type ### {#wording_p0443_static_thread_pool} 2292 | 2293 | **Replace:** 2294 | 2295 |
2296 | ``` 2297 | template 2298 | std::experimental::future()>> 2299 | twoway_execute(Function&& f) const 2300 | 2301 | template 2302 | std::experimental::future(decay_t)>> 2303 | then_execute(Function&& f, Future&& pred) const; 2304 | 2305 | template 2306 | void bulk_execute(Function&& f, size_t n, SharedFactory&& sf) const; 2307 | 2308 | template 2309 | std::experimental::future()>> 2310 | void bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const; 2311 | ``` 2312 |
2313 | 2314 | **With:** 2315 | 2316 |
2317 | ``` 2318 | template 2319 | std::execution::continuable_future()>, C> 2320 | twoway_execute(Function&& f) const 2321 | 2322 | template 2323 | execution::continuable_future(decay_t)>, C> 2324 | then_execute(Function&& f, Future&& pred) const; 2325 | 2326 | template 2327 | void bulk_execute(Function&& f, size_t n, SharedFactory&& sf) const; 2328 | 2329 | template 2330 | execution::continuable_future()>>, C> 2331 | void bulk_twoway_execute(Function&& f, size_t n, ResultFactory&& rf, SharedFactory&& sf) const 2332 | ``` 2333 | 2334 | Replace the same instances in the documentation section below the main code block. 2335 |
2336 | -------------------------------------------------------------------------------- /notes/future_continuation_concept.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using std::exception_ptr; 4 | 5 | struct exception_arg_t {}; 6 | inline constexpr exception_arg_t exception_arg{}; 7 | 8 | template 9 | concept bool FutureContinuation = 10 | requires (Continuation c, T value) { c(value); } 11 | || 12 | requires (Continuation c) { c(exception_arg, exception_ptr{}); }; 13 | 14 | -------------------------------------------------------------------------------- /notes/lexicon.md: -------------------------------------------------------------------------------- 1 | ISO C++ Executors and Futures 2 | ============================= 3 | 4 | Lexicon 5 | ------- 6 | 7 | This document seeks to provide linguistic clarity to the ISO C++ Executors and 8 | Futures standardization project. 9 | 10 | | P0443 and P1054 Terms | P1055 Terms | Other Terms | Meaning | 11 | |-----------------------|-------------|-------------|---------| 12 | | Executor | Sender | | An object that creates execution agents to invoke function-like objects. | 13 | | Promise, continuation, function object | Receiver | Task, work | Function-like objects that are invoked by executors. | 14 | | Future | Sender | | An asynchronous value: content (a value or an error) that may not be ready yet. | 15 | | `make_ready_future(x)` | `just(x)` | | An immediately available asynchronous value. | 16 | | `exec.execute(f)` | `exec.submit(f)` | | Use `exec` to invoke `f`. | 17 | 18 | 19 | -------------------------------------------------------------------------------- /notes/meetings/2018_04_25__futures__face_to_face__planning__lee_bryce.md: -------------------------------------------------------------------------------- 1 | ISO C++ Futures 2 | =============== 3 | 4 | Pre Face-to-Face 2018-04-26 at NVIDIA - Notes - 2018-04-25 5 | ---------------------------------------------------------- 6 | 7 | **Kindly forgive and identify any mistakes and omissions in these minutes.** 8 | 9 | Attendees: 10 | - Bryce Adelstein Lelbach (Note Taker) 11 | - Lee Howes 12 | 13 | Discussed the following things not relating to the face to face meeting at NVIDIA tomorrow: 14 | - Implementing future/promise model in CUDA. 15 | - Bryce: I think it can be done. 16 | - Incorporating executor promise property paper into this paper. 17 | - Bryce: I wants to have the concepts and mechanism in this paper so it's self contained. 18 | - Lee: I'm oncerned about this paper growing too large. 19 | - Consensus: We're okay with doing this, but want to wait to hear back from Chris (which one?) next week on the design. 20 | - Subsetting `then_execute` out of the executors proposal. 21 | - Lee: I feel this might be necessary for him and others (ex: LEWG) to be comfortable with this. 22 | - Bryce: I feel we should have done this a long time ago and would be okay with this. 23 | - Bryce: I think that we shouldn't push for this now as LEWG asked us to limit changes to the executors paper. 24 | - Bryce: But we should explore this if it becomes clear it would increase consensus during/after Rapperswil. 25 | 26 | Action items before the face to face meeting at NVIDIA tomorrow: 27 | - Add separate outline document to repo. 28 | - Add placeholder sections to the paper. 29 | - Review and triage Github issues. 30 | 31 | Planning for the face to face meeting at NVIDIA tomorrow: 32 | - Agenda: 33 | - Discuss and formulate solutions to remaining open technical questions that need answers for the Rapperswil paper. 34 | - Review and expand on paper outline. 35 | - Identify action items in both the paper and the reference implementation. 36 | - Assign action items to collaborators. 37 | - Review and triage Github issues. 38 | - We should try to keep the group on topic and avoid tangents. 39 | - Some attendees unfamiliar with ISO process/new to participation (Felix, Sean). 40 | - Some attendees new to this effort (Thomas Rodgers). 41 | 42 | Outline: 43 | - Motivation 44 | - What problem are we solving? 45 | - Avoid detailed discussion in this paper. 46 | - Instead, reference prior papers co-authored by Bryce, Lee and others. 47 | - Design: How are we solving the problem? 48 | - Avoid detailed discussion of all the alternative design options. 49 | - Instead, reference prior papers co-authored by Bryce, Lee and others. 50 | - Focus on the design decisions we have decided on. 51 | - Reference supporting polls/minutes from previous committee meetings to support our decisions. 52 | - Examples (we won't be able to do all of these, but we should try to get some volunteers to work on these) 53 | - Archetypes 54 | - Always ready future 55 | - Locking traditional future 56 | - Lockfree traditional future 57 | - CUDA future 58 | - HPX/fiber future 59 | - Network/distributed future 60 | - Heterogeneous future interop 61 | E.g. `network_future(/* ... */).via(cuda_executor).then(f).via(hpx_executor).then(g).via(network_executor)`. 62 | - Coroutines TS future interop 63 | - Generic executor-based `async` (not part of this proposal, but could be an example in the repo) 64 | - Generic `when_*` (not part of this proposal, but could be an example in the repo) 65 | - Generic asynchronous algorithm (not part of this proposal, but could be an example in the repo) 66 | - Custom `future_wait`/`future_get` function 67 | - Future Work (things that are not version 1 features) 68 | - `std2::async` 69 | - `std2::when_*` 70 | - Cancellation 71 | - `.bulk_then` 72 | - Wording 73 | - Promise concept 74 | - Executor promise property 75 | - Error-handling disambiguation tag 76 | - Future concepts 77 | - SemiFuture 78 | - ContinuableFuture 79 | - UniqueFuture [OPEN TECHNICAL QUESTION, ISSUE #26 AND #27] 80 | - SharedFuture [OPEN TECHNICAL QUESTION, ISSUE #26 AND #27] 81 | - `.then` 82 | - Syntax 83 | - Semantics 84 | - Requirements [ISSUE #25 AND #27] 85 | - .via 86 | - Syntax 87 | - Semantics 88 | - Requirements [ISSUE #21] 89 | - Requirements on continuation functions 90 | - Error handling model 91 | - Future unwrapping constructor [OPEN TECHNICAL QUESTION, ISSUE #29] 92 | - Forward progress guarantees [OPEN TECHNICAL QUESTION, ISSUE #6] 93 | - Future/promise synchronization gurantees [OPEN TECHNICAL QUESTION, ISSUES #2 AND #3] 94 | - `is_always_lockfree(ish)` property 95 | - Concrete executor type-erasing future types 96 | - std2::semi_future 97 | - std2::continuable_future 98 | - std::thread Future Waiting Functions 99 | - std::this_thread::future_wait (ADLable) 100 | - std::this_thread::future_get (ADLable) 101 | 102 | New questions from call (mostly notes to myself of stuff to look at): 103 | - Look at `.via`/`.on` consistency discussions [ISSUE #4] 104 | - Does `hpx_future>` unwrap? [ISSUE #29] 105 | - Are nested CUDA streams a thing (in CNP for example)? 106 | - Is future convertible to future (HPX does this)? 107 | - Is every unique future type convertible to a shared future type? 108 | 109 | -------------------------------------------------------------------------------- /notes/meetings/2018_04_26__futures__face_to_face__attendees.md: -------------------------------------------------------------------------------- 1 | ISO C++ Futures 2 | =============== 3 | 4 | Face-to-Face 2018-04-26 at NVIDIA - Attendance - 2018-04-26 5 | ----------------------------------------------------------- 6 | 7 | Attendees: 8 | - Andrii Grynenko 9 | - Bryce Adelstein Lelbach (Note Taker) 10 | - Carter Edwards 11 | - David Hollman 12 | - Hartmut Kaiser 13 | - Lee Howes 14 | - Michal Dominiak 15 | - Sean Parent 16 | - Thomas Rodgers 17 | 18 | -------------------------------------------------------------------------------- /notes/meetings/2018_05_03__rapperswil_2018__pre_mailing_deadline__lee_bryce.md: -------------------------------------------------------------------------------- 1 | ISO C++ Futures 2 | =============== 3 | 4 | Pre 2018 Rapperswil Mailing Deadline Meeting - Notes - 2018-05-03 5 | ----------------------------------------------------------------- 6 | 7 | **Kindly forgive and identify any mistakes and omissions in these notes.** 8 | 9 | Attendees: 10 | - Bryce Adelstein Lelbach (Note Taker) 11 | - Lee Howes 12 | 13 | ### Synchronization 14 | 15 | Forward progress: 16 | - Blocking mechanisms need to call some `progress` mechanism for whatever executor they're running on. 17 | - Don't hold locks when you call `progress`. 18 | 19 | Issues: 20 | - Pre-volta, scheduler was not fair, and could preempt you at any point, and you could be holding a mutex, and then be screwed. 21 | - (Maybe this is a GPU problem, not an ISO problem): you can't call `try_lock` on a std::mutex on a GPU. 22 | - In the fiber example, you CAN actually call whatever OS synchronization mechanism, it just might deadlock you. 23 | - On a GPU, you may not be able to call it. 24 | - But it may not just be a GPU problem. 25 | - HPX mutexes/sync primitives can ONLY be called from HPX scheduler threads, because they context switch out (and thus assume the thread stack is an HPX managed one). 26 | 27 | ### P1054r0 AKA V1.0 28 | 29 | By COB tmrw send draft to contributors. 30 | 31 | Submit on Saturday evening. 32 | - Plan is for Bryce to submit, but he is traveling. If Lee doesn't here from Bryce by Sunday, he should submit. 33 | 34 | -------------------------------------------------------------------------------- /notes/meetings/2018_06_20__futures__weekly_us.md: -------------------------------------------------------------------------------- 1 | ISO C++ Futures 2 | =============== 3 | 4 | Weekly US Timezone Meeting - Notes - 2018-06-20 5 | ----------------------------------------------- 6 | 7 | **Kindly forgive and identify any mistakes and omissions in these notes.** 8 | 9 | Attendees: **???** 10 | 11 | Agenda: 12 | - Debrief from Rapperswil/CSCS workshop discussion of futures/post-Rapperswil Executors telecon. 13 | - Discuss `then`/`then_execute` taking promises. 14 | - Review work on prototype/reference implementation. 15 | - Assign work for P1054 update for post-meeting mailing (adding more examples as requested by SG1). 16 | - Triage issues/milestones. 17 | 18 | ### Open Questions (Not Addressed at This Meeting) 19 | 20 | - What type of futures can `then_execute` take as a parameter? 21 | - How do we specialize for "foreign" future types that we want to handle specially? 22 | - Can we remove `twoway_execute`? 23 | - What type of thing should `then_execute`/`then` take as a parameter? 24 | 25 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_09__futures__biweekly_non_us.md: -------------------------------------------------------------------------------- 1 | ISO C++ Futures 2 | =============== 3 | 4 | Biweekly Non-US Timezone Meeting - Notes - 2018-07-09 5 | ----------------------------------------------------- 6 | 7 | Attendees: 8 | - Bryce Adelstein Lelbach (Note Taker) 9 | - David Hollman 10 | - Eric Niebler 11 | - Gordon Brown 12 | - Kirk Shoop 13 | - Michael Garland 14 | - Michał Dominiak 15 | 16 | ### Discussion 17 | 18 | How can P0443 and P1054 be modified based on P1055? 19 | - Bryce: 20 | - First, `Promise`s, `Continuation`s, `Callable`s -> `Receiver`s. 21 | - Then, `ContinuableFuture`, `SharedFuture` -> `Sender`s. 22 | - Then, `Executors -> `Sender`s. 23 | 24 | ### Open Questions (Not Addressed at This Meeting) 25 | 26 | - `Executors passing themselves to tasks. 27 | - `Executor as `Future`s. 28 | - (from Gordon) How do you distinguish between executors (which have properties) and futures (which are lightweight things)? 29 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_11__futures__weekly_us.md: -------------------------------------------------------------------------------- 1 | ISO C++ Futures 2 | =============== 3 | 4 | Weekly US Timezone Meeting - Notes - 2018-07-11 5 | ----------------------------------------------- 6 | 7 | **Kindly forgive and identify any mistakes and omissions in these notes.** 8 | 9 | Attending: 10 | - Bryce Adelstein Lelbach (Note Taker) 11 | - David Hollman 12 | - Gordon Brown 13 | - Kirk Shoop 14 | - Lee Howes 15 | - Mathias Stearn 16 | - Michał Dominiak 17 | 18 | Agenda: 19 | - Review high-level strategy for initial PR against P0443 based on P1053/1054/1055 for Friday. 20 | - Discuss issues stemming from the above and from email. 21 | 22 | Initial PR Plan ([repository, WIP](github.com/brycelelbach/executors)): 23 | - 0) Add `None`/`SingleReceiver` concepts and replace raw callables/continuations with them. 24 | - Status: In progress. 25 | - 1) Add P1054 style `Receiver` wrappers. 26 | - Status: TODO. 27 | - 2) Add `None`/`SingleSender` concepts. 28 | - Status: TODO. 29 | - 3) Replace `then_execute` with `via_execute` or an alternative. 30 | - (as member function or property, transition to a customization point later). 31 | - Status: TODO. 32 | - 4) Refactor two-way execution in terms of one-way execution. 33 | - Status: TODO. 34 | - 5) Refactor bulk execution in terms of one-way execution. 35 | - Status: TODO. 36 | - 6) Replace executor concepts with sender concepts. 37 | - Status: TODO. 38 | 39 | ### Discussion 40 | 41 | (from David) Using P0443 friendly terminology 42 | - Bryce: I was planning to try and do that. 43 | - Bryce, Kirk: Concerned about overloading terminology. 44 | 45 | Proposed `Reciever`-related proposed changes to P0443 and P1054: 46 | - `NoneReceiver::done` vs `SingleReceiver::done`. 47 | - Michal: Remove `done` from `SingleReceiver`, you can require both concepts if you need to. 48 | - Kirk: `done` is needed `ManyReceiver`, which has the same interface as `SingleReceiver`, but can receive multiple values/errors. 49 | - **Consensus: No `done` for now.** 50 | - `Callable`-compatible `Receiver`s. 51 | - Consensus: `Callable` compatibility isn't critical at the `Executor` interface level. 52 | `Executor`s passing themselves to Receivers 53 | - Kirk: The `Receiver`'s value method runs in the caller's context, and submits the actual work (which doesn't need to take the `Executor`) to the `Executor`. 54 | - Bryce: Ah, I think I understand now, will have to think about this. Passing the `Executor` may be useful for bulk `Receiver`s. 55 | 56 | Bulk Execution: 57 | - (from Mathias) Deferring completion during bulk execution 58 | - (from Kirk) Data movement and bulk execution 59 | - Bryce: Bulk is not tied to this. SG1 decided to only deal with accessible memory for now. There are natively-bulk execution frameworks that do not need data movement because they have global memory. 60 | - Bryce, Gordon: Not a part of executors. Data movement is something to be addressed separately. 61 | 62 | ### Open Questions (Not Addressed at This Meeting) 63 | 64 | - Semantics of `via_execute`. 65 | - Distinction between via and `via_execute`. 66 | - `BulkReceiver` vs bulk operator. 67 | - Implications of `Executor`s and `Future`s satisfying the same concepts. 68 | - (from Gordon) Distinguishing between `Executor`s (which have properties) and `Future`s (which don't). 69 | 70 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly.md: -------------------------------------------------------------------------------- 1 | ISO C++ Executors 2 | ================= 3 | 4 | Weekly Meeting - Notes - 2018-07-13 5 | ----------------------------------- 6 | 7 | **Kindly forgive and identify any mistakes and omissions in these notes.** 8 | 9 | Attending: 10 | - Bryce Adelstein Lelbach (Note Taker) 11 | - Christopher Kohlhoff 12 | - David Hollman 13 | - Jared Hoberock 14 | - Lee Howes 15 | - **???** 16 | 17 | Agenda: 18 | - (from Bryce) Review of proposed `Receiver`-related changes to P0443 and P1054: 19 | - (from Chris K) Discuss experiments with `then_execute` alternatives. 20 | 21 | ### Discussion 22 | 23 | #### (from Bryce) Review of proposed `Receiver`-related changes to P0443 and P1054: 24 | 25 | - Bryce: My goal is to try and identify a concrete series of atomic changes that can be applied (in order) based on P1053 and P1055. This will let us evaluate each change and its implications individually. 26 | - Bryce: I'm not necessarily advocating for all of these changes, just bringing them here for us to evaluate as a group. 27 | - Bryce: The plan I came up with was: 28 | - 0) Add `None`/`SingleReceiver` concepts and replace raw callables/continuations with it 29 | - 1) Add P1054 style `Receiver` wrappers. 30 | - 2) Add `None`/`SingleSender` concepts. 31 | - 3) Replace `then_execute` with `via_execute` or an alternative. 32 | - 4) Refactor two-way execution in terms of one-way execution. 33 | - 5) Refactor bulk execution in terms of one-way execution. 34 | - 6) Replace executor concepts with sender concepts. 35 | - Bryce: I thought we should start with the receiver concepts because we did similar work in P0443 and P1054 pre-Rapperswil to define `(bulk_)then_execute` error handling worked. 36 | 37 | Lexicon: 38 | - Receiver == Promise == (Future)Continuation == Callable == Task == Work Item 39 | - E.g. the things that executors invoke. 40 | 41 | **Status Quo (P0443 and P1054):** 42 | ``` 43 | // `execute` takes: 44 | struct VoidNoneReceiver /* AKA a `Callable` */ { 45 | void operator()(); 46 | }; 47 | 48 | // `twoway_execute` takes: 49 | struct NoneReceiver /* AKA a `Callable` */ { 50 | U operator()(); 51 | }; 52 | 53 | // `then_execute` takes: 54 | struct Receiver { 55 | // At least one of these is required (satisfied by `Callable`s): 56 | U operator()(T value); 57 | U operator()(exception_arg_t, std::exception_ptr error); 58 | }; 59 | 60 | // P1054 helper functions for constructing receivers: 61 | Receiver on_value(Callable value_f); 62 | Receiver on_error(Callable error_f); 63 | Receiver on_value_or_error(Callable value_f, Callable error_f); 64 | ``` 65 | 66 | 67 | **Ask 0:** A receiver's error method shouldn't be an overload of the value method. 68 | 69 | [EXECUTORS ISSUE #408](https://github.com/executors/executors/issues/408) 70 | 71 | ``` 72 | // Before: 73 | struct Receiver { 74 | // At least one of these is required (satisfied by `Callable`s): 75 | U operator()(T value); 76 | U operator()(exception_arg_t, std::exception_ptr error); 77 | }; 78 | 79 | 80 | // After: 81 | struct Receiver { 82 | // At least one of these is required (satisfied by `Callable`s): 83 | U operator()(T value); 84 | U error(std::exception_ptr ptr); 85 | }; 86 | ``` 87 | 88 | - Bryce, Lee: As P1053 points out, it's very easy today to accidentally forget the `exception_arg_t` parameter when writing your error method. You'll then be surprised when your code compiles, but your "error" method is never called. 89 | - **There was consensus to accept this.** 90 | 91 | 92 | **Ask 1:** A receiver's error method should take a generic error type instead of an `std::exception_ptr`. 93 | 94 | [EXECUTORS ISSUE #409](https://github.com/executors/executors/issues/409) 95 | 96 | ``` 97 | // Before: 98 | struct Receiver { 99 | // At least one of these is required (satisfied by `Callable`s): 100 | U operator()(T value); 101 | U operator()(exception_arg_t, std::exception_ptr error); 102 | }; 103 | 104 | 105 | // After: 106 | struct Receiver { 107 | // At least one of these is required (satisfied by `Callable`s): 108 | U operator()(T value); 109 | U operator()(exception_arg_t, E error); 110 | }; 111 | ``` 112 | 113 | - Bryce: Places additional burden on authors of receiver. 114 | - Kirk: The error type should be required to be `SemiMovable`. 115 | - Jared: Would you be able to just write an overload for one concrete type? 116 | - Bryce: This is related to whether the error method should be required. 117 | - Bryce: Two options if no error method matches: 118 | - Compile fail. 119 | - Propagate. 120 | - Chris K: What's the impact on the polymorphic executor? 121 | 122 | 123 | **Ask 2:** The value and error methods of a receiver should not be required to have the same return type. 124 | 125 | [EXECUTORS ISSUE #410](https://github.com/executors/executors/issues/410) 126 | 127 | ``` 128 | // Before: 129 | struct Receiver { 130 | // At least one of these is required (satisfied by `Callable`s): 131 | U operator()(T value); 132 | U operator()(exception_arg_t, std::exception_ptr error); 133 | }; 134 | 135 | 136 | // After: 137 | struct Receiver { 138 | // At least one of these is required (satisfied by `Callable`s): 139 | U operator()(T value); 140 | V operator()(exception_arg_t, std::exception_ptr error); 141 | }; 142 | ``` 143 | 144 | - Bryce: Lee is the champion for this one, this is inspired by P1053. 145 | - Bryce: If we move towards a model where we have a single one-way execution function, this problem will solve itself, as the only types of receivers we have will be ones that return `void`. 146 | - Jared, Bryce: What type of future do you get from an `on_error`. `then_execute(on_error(e), f)` -> `future`. 147 | - Bryce: Maybe `future`? 148 | - Lee: I think so. 149 | - Jared: I'd like to see more motivation/use cases. 150 | - **Lee: I will come back with examples.** 151 | 152 | 153 | **Ask 3:** All execution functions should take receivers (AKA how do executors deal with asynchronous system errors and allow users to customize that handling?). 154 | 155 | [EXECUTORS ISSUE #411](https://github.com/executors/executors/issues/411) 156 | 157 | ``` 158 | // Before: 159 | // `execute` takes: 160 | struct VoidNoneReceiver /* AKA a `Callable` */ { 161 | void operator()(); 162 | }; 163 | // `twoway_execute` takes: 164 | struct NoneReceiver /* AKA a `Callable` */ { 165 | U operator()(); 166 | }; 167 | // `then_execute` takes: 168 | struct Receiver { 169 | // At least one of these is required (satisfied by `Callable`s): 170 | U operator()(T value); 171 | U operator()(exception_arg_t, std::exception_ptr error); 172 | }; 173 | 174 | 175 | // After: 176 | // `execute` takes: 177 | struct VoidNoneReceiver { 178 | // At least one of these is required (satisfied by `Callable`s): 179 | void operator()(); 180 | void operator()(exception_arg_t, std::exception_ptr error); 181 | }; 182 | // `twoway_execute` takes: 183 | struct NoneReceiver /* AKA a `Callable` */ { 184 | // At least one of these is required (satisfied by `Callable`s): 185 | U operator()(); 186 | U operator()(exception_arg_t, std::exception_ptr error); 187 | }; 188 | // `then_execute` takes: 189 | struct Receiver { 190 | // At least one of these is required (satisfied by `Callable`s): 191 | U operator()(T value); 192 | U operator()(exception_arg_t, std::exception_ptr error); 193 | }; 194 | ``` 195 | 196 | - Bryce: P0443 is consistent with the IS; exceptions that escape from a thread of execution lead to `std::terminate`. 197 | - Lee: The example is a non-blocking launch + a shutdown request that arrives before the work is started. 198 | - Chris K: This should be handled through a different channel. 199 | - Chris K: The problem is that the error function is not called in the right execution agent. But for `then_execute`, it will be called in the execution agent. You are using the same channel for two different things. 200 | - Bryce: For `then_execute`, can this be called due to a launch error. 201 | - Bryce: The proposal is that this error method would be used to handle both "system" errors and errors contained in tasks? 202 | - Bryce: This means that error is invoked on an unspecified execution agent. Remember the Concurrency TS v1. 203 | - Jared: Is an executor allowed to throw for synchronous errors? Or does it have to use the error method? 204 | - Bryce: If you don't always use the error method, changes in the implementation of an executor can change the error handling path. I think the error method would always have to be used. 205 | - Lee: Not just about launch errors, also things like shutdown, migration, cancellation, etc. 206 | - Chris K: We want two channels, one for dependency errors, one for system errors. 207 | - **Bryce: Idea - have two channels (or a way to signal error category) on receivers.** 208 | - Lee: What if the preceding task forwarded the error to it's dependency? 209 | - Bryce: Two questions for Jared and Chris K: 210 | - Should we provide some way of providing an error handling hook for asynchronous system errors? 211 | - Would you feel differently about this if there was a way to distinguish the error category (system or predecessor)? 212 | - Jared: Could this be done with properties? 213 | - Lee, Kirk: Executors do not have to report system errors with the error method even if it exists. 214 | - Lee, Kirk: This is needed for error propagation if we rebase `then_execute` on `execute`. 215 | - Bryce: So a non- or maybe-blocking executor may choose to, but doesn't have to, report system failures (shutdown, launch failure, etc) that occur after the execution function returns via the receiver's error method? 216 | - Bryce: That makes error handling inconsistency in generic code. 217 | 218 | 219 | **Ask 4:** Receivers should be required to have both the value method and the error method. 220 | 221 | [EXECUTORS ISSUE #412](https://github.com/executors/executors/issues/412) 222 | 223 | ``` 224 | // Before: 225 | struct Receiver { 226 | // At least one of these is required (satisfied by `Callable`s): 227 | U operator()(T value); 228 | U operator()(exception_arg_t, std::exception_ptr error); 229 | }; 230 | 231 | auto f = exec.then_execute([] { /* ... */ }, pred); 232 | 233 | 234 | // After: 235 | struct Receiver { 236 | // BOTH of these are required (NOT satisfied by `Callable`s): 237 | U operator()(T value); 238 | U operator()(exception_arg_t, std::exception_ptr error); 239 | }; 240 | 241 | // Using P1054-style `on_value`: 242 | auto f = exec.then_execute(on_value([] { /* ... */ }), pred); 243 | ``` 244 | 245 | Note that this would apply to all execution functions and all types of receivers if Ask 3 is accepted: 246 | ``` 247 | // Before (with Ask 3): 248 | // `execute` takes: 249 | struct VoidNoneReceiver { 250 | // At least one of these is required (satisfied by `Callable`s): 251 | void operator()(); 252 | void operator()(exception_arg_t, std::exception_ptr error); 253 | }; 254 | // `twoway_execute` takes: 255 | struct NoneReceiver { 256 | // At least one of these is required (satisfied by `Callable`s): 257 | U operator()(); 258 | U operator()(exception_arg_t, std::exception_ptr error); 259 | }; 260 | 261 | exec.execute([] { /* ... */ }); 262 | auto f = exec.twoway_execute([] { /* ... */ }); 263 | 264 | 265 | // After (with Ask 3): 266 | // `execute` takes: 267 | struct VoidNoneReceiver { 268 | // BOTH of these are required (NOT satisfied by `Callable`s): 269 | void operator()(); 270 | void operator()(exception_arg_t, std::exception_ptr error); 271 | }; 272 | // `twoway_execute` takes: 273 | struct NoneReceiver { 274 | // BOTH of these are required (NOT satisfied by `Callable`s): 275 | U operator()(); 276 | U operator()(exception_arg_t, std::exception_ptr error); 277 | }; 278 | 279 | // Using P1054-style `on_value`: 280 | exec.execute(on_value([] { /* ... */ })); 281 | auto f = exec.twoway_execute(on_value([] { /* ... */ })); 282 | ``` 283 | 284 | - Bryce: Presumably, P1054-style helper functions would also be added to ease construction of receivers. 285 | - Bryce: This would mean that `(bulk_)then_execute` could no longer be passed a regular `Callable` (a lambda, etc). When combined with Ask 3, this would mean ALL execution function swould no longer be passed a regular `Callable`. 286 | - Bryce: I don't understand how this is needed for exception propagation. It just moves the burden out of the executor, to the receiver or the user of the executor. 287 | - Lee: The error method should only be required if it's used. 288 | - Kirk: I think it should always be required. 289 | - Bryce: If I don't write an error method and call inline_executor::execute, should it fail to compile? 290 | - Lee: No 291 | - Kirk: Yes 292 | - Jared: I could be okay with requiring this for then_execute. 293 | - **Bryce: What I would want is to be able to `via` the value and error method separately. I want to be able to say `via(cpu).on_error(f), via(gpu).on_value(g)`.** 294 | - Jared: That's what I want. 295 | - Bryce: I need some before/after examples. 296 | - **Bryce: Does error propagation in P0443 chain properly? If it's broken we need an example of how.** 297 | 298 | 299 | **Ask 5:** Non-dependent execution functions should take `Callable`s that take an `Executor` parameter instead of nullary `Callable`s. 300 | 301 | [EXECUTORS ISSUE #413](https://github.com/executors/executors/issues/413) 302 | 303 | ``` 304 | // Before: 305 | struct VoidNoneReceiver /* AKA a `Callable` */ { 306 | void operator()(); 307 | }; 308 | struct OneWayExecutor { 309 | void execute(VoidNoneReceiver r); 310 | }; 311 | 312 | exec.execute([] { /* ... */ }); 313 | 314 | 315 | // After: 316 | struct ExecutorReceiver { 317 | void operator()(Executor exec); 318 | }; 319 | struct OneWayExecutor { 320 | void execute(ExecutorReceiver r); 321 | }; 322 | 323 | exec.execute([] (Executor e) { /* ... */ }); 324 | ``` 325 | 326 | - Bryce: An executor is free to pass any `Executor` to a `ExecutorReceiver`, not necessarily themselves. 327 | - Bryce: For example, in pushmi, the `std::thread` executor passes a "trampoline" executor that protects the stack. In the simplest case you can just pass an `inline_executor`. 328 | - Jared: What's the motivation? 329 | - Kirk: This is needed for lazy submission. 330 | - Gordon: I'm concerned about whether this work. 331 | - Jared: I need some examples. 332 | - **Bryce: Kirk, Lee - Would you be fine with this being optional, not required?** 333 | 334 | 335 | **Ask 6:** Receiver's value method shouldn't be the call operator. 336 | 337 | [EXECUTORS ISSUE #414](https://github.com/executors/executors/issues/414) 338 | 339 | ``` 340 | // Before: 341 | // `execute` takes: 342 | struct VoidNoneReceiver /* AKA a `Callable` */ { 343 | void operator()(); 344 | }; 345 | // `twoway_execute` takes: 346 | struct NoneReceiver /* AKA a `Callable` */ { 347 | U operator()(); 348 | }; 349 | // `then_execute` takes: 350 | struct Receiver { 351 | // At least one of these is required (satisfied by `Callable`s): 352 | U operator()(T value); 353 | U operator()(exception_arg_t, std::exception_ptr error); 354 | }; 355 | 356 | exec.execute([] { /* ... */ }); 357 | auto f = exec.twoway_execute([] { /* ... */ }); 358 | auto f = exec.then_execute([] { /* ... */ }, pred); 359 | 360 | 361 | // After: 362 | // `execute` takes: 363 | struct VoidNoneReceiver /* AKA a `Callable` */ { 364 | void done(); 365 | }; 366 | // `twoway_execute` takes: 367 | struct NoneReceiver /* AKA a `Callable` */ { 368 | U done(); 369 | }; 370 | // `then_execute` takes: 371 | struct Receiver { 372 | // At least one of these is required (satisfied by `Callable`s): 373 | U value(T value); 374 | U operator()(exception_arg_t, std::exception_ptr error); 375 | }; 376 | 377 | // Using P1054-style `on_value`: 378 | exec.execute(on_value([] { /* ... */ })); 379 | auto f = exec.twoway_execute(on_value([] { /* ... */ })); 380 | auto g = exec.then_execute(on_value([] { /* ... */ }), pred); 381 | ``` 382 | 383 | Note that this change makes the most sense if done in conjunction with Ask 1 and Ask 4: 384 | ``` 385 | // Before (with Ask 1 and Ask 4): 386 | // `execute` takes: 387 | struct VoidNoneReceiver { 388 | // BOTH of these are required (NOT satisfied by `Callable`s): 389 | void operator()(); 390 | void error(exception_arg_t, std::exception_ptr error); 391 | }; 392 | // `twoway_execute` takes: 393 | struct NoneReceiver { 394 | // BOTH of these are required (NOT satisfied by `Callable`s): 395 | U operator()(); 396 | U error(exception_arg_t, std::exception_ptr error); 397 | }; 398 | struct Receiver { 399 | // BOTH of these are required (NOT satisfied by `Callable`s): 400 | U operator()(T value); 401 | U error(exception_arg_t, std::exception_ptr error); 402 | }; 403 | 404 | // Using P1054-style `on_value`: 405 | exec.execute(on_value([] { /* ... */ }); 406 | auto f = exec.twoway_execute(on_value([] { /* ... */ })); 407 | auto g = exec.then_execute(on_value([] { /* ... */ }, pred)); 408 | 409 | 410 | // After (with Ask 1 and Ask 4): 411 | // `execute` takes: 412 | struct VoidNoneReceiver { 413 | // BOTH of these are required (NOT satisfied by `Callable`s): 414 | void done(); 415 | void error(exception_arg_t, std::exception_ptr error); 416 | }; 417 | // `twoway_execute` takes: 418 | struct NoneReceiver { 419 | // BOTH of these are required (NOT satisfied by `Callable`s): 420 | U done(); 421 | U error(exception_arg_t, std::exception_ptr error); 422 | }; 423 | // `then_execute` takes: 424 | struct Receiver { 425 | // BOTH of these are required (NOT satisfied by `Callable`s): 426 | U value(T value); 427 | U error(exception_arg_t, std::exception_ptr error); 428 | }; 429 | 430 | // Using P1054-style `on_value`: 431 | exec.execute(on_value([] { /* ... */ })); 432 | auto f = exec.twoway_execute(on_value([] { /* ... */ })); 433 | auto g = exec.then_execute(on_value([] { /* ... */ }), pred); 434 | ``` 435 | 436 | - Lee: This increases consistency. 437 | - Jared: If both cases are required, I agree. Otherwise, I'd prefer the normal case to just be the call operator 438 | 439 | #### (from Chris K) `then_execute` alternatives 440 | 441 | - Bryce: You need two hooks, both to and from. 442 | - Chris K: The other customization is done in .then. 443 | - Bryce: At the start of this effort 2 years ago, we decided `then_execute` should be more primitive than `then`. I'm starting to feel like this was a mistake. 444 | - Bryce: My thinking is we move towards a model where we have a single execution function no data dependencies. 445 | - **Chris K and Bryce to sync up after meeting.** 446 | 447 | ### Action Items 448 | 449 | - (Bryce) Create executor GitHub issues for the proposed `Receiver`-related changes. 450 | - (Bryce, Chris K) Sync up after meeting regarding stream-based execution. 451 | 452 | 453 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly/receiver__ask_0.cpp: -------------------------------------------------------------------------------- 1 | // Proposed `Receiver`-related changes to P0443 based on P1053/P1055 2 | // Reply-To: Bryce Adelstein Lelbach 3 | 4 | // Ask 0: A receiver's error method shouldn't be an overload of the value 5 | // method. 6 | 7 | /////////////////////////////////////////////////////////////////////////////// 8 | // Before: 9 | struct Receiver { 10 | // At least one of these is required (satisfied by `Callable`s): 11 | U operator()(T value); 12 | U operator()(exception_arg_t, std::exception_ptr error); 13 | }; 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // After: 17 | struct Receiver { 18 | // At least one of these is required (satisfied by `Callable`s): 19 | U operator()(T value); 20 | U error(std::exception_ptr ptr); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly/receiver__ask_1.cpp: -------------------------------------------------------------------------------- 1 | // Proposed `Receiver`-related changes to P0443 based on P1053/P1055 2 | // Reply-To: Bryce Adelstein Lelbach 3 | 4 | // Ask 1: A receiver's error method should take a generic error type instead 5 | // of an `std::exception_ptr`. 6 | 7 | /////////////////////////////////////////////////////////////////////////////// 8 | // Before: 9 | struct Receiver { 10 | // At least one of these is required (satisfied by `Callable`s): 11 | U operator()(T value); 12 | U operator()(exception_arg_t, std::exception_ptr error); 13 | }; 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // After: 17 | struct Receiver { 18 | // At least one of these is required (satisfied by `Callable`s): 19 | U operator()(T value); 20 | U operator()(exception_arg_t, E error); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly/receiver__ask_2.cpp: -------------------------------------------------------------------------------- 1 | // Proposed `Receiver`-related changes to P0443 based on P1053/P1055 2 | // Reply-To: Bryce Adelstein Lelbach 3 | 4 | // Ask 2: The value and error methods of a receiver should not be required 5 | // to have the same return type. 6 | 7 | /////////////////////////////////////////////////////////////////////////////// 8 | // Before: 9 | struct Receiver { 10 | // At least one of these is required (satisfied by `Callable`s): 11 | U operator()(T value); 12 | U operator()(exception_arg_t, std::exception_ptr error); 13 | }; 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // After: 17 | struct Receiver { 18 | // At least one of these is required (satisfied by `Callable`s): 19 | U operator()(T value); 20 | V operator()(exception_arg_t, std::exception_ptr error); 21 | }; 22 | 23 | /////////////////////////////////////////////////////////////////////////////// 24 | 25 | // NOTE: If we move towards a model where we have a single one-way execution 26 | // function, this problem will solve itself, as the only types of receivers we 27 | // have will be ones that return `void`. 28 | 29 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly/receiver__ask_3.cpp: -------------------------------------------------------------------------------- 1 | // Proposed `Receiver`-related changes to P0443 based on P1053/P1055 2 | // Reply-To: Bryce Adelstein Lelbach 3 | 4 | // Ask 3: All execution functions should take receivers. A non- or 5 | // maybe-blocking executor may choose to report system failures (shutdown, 6 | // launch failure, etc) that occur after the execution function returns via the 7 | // receiver's error method. 8 | 9 | /////////////////////////////////////////////////////////////////////////////// 10 | // Before: 11 | // `execute` takes: 12 | struct VoidNoneReceiver /* AKA a `Callable` */ { 13 | void operator()(); 14 | }; 15 | // `twoway_execute` takes: 16 | struct NoneReceiver /* AKA a `Callable` */ { 17 | U operator()(); 18 | }; 19 | // `then_execute` takes: 20 | struct Receiver { 21 | // At least one of these is required (satisfied by `Callable`s): 22 | U operator()(T value); 23 | U operator()(exception_arg_t, std::exception_ptr error); 24 | }; 25 | 26 | /////////////////////////////////////////////////////////////////////////////// 27 | // After: 28 | // `execute` takes: 29 | struct VoidNoneReceiver { 30 | // At least one of these is required (satisfied by `Callable`s): 31 | void operator()(); 32 | void operator()(exception_arg_t, std::exception_ptr error); 33 | }; 34 | // `twoway_execute` takes: 35 | struct NoneReceiver /* AKA a `Callable` */ { 36 | // At least one of these is required (satisfied by `Callable`s): 37 | U operator()(); 38 | U operator()(exception_arg_t, std::exception_ptr error); 39 | }; 40 | // `then_execute` takes: 41 | struct Receiver { 42 | // At least one of these is required (satisfied by `Callable`s): 43 | U operator()(T value); 44 | U operator()(exception_arg_t, std::exception_ptr error); 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly/receiver__ask_4.cpp: -------------------------------------------------------------------------------- 1 | // Proposed `Receiver`-related changes to P0443 based on P1053/P1055 2 | // Reply-To: Bryce Adelstein Lelbach 3 | 4 | // Ask 4: Receivers should be required to have both the value method and the 5 | // error method. P1054-style helper functions should be added to ease 6 | // construction of receivers. 7 | 8 | /////////////////////////////////////////////////////////////////////////////// 9 | // Before: 10 | struct Receiver { 11 | // At least one of these is required (satisfied by `Callable`s): 12 | U operator()(T value); 13 | U operator()(exception_arg_t, std::exception_ptr error); 14 | }; 15 | 16 | auto f = exec.then_execute([] { /* ... */ }, pred); 17 | 18 | /////////////////////////////////////////////////////////////////////////////// 19 | // After: 20 | struct Receiver { 21 | // BOTH of these are required (NOT satisfied by `Callable`s): 22 | U operator()(T value); 23 | U operator()(exception_arg_t, std::exception_ptr error); 24 | }; 25 | 26 | // Using P1054-style `on_value`: 27 | auto f = exec.then_execute(on_value([] { /* ... */ }), pred); 28 | 29 | /////////////////////////////////////////////////////////////////////////////// 30 | 31 | // NOTE: This would apply to all execution functions and all types of receivers 32 | // if Ask 3 is accepted. 33 | 34 | /////////////////////////////////////////////////////////////////////////////// 35 | // Before (with Ask 3): 36 | // `execute` takes: 37 | struct VoidNoneReceiver { 38 | // At least one of these is required (satisfied by `Callable`s): 39 | void operator()(); 40 | void operator()(exception_arg_t, std::exception_ptr error); 41 | }; 42 | // `twoway_execute` takes: 43 | struct NoneReceiver { 44 | // At least one of these is required (satisfied by `Callable`s): 45 | U operator()(); 46 | U operator()(exception_arg_t, std::exception_ptr error); 47 | }; 48 | 49 | exec.execute([] { /* ... */ }); 50 | auto f = exec.twoway_execute([] { /* ... */ }); 51 | 52 | /////////////////////////////////////////////////////////////////////////////// 53 | // After (with Ask 3): 54 | // `execute` takes: 55 | struct VoidNoneReceiver { 56 | // BOTH of these are required (NOT satisfied by `Callable`s): 57 | void operator()(); 58 | void operator()(exception_arg_t, std::exception_ptr error); 59 | }; 60 | // `twoway_execute` takes: 61 | struct NoneReceiver { 62 | // BOTH of these are required (NOT satisfied by `Callable`s): 63 | U operator()(); 64 | U operator()(exception_arg_t, std::exception_ptr error); 65 | }; 66 | 67 | // Using P1054-style `on_value`: 68 | exec.execute(on_value([] { /* ... */ })); 69 | auto f = exec.twoway_execute(on_value([] { /* ... */ })); 70 | 71 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly/receiver__ask_5.cpp: -------------------------------------------------------------------------------- 1 | // Proposed `Receiver`-related changes to P0443 based on P1053/P1055 2 | // Reply-To: Bryce Adelstein Lelbach 3 | 4 | // Ask 5: Non-dependent execution functions should take `ExecutorReceiver`s 5 | // (e.g. `Callable`s that take an `Executor` parameter) instead of 6 | // `VoidNoneReceiver`s (nullary `Callable`s). An executor is free to pass any 7 | // `Executor` to a `ExecutorReceiver`, not necessarily themselves. 8 | 9 | /////////////////////////////////////////////////////////////////////////////// 10 | // Before: 11 | struct VoidNoneReceiver /* AKA a `Callable` */ { 12 | void operator()(); 13 | }; 14 | struct OneWayExecutor { 15 | void execute(VoidNoneReceiver r); 16 | }; 17 | 18 | exec.execute([] { /* ... */ }); 19 | 20 | /////////////////////////////////////////////////////////////////////////////// 21 | // After: 22 | struct ExecutorReceiver { 23 | void operator()(Executor exec); 24 | }; 25 | struct OneWayExecutor { 26 | void execute(ExecutorReceiver r); 27 | }; 28 | 29 | exec.execute([] (Executor e) { /* ... */ }); 30 | 31 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly/receiver__ask_6.cpp: -------------------------------------------------------------------------------- 1 | // Proposed `Receiver`-related changes to P0443 based on P1053/P1055 2 | // Reply-To: Bryce Adelstein Lelbach 3 | 4 | // Ask 6: Receiver's value method shouldn't be the call operator. 5 | 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // Before: 8 | // `execute` takes: 9 | struct VoidNoneReceiver /* AKA a `Callable` */ { 10 | void operator()(); 11 | }; 12 | // `twoway_execute` takes: 13 | struct NoneReceiver /* AKA a `Callable` */ { 14 | U operator()(); 15 | }; 16 | // `then_execute` takes: 17 | struct Receiver { 18 | // At least one of these is required (satisfied by `Callable`s): 19 | U operator()(T value); 20 | U operator()(exception_arg_t, std::exception_ptr error); 21 | }; 22 | 23 | exec.execute([] { /* ... */ }); 24 | auto f = exec.twoway_execute([] { /* ... */ }); 25 | auto f = exec.then_execute([] { /* ... */ }, pred); 26 | 27 | /////////////////////////////////////////////////////////////////////////////// 28 | // After: 29 | // `execute` takes: 30 | struct VoidNoneReceiver /* AKA a `Callable` */ { 31 | void done(); 32 | }; 33 | // `twoway_execute` takes: 34 | struct NoneReceiver /* AKA a `Callable` */ { 35 | U done(); 36 | }; 37 | // `then_execute` takes: 38 | struct Receiver { 39 | // At least one of these is required (satisfied by `Callable`s): 40 | U value(T value); 41 | U operator()(exception_arg_t, std::exception_ptr error); 42 | }; 43 | 44 | // Using P1054-style `on_value`: 45 | exec.execute(on_value([] { /* ... */ })); 46 | auto f = exec.twoway_execute(on_value([] { /* ... */ })); 47 | auto g = exec.then_execute(on_value([] { /* ... */ }), pred); 48 | 49 | /////////////////////////////////////////////////////////////////////////////// 50 | 51 | // NOTE: This change makes the most sense if done in conjunction with Ask 1 and 52 | // Ask 4. 53 | 54 | /////////////////////////////////////////////////////////////////////////////// 55 | // Before (with Ask 1 and Ask 4): 56 | // `execute` takes: 57 | struct VoidNoneReceiver { 58 | // BOTH of these are required (NOT satisfied by `Callable`s): 59 | void operator()(); 60 | void error(exception_arg_t, std::exception_ptr error); 61 | }; 62 | // `twoway_execute` takes: 63 | struct NoneReceiver { 64 | // BOTH of these are required (NOT satisfied by `Callable`s): 65 | U operator()(); 66 | U error(exception_arg_t, std::exception_ptr error); 67 | }; 68 | struct Receiver { 69 | // BOTH of these are required (NOT satisfied by `Callable`s): 70 | U operator()(T value); 71 | U error(exception_arg_t, std::exception_ptr error); 72 | }; 73 | 74 | // Using P1054-style `on_value`: 75 | exec.execute(on_value([] { /* ... */ }); 76 | auto f = exec.twoway_execute(on_value([] { /* ... */ })); 77 | auto g = exec.then_execute(on_value([] { /* ... */ }, pred)); 78 | 79 | /////////////////////////////////////////////////////////////////////////////// 80 | // After (with Ask 1 and Ask 4): 81 | // `execute` takes: 82 | struct VoidNoneReceiver { 83 | // BOTH of these are required (NOT satisfied by `Callable`s): 84 | void done(); 85 | void error(exception_arg_t, std::exception_ptr error); 86 | }; 87 | // `twoway_execute` takes: 88 | struct NoneReceiver { 89 | // BOTH of these are required (NOT satisfied by `Callable`s): 90 | U done(); 91 | U error(exception_arg_t, std::exception_ptr error); 92 | }; 93 | // `then_execute` takes: 94 | struct Receiver { 95 | // BOTH of these are required (NOT satisfied by `Callable`s): 96 | U value(T value); 97 | U error(exception_arg_t, std::exception_ptr error); 98 | }; 99 | 100 | // Using P1054-style `on_value`: 101 | exec.execute(on_value([] { /* ... */ })); 102 | auto f = exec.twoway_execute(on_value([] { /* ... */ })); 103 | auto g = exec.then_execute(on_value([] { /* ... */ }), pred); 104 | 105 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_13__executors__weekly/receiver__intro.cpp: -------------------------------------------------------------------------------- 1 | // Proposed `Receiver`-related changes to P0443 based on P1053/P1055 2 | // Reply-To: Bryce Adelstein Lelbach 3 | 4 | // Lexicon: 5 | // Receiver == Promise == (Future)Continuation == Callable == Task == Work Item 6 | // E.g. the things that executors invoke. 7 | 8 | // P0443 and P1054 Type Requirements for Work Items (Status Quo) 9 | 10 | // `execute` takes: 11 | struct VoidNoneReceiver /* AKA a `Callable` */ { 12 | void operator()(); 13 | }; 14 | 15 | // `twoway_execute` takes: 16 | struct NoneReceiver /* AKA a `Callable` */ { 17 | U operator()(); 18 | }; 19 | 20 | // `then_execute` takes: 21 | struct Receiver { 22 | // At least one of these is required (satisfied by `Callable`s): 23 | U operator()(T value); 24 | U operator()(exception_arg_t, std::exception_ptr error); 25 | }; 26 | 27 | // P1054 helper functions for constructing receivers: 28 | Receiver on_value(Callable value_f); 29 | Receiver on_error(Callable error_f); 30 | Receiver on_value_or_error(Callable value_f, Callable error_f); 31 | 32 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_18__futures__weekly_us.md: -------------------------------------------------------------------------------- 1 | ISO C++ Futures 2 | =============== 3 | 4 | Weekly US Timezone Meeting - Notes - 2018-07-18 5 | ----------------------------------------------- 6 | 7 | **Kindly forgive and identify any mistakes and omissions in these notes.** 8 | 9 | Attending: 10 | - Bryce Adelstein Lelbach (Note Taker) 11 | - David Hollman 12 | - Gordon Brown 13 | - Kirk Shoop 14 | - Lee Howes 15 | - Mathias Stearn 16 | 17 | Agenda: 18 | - Debriefing from discussion of proposed `Receiver`-related changes to P0443 and P1054. 19 | - Discuss issues stemming from the above and from email. 20 | - Discuss parallel algorithms and executors interactions (David's emails). 21 | 22 | Initial PR Plan ([repository, WIP](github.com/brycelelbach/executors)): 23 | - 0) Add `None`/`SingleReceiver` concepts and replace raw callables/continuations with it 24 | - Status: Under discussion by executors group. 25 | - Futures Ask 0, [EXECUTORS ISSUE #408](https://github.com/executors/executors/issues/408) 26 | - Futures Ask 1, [EXECUTORS ISSUE #409](https://github.com/executors/executors/issues/409) 27 | - Futures Ask 2, [EXECUTORS ISSUE #410](https://github.com/executors/executors/issues/410) 28 | - Futures Ask 3, [EXECUTORS ISSUE #411](https://github.com/executors/executors/issues/411) 29 | - Futures Ask 4, [EXECUTORS ISSUE #412](https://github.com/executors/executors/issues/412) 30 | - Futures Ask 5, [EXECUTORS ISSUE #413](https://github.com/executors/executors/issues/413) 31 | - Futures Ask 6, [EXECUTORS ISSUE #414](https://github.com/executors/executors/issues/414) 32 | - 1) Add P1054 style `Receiver` wrappers. 33 | - Status: TODO. 34 | - 2) Add `None`/`SingleSender` concepts. 35 | - Status: TODO. 36 | - 3) Replace `then_execute` with `via_execute` or an alternative. 37 | - (as member function or property, transition to a customization point later). 38 | - Status: TODO. 39 | - 4) Refactor two-way execution in terms of one-way execution. 40 | - Status: TODO. 41 | - 5) Refactor bulk execution in terms of one-way execution. 42 | - Status: TODO. 43 | - 6) Replace executor concepts with sender concepts. 44 | - Status: TODO. 45 | 46 | ### Discussion 47 | 48 | David, Bryce: We should have a lexicon document. 49 | 50 | #### Debriefing from discussion of proposed `Receiver`-related changes to P0443 and P1054 51 | 52 | **Ask 1:** A receiver's error method should take a generic error type instead of an `std::exception_ptr`. 53 | 54 | [EXECUTORS ISSUE #409](https://github.com/executors/executors/issues/409) 55 | 56 | - Mathias: We may want an infallible future. 57 | - Kirk: Haven't written up what the concept tree for something like this would look like. I don't think it's a good design. 58 | - Mathias: Subsumption goes in the opposite direction than for `Receiver`s. 59 | - David: How about having an `AlwaysFallible` and a `Fallible`, and then having futures be a composition of the two? 60 | - Kirk: This differs from the default in C++. 61 | - David: Default in C++ is to propagate errors up if they are uncaught. 62 | - Kirk: You need this to be able to propagate. 63 | - Gordon: Receiver needs to always be able to accept an error, but won't always receive one. 64 | 65 | 66 | **Ask 4:** Receivers should be required to have both the value method and the error method. 67 | 68 | [EXECUTORS ISSUE #412](https://github.com/executors/executors/issues/412) 69 | 70 | - Bryce: Right now the default is to propagate errors if no error method is required. 71 | - Bryce: Why should we require an error method instead of defaulting to propagate. 72 | - Kirk: If you librarize your `Receiver`s, you probably have to deal with this in one place, and you probably had to anyways. 73 | - Mathias: It's valid to pass a `FallibleReceiver` to an `InfallibleSender`, but not vice versa. 74 | - Bryce: Do all `Executor`s, even those that do not care about errors, have to propagate today? Do we want that requirement? Or do we want it to fail to compile? 75 | - Mathias: I think it should fail to compile. 76 | - Gordon, Bryce: I agree. 77 | - **Consensus: Try out Mathias' `Fallible`/`Infallible` approach.** 78 | 79 | 80 | **Ask 5:** Non-dependent execution functions should take `Callable`s that take an `Executor` parameter instead of nullary `Callable`s. 81 | 82 | [EXECUTORS ISSUE #413](https://github.com/executors/executors/issues/413) 83 | 84 | - **Lee: Working on examples.** 85 | - Bryce: Will add an action item for you. 86 | - David: This may be needed for sub-executors. 87 | - Bryce: One thing to note is that IIUC Jared's Agency passes through a descriptor of the shape of execution agents to its `Receiver`. 88 | - Bryce: This would also allow asynchronous `Receiver`s to place requirements on their executor. 89 | - Bryce: Maybe these would be spelled as two methods, but I'd imagine that calling `submit` on a `NoneReceiver` would execute it and calling `submit` on an `ExecutorReceiver` would enqueue work. 90 | - Kirk: Work construction is done in the constructor of the sender. 91 | - Bryce: Not sure /that/ fits my mental model, but will think about it. 92 | - Bryce: New thread executor from pushmi. 93 | - Kirk, Bryce: You get stuff out of futures by calling `submit`. 94 | 95 | #### (from David) Generic parallel algorithms 96 | 97 | - David: Started trying to write quicksort 98 | - David: Need to be more specific about unwrapping. 99 | - David: Only works if I have share? 100 | - Bryce: Ah, the fundamental problem is you need to fork generically? 101 | - Mathias, Bryce: Can't you do this with continuations? You receive the value in some continuation, then you fork there by calling one-way execute twice. 102 | - Kirk: Maybe we should look at the alternative 103 | 104 | Bryce: I need to make sure to share meeting notes, etc. Will be doing some logistics/project management stuff on the GitHub later today. 105 | 106 | Bryce: We should have a resource list of gists, prototype implementations, etc. 107 | 108 | ### Open Questions (Not Addressed at This Meeting) 109 | 110 | - Semantics of `via_execute`. 111 | - Distinction between via and `via_execute`. 112 | - `BulkReceiver` vs bulk operator. 113 | - Implications of `Executor`s and `Future`s satisfying the same concepts. 114 | - (from Gordon) Distinguishing between `Executor`s (which have properties) and `Future`s (which don't). 115 | 116 | ### Action Items 117 | 118 | - (Bryce) Upload and organize meeting notes. 119 | - (Bryce) Create executor GitHub issues for the proposed `Receiver`-related changes. 120 | - (Bryce) Create lexicon document. 121 | - (Bryce) Create resources document. 122 | - (Mathias) Work on Ask #4. 123 | - (Lee) Work on Ask #5. 124 | 125 | 126 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_20__executors__weekly.md: -------------------------------------------------------------------------------- 1 | # ISO C++ Executors 2 | 3 | ## Weekly Meeting - Notes - 2018-07-20 4 | 5 | **Kindly forgive and identify any mistakes and omissions in these notes.** 6 | 7 | #### Attendance 8 | 9 | - Jared Hoberock (Note Taker) 10 | - Chris Kohlhoff 11 | - Kirk Shoop 12 | - David Hollman 13 | - Carter Edwards 14 | - Lee Howes 15 | 16 | #### Agenda 17 | 18 | - Continue discussion of P0443/P1055 unification. 19 | - Discuss `DependencyId` model (alternative to futures). 20 | - Review execution context [EXECUTOR PR #402](https://github.com/executors/executors/pull/402). 21 | 22 | #### Summary 23 | 24 | Today we discussed two different ideas for reducing the number of execution functions. 25 | 26 | - Jared presented a prototype of ChrisK's idea of introducing DependencyIds, intended to be a property-based, primitive substitute for full-fledged Future types. 27 | - Lee presented a prototype built on top of pushmi which exposed different task 28 | types via customization points. 29 | 30 | There was more support for Lee's direction if it could be expressed via P0443's existing properties mechanism. 31 | We will follow up on this direction next week. 32 | The hope is that it will allow P0443 to define a single .execute() function and allow different types of tasks to be introduced via the properties extension mechanism rather than through different executor member functions. 33 | 34 | We also discussed Carter's outstanding [EXECUTOR PR #402](https://github.com/executors/executors/pull/402) on execution contexts. 35 | 36 | ### Discussion 37 | 38 | #### P0443/P1055 Unification 39 | 40 | Jared: *presents dependency id experiment* 41 | 42 | Kirk: Querying for an id is racy 43 | 44 | Jared: Ok, noted 45 | 46 | Chris K: If the property is not present, then it doesn't actually affect semantics 47 | 48 | Lee: Building dependencies is required? if you can't enforce an ordering, you can't relax 49 | 50 | Chris K: We've been told that the lazy future stuff can be built on top of oneway 51 | 52 | Chris K: you'd do lazy chaining in the higher level lib. the problem we're trying to solve: Cuda has dependency support in the runtime 53 | 54 | Chris K: We're trying to provide a way to communicate through to the runtime 55 | 56 | Chris K: The way i'd expect it to work is that it's not required to be present to work 57 | 58 | Chris K: So you could still write a lazy-based chaining library 59 | 60 | Lee: That's true. allows you to greedily chain into dependencies 61 | 62 | Chris K: This does allow some easure of lazy as well. require still has an opportunity to xfrm the type 63 | 64 | DavidD: But query doesn't, right? that's one concern i'd have. if you query for the token you can't put a non type erased continuation 65 | 66 | Chris K: I'd have to have a look at the example programs. i'm not sure if there's a need to query for an ID or not 67 | 68 | Chris K: If we have a separate signalling factory, it may be that that's not required 69 | 70 | David: You'd still need to 71 | 72 | David: The signaler factory needs to take a continuation to run to do the type erasure 73 | 74 | David: You have to generate the token in the same statement as when you submit the continuation. 75 | 76 | Gordon: This is why i was thinking .execute() could return the id itself. you could have execute return a continuation that could be passed as a contnuation to another executor 77 | 78 | Chris K: Just an idea -- not intended to be necessary 79 | 80 | Chris K: I'll go away and experiment with it. this is predominantly about supporting the runtime dependency stuff that runtimes have indepdently of high evel chaining libs 81 | 82 | Matthias: What is the difference b/t a dependency id and a future with no public methods 83 | 84 | David: By no public method, i think you mean no concept enforced methods 85 | 86 | Kirk: Something that seems to be lost here - i dont know how to characterize it, could be a loss of fwd progress if signal never gets called 87 | 88 | Kirk: The thing that used the depends_on is just blocked forever 89 | 90 | Mathias: Isn't that also a problem with P1055 receivers? if you don't call it you lose fwd prog 91 | 92 | Kirk: The difference here is that you dont start the work until you give a receiver. in this case, you do start the work and you wait for the receiver 93 | 94 | Jared: What do you mean by start and then wait? 95 | 96 | Kirk: This allows deadlocks without calling signal. in P1055 there's no deadlock unless you call the receiver 97 | 98 | Mathias: Calling the .signal() method to me ffels exactly isomorphic to .value() or .done() or whatever on a Receiver 99 | 100 | Mathias: The fact that it is possible to forget to call it, feels concerning to me as well but it seems 101 | 102 | Chris K: This not aimed at end users so much 103 | 104 | David: That's entirely the same thing bc the P0443 proposal targets a slightly lower level than the P1055 proposal. so i'd say the logic applies to both cases 105 | 106 | David: I dont think most users should call either of these methods 107 | 108 | Kirk: Just pointing out that we'd lose the guarantee from P0443, it could be reintroduced at a higher level 109 | 110 | Mathias: It could also be guaranteed via a dtor of a signaller 111 | 112 | Kirk: Dtor signalling is a real problem 113 | 114 | David: You can't do things youre not supposed to do by boost blocking 115 | 116 | Kirk: You can only use TLS to communicate state 117 | 118 | Mathias: Signal() has no args, that's already the case 119 | 120 | Kirk: There's a difference between a method called .signal() and a dtor 121 | 122 | David: You can trampoline 123 | 124 | Kirk: The only way to trampoline is to queue something somewhere, and i wouldn't want to queue from a dtor. the dtor is not a place to do work, it's a place to release resources 125 | 126 | Mathias: I know a lot of algos build on folly future do work in dtors 127 | 128 | Kirk: I cant defend that 129 | 130 | Lee: I cant think of any examples off the top of my head 131 | 132 | Mathias: I'll find a link 133 | 134 | Jared: Do people think this is at least worth exploring? 135 | 136 | David: It's pretty close to a NoneReceiver. 137 | 138 | Kirk: NoneSender as .submit() method with a NoneReceiver. this is nothing like that 139 | 140 | David: If you really stripped it way down, it has analogies to promise_factory or promise_contract ideas from earlier 141 | 142 | David: I understand some of the particulars are different. it'd be helpful to know how the particulars differ 143 | 144 | David: I want to understand those tradeoffs. that's my own homework 145 | 146 | Lee: As a mechanism for avoiding Futures in this paper, i think this is a reasonable starting point for discussion. it will need a lot of clarity on how we really expect to efficiently pass values & errors around 147 | 148 | Lee: Would we always hang this stuff off the side in shared state? everything else is sidebanded? that sounds like it could become expensive in practice 149 | 150 | Gordon: From this as a starting point, we could make these ids richer types 151 | 152 | Lee: At which point you're approaching Future. or should we just close the gap b/t executors & futures 153 | 154 | Gordon: on a related note: Is it correct to draw an analogy between the task and the receiver in P1055? 155 | 156 | Kirk: I dont see the connection 157 | 158 | Gordon: So .signal() would be like .set_value() or .done() on a reciever? 159 | 160 | Kirk: If you think of .signal() as .done(), it does map task_a to being a subset of a NoneReceiver that only has .done() on it 161 | 162 | David: That's what i was trying to get at. what is lost there? and what are we losing functionally by losing those kinds of pieces? 163 | 164 | Mathias: One thing i noticed that is different. these are only able to handle the equivalent of a future they are not able to do a future 165 | 166 | Mathias: They're not able to propagate data. that leaves the user to do it another way, which we've found problematic 167 | 168 | Chris K: That's the intention, that you would build up futures on top of it. inside of ctl structures, you'd use these properties to communicate info through to the executor 169 | 170 | Chris K: It's not intended to give you semantics that you can universally rely on. 171 | 172 | Chris K: I would expect you to go away and write Futures that would still work just fine if you were never given an executor that could support these 173 | 174 | Lee: If all executors can handle is ordering of tasks and can't optimize the way data flows between then, that's a limited set of capabilities 175 | 176 | Lee: Id question whether we really get much value out of that in the end 177 | 178 | Lee: It covers that limited case where you use these tokens to build a work queue and allocation happens out of band 179 | 180 | Lee: But you lose the ability of an executor to participate in optimization 181 | 182 | Lee: Youd have to question whether the executor itself adds much value there at all 183 | 184 | Chris K: Jared's written some examples, we dont have an answer right now. taking things like a really lightweight future chaining library 185 | 186 | Lee: That's fair, theres nothing wrong with exploring this. maybe you can show that there's significant 187 | 188 | Jared: The whole idea of this proposal is to eliminate functionality -- the extra executino functions 189 | 190 | Lee: Can i run you through some example code that would take a different tack? 191 | 192 | Lee: This is in issue #417 193 | 194 | Lee: What i am getting at here -- without chaning the concepts in pushmi, we just make some small changes so that rather than special casing bulk, we dont necessarily special case bulk for asynchrony 195 | 196 | Lee: The way we can achieve this on top of pushmi -- pushmi passes values through as each task completes 197 | 198 | Lee: That's what futures do traditionally. what you really need to do for gpus - when you chain using futures, you need to hook inside and ask what is the sync prim i actually need here 199 | 200 | Lee: The actual value is going to be needed at the end 201 | 202 | Lee: This is a trivial task that adds 1, it 203 | 204 | Lee: This code is going to run ahead as scalar code, it's not going to wait for the actual code to complete. it'll convert this code into a token 205 | 206 | Lee: *continues describing code example* 207 | 208 | Lee: Rather than trying to encode dependency magic in the executor, we can use the executor as the paramterization hook in customization points outside the executor 209 | 210 | Lee: We'd just define all sorts of free functions. we dont need to hook up magic on the executor 211 | 212 | Gordon: Can we see async_fork to see how it works 213 | 214 | Lee: *describes async_fork impl* 215 | 216 | Gordon, Lee: At the point of the fork, we start to go through the queue as if they're all ready 217 | 218 | Lee: Until you get to the join at which point the entire chain waits on the previous 219 | 220 | Lee: This isn't very long, hopefully it's 221 | 222 | Jared: I'm having trouble understanding how 443 woudl change to support what you'd like to do here 223 | 224 | Lee: We'd pull it down to the set of Concepts Kirk wants 225 | 226 | Lee: We'd add the explicit task construction functions 227 | 228 | Lee: Task factory functions with well=defined sets of customization points for this type of task 229 | 230 | Jared: Its the refactor of multiple execution functions into a single function + task factories? 231 | 232 | Lee: Yes 233 | 234 | Mathias: What if the shape is a million? 235 | 236 | Lee: Bulk is very much like an openmp parallel for 237 | 238 | Lee: I've just implemented a for loop here. a new thread version could issue n threads where n is the number of cores and split the loop up 239 | 240 | Lee: The cuda one would just run the cuda task 241 | 242 | Mathias: You still need to customize the bulk behavior for each executor, right? 243 | 244 | Lee: Sure 245 | 246 | Mathias: What's the advantage of the extra indirection versus just a method? 247 | 248 | Lee: Scalability later. it's much easier to introduce new types of tasks later 249 | 250 | Jared: People who arent paying much attention will only see .execute() 251 | 252 | Chris K: This really is just another spelling of properties. it's the same -- we've chosen to make bulk submission a member function, but that's just how we've defined the property 253 | 254 | Lee: It's more common in existing libraries 255 | 256 | Chris K: Right, but the properties are our way of exposing this type of thing 257 | 258 | Jared: We've explored task factories earlier, chrisk was worried about polymorphic executors 259 | 260 | Chris K: Right. if we're creating some task with a special interface, and then you take that object and pass to .execute(), how does the poly wrapper preserve that inerface in the common case such that it gets to the underlying executor with those interfaces still avaiable? 261 | 262 | Lee: You'd have to erase the token type and task type. i dont think this is any different than type erasing each method of the poly wrapper 263 | 264 | Chris K: Right, that's something we're trying to address by moving things into properties 265 | 266 | Lee: Yeah. if you want to represent the customization point as properties, you could do that. the only way to get the type safety back is to expose a lot of types through its interface 267 | 268 | Chris K: Right now, the wrapper takes a set of property types 269 | 270 | Lee: Yeah, but if you're going to type erase the fucntions passed to builk_execute, either you're going to have to pass type erased functions in there and unpack or expose all the potential types you're going to pass in 271 | 272 | Chris K: That's the exact problem of having separate task factories 273 | 274 | Lee: I'm not clear if you do type erase those 275 | 276 | Mathias: Does your objection go away if tasks are objects? 277 | 278 | Chris K: I'll have to go back and look at the email jared sent 279 | 280 | Mathias: If they're objects with methods, we can erase those 281 | 282 | Lee: The tasks are objects with methods 283 | 284 | Jared: *shares task factory idea* 285 | 286 | Gordon: I think oen of the concerns i have. if we had a single .execute(), would the tasks you create self-contained? or would executors still have to have knowledge? 287 | 288 | Lee: In the design i shared, the tasks are not self-contained. they hook into the executor they are run on using customization points 289 | 290 | David: In the polymorphic case, this is 2x type erasure because you have two separate functions that do this 291 | 292 | Lee: It may be 2x erasure, if you can limit the set of types available to the code, the point in the code where the type of the executor is checked is in the type of the task 293 | 294 | Lee: It's right there in the middle of the code with the executor type. but somehow you're going to have to get to the fundamental set of types 295 | 296 | Lee: At that point as soon as you choose the right type, all of the code is visible, but you've tied the two things you need for efficient compilation together at the heart of it 297 | 298 | Lee: There might be other ways to hook thorugh the poly wrapper, but i dont see how 299 | 300 | Chris K: Right now, if this code had like a task.submit() instead of ex.execute(task) at the end, then it would work 301 | 302 | Chris K: We already have the mechanism for erasing properties 303 | 304 | Mathias: I think there's at least three or four erased things here if i count right 305 | 306 | Mathias: What are we gaining over having a method that can be virtual if you want it to be? 307 | 308 | Lee: A method on what? if you construct a task against an executor, then type erase it, and then call submit so that you're later submitting it 309 | 310 | Lee: You've lost your ability to defer the choise of executor 311 | 312 | Lee: If you have a virtual interface to the taskk you've lost the ability to type erase the executor 313 | 314 | Mathias: What do you mean by unpack? 315 | 316 | Lee: If you need type info on the executor, it's gonna have to be erased on the way in and then selected from a list of types inside 317 | 318 | Mathias: That's why a method makes this much simpler 319 | 320 | Lee: You can't template the virtual method in any meaningful way 321 | 322 | Lee: You can't pass one or other to the other's virtual method 323 | 324 | Mathias: I think you can do it if the virtual one is over void 325 | 326 | Lee: But then how do you get the untype rased task back later? 327 | 328 | Mathias: If you already type erased at both ends, i dont think you can get back 329 | 330 | David: Some of this has to do with free functions, right? instead of semicolons 331 | 332 | David: Some of this has to do with exposing the sequence of things to the executor all at once 333 | 334 | Lee: No, one statment doesnt matter 335 | 336 | Lee: You could erase the whole sequence of statements, but that doesnt hep you if the executor was erased at the start 337 | 338 | Chris K: Going back to lee's code. what you've wrote could be expressed with properties right now 339 | 340 | Chris K: You could use properties to select what youre calling customization points, and it works out of the box with our existing poly wrapper 341 | 342 | Lee: I'm unconvinced the polymorphic wrapper actually works. but yes, other than that the properties do work for this 343 | 344 | Lee: This is like saying we could have c++ objects implemented with maps as properties, but it's a nonstd way of doing it 345 | 346 | Chris K: It really is just a different spelling. with your bulk example, at some point you have to call a function with a particluar signature 347 | 348 | Chris K: At some point, someone who wants to customize for their gpu executor, has to write that code 349 | 350 | Chris K: What i'm saying is that that signature could be represented as a property in our model 351 | 352 | Lee: I acknowledge that 353 | 354 | Chris K: So why not do that? anything you'd like to customize with an executor you could do so by writing a property 355 | 356 | Chris K: At some point, you've got to write specific code on a specific executor. 357 | 358 | David: Chri's point is that properties are more general than a number of ways than free function customization points 359 | 360 | David: Properties are a grouping of ways to establish customizations 361 | 362 | Kirk: So that justifies making it a completely separate paper that can be sued in general? 363 | 364 | David: Thats interesting, but the urgency of the executors effort supercedes that in this case. i do want that paper to be written, and would like this mechanism more broadly used 365 | 366 | Kirk: I agree this should be a separate paper, esp if theres a possibility that this mechanism could affect the standardization of executors as a whole 367 | 368 | Lee: What if the big LEWG objection to executors was properties? 369 | 370 | David: I dont htink that will happen 371 | 372 | Jared: I dont recall any LEWG objections to properties 373 | 374 | Gordon: I think as well that properties have been shown to be an effective mechanism. there have been a lot of follow on papers that propose additional propertiesk 375 | 376 | Chris K: I think a good way forward is to take what you've shown lee and describe them in terms of properties 377 | 378 | Chris K: Technically, moving bulk into a property feels like a trick 379 | 380 | Lee: It makes more difference when you start building futures on top. from futures POV, bulk is no different from non-bulk 381 | 382 | Chris K: If we choose to put these more exotic functions inside of properties, we haven't really reduced the number 383 | 384 | Jared: I think everyone agrees that factoring bulk into a property does not actually reduce complexity 385 | 386 | Gordon: The approach of having task factories would solve what LEWG was looking for to reduce the number of functions 387 | 388 | Gordon: I'd have to see it, but if the tasks were self contained, then i could see the poly executor still working 389 | 390 | Gordon: Bc then you could rely on a std interface. as long as they could be separated, but i'm not sure that's possible 391 | 392 | Kirk: I belive that would be exatly as capapble as the existin bulk_execute through the poly wrapper, but it would be incredibly instructive to see bulk_execute for something other than cpu 393 | 394 | Lee: I cant picture what the code would look like 395 | 396 | Lee: Because of the type erasure of the functions, i dont understand how to write bulk_execute on the poly wrapper that successfully runs a cuda task on a cuda executor underneath 397 | 398 | Jared: P0443 as it stands now or after some delta? 399 | 400 | Chris K: This is just a problem in general. 401 | 402 | Lee: Yeah, it applies to both 403 | 404 | Chris K: I thought early on that the poly wrapper was intended for cpu-based uses and we weren't trying to make it work for other platforms 405 | 406 | Gordon: I'm not sure i entirely understand the problem with P0443 poly wrapper. we have the different execution functions. each of those knows 407 | 408 | Gordon: Why do you have to erase the callable of the task? 409 | 410 | Lee: You can't template the interface of the underlying executor 411 | 412 | Chris K: As it stands, i think that's correct the LUT approach may work in either case 413 | 414 | Chris K: It is a more general problem than our poly wrapper 415 | 416 | Lee: It is. i think the LUT approach becomes easier if you wrap the whole task and do the lookup inside it than trying to erase the execution functions. or maybe not 417 | 418 | Chris K: If we said that the single function was the rawest primitive only, and task factories themselves provide extended submission functions, then i htink we could make that work? 419 | 420 | Jared: Who would like to prototype this refactorization? 421 | 422 | Chris K: XXX i'll have a go at hacking something up for bulk, i suppose 423 | 424 | Lee: What do yoj mean by extended submission methods? 425 | 426 | Kirk: If we're only going to have a single function, and do everything else through properties equivalent to existing poitns in the language 427 | 428 | Kirk: It doesnt make sense why we'd try to force the property function with the single function 429 | 430 | Chris K: I dont understand the last bit 431 | 432 | Kirk: Properties should be extracted frmo P0443 433 | 434 | David: The argument is that execution is more important -- that's why it deserves searching out of this more general mechanism 435 | 436 | Lee: what kirk's saying: Wouldn't it be an easier road to define the fundamentals to get this done in terms of customization points? 437 | 438 | David: Properties are the customization point 439 | 440 | Kirk: Once we have a requires paper that is accepted, it is always possible to add that support 441 | 442 | Jared: Pragmatically, i dont think jettisoning properties will increase consensus 443 | 444 | Chris K: No, not at all 445 | 446 | David: I agree 447 | 448 | Chris K: If properties never went any further than executors, that's not a problem 449 | 450 | Gordon: Yeah, i kinda feel like the properties mechanism is the fundamental way that we've supported features through executors. losing that would lose sight of that original purpose 451 | 452 | Gordon: Particuarly for our case, removing properties would preclude a lot of our use cases 453 | 454 | Mathias: Are we describing what is currently on the screen? 455 | 456 | Chris K: Almost. the difference would be the last line. it wouldn't go through the single execute method at that point 457 | 458 | Mathias: So at that point you're submitting your jobs through a property 459 | 460 | Mathias: Why isnt execute a property? 461 | 462 | Chris K: It could be. they could all be properties. i dont know what i think of that either 463 | 464 | Lee: One difference between that and what i proposed is that my task definition which accepted values at the end was sitting above all of this 465 | 466 | Lee: The trigger onto the executor was happening when the incoming dependencies of the task was satisfied 467 | 468 | Lee: Constructing the whole task and passing it to execute and then trying to unpack that again, there's still a difference in the encoding 469 | 470 | Lee: I can still imagine doing exactly what i proposed through properties as well 471 | 472 | Jared: Anything more to say on this topic? 473 | 474 | 475 | #### Execution Context EXECUTORS PR #402 476 | 477 | *Wording review of [EXECUTORS PR #402](https://github.com/executors/executors/pull/402)* 478 | 479 | Jared: Remind us what we want to do with this PR 480 | 481 | Carter: To clarify what an EA is, its relation with the callalbe, its relationship with an executor, and then responsibilities for what entity manages the callables 482 | 483 | Carter: Qotes the def of EA in [thread.req.lockable.general] 484 | 485 | Carter: For the purposes of this library, an EA is the thing which invokes the callable to get it to run 486 | 487 | Carter: Within an execution context -- such as a calling thread or thread pool 488 | 489 | Carter: An executor is a thing which submits a callable to a context to be invoked by an EA 490 | 491 | Jared: Does everyone agree a thread is an execution context? 492 | 493 | David: I'm concerned with that in the context of subexecutors. id think you'd often want executors to have inheritance strucutre in terms of subexecutor relationships that might have a common context 494 | 495 | David: It makes sense for the context to be the same in all of them, but the place in the hierarchy 496 | 497 | Carter: How is that incompatible? 498 | 499 | David: Youd want a thread_pool_executor and a thread_executor to both agree on a thread_pool context 500 | 501 | David: I dont think we want hierarchies of executors to directly match hierarchies of contexts 502 | 503 | Carter: Nothing in there says that. an inline_executor's context is tht thread in which the executor is running 504 | 505 | David: I agree with that 506 | 507 | Chris K: Would it help to make the such as a note or example? to remove it from normative text? 508 | 509 | Carter: Such as is non-normative 510 | 511 | Chris K: Maybe move the examples into the note 512 | 513 | David: I agree 514 | 515 | Gordon: I think incuding this in the normative wording goes down the road of defining what an execution context is 516 | 517 | Mathias, Jared: I agree, i would not identify an EA with a thread 518 | 519 | Gordon: From that paragraph from the std, i looked into it. i think it's a defect 520 | 521 | Jared: Could we avoid copy pasting the definition from the std which we think may have a bug in it 522 | 523 | Carter: I put that in here to point out that defect :-) 524 | 525 | Gordon: I was thinking of filing a defect report for this as a separate issue 526 | 527 | Carter: I'd be happy to do that. let me know why you file it, we can cite it 528 | 529 | Gordon: XXX I'll ping you when i do 530 | 531 | Carter: Clarifying that an executor submits callables with properties to a context to be run at some point by an EA 532 | 533 | Carter: This third paragraph gets a handle on the lifetime of an EA 534 | 535 | Carter: The observable lifetime of an EA begins immediately before the callable is invoked and ends immediately after it completes 536 | 537 | Carter: So anything affiliated with that agent does not outlive the callable 538 | 539 | Mathias: Does it extend to the callable's destructor? 540 | 541 | Jared: I dont think so 542 | 543 | Carter: Immediately after the invocation of the calalbe completes? 544 | 545 | David: What's the distinction between agent and callable? 546 | 547 | Carter: When you have a callable which is going to be called many times, but each time it has a different EA calling it 548 | 549 | Chris K: Is the word "immediately" too tight? 550 | 551 | Carter: Some imaginable scenarios. if you have a callable running in bulk by many EAs, in the dtor of the callable, there is no EA to query its TLS 552 | 553 | Carter: It's not there 554 | 555 | Chris K: There's a difference in saying we don't guarantee, but we say nothing about saying whether it's there or not 556 | 557 | Carter: It's the association with the callable that is broken after the invocation of the callble 558 | 559 | David: In that case, agent LS is the same as a stack frame 560 | 561 | Mathias: I thought one of the points of ALS was to break lifetime requiresments. you'r required to construct and destroy them, for each thread youre not able to reuse them 562 | 563 | Carter: Why is it undesirable for ALS to be created & destroyed with each EA? 564 | 565 | Mathias: If i have a thread pool, i may want to create one object for each EA 566 | 567 | Jared: Then you want TLS, not ALS 568 | 569 | Chris K: Can i suggest that maybe "observable" is too strong? 570 | 571 | Chris K: If you try and do something in the dtor, then you're past the end of the lifetime of the EA 572 | 573 | Mathias: But the behavior of dtors and ctors is defined 574 | 575 | ChrisK: We're talking about the liftime of the EA which encapsulates the invocation 576 | 577 | Mathias: Maybe i'm missing what an EA is 578 | 579 | Chris K: It's a scope for the properties which apply to the invocation like prioerity, blockingk etc. it defines the scope of the application of properties that were established upon submission 580 | 581 | Carter: Yes, & for like bulk it establishes an id 582 | 583 | Gordon: The EA also represetnts its place within a parallel execution as well. once you launch a parallel algo you have multiple EAs 584 | 585 | Carter: It's possible that the only thing you can see of a bulk-launched agent is its id 586 | 587 | Gordon: one thing that comes to mind: If we're discussing whether this lifetime includes the dtor of the callable. does the EA include the destruction of the object as well? 588 | 589 | Chris K: I dont want to require the dtor to be within the lifetime. i wonder whether or not to require the dtor to be observably outside the ifteime 590 | 591 | Gordon: Makes sense 592 | 593 | Jared: Agree 594 | 595 | David: I dont understand what we mean by lifetime -- it's deifned by ctor/dtor 596 | 597 | Gordon: It defines the lifetime of the callable object when it will be invoked 598 | 599 | Carter: heres some of the implictions: You cant find out the identity of an EA outside of the callable invocation 600 | 601 | Carter: You cannot access ALS outside the lifetime of the callable invocation 602 | 603 | Jared: David, can you suggest an improvement which would avoid ambiguous use of "lifetime"? 604 | 605 | David: XXX i can try 606 | 607 | Carter: The ctor for the callable (its spelled out later) the ctor of the callable is invoked at the time of submission 608 | 609 | Carter: It is run by the submitter's agent. wherever execute is run, that's where the ctor is run 610 | 611 | Jared: I'd suggest the dtor runs on an unspecified agent. there's nothing useful you can do in the dtor without the agent's ID, and that is unavailable 612 | 613 | Carter: Yep 614 | 615 | ### Action Items 616 | 617 | - (Chris K) Prototype a mechanism for exposing task factories via properties with particular attention paid to their interoperability with polymorphic executors 618 | - (Gordon) File a defect report against the standard's definition of execution agent 619 | - (Carter) Update [EXECUTORS PR #402](https://github.com/executors/executors/pull/402). 620 | 621 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_25__futures__weekly_us.md: -------------------------------------------------------------------------------- 1 | ISO C++ Futures 2 | =============== 3 | 4 | Weekly US Timezone Meeting - Notes - 2018-07-25 5 | ----------------------------------------------- 6 | 7 | **Kindly forgive and identify any mistakes and omissions in these notes.** 8 | 9 | Attending: 10 | - Bryce Adelstein Lelbach (Note Taker) 11 | - David Hollman 12 | - Gordon Brown 13 | - Kirk Shoop 14 | - Lee Howes 15 | - Mathias Stearn 16 | 17 | Agenda: 18 | - Discuss task factory model and issues relating to it. 19 | 20 | ### Discussion 21 | 22 | David: `Adaptor` is using the call operator, perhaps it should be called 23 | something else. 24 | 25 | Bryce: I don't see `Adaptor` as fundamental. 26 | 27 | David: It's as fundamental as `future::then`. 28 | 29 | Bryce: I don't see that as fundamental. 30 | 31 | *David, Kirk try to explain `Adaptor` to Bryce.* 32 | 33 | Gordon: Not clear how you pass through an `Executor` through an entire execution chain. If I have to pass through both a result value and an executor, what do you do? I tried just sending through a pair, but it didn't seem clean. 34 | 35 | Kirk: Two ways to do this in pushmi. What you described, and something like the trampoline executor. 36 | 37 | Gordon: Concerned that the model may embed an implicit barrier. What if you want to do task fusion? 38 | 39 | Lee: But if you're going to wait for the predecessor value before you launch, then you need it anyways. 40 | 41 | Gordon: If the design is intended to pass the executor through to the receiver, what are we supposed to do? 42 | 43 | David: For example, if you're using a thread pool executor, the application code needs to know what thread it's executing on. 44 | 45 | Kirk: You can do that with thread local storage. 46 | 47 | Bryce, David: No, not all executors invoke new threads. 48 | 49 | Bryce: What about a GPU executor? Or an HPX executor? 50 | 51 | Lee: All C++ code runs in some thread; thus all executors run in some thread. 52 | 53 | Bryce: Ah, yes. I see what you're saying. 54 | 55 | David: It's the moral equivalent of setting a TLS variable, then calling a function that reads it, then reseting it afterwards, instead of changing the signature of the function. 56 | 57 | David: How does this work for generic code? 58 | 59 | Kirk: The trampoline executor is just one concrete example, the fact that it uses TLS is an implementation detail. You could write your own that uses it's own TLS. 60 | 61 | Mathias: If you have multiple TLS varibles, how do you know all the ones that you have to update? 62 | 63 | Bryce: Trampoline is just used for a deferred submission model. It's not necessarily universal. 64 | 65 | David, Mathias: Not clear how this works for nested executors or thread pool executors. 66 | 67 | Bryce: There is a distinction between a Receiver that expects an Executor (and thus does an asynchronous launch) and a Receiver that expects a value (e.g. dependent execution). Gordon is saying you might need both an Executor and a value in the case of asynchronous dependent execution (which is not something that pushmi normally does). 68 | 69 | Bryce: This makes me think there may be a dichotomy here, and perhaps we should explicitly have different interfaces for asynchronous launch (e.g. executors) and dependent execution (e.g. futures). 70 | 71 | David: How about adding a Sender argument to set_value and set_error, indicating which executor to run on it. Kirk, you indicated this was problematic. Why? 72 | 73 | Kirk: There's nothing that prescribes that the Sender you're passing in is an executor, which would make this weird. 74 | 75 | Bryce: I think Kirk finds this odd because futures and executors are the same thing in the pushmi model, so you can't really add an "executor" only parameter. 76 | 77 | Gordon: Right, but what if you need to support an eager submission model? In that case, you need this. If we can't do that in the current model, then maybe executors shouldn't be futures. 78 | 79 | David, Mathias: Maybe executors should be things that make Senders. 80 | 81 | Bryce: So maybe executors should be Adaptors? 82 | 83 | Mathias: No, you need something different. Adaptors are like then_execute, they take a sender. The fundamental thing is something that is one way. 84 | 85 | Gordon: This would sort of look like the "executors as task factories" model, similar to Bryce's PR against P0443. 86 | 87 | David: Are tasks receivers? Or senders? 88 | 89 | Bryce: Right now, we're thinking of tasks as receivers. 90 | 91 | Kirk: They could also be senders. 92 | 93 | Bryce: We could think of tasks as another, third thing, which can be represented as a receiver or as a sender. 94 | 95 | Bryce: E.g. you can have a receiver that models/contains a task. 96 | 97 | David: Can we define a new concept for executor? 98 | 99 | Bryce: Something like Adaptor, with a method `execute` that returns a Sender and takes... a Receiver? Nothing? 100 | 101 | Mathias: I think it would have to be nullary. 102 | 103 | Kirk: I imagine adding some mechanism to a sender that would let you get a task factory from it. 104 | 105 | Kirk: A nullary method on senders that returns an ExecutorFactory that has a method that makes a sender. 106 | 107 | Mathias: https://gist.github.com/RedBeard0531/1fb12c3bd9c469a8549bab6c9e22492b#file-lazy_futures-h-L430-L476 108 | 109 | Bryce: That looks a lot like `make_promise_contract`. 110 | 111 | Mathias: It is. 112 | 113 | Kirk: What happens if you attach a continuation afterwards? 114 | 115 | Kirk: `Lifter`s - functions that take receivers and return receivers. Not in pushmi yet. 116 | 117 | Mathias: This model pushes at the end. 118 | 119 | Mathias: The `promise` is only produced by rvalue-reference qualified methods. 120 | 121 | Kirk: What does completion/continuation attachment? 122 | 123 | Mathias: `get`. 124 | 125 | Mathias: I found that when working with multiple future types, I needed type erasure. 126 | 127 | Kirk: Showed how you can avoid this in an email thread. 128 | 129 | ### Open Questions (Not Addressed at This Meeting) 130 | 131 | - Semantics of `via_execute`. 132 | - Distinction between via and `via_execute`. 133 | - `BulkReceiver` vs bulk operator. 134 | - Implications of `Executor`s and `Future`s satisfying the same concepts. 135 | - (from Gordon) Distinguishing between `Executor`s (which have properties) and `Future`s (which don't). 136 | 137 | ### Action Items 138 | 139 | - (Bryce) Process other action items from these notes. 140 | 141 | 142 | -------------------------------------------------------------------------------- /notes/meetings/2018_07_27__executors__weekly.md: -------------------------------------------------------------------------------- 1 | # ISO C++ Executors 2 | 3 | ## Weekly Meeting - Notes - 2018-07-27 4 | 5 | **Kindly forgive and identify any mistakes and omissions in these notes.** 6 | 7 | #### Attendance 8 | 9 | - Bryce Adelstein Lelbach (Note Taker) 10 | - Carter Edwards 11 | - Chris Kohlhoff 12 | - David Hollman 13 | - Gordon Brown 14 | - Jared Hoberock 15 | - Kirk Shoop 16 | - Lee Howes 17 | - Michael Garland 18 | 19 | #### Agenda 20 | 21 | - Continue discussion of P0443/P1055 unification. 22 | - Discuss task factory model. 23 | - Review execution context [EXECUTOR PR #402](https://github.com/executors/executors/pull/402). 24 | 25 | #### Summary 26 | 27 | This week, we discussed moving towards a task factory model based on prototype work that Chris K worked on over the last week. 28 | 29 | After discussion, there seemed to be a consensus that we do not want to move in the direction of task factories. 30 | We discussed an alternative work-queue model that would let us decouple futures and data dependencies to a degree, replacing them with a coarser grained dependency mechanism. 31 | Bryce and Chris K will continue working on this and we will revisit it next week. 32 | 33 | We also discussed Carter's outstanding [EXECUTOR PR #402](https://github.com/executors/executors/pull/402) on execution contexts. 34 | 35 | ### Discussion 36 | 37 | #### P0443/P1055 Unification 38 | 39 | Jared: Last week we asked Chris K to go prototype task factories. Let's start with that. 40 | 41 | *Looked at Chris K's branch in the executors-impl repo* 42 | 43 | Bryce: Would we still have `then_execute`? 44 | 45 | Chris K: It would become a property. We'd just have `execute` as an execution function. 46 | 47 | Chris K, David: Adaption is hard if there's an arbitrarily large set of properties. 48 | 49 | Gordon, Bryce: In our prototypes we had concrete types, e.g. `bulk_task`, to simplify this. 50 | 51 | Jared: Is this better? What does this get us? 52 | 53 | Chris K, Mathias: Not clear that it is. 54 | 55 | Gordon: This may give us a path to enable lazy execution. 56 | 57 | David: Does this give Kirk what he needs? 58 | 59 | Kirk: No, it doesn't. 60 | 61 | Chris K: This is the properties equivalent of pushmi's customization points. 62 | 63 | David: Kirk, why doesn't this help you? 64 | 65 | Kirk: It doesn't make the default interface handle errors, cancellation, or be extensible to concepts, etc 66 | 67 | Chris K: That's orthogonal. If you want to add error handling, cancellation, etc, you'd do it with a property. 68 | 69 | Kirk: I only added customization points for bulk, this isn't how I'd build the core. 70 | 71 | Bryce: How would you transition dependencies between executors without customization points? 72 | 73 | Kirk: Yes, that needs it too. pushmi doesn't need that, so it's not fundamental. 74 | 75 | David: But bulk and inter-executor dependencies are fundamental to other people. 76 | 77 | David: You added what others needed via customization points, but you're saying that adding the functionality you need to P0443 with properties doesn't work. Why? 78 | 79 | Kirk: I haven't seen anyone show me what I need with this model. 80 | 81 | Gordon: I think this can work and support what pushmi requires. 82 | 83 | Bryce: Kirk, would an example you help you? 84 | 85 | Lee: Bryce, does this meet your needs? 86 | 87 | Bryce: I think this would work for bulk. If you passed a non-device function to a non-polymorphic executor, it would fail at compile time with CUDA today. With a polymorphic executor, it would fail at runtime. 88 | 89 | Bryce: But I don't think we should think about things in terms of the limitations of a particular GPU programming framework today. That's just an implementation detail. We should just focus on expressing bulk in standard C++. 90 | 91 | Jared: Is this an improvement? I don't think so. It makes bulk execution second class. 92 | 93 | Bryce: By logic, it also makes two-way and dependent execution (then) second class. 94 | 95 | Kirk: What if we still had require and query, and just a single interface method? 96 | 97 | Mathias: That makes executors a very general purpose thing, they'd just be a bag of things. 98 | 99 | Kirk: That's what they are today. Example: properties for getting thread ids, for getting future/promises, etc. 100 | 101 | Mathias: But there's a fundamental interface. 102 | 103 | Bryce: I'm not sure how far you can get with the base operations without properties if you're doing two-way or dependent execution. You need `make_promise_contract` pretty quickly. 104 | 105 | Mathias: Properties shouldn't be things that are fundamental. If `make_promise_contract` is fundamental, then it shouldn't be a property. 106 | 107 | David: But different people believe different things are fundamental. Facebook doesn't believe that bulk is fundamental. 108 | 109 | Kirk: I disagree, I (personally) feel that bulk doesn't fit, not that it's not fundamental. 110 | 111 | Lee: We care about bulk. 112 | 113 | Bryce: Where do we go from here? 114 | 115 | Jared: Chris what do you think? You implemented it? 116 | 117 | Chris K: I don't think this is the right model for fundamental execution functions, like `execute` and `bulk_execute`. You can make it fit, but it doesn't fit cleanly. I'd rather keep `bulk_execute` as a function. 118 | 119 | Gordon: One thing about this approach is that executors are no longer objects that execute work, they're factories that create tasks. 120 | 121 | Michael: I don't even understand why these classes have execute anymore, if their whole purpose is to create things tasks. 122 | 123 | Bryce: I don't think we all have the same definition of "executor". I don't think the P0443 and pushmi definitions are necessarily the same thing or compatible. I'm not sure that's a bad thing. 124 | 125 | Gordon: I agree. 126 | 127 | Gordon: We discussed this on the futures call on 2018-07-25 - having executors be senders as well is still problematic for me. It means that executors are passed down the chain of tasks. 128 | 129 | Kirk: I think you can do the same things with a pushmi executor that you can do with a P0443 executor. The existing P0443 model doesn't really support composition while keeping the executor. 130 | 131 | Gordon: You can compose in P0443 with `then_execute`. 132 | 133 | Gordon: What would we have to add to this design to support pushmi? 134 | 135 | Bryce: By "this design" do you mean the proposed changes from Chris K, or P0443? 136 | 137 | Gordon: The proposed changes from Chris K. 138 | 139 | Jared, Michael: I think we're saying we don't want to go in this direction, though. 140 | 141 | Mathias: Is the consensus that we don't want to go in this direction? 142 | 143 | David: We tried this as a way to move towards a consensus proposal. This seems to have not succeeded. 144 | 145 | Bryce: We've spent a lot of time exploring reducing the number of execution functions in P0443. Are we now at a point where we no longer want to explore that route? 146 | 147 | Jared: There are other ways to approach this that might work. 148 | 149 | Mathias: I have an idea for an alternative futures model that may support lazy execution. 150 | 151 | Kirk: This sounds like super-type. 152 | 153 | Mathias: I disagree. 154 | 155 | Mathias: I want futures to be usable, not a minimal concept. 156 | 157 | Mathias: We shouldn't focus on making it easy to write futures. The burden should be on the authors, not the users. 158 | 159 | Kirk: I somewhat agree, but there are many different types of futures that people want to write. 160 | 161 | Jared, Bryce: I think we should move towards a model where futures are decoupled from P0443. 162 | 163 | Lee: I agree, but we still need a way to do dependent execution. 164 | 165 | Jared: Can we do that in the next month? 166 | 167 | Mathias: I'd be okay with trying to come up with a minimal future concept. 168 | 169 | Bryce: That's what we did. I think what Jared is saying is we should move towards a futureless P0443 that has execution dependencies, but not data dependencies. 170 | 171 | Lee: We still need a way to manage data lifetimes. 172 | 173 | Mathias: I think the terminal forms are important. 174 | 175 | Jared: Is there anyone who thinks we DON'T need some sort of task chaining mechanism for September? 176 | 177 | David: No. 178 | 179 | Mathias: I think I could be okay with that. 180 | 181 | Lee: I think it's a question of greedy task chaining. 182 | 183 | Bryce: I think I could build on top of sequenced executors (e.g. work queues) for some models, but not for everything. I'm not sure how I'd do it with HPX, for example. 184 | 185 | Bryce: I could see us splitting into two proposals - execution and dependent execution - but I'm not sure that I would want to have the first of those make it into the standard without the second. 186 | 187 | David: I'm concerned about relying on data dependencies as a side channel. 188 | 189 | Michael: I agree with Bryce, I could see how you could build things on top of work queues. But, you loose some capabilities, like point to point dependencies. You end up with something coarser. 190 | 191 | Mathias: Ordering would be more dependent on scheduling. 192 | 193 | David: Ordering doesn't give you concurrency. 194 | 195 | Michael, Bryce: I disagree. You can create concurrency by creating more executors. 196 | 197 | Michael: If you had no way to create point to point dependencies with `then_execute`, you'd end up with a model where you'd have ordered executors that would guarantee that work enqueued to them would run in order. 198 | 199 | Michael: This is kinda related to something Torvald said a few meetings ago, when he asked how you would wait on all outstanding work. 200 | 201 | David: How would you do a join in this model? 202 | 203 | Lee: The question is whether we would need both initially. 204 | 205 | Kirk: This work queuing model is what we do in reactive. It's the many model. 206 | 207 | Michael: What's the many model? It replaces the value method with a next method that can be called multiple times. It's the closest thing to an async iterator. 208 | 209 | Bryce: Do we want to look at a work queuing model? 210 | 211 | Jared: We spent some time looking at this last week, and we ran into some models, which is why we moved towards this model. 212 | 213 | Michael: Is a meta-point that one of the biggest points of contention is the use of futures in P0443? 214 | 215 | Kirk: That is one way to express it. 216 | 217 | Kirk: I would express it as: why aren't P0443 executors futures? 218 | 219 | Michael: The biggest difference is that futures are inherently one use, and executors are not. 220 | 221 | Kirk: I don't think I agree that futures are one use. 222 | 223 | Mathias: We discussed on the 2018-07-25 futures call a small change - making executors have a nullary function that returns a sender (e.g. making executors some sort of factory). 224 | 225 | Bryce: Isn't that similar to the task factories idea we just discussed? 226 | 227 | Michael: One naive fear I have about thinking of executors as futures is then you might end up needing a "shared" executor. 228 | 229 | Kirk: The difference between a future and shared future in the pushmi model is whether or not submit is rvalue ref qualified. 230 | 231 | Michael: What you're saying is that the model of futures in pushmi doesn't suffer from the unique future/shared future distinction in the P0443/P1054 model. 232 | 233 | Jared: Bryce and Chris please come back next week with a proposal for coarser grained dependencies/work queue model. 234 | 235 | #### Execution Context EXECUTORS PR #402 236 | 237 | Jared: Moving on to Carter's pull request. 238 | 239 | *Wording review of [EXECUTORS PR #402](https://github.com/executors/executors/pull/402)* 240 | 241 | ### Action Items 242 | 243 | - (Bryce, Chris) Explore and prepare proposal on work queue model for next week's call. 244 | - (Carter) Update [EXECUTORS PR #402](https://github.com/executors/executors/pull/402). 245 | 246 | -------------------------------------------------------------------------------- /notes/meetings/2018_08_01__futures__weekly_us.md: -------------------------------------------------------------------------------- 1 | # ISO C++ Futures 2 | 3 | ## Weekly Meeting - US Timezone - Notes - 2018-08-01 4 | 5 | **Kindly forgive and identify any mistakes and omissions in these notes.** 6 | 7 | #### Attendance 8 | 9 | - Bryce Adelstein Lelbach (Note Taker) 10 | - David Hollman 11 | - Gordon Brown 12 | - Kirk Shoop 13 | - Lee Howes 14 | - Mathias Stearn 15 | - Michal Dominiak 16 | 17 | #### Agenda 18 | 19 | - Discuss David's new proposal and gist. 20 | - Explaination of lifters from reactive by Kirk. 21 | - Bryce's update on the work queue model discussed at the 2018-07-27 executors meeting. 22 | 23 | ## Discussion 24 | 25 | ### David's New Proposal and Gist. 26 | 27 | [David's Gist](https://gist.github.com/dhollman/0ff509a3a96a99f556d164c155e6b915) 28 | 29 | *David begins presenting.* 30 | 31 | *Starting with continuations.* 32 | 33 | Mathias: In MyContinuation, what are R and S? 34 | 35 | David: Lee indicated that he wanted to be able to return different types from the value and error methods. 36 | 37 | Lee: Yes, but I don't think these functions should return anything, because they should be one-way. 38 | 39 | David: This is intended to replace one-way and two-way. 40 | 41 | Bryce, Lee: Right, but this continuation API is inherently two-way. The methods return something. 42 | 43 | David: What are you proposing for future.then? Do you use a side channel? 44 | 45 | Lee: For future.then, a receiver that gets passed the value is taken. 46 | 47 | Bryce: I think that's what David would consider a side channel. 48 | 49 | Mathias: I think there's a terminology issue. What traditional C++ futures call then is more like an adaptor in P1055. What's called a future in P1055 doesn't have an analog in traditional C++ futures. 50 | 51 | *Discussion about which model is prevalent in other languages.* 52 | 53 | Bryce: I agree side channels are needed in a P1055 model. But I don't think that's a problem. 54 | 55 | Lee: P1053 was trying to show that you can build future.then on top of P1055 56 | 57 | David: What you're saying is your passing the next receiver to value method? 58 | 59 | Lee: No, it is passed in on construction. 60 | 61 | Kirk: What we're trying to say is that when you only have `void`, the only thing that has to be passed through a side channel is the next continuation. 62 | 63 | David: Even if these return `void`, you'd still need to template/pass through the `R` and `S` types. 64 | 65 | Bryce: Why? I don't think it needs to be done at this layer. It would just be a part of the receiver's type. 66 | 67 | Bryce: I think building futures on top of one-way continuations is perfectly fine. 68 | 69 | Bryce: Regarding error handling, we should discuss that separately from the question of lazy vs eager. If they appear linked, we won't make any forward progress. 70 | 71 | *Moving on to sub-executors.* 72 | 73 | David: Kirk last week you indicated that you didn't want to pass receivers into receivers. 74 | 75 | Kirk: I just think it's a little recursive, I'd prefer some sort of get method on the sender. 76 | 77 | Lee: Kirk and I agree that propagating the executor/sub-executor down the chain. We don't like this particular design. 78 | 79 | Bryce: By this particular design, you mean passing it as a parameter instead of having it be some component of the sender (which is retrieved by a get method). 80 | 81 | Lee: Kirk's latest version of the gist does this. We're not disagreeing about the fundamental concept, just with the interface. 82 | 83 | David: I like this approach because it fits better with bulk. 84 | 85 | *Discussing error channels.* 86 | 87 | Lee: There's a difference between handling errors from your asynchronous stream and handling errors from the executors. 88 | 89 | Lee: I think it's feasible to separate those types of errors. 90 | 91 | Bryce: How would that be done? Do you think it's fundamental that all errors go through the error method, or are two completely different paths needed? 92 | 93 | Lee: If what you propagate is an async notion of what the value is, you can propagate an error, or a value that is both a value and an error. 94 | 95 | Bryce: Who are you propagating to? To the next error method? 96 | 97 | Lee: You are not propagating the async launch error to the next error method. But you will have a way to look at the propagated asynchronous error that happened. 98 | 99 | Mathias: I think we have a different understanding of what a "switch to" executor is and when it happens. 100 | 101 | Mathias: My mental model is that the enqueue happens when the previous value completes. 102 | 103 | Bryce: It is fundamental that we support eager submission of dependent work in addition to what you just described Mathias. 104 | 105 | *Going back to David's gist.* 106 | 107 | David: Two executors, one that is terminal (`MyExecutor`) and one that is not (`MyReusableExecutor`). 108 | 109 | David: Bulk I've moved into a continuation. I think we just need to demonstrate that it doesn't have the same problems as the task factory approach. 110 | 111 | Bryce: Why do you think it doesn't? I think the objection on Friday wasn't to task factories in particular. We looked at it, agreed the reformulation might work, but people didn't see the motivation for it. 112 | 113 | Bryce, Lee: How would passing this bulk continuation to a non-bulk executor work? 114 | 115 | David: If the bulk continuation is passed a non-bulk sub-executor, it just uses that. 116 | 117 | Bryce: Ah, I see. You don't have to bind to a particular executor or disallow use with other executors as in the task factory model, because you always pass the sub executor in. 118 | 119 | Bryce: I like this sub executor idea, and I like having it as a parameter as it makes it fundamental. 120 | 121 | Lee: What is index in the bulk continuation? 122 | 123 | David: It's a single index. 124 | 125 | Bryce: How many execution agents does value call? Is it a bulk launch, or is it the element function? 126 | 127 | David: It's the element function. 128 | 129 | Lee: I think this is close to what I would like, except I would not expose new value and error methods. I would just make it satisify the non-bulk interface. 130 | 131 | Bryce: I agree. 132 | 133 | Bryce: When I was designing this, I imagined the continuation value method (that any executor can use) as being one that would handle the "default" case, contain the for loop, and spawn N execution agents. I had a separate element function method that bulk executors could use. Is what you are suggesting that the value method on a bulk continuation -is- the element function? 134 | 135 | David, Lee: No. 136 | 137 | Lee: If you made shape, *_factory, and *_element private, it would be fine; these don't need to be exposed. It could just be an implementation detail of the executor. 138 | 139 | David: No, we need a common interface. I need to be able to pass a bulk task to an OpenMP executor, a CUDA executor, etc. 140 | 141 | Lee: The things you pass this entity into do not need this interface. 142 | 143 | Mathias, David, Bryce: I think we disagree. 144 | 145 | Mathias: Does anyone have a use case for bulk tasks being used by non-bulk executors. 146 | 147 | Bryce: Debugging. You might want to switch a bulk executor for a simple sequential executor, like the inline executor. 148 | 149 | Mathias: I agree that something like an inline executor should be able to accept bulk tasks. But when would you ever want to call the value method on this continuation? 150 | 151 | Lee, Bryce: It would be called by executors, when this gets wrapped up and passed down to that layer. 152 | 153 | David: Type-erased executor interfaces might need it to. 154 | 155 | Bryce: It might be called when composing together execution graphs. 156 | 157 | David: I think that's what Lee is saying. 158 | 159 | Lee: I disagree that it would come up during type erasure. 160 | 161 | Mathias: That feels like it's not a bulk task, but instead it's a task that schedule works. 162 | 163 | Lee: Any continuation you can use with futures is a thing that takes a value in and puts a value. 164 | 165 | David: If you want to do non-fork-join bulk, like Mathias has described in the past, that wouldn't work. 166 | 167 | Bryce: Lee, do you view all continuations, not just bulk continuations, as things that schedule work? 168 | 169 | Bryce: We are starting to run low on time before the CppCon meeting. We have four weeks until the mailing deadline for the CppCon meeting, and six weeks until the meeting. I want everyone to start thinking about whether we can execute on a joint proposal by the CppCon meeting, or if we will be bringing separate papers/proposals to the CppCon meeting with the intention of working towards merging them there. 170 | 171 | ### Agenda Items Not Addressed at This Meeting 172 | 173 | - Explaination of lifters from reactive by Kirk. 174 | - Bryce's update on the work queue model discussed at the 2018-07-27 executors meeting. 175 | 176 | ### Open Questions (Not Addressed at This Meeting) 177 | 178 | - Is it alright to build dependencies in side channels? 179 | - Adaptor's use of `operator()` 180 | 181 | -------------------------------------------------------------------------------- /notes/rapperswil_2018__pre_mailing_deadline__outline.md: -------------------------------------------------------------------------------- 1 | Version 1 Outline 2 | ================= 3 | 4 | * Motivation 5 | * What problem are we solving? 6 | * Avoid detailed discussion in this paper. 7 | * Instead, reference prior papers co-authored by Bryce, Lee and others. 8 | * Design: How are we solving the problem? 9 | * Avoid detailed discussion of all the alternative design options. 10 | * Instead, reference prior papers co-authored by Bryce, Lee and others. 11 | * Focus on the design decisions we have decided on. 12 | * Reference supporting polls/minutes from previous committee meetings to support our decisions. 13 | * Examples (we won't be able to do all of these, but we should try to get some volunteers to work on these) 14 | * Archetypes 15 | * Always ready future 16 | * Locking traditional future 17 | * Lockfree traditional future 18 | * CUDA future 19 | * HPX/fiber future 20 | * Network/distributed future 21 | * Heterogeneous future interop 22 | * E.g. `network_future(/* ... */).via(cuda_executor).then(f).via(hpx_executor).then(g).via(network_executor)` 23 | * Coroutines TS future interop 24 | * Generic executor-based `async` (not part of this proposal, but could be an example in the repo) 25 | * Generic `when_*` (not part of this proposal, but could be an example in the repo) 26 | * Generic asynchronous algorithm (not part of this proposal, but could be an example in the repo) 27 | * Custom future_wait/future_get function 28 | * Future Work (things that are not version 1 features) 29 | * `std2::async` 30 | * `std2::when_*` 31 | * Cancellation 32 | * `.bulk_then` 33 | * Wording 34 | * Promise concept 35 | * Executor promise property 36 | * Error-handling disambiguation tag 37 | * Future concepts 38 | * SemiFuture 39 | * ContinuableFuture 40 | * UniqueFuture [OPEN TECHNICAL QUESTION, ISSUE #26 AND #27] 41 | * SharedFuture [OPEN TECHNICAL QUESTION, ISSUE #26 AND #27] 42 | * .then 43 | * Syntax 44 | * Semantics 45 | * Requirements [ISSUE #25 AND #27] 46 | * .via 47 | * Syntax 48 | * Semantics 49 | * Requirements [ISSUE #21] 50 | * Requirements on continuation functions 51 | * Error handling model 52 | * Future unwrapping constructor [OPEN TECHNICAL QUESTION, ISSUE #29] 53 | * Forward progress guarantees [OPEN TECHNICAL QUESTION, ISSUE #6] 54 | * Future/promise synchronization gurantees [OPEN TECHNICAL QUESTION, ISSUES #2 AND #3] 55 | * is_always_lockfree(ish) property 56 | * Concrete executor type-erasing future types 57 | * std2::semi_future 58 | * std2::continuable_future 59 | * std::thread Future Waiting Functions 60 | * std::this_thread::future_wait (ADLable) 61 | * std::this_thread::future_get (ADLable) 62 | 63 | -------------------------------------------------------------------------------- /notes/resources.md: -------------------------------------------------------------------------------- 1 | ISO C++ Executors and Futures 2 | ============================= 3 | 4 | Resources 5 | --------- 6 | 7 | This is a collection of links to material (documents, code, etc) useful to 8 | those involved in the ISO C++ Executors and Futures standardization project. 9 | 10 | Pull requests are most welcome. 11 | 12 | **[ISO C++ Executors and Futures GitHub Organization](https://github.com/executors/executors)** 13 | - [Executors Proposals and Design Documents]() 14 | - [Executors Reference Implementation]() 15 | - [Futures Proposals and Design Documents]() 16 | - [Futures Reference Implementation]() 17 | 18 | **ISO C++ Committee Proposals** 19 | - [P0761: Executors Wording](https://wg21.link/P0443) 20 | - [P0761: Executors Design](https://wg21.link/P0761) 21 | - [P1055: Alternative Executors Design](https://wg21.link/P1055) 22 | - [P1054: Futures Design and Wording](https://wg21.link/P1054) 23 | - *[P1053](https://wg21.link/P1053)* 24 | - *[P0701r0](https://wg21.link/P0701r0)* 25 | - *[P0701r1](https://wg21.link/P0701r1)* 26 | - *[P0783](https://wg21.link/P0783)* 27 | - *[P0904](https://wg21.link/P0904)* 28 | 29 | **Prototypes and Brainstorming** 30 | - [Bryce Adelstein Lelbach's P0443/P1054 CUDA and CPU Prototype Library](https://github.com/executors/futures-impl/tree/master/rapperswil-prototype) 31 | - [Bryce Adelstein Lelbach's P1054 Lazy Future](https://gist.github.com/brycelelbach/d79c35e53a480a9d04e6a8a0f736bdd6) 32 | - [Kirk Shoop's Minimal Concepts](https://gist.github.com/kirkshoop/f743929f16dd2ba2f89f8d3fe3e10d42) 33 | - [Kirk Shoop's P1055 Sort](https://gist.github.com/dhollman/0cb3cedc55b198aa66443eda419331b3#gistcomment-2651447) 34 | - [Mathias Stearn's Lazy Future](https://gist.github.com/RedBeard0531/1fb12c3bd9c469a8549bab6c9e22492b#file-lazy_futures-h-L110) 35 | - [David Hollman's Executor Algorithms](https://gist.github.com/dhollman/0cb3cedc55b198aa66443eda419331b3) 36 | 37 | **Relevant Software Projects** 38 | - [Agency](https://github.com/agency-library/agency) 39 | - [Networking TS Reference Implementation](https://github.com/chriskohlhoff/networking-ts-impl) 40 | - [CUDA Thrust](https://github.com/thrust/thrust) 41 | - [SYCL](https://github.com/KhronosGroup/SyclParallelSTL) 42 | - [HPX](https://github.com/STEllAR-GROUP/hpx) 43 | - [pushmi](https://github.com/facebookresearch/pushmi) 44 | 45 | 46 | --------------------------------------------------------------------------------