Libprocess
Library that provides an actor style message-passing programming model (in C++).
Install / Use
/learn @3rdparty/LibprocessREADME
Libprocess User Guide
Bazel
Follows a "repos/deps" pattern (in order to help with recursive dependencies). To use:
-
Copy
bazel/repos.bzlinto your repository at3rdparty/libprocess/repos.bzland add an emptyBUILD(orBUILD.bazel) to3rdparty/libprocessas well. -
Copy all of the directories from
3rdpartythat you don't already have in your repository's3rdpartydirectory. -
Either ... add the following to your
WORKSPACE(orWORKSPACE.bazel):
load("//3rdparty/libprocess:repos.bzl", libprocess_repos="repos")
libprocess_repos()
load("@com_github_3rdparty_libprocess//bazel:deps.bzl", libprocess_deps="deps")
libprocess_deps()
Or ... to simplify others depending on your repository, add the following to your repos.bzl:
load("//3rdparty/libprocess:repos.bzl", libprocess="repos")
def repos():
libprocess()
And the following to your deps.bzl:
load("@com_github_3rdparty_libprocess//bazel:deps.bzl", libprocess="deps")
def deps():
libprocess()
-
You can then use
@com_github_3rdparty_libprocess//:processin your target'sdeps. -
Repeat the steps starting at (1) at the desired version of this repository that you want to use.
libprocess provides general primitives and abstractions for asynchronous programming with futures/promises, HTTP, and actors.
<br> Inspired by Erlang, libprocess gets it's name from calling an "actor" a "process" (not to be confused by an operating system process). <br><br>
Table of Contents
- Presentations
- Overview
- Futures and Promises
- HTTP
- Processes (aka Actors)
- Clock Management and Timeouts
- Miscellaneous Primitives
- Optimized Run Queue and Event Queue
<a name="presentations"></a> Presentations
The following talks are recommended to get an overview of libprocess:
<a name="overview"></a> Overview
This user guide is meant to help understand the constructs within the libprocess library. The main constructs are:
- Futures and Promises which are used to build ...
- HTTP abstractions, which make the foundation for ...
- Processes (aka Actors).
For most people processes (aka actors) are the most foreign of the concepts, but they are arguably the most critical part of the library (they library is named after them!). Nevertheless, we organized this guide to walk through futures/promises and HTTP before processes because the former two are prerequisites for the latter.
<a name="futures-and-promises"></a> Futures and Promises
The Future and Promise primitives are used to enable programmers to write asynchronous, non-blocking, and highly concurrent software.
A Future acts as the read-side of a result which might be computed asynchronously. A Promise, on the other hand, acts as the write-side "container".
Looking for a specific topic?
- Basics
- States
- Disarding a Future (aka Cancellation)
- Abandoned Futures
- Composition:
Future::then(),Future::repair(), andFuture::recover() - Discarding and Composition
- Callback Semantics
CHECK()Overloads
<a name="futures-and-promises-basics"></a> Basics
A Promise is templated by the type that it will "contain". A Promise is not copyable or assignable in order to encourage strict ownership rules between processes (i.e., it's hard to reason about multiple actors concurrently trying to complete a Promise, even if it's safe to do so concurrently).
You can get a Future from a Promise using Promise::future(). Unlike Promise, a Future can be both copied and assigned.
<br> As of this time, the templated type of the future must be the exact same as the promise: you cannot create a covariant or contravariant future. <br><br>
Here is a simple example of using Promise and Future:
using process::Future;
using process::Promise;
int main(int argc, char** argv)
{
Promise<int> promise;
Future<int> future = promise.future();
// You can copy a future.
Future<int> future2 = future;
// You can also assign a future (NOTE: this future will never
// complete because the Promise goes out of scope, but the
// Future is still valid and can be used normally.)
future = Promise<int>().future();
return 0;
}
<a name="futures-and-promises-states"></a> States
A promise starts in the PENDING state and can then transition to any of the READY, FAILED, or DISCARDED states. You can check the state using Future::isPending(), Future::isReady(), Future::isFailed(), and Future::isDiscarded().
<br> We typically refer to transitioning to
READYas completing the promise/future. <br><br>
You can also add a callback to be invoked when (or if) a transition occurs (or has occcured) by using the Future::onReady(), Future::onFailed(), and Future::onDiscarded(). As a catch all you can use Future::onAny() which will invoke it's callbacks on a transition to all of READY, FAILED, and DISCARDED. See Callback Semantics for a discussion of how/when these callbacks get invoked.
The following table is meant to capture these transitions:
| Transition | Promise::*() | Future::is*() | Future::on*() |
| ----------- | ----------------------------------- | ----------------------- | -------------------------- |
| READY | Promise::set(T) | Future::isReady() | Future::onReady(F&&) |
| FAILED | Promise::fail(const std::string&) | Future::isFailed() | Future::onFailed(F&&) |
| DISCARDED | Promise::discard() | Future::isDiscarded() | Future::onDiscarded(F&&) |
<br> Code Style: prefer composition using
Future::then()andFuture::recover()overFuture::onReady(),Future::onFailed(),Future::onDiscarded(), andFuture::onAny(). A good rule of thumb is if you find yourself creating your own instance of aPromiseto compose an asynchronous operation you should use composition instead! <br><br>
We use the macros CHECK_PENDING(), CHECK_READY(), CHECK_FAILED(), CHECK_DISCARDED() throughout our examples. See CHECK() Overloads for more details about these macros.
<a name="futures-and-promises-discarding-a-future"></a> Discarding a Future (aka Cancellation)
You can "cancel" the result of some asynchronous operation by discarding a future. Unlike doing a discard on a promise, discarding a future is a request that may or may not be be satisfiable. You discard a future using Future::discard(). You can determine if a future has a discard request by using Future::hasDiscard() or set up a callback using Future::onDiscard(). Here's an example:
using process::Future;
using process::Promise;
int main(int argc, char** argv)
{
Promise<int> promise;
Future<int> future = promise.future();
CHECK_PENDING(future);
future.discard();
CHECK(promise.future().hasDiscard());
CHECK_PENDING(future); // THE FUTURE IS STILL PENDING!
return 0;
}
The provider of the future will often use Future::onDiscard() to watch for discard requests and try and act accordingly, for example:
using process::Future;
using process::Promise;
int main(int argc, char** argv)
{
Promise<int> promise;
// Set up a callback to discard the future if
// requested (this is not always possible!).
promise.future().onDiscard([&]() {
promise.discard();
});
Future<int> future = promise.future();
CHECK_PENDING(future);
future.discard();
CHECK_DISCARDED(future); // NO LONGER PENDING!
return 0;
}
<a name="futures-and-promises-abandoned-futures"></a> Abandoned Futures
An instance of Promise that is deleted before it has transitioned out of PENDING is considered abandoned. The concept of abandonment was added late to the library so for backwards compatibility reasons we could not add a new state but instead needed to have it be a sub-state of PENDING.
You can check if a future has been abandoned by doing Future::isAbandoned() and set up a callback using Future::onAbandoned(). Here's an example:
using process::Future;
using process::Promise;
int main(int argc, char** argv)
{
Promise<int>* promise = new Promise<int>();
Future<int> future = promise->future();
CHECK(!future.isAbandoned());
delete promise; // ABANDONMENT!
CHECK_ABANDONED(future);
CHECK_PENDING(future); // ALSO STILL PENDING!
return 0;
}
<a name="futures-and-promises-composition"></a> Composition: Future::then(), Future::repair(), and Future::recover()
You can compose together asynchronous function calls using Future::then(), Future::repair(), and Future::recover(). To help understand the value of composition, we'll start with an example of how you might manually do this composition:
using process::Future;
using process::Promise;
// Returns an instance of `Person` for the specified `name`.
Future<Person> find(const std::string
