# Handling fork() with ASIO io_context in Multiprocess Applications

> Safely reuse ASIO io_context in multiprocess apps after fork. Learn how to use asio execution context notify_fork to avoid crashes and undefined behavior.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: how-to-guide
- Published: 2026-07-11

---

**ASIO provides a fork-notification API through `asio::execution_context::notify_fork()` that must be called before, during, and after `fork()` to safely reuse `io_context` objects in child processes without undefined behavior or crashes.**

When building multiprocess applications with the chriskohlhoff/asio library, calling `fork()` without notifying the execution context leads to corrupted internal state, duplicated file descriptors, and potential deadlocks. The library implements a specific notification protocol through the `execution_context` base class that allows services to reset their state across process boundaries. Understanding this mechanism is essential for reliable handling of `fork()` with ASIO `io_context` in multiprocess applications.

## Why fork() Breaks ASIO Execution Contexts

ASIO’s architecture relies on **service registries**, **thread pools**, and **reactor mechanisms** that maintain open file descriptors and internal state machines. When a process calls `fork()`, the child inherits the parent's address space, but OS resources like epoll or kqueue file descriptors become shared or duplicated in undefined ways.

Without explicit notification, the child process inherits invalid internal pointers, duplicated timers, and inconsistent work counters. In [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp), the library defines the `fork_event` enumeration to categorize these transitions, ensuring each registered service can execute cleanup or reinitialization logic appropriate to the process lifecycle.

## The Fork Notification API

The `asio::execution_context` class provides the central coordination point for fork safety. All derived contexts—including `io_context` and `thread_pool`—inherit this functionality directly.

### Fork Event Types

The `fork_event` enum defined in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp) (lines 79-92) distinguishes three critical phases:

- **`fork_prepare`** – Signals that the process is about to call `fork()`. Services should release locks and flush buffers.
- **`fork_parent`** – Executed in the parent process after `fork()` returns the child's PID. Services restore normal operation.
- **`fork_child`** – Executed in the child process after `fork()` returns `0`. Services must rebuild internal state, close duplicated descriptors, and reset work counters.

### Implementation Details

The `notify_fork()` method in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp) (lines 98-108) iterates through all registered services and invokes their individual `notify_fork()` hooks. This centralized dispatch ensures consistent state management across timers, sockets, and custom services.

Additionally, `io_context` inherits from `execution_context` (as seen in [`include/asio/io_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/io_context.hpp), lines 78-86), automatically gaining fork-awareness. For executors requiring explicit fork semantics, `io_context::basic_executor_type` provides a `require()` overload for `relationship.fork` (lines 108-118), enabling fine-grained control over work tracking across process boundaries.

## Step-by-Step Implementation Pattern

To safely use an `io_context` across a `fork()` boundary, follow this strict notification sequence:

1. **Prepare**: Call `ctx.notify_fork(asio::execution_context::fork_prepare)` before invoking `fork()`.
2. **Fork**: Execute `pid_t pid = ::fork()`.
3. **Child**: If `pid == 0`, call `ctx.notify_fork(asio::execution_context::fork_child)` immediately.
4. **Parent**: If `pid > 0`, call `ctx.notify_fork(asio::execution_context::fork_parent)` before resuming operations.

This protocol ensures that internal service registries in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp) properly handle file descriptor duplication and work queue resets.

## Complete Code Examples

### Basic Fork Pattern

The following example demonstrates the core notification sequence required when handling `fork()` with ASIO `io_context`:

```cpp
#include <asio.hpp>
#include <unistd.h>

int main()
{
    asio::io_context ctx;

    // 1. Tell ASIO we are about to fork.
    ctx.notify_fork(asio::execution_context::fork_prepare);

    if (pid_t pid = ::fork())
    {
        // -------------------- Parent process --------------------
        if (pid > 0)
        {
            // 3. Notify the parent side.
            ctx.notify_fork(asio::execution_context::fork_parent);
        }

        // Parent can continue using ctx.
        ctx.run();
    }
    else
    {
        // -------------------- Child process --------------------
        // 3. Notify the child side.
        ctx.notify_fork(asio::execution_context::fork_child);

        // Child may reuse the existing io_context or create a new one.
        ctx.run();
        _exit(0);
    }
}

```

### Forking a Network Server

This pattern extends to network servers that spawn child processes to handle individual connections:

```cpp
#include <asio.hpp>
#include <iostream>
#include <unistd.h>

void session(asio::ip::tcp::socket sock)
{
    // Echo server logic...
}

void accept_connections(asio::io_context& ctx,
                        asio::ip::tcp::acceptor& acceptor)
{
    acceptor.async_accept([&](std::error_code ec,
                              asio::ip::tcp::socket sock)
    {
        if (!ec)
        {
            // Fork a new process to handle the connection.
            ctx.notify_fork(asio::execution_context::fork_prepare);
            if (pid_t pid = ::fork())
            {
                // Parent – keep accepting.
                if (pid > 0)
                    ctx.notify_fork(asio::execution_context::fork_parent);
                accept_connections(ctx, acceptor);
            }
            else
            {
                // Child – handle the single session.
                ctx.notify_fork(asio::execution_context::fork_child);
                session(std::move(sock));
                _exit(0);
            }
        }
    });
}

int main()
{
    asio::io_context ctx;
    asio::ip::tcp::acceptor acc(ctx,
        asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 12345));
    accept_connections(ctx, acc);
    ctx.run();
}

```

### Using Executors with Fork Relationship

For applications using the executor model, explicitly require the fork relationship to ensure proper work tracking:

```cpp
auto exec = ctx.get_executor();
auto fork_exec = asio::require(exec, asio::execution::relationship.fork);

// Now fork_exec respects fork semantics for custom services.
asio::post(fork_exec, []{ std::cout << "Running with fork awareness\n"; });

```

The `relationship.fork` property is defined in [`include/asio/execution/relationship.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution/relationship.hpp) and handled specifically in `io_context::basic_executor_type` to manage outstanding work counters across process boundaries.

## Critical Safety Considerations

**Thread Safety**: You must call `notify_fork()` only when no other ASIO functions are active on other threads. Calling from within a completion handler is safe because the handler already executes within the context's thread.

**Multiple Contexts**: Each `io_context` or `thread_pool` instance you intend to use after the fork requires separate notification. The `execution_context::notify_fork()` implementation iterates through each service registry individually, so omitting a context leaves its services in an invalid state.

**Work Counters**: Without the `fork_child` notification, the child process inherits incorrect outstanding work counts from the parent, potentially causing the event loop to terminate immediately. The notification resets these counters in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp).

## Summary

- **Always notify before forking**: Call `notify_fork(asio::execution_context::fork_prepare)` prior to `fork()` to allow services to release critical resources.
- **Handle both sides**: Invoke `fork_child` in the child process and `fork_parent` in the parent to reinitialize internal state and file descriptors.
- **Use fork-aware executors**: Apply `asio::require(exec, asio::execution::relationship.fork)` when executors must track work across process boundaries.
- **Reference the source**: The implementation resides in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp) and [`include/asio/io_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/io_context.hpp), with the `fork_event` enum defined at lines 79-92.

## Frequently Asked Questions

### What happens if I don't call notify_fork() after forking?

Without calling `notify_fork()`, the child process inherits duplicated file descriptors, invalid timer handles, and corrupted service registries from the parent. This leads to undefined behavior including crashes, deadlocks, and lost I/O events as the child attempts to use resources that are only valid in the parent context.

### Can I use the same io_context in both parent and child after fork?

Yes, provided you follow the notification protocol. After calling `notify_fork(asio::execution_context::fork_child)` in the child and `notify_fork(asio::execution_context::fork_parent)` in the parent, both processes can safely continue using their respective instances of the `io_context`. The notifications trigger service-specific reinitialization that makes the inherited context valid in each process.

### Is notify_fork() thread-safe?

No, `notify_fork()` is not thread-safe relative to other ASIO operations. You must ensure no other threads are calling ASIO functions when `notify_fork()` executes. The safest approach is to call it from within a completion handler running on the `io_context` thread, or to ensure all other threads have completed their ASIO work before forking.

### Does this apply to thread_pool as well as io_context?

Yes, the fork notification mechanism applies to any class derived from `asio::execution_context`, including `asio::thread_pool`. Each context maintains its own service registry in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp), so you must call `notify_fork()` on every context instance that will remain active after the fork operation.