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