MCPcopy Create free account
hub / github.com/3rdparty/libprocess

github.com/3rdparty/libprocess @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
1,704 symbols 5,037 edges 166 files ⚖ Apache-2.0 263 documented · 15% updated 4y ago★ 2405 open issues

Browse by type

Functions 1,355 Types & classes 349
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Libprocess User Guide

Bazel

Follows a "repos/deps" pattern (in order to help with recursive dependencies). To use:

  1. Copy bazel/repos.bzl into your repository at 3rdparty/libprocess/repos.bzl and add an empty BUILD (or BUILD.bazel) to 3rdparty/libprocess as well.

  2. Copy all of the directories from 3rdparty that you don't already have in your repository's 3rdparty directory.

  3. Either ... add the following to your WORKSPACE (or WORKSPACE.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()
  1. You can then use @com_github_3rdparty_libprocess//:process in your target's deps.

  2. 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.

Inspired by Erlang, libprocess gets it's name from calling an "actor" a "process" (not to be confused by an operating system process).

Table of Contents


Presentations

The following talks are recommended to get an overview of libprocess:

Overview

This user guide is meant to help understand the constructs within the libprocess library. The main constructs are:

  1. Futures and Promises which are used to build ...
  2. HTTP abstractions, which make the foundation for ...
  3. 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.

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

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.

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.

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;
}

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().

We typically refer to transitioning to READY as completing the promise/future.

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&&)

Code Style: prefer composition using Future::then() and Future::recover() over Future::onReady(), Future::onFailed(), Future::onDiscarded(), and Future::onAny(). A good rule of thumb is if you find yourself creating your own instance of a Promise to compose an asynchronous operation you should use composition instead!

We use the macros CHECK_PENDING(), CHECK_READY(), CHECK_FAILED(), CHECK_DISCARDED() throughout our examples. See CHECK() Overloads for more details about these macros.

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;
}

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;
}

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& name);

// Returns the mother (an instance of `Person`) of the specified `name`.
Future<Person> mother(const std::string& name)
{
  // First find the person.
  Future<Person> person = find(name);

  // Now create a `Promise` that we can use to compose the two asynchronous calls.
  Promise<Person>* promise = new Promise<Person>();

  Future<Person> mother = promise->future();

  // Here is the boiler plate that can be replaced by `Future::then()`!
  person.onAny([](const Future<Person>& person) {
    if (person.isFailed()) {
      promise->fail(person.failure());
    } else if (person.isDiscarded()) {
      promise->discard();
    } else {
      CHECK_READY(person);
      promise->set(find(person->mother));
    }
    delete promise;
  });

  return mother;
}

Using Future::then() this can be simplified to:

using process::Future;

// Returns an instance of `Person` for the specified `name`.
Future<Person> find(const std::string& name);

// Returns the mother (an instance of `Person`) of the specified `name`.
Future<Person> mother(const std::string& name)
{
  return find(name)
    .then([](const Person& person) {
      return find(person.mother);
    });
}

Each of Future::then(), Future::repair(), and Future::recover() takes a callback that will be invoked after certain transitions, captured by this table:

Transition Future::*()
READY Future::then(F&&)
FAILED Future::repair(F&&) and Future::recover(F&&)
DISCARDED Future::recover(F&&)
Abandoned (PENDING and Future::isAbandoned()) Future::recover(F&&)

Future::then() allows you to transform the type of the Future into a new type but both Future::repair() and Future::recover() must return the same type as Future because they may not get executed! Here's an example using Future::recover() to handle a failure:

using process::Future;

// Returns an instance of `Person` for the specified `name`.
Future<Person> find(const std::string& name);

// Returns a parent (an instance of `Person`) of the specified `name`.
Future<Person> parent(const std::string& name)
{
  return find(name)
    .then([](const Person& person) {
      // Try to find the mother and if that fails try the father!
      return find(person.mother)
        .recover([=](const Future<Person>&) {
          return find(person.father);
        });
    });
}

Be careful what you capture in your callbacks! Depending on the state of the future the callback may be executed from a different scope and what ever you captured may no longer be valid; see Callback Semantics for more details.

Discarding and Composition

Doing a Future::discard() will propagate through each of the futures composed with Future::then(), Future::recover(), etc. This is usually what you want, but there are two important caveats to look out for:

1. Future::then() enforces discards

The future returned by Future::then() will not execute the callback if a discard has been requested. That is, even if the future transitions to READY, Future::then() will still enforce the request to discard and transition the future to DISCARDED.

These semantics are surprising to many, and, admittedly, the library may at one point in the future change the semantics and introduce a discardable() helper for letting people explicitly decide if/when they want a callback to be discarded. Historic

Core symbols most depended-on inside this repo

browse all functions →

Shape

Method 984
Function 371
Class 329
Enum 20

Languages

C++100%

Modules by API surface

src/process.cpp97 symbols
include/process/http.hpp83 symbols
src/tests/process_tests.cpp78 symbols
include/process/future.hpp72 symbols
src/http.cpp70 symbols
src/decoder.hpp63 symbols
include/process/gmock.hpp41 symbols
include/process/event.hpp41 symbols
src/tests/benchmarks.cpp40 symbols
src/memory_profiler.cpp35 symbols
src/windows/libwinio.cpp28 symbols
src/tests/http_tests.cpp26 symbols

For agents

$ claude mcp add libprocess \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page