Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc/future: add documentation for future. #30

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ calc
*.kate-swp
*.d
*.so
_site
.color_coded
.ycm*
tests/test
4 changes: 2 additions & 2 deletions docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ google_analytics_id: ''
navigation: 1

# URL to source code, used in _includes/footer.html
codeurl: 'https://docs,reaver-project.org/reaverlib'
codeurl: 'https://github.com/reaver-project/reaverlib'

# Default categories (in order) to appear in the navigation
sections: [
Expand All @@ -28,7 +28,7 @@ sections: [

# Keep as an empty string if served up at the root. If served up at a specific
# path (e.g. on GitHub pages) leave off the trailing slash, e.g. /my-project
baseurl: 'https://griwes.github.io/github-pages-experiments'
baseurl: '/reaverlib'

# Dates are not included in permalinks
permalink: none
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/futures.md
191 changes: 191 additions & 0 deletions docs/_posts/2016-09-18-futures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
layout: page
title: "Futures"
category: tut
date: 2016-09-18 11:00:05
---

## Introduction

This is the tutorial for `reaver::future` and related functionalities. `reaver::future` is a type useful for doing
computations on values that might not be available yet by allowing you to poll the status of the future and attach
continuations, or code that will be executed as soon as the future becomes ready.

### The problem

Imagine you have to do some expensive computation; for simplicity, let's you need to call function `compute`, that
takes very long to finish. Then you want to return that value times 2, and the user of your function will use that
value. Typically, you'd write it like this:

```cpp
auto wrapped_compute()
{
return compute() * 2;
}
```

This has the disadvantage of having to complete the `compute()` call before you return. This is unfortunate,
especially when `wrapped_compute` has additional work that's not dependent on that value to do, today, in the
era of multicore processors. An especially nasty case is when `compute()` is a blocking function that waits for
some input, because that makes the entire thread do nothing until that happens - even if the return value isn't
immediately needed.

A way to improve with that in mind could be to use threads, but that requires a lot of explicit synchronization -
without a sophisticated mechanism for passing values between threads, this is not easy.

### Solving it with futures

Enter `reaver::future`. Futures are exactly that - a mechanism for passing values between threads. A future object
represents a value that will become available at some point in the future. Here's how `wrapped_compute` could use
futures:

```cpp
auto wrapped_compute()
{
return reaver::async(compute).then([](auto v){ return v * 2; });
}
```

This changes the codomain of `wrapped_compute`, since it now returns a future. It's not easy to escape this domain
(*some would call it "category"*), and that's by design - once you're in the asynchronous world, it often doesn't
make sense to go back, since that involves blocking - and blocking is exactly what we are trying to avoid here.

`reaver::async` is a helper function that schedules the task of calling `compute()` to a thread pool created
by the library. If you want to control that behavior, please see the section on executors.

### Basic future API

There are two primary ways of accessing the value hold inside of the future.

The first one is `future::try_get`. If the value is present, it returns an engaged optional containing the value;
otherwise, it returns an empty optional.

```cpp
auto fut = get_some_future();
auto opt = fut.try_get();
if (opt)
{
auto val = *opt;
std::cout << "the value is " << val;
}
else
{
std::cout << "the value is not available yet";
}
```

`reaver::future` behaves as both shared and unique future. If the future you're calling `try_get()` on is the only
copy pointing to the same value, and the value is ready, it will be moved out of the future and into the return
value of `try_get()`, and the future will become disengaged. If the future is *not* the last copy, the value will
be copied.

The other one is called `future::then` and you've already seen it used above. `then()` attaches a **continuation**
to the future, which means that after the future becomes fulfilled, the function passed to `then()` will be called.
For example:

```cpp
auto fut = get_some_future();
auto keep_alive = fut.then([](auto val) {
std::cout << "the value is " << val; // plesae keep in mind that std::cout isn't thread-safe!
};
```

`reaver::future`s are cancelled on destruction; that is, when a future object goes out of scope, and it's the last
future that points to the same state (in other words, the last copy of the same future), and there's no
continuations attached to that future, the task that has been scheduled (if any) is cancelled if it didn't start
yet. To avoid that behavior, you can call `detach()` on a future; that'll make it's task run regardless of future
lifetimes.

```cpp
make_some_future().detach(); // will not cancel the returned future
```

### Creating ready futures

Two helper functions exist to help you create ready and exceptional futures.

`make_ready_future` returns a future with a value set to its argument (or a ready `future<void>` if there's no
argument passed to it).

`make_exceptional_future` returns an exceptional future, and the exception object is set to the argument.

```cpp
auto fut1 = make_ready_future();
assert(fut1.try_get());
auto fut2 = make_ready_future(1);
assert(fut1.try_get() == 1);
auto fut3 = make_exceptional_future(1);
fut3.try_get(); // throws 1; see "Error handling"
```

### Packaged task

The futurized implementation of `wrapped_compute()` is equivalent to:

```cpp
auto wrapped_compute()
{
auto pair = reaver::package(compute);
default_executor()->push([task = std::move(pair.packaged_task])](){ task(); });
return pair.future.then([](auto v){ return v * 2; });
}
```

`reaver::packaged_task` can also be called manually in any thread, If the function wrapped by it returns a value,
the value of the associated future will be set to that. If it throws an exception, the future will become
exceptional.

### Error handling

In case of exceptional futures, the behavior of accessing functions changes.

If `try_get()` is called on an exceptional future, the original exception is rethrown.

If `then()` is called on an exceptional future, or a future with attached continuations becomes exceptional,
the callback will not be called, and the future returned from `then()` will become exceptional, with the same
exception as the original one.

There's one more function of `reaver::future` for error handling: `on_error()`. It behaves similarly to `then()`,
only the callback is called when the original future becomes becomes exceptional, and the return type is different.
It should be used for handling errors explicitly, with different action than setting the future to exceptional state.
Namely, it uses `reaver::expected` instead of language exceptions to propagate.

For example:

```cpp
enum class errors
{
negative_square_root
};

float do_something(float);

auto fut = make_some_future().then([](auto v) {
if (v < 0) { throw std::logic_error{"cannot take a square root of a negative number"}; }
return sqrt(v);
}).then([](auto v) {
return do_something(v);
}).on_error([](std::exception_ptr ex) {
try
{
std::rethrow_exception(ex);
}

catch (std::logic_error & err)
{
return errors::negative_square_root;
}
});
```

The whole construct of `try { rethrow } catch () {}` is necessary because of the way C++ handles exceptions (and
allows exceptions of arbitrary types).

The future `fut` is now of type `future<expected<float, errors>>`. The simplest way to access the underlying value
(that will throw if either the future or the expected object don't hold a value) is `**fut.try_get()`, and the
simplest way of accessing the error is `fut.try_get()->get_error()`, though the expectation is that this will
not generally be used with `try_get()`, but with `then()` instead.

Notice that since `then()` chains propagate exceptions, it's enough to attach one `on_error` to catch all errors
that could happen up the chain.

File renamed without changes.