Line data Source code
1 : #include "fd_accdb_impl_v2.h"
2 : #include "fd_accdb_funk.h"
3 : #include "fd_vinyl_req_pool.h"
4 : #include <stdatomic.h>
5 :
6 : FD_STATIC_ASSERT( alignof(fd_accdb_user_v2_t)<=alignof(fd_accdb_user_t), layout );
7 : FD_STATIC_ASSERT( sizeof (fd_accdb_user_v2_t)<=sizeof(fd_accdb_user_t), layout );
8 :
9 : /* Record synchronization *********************************************/
10 :
11 : /* fd_funk_rec_write_{lock,unlock} acquire/release a record write lock.
12 :
13 : These are assumed to never fail because writes to accdb records are
14 : externally coordinated to be non-conflicting at the record level.
15 : In other words, it is assumed that, when the caller attempts to
16 : write to a record:
17 : - no admin attempts to root or cancel the DB txn this write targets
18 : (admin may only root/cancel txns after they are frozen, and users
19 : may only write to txns before they are frozen)
20 : - no other user attempts to read/write to the same record until the
21 : current thread is done writing. (Other threads wait for the
22 : current thread to signal completion before attempting to access) */
23 :
24 : static void
25 : fd_funk_rec_write_lock( fd_funk_t const * funk,
26 0 : fd_funk_rec_t * rec ) {
27 0 : ulong rec_idx = (ulong)( rec - funk->rec_pool->ele );
28 0 : ulong volatile * vl = &funk->rec_lock[ rec_idx ];
29 0 : ulong val = FD_VOLATILE_CONST( *vl );
30 0 : if( FD_UNLIKELY( fd_funk_rec_lock_bits( val ) ) ) {
31 0 : FD_LOG_CRIT(( "fd_funk_rec_write_lock(" FD_FUNK_REC_PAIR_FMT ") failed: record has active readers",
32 0 : FD_FUNK_REC_PAIR_FMT_ARGS( rec->pair ) ));
33 0 : }
34 0 : ulong val_new = fd_funk_rec_ver_lock( fd_funk_rec_ver_bits( val ), FD_FUNK_REC_LOCK_MASK );
35 0 : if( FD_UNLIKELY( FD_ATOMIC_CAS( vl, val, val_new )!=val ) ) {
36 0 : FD_LOG_CRIT(( "fd_funk_rec_write_lock(" FD_FUNK_REC_PAIR_FMT ") failed: data race detected",
37 0 : FD_FUNK_REC_PAIR_FMT_ARGS( rec->pair ) ));
38 0 : }
39 0 : }
40 :
41 : static void
42 : fd_funk_rec_write_lock_uncontended( fd_funk_t const * funk,
43 0 : fd_funk_rec_t * rec ) {
44 0 : ulong rec_idx = (ulong)( rec - funk->rec_pool->ele );
45 0 : ulong val = funk->rec_lock[ rec_idx ];
46 0 : ulong val_ver = fd_funk_rec_ver_bits( val );
47 0 : funk->rec_lock[ rec_idx ] = fd_funk_rec_ver_lock( val_ver, FD_FUNK_REC_LOCK_MASK );
48 0 : }
49 :
50 : static void
51 : fd_funk_rec_write_unlock( fd_funk_t const * funk,
52 0 : fd_funk_rec_t * rec ) {
53 0 : ulong rec_idx = (ulong)( rec - funk->rec_pool->ele );
54 0 : ulong volatile * vl = &funk->rec_lock[ rec_idx ];
55 0 : ulong val = FD_VOLATILE_CONST( *vl );
56 0 : if( FD_UNLIKELY( fd_funk_rec_lock_bits( val )!=FD_FUNK_REC_LOCK_MASK ) ) {
57 0 : FD_LOG_CRIT(( "fd_funk_rec_write_unlock(" FD_FUNK_REC_PAIR_FMT ") failed: record is not write locked",
58 0 : FD_FUNK_REC_PAIR_FMT_ARGS( rec->pair ) ));
59 0 : }
60 0 : ulong val_new = fd_funk_rec_ver_lock( fd_funk_rec_ver_bits( val ), 0UL );
61 0 : if( FD_UNLIKELY( FD_ATOMIC_CAS( vl, val, val_new )!=val ) ) {
62 0 : FD_LOG_CRIT(( "fd_funk_rec_write_unlock(" FD_FUNK_REC_PAIR_FMT ") failed: data race detected",
63 0 : FD_FUNK_REC_PAIR_FMT_ARGS( rec->pair ) ));
64 0 : }
65 0 : }
66 :
67 : /* fd_funk_rec_read_{lock_try,unlock} try-acquire/release a record read
68 : lock.
69 :
70 : Read lock acquires may fail for frozen txn. This is because an admin
71 : may concurrently root the txn as we are querying. */
72 :
73 : static int
74 : fd_funk_rec_read_lock( fd_funk_t const * funk,
75 0 : fd_funk_rec_t * rec ) {
76 0 : ulong rec_idx = (ulong)( rec - funk->rec_pool->ele );
77 0 : ulong volatile * vl = &funk->rec_lock[ rec_idx ];
78 0 : for(;;) {
79 0 : ulong val = FD_VOLATILE_CONST( *vl );
80 0 : ulong val_ver = fd_funk_rec_ver_bits ( val );
81 0 : ulong val_lock = fd_funk_rec_lock_bits( val );
82 0 : if( FD_UNLIKELY( !fd_funk_rec_ver_alive( val_ver ) ) ) {
83 0 : return FD_MAP_ERR_AGAIN;
84 0 : }
85 0 : if( FD_UNLIKELY( val_lock>=FD_FUNK_REC_LOCK_MASK-1 ) ) {
86 0 : FD_LOG_CRIT(( "fd_funk_rec_read_lock(" FD_FUNK_REC_PAIR_FMT ") failed: val_lock=%#lx (too many readers or already write locked)",
87 0 : FD_FUNK_REC_PAIR_FMT_ARGS( rec->pair ), val_lock ));
88 0 : }
89 0 : ulong val_new = fd_funk_rec_ver_lock( val_ver, val_lock+1UL );
90 0 : if( FD_LIKELY( FD_ATOMIC_CAS( vl, val, val_new )==val ) ) {
91 0 : return FD_MAP_SUCCESS;
92 0 : }
93 0 : }
94 0 : }
95 :
96 : static void
97 : fd_funk_rec_read_unlock( fd_funk_t const * funk,
98 0 : fd_funk_rec_t * rec ) {
99 0 : ulong rec_idx = (ulong)( rec - funk->rec_pool->ele );
100 0 : ulong volatile * vl = &funk->rec_lock[ rec_idx ];
101 0 : for(;;) {
102 0 : ulong val = FD_VOLATILE_CONST( *vl );
103 0 : ulong val_ver = fd_funk_rec_ver_bits ( val );
104 0 : ulong val_lock = fd_funk_rec_lock_bits( val );
105 0 : if( FD_UNLIKELY( val_lock==0UL || val_lock==FD_FUNK_REC_LOCK_MASK ) ) {
106 0 : FD_LOG_CRIT(( "fd_funk_rec_read_unlock(" FD_FUNK_REC_PAIR_FMT ") failed: val_lock=%#lx (cannot unlock)",
107 0 : FD_FUNK_REC_PAIR_FMT_ARGS( rec->pair ), val_lock ));
108 0 : }
109 0 : ulong val_new = fd_funk_rec_ver_lock( val_ver, val_lock-1UL );
110 0 : if( FD_LIKELY( FD_ATOMIC_CAS( vl, val, val_new )==val ) ) {
111 0 : return;
112 0 : }
113 0 : }
114 0 : }
115 :
116 : /* Record acquisition *************************************************/
117 :
118 : /* funk_rec_acquire finds the newest revision of 'key' in the funk hash
119 : chain at index chain_idx. Only considers records on the current fork
120 : (fork nodes stored in accdb).
121 :
122 : On success, returns ACQUIRE_{READ,WRITE} and points *out_rec to the
123 : record found. If is_write, a write-lock is acquired for this record,
124 : otherwise a read lock. It is the caller's responsibility to release
125 : this lock.
126 :
127 : If no record was found, returns ACQUIRE_NOT_FOUND.
128 :
129 : Returns ACQUIRE_FAILED on index contention (hash map locks) or
130 : record contention (admin started deleting the record just as we were
131 : about to start the access). The caller should retry in this case. */
132 :
133 0 : #define ACQUIRE_READ 0
134 0 : #define ACQUIRE_WRITE 1
135 0 : #define ACQUIRE_NOT_FOUND 2
136 0 : #define ACQUIRE_FAILED 3
137 :
138 : static int
139 : funk_rec_acquire( fd_accdb_user_v2_t const * accdb,
140 : ulong chain_idx,
141 : fd_funk_rec_key_t const * key,
142 : fd_funk_rec_t ** out_rec,
143 0 : _Bool is_write ) {
144 0 : *out_rec = NULL;
145 :
146 0 : fd_accdb_lineage_t const * lineage = accdb->lineage;
147 0 : fd_funk_rec_map_shmem_t const * shmap = accdb->funk->rec_map->map;
148 0 : fd_funk_rec_map_shmem_private_chain_t const * chain_tbl = fd_funk_rec_map_shmem_private_chain_const( shmap, 0UL );
149 0 : fd_funk_rec_map_shmem_private_chain_t const * chain = chain_tbl + chain_idx;
150 0 : fd_funk_rec_t * rec_tbl = accdb->funk->rec_pool->ele;
151 0 : ulong rec_max = fd_funk_rec_pool_ele_max( accdb->funk->rec_pool );
152 :
153 0 : uint ele_idx = chain->head_cidx;
154 0 : ulong _Atomic * ver_cnt_p = (ulong _Atomic *)&chain->ver_cnt;
155 0 : ulong ver_cnt = atomic_load_explicit( ver_cnt_p, memory_order_acquire );
156 :
157 : /* Start a speculative transaction for the chain containing revisions
158 : of the account key we are looking for. */
159 0 : ulong cnt = fd_funk_rec_map_private_vcnt_cnt( ver_cnt );
160 0 : if( FD_UNLIKELY( fd_funk_rec_map_private_vcnt_ver( ver_cnt )&1 ) ) {
161 0 : return ACQUIRE_FAILED; /* chain is locked */
162 0 : }
163 :
164 : /* Walk the map chain, bail at the first entry
165 : (Each chain is sorted newest-to-oldest) */
166 0 : fd_funk_rec_t * best = NULL;
167 0 : for( ulong i=0UL; i<cnt; i++ ) {
168 0 : uint ele_next = rec_tbl[ ele_idx ].map_next;
169 0 : if( FD_UNLIKELY( atomic_load_explicit( ver_cnt_p, memory_order_acquire )!=ver_cnt ) ) {
170 0 : return ACQUIRE_FAILED;
171 0 : }
172 :
173 0 : if( FD_UNLIKELY( ele_idx>=rec_max ) ) {
174 0 : FD_LOG_CRIT(( "funk_rec_acquire detected memory corruption: invalid ele_idx at node %lu:%u (rec_max %lu)",
175 0 : chain_idx, ele_idx, rec_max ));
176 0 : }
177 :
178 0 : fd_funk_rec_t * rec = &rec_tbl[ ele_idx ];
179 :
180 : /* Skip over unrelated records (hash collision) */
181 0 : if( FD_UNLIKELY( !fd_funk_rec_key_eq( rec->pair.key, key ) ) ) goto next;
182 :
183 : /* Confirm that record is part of the current fork */
184 0 : if( FD_UNLIKELY( !fd_accdb_lineage_has_xid( lineage, rec->pair.xid ) ) ) goto next;
185 :
186 0 : if( FD_UNLIKELY( ele_next==ele_idx ) ) {
187 0 : FD_LOG_CRIT(( "funk_rec_acquire detected cycle" ));
188 0 : }
189 0 : best = rec;
190 0 : break;
191 :
192 0 : next:
193 0 : ele_idx = ele_next;
194 0 : }
195 0 : if( FD_UNLIKELY( !best && ele_idx!=FD_FUNK_REC_IDX_NULL ) ) {
196 0 : FD_LOG_CRIT(( "funk_rec_acquire detected malformed chain (%lu): found more nodes than chain header indicated (%lu)", chain_idx, cnt ));
197 0 : }
198 :
199 : /* Found a record, acquire a lock */
200 0 : if( best ) {
201 : /* If the write does not target the current transaction, demote it */
202 0 : if( is_write && !fd_accdb_lineage_is_tip( accdb->lineage, best->pair.xid ) ) {
203 0 : is_write = 0;
204 0 : }
205 0 : if( is_write ) {
206 0 : fd_funk_rec_write_lock( accdb->funk, best );
207 0 : } else {
208 0 : if( fd_funk_rec_read_lock( accdb->funk, best )!=FD_MAP_SUCCESS ) {
209 0 : return ACQUIRE_FAILED; /* record about to be moved to vinyl */
210 0 : }
211 0 : }
212 0 : }
213 :
214 : /* Retry if there was contention at the hash map */
215 0 : if( FD_UNLIKELY( atomic_load_explicit( ver_cnt_p, memory_order_acquire )!=ver_cnt ) ) {
216 0 : if( best ) {
217 0 : if( is_write ) fd_funk_rec_write_unlock( accdb->funk, best );
218 0 : else fd_funk_rec_read_unlock ( accdb->funk, best );
219 0 : }
220 0 : return ACQUIRE_FAILED;
221 0 : }
222 :
223 0 : *out_rec = best;
224 0 : return best ? ( is_write ? ACQUIRE_WRITE : ACQUIRE_READ ) : ACQUIRE_NOT_FOUND;
225 0 : }
226 :
227 : static int
228 : funk_open_ref( fd_accdb_user_v2_t * accdb,
229 : fd_accdb_ref_t * ref,
230 : fd_funk_txn_xid_t const * xid,
231 : void const * address,
232 0 : _Bool is_write ) {
233 0 : fd_funk_t const * funk = accdb->funk;
234 0 : fd_funk_rec_key_t key[1]; memcpy( key->uc, address, 32UL );
235 :
236 : /* Hash key to chain */
237 0 : fd_funk_xid_key_pair_t pair[1];
238 0 : fd_funk_txn_xid_copy( pair->xid, xid );
239 0 : fd_funk_rec_key_copy( pair->key, key );
240 0 : fd_funk_rec_map_t const * rec_map = funk->rec_map;
241 0 : ulong hash = fd_funk_rec_map_key_hash( pair, rec_map->map->seed );
242 0 : ulong chain_idx = (hash & (rec_map->map->chain_cnt-1UL) );
243 :
244 : /* Traverse chain for candidate */
245 0 : fd_funk_rec_t * rec = NULL;
246 0 : int err;
247 0 : for(;;) {
248 0 : err = funk_rec_acquire( accdb, chain_idx, key, &rec, is_write );
249 0 : if( FD_LIKELY( err!=ACQUIRE_FAILED ) ) break;
250 0 : FD_SPIN_PAUSE();
251 : /* FIXME backoff */
252 0 : }
253 0 : if( rec ) {
254 0 : memcpy( ref->address, address, 32UL );
255 0 : ref->accdb_type = FD_ACCDB_TYPE_V1;
256 0 : ref->ref_type = err==ACQUIRE_WRITE ? FD_ACCDB_REF_RW : FD_ACCDB_REF_RO;
257 0 : ref->user_data = (ulong)rec;
258 0 : ref->user_data2 = 0UL;
259 0 : ref->meta_laddr = (ulong)fd_funk_val( rec, funk->wksp );
260 0 : } else {
261 0 : memset( ref, 0, sizeof(fd_accdb_rw_t) );
262 0 : }
263 0 : return err;
264 0 : }
265 :
266 : /* Read method ********************************************************/
267 :
268 : void
269 : fd_accdb_user_v2_open_ro_multi( fd_accdb_user_t * accdb,
270 : fd_accdb_ro_t * ro0,
271 : fd_funk_txn_xid_t const * xid,
272 : void const * addr0,
273 0 : ulong cnt ) {
274 0 : fd_accdb_user_v2_t * v2 = (fd_accdb_user_v2_t *)accdb;
275 0 : fd_vinyl_rq_t * rq = v2->vinyl_rq; /* "request queue "*/
276 0 : fd_vinyl_req_pool_t * req_pool = v2->vinyl_req_pool; /* "request pool" */
277 0 : fd_wksp_t * req_wksp = v2->vinyl_req_wksp; /* shm workspace containing request buffer */
278 0 : fd_wksp_t * data_wksp = v2->vinyl_data_wksp; /* shm workspace containing vinyl data cache */
279 0 : ulong link_id = v2->vinyl_link_id; /* vinyl client ID */
280 :
281 0 : if( FD_UNLIKELY( cnt>fd_vinyl_req_batch_key_max( req_pool ) ) ) {
282 0 : FD_LOG_CRIT(( "open_ro_multi cnt %lu exceeds vinyl request batch max %lu",
283 0 : cnt, fd_vinyl_req_batch_key_max( req_pool ) ));
284 0 : }
285 :
286 : /* Open accounts from funk
287 :
288 : (FIXME this is a potentially slow operation, might want to fire off
289 : a 'prefetch' instruction to vinyl asynchronously before doing this,
290 : so that the vinyl data is in cache by the time v1_open_rw_multi
291 : finishes) */
292 :
293 0 : fd_accdb_lineage_set_fork( v2->lineage, v2->funk, xid );
294 0 : ulong addr_laddr = (ulong)addr0;
295 0 : for( ulong i=0UL; i<cnt; i++ ) {
296 0 : void const * addr_i = (void const *)( (ulong)addr_laddr + i*32UL );
297 0 : if( funk_open_ref( v2, ro0[i].ref, xid, addr_i, 0 )==ACQUIRE_READ ) {
298 0 : v2->base.ro_active++;
299 0 : } else {
300 0 : fd_accdb_ro_init_empty( &ro0[i], addr_i );
301 0 : }
302 0 : }
303 :
304 : /* For the accounts that were not found in funk, open vinyl records */
305 :
306 0 : ulong batch_idx = fd_vinyl_req_pool_acquire( req_pool );
307 : /* req_pool_release called before returning */
308 0 : fd_vinyl_comp_t * comp = fd_vinyl_req_batch_comp ( req_pool, batch_idx );
309 0 : fd_vinyl_key_t * req_key0 = fd_vinyl_req_batch_key ( req_pool, batch_idx );
310 0 : schar * req_err0 = fd_vinyl_req_batch_err ( req_pool, batch_idx );
311 0 : ulong * req_val_gaddr0 = fd_vinyl_req_batch_val_gaddr( req_pool, batch_idx );
312 :
313 : /* Create a read-only vinyl "ACQUIRE" batch */
314 :
315 0 : ulong req_cnt = 0UL;
316 0 : for( ulong i=0UL; i<cnt; i++ ) {
317 0 : if( ro0[i].ref->accdb_type!=FD_ACCDB_TYPE_NONE ) continue;
318 : /* At this point, addr0[i] not found in funk, load from vinyl */
319 0 : void const * addr_i = (void const *)( (ulong)addr0 + i*32UL );
320 :
321 0 : fd_vinyl_key_init( req_key0+req_cnt, addr_i, 32UL );
322 0 : req_err0 [ req_cnt ] = 0;
323 0 : req_val_gaddr0[ req_cnt ] = 0UL;
324 0 : req_cnt++;
325 0 : }
326 0 : if( !req_cnt ) {
327 : /* All records were found in funk, bail early */
328 0 : fd_vinyl_req_pool_release( req_pool, batch_idx );
329 0 : return;
330 0 : }
331 :
332 : /* Send read-only "ACQUIRE" batch to vinyl and wait for response */
333 :
334 0 : ulong req_id = v2->vinyl_req_id++;
335 0 : memset( comp, 0, sizeof(fd_vinyl_comp_t) );
336 0 : fd_vinyl_req_send_batch( rq, req_pool, req_wksp, req_id, link_id, FD_VINYL_REQ_TYPE_ACQUIRE, 0UL, batch_idx, req_cnt );
337 :
338 0 : while( FD_VOLATILE_CONST( comp->seq )!=1UL ) FD_SPIN_PAUSE();
339 0 : FD_COMPILER_MFENCE();
340 0 : int comp_err = FD_VOLATILE_CONST( comp->err );
341 0 : if( FD_UNLIKELY( comp_err!=FD_VINYL_SUCCESS ) ) {
342 0 : FD_LOG_CRIT(( "vinyl tile rejected my ACQUIRE request: %i-%s", comp_err, fd_vinyl_strerror( comp_err ) ));
343 0 : }
344 :
345 : /* For the accounts that were newly found in vinyl, create accdb
346 : handles */
347 :
348 0 : req_cnt = 0UL;
349 0 : for( ulong i=0UL; i<cnt; i++ ) {
350 0 : if( ro0[i].ref->accdb_type!=FD_ACCDB_TYPE_NONE ) continue;
351 0 : void const * addr_i = (void const *)( (ulong)addr0 + i*32UL );
352 :
353 0 : int req_err = FD_VOLATILE_CONST( req_err0 [ req_cnt ] );
354 0 : ulong val_gaddr = FD_VOLATILE_CONST( req_val_gaddr0[ req_cnt ] );
355 :
356 0 : fd_accdb_ro_t * ro = &ro0[ i ];
357 0 : if( req_err==0 ) {
358 : /* Record found in vinyl, create reference */
359 0 : fd_account_meta_t const * meta = fd_wksp_laddr_fast( data_wksp, val_gaddr );
360 :
361 0 : accdb->base.ro_active++;
362 0 : *ro = (fd_accdb_ro_t) {0};
363 0 : memcpy( ro->ref->address, addr_i, 32UL );
364 0 : ro->ref->accdb_type = FD_ACCDB_TYPE_V2;
365 0 : ro->ref->ref_type = FD_ACCDB_REF_RO;
366 0 : ro->meta = meta;
367 0 : } else if( FD_UNLIKELY( req_err!=FD_VINYL_ERR_KEY ) ) {
368 0 : FD_LOG_CRIT(( "vinyl tile ACQUIRE request failed: %i-%s", req_err, fd_vinyl_strerror( req_err ) ));
369 0 : }
370 0 : req_cnt++;
371 0 : }
372 :
373 0 : fd_vinyl_req_pool_release( req_pool, batch_idx );
374 :
375 : /* At this point, ownership of vinyl records transitions to caller.
376 : (Released using close_ro_multi) */
377 0 : }
378 :
379 : /* Write method *******************************************************/
380 :
381 : void
382 : fd_accdb_user_v2_open_rw_multi( fd_accdb_user_t * accdb,
383 : fd_accdb_rw_t * rw0,
384 : fd_funk_txn_xid_t const * xid,
385 : void const * addr0,
386 : ulong const * data_max0,
387 : int flags,
388 0 : ulong cnt ) {
389 0 : fd_accdb_user_v2_t * v2 = (fd_accdb_user_v2_t *)accdb;
390 0 : fd_funk_t * funk = v2->funk;
391 0 : fd_vinyl_rq_t * rq = v2->vinyl_rq; /* "request queue "*/
392 0 : fd_vinyl_req_pool_t * req_pool = v2->vinyl_req_pool; /* "request pool" */
393 0 : fd_wksp_t * req_wksp = v2->vinyl_req_wksp; /* shm workspace containing request buffer */
394 0 : fd_wksp_t * data_wksp = v2->vinyl_data_wksp; /* shm workspace containing vinyl data cache */
395 0 : ulong link_id = v2->vinyl_link_id; /* vinyl client ID */
396 :
397 0 : fd_accdb_lineage_set_fork( v2->lineage, v2->funk, xid );
398 0 : fd_funk_txn_t * txn = fd_accdb_lineage_write_check( v2->lineage, v2->funk );
399 :
400 0 : int const flag_truncate = !!( flags & FD_ACCDB_FLAG_TRUNCATE );
401 0 : int const flag_create = !!( flags & FD_ACCDB_FLAG_CREATE );
402 0 : if( FD_UNLIKELY( flags & ~(FD_ACCDB_FLAG_CREATE|FD_ACCDB_FLAG_TRUNCATE) ) ) {
403 0 : FD_LOG_CRIT(( "invalid flags for open_rw: %#02x", (uint)flags ));
404 0 : }
405 :
406 0 : if( FD_UNLIKELY( cnt>fd_vinyl_req_batch_key_max( req_pool ) ) ) {
407 0 : FD_LOG_CRIT(( "open_rw_multi cnt %lu exceeds vinyl request batch max %lu",
408 0 : cnt, fd_vinyl_req_batch_key_max( req_pool ) ));
409 0 : }
410 :
411 : /* Query for existing funk records
412 :
413 : (FIXME this is a potentially slow operation, might want to fire off
414 : a 'prefetch' instruction to vinyl asynchronously before doing this,
415 : so that the vinyl data is in cache by the time v1_open_rw_multi
416 : finishes) */
417 :
418 0 : ulong addr_laddr = (ulong)addr0;
419 0 : for( ulong i=0UL; i<cnt; i++ ) {
420 0 : void const * addr_i = (void const *)( (ulong)addr_laddr + i*32UL );
421 0 : funk_open_ref( v2, rw0[ i ].ref, xid, addr_i, 1 );
422 0 : }
423 :
424 : /* For the accounts that were not found in funk, create writable funk
425 : records from elements in vinyl. */
426 :
427 0 : ulong batch_idx = fd_vinyl_req_pool_acquire( req_pool );
428 : /* req_pool_release called before returning */
429 0 : fd_vinyl_comp_t * comp = fd_vinyl_req_batch_comp ( req_pool, batch_idx );
430 0 : fd_vinyl_key_t * req_key0 = fd_vinyl_req_batch_key ( req_pool, batch_idx );
431 0 : schar * req_err0 = fd_vinyl_req_batch_err ( req_pool, batch_idx );
432 0 : ulong * req_val_gaddr0 = fd_vinyl_req_batch_val_gaddr( req_pool, batch_idx );
433 :
434 : /* Create a read-only vinyl "ACQUIRE" batch */
435 :
436 0 : ulong req_cnt = 0UL;
437 0 : for( ulong i=0UL; i<cnt; i++ ) {
438 0 : if( rw0[i].ref->ref_type!=FD_ACCDB_REF_INVAL ) continue;
439 : /* At this point, addr0[i] not found in funk, load from vinyl */
440 0 : void const * addr_i = (void const *)( (ulong)addr0 + i*32UL );
441 :
442 0 : fd_vinyl_key_init( req_key0+req_cnt, addr_i, 32UL );
443 0 : req_err0 [ req_cnt ] = 0;
444 0 : req_val_gaddr0[ req_cnt ] = 0UL;
445 0 : req_cnt++;
446 0 : }
447 :
448 : /* Send read-only "ACQUIRE" batch to vinyl and wait for response */
449 :
450 0 : if( req_cnt ) {
451 0 : ulong req_id = v2->vinyl_req_id++;
452 0 : memset( fd_vinyl_req_batch_comp( req_pool, batch_idx ), 0, sizeof(fd_vinyl_comp_t) );
453 0 : fd_vinyl_req_send_batch( rq, req_pool, req_wksp, req_id, link_id, FD_VINYL_REQ_TYPE_ACQUIRE, 0UL, batch_idx, req_cnt );
454 :
455 0 : while( FD_VOLATILE_CONST( comp->seq )!=1UL ) FD_SPIN_PAUSE();
456 0 : FD_COMPILER_MFENCE();
457 0 : int comp_err = FD_VOLATILE_CONST( comp->err );
458 0 : if( FD_UNLIKELY( comp_err!=FD_VINYL_SUCCESS ) ) {
459 0 : FD_LOG_CRIT(( "vinyl tile rejected my ACQUIRE request: %i-%s", comp_err, fd_vinyl_strerror( comp_err ) ));
460 0 : }
461 0 : }
462 :
463 : /* Promote any found accounts to writable accounts */
464 :
465 0 : req_cnt = 0UL;
466 0 : for( ulong i=0UL; i<cnt; i++ ) {
467 0 : void const * addr_i = (void const *)( (ulong)addr0 + i*32UL );
468 0 : fd_accdb_rw_t * rw = &rw0[ i ];
469 0 : fd_funk_rec_t * rec = (fd_funk_rec_t *)rw->ref->user_data;
470 0 : ulong data_max = data_max0[ i ];
471 :
472 0 : if( rw->ref->ref_type==FD_ACCDB_REF_RW ) {
473 : /* Mutable record found, modify in-place */
474 :
475 0 : if( FD_UNLIKELY( !flag_create && fd_accdb_ref_lamports( rw->ro )==0UL ) ) {
476 : /* Tombstone */
477 0 : fd_funk_rec_write_unlock( v2->funk, rec );
478 0 : goto not_found;
479 0 : }
480 :
481 0 : ulong acc_orig_sz = fd_accdb_ref_data_sz( rw->ro );
482 0 : ulong val_sz_min = sizeof(fd_account_meta_t)+fd_ulong_max( data_max, acc_orig_sz );
483 0 : void * val = fd_funk_val_truncate( rec, funk->alloc, funk->wksp, 16UL, val_sz_min, NULL );
484 0 : if( FD_UNLIKELY( !val ) ) {
485 0 : FD_LOG_CRIT(( "Failed to modify account: out of memory allocating %lu bytes", acc_orig_sz ));
486 0 : }
487 0 : fd_accdb_funk_prep_inplace( rw, funk, rec );
488 0 : if( flag_truncate ) {
489 0 : rec->val_sz = sizeof(fd_account_meta_t);
490 0 : rw->meta->dlen = 0;
491 0 : }
492 0 : accdb->base.rw_active++;
493 : /* Retain write lock */
494 :
495 0 : continue; /* next account */
496 0 : }
497 :
498 0 : if( rw->ref->ref_type==FD_ACCDB_REF_RO ) {
499 : /* Frozen record found, copy out to new object */
500 :
501 0 : fd_accdb_ro_t * ro = rw->ro;
502 0 : if( FD_UNLIKELY( !flag_create && fd_accdb_ref_lamports( ro )==0UL ) ) {
503 : /* Tombstone */
504 0 : fd_funk_rec_read_unlock( v2->funk, rec );
505 0 : goto not_found;
506 0 : }
507 :
508 0 : ulong acc_orig_sz = fd_accdb_ref_data_sz( ro );
509 0 : ulong val_sz_min = sizeof(fd_account_meta_t)+fd_ulong_max( data_max, acc_orig_sz );
510 0 : ulong val_sz = flag_truncate ? sizeof(fd_account_meta_t) : rec->val_sz;
511 0 : ulong val_max = 0UL;
512 0 : void * val = fd_alloc_malloc_at_least( funk->alloc, 16UL, val_sz_min, &val_max );
513 0 : if( FD_UNLIKELY( !val ) ) {
514 0 : FD_LOG_CRIT(( "Failed to modify account: out of memory allocating %lu bytes", acc_orig_sz ));
515 0 : }
516 :
517 0 : fd_account_meta_t * meta = val;
518 0 : uchar * data = (uchar *)( meta+1 );
519 0 : ulong data_max_actual = val_max - sizeof(fd_account_meta_t);
520 0 : if( flag_truncate ) fd_accdb_funk_copy_truncated( meta, ro->meta );
521 0 : else fd_accdb_funk_copy_account ( meta, data, ro->meta, fd_account_data( ro->meta ) );
522 0 : if( acc_orig_sz<data_max_actual ) {
523 : /* Zero out trailing data */
524 0 : uchar * tail = data +acc_orig_sz;
525 0 : ulong tail_sz = data_max_actual-acc_orig_sz;
526 0 : fd_memset( tail, 0, tail_sz );
527 0 : }
528 :
529 0 : fd_accdb_funk_prep_create( rw, funk, txn, addr_i, val, val_sz, val_max );
530 0 : accdb->base.rw_active++;
531 0 : accdb->base.created_cnt++;
532 :
533 0 : FD_COMPILER_MFENCE();
534 0 : fd_funk_rec_read_unlock( v2->funk, rec );
535 :
536 0 : continue; /* next account */
537 0 : }
538 :
539 : /* Record not found in funk */
540 :
541 0 : int req_err = req_err0[ req_cnt ];
542 0 : if( req_err ) {
543 : /* Record not found in vinyl either (truly does not exist)
544 : If CREATE flag was requested, create it in funk */
545 0 : if( FD_UNLIKELY( req_err!=FD_VINYL_ERR_KEY ) ) {
546 0 : FD_LOG_CRIT(( "vinyl tile ACQUIRE request failed: %i-%s", req_err, fd_vinyl_strerror( req_err ) ));
547 0 : }
548 0 : not_found:
549 0 : if( flag_create ) {
550 0 : fd_accdb_funk_create( v2->funk, rw, txn, addr_i, data_max0[ i ] );
551 0 : fd_funk_rec_write_lock_uncontended( v2->funk, (fd_funk_rec_t *)rw->ref->user_data );
552 0 : accdb->base.rw_active++;
553 0 : } else {
554 0 : memset( rw, 0, sizeof(fd_accdb_ref_t) );
555 0 : }
556 0 : req_cnt++;
557 0 : continue;
558 0 : }
559 :
560 : /* Record found in vinyl */
561 :
562 0 : ulong req_val_gaddr = req_val_gaddr0[ req_cnt ];
563 0 : fd_account_meta_t * src_meta = fd_wksp_laddr_fast( data_wksp, req_val_gaddr );
564 0 : uchar const * src_data = (uchar *)( src_meta+1 );
565 :
566 0 : if( FD_UNLIKELY( src_meta->lamports==0UL ) ) goto not_found; /* tombstone */
567 :
568 0 : ulong acc_orig_sz = src_meta->dlen;
569 0 : ulong val_sz_min = sizeof(fd_account_meta_t)+fd_ulong_max( data_max0[ i ], acc_orig_sz );
570 0 : ulong acc_sz = flag_truncate ? 0UL : acc_orig_sz;
571 0 : ulong val_sz = sizeof(fd_account_meta_t)+acc_sz;
572 0 : ulong val_max = 0UL;
573 0 : void * val = fd_alloc_malloc_at_least( v2->funk->alloc, 16UL, val_sz_min, &val_max );
574 0 : if( FD_UNLIKELY( !val ) ) {
575 0 : FD_LOG_CRIT(( "Failed to modify account: out of memory allocating %lu bytes", acc_orig_sz ));
576 0 : }
577 :
578 0 : fd_account_meta_t * dst_meta = val;
579 0 : uchar * dst_data = (uchar *)( dst_meta+1 );
580 0 : ulong data_max_actual = val_max - sizeof(fd_account_meta_t);
581 0 : if( flag_truncate ) fd_accdb_funk_copy_truncated( dst_meta, src_meta );
582 0 : else fd_accdb_funk_copy_account ( dst_meta, dst_data, src_meta, src_data );
583 0 : if( acc_orig_sz<data_max_actual ) {
584 : /* Zero out trailing data */
585 0 : uchar * tail = dst_data +acc_orig_sz;
586 0 : ulong tail_sz = data_max_actual-acc_orig_sz;
587 0 : fd_memset( tail, 0, tail_sz );
588 0 : }
589 :
590 0 : fd_accdb_funk_prep_create( rw, v2->funk, txn, addr_i, val, val_sz, val_max );
591 0 : fd_funk_rec_write_lock_uncontended( v2->funk, (fd_funk_rec_t *)rw->ref->user_data );
592 :
593 0 : req_cnt++;
594 0 : accdb->base.rw_active++;
595 0 : accdb->base.created_cnt++;
596 0 : }
597 :
598 : /* Send "RELEASE" batch (reuse val_gaddr values),
599 : and wait for response */
600 :
601 0 : if( req_cnt ) {
602 0 : ulong req_id = v2->vinyl_req_id++;
603 0 : memset( fd_vinyl_req_batch_comp( req_pool, batch_idx ), 0, sizeof(fd_vinyl_comp_t) );
604 0 : fd_vinyl_req_send_batch( rq, req_pool, req_wksp, req_id, link_id, FD_VINYL_REQ_TYPE_RELEASE, 0UL, batch_idx, req_cnt );
605 :
606 0 : while( FD_VOLATILE_CONST( comp->seq )!=1UL ) FD_SPIN_PAUSE();
607 0 : FD_COMPILER_MFENCE();
608 0 : int comp_err = FD_VOLATILE_CONST( comp->err );
609 0 : if( FD_UNLIKELY( comp_err!=FD_VINYL_SUCCESS ) ) {
610 0 : FD_LOG_CRIT(( "vinyl tile rejected my RELEASE request: %i-%s", comp_err, fd_vinyl_strerror( comp_err ) ));
611 0 : }
612 0 : }
613 :
614 0 : fd_vinyl_req_pool_release( req_pool, batch_idx );
615 0 : }
616 :
617 : void
618 : funk_close_rw( fd_accdb_user_v2_t * accdb,
619 0 : fd_accdb_rw_t * write ) {
620 0 : fd_funk_rec_t * rec = (fd_funk_rec_t *)write->ref->user_data;
621 :
622 0 : if( FD_UNLIKELY( !accdb->base.rw_active ) ) {
623 0 : FD_LOG_CRIT(( "Failed to modify account: ref count underflow" ));
624 0 : }
625 :
626 0 : if( write->ref->user_data2 ) {
627 0 : fd_funk_txn_t * txn = (fd_funk_txn_t *)write->ref->user_data2;
628 0 : fd_funk_rec_prepare_t prepare = {
629 0 : .rec = rec,
630 0 : .rec_head_idx = &txn->rec_head_idx,
631 0 : .rec_tail_idx = &txn->rec_tail_idx
632 0 : };
633 0 : fd_funk_rec_publish( accdb->funk, &prepare );
634 0 : }
635 :
636 0 : fd_funk_rec_write_unlock( accdb->funk, rec );
637 0 : accdb->base.rw_active--;
638 0 : }
639 :
640 : void
641 : fd_accdb_user_v2_close_ref_multi( fd_accdb_user_t * accdb,
642 : fd_accdb_ref_t * ref0,
643 0 : ulong cnt ) {
644 0 : fd_accdb_user_v2_t * v2 = (fd_accdb_user_v2_t *)accdb;
645 0 : fd_vinyl_rq_t * rq = v2->vinyl_rq; /* "request queue "*/
646 0 : fd_vinyl_req_pool_t * req_pool = v2->vinyl_req_pool; /* "request pool" */
647 0 : fd_wksp_t * req_wksp = v2->vinyl_req_wksp; /* shm workspace containing request buffer */
648 0 : fd_wksp_t * data_wksp = v2->vinyl_data_wksp; /* shm workspace containing vinyl data cache */
649 0 : ulong link_id = v2->vinyl_link_id; /* vinyl client ID */
650 :
651 0 : if( FD_UNLIKELY( cnt>fd_vinyl_req_batch_key_max( req_pool ) ) ) {
652 0 : FD_LOG_CRIT(( "close_ref_multi cnt %lu exceeds vinyl request batch max %lu",
653 0 : cnt, fd_vinyl_req_batch_key_max( req_pool ) ));
654 0 : }
655 :
656 : /* First, release all references to vinyl records
657 : (This is a prefetch friendly / fast loop) */
658 :
659 0 : ulong batch_idx = fd_vinyl_req_pool_acquire( req_pool );
660 : /* req_pool_release called before returning */
661 0 : fd_vinyl_comp_t * comp = fd_vinyl_req_batch_comp ( req_pool, batch_idx );
662 0 : schar * req_err0 = fd_vinyl_req_batch_err ( req_pool, batch_idx );
663 0 : ulong * req_val_gaddr0 = fd_vinyl_req_batch_val_gaddr( req_pool, batch_idx );
664 :
665 0 : ulong ro_close_cnt = 0UL;
666 0 : ulong rw_close_cnt = 0UL;
667 0 : ulong req_cnt = 0UL;
668 0 : for( ulong i=0UL; i<cnt; i++ ) {
669 0 : fd_accdb_ref_t * ref = &ref0[ i ];
670 0 : if( ref->accdb_type!=FD_ACCDB_TYPE_V2 ) continue;
671 0 : ref->ref_type==FD_ACCDB_REF_RO ? ro_close_cnt++ : rw_close_cnt++;
672 0 : req_err0 [ req_cnt ] = 0;
673 0 : req_val_gaddr0[ req_cnt ] = fd_wksp_gaddr_fast( data_wksp, (void *)ref->meta_laddr );
674 0 : memset( ref, 0, sizeof(fd_accdb_ref_t) );
675 0 : req_cnt++;
676 0 : }
677 0 : if( req_cnt ) {
678 0 : if( FD_UNLIKELY( ro_close_cnt > accdb->base.ro_active ) ) {
679 0 : FD_LOG_CRIT(( "attempted to close more accdb_ro (%lu) than are open (%lu)",
680 0 : ro_close_cnt, accdb->base.ro_active ));
681 0 : }
682 0 : if( FD_UNLIKELY( rw_close_cnt > accdb->base.rw_active ) ) {
683 0 : FD_LOG_CRIT(( "attempted to close more accdb_rw (%lu) than are open (%lu)",
684 0 : rw_close_cnt, accdb->base.rw_active ));
685 0 : }
686 0 : ulong req_id = v2->vinyl_req_id++;
687 0 : memset( fd_vinyl_req_batch_comp( req_pool, batch_idx ), 0, sizeof(fd_vinyl_comp_t) );
688 0 : fd_vinyl_req_send_batch( rq, req_pool, req_wksp, req_id, link_id, FD_VINYL_REQ_TYPE_RELEASE, 0UL, batch_idx, req_cnt );
689 0 : }
690 :
691 : /* While our vinyl request is inflight, release funk records
692 : (This does expensive DRAM accesses, which are convenient to do when
693 : we are waiting for the database to asynchronously respond) */
694 :
695 0 : for( ulong i=0UL; i<cnt; i++ ) {
696 0 : fd_accdb_ref_t * ref = &ref0[ i ];
697 0 : if( ref->accdb_type!=FD_ACCDB_TYPE_V1 ) continue;
698 0 : switch( ref0[ i ].ref_type ) {
699 0 : case FD_ACCDB_REF_RO:
700 0 : accdb->base.ro_active--;
701 0 : fd_funk_rec_read_unlock( v2->funk, (fd_funk_rec_t *)ref->user_data );
702 0 : break;
703 0 : case FD_ACCDB_REF_RW:
704 0 : funk_close_rw( v2, (fd_accdb_rw_t *)ref );
705 0 : break;
706 0 : default:
707 0 : FD_LOG_CRIT(( "invalid ref_type %u in fd_accdb_user_v1_close_ref", (uint)ref->ref_type ));
708 0 : }
709 0 : memset( ref, 0, sizeof(fd_accdb_ref_t) );
710 0 : }
711 :
712 : /* Wait for response from vinyl */
713 :
714 0 : if( req_cnt ) {
715 0 : while( FD_VOLATILE_CONST( comp->seq )!=1UL ) FD_SPIN_PAUSE();
716 0 : FD_COMPILER_MFENCE();
717 0 : int comp_err = FD_VOLATILE_CONST( comp->err );
718 0 : if( FD_UNLIKELY( comp_err!=FD_VINYL_SUCCESS ) ) {
719 0 : FD_LOG_CRIT(( "vinyl tile rejected my RELEASE request: %i-%s", comp_err, fd_vinyl_strerror( comp_err ) ));
720 0 : }
721 0 : for( ulong i=0UL; i<req_cnt; i++ ) {
722 0 : int req_err = req_err0[ i ];
723 0 : if( FD_UNLIKELY( req_err!=FD_VINYL_SUCCESS ) ) {
724 0 : FD_LOG_CRIT(( "vinyl tile RELEASE request failed: %i-%s", req_err, fd_vinyl_strerror( req_err ) ));
725 0 : }
726 0 : }
727 :
728 0 : accdb->base.ro_active -= ro_close_cnt;
729 0 : accdb->base.rw_active -= rw_close_cnt;
730 0 : }
731 :
732 0 : fd_vinyl_req_pool_release( req_pool, batch_idx );
733 0 : }
734 :
735 : ulong
736 : fd_accdb_user_v2_rw_data_max( fd_accdb_user_t * accdb,
737 0 : fd_accdb_rw_t const * rw ) {
738 0 : (void)accdb;
739 0 : if( rw->ref->accdb_type==FD_ACCDB_TYPE_NONE ) {
740 0 : return rw->ref->user_data; /* data_max */
741 0 : }
742 0 : fd_funk_rec_t * rec = (fd_funk_rec_t *)rw->ref->user_data;
743 0 : return (ulong)( rec->val_max - sizeof(fd_account_meta_t) );
744 0 : }
745 :
746 : void
747 : fd_accdb_user_v2_rw_data_sz_set( fd_accdb_user_t * accdb,
748 : fd_accdb_rw_t * rw,
749 : ulong data_sz,
750 0 : int flags ) {
751 0 : int flag_dontzero = !!( flags & FD_ACCDB_FLAG_DONTZERO );
752 0 : if( FD_UNLIKELY( flags & ~(FD_ACCDB_FLAG_DONTZERO) ) ) {
753 0 : FD_LOG_CRIT(( "invalid flags for rw_data_sz_set: %#02x", (uint)flags ));
754 0 : }
755 :
756 0 : ulong prev_sz = rw->meta->dlen;
757 0 : if( data_sz>prev_sz ) {
758 0 : ulong data_max = fd_accdb_user_v2_rw_data_max( accdb, rw );
759 0 : if( FD_UNLIKELY( data_sz>data_max ) ) {
760 0 : FD_LOG_CRIT(( "attempted to write %lu bytes into a rec with only %lu bytes of data space",
761 0 : data_sz, data_max ));
762 0 : }
763 0 : if( !flag_dontzero ) {
764 0 : void * tail = (uchar *)fd_accdb_ref_data( rw ) + prev_sz;
765 0 : fd_memset( tail, 0, data_sz-prev_sz );
766 0 : }
767 0 : }
768 0 : rw->meta->dlen = (uint)data_sz;
769 :
770 0 : if( rw->ref->accdb_type==FD_ACCDB_TYPE_V1 ) {
771 0 : fd_funk_rec_t * rec = (fd_funk_rec_t *)rw->ref->user_data;
772 0 : rec->val_sz = (uint)( sizeof(fd_account_meta_t)+data_sz ) & FD_FUNK_REC_VAL_MAX;
773 0 : }
774 0 : }
775 :
776 : fd_accdb_user_t *
777 : fd_accdb_user_v2_init( fd_accdb_user_t * accdb_,
778 : void * shfunk,
779 : void * shlocks,
780 : void * vinyl_rq,
781 : void * vinyl_data,
782 : void * vinyl_req_pool,
783 : ulong vinyl_link_id,
784 0 : ulong max_depth ) {
785 0 : if( FD_UNLIKELY( !accdb_ ) ) {
786 0 : FD_LOG_WARNING(( "NULL ljoin" ));
787 0 : return NULL;
788 0 : }
789 0 : if( FD_UNLIKELY( !shfunk ) ) {
790 0 : FD_LOG_WARNING(( "NULL shfunk" ));
791 0 : return NULL;
792 0 : }
793 0 : if( FD_UNLIKELY( !vinyl_data ) ) {
794 0 : FD_LOG_WARNING(( "NULL vinyl_data" ));
795 0 : return NULL;
796 0 : }
797 :
798 0 : fd_accdb_user_v2_t * accdb = fd_type_pun( accdb_ );
799 0 : memset( accdb, 0, sizeof(fd_accdb_user_v2_t) );
800 :
801 0 : if( FD_UNLIKELY( !fd_funk_join( accdb->funk, shfunk, shlocks ) ) ) {
802 0 : FD_LOG_CRIT(( "fd_funk_join failed" ));
803 0 : }
804 :
805 0 : fd_vinyl_rq_t * rq = fd_vinyl_rq_join( vinyl_rq );
806 0 : fd_vinyl_req_pool_t * req_pool = fd_vinyl_req_pool_join( vinyl_req_pool );
807 0 : if( FD_UNLIKELY( !rq || !req_pool ) ) {
808 : /* component joins log warning if this is reached */
809 0 : FD_LOG_WARNING(( "Failed to initialize database client" ));
810 0 : return NULL;
811 0 : }
812 :
813 0 : accdb->lineage->max_depth = max_depth;
814 0 : accdb->vinyl_req_id = 0UL;
815 0 : accdb->vinyl_rq = rq;
816 0 : accdb->vinyl_link_id = vinyl_link_id;
817 0 : accdb->vinyl_data_wksp = vinyl_data;
818 0 : accdb->vinyl_req_wksp = fd_wksp_containing( req_pool );
819 0 : accdb->vinyl_req_pool = req_pool;
820 0 : accdb->base.accdb_type = FD_ACCDB_TYPE_V2;
821 0 : accdb->base.vt = &fd_accdb_user_v2_vt;
822 0 : return accdb_;
823 0 : }
824 :
825 : void
826 0 : fd_accdb_user_v2_fini( fd_accdb_user_t * accdb ) {
827 0 : fd_accdb_user_v2_t * user = (fd_accdb_user_v2_t *)accdb;
828 :
829 0 : fd_vinyl_rq_leave( user->vinyl_rq );
830 :
831 0 : if( FD_UNLIKELY( !fd_funk_leave( user->funk, NULL, NULL ) ) ) FD_LOG_CRIT(( "fd_funk_leave failed" ));
832 0 : }
833 :
834 : ulong
835 0 : fd_accdb_user_v2_batch_max( fd_accdb_user_t * accdb ) {
836 0 : fd_accdb_user_v2_t * user = (fd_accdb_user_v2_t *)accdb;
837 0 : return fd_vinyl_req_batch_key_max( user->vinyl_req_pool );
838 0 : }
839 :
840 :
841 : fd_accdb_user_vt_t const fd_accdb_user_v2_vt = {
842 : .fini = fd_accdb_user_v2_fini,
843 : .batch_max = fd_accdb_user_v2_batch_max,
844 : .open_ro_multi = fd_accdb_user_v2_open_ro_multi,
845 : .open_rw_multi = fd_accdb_user_v2_open_rw_multi,
846 : .close_ref_multi = fd_accdb_user_v2_close_ref_multi,
847 : .rw_data_max = fd_accdb_user_v2_rw_data_max,
848 : .rw_data_sz_set = fd_accdb_user_v2_rw_data_sz_set
849 : };
|