libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_cskip.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * Use of this source code is governed by the ISC License.
4 *
5 * src/inc/xtc_cskip.h
6 * RCU-protected concurrent ordered map -- a lock-free-reader
7 * skiplist (Pugh 1990), the ORDERED sibling of xtc_chash. Where
8 * chash answers "is this exact key present," cskip additionally
9 * keeps keys in sorted order, so it answers "what is the smallest
10 * key," and "what is the largest key <= K" (a floor / predecessor
11 * lookup) -- the queries an unordered hash table cannot serve.
12 *
13 * Same storage convention as xtc_chash / xtc_pdict / xtc_chan:
14 * caller-owned `void *` key and value stored verbatim (no deep
15 * copy), with a caller-supplied comparator (qsort(3) convention;
16 * cskip uses the FULL ordering, not just equality). Same RCU
17 * reader contract: xtc_cskip_get / _min / _floor return a value
18 * pointer valid only inside the caller's OWN
19 * xtc_rcu_read_lock/_read_unlock bracket; insert/remove take the
20 * read-side internally for their traversal.
21 *
22 * Concurrency model (see src/ptc/cskip.c for the full discipline):
23 * - Readers are lock-free: they walk the tower with acquire
24 * loads. Each level's forward pointer is published with ONE
25 * release store, so a reader sees a fully-formed node or the
26 * prior chain, never a torn link.
27 * - A SINGLE writer mutex serializes insert/remove (v1 -- the
28 * reader path is the one that must scale; a fine-grained
29 * per-node writer lock is a deferred optimization). A node is
30 * linked bottom-up (level 0 first, so the moment it is
31 * reachable at the base level it is fully present at every
32 * level it will ever occupy is NOT assumed -- readers tolerate
33 * a node still being spliced into upper levels, because the
34 * base level is the source of truth for membership).
35 * - Removal unlinks top-down under the writer mutex, then
36 * retires the node via xtc_rcu_retire -- never freed inline; a
37 * reader already parked on the node keeps walking its
38 * unchanged forward pointers until it leaves its read-side.
39 * - This module never calls xtc_rcu_synchronize itself:
40 * reclamation is caller/reaper-driven, exactly like xtc_chash
41 * and every other xtc_rcu consumer here.
42 */
43
44#ifndef XTC_CSKIP_H
45#define XTC_CSKIP_H
46
47#include "xtc_export.h"
48
49#include <stddef.h>
50#include <stdint.h>
51
52#include "xtc.h"
53
54typedef struct xtc_cskip xtc_cskip_t;
55
56/* Key comparator, qsort(3) convention: <0 if a<b, 0 if equal, >0 if
57 * a>b. cskip uses the FULL ordering (it keeps keys sorted), so the
58 * comparator MUST be a total order over the key space, not merely an
59 * equality test. */
60typedef int (*xtc_cskip_cmp_fn)(const void *a, const void *b);
61
62/*
63 * PUBLIC: int xtc_cskip_create __P((xtc_cskip_cmp_fn, xtc_cskip_t **));
64 * PUBLIC: void xtc_cskip_destroy __P((xtc_cskip_t *));
65 * PUBLIC: int xtc_cskip_get __P((xtc_cskip_t *, const void *, void **));
66 * PUBLIC: int xtc_cskip_insert __P((xtc_cskip_t *, void *, void *, void **));
67 * PUBLIC: int xtc_cskip_remove __P((xtc_cskip_t *, const void *, void **));
68 * PUBLIC: int xtc_cskip_min __P((xtc_cskip_t *, void **, void **));
69 * PUBLIC: int xtc_cskip_floor __P((xtc_cskip_t *, const void *, void **, void **));
70 * PUBLIC: size_t xtc_cskip_size __P((const xtc_cskip_t *));
71 */
72
73/*
74 * Create an ordered map. cmp is required (non-NULL) and must be a
75 * total order. Returns XTC_OK and writes *out, or XTC_E_INVAL /
76 * XTC_E_NOMEM.
77 */
78XTC_API int xtc_cskip_create(xtc_cskip_cmp_fn cmp, xtc_cskip_t **out);
79
80/*
81 * Destroy the map. NOT concurrency-safe with any other call -- the
82 * caller must quiesce first (same contract as xtc_chash_destroy /
83 * xtc_rcu_fini). Frees node bookkeeping but NOT the caller-owned
84 * key/value pointers.
85 */
86XTC_API void xtc_cskip_destroy(xtc_cskip_t *s);
87
88/*
89 * Look up `key`. On a hit, XTC_OK + *out_value written; on a miss,
90 * XTC_E_NOTFOUND and *out_value untouched. MUST run inside the
91 * caller's own xtc_rcu_read_lock/_read_unlock bracket; the returned
92 * value pointer is valid only until the read-side ends.
93 */
94XTC_API int xtc_cskip_get(xtc_cskip_t *s, const void *key,
95 void **out_value);
96
97/*
98 * Insert (key, value), replacing any existing entry for an equal key.
99 * Takes the read-side internally. On replace, the OLD value pointer
100 * is written to *out_old_value (may be NULL) and is the caller's to
101 * free/retire (this map never frees caller payloads); on a fresh
102 * insert, *out_old_value (if non-NULL) is set to NULL. Returns
103 * XTC_OK or XTC_E_NOMEM.
104 */
105XTC_API int xtc_cskip_insert(xtc_cskip_t *s, void *key, void *value,
106 void **out_old_value);
107
108/*
109 * Remove the entry for `key`. On a hit, XTC_OK + removed value
110 * written to *out_value (may be NULL); the node is retired via
111 * xtc_rcu_retire, never freed synchronously. On a miss,
112 * XTC_E_NOTFOUND.
113 */
114XTC_API int xtc_cskip_remove(xtc_cskip_t *s, const void *key,
115 void **out_value);
116
117/*
118 * Smallest key currently in the map (the ordered query a hash table
119 * cannot serve). On a non-empty map, XTC_OK and writes the key
120 * pointer to *out_key (may be NULL) and the value to *out_value (may
121 * be NULL); on an empty map, XTC_E_NOTFOUND. MUST run inside the
122 * caller's own read-side; the returned pointers are valid only until
123 * the read-side ends.
124 */
125XTC_API int xtc_cskip_min(xtc_cskip_t *s, void **out_key,
126 void **out_value);
127
128/*
129 * Floor lookup: the entry with the largest key <= `key`. On a hit,
130 * XTC_OK and writes *out_key / *out_value (either may be NULL); if no
131 * key is <= `key` (or the map is empty), XTC_E_NOTFOUND. MUST run
132 * inside the caller's own read-side.
133 */
134XTC_API int xtc_cskip_floor(xtc_cskip_t *s, const void *key,
135 void **out_key, void **out_value);
136
137/* Approximate live entry count (an _Atomic counter; exact with no
138 * concurrent writers). */
139XTC_API size_t xtc_cskip_size(const xtc_cskip_t *s);
140
141#endif /* XTC_CSKIP_H */