Central Object Registry in bpftime: handler_manager.hpp Implementation Guide

The central object registry in bpftime is the handler_manager class located in 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. 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:

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:

#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:

// 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:

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:

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

Summary

  • 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. 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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →