Line data Source code
1 : #include "fd_progcache.h"
2 : #include "fd_progcache_admin.h"
3 : #include "fd_progcache_rec.h"
4 : #include "fd_prog_load.h"
5 : #include "../runtime/program/fd_bpf_loader_program.h"
6 : #include "../runtime/program/fd_loader_v4_program.h"
7 : #include "../runtime/fd_system_ids.h"
8 : #include "../../util/wksp/fd_wksp_private.h"
9 :
10 : FD_TL fd_progcache_admin_metrics_t fd_progcache_admin_metrics_g;
11 :
12 : /* Algorithm to estimate size of cache metadata structures (rec_pool
13 : object pool and rec_map hashchain table).
14 :
15 : FIXME Carefully balance this */
16 :
17 : static ulong
18 : fd_progcache_est_rec_max1( ulong wksp_footprint,
19 0 : ulong mean_cache_entry_size ) {
20 0 : return wksp_footprint / mean_cache_entry_size;
21 0 : }
22 :
23 : ulong
24 : fd_progcache_est_rec_max( ulong wksp_footprint,
25 0 : ulong mean_cache_entry_size ) {
26 0 : ulong est = fd_progcache_est_rec_max1( wksp_footprint, mean_cache_entry_size );
27 0 : if( FD_UNLIKELY( est>(1UL<<31) ) ) FD_LOG_ERR(( "fd_progcache_est_rec_max(wksp_footprint=%lu,mean_cache_entry_size=%lu) failed: invalid parameters", wksp_footprint, mean_cache_entry_size ));
28 0 : return fd_ulong_max( est, 2048UL );
29 0 : }
30 :
31 : /* Begin transaction-level operations. It is assumed that txn data
32 : structures are not concurrently modified. This includes txn_pool and
33 : txn_map. */
34 :
35 : void
36 : fd_progcache_txn_attach_child( fd_progcache_join_t * cache,
37 : fd_progcache_xid_t const * xid_parent,
38 55100 : fd_progcache_xid_t const * xid_new ) {
39 55100 : fd_rwlock_write( &cache->shmem->txn.rwlock );
40 :
41 55100 : if( FD_UNLIKELY( fd_prog_txnm_idx_query_const( cache->txn.map, xid_new, ULONG_MAX, cache->txn.pool )!=ULONG_MAX ) ) {
42 0 : FD_LOG_ERR(( "fd_progcache_txn_attach_child failed: xid %lu:%lu already in use",
43 0 : xid_new->ul[0], xid_new->ul[1] ));
44 0 : }
45 55100 : if( FD_UNLIKELY( fd_prog_txnp_free( cache->txn.pool )==0UL ) ) {
46 0 : FD_LOG_ERR(( "fd_progcache_txn_attach_child failed: transaction object pool out of memory" ));
47 0 : }
48 :
49 55100 : ulong parent_idx;
50 55100 : uint * _child_head_idx;
51 55100 : uint * _child_tail_idx;
52 :
53 55100 : if( FD_UNLIKELY( fd_funk_txn_xid_eq( xid_parent, cache->shmem->txn.last_publish ) ) ) {
54 :
55 30172 : parent_idx = FD_FUNK_TXN_IDX_NULL;
56 :
57 30172 : _child_head_idx = &cache->shmem->txn.child_head_idx;
58 30172 : _child_tail_idx = &cache->shmem->txn.child_tail_idx;
59 :
60 30172 : } else {
61 :
62 24928 : parent_idx = fd_prog_txnm_idx_query( cache->txn.map, xid_parent, ULONG_MAX, cache->txn.pool );
63 24928 : if( FD_UNLIKELY( parent_idx==ULONG_MAX ) ) {
64 0 : FD_LOG_CRIT(( "fd_funk_txn_prepare failed: user provided invalid parent XID %lu:%lu",
65 0 : xid_parent->ul[0], xid_parent->ul[1] ));
66 0 : }
67 :
68 24928 : _child_head_idx = &cache->txn.pool[ parent_idx ].child_head_idx;
69 24928 : _child_tail_idx = &cache->txn.pool[ parent_idx ].child_tail_idx;
70 :
71 24928 : }
72 :
73 55100 : uint txn_idx = (uint)fd_prog_txnp_idx_acquire( cache->txn.pool );
74 55100 : if( FD_UNLIKELY( txn_idx==UINT_MAX ) ) FD_LOG_ERR(( "fd_funk_txn_prepare failed: transaction object pool out of memory" ));
75 55100 : fd_progcache_txn_t * txn = &cache->txn.pool[ txn_idx ];
76 55100 : fd_funk_txn_xid_copy( &txn->xid, xid_new );
77 :
78 55100 : uint sibling_prev_idx = *_child_tail_idx;
79 :
80 55100 : int first_born = sibling_prev_idx==UINT_MAX;
81 :
82 55100 : txn->parent_idx = (uint)parent_idx;
83 55100 : txn->child_head_idx = UINT_MAX;
84 55100 : txn->child_tail_idx = UINT_MAX;
85 55100 : txn->sibling_prev_idx = (uint)sibling_prev_idx;
86 55100 : txn->sibling_next_idx = UINT_MAX;
87 :
88 55100 : txn->rec_head_idx = UINT_MAX;
89 55100 : txn->rec_tail_idx = UINT_MAX;
90 :
91 : /* TODO: consider branchless impl */
92 55100 : if( FD_LIKELY( first_born ) ) *_child_head_idx = (uint)txn_idx; /* opt for non-compete */
93 3 : else cache->txn.pool[ sibling_prev_idx ].sibling_next_idx = (uint)txn_idx;
94 :
95 55100 : *_child_tail_idx = (uint)txn_idx;
96 :
97 55100 : fd_prog_txnm_idx_insert( cache->txn.map, txn_idx, cache->txn.pool );
98 :
99 55100 : fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
100 :
101 55100 : FD_LOG_INFO(( "progcache xid %lu:%lu: created with parent %lu:%lu",
102 55100 : xid_new ->ul[0], xid_new ->ul[1],
103 55100 : xid_parent->ul[0], xid_parent->ul[1] ));
104 55100 : }
105 :
106 : static void
107 : fd_progcache_rec_list_append( fd_progcache_rec_t * rec_pool,
108 : uint * list_head,
109 : uint * list_tail,
110 : uint rec_head,
111 0 : uint rec_tail ) {
112 0 : if( rec_head==UINT_MAX ) return;
113 0 : if( *list_tail!=UINT_MAX ) {
114 0 : rec_pool[ *list_tail ].next_idx = rec_head;
115 0 : } else {
116 0 : *list_head = rec_head;
117 0 : }
118 0 : *list_tail = rec_tail;
119 0 : }
120 :
121 : static void
122 : fd_progcache_txn_cancel_one( fd_progcache_join_t * cache,
123 : fd_progcache_txn_t * txn,
124 : uint * cancel_head,
125 0 : uint * cancel_tail ) {
126 0 : FD_LOG_INFO(( "progcache txn laddr=%p xid %lu:%lu: cancel", (void *)txn, txn->xid.ul[0], txn->xid.ul[1] ));
127 0 : fd_rwlock_write( &txn->lock );
128 :
129 0 : if( FD_UNLIKELY( txn->child_head_idx!=UINT_MAX ||
130 0 : txn->child_tail_idx!=UINT_MAX ) ) {
131 0 : FD_LOG_CRIT(( "fd_progcache_txn_cancel failed: txn at %p with xid %lu:%lu has children (data corruption?)",
132 0 : (void *)txn, txn->xid.ul[0], txn->xid.ul[1] ));
133 0 : }
134 :
135 : /* Remove records */
136 :
137 0 : for( uint idx = txn->rec_head_idx; idx!=UINT_MAX; idx = cache->rec.pool->ele[ idx ].next_idx ) {
138 0 : fd_progcache_rec_t * rec = &cache->rec.pool->ele[ idx ];
139 0 : if( rec->exists ) {
140 0 : fd_prog_recm_query_t query[1];
141 0 : int remove_err = fd_prog_recm_remove( cache->rec.map, &rec->pair, NULL, query, FD_MAP_FLAG_BLOCKING );
142 0 : if( FD_UNLIKELY( remove_err ) ) FD_LOG_CRIT(( "fd_funk_rec_map_remove failed: %i-%s", remove_err, fd_map_strerror( remove_err ) ));
143 0 : }
144 0 : }
145 :
146 : /* Detach record list from txn and append to cancel list */
147 :
148 0 : fd_progcache_rec_list_append( cache->rec.pool->ele,
149 0 : cancel_head, cancel_tail,
150 0 : txn->rec_head_idx, txn->rec_tail_idx );
151 0 : txn->rec_head_idx = UINT_MAX;
152 0 : txn->rec_tail_idx = UINT_MAX;
153 :
154 : /* Remove transaction from fork graph */
155 :
156 0 : uint self_idx = (uint)( txn - cache->txn.pool );
157 0 : uint prev_idx = txn->sibling_prev_idx;
158 0 : uint next_idx = txn->sibling_next_idx;
159 0 : if( next_idx!=UINT_MAX ) {
160 0 : cache->txn.pool[ next_idx ].sibling_prev_idx = prev_idx;
161 0 : }
162 0 : if( prev_idx!=UINT_MAX ) {
163 0 : cache->txn.pool[ prev_idx ].sibling_next_idx = next_idx;
164 0 : }
165 0 : if( txn->parent_idx!=UINT_MAX ) {
166 0 : fd_progcache_txn_t * parent = &cache->txn.pool[ txn->parent_idx ];
167 0 : if( parent->child_head_idx==self_idx ) parent->child_head_idx = next_idx;
168 0 : if( parent->child_tail_idx==self_idx ) parent->child_tail_idx = prev_idx;
169 0 : } else {
170 0 : if( cache->shmem->txn.child_head_idx==self_idx ) cache->shmem->txn.child_head_idx = next_idx;
171 0 : if( cache->shmem->txn.child_tail_idx==self_idx ) cache->shmem->txn.child_tail_idx = prev_idx;
172 0 : }
173 :
174 : /* Remove transaction from index */
175 :
176 0 : if( FD_UNLIKELY( !fd_prog_txnm_ele_remove( cache->txn.map, &txn->xid, NULL, cache->txn.pool ) ) ) {
177 0 : FD_LOG_CRIT(( "fd_progcache_txn_cancel failed: fd_funk_txn_map_remove(%lu:%lu) failed",
178 0 : txn->xid.ul[0], txn->xid.ul[1] ));
179 0 : }
180 :
181 : /* Free transaction object */
182 :
183 0 : fd_rwlock_unwrite( &txn->lock );
184 0 : fd_prog_txnp_ele_release( cache->txn.pool, txn );
185 0 : }
186 :
187 : /* Cancels txn and all children */
188 :
189 : static void
190 : fd_progcache_txn_cancel_tree( fd_progcache_join_t * cache,
191 : fd_progcache_txn_t * txn,
192 : uint * cancel_head,
193 0 : uint * cancel_tail ) {
194 0 : for(;;) {
195 0 : uint child_idx = txn->child_head_idx;
196 0 : if( child_idx==UINT_MAX ) break;
197 0 : fd_progcache_txn_t * child = &cache->txn.pool[ child_idx ];
198 0 : fd_progcache_txn_cancel_tree( cache, child, cancel_head, cancel_tail );
199 0 : }
200 0 : fd_progcache_txn_cancel_one( cache, txn, cancel_head, cancel_tail );
201 0 : }
202 :
203 : /* Cancels all left/right siblings */
204 :
205 : static void
206 : fd_progcache_txn_cancel_prev_list( fd_progcache_join_t * cache,
207 : fd_progcache_txn_t * txn,
208 : uint * cancel_head,
209 0 : uint * cancel_tail ) {
210 0 : uint cur_idx = txn->sibling_prev_idx;
211 0 : while( cur_idx!=UINT_MAX ) {
212 0 : fd_progcache_txn_t * sibling = &cache->txn.pool[ cur_idx ];
213 0 : uint next = sibling->sibling_prev_idx;
214 0 : fd_progcache_txn_cancel_tree( cache, sibling, cancel_head, cancel_tail );
215 0 : cur_idx = next;
216 0 : }
217 0 : }
218 :
219 : static void
220 : fd_progcache_txn_cancel_next_list( fd_progcache_join_t * cache,
221 : fd_progcache_txn_t * txn,
222 : uint * cancel_head,
223 0 : uint * cancel_tail ) {
224 0 : uint cur_idx = txn->sibling_next_idx;
225 0 : while( cur_idx!=UINT_MAX ) {
226 0 : fd_progcache_txn_t * sibling = &cache->txn.pool[ cur_idx ];
227 0 : uint next = sibling->sibling_next_idx;
228 0 : fd_progcache_txn_cancel_tree( cache, sibling, cancel_head, cancel_tail );
229 0 : cur_idx = next;
230 0 : }
231 0 : }
232 :
233 : /* Drain readers and free cancelled records */
234 :
235 : static void
236 : fd_progcache_txn_cancel_release( fd_progcache_join_t * cache,
237 0 : uint head ) {
238 0 : while( head!=UINT_MAX ) {
239 0 : fd_progcache_rec_t * rec = &cache->rec.pool->ele[ head ];
240 0 : uint next = rec->next_idx;
241 :
242 0 : if( FD_LIKELY( rec->exists ) ) {
243 0 : fd_rwlock_write( &rec->lock );
244 0 : fd_progcache_val_free( rec, cache );
245 0 : rec->exists = 0;
246 0 : fd_rwlock_unwrite( &rec->lock );
247 0 : }
248 0 : fd_prog_recp_release( cache->rec.pool, rec, 1 );
249 :
250 0 : head = next;
251 0 : }
252 0 : }
253 :
254 : /* fd_progcache_gc_root cleans up a stale "rooted" version of a
255 : record. */
256 :
257 : static void
258 : fd_progcache_gc_root( fd_progcache_join_t * cache,
259 0 : fd_funk_xid_key_pair_t const * pair ) {
260 : /* Phase 1: Remove record from map if found */
261 :
262 0 : fd_prog_recm_query_t query[1];
263 0 : int rm_err = fd_prog_recm_remove( cache->rec.map, pair, NULL, query, FD_MAP_FLAG_BLOCKING );
264 0 : if( rm_err==FD_MAP_ERR_KEY ) return;
265 0 : if( FD_UNLIKELY( rm_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_remove failed: %i-%s", rm_err, fd_map_strerror( rm_err ) ));
266 0 : FD_COMPILER_MFENCE();
267 :
268 : /* Phase 2: Drain readers */
269 :
270 0 : fd_rwlock_write( &query->ele->lock );
271 0 : fd_progcache_rec_t * old_rec = query->ele;
272 0 : memset( &old_rec->pair, 0, sizeof(fd_funk_xid_key_pair_t) );
273 0 : FD_COMPILER_MFENCE();
274 :
275 : /* Phase 3: Free record */
276 :
277 0 : fd_progcache_val_free( old_rec, cache );
278 0 : old_rec->exists = 0;
279 0 : fd_rwlock_unwrite( &old_rec->lock );
280 0 : fd_prog_recp_release( cache->rec.pool, old_rec, 1 );
281 0 : fd_progcache_admin_metrics_g.gc_root_cnt++;
282 0 : }
283 :
284 : /* fd_progcache_gc_invalidation cleans up a "cache invalidate" record */
285 :
286 : static void
287 : fd_progcache_gc_invalidation( fd_progcache_join_t * cache,
288 0 : fd_progcache_rec_t * rec ) { /* no lock */
289 :
290 : /* Phase 1: Remove record from map if found */
291 :
292 0 : fd_funk_xid_key_pair_t pair = rec->pair;
293 0 : fd_prog_recm_query_t query[1];
294 0 : int rm_err = fd_prog_recm_remove( cache->rec.map, &pair, NULL, query, FD_MAP_FLAG_BLOCKING );
295 0 : if( rm_err==FD_MAP_ERR_KEY ) return;
296 0 : if( FD_UNLIKELY( rm_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_remove failed: %i-%s", rm_err, fd_map_strerror( rm_err ) ));
297 0 : if( FD_UNLIKELY( query->ele!=rec ) ) {
298 0 : FD_LOG_CRIT(( "Found record collision in program cache: xid=%lu:%lu key=%016lx%016lx%016lx%016lx ele0=%u ele1=%u",
299 0 : pair.xid->ul[0], pair.xid->ul[1],
300 0 : fd_ulong_bswap( pair.key->ul[0] ),
301 0 : fd_ulong_bswap( pair.key->ul[1] ),
302 0 : fd_ulong_bswap( pair.key->ul[2] ),
303 0 : fd_ulong_bswap( pair.key->ul[3] ),
304 0 : (uint)( query->ele - cache->rec.pool->ele ),
305 0 : (uint)( rec - cache->rec.pool->ele ) ));
306 0 : }
307 :
308 : /* Phase 2: Wait for readers to drain */
309 :
310 0 : fd_rwlock_write( &rec->lock );
311 :
312 : /* Phase 3: Invalidate and free record */
313 :
314 0 : memset( &rec->pair, 0, sizeof(fd_funk_xid_key_pair_t) );
315 0 : FD_COMPILER_MFENCE();
316 :
317 0 : rec->map_next = UINT_MAX;
318 0 : fd_progcache_val_free( rec, cache );
319 0 : rec->exists = 0;
320 0 : fd_rwlock_unwrite( &rec->lock );
321 0 : fd_prog_recp_release( cache->rec.pool, rec, 1 );
322 0 : fd_progcache_admin_metrics_g.gc_root_cnt++;
323 0 : }
324 :
325 : /* Move list of records to root txn (advance_root)
326 :
327 : For each record to be rooted:
328 : - gc_root to remove any shadowed rooted revision
329 : - drain readers
330 : - release if invalidation (which are now a no-op) */
331 :
332 : static void
333 : fd_progcache_txn_publish_release( fd_progcache_join_t * cache,
334 0 : uint head ) {
335 0 : while( head!=UINT_MAX ) {
336 0 : fd_progcache_rec_t * rec = &cache->rec.pool->ele[ head ];
337 0 : uint next = rec->next_idx;
338 :
339 0 : if( FD_UNLIKELY( !rec->exists ) ) {
340 0 : fd_prog_recp_release( cache->rec.pool, rec, 1 );
341 0 : head = next;
342 0 : continue;
343 0 : }
344 :
345 : /* Evict previous root value from hash chain */
346 0 : fd_funk_xid_key_pair_t pair[1];
347 0 : fd_funk_rec_key_copy( pair->key, rec->pair.key );
348 0 : fd_funk_txn_xid_set_root( pair->xid );
349 0 : fd_progcache_gc_root( cache, pair );
350 :
351 : /* Detach from txn list */
352 0 : rec->prev_idx = UINT_MAX;
353 0 : rec->next_idx = UINT_MAX;
354 :
355 0 : if( FD_UNLIKELY( rec->invalidate ) ) {
356 : /* Drop cache invalidate records */
357 0 : fd_progcache_gc_invalidation( cache, rec );
358 0 : } else {
359 : /* Migrate record to root */
360 0 : fd_rwlock_write( &rec->lock );
361 0 : fd_progcache_xid_t const root = { .ul = { ULONG_MAX, ULONG_MAX } };
362 0 : fd_funk_txn_xid_st_atomic( rec->pair.xid, &root );
363 0 : fd_rwlock_unwrite( &rec->lock );
364 0 : fd_progcache_admin_metrics_g.root_cnt++;
365 0 : }
366 :
367 0 : head = next;
368 0 : }
369 0 : }
370 :
371 : /* fd_progcache_txn_publish_one merges an in-prep transaction whose
372 : parent is the last published, into the parent. */
373 :
374 : static uint
375 : fd_progcache_txn_publish_one( fd_progcache_join_t * cache,
376 0 : fd_progcache_txn_t * txn ) {
377 :
378 : /* Phase 1: Mark transaction as "last published" */
379 :
380 0 : fd_progcache_xid_t const xid = txn->xid;
381 0 : FD_LOG_INFO(( "progcache txn laddr=%p xid %lu:%lu: publish", (void *)txn, xid.ul[0], xid.ul[1] ));
382 0 : if( FD_UNLIKELY( txn->parent_idx!=UINT_MAX ) ) {
383 0 : FD_LOG_CRIT(( "fd_progcache_publish failed: txn with xid %lu:%lu is not a child of the last published txn", xid.ul[0], xid.ul[1] ));
384 0 : }
385 0 : fd_funk_txn_xid_st_atomic( cache->shmem->txn.last_publish, &xid );
386 :
387 : /* Phase 2: Drain inserters from transaction */
388 :
389 0 : fd_rwlock_write( &txn->lock );
390 :
391 : /* Phase 3: Detach records */
392 :
393 0 : uint rec_head = txn->rec_head_idx;
394 0 : txn->rec_head_idx = UINT_MAX;
395 0 : txn->rec_tail_idx = UINT_MAX;
396 :
397 : /* Phase 4: Remove transaction from fork graph */
398 :
399 0 : { /* Adjust the parent pointers of the children to point to "last published" */
400 0 : ulong child_idx = txn->child_head_idx;
401 0 : while( child_idx!=UINT_MAX ) {
402 0 : cache->txn.pool[ child_idx ].parent_idx = UINT_MAX;
403 0 : child_idx = cache->txn.pool[ child_idx ].sibling_next_idx;
404 0 : }
405 0 : }
406 :
407 : /* Phase 5: Remove transaction from index */
408 :
409 0 : if( FD_UNLIKELY( fd_prog_txnm_idx_remove( cache->txn.map, &txn->xid, ULONG_MAX, cache->txn.pool )==ULONG_MAX ) ) {
410 0 : FD_LOG_CRIT(( "fd_progcache_publish failed: fd_funk_txn_map_remove(%lu:%lu) failed",
411 0 : xid.ul[0], xid.ul[1] ));
412 0 : }
413 :
414 : /* Phase 6: Free transaction object */
415 :
416 0 : fd_rwlock_unwrite( &txn->lock );
417 0 : txn->parent_idx = UINT_MAX;
418 0 : txn->sibling_prev_idx = UINT_MAX;
419 0 : txn->sibling_next_idx = UINT_MAX;
420 0 : txn->child_head_idx = UINT_MAX;
421 0 : txn->child_tail_idx = UINT_MAX;
422 0 : fd_prog_txnp_ele_release( cache->txn.pool, txn );
423 :
424 0 : return rec_head;
425 0 : }
426 :
427 : void
428 : fd_progcache_txn_advance_root( fd_progcache_join_t * cache,
429 0 : fd_funk_txn_xid_t const * xid ) {
430 :
431 : /* Detach records from txns without acquiring record locks */
432 :
433 0 : fd_rwlock_write( &cache->shmem->txn.rwlock );
434 :
435 0 : uint txn_idx = (uint)fd_prog_txnm_idx_query( cache->txn.map, xid, UINT_MAX, cache->txn.pool );
436 0 : if( FD_UNLIKELY( txn_idx==UINT_MAX ) ) {
437 0 : FD_LOG_CRIT(( "fd_progcache_txn_advance_root failed: invalid XID %lu:%lu",
438 0 : xid->ul[0], xid->ul[1] ));
439 0 : }
440 0 : fd_progcache_txn_t * txn = &cache->txn.pool[ txn_idx ];
441 0 : if( FD_UNLIKELY( txn->parent_idx!=UINT_MAX ) ) {
442 0 : FD_LOG_CRIT(( "fd_progcache_txn_advance_root: parent of txn %lu:%lu is not root", xid->ul[0], xid->ul[1] ));
443 0 : }
444 :
445 0 : uint cancel_head = UINT_MAX;
446 0 : uint cancel_tail = UINT_MAX;
447 0 : fd_progcache_txn_cancel_prev_list( cache, txn, &cancel_head, &cancel_tail );
448 0 : fd_progcache_txn_cancel_next_list( cache, txn, &cancel_head, &cancel_tail );
449 :
450 0 : txn->sibling_prev_idx = UINT_MAX;
451 0 : txn->sibling_next_idx = UINT_MAX;
452 0 : cache->shmem->txn.child_head_idx = txn->child_head_idx;
453 0 : cache->shmem->txn.child_tail_idx = txn->child_tail_idx;
454 :
455 0 : uint publish_head = fd_progcache_txn_publish_one( cache, txn );
456 :
457 0 : fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
458 :
459 : /* Update records */
460 :
461 0 : fd_progcache_txn_cancel_release ( cache, cancel_head );
462 0 : fd_progcache_txn_publish_release( cache, publish_head );
463 0 : }
464 :
465 : void
466 : fd_progcache_txn_cancel( fd_progcache_join_t * cache,
467 0 : fd_progcache_xid_t const * xid ) {
468 :
469 0 : fd_rwlock_write( &cache->shmem->txn.rwlock );
470 0 : fd_progcache_txn_t * txn = fd_prog_txnm_ele_query( cache->txn.map, xid, NULL, cache->txn.pool );
471 0 : if( FD_UNLIKELY( !txn ) ) {
472 0 : FD_LOG_CRIT(( "fd_progcache_txn_cancel failed: invalid XID %lu:%lu",
473 0 : xid->ul[0], xid->ul[1] ));
474 0 : }
475 0 : uint cancel_head = UINT_MAX;
476 0 : uint cancel_tail = UINT_MAX;
477 0 : fd_progcache_txn_cancel_tree( cache, txn, &cancel_head, &cancel_tail );
478 0 : fd_rwlock_unwrite( &cache->shmem->txn.rwlock );
479 :
480 0 : fd_progcache_txn_cancel_release( cache, cancel_head );
481 0 : }
482 :
483 : /* reset_txn_list does a depth-first traversal of the txn tree.
484 : Detaches all recs from txns by emptying rec linked lists. */
485 :
486 : static void
487 : reset_txn_list( fd_progcache_join_t * cache,
488 0 : uint txn_head_idx ) {
489 0 : for( uint idx = txn_head_idx; idx!=UINT_MAX; ) {
490 0 : fd_progcache_txn_t * txn = &cache->txn.pool[ idx ];
491 0 : txn->rec_head_idx = UINT_MAX;
492 0 : txn->rec_tail_idx = UINT_MAX;
493 0 : reset_txn_list( cache, txn->child_head_idx );
494 0 : idx = txn->sibling_next_idx;
495 0 : }
496 0 : }
497 :
498 : /* reset_rec_map frees all records in a funk instance. */
499 :
500 : static void
501 30150 : reset_rec_map( fd_progcache_join_t * cache ) {
502 30150 : ulong chain_cnt = fd_prog_recm_chain_cnt( cache->rec.map );
503 15340834 : for( ulong chain_idx=0UL; chain_idx<chain_cnt; chain_idx++ ) {
504 15310684 : for(
505 15310684 : fd_prog_recm_iter_t iter = fd_prog_recm_iter( cache->rec.map, chain_idx );
506 15340484 : !fd_prog_recm_iter_done( iter );
507 15310684 : ) {
508 29800 : fd_progcache_rec_t * rec = fd_prog_recm_iter_ele( iter );
509 29800 : ulong next = fd_prog_recm_private_idx( rec->map_next );;
510 :
511 29805 : if( rec->exists ) {
512 29805 : fd_prog_recm_query_t rec_query[1];
513 29805 : int err = fd_prog_recm_remove( cache->rec.map, &rec->pair, NULL, rec_query, FD_MAP_FLAG_BLOCKING );
514 29805 : fd_funk_rec_key_t key; fd_funk_rec_key_copy( &key, rec->pair.key );
515 29805 : if( FD_UNLIKELY( err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_remove failed (%i-%s)", err, fd_map_strerror( err ) ));
516 29805 : fd_progcache_val_free( rec, cache );
517 29805 : }
518 :
519 29800 : rec->exists = 0;
520 29800 : fd_prog_recp_release( cache->rec.pool, rec, 1 );
521 29800 : iter.ele_idx = next;
522 29800 : }
523 15310684 : }
524 30150 : }
525 :
526 : void
527 0 : fd_progcache_reset( fd_progcache_join_t * cache ) {
528 0 : reset_txn_list( cache, cache->shmem->txn.child_head_idx );
529 0 : reset_rec_map( cache );
530 0 : }
531 :
532 : /* clear_txn_list does a depth-first traversal of the txn tree.
533 : Removes all txns. */
534 :
535 : static void
536 : clear_txn_list( fd_progcache_join_t * join,
537 85198 : uint txn_head_idx ) {
538 140257 : for( uint idx = txn_head_idx; idx!=UINT_MAX; ) {
539 55059 : fd_progcache_txn_t * txn = &join->txn.pool[ idx ];
540 55059 : uint next_idx = txn->sibling_next_idx;
541 55059 : uint child_idx = txn->child_head_idx;
542 55059 : txn->rec_head_idx = UINT_MAX;
543 55059 : txn->rec_tail_idx = UINT_MAX;
544 55059 : txn->child_head_idx = UINT_MAX;
545 55059 : txn->child_tail_idx = UINT_MAX;
546 55059 : txn->parent_idx = UINT_MAX;
547 55059 : txn->sibling_prev_idx = UINT_MAX;
548 55059 : txn->sibling_next_idx = UINT_MAX;
549 55059 : clear_txn_list( join, child_idx );
550 55059 : if( FD_UNLIKELY( !fd_prog_txnm_ele_remove( join->txn.map, &txn->xid, NULL, join->txn.pool ) ) ) FD_LOG_CRIT(( "fd_prog_txnm_ele_remove failed" ));
551 55059 : fd_prog_txnp_ele_release( join->txn.pool, txn );
552 55059 : idx = next_idx;
553 55059 : }
554 85198 : }
555 :
556 : void
557 30154 : fd_progcache_clear( fd_progcache_join_t * cache ) {
558 30154 : clear_txn_list( cache, cache->shmem->txn.child_head_idx );
559 30154 : cache->shmem->txn.child_head_idx = UINT_MAX;
560 30154 : cache->shmem->txn.child_tail_idx = UINT_MAX;
561 30154 : reset_rec_map( cache );
562 30154 : }
563 :
564 : void
565 : fd_progcache_inject_rec( fd_progcache_join_t * cache,
566 : void const * prog_addr,
567 : void const * prog_owner,
568 : fd_account_meta_t const * progdata_meta,
569 : fd_features_t const * features,
570 : ulong slot,
571 : uchar * scratch,
572 31041 : ulong scratch_sz ) {
573 :
574 : /* XID overview:
575 :
576 : - load_xid: tip of fork currently being executed
577 : - modify_xid: xid in which program was last modified / deployed
578 : - txn->xid: xid in which program cache entry is inserted
579 :
580 : slot(load_xid) > slot(entry_xid) >= slot(txn->xid) */
581 :
582 : /* Acquire reference to ELF binary data */
583 :
584 31041 : uchar const * elf_bin = NULL;
585 31041 : ulong elf_bin_sz = progdata_meta->dlen;
586 31041 : if( !memcmp( prog_owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) {
587 11188 : if( FD_UNLIKELY( elf_bin_sz<PROGRAMDATA_METADATA_SIZE ) ) return;
588 :
589 8521 : elf_bin = (uchar const *)fd_account_data( progdata_meta ) + PROGRAMDATA_METADATA_SIZE;
590 8521 : elf_bin_sz -= PROGRAMDATA_METADATA_SIZE;
591 19853 : } else if( !memcmp( prog_owner, fd_solana_bpf_loader_v4_program_id.key, sizeof(fd_pubkey_t) ) ) {
592 381 : if( FD_UNLIKELY( elf_bin_sz<LOADER_V4_PROGRAM_DATA_OFFSET ) ) return;
593 :
594 381 : elf_bin = (uchar const *)fd_account_data( progdata_meta ) + LOADER_V4_PROGRAM_DATA_OFFSET;
595 381 : elf_bin_sz -= LOADER_V4_PROGRAM_DATA_OFFSET;
596 19472 : } else if( !memcmp( prog_owner, fd_solana_bpf_loader_program_id.key, sizeof(fd_pubkey_t) ) ||
597 19478 : !memcmp( prog_owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) ) {
598 19478 : elf_bin = (uchar const *)fd_account_data( progdata_meta );
599 19478 : }
600 28374 : if( FD_UNLIKELY( !elf_bin ) ) return;
601 :
602 : /* Allocate a rec */
603 :
604 28374 : fd_progcache_rec_t * rec = fd_prog_recp_acquire( cache->rec.pool, NULL, 0, NULL );
605 28374 : if( FD_UNLIKELY( !rec ) ) {
606 0 : FD_LOG_ERR(( "Program cache is out of memory: fd_funk_rec_pool_acquire failed (rec_max=%lu)",
607 0 : fd_prog_recp_ele_max( cache->rec.pool ) ));
608 0 : }
609 28374 : memset( rec, 0, sizeof(fd_progcache_rec_t) );
610 28374 : rec->exists = 1;
611 28374 : rec->slot = slot;
612 :
613 28374 : rec->prev_idx = UINT_MAX;
614 28374 : rec->next_idx = UINT_MAX;
615 28374 : memcpy( rec->pair.key, prog_addr, 32UL );
616 28374 : fd_funk_txn_xid_set_root( rec->pair.xid );
617 :
618 : /* Load program */
619 :
620 28374 : ulong const load_slot = slot;
621 28374 : fd_prog_versions_t versions = fd_prog_versions( features, load_slot );
622 28374 : fd_sbpf_loader_config_t config = {
623 28374 : .sbpf_min_version = versions.min_sbpf_version,
624 28374 : .sbpf_max_version = versions.max_sbpf_version,
625 28374 : };
626 28374 : fd_sbpf_elf_info_t elf_info[1];
627 :
628 28374 : if( FD_LIKELY( fd_sbpf_elf_peek( elf_info, elf_bin, elf_bin_sz, &config )==FD_SBPF_ELF_SUCCESS ) ) {
629 9000 : ulong val_sz = fd_progcache_val_footprint( elf_info );
630 9000 : if( FD_UNLIKELY( !fd_progcache_val_alloc( rec, cache, fd_progcache_val_align(), val_sz ) ) ) {
631 : /* This is a test API, so no need to do cache eviction */
632 0 : FD_LOG_ERR(( "Program cache is out of memory: fd_alloc_malloc failed (requested sz=%lu)", val_sz ));
633 0 : }
634 :
635 9000 : if( FD_UNLIKELY( !fd_progcache_rec_load( rec, cache->wksp, elf_info, &config, load_slot, features, elf_bin, elf_bin_sz, scratch, scratch_sz ) ) ) {
636 128 : fd_progcache_val_free( rec, cache );
637 128 : fd_progcache_rec_nx( rec );
638 128 : }
639 19374 : } else {
640 19374 : fd_progcache_rec_nx( rec );
641 19374 : }
642 :
643 : /* Publish cache entry to funk index */
644 :
645 28374 : int insert_err = fd_prog_recm_txn_insert( cache->rec.map, rec );
646 28374 : if( FD_UNLIKELY( insert_err!=FD_MAP_SUCCESS ) ) {
647 0 : FD_LOG_CRIT(( "fd_funk_rec_map_txn_insert failed: %i-%s", insert_err, fd_map_strerror( insert_err ) ));
648 0 : }
649 28374 : }
650 :
651 : void
652 0 : fd_progcache_wksp_metrics_update( fd_progcache_join_t * cache ) {
653 0 : fd_wksp_t * wksp = cache->wksp;
654 0 : if( FD_UNLIKELY( fd_wksp_private_lock( wksp ) ) ) FD_LOG_CRIT(( "fd_wksp_private_lock failed" ));
655 :
656 0 : fd_wksp_private_pinfo_t * pinfo = fd_wksp_private_pinfo( wksp );
657 0 : ulong part_max = wksp->part_max;
658 0 : ulong cycle_tag = wksp->cycle_tag++;
659 :
660 0 : ulong free_part_cnt = 0UL;
661 0 : ulong free_sz = 0UL;
662 0 : ulong total_sz = 0UL;
663 0 : ulong free_part_max = 0UL;
664 :
665 0 : ulong i = fd_wksp_private_pinfo_idx( wksp->part_head_cidx );
666 0 : while( !fd_wksp_private_pinfo_idx_is_null( i ) ) {
667 0 : if( FD_UNLIKELY( i>=part_max ) || FD_UNLIKELY( pinfo[ i ].cycle_tag==cycle_tag ) ) {
668 0 : FD_LOG_CRIT(( "corrupt wksp detected" ));
669 0 : }
670 0 : pinfo[ i ].cycle_tag = cycle_tag; /* mark i as visited */
671 0 : ulong part_sz = fd_wksp_private_pinfo_sz( pinfo + i );
672 0 : ulong part_tag = pinfo[ i ].tag;
673 0 : ulong free_psz = fd_ulong_if( !part_tag, part_sz, 0UL );
674 0 : free_part_cnt += !part_tag;
675 0 : free_sz += free_psz;
676 0 : total_sz += part_sz;
677 0 : free_part_max = fd_ulong_max( free_part_max, free_psz );
678 0 : i = fd_wksp_private_pinfo_idx( pinfo[ i ].next_cidx );
679 0 : }
680 0 : fd_wksp_private_unlock( wksp );
681 :
682 0 : fd_progcache_admin_metrics_t * m = &fd_progcache_admin_metrics_g;
683 0 : m->wksp.free_part_cnt = free_part_cnt;
684 0 : m->wksp.free_sz = free_sz;
685 0 : m->wksp.total_sz = total_sz;
686 0 : m->wksp.free_part_max = free_part_max;
687 0 : }
|