Line data Source code
1 : #ifndef HEADER_fd_src_funk_fd_funk_h
2 : #define HEADER_fd_src_funk_fd_funk_h
3 :
4 : /* Funk is a hybrid of a database and version control system designed
5 : for ultra high performance blockchain applications.
6 :
7 : The data model is a flat table of records. A record is a xid/key-val
8 : pair and records are fast O(1) indexable by their xid/key. xid is
9 : short for "transaction id" and xids have a compile time fixed size
10 : (e.g. 32-bytes). keys also have a compile time fixed size (e.g.
11 : 64-bytes). Record values can vary in length from zero to a compile
12 : time maximum size. The xid of all zeros is reserved for the "root"
13 : transaction described below. Outside this, there are no
14 : restrictions on what a record xid, key or val can be. Individual
15 : records can be created, updated, and deleted arbitrarily. They are
16 : just binary data as far as funk is concerned.
17 :
18 : The maximum number of records is practically only limited by the size
19 : of the workspace memory backing it. At present, each record requires
20 : 160 bytes of metadata (this includes records that are published and
21 : records that are in the process of being updated). In other words,
22 : about 15 GiB record metadata per hundred million records. The
23 : maximum number of records that can be held by a funk instance is set
24 : when that it was created (given the persistent and relocatable
25 : properties described below though, it is straightforward to resize
26 : this).
27 :
28 : The transaction model is richer than what is found in a regular
29 : database. A transaction is a xid-"updates to parent transaction"
30 : pair and transactions are fast O(1) indexable by xid. There is no
31 : limitation on the number of updates in a transaction. Updates to the
32 : record value are represented as the complete value record to make it
33 : trivial to apply cryptographic operations like hashing to all updated
34 : values in a transaction with file I/O, operating system calls, memory
35 : data marshalling overhead, etc.
36 :
37 : Like records, the maximum number of transactions in preparation is
38 : practically only limited by the size of the workspace memory backing
39 : it. At present, a transaction requires 96 bytes of memory. As such,
40 : it is practical to track a large number of forks during an extended
41 : period of time of consensus failure in a block chain application
42 : without using much workspace memory at all. The maximum number of
43 : transactions that can be in preparation at any given time by a funk
44 : instance is set when that it was created (as before, given the
45 : persistent and relocatable properties described below, it is
46 : straightforward to resize this).
47 :
48 : That is, a transaction is a compact representation of the entire
49 : history of _all_ the database records up to that transaction. We can
50 : trace a transaction's ancestors back to the "root" give the complete
51 : history of all database records up to that transaction. The “root”
52 : transaction is the ancestor of all transactions. The transaction
53 : history is linear from the root transaction until the "last
54 : published" transaction and cannot be modified.
55 :
56 : To start "preparing" a new transaction, we pick the new transaction's
57 : xid (ideally unique among all transactions thus far) and fork off a
58 : "parent" transaction. This operation virtually clones all database
59 : records in the parent transaction, even if the parent itself has not
60 : yet been "published". Given the above, the parent transaction can be
61 : the last published transaction or another in-preparation transaction.
62 :
63 : Record creates, reads, writes, erases take place within the context
64 : of a transaction, effectively isolating them to a private view of the
65 : world. If a transaction is "cancelled", the changes to a record are
66 : harmlessly discarded. Records in a transaction that has children
67 : cannot be changed ("frozen").
68 :
69 : As such, it is not possible to modify the records in transactions
70 : strictly before the last published transaction. However, it is
71 : possible to modify the records of the last published transaction if
72 : there is no transactions in preparation. This is useful, for
73 : example, loading up a transaction from a checkpointed state on
74 : startup. A common idiom at start of a block though is to fork the
75 : potential transaction of that block from its parent (freezing its
76 : parent) and then fork a child of the potential transaction that will
77 : hold updates to the block that are incrementally "merged" into the
78 : potential transaction as block processing progresses.
79 :
80 : Critically, in-preparation transactions form a tree of dependent and
81 : competing histories. This model matches blockchains, where
82 : speculative work can proceed on several blocks at once long before
83 : the blocks are finalized. When a transaction is published, all its
84 : ancestors are also published, any competing histories are
85 : cancelled, leaving only a linear history up to the published
86 : transaction. There is no practical limitation on the complexity of
87 : this tree.
88 :
89 : Funk tolerates applications crashing or being killed. On a clean
90 : process termination, the state of the database will correspond to the
91 : last published transactions and all in-preparation transactions as
92 : they were at termination. Extensive memory integrity checkers are
93 : provided to help with resuming / recovering if a code is killed
94 : uncleanly / crashes / etc in the middle of funk operations. Hardware
95 : failures (or abrupt power loss) are not handled. These latter
96 : scenarios require hardware solutions such redundant disk arrays and
97 : uninterruptible power supplies and/or background methods for writing
98 : published records to permanent storage described below.
99 :
100 : Under the hood, the database state is stored in NUMA and TLB
101 : optimized shared memory (i.e. fd_wksp) such that various database
102 : operations can be used concurrently by multiple threads distributed
103 : arbitrarily over multiple processes zero copy.
104 :
105 : Database operations are at algorithmic minimums with reasonably high
106 : performance implementations. Most are fast O(1) time and all are
107 : small O(1) space (e.g. in complex transaction tree operations, there
108 : is no use of dynamic allocation to hold temporaries and no use of
109 : recursion to bound stack utilization at trivial levels). Further,
110 : there are no explicit operating system calls and, given a well
111 : optimized workspace (i.e. the wksp pages fit within a core's TLBs) no
112 : implicit operating system calls. Critical operations (e.g. those
113 : that actually might impact transaction history) are fortified against
114 : memory corruption (e.g. robust against DoS attack by corrupting
115 : transaction metadata to create loops in transaction trees or going
116 : out of bounds in memory). Outside of record values, all memory used
117 : is preallocated. And record values are O(1) lockfree concurrent
118 : allocated via fd_alloc using the same wksp as funk (the
119 : implementation is structured in layers that are straightforward to
120 : retarget for particular applications as might be necessary).
121 :
122 : The shared memory used by a funk instance is within a workspace such
123 : that it is also persistent and remotely inspectable. For example, a
124 : process attached to a funk instance can be terminated and a new
125 : process can resume exactly where the original process left off
126 : instantly (e.g. no file I/O). Or a real-time monitor could be
127 : visualizing the ongoing activity in a database non-invasively (e.g.
128 : forks in flight, records updated by forks, etc). Or an auxiliary
129 : process could be lazily and non-invasively writing all published
130 : records to permanent storage in the background in parallel with
131 : on-going operations.
132 :
133 : The records are further stored in the workspace memory relocatably.
134 : For example, workspace memory could just be committed to a persistent
135 : memory as is (or backed by NVMe or such directly), copied to a
136 : different host, and processes on the new host could resume (indeed,
137 : though it wouldn't be space efficient, the shared memory region is
138 : usable as is as an on-disk checkpoint file). Or the workspace could
139 : be resized and what not to handle large needs than when the database
140 : was initially created and it all "just works". */
141 :
142 : //#include "fd_funk_base.h" /* Includes ../util/fd_util.h */
143 : //#include "fd_funk_txn.h" /* Includes fd_funk_base.h */
144 : //#include "fd_funk_rec.h" /* Includes fd_funk_txn.h */
145 : #include "fd_funk_val.h" /* Includes fd_funk_rec.h */
146 :
147 : /* FD_FUNK_{ALIGN,FOOTPRINT} describe the alignment and footprint needed
148 : for a funk. ALIGN should be a positive integer power of 2.
149 : FOOTPRINT is multiple of ALIGN. These are provided to facilitate
150 : compile time declarations. */
151 :
152 : #define FD_FUNK_ALIGN (128UL)
153 : #define FD_FUNK_FOOTPRINT (256UL)
154 :
155 : /* The details of a fd_funk_private are exposed here to facilitate
156 : inlining various operations. */
157 :
158 30 : #define FD_FUNK_MAGIC (0xf17eda2ce7fc2c01UL) /* firedancer funk version 1 */
159 :
160 : struct __attribute__((aligned(FD_FUNK_ALIGN))) fd_funk_private {
161 :
162 : /* Metadata */
163 :
164 : ulong magic; /* ==FD_FUNK_MAGIC */
165 : ulong funk_gaddr; /* wksp gaddr of this in the backing wksp, non-zero gaddr */
166 : ulong wksp_tag; /* Tag to use for wksp allocations, positive */
167 : ulong seed; /* Seed for various hashing function used under the hood, arbitrary */
168 : ulong cycle_tag; /* Next cycle_tag to use, used internally for various data integrity checks */
169 : volatile ulong write_lock; /* Incremented at the start of a write operation, and again at the end */
170 :
171 : /* The funk transaction map stores the details about transactions
172 : in preparation and their relationships to each other. This is a
173 : fd_map_giant and more details are given in fd_funk_txn.h
174 :
175 : txn_max is the maximum number of transactions that can be in
176 : preparation. Due to the use of compressed map indices to reduce
177 : workspace memory footprint required, txn_max is at most
178 : FD_FUNK_TXN_IDX_NULL (currently ~4B). This should be more than
179 : ample for anticipated uses cases ... e.g. every single validator in
180 : a pool of tens of thousands Solana validator had its own fork and
181 : with no consensus ever being achieved, a funk with txn_max at the
182 : limits of a compressed index will be chug along for days to weeks
183 : before running out of indexing space. But if ever needing to
184 : support more, it is straightforward to change the code to not use
185 : index compression. Then, a funk (with a planet sized workspace
186 : backing it) would survive a similar scenario for millions of years.
187 : Presumably, if such a situation arose, in the weeks to eons while
188 : there was consensus, somebody would notice and care enough to
189 : intervene (if not it is probably irrelevant to the real world
190 : anyway).
191 :
192 : txn_map_gaddr is the wksp gaddr of the fd_funk_txn_map_t used by
193 : this funk. Since this is a fd_map_giant under the hood and those
194 : are relocatable, it is possible to move this around within the wksp
195 : backing the funk if necessary. Such can be helpful if needing to
196 : do offline rebuilding, resizing, serialization, deserialization,
197 : etc.
198 :
199 : child_{head,tail}_cidx are compressed txn map indices. After
200 : decompression, they give the txn map index of the {oldest,youngest}
201 : child of funk (i.e. an in-preparation transaction whose parent
202 : transaction id is last_publish). FD_FUNK_TXN_IDX_NULL indicates
203 : the funk is childless. Thus, if head/tail is FD_FUNK_TXN_IDX_NULL,
204 : tail/head will be too. Records in a childless funk can be
205 : modified. Will be FD_FUNK_TXN_IDX_NULL if txn_max is zero.
206 :
207 : last_publish is the ID of the last published transaction. It will
208 : be the root transaction if no transactions have been published.
209 : Will be the root transaction immediately after construction. */
210 :
211 : ulong txn_max; /* In [0,FD_FUNK_TXN_IDX_NULL] */
212 : ulong txn_map_gaddr; /* Non-zero wksp gaddr with tag wksp_tag
213 : seed ==fd_funk_txn_map_seed (txn_map)
214 : txn_max==fd_funk_txn_map_key_max(txn_map) */
215 : uint child_head_cidx; /* After decompression, in [0,txn_max) or FD_FUNK_TXN_IDX_NULL, FD_FUNK_TXN_IDX_NULL if txn_max 0 */
216 : uint child_tail_cidx; /* " */
217 :
218 : /* Padding to FD_FUNK_TXN_XID_ALIGN here */
219 :
220 : fd_funk_txn_xid_t root[1]; /* Always equal to the root transaction */
221 : fd_funk_txn_xid_t last_publish[1]; /* Root transaction immediately after construction, not root thereafter */
222 :
223 : /* The funk record map stores the details about all the records in
224 : the funk, including all those in the last published transaction and
225 : all those getting updated in an in-preparation translation. This
226 : is a fd_map_giant and more details are given in fd_funk_txn.h
227 :
228 : rec_max is the maximum number of records that can exist in this
229 : funk.
230 :
231 : rec_map_gaddr is the wksp gaddr of the fd_funk_rec_map_t used by
232 : this funk. Since this is a fd_map_giant under the hood and those
233 : are relocatable, it is possible to move this around within the wksp
234 : backing the funk if necessary. Such can be helpful if needing to
235 : do offline rebuilding, resizing, serialization, deserialization,
236 : etc. */
237 :
238 : ulong rec_max;
239 : ulong rec_map_gaddr; /* Non-zero wksp gaddr with tag wksp_tag
240 : seed ==fd_funk_rec_map_seed (rec_map)
241 : rec_max==fd_funk_rec_map_key_max(rec_map) */
242 : ulong rec_head_idx; /* Record map index of the first record, FD_FUNK_REC_IDX_NULL if none (from oldest to youngest) */
243 : ulong rec_tail_idx; /* " last " */
244 :
245 : /* The funk alloc is used for allocating wksp resources for record
246 : values. This is a fd_alloc and more details are given in
247 : fd_funk_val.h. Allocations from this allocator will be tagged with
248 : wksp_tag and operations on this allocator will use concurrency
249 : group 0.
250 :
251 : TODO: Consider letter user just passing a join of alloc (and maybe
252 : the cgroup_idx to give the funk), inferring the wksp, cgroup from
253 : that and allocating exclusively from that? */
254 :
255 : ulong alloc_gaddr; /* Non-zero wksp gaddr with tag wksp tag */
256 :
257 : /* Padding to FD_FUNK_ALIGN here */
258 : };
259 :
260 : FD_PROTOTYPES_BEGIN
261 :
262 : /* Constructors */
263 :
264 : /* fd_funk_{align,footprint} return FD_FUNK_{ALIGN,FOOTPRINT}. */
265 :
266 : FD_FN_CONST ulong
267 : fd_funk_align( void );
268 :
269 : FD_FN_CONST ulong
270 : fd_funk_footprint( void );
271 :
272 : /* fd_wksp_new formats an unused wksp allocation with the appropriate
273 : alignment and footprint as a funk. Caller is not joined on return.
274 : Returns shmem on success and NULL on failure (shmem NULL, shmem
275 : misaligned, zero wksp_tag, shmem is not backed by a wksp ... logs
276 : details). A workspace can be used by multiple funk concurrently.
277 : They will dynamically share the underlying workspace (along with any
278 : other non-funk usage) but will otherwise act as completely separate
279 : non-conflicting funks. To help with various diagnostics, garbage
280 : collection and what not, all allocations to the underlying wksp are
281 : tagged with the given tag (positive). Ideally, the tag used here
282 : should be distinct from all other tags used by this workspace but
283 : this is not required. */
284 :
285 : void *
286 : fd_funk_new( void * shmem,
287 : ulong wksp_tag,
288 : ulong seed,
289 : ulong txn_max,
290 : ulong rec_max );
291 :
292 : /* fd_funk_join joins the caller to a funk instance. shfunk points to
293 : the first byte of the memory region backing the funk in the caller's
294 : address space. Returns an opaque handle of the join on success
295 : (IMPORTANT! DO NOT ASSUME THIS IS A CAST OF SHFUNK) and NULL on
296 : failure (NULL shfunk, misaligned shfunk, shfunk is not backed by a
297 : wksp, bad magic, ... logs details). Every successful join should
298 : have a matching leave. The lifetime of the join is until the
299 : matching leave or the thread group is terminated (joins are local to
300 : a thread group). */
301 :
302 : fd_funk_t *
303 : fd_funk_join( void * shfunk );
304 :
305 : /* fd_funk_leave leaves an existing join. Returns the underlying
306 : shfunk (IMPORTANT! DO NOT ASSUME THIS IS A CAST OF FUNK) on success
307 : and NULL on failure. Reasons for failure include funk is NULL (logs
308 : details). */
309 :
310 : void *
311 : fd_funk_leave( fd_funk_t * funk );
312 :
313 : /* fd_funk_delete unformats a wksp allocation used as a funk
314 : (additionally frees all wksp allocations used by that funk). Assumes
315 : nobody is or will be joined to the funk. Returns shmem on success
316 : and NULL on failure (logs details). Reasons for failure include
317 : shfunk is NULL, misaligned shfunk, shfunk is not backed by a
318 : workspace, etc. */
319 :
320 : void *
321 : fd_funk_delete( void * shfunk );
322 :
323 : /* Accessors */
324 :
325 : /* fd_funk_wksp returns the local join to the wksp backing the funk.
326 : The lifetime of the returned pointer is at least as long as the
327 : lifetime of the local join. Assumes funk is a current local join. */
328 :
329 1770232167 : FD_FN_PURE static inline fd_wksp_t * fd_funk_wksp( fd_funk_t * funk ) { return (fd_wksp_t *)(((ulong)funk) - funk->funk_gaddr); }
330 :
331 : /* fd_funk_wksp_tag returns the workspace allocation tag used by the
332 : funk for its wksp allocations. Will be positive. Assumes funk is a
333 : current local join. */
334 :
335 9692199 : FD_FN_PURE static inline ulong fd_funk_wksp_tag( fd_funk_t * funk ) { return funk->wksp_tag; }
336 :
337 : /* fd_funk_seed returns the hash seed used by the funk for various hash
338 : functions. Arbitrary value. Assumes funk is a current local join.
339 : TODO: consider renaming hash_seed? */
340 :
341 3 : FD_FN_PURE static inline ulong fd_funk_seed( fd_funk_t * funk ) { return funk->seed; }
342 :
343 : /* fd_funk_txn_max returns maximum number of in-preparations the funk
344 : can support. Assumes funk is a current local join. Return in
345 : [0,FD_FUNK_TXN_IDX_NULL]. */
346 :
347 3 : FD_FN_PURE static inline ulong fd_funk_txn_max( fd_funk_t * funk ) { return funk->txn_max; }
348 :
349 : /* fd_funk_txn_map returns a pointer in the caller's address space to
350 : the funk's transaction map. */
351 :
352 : FD_FN_PURE static inline fd_funk_txn_t * /* Lifetime is that of the local join */
353 : fd_funk_txn_map( fd_funk_t * funk, /* Assumes current local join */
354 598413378 : fd_wksp_t * wksp ) { /* Assumes wksp == fd_funk_wksp( funk ) */
355 598413378 : return (fd_funk_txn_t *)fd_wksp_laddr_fast( wksp, funk->txn_map_gaddr );
356 598413378 : }
357 :
358 : /* fd_funk_last_publish_child_{head,tail} returns a pointer in the
359 : caller's address space to {oldest,young} child of funk, NULL if the
360 : funk is childless. All pointers are in the caller's address space.
361 : These are all a fast O(1) but not fortified against memory data
362 : corruption. */
363 :
364 : FD_FN_PURE static inline fd_funk_txn_t * /* Lifetime as described in fd_funk_txn_query */
365 : fd_funk_last_publish_child_head( fd_funk_t * funk, /* Assumes current local join */
366 5541 : fd_funk_txn_t * map ) { /* Assumes map == fd_funk_txn_map( funk, fd_funk_wksp( funk ) ) */
367 5541 : ulong idx = fd_funk_txn_idx( funk->child_head_cidx );
368 5541 : if( fd_funk_txn_idx_is_null( idx ) ) return NULL; /* TODO: Consider branchless? */
369 5538 : return map + idx;
370 5541 : }
371 :
372 : FD_FN_PURE static inline fd_funk_txn_t * /* Lifetime as described in fd_funk_txn_query */
373 : fd_funk_last_publish_child_tail( fd_funk_t * funk, /* Assumes current local join */
374 5541 : fd_funk_txn_t * map ) { /* Assumes map == fd_funk_txn_map( funk, fd_funk_wksp( funk ) ) */
375 5541 : ulong idx = fd_funk_txn_idx( funk->child_tail_cidx );
376 5541 : if( fd_funk_txn_idx_is_null( idx ) ) return NULL; /* TODO: Consider branchless? */
377 5538 : return map + idx;
378 5541 : }
379 :
380 : /* fd_funk_root returns a pointer in the caller's address space to the
381 : transaction id of the root transaction. Assumes funk is a current
382 : local join. Lifetime of the returned pointer is the lifetime of the
383 : current local join. The value at this pointer will always be the
384 : root transaction id. */
385 :
386 412894035 : FD_FN_CONST static inline fd_funk_txn_xid_t const * fd_funk_root( fd_funk_t * funk ) { return funk->root; }
387 :
388 : /* fd_funk_last_publish returns a pointer in the caller's address space
389 : to transaction id of the last published transaction. Assumes funk is
390 : a current local join. Lifetime of the returned pointer is the
391 : lifetime of the current local join. The value at this pointer will
392 : be constant until the next transaction is published. */
393 :
394 3145734 : FD_FN_CONST static inline fd_funk_txn_xid_t const * fd_funk_last_publish( fd_funk_t * funk ) { return funk->last_publish; }
395 :
396 : /* fd_funk_is_frozen returns 1 if the records of the last published
397 : transaction are frozen (i.e. the funk has children) and 0 otherwise
398 : (i.e. the funk is childless). Assumes funk is a current local join. */
399 :
400 : FD_FN_PURE static inline int
401 244149156 : fd_funk_last_publish_is_frozen( fd_funk_t const * funk ) {
402 244149156 : return fd_funk_txn_idx( funk->child_head_cidx )!=FD_FUNK_TXN_IDX_NULL;
403 244149156 : }
404 :
405 : /* fd_funk_rec_max returns maximum number of records that can be held
406 : in the funk. This includes both records of the last published
407 : transaction and records for transactions that are in-flight. */
408 :
409 0 : FD_FN_PURE static inline ulong fd_funk_rec_max( fd_funk_t * funk ) { return funk->rec_max; }
410 :
411 : /* fd_funk_rec_map returns a pointer in the caller's address space to
412 : the funk's record map. */
413 :
414 : FD_FN_PURE static inline fd_funk_rec_t * /* Lifetime is that of the local join */
415 : fd_funk_rec_map( fd_funk_t * funk, /* Assumes current local join */
416 1706714631 : fd_wksp_t * wksp ) { /* Assumes wksp == fd_funk_wksp( funk ) */
417 1706714631 : return (fd_funk_rec_t *)fd_wksp_laddr_fast( wksp, funk->rec_map_gaddr );
418 1706714631 : }
419 :
420 : /* fd_funk_rec_global_cnt returns current number of records that are held
421 : in the funk. This includes both records of the last published
422 : transaction and records for transactions that are in-flight. */
423 : FD_FN_PURE static inline ulong
424 : fd_funk_rec_global_cnt( fd_funk_t * funk, /* Assumes current local join */
425 0 : fd_wksp_t * wksp ) { /* Assumes wksp == fd_funk_wksp( funk ) */
426 0 : fd_funk_rec_t * map = (fd_funk_rec_t *)fd_wksp_laddr_fast( wksp, funk->rec_map_gaddr );
427 0 : return fd_funk_rec_map_key_cnt( map );
428 0 : }
429 :
430 : /* fd_funk_last_publish_rec_{head,tail} returns a pointer in the
431 : caller's address space to {oldest,young} record (by creation) of all
432 : records in the last published transaction, NULL if the last published
433 : transaction has no records. All pointers are in the caller's address
434 : space. These are all a fast O(1) but not fortified against memory
435 : data corruption. */
436 :
437 : FD_FN_PURE static inline fd_funk_rec_t const * /* Lifetime as described in fd_funk_rec_query */
438 : fd_funk_last_publish_rec_head( fd_funk_t const * funk, /* Assumes current local join */
439 204333378 : fd_funk_rec_t const * rec_map ) { /* Assumes == fd_funk_rec_map( funk, fd_funk_wksp( funk ) ) */
440 204333378 : ulong rec_head_idx = funk->rec_head_idx;
441 204333378 : if( fd_funk_rec_idx_is_null( rec_head_idx ) ) return NULL; /* TODO: consider branchless */
442 204333366 : return rec_map + rec_head_idx;
443 204333378 : }
444 :
445 : FD_FN_PURE static inline fd_funk_rec_t const * /* Lifetime as described in fd_funk_rec_query */
446 : fd_funk_last_publish_rec_tail( fd_funk_t const * funk, /* Assumes current local join */
447 201187650 : fd_funk_rec_t const * rec_map ) { /* Assumes == fd_funk_rec_map( funk, fd_funk_wksp( funk ) ) */
448 201187650 : ulong rec_tail_idx = funk->rec_tail_idx;
449 201187650 : if( fd_funk_rec_idx_is_null( rec_tail_idx ) ) return NULL; /* TODO: consider branchless */
450 201187647 : return rec_map + rec_tail_idx;
451 201187650 : }
452 :
453 : /* fd_funk_alloc returns a pointer in the caller's address space to
454 : the funk's allocator. */
455 :
456 : FD_FN_PURE static inline fd_alloc_t * /* Lifetime is that of the local join */
457 : fd_funk_alloc( fd_funk_t * funk, /* Assumes current local join */
458 17456265 : fd_wksp_t * wksp ) { /* Assumes wksp == fd_funk_wksp( funk ) */
459 17456265 : return fd_alloc_join_cgroup_hint_set( (fd_alloc_t *)fd_wksp_laddr_fast( wksp, funk->alloc_gaddr ), fd_tile_idx() );
460 17456265 : }
461 :
462 : /* Operations */
463 :
464 : /* fd_funk_descendant returns the funk's youngest descendant that has no
465 : globally competing transaction history currently or NULL if funk
466 : has no children or all of the children of funk are in competition.
467 : That is, this is as far as fd_funk_txn_publish can publish before it
468 : needs to start canceling competing transaction histories. This is
469 : O(length of descendant history) and this is not fortified against
470 : transaction map data corruption. Assumes funk is a current local
471 : join. The returned pointer lifetime and address space is as
472 : described in fd_funk_txn_query. */
473 :
474 : FD_FN_PURE static inline fd_funk_txn_t *
475 : fd_funk_last_publish_descendant( fd_funk_t * funk,
476 3145731 : fd_funk_txn_t * txn_map ) { /* Assumes == fd_funk_txn_map( funk, fd_funk_wksp( funk ) ) */
477 3145731 : ulong child_idx = fd_funk_txn_idx( funk->child_head_cidx );
478 3145731 : if( fd_funk_txn_idx_is_null( child_idx ) ) return NULL;
479 2919273 : return fd_funk_txn_descendant( txn_map + child_idx, txn_map );
480 3145731 : }
481 :
482 : /* Misc */
483 :
484 : /* fd_funk_verify verifies the integrity of funk. Returns
485 : FD_FUNK_SUCCESS if funk appears to be intact and FD_FUNK_ERR_INVAL
486 : otherwise (logs details). Assumes funk is a current local join (NULL
487 : returns FD_FUNK_ERR_INVAL and logs details.) */
488 :
489 : int
490 : fd_funk_verify( fd_funk_t * funk );
491 :
492 : /* fd_funk_log_mem_usage logs useful statistics about memory usage */
493 :
494 : void
495 : fd_funk_log_mem_usage( fd_funk_t * funk );
496 :
497 : /* APIs for marking the start and end of an operation that modifies
498 : the database. These should be called by the application before and
499 : after doing an update. */
500 :
501 : void fd_funk_start_write( fd_funk_t * funk );
502 : void fd_funk_end_write( fd_funk_t * funk );
503 :
504 : /* Checks that we are inside a start_write/end_write block. Fails if
505 : * we are not. */
506 :
507 : void fd_funk_check_write( fd_funk_t * funk );
508 :
509 : FD_PROTOTYPES_END
510 :
511 : #endif /* HEADER_fd_src_funk_fd_funk_h */
|