|
|
| occ_box (const occ_box &other) noexcept |
| | Copy constructor: manually resets flag_ to false, cannot use =default.
|
|
occ_box & | operator= (const occ_box &other) noexcept |
| | Copy assignment: manually resets flag_ to false, cannot use =default.
|
|
| occ_box (occ_box &&other) noexcept |
| | Move constructor: transfers state but resets flag_ to false, cannot use =default.
|
|
occ_box & | operator= (occ_box &&other) noexcept |
| | Move assignment: transfers state but resets flag_ to false, cannot use =default.
|
| | occ_box (std::shared_ptr< T > ptr) |
| | Construct a new occ_box from an existing shared_ptr<T>.
|
| template<typename... Args> |
| | occ_box (Args &&... args) |
| | Construct a new occ_box by forwarding arguments to T.
|
| template<typename F, typename... Args> |
| auto | read (F &&f, Args &&... args) const |
| | Blocking read with optimistic validation.
|
| template<typename F, typename... Args> |
| auto | try_read (F &&f, std::uint16_t retries=1, Args &&... args) const -> std::optional< std::invoke_result_t< F, const T &, Args... > > |
| | Non-blocking read with limited retries.
|
| template<typename F, typename... Args> |
| void | write (F &&f, Args &&... args) |
| | Blocking write with optimistic commit-replace semantics.
|
| template<typename F, typename... Args> |
| bool | try_write (F &&f, std::uint16_t retries=1, Args &&... args) |
| | Non-blocking write with limited retries.
|
| template<typename F, typename... Args> |
| void | write_ptr (F &&f, Args &&... args) |
| | Blocking write using pointer replacement.
|
| template<typename F, typename... Args> |
| bool | try_write_ptr (F &&f, std::uint16_t retries=1, Args &&... args) |
| | Non-blocking pointer-based write with limited retries.
|
| std::uint64_t | get_version () const noexcept |
| | Get the current version counter of the box.
|
template<typename T>
requires (std::is_copy_constructible_v<T> && std::is_move_constructible_v<T>)
class jh::conc::occ_box< T >
Generic container providing Optimistic Concurrency Control (OCC).
- Template Parameters
-
| T | Value type, must be copy- and move-constructible. |
Semantics
-
Encapsulates a value of type
T with atomic versioned state.
-
Reads are wait-free: they either succeed with a consistent snapshot or retry internally.
-
Writes are commit-replace: each update creates a fresh state and replaces atomically via CAS.
-
No reader ever observes a partially written value.
Retry model
-
All
try_*() APIs attempt once outside the loop, then retry up to N-1 times.
-
retries == 0 is equivalent to one attempt.
-
Backoff and jitter strategies can be layered on top (see examples).
- Note
- When
JH_OCC_ENABLE_MULTI_COMMIT == 1 (default), boxes under apply_to() are given priority over single writes and reads, ensuring that multi-box transactions cannot be broken by concurrent commits.
template<typename T>
template<typename F, typename... Args>
Blocking read with optimistic validation.
- Template Parameters
-
| F | Callable type, must accept const T& and optional extra args. |
| Args | Additional argument types. |
- Parameters
-
| f | User-provided callable, invoked with the current value. |
| args | Optional extra arguments forwarded to the callable. |
- Returns
R, the value returned by the callable, provided the read passes optimistic validation (snapshot is consistent).
Semantics
-
Performs a load-invoke-validate sequence under optimistic concurrency.
-
If the state changes between two atomic loads, the read retries internally.
-
Wait-free for readers: never blocks writers.
Why void is disallowed
Returning void is forbidden because read() must conceptually produce a value from the snapshot. Allowing void would encourage using this API solely for side effects, which violates the read model.
Permitted side effects
Minor auxiliary effects (e.g. updating a duration& for backoff logic, or writing to a log) are acceptable, provided they do not alter application state or depend on non-idempotent behavior.
For output purposes, prefer returning a value (e.g. a std::string built from an ostringstream) instead of directly printing inside read().
template<typename T>
template<typename F, typename... Args>
| auto jh::conc::occ_box< T >::try_read |
( |
F && | f, |
|
|
std::uint16_t | retries = 1, |
|
|
Args &&... | args ) const -> std::optional< std::invoke_result_t< F, const T &, Args... > > |
|
inlinenodiscard |
Non-blocking read with limited retries.
- Template Parameters
-
| F | Callable type, must accept const T& and optional extra args. |
| Args | Additional argument types. |
- Parameters
-
| f | User-provided callable, invoked with the current value. |
| retries | Maximum number of attempts (0 treated as 1). |
| args | Optional extra arguments forwarded to the callable. |
- Returns
std::optional<R>, where R is the callable's return type. Returns std::nullopt if all retries fail validation.
Semantics
-
Performs optimistic load-invoke-validate like
read().
-
Unlike
read(), it gives up after at most retries attempts.
-
Retry count
0 is normalized to one attempt.
Purity rule
Pure side-effect-only operations are disallowed: this method must conceptually produce a value from the snapshot. Minor auxiliary effects (e.g. backoff instrumentation, logging) are acceptable if they do not alter application state.
- Note
retries must be explicitly specified if extra args... are provided, since args always follow it in the parameter list.
- See also
- read()
template<typename T>
template<typename F, typename... Args>
| bool jh::conc::occ_box< T >::try_write |
( |
F && | f, |
|
|
std::uint16_t | retries = 1, |
|
|
Args &&... | args ) |
|
inlinenodiscard |
Non-blocking write with limited retries.
- Template Parameters
-
| F | Callable type, must accept T& and optional extra args. |
| Args | Additional argument types. |
- Parameters
-
| f | User-provided callable, applied to a freshly copied value. |
| retries | Maximum number of attempts (0 treated as 1). |
| args | Optional extra arguments forwarded to the callable. |
- Returns
true if the update is committed successfully, false if all attempts fail due to contention.
Semantics
-
Each attempt loads the current state, copies the value, applies the user function, and tries to commit via CAS.
-
Fails if another writer replaces the state before CAS succeeds.
-
Unlike
write(), this method does not spin indefinitely: it retries at most retries times.
Copy semantics
Each attempt deep-copies the underlying value. If deep copies are undesirable, consider try_write_ptr() to construct and install a new object directly.
Usage note
For retryable operations or performance-sensitive paths, prefer try_write() or try_write_ptr() over their blocking counterparts, since they allow graceful failure handling under contention.
- Note
retries must be explicitly specified if extra args... are provided, since args always follow it in the parameter list.
- See also
- write()
template<typename T>
template<typename F, typename... Args>
Blocking write with optimistic commit-replace semantics.
- Template Parameters
-
| F | Callable type, must accept T& and optional extra args. |
| Args | Additional argument types. |
- Parameters
-
| f | User-provided callable, applied to a fresh copy of the object. |
| args | Optional extra arguments forwarded to the callable. |
- Returns
- void
Semantics
-
Performs a load-copy-invoke-CAS loop until commit succeeds.
-
Always copies the current object before applying
f.
-
Guarantees atomic replacement: readers never see a partially written object.
Performance notes
-
Safe under high contention: even if writes are very frequent, each commit is strictly atomic and never exposes torn or inconsistent states.
-
Excessive use may hurt performance due to repeated deep copies and CAS retries, but correctness and race-freedom are guaranteed.
-
Prefer embedding repeated logic inside
f rather than invoking write() repeatedly in a loop.
-
If deep copies are undesirable, consider
write_ptr() to construct and install a new object directly.
Return semantics
This method always returns void. The write is guaranteed to complete before returning. It is best suited for critical update paths where completion is mandatory.
Fairness
On most POSIX platforms, the scheduler tends to grant forward progress, so livelock is practically avoided. However, applications should not over-rely on this property.
- See also
- write_ptr()