JH-Toolkit v1.4.1
An engineering-oriented C++20 toolkit with duck-typed concepts, static design, async coroutines, and semantic containers — header-only, RTTI-free, and concurrency-friendly.
Loading...
Searching...
No Matches
occ_box.h File Reference

A generic container abstraction based on OCC (Optimistic Concurrency Control). More...

#include "jh/detail/shared_ptr_atomic_shim.h"
#include <memory>
#include <atomic>
#include <concepts>
#include <cstdint>
#include <functional>
#include <type_traits>
#include <optional>

Go to the source code of this file.

Classes

class  jh::conc::occ_box< T >
 Generic container providing Optimistic Concurrency Control (OCC). More...

Namespaces

namespace  jh::conc
 Aggregated namespace for concurrency-aware resource containers.

Macros

#define JH_OCC_ENABLE_MULTI_COMMIT   1
 Enable transactional multi-commit support for occ_box.

Functions

template<typename... Boxes, typename... Funcs>
bool jh::conc::apply_to (std::tuple< Boxes &... > boxes, std::tuple< Funcs... > &&funcs)
 Apply functions to multiple occ_boxes atomically.

Detailed Description

A generic container abstraction based on OCC (Optimistic Concurrency Control).

Author
JeongHan-Bae <mastropseudo@gmail.com>

Concurrency control models

  • LBCC (Lock-Based Concurrency Control)
    Built-in support in C++ (std::mutex, std::shared_mutex).
    Flexible, efficient, but requires careful lock ordering to avoid deadlocks.
    No wrapper provided in this library.

  • MVCC (Multi-Version Concurrency Control)
    Used in databases for snapshot isolation.
    Requires version chains, garbage collection, and complex rules.
    Not suitable for lightweight, in-memory concurrency here.

  • OCC (Optimistic Concurrency Control)
    Implemented here as occ_box<T>.
    Works for arbitrary copy/move-constructible types.
    Provides optimistic reads and atomic replacement writes.

Read cost model

A single read() operation typically incurs:

  • Two atomic loads of shared_ptr<state> (before/after validation).
  • Two pointer dereferences (state → data → object).
  • One function invocation (the user lambda).
  • By default (JH_OCC_ENABLE_MULTI_COMMIT == 1), one extra atomic load of flag_ is performed during validation.

Reads are wait-free, never block writes, and retries only if a concurrent commit changes the state pointer between the two loads.

Write semantics

  • write() always creates a fresh copy of the object, applies the user lambda, and commits with a single CAS.
  • write_ptr() allows the caller to supply a new shared_ptr<T>, avoiding deep copy overhead for large or expensive-to-copy objects.
  • Both guarantee atomic replacement: no reader ever observes a partially written object.

try_* methods and retries

  • All try_*() methods perform the first attempt outside of the loop.
  • This ensures that retries == 1 executes exactly once with no loop overhead.
  • retries == 0 is equivalent to 1 (a single attempt).
  • For retries > 1, the loop covers the remaining retries - 1 attempts.
  • Implementations are intentionally not abstracted into a common helper: any such abstraction would either add an std::optional allocation or an extra invoke in the retry path, both undesirable in hot loops.
  • Users may implement exponential backoff or jitter in retry lambdas to mitigate contention. See examples/example_occ_box.cpp for a full example. In short: the lambda can take a duration&, sleep if nonzero, update it (0 → min → min×base ... capped at max), then run business logic.

Atomicity and contention

  • Strong atomicity: each commit replaces the entire state (data + version) with a single CAS.
  • Readers are always safe: they either succeed with a consistent snapshot or retry internally.
  • Writers never expose intermediate states.
  • High-frequency writes may increase retries, but safety is never compromised.
  • When JH_OCC_ENABLE_MULTI_COMMIT == 1 (default), contention is resolved by strict priority: multi-write > single-write > read.

Multi-commit policy

  • If JH_OCC_ENABLE_MULTI_COMMIT is 1 (default):
    • occ_box supports apply_to() for atomic multi-box transactions.
    • Each box carries an extra flag_ (atomic<bool>) used as a transaction marker.
    • read() incurs one additional atomic load to check flag_.
    • Conflict resolution follows: multi-write > single-write > read.
  • If JH_OCC_ENABLE_MULTI_COMMIT is 0:
    • apply_to() is disabled.
    • occ_box does not contain flag_, reducing object size.
    • Single-box OCC still works, with lighter read() cost.

Design intent

  • Correctness and composability over raw microsecond performance.
  • Deadlock-free by design: readers never block writers, writers never block readers.
  • Best suited for application-level concurrency where retries are acceptable.
Version
1.4.x
Date
2025

Macro Definition Documentation

◆ JH_OCC_ENABLE_MULTI_COMMIT

#define JH_OCC_ENABLE_MULTI_COMMIT   1

Enable transactional multi-commit support for occ_box.

Default: 1 (enabled)

If not defined manually, it defaults to:

#define JH_OCC_ENABLE_MULTI_COMMIT 1

If overriding manually in source code, it must be defined before any inclusion of the library public header (i.e. <jh/concurrency>) within the translation unit.

Configuration methods:

  • Source-level (before any include of the library header):
        #define JH_OCC_ENABLE_MULTI_COMMIT 0
        
  • CMake:
        target_compile_definitions(target PRIVATE JH_OCC_ENABLE_MULTI_COMMIT=0)
        
  • Compiler flag:
        -DJH_OCC_ENABLE_MULTI_COMMIT=0
        

When set to 1:

  • Enables apply_to() transactional operations.
  • Allows multiple occ_box instances to commit atomically as a single transaction (multi-commit).
  • Single-box logic and correctness guarantees remain unchanged.
  • Adds a small additional read-path overhead (one extra atomic load).

When set to 0:

  • apply_to() is not compiled.
  • Only single-box OCC is available.
  • No transactional coordination overhead is introduced.