Handle System¶
Overview¶
The Hakka JSON library implements a sophisticated handle-based memory management system specifically engineered to prevent Out-Of-Memory (OOM) conditions when processing millions to billions of small JSON objects. The handle system uses compact 32-bit tokens instead of 64-bit pointers, achieving 50% memory reduction per reference—critical for preventing catastrophic memory exhaustion in large-scale JSON workloads.
Core Components¶
JsonHandleCompact¶
JsonHandleCompact
is the primary handle class that provides a smart pointer-like interface for managing JSON objects. It encapsulates a 32-bit HandleManagerToken
and coordinates with specialized managers for lifecycle management.
Header: include/hakka_json_handle.hpp
Class Definition¶
class JsonHandleCompact
{
HandleManagerToken data; // 32-bit encoded token
static constexpr auto type_mask = 0xC0000000; // Top 2 bits for type
public:
JsonHandleCompact(); // Creates INVALID handle (data = 0)
explicit JsonHandleCompact(HandleManagerToken token);
// Copy semantics with reference counting
JsonHandleCompact(const JsonHandleCompact &other);
JsonHandleCompact &operator=(const JsonHandleCompact &other);
// Move semantics (zero-cost transfer)
JsonHandleCompact(JsonHandleCompact &&other) noexcept;
JsonHandleCompact &operator=(JsonHandleCompact &&other) noexcept;
~JsonHandleCompact();
// Type information
HakkaJsonType get_type() const;
// Object access
UniformCompactPointerView get_view() const;
UniformCompactPointer get_mut_ptr(); // Only for Array/Object
// Reference counting (CPython integration)
uint32_t retain() const;
void release();
// Validity checks
bool operator!() const;
operator bool() const;
bool is_valid() const;
// C API conversion
operator uint64_t() const;
};
Memory Layout¶
The JsonHandleCompact
contains only a single 32-bit HandleManagerToken
:
JsonHandleCompact structure:
HandleManagerToken data (32-bit)
$
Total size: 4 bytes
Compare to traditional pointer-based approach:
void* pointer (64-bit)
$
Total size: 8 bytes (100% overhead)
Memory Efficiency: 50% reduction compared to raw pointers.
Token Encoding¶
The 32-bit token encodes both type information and object index:
HandleManagerToken Binary Layout:
,
31 ... 30 29 ... 0
Type Index
4
Type Encoding (bits 31-30):
00 - Scalar types (int, float, bool, null, invalid)
01 - String
10 - Array
11 - Object
Additional Scalar Discrimination (bit 29 when bits 31-30 = 00):
001 - Integer (bit 29 = 1)
000 - Float/Bool/Null/Invalid (bit 29 = 0)
Examples:
0x00000000 // INVALID (default constructed handle)
0x20000001 // Integer at index 1 (001 prefix)
0x00000005 // Float at index 5 (000 prefix)
0x40000000 // String at index 0 (01 prefix)
0x80000010 // Array at index 16 (10 prefix)
0xC0000020 // Object at index 32 (11 prefix)
Constructors and Initialization¶
Default Constructor:
JsonHandleCompact(); // data = 0 (INVALID handle)
INVALID_NAN
in the scalar manager.
Token Constructor:
explicit JsonHandleCompact(HandleManagerToken token);
create()
methods.
Copy Constructor:
JsonHandleCompact(const JsonHandleCompact &other);
retain()
to increment the reference count.
Move Constructor:
JsonHandleCompact(JsonHandleCompact &&other) noexcept;
other
to the new handle. Sets other.data = 0
(INVALID state) to prevent double-release.
Copy and Move Semantics¶
Copy Assignment:
JsonHandleCompact &operator=(const JsonHandleCompact &other);
other
- Retains new object (increments reference count)
- Self-assignment safe
Move Assignment:
JsonHandleCompact &operator=(JsonHandleCompact &&other) noexcept;
other
- Sets other.data = 0
to prevent double-release
- Self-assignment safe
- Zero-cost operation (no reference counting overhead)
Usage Example:
// Copy semantics (reference counting)
JsonHandleCompact handle1 = JsonIntCompact::create(42);
JsonHandleCompact handle2 = handle1; // retain() called, ref_count = 2
// Move semantics (zero-cost transfer)
JsonHandleCompact handle3 = std::move(handle1); // No retain(), handle1 becomes INVALID
Reference Counting for Python FFI Integration¶
The handle system uses explicit reference counting to enable Foreign Function Interface (FFI) integration with Python, without depending on CPython's internal headers or ABI.
Why Not RAII?
- Python FFI requires explicit lifetime control across language boundaries
- C++ RAII scope-based destruction conflicts with Python's reference-based lifetime management
- Python objects can outlive C++ stack frames
- FFI layer needs manual retain()
/release()
calls to coordinate with Python's reference counting model
Reference Counting Methods:
uint32_t retain() const;
Implementation:
- Dispatches to appropriate type's inc_ref()
method
- Thread-safe (uses atomic operations)
- Returns 0 for INVALID handles
- Used automatically by copy constructor/assignment
void release();
Implementation:
- Calls manager's release()
method
- When ref_count reaches 0, object is destroyed
- Sets data = 0
(INVALID state) after release
- Called automatically by destructor and move operations
Usage Example:
// Manual reference counting
JsonHandleCompact handle = JsonIntCompact::create(42);
handle.retain(); // ref_count = 2
handle.release(); // ref_count = 1
handle.release(); // ref_count = 0, object destroyed, handle becomes INVALID
Python FFI Integration:
The library provides a C ABI-compatible FFI layer (capi/
directory) that exposes C functions for lifetime management. The FFI layer converts between HakkaHandle
(uint64_t typedef) and JsonHandleCompact
internally, enabling Python bindings to manage C++ object lifetimes without requiring CPython headers—achieving complete ABI decoupling.
Type Information and Access¶
Get Type:
HakkaJsonType get_type() const;
type()
method.
Possible Return Values:
- HAKKA_JSON_NULL
- HAKKA_JSON_BOOL
- HAKKA_JSON_INT
- HAKKA_JSON_FLOAT
- HAKKA_JSON_STRING
- HAKKA_JSON_ARRAY
- HAKKA_JSON_OBJECT
- HAKKA_JSON_INVALID
Get Immutable View:
UniformCompactPointerView get_view() const;
std::variant
.
Return Type (UniformCompactPointerView
):
std::variant<
std::monostate, // Invalid/empty
const JsonIntCompact*,
const JsonFloatCompact*,
const JsonBoolCompact*,
const JsonStringCompact*,
const JsonArrayCompact*,
const JsonObjectCompact*,
const JsonNullCompact*,
const JsonInvalidCompact*
>
Usage Example:
JsonHandleCompact handle = JsonIntCompact::create(42);
UniformCompactPointerView view = handle.get_view();
if (auto* int_ptr = std::get_if<const JsonIntCompact*>(&view)) {
auto value = (*int_ptr)->get(); // Retrieve int64_t value
}
Get Mutable Pointer:
UniformCompactPointer get_mut_ptr();
Return Type (UniformCompactPointer
):
std::variant<
std::monostate, // Invalid or immutable type
JsonArrayCompact*,
JsonObjectCompact*
>
Important: Only Array and Object types return valid pointers. All primitive types (Int, Float, String, Bool, Null) return std::monostate
because they are immutable.
Usage Example:
JsonHandleCompact handle = JsonArrayCompact::create();
UniformCompactPointer mut_ptr = handle.get_mut_ptr();
if (auto* array_ptr = std::get_if<JsonArrayCompact*>(&mut_ptr)) {
(*array_ptr)->push_back(JsonIntCompact::create(42));
}
Validity Checks¶
Boolean Conversion Operators:
bool operator!() const; // Returns true if INVALID
operator bool() const; // Returns true if valid
bool is_valid() const; // Returns true if valid (data != 0)
All three methods check if data != 0
. A handle with data = 0
represents an INVALID state.
Usage Example:
JsonHandleCompact handle; // Default constructed (INVALID)
if (!handle) {
// Handle is INVALID
}
handle = JsonIntCompact::create(42);
if (handle.is_valid()) {
// Handle is valid
}
Destructor¶
~JsonHandleCompact();
release()
to decrement reference count. When the last handle is destroyed and ref_count reaches 0, the underlying object is deallocated.
RAII-Style Cleanup:
{
JsonHandleCompact handle = JsonIntCompact::create(42);
// Use handle...
} // Destructor called, release() decrements ref_count
Manager Coordination¶
Internal Method:
JsonHandleManagerCompact &get_manager() const;
Manager Dispatch Logic:
auto type = static_cast<JsonHandleManagerType>((data & type_mask) >> 30);
// type values:
// 0 (00) -> Scalar
// 1 (01) -> String
// 2 (10) -> Array
// 3 (11) -> Object
return *JsonHandleManagerRegistryCompact::get_instance().get_manager(type);
Uniform Pointer System¶
The handle system uses a pointer abstraction to provide type-safe, memory-efficient access to JSON objects.
UniformCompactPointerView¶
A type-safe std::variant
wrapper for const access to JSON objects.
Header: include/uniform_compact_pointer.hpp
Definition:
using UniformCompactPointerView = std::variant<
std::monostate, // Invalid/empty state
const JsonIntCompact*,
const JsonFloatCompact*,
const JsonBoolCompact*,
const JsonStringCompact*,
const JsonArrayCompact*,
const JsonObjectCompact*,
const JsonNullCompact*,
const JsonInvalidCompact*
>;
Key Characteristics:
- Non-owning: Does not manage object lifetime
- Type-safe: Compile-time type checking through std::variant
- Zero-cost abstraction: No runtime overhead beyond vtable-free dispatch
- NaN-boxing aware: Special handling for NaN-boxed types (Bool, Null, Invalid)
NaN-Boxing Concept:
template <typename T>
concept isNanBoxingType = std::is_same_v<T, const JsonFloatCompact*> ||
std::is_same_v<T, const JsonBoolCompact*> ||
std::is_same_v<T, const JsonNullCompact*> ||
std::is_same_v<T, const JsonInvalidCompact*>;
Bool, Null, and Invalid types are implemented as NaN-boxed values within JsonFloatCompact
. The dispatch function automatically handles the type cast.
Dispatch Function:
template <class Invoker, class Ret, class... Args>
auto dispatch(UniformCompactPointerView v, Invoker&& invoke, Args&&... args)
-> tl::expected<Ret, HakkaJsonResultEnum>;
Provides type-safe visitation with automatic NaN-boxing handling and error propagation.
Usage Example:
UniformCompactPointerView view = handle.get_view();
// Using std::visit
std::visit([](auto&& ptr) {
using T = std::decay_t<decltype(ptr)>;
if constexpr (std::is_same_v<T, const JsonIntCompact*>) {
// Handle integer
} else if constexpr (std::is_same_v<T, const JsonStringCompact*>) {
// Handle string
}
// ... other types
}, view);
// Using dispatch helper
auto result = dispatch<std::string>(view, [](auto* ptr) {
return ptr->dump(0).value();
});
UniformCompactPointer¶
A type-safe std::variant
wrapper for mutable access to structured types only.
Definition:
using UniformCompactPointer = std::variant<
std::monostate, // Invalid or immutable type
JsonArrayCompact*,
JsonObjectCompact*
>;
Key Characteristics: - Non-owning: Does not manage object lifetime - Mutable access: Allows modification of Array and Object contents - Limited scope: Only structured types support mutation (primitives are immutable) - Type-safe: Compile-time enforcement of mutability constraints
Usage Example:
UniformCompactPointer mut_ptr = handle.get_mut_ptr();
if (auto* array = std::get_if<JsonArrayCompact*>(&mut_ptr)) {
(*array)->push_back(JsonIntCompact::create(42));
} else if (auto* object = std::get_if<JsonObjectCompact*>(&mut_ptr)) {
(*object)->set("key", JsonStringCompact::create("value"));
}
OwnedUniformCompactPointer¶
A tagged pointer that owns the underlying JSON object. Used internally by handle managers for storage.
Header: include/uniform_compact_pointer.hpp
Key Features: - Ownership: Automatically destroys the object in destructor - Tagged pointer: Uses 4 high bits for type information - Move-only: Cannot be copied (enforces unique ownership) - Type-safe construction: Separate constructors for each JSON type
Tagged Pointer Encoding¶
OwnedUniformCompactPointer Memory Layout:
,
Type (4 bits) Pointer (60 bits)
<