Execution Contexts

This page explains execution contexts, service management, and the thread pool.

Code snippets assume using namespace boost::capy; is in effect.

What is an Execution Context?

An execution context is a place where work runs. It provides:

  • A registry of services (polymorphic components)

  • An associated executor type

  • Lifecycle management (shutdown, destroy)

The execution_context class is the base class for all contexts:

class my_context : public execution_context
{
public:
    using executor_type = /* ... */;

    executor_type get_executor();

    ~my_context()
    {
        shutdown();
        destroy();
    }
};

Service Management

Services are polymorphic components owned by an execution context. Each service type can be registered at most once.

Creating Services

// Get or create a service
my_service& svc = ctx.use_service<my_service>();

// Explicitly create with arguments
my_service& svc = ctx.make_service<my_service>(arg1, arg2);

// Check if a service exists
if (ctx.has_service<my_service>())
    // ...

// Find without creating
my_service* svc = ctx.find_service<my_service>();  // nullptr if not found

Implementing Services

Services derive from execution_context::service:

struct my_service : execution_context::service
{
    explicit my_service(execution_context& ctx)
    {
        // Initialize...
    }

protected:
    void shutdown() override
    {
        // Cancel pending operations
        // Release resources
        // Must not block or throw
    }
};

The shutdown() method is called when the context is destroyed, in reverse order of service creation.

Key Type Aliasing

Services can specify a key_type to enable base-class lookup:

struct file_service : execution_context::service
{
protected:
    void shutdown() override {}
};

struct posix_file_service : file_service
{
    using key_type = file_service;  // Register under base class

    explicit posix_file_service(execution_context& ctx) {}
};

// Usage:
ctx.make_service<posix_file_service>();
file_service* svc = ctx.find_service<file_service>();  // Returns posix_file_service*

Handler Queue

The execution_context::queue class stores completion handlers:

class queue
{
public:
    bool empty() const noexcept;
    void push(handler* h) noexcept;
    void push(queue& other) noexcept;  // Splice
    handler* pop() noexcept;
};

Handlers implement the ownership contract:

  • Call operator() for normal invocation (handler cleans itself up)

  • Call destroy() to discard without invoking (e.g., during shutdown)

  • Never call both, and never use delete directly

Thread Pool

The thread_pool class is an execution context that provides a pool of worker threads. It inherits from execution_context, providing service management and a nested executor_type that satisfies the capy::executor concept.

class thread_pool : public execution_context
{
public:
    class executor_type;

    thread_pool(std::size_t num_threads = 0);
    ~thread_pool();

    executor_type get_executor() const noexcept;
};

Basic Usage

thread_pool pool(4);  // 4 worker threads
auto ex = pool.get_executor();

// Post work to the pool
ex.post(my_coroutine_handle);

// dispatch() always posts for thread_pool
// (caller is never "inside" the pool's run loop)
ex.dispatch(another_handle);

Executor Operations

The thread_pool::executor_type provides the standard executor interface:

auto ex = pool.get_executor();

// Access the owning context
thread_pool& ctx = ex.context();

// Work tracking
ex.on_work_started();
ex.on_work_finished();

// Submit coroutines
ex.post(handle);      // Queue for execution
ex.dispatch(handle);  // Same as post for thread_pool
ex.defer(handle);     // Same as post for thread_pool

// Comparison
auto ex2 = pool.get_executor();
assert(ex == ex2);  // Same pool = equal executors

Construction

thread_pool();                  // Default: hardware_concurrency threads
thread_pool(std::size_t n);     // Explicit thread count

Service Management

Since thread_pool inherits from execution_context, it supports services:

thread_pool pool(4);

// Add a service
pool.make_service<my_service>(arg1, arg2);

// Get or create
my_service& svc = pool.use_service<my_service>();

// Query
if (pool.has_service<my_service>())
    // ...

Destruction

The destructor signals all threads to stop and waits for them to complete. Services are shut down and destroyed in reverse order of creation. Pending work is discarded.

Lifecycle Pattern

Derived contexts must follow this destruction pattern:

class my_context : public execution_context
{
public:
    ~my_context()
    {
        shutdown();  // Notify services, cancel pending work
        destroy();   // Delete services in reverse order
        // Now safe to destroy members
    }
};

Calling shutdown() and destroy() from the base class destructor is too late—derived class members may already be destroyed.

The is_execution_context Concept

Types satisfying is_execution_context can be used with framework components:

template<class X>
concept is_execution_context =
    std::derived_from<X, execution_context> &&
    requires { typename X::executor_type; } &&
    executor<typename X::executor_type> &&
    requires(X& x) {
        { x.get_executor() } -> std::same_as<typename X::executor_type>;
    };

Thread Safety

Service management functions (use_service, make_service, find_service) are thread-safe. The shutdown() and destroy() functions are NOT thread-safe and must only be called during destruction.

When NOT to Use execution_context Directly

Use execution_context directly when:

  • Building a custom I/O context

  • Implementing a new execution model

  • Managing polymorphic services

Do NOT use execution_context directly when:

  • You just need to run coroutines — use thread_pool

  • You need simple work distribution — use thread_pool directly

Summary

Component Purpose

execution_context

Base class providing service registry

service

Polymorphic component owned by a context

handler

Base class for completion callbacks

queue

FIFO queue of handlers

thread_pool

Execution context with worker thread pool (derives from execution_context)

thread_pool::executor_type

Executor for posting work to a thread_pool

is_execution_context

Concept for valid execution contexts

Next Steps