# Central Object Registry in bpftime: handler_manager.hpp Implementation Guide

> Explore the handler_manager.hpp file, the central object registry in bpftime. Learn how it manages eBPF objects across processes using shared memory and file descriptors.

- Repository: [eunomia-bpf/bpftime](https://github.com/eunomia-bpf/bpftime)
- Tags: internals
- Published: 2026-03-01

---

**The central object registry in bpftime is the `handler_manager` class located in [`runtime/src/handler/handler_manager.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/handler/handler_manager.hpp), which maintains a shared-memory vector of all eBPF objects (maps, programs, links, and perf events) accessible across multiple processes by file-descriptor-like identifiers.**

The **bpftime** runtime requires a unified mechanism to track eBPF objects across process boundaries. The **central object registry** solves this by storing maps, programs, links, and perf events in a single shared-memory data structure. This registry acts as the single source of truth for object lifecycle management, enabling the daemon, agents, and user programs to access eBPF resources consistently.

## What Is the bpftime Central Object Registry?

The central object registry is a **shared-memory-backed index** that assigns integer identifiers (similar to file descriptors) to every eBPF object created within the bpftime ecosystem. Unlike traditional in-process registries, this design allows multiple independent processes to reference the same eBPF map or program without duplicating kernel state.

According to the bpftime source code, the registry implementation resides in [`runtime/src/handler/handler_manager.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/handler/handler_manager.hpp). The `handler_manager` class declared at **lines 95-127** encapsulates a `handler_variant_vector handlers` member that stores all object instances in a `boost::interprocess` managed vector. This placement in shared memory makes the registry accessible to the daemon, agents, and user applications simultaneously.

## handler_manager.hpp Architecture

### The handler_manager Class (Lines 95-127)

The core registry logic centers on the `handler_manager` class definition spanning lines 95-127. This class encapsulates the `handlers` vector and provides type-safe access to heterogeneous eBPF objects through a variant-based design.

Key design elements include:

- **Indexed Storage**: Objects reside in a `vector<handler_variant>` indexed by integer IDs that mimic Unix file descriptors
- **Process-Agnostic**: The backing storage uses `boost::interprocess::managed_shared_memory` rather than process-local heap memory
- **Lifecycle Control**: Methods like `set_handler`, `clear_id_at`, and `clear_all` manage object insertion and removal atomically

### Shared Memory Integration

The registry leverages **Boost.Interprocess** to place the `handlers` vector inside a named shared memory segment. When initializing the manager, the code opens or creates a `managed_shared_memory` object using `bpftime::get_global_shm_name()` as the segment identifier.

This architecture ensures that when one process creates a BPF map via the registry, another process can immediately look up that same map using its assigned identifier. The shared memory segment persists independently of any single process lifetime, allowing the daemon to outlive short-lived agent processes.

### Handler Type Variants (Lines 84-88)

Lines 84-88 define the `handler_variant` type alias that enumerates all storable eBPF objects:

```cpp
using handler_variant = boost::variant<
    bpf_map_handler,
    bpf_link_handler,
    bpf_prog_handler,
    bpf_perf_event_handler
    // ... additional handler types
>;

```

This variant type enables the heterogeneous storage of maps, programs, links, and perf events within a single homogeneous vector. When retrieving an object, code uses `std::visit` or `boost::get` to dispatch to the concrete handler type.

## Core Registry Operations

### Allocating and Registering Objects

The registry provides two primary methods for inserting objects:

- **`set_handler(int idx, handler_variant &&handler)`**: Places a handler at a specific index, overwriting any existing entry
- **`set_handler_at_empty_slot(handler_variant &&handler, managed_shared_memory &shm)`**: Automatically finds the first unallocated slot and returns its index (the pseudo-file-descriptor)

The `set_handler_at_empty_slot` method is the preferred entry point for new registrations because it mimics the kernel's behavior of returning the lowest available file descriptor number.

### Object Retrieval by File Descriptor

To access stored objects, the registry exposes:

- **`const handler_variant &get_handler(int idx) const`**: Returns a const reference to the handler at the specified index
- **`handler_variant &operator[](int idx)`**: Provides mutable access with bounds checking
- **`bool is_allocated(int idx) const`**: Verifies whether a slot contains a valid object before access

These methods enable safe lookup patterns where agents validate an ID's existence via `is_allocated()` before dereferencing through `get_handler()`.

### Registry Cleanup and Management

The manager supports granular and bulk deletion:

- **`void clear_id_at(int idx, managed_shared_memory &shm)`**: Removes a single handler and frees its shared memory resources
- **`void clear_all(managed_shared_memory &shm)`**: Empties the entire registry, invoked during daemon shutdown to prevent resource leaks

Both operations properly invoke destructors within the shared memory context to prevent segmentation faults when other processes detach.

## Working with the Registry: Code Examples

### Initializing the Handler Manager

This example demonstrates creating the shared memory segment and instantiating the registry with capacity for 1024 objects:

```cpp
#include <boost/interprocess/managed_shared_memory.hpp>
#include "runtime/src/handler/handler_manager.hpp"

int main() {
    // Open or create the shared memory segment used by bpftime
    boost::interprocess::managed_shared_memory shm{
        boost::interprocess::open_or_create,
        bpftime::get_global_shm_name(),
        10 * 1024 * 1024   // 10 MiB – size chosen for the example
    };

    // Allocate space for up to 1024 "fd" entries
    constexpr std::size_t max_fds = 1024;
    bpftime::handler_manager hm{shm, max_fds};

    // Registry is now ready for object insertion
}

```

### Registering a BPF Map Handler

The following snippet allocates a map handler to the first available slot and receives a pseudo-file-descriptor:

```cpp
// Assume map_handler is a previously constructed bpf_map_handler
bpftime::bpf_map_handler map_handler{/*...*/};

// Move the handler into the manager, automatically picking the first free slot
int fd = hm.set_handler_at_empty_slot(std::move(map_handler), shm);
if (fd < 0) {
    std::cerr << "Failed to allocate a map handler slot\n";
} else {
    std::cout << "Map registered with pseudo-fd: " << fd << '\n';
}

```

### Looking Up Objects by File Descriptor

This pattern shows type-safe retrieval using `std::visit` to handle different handler variants:

```cpp
int target_fd = 5;   // example fd obtained earlier
if (hm.is_allocated(target_fd)) {
    const auto &variant = hm.get_handler(target_fd);
    // Use std::visit to act on the concrete handler type
    std::visit([](auto &&h){
        using T = std::decay_t<decltype(h)>;
        if constexpr (std::is_same_v<T, bpftime::bpf_prog_handler>) {
            // operate on program handler
            h.run_some_action();
        }
        // add branches for other handler types as needed
    }, variant);
}

```

### Cleaning Up the Registry

During shutdown or cleanup phases, remove all registered objects:

```cpp
hm.clear_all(shm);   // removes every handler from shared memory

```

## Summary

- **[`runtime/src/handler/handler_manager.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/handler/handler_manager.hpp)** implements the **central object registry** for the bpftime runtime
- The `handler_manager` class (lines 95-127) stores all eBPF objects in a `boost::interprocess` backed `vector<handler_variant>`
- Objects are indexed by pseudo-file-descriptors and shared across processes via `managed_shared_memory`
- The `handler_variant` type (lines 84-88) supports maps, programs, links, and perf events through Boost.Variant
- Core methods include `set_handler_at_empty_slot` for registration, `get_handler` for retrieval, and `clear_all` for cleanup

## Frequently Asked Questions

### What file contains the central object registry in bpftime?

The central object registry is implemented in **[`runtime/src/handler/handler_manager.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/handler/handler_manager.hpp)**. This file defines the `handler_manager` class that acts as the single source of truth for all eBPF object lifecycles within the bpftime ecosystem.

### How does bpftime share eBPF objects between processes?

bpftime uses **Boost.Interprocess** to place the `handler_manager` and its underlying `vector<handler_variant>` inside a named shared memory segment. Multiple processes open this segment using `bpftime::get_global_shm_name()` to access the same object table concurrently.

### What types of eBPF handlers are stored in the registry?

According to lines 84-88 of [`handler_manager.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/handler_manager.hpp), the registry stores `bpf_map_handler`, `bpf_link_handler`, `bpf_prog_handler`, and `bpf_perf_event_handler` instances within the `handler_variant` type. Additional handler types may be appended to this variant as the runtime evolves.

### How do I look up a specific eBPF object by its file descriptor?

Use the `is_allocated(int idx)` method to verify the slot is active, then call `get_handler(int idx)` to retrieve the `handler_variant`. Apply `std::visit` or `boost::get` to extract the concrete handler type (e.g., `bpf_map_handler`) from the variant for type-specific operations.