{"action":"created","issue":{"url":"https://api.github.com/repos/nikomatsakis/rayon/issues/193","repository_url":"https://api.github.com/repos/nikomatsakis/rayon","labels_url":"https://api.github.com/repos/nikomatsakis/rayon/issues/193/labels{/name}","comments_url":"https://api.github.com/repos/nikomatsakis/rayon/issues/193/comments","events_url":"https://api.github.com/repos/nikomatsakis/rayon/issues/193/events","html_url":"https://github.com/nikomatsakis/rayon/pull/193","id":198133337,"number":193,"title":"Add experimental support for futures","user":{"login":"nikomatsakis","id":155238,"avatar_url":"https://avatars.githubusercontent.com/u/155238?v=3","gravatar_id":"","url":"https://api.github.com/users/nikomatsakis","html_url":"https://github.com/nikomatsakis","followers_url":"https://api.github.com/users/nikomatsakis/followers","following_url":"https://api.github.com/users/nikomatsakis/following{/other_user}","gists_url":"https://api.github.com/users/nikomatsakis/gists{/gist_id}","starred_url":"https://api.github.com/users/nikomatsakis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nikomatsakis/subscriptions","organizations_url":"https://api.github.com/users/nikomatsakis/orgs","repos_url":"https://api.github.com/users/nikomatsakis/repos","events_url":"https://api.github.com/users/nikomatsakis/events{/privacy}","received_events_url":"https://api.github.com/users/nikomatsakis/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2016-12-30T10:57:24Z","updated_at":"2016-12-30T18:48:54Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/nikomatsakis/rayon/pulls/193","html_url":"https://github.com/nikomatsakis/rayon/pull/193","diff_url":"https://github.com/nikomatsakis/rayon/pull/193.diff","patch_url":"https://github.com/nikomatsakis/rayon/pull/193.patch"},"body":"This branch adds a new method to a scope: `spawn_future(F)`. The `spawn_future` API allows Rayon to play the role of an *executor*. That is, it takes some future `F` and gives it life, causing it to start executing. The result is another future, called a *rayon future*, which can be used to check if the result of F is ready.\r\n\r\n### The role of Rayon and futures\r\n\r\nTo understand the role Rayon plays here, recall that a future F is basically the *plan* for an async computation, much like an iterator is a *plan* for a loop. Thus a future by itself is inert. When you invoke `spawn_future()`, however, Rayon starts to put that plan into action: it pushes a job to a worker thread which will invoke `poll()` on the future F. This will trigger various bits of work to be done and may wind up blocked on I/O requests and the like. In the meantime, you get back another future F' that you can use to check on the status of this work or to compose new futures.\r\n\r\nThe simplest usage pattern, where you just want to push some work to another thread and then block on it and use the result, is like so:\r\n\r\n```rust\r\nscope(|s| {\r\n    let x = s.spawn_future(future_x).rayon_wait();\r\n    let y = s.spawn_future(future_y).rayon_wait();\r\n    do_computation(x, y);\r\n});\r\n```\r\n\r\nNote the use of the `rayon_wait()` method instead of `wait()` -- `rayon_wait()` will block intelligently, so that even if you are on a Rayon worker thread the system doesn't seize up. However, blocking is not the recommended way to use futures. Instead, it would be better to compose newer and bigger futures that use the result from spawn -- or, better yet, compose the futures before you spawn. If you must block, block at the very end:\r\n\r\n```rust\r\nscope(|s| {\r\n    s.spawn_future(\r\n        future_x\r\n            .join(future_y)\r\n            .map(|(x, y)| do_computation(x, y))\r\n    ).rayon_wait()\r\n})\r\n```\r\n\r\ncc @alexcrichton @aturon @carllerche -- please double-check my understanding here :)\r\n\r\n### Comparing `spawn()` and `spawn_future()`\r\n\r\nSo how does `spawn_future()` compare to the existing `scope.spawn()`? The rule of thumb is that `spawn()` is used to launch a computation *for side-effects* whereas `spawn_future()` is used to launch a computation *for its result*. You can observe the difference when it comes to the result type: `spawn()` takes a closure that returns `()`, so if you want to get any value out, it must write it somewhere external. In contrast, the future you give to `spawn_future()` has a result type. \r\n\r\nAnother place that this difference is important is **cancellation**. If you drop the future that is returned by `spawn_future()`, that is interpreted as a signal that you no longer care about that result. This will cause the spawned future to stop executing, possibly before its complete. This is a key mechanism used throughout the futures library to signal when results are no longer needed and hence avoid doing useless work. In contrast, once you spawn a task with `s.spawn()`, it will always execute. There is no way to cancel it.\r\n\r\n### API questions\r\n\r\nAPI-wise, I've kept this to the bare minimum for the moment, simply adding `spawn_future()`. My general plan however is to do the following:\r\n\r\n- There is no support here for streams.\r\n- Add some form of `spawn_future_fn()` wrappers that, instead of taking a future, takes a closure and create a future for its result (using `future::lazy`). We probably want one for closures that return `Result` (in which case the future is fallible) and one for an \"infallible\" computation (this would wrap the result of the closure in `Ok()`, basically). \r\n- Add corresponding free functions `spawn_async()` and `spawn_future_async()`. These are analogous to the `scope()` methods but they execute outside of any scope, just injecting spawned jobs into the asynchronous thread-pool. The idea is that there is (conceptually) always an outermost scope that you don't have a handle to. This would solve the Servo use-case of wanting to inject work into a parallel thread-pool and query its result later (ideally, you would use `spawn_future_async()` for that, since it fits into the \"inject job for result\" use-case, not \"inject job for side-effects\").\r\n\r\nI'd probably pursue all of those in follow-up PRs.\r\n\r\n### Status\r\n\r\nMildly experimental.\r\n\r\nMissing work items:\r\n\r\n- [ ] More tests (it's kind of hard to write stand-alone tests for this stuff!)\r\n- [ ] Better docs probably\r\n- [ ] Simplify cancellation implementation, as discussed with @alexcrichton\r\n- [ ] Tests for future cancellation?\r\n- [ ] Review results from my review with @alexcrichton, which I don't have in front of me :)\r\n- [ ] Make a distinct feature for \"futures\" support?"},"comment":{"url":"https://api.github.com/repos/nikomatsakis/rayon/issues/comments/269807632","html_url":"https://github.com/nikomatsakis/rayon/pull/193#issuecomment-269807632","issue_url":"https://api.github.com/repos/nikomatsakis/rayon/issues/193","id":269807632,"user":{"login":"alexcrichton","id":64996,"avatar_url":"https://avatars.githubusercontent.com/u/64996?v=3","gravatar_id":"","url":"https://api.github.com/users/alexcrichton","html_url":"https://github.com/alexcrichton","followers_url":"https://api.github.com/users/alexcrichton/followers","following_url":"https://api.github.com/users/alexcrichton/following{/other_user}","gists_url":"https://api.github.com/users/alexcrichton/gists{/gist_id}","starred_url":"https://api.github.com/users/alexcrichton/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/alexcrichton/subscriptions","organizations_url":"https://api.github.com/users/alexcrichton/orgs","repos_url":"https://api.github.com/users/alexcrichton/repos","events_url":"https://api.github.com/users/alexcrichton/events{/privacy}","received_events_url":"https://api.github.com/users/alexcrichton/received_events","type":"User","site_admin":false},"created_at":"2016-12-30T18:48:54Z","updated_at":"2016-12-30T18:48:54Z","body":"> cc @alexcrichton @aturon @carllerche -- please double-check my understanding here :)\r\n\r\nThat all sounds great to me! \r\n\r\nTo double check my own understanding as well, a lot of this is very similar to basically:\r\n\r\n```rust\r\nfn spawn_rayon<F: Future>(f: F) -> RayonFuture {\r\n    let (tx, rx) = oneshot::channel();\r\n    rayon.spawn(|| tx.send(f.wait()));\r\n    return tx\r\n}\r\n```\r\n\r\nExcept that this solves a few crucial problems when literally using a \"oneshot\"\r\n\r\n* When a future is blocked, rayon can continue to make progress with other work. That is, the blocking happens on a queue, not by taking a thread.\r\n* The rayon version is slightly more optimized, only requiring one allocation, not two.\r\n* Rayon handles everything like panics for you.\r\n\r\n> There is no support here for streams.\r\n\r\nI'm curious, what's your thinking here? E.g. what would a stream look like? I could imagine that `futures::sync` would suffice for some bare bones business, but I suppose that like why `oneshot` isn't used under the hood everywhere it'd want to interact differently with rayon. Do you have some API sketches in mind?\r\n\r\n> Add some form of `spawn_future_fn()` wrappers that, instead of taking a future, takes a closure and create a future for its result (using future::lazy). \r\n\r\nYeah this is the general trend of \"those things that can spawn futures\" right now. We're likely to consider a unifying spawn trait (https://github.com/alexcrichton/futures-rs/issues/313) in which case this'd come for free. I haven't thought too much about the trait here, but we'd definitely want to consider rayon when designing it!\r\n\r\n> We probably want one for closures that return Result (in which case the future is fallible) and one for an \"infallible\" computation (this would wrap the result of the closure in Ok(), basically).\r\n\r\nThe general trend is to have `spawn(Future)` and `spawn_fn(FnOnce() -> IntoFuture)`, so that'd work for fallibly computations as `Result` implements `IntoFuture`\r\n\r\n> Add corresponding free functions spawn_async() and spawn_future_async().\r\n\r\nThere's some [prior art on `CpuFuture`](https://github.com/alexcrichton/futures-rs/blob/27b7a1e35b9862ebf1c3ff417a3888166cb993df/futures-cpupool/src/lib.rs#L245-L254) for this with an inherent `forget` function on the normal spawn value. Would that suffice for this use case to avoid adding new spawning methods?"}}
{"actor":{"display_login":"alexcrichton"}}