Line data Source code
1 : #include "fd_prog_load.h"
2 : #include "fd_progcache_user.h"
3 : #include "fd_progcache_reclaim.h"
4 : #include "../../util/racesan/fd_racesan_target.h"
5 :
6 : FD_TL fd_progcache_metrics_t fd_progcache_metrics_default;
7 :
8 : fd_progcache_t *
9 : fd_progcache_join( fd_progcache_t * cache,
10 : fd_progcache_shmem_t * shmem,
11 : uchar * scratch,
12 180 : ulong scratch_sz ) {
13 180 : if( FD_UNLIKELY( !cache ) ) {
14 0 : FD_LOG_WARNING(( "NULL cache" ));
15 0 : return NULL;
16 0 : }
17 180 : if( FD_LIKELY( scratch_sz ) ) {
18 180 : if( FD_UNLIKELY( !scratch ) ) {
19 3 : FD_LOG_WARNING(( "NULL scratch" ));
20 3 : return NULL;
21 3 : }
22 177 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)scratch, FD_PROGCACHE_SCRATCH_ALIGN ) ) ) {
23 3 : FD_LOG_WARNING(( "misaligned scratch" ));
24 3 : return NULL;
25 3 : }
26 177 : }
27 174 : memset( cache, 0, sizeof(fd_progcache_t) );
28 174 : if( FD_UNLIKELY( !fd_progcache_shmem_join( cache->join, shmem ) ) ) return NULL;
29 :
30 174 : cache->metrics = &fd_progcache_metrics_default;
31 174 : cache->scratch = scratch;
32 174 : cache->scratch_sz = scratch_sz;
33 :
34 174 : return cache;
35 174 : }
36 :
37 : void *
38 : fd_progcache_leave( fd_progcache_t * cache,
39 171 : fd_progcache_shmem_t ** opt_shmem ) {
40 171 : if( FD_UNLIKELY( !cache ) ) {
41 0 : FD_LOG_WARNING(( "NULL cache" ));
42 0 : return NULL;
43 0 : }
44 :
45 171 : while( cache->join->rec.reclaim_head!=UINT_MAX ) {
46 0 : fd_prog_reclaim_work( cache->join );
47 0 : FD_SPIN_PAUSE();
48 0 : }
49 :
50 171 : if( FD_UNLIKELY( !fd_progcache_shmem_leave( cache->join, opt_shmem ) ) ) return NULL;
51 171 : cache->scratch = NULL;
52 171 : cache->scratch_sz = 0UL;
53 171 : return cache;
54 171 : }
55 :
56 : /* fd_progcache_load_fork pivots the progcache object to the selected
57 : fork (identified by tip XID).
58 :
59 : Populates cache->fork, which is a array-backed list of XIDs sorted
60 : newest to oldest. Cache lookups only respect records with an XID
61 : present in that list.
62 :
63 : For any given xid, the epoch_slot0 is assumed to stay constant. */
64 :
65 : static void
66 : fd_progcache_load_fork_slow( fd_progcache_t * cache,
67 105 : fd_xid_t const * xid ) {
68 105 : fd_accdb_lineage_t * lineage = cache->lineage;
69 105 : fd_progcache_join_t const * ljoin = cache->join;
70 105 : fd_rwlock_read( &ljoin->shmem->txn.rwlock );
71 105 : lineage->fork_depth = 0UL;
72 :
73 105 : ulong txn_max = fd_prog_txnp_max( ljoin->txn.pool );
74 105 : fd_xid_t next_xid = *xid;
75 105 : ulong i;
76 141 : for( i=0UL;; i++ ) {
77 141 : if( FD_UNLIKELY( i>=FD_PROGCACHE_DEPTH_MAX ) ) {
78 0 : FD_LOG_CRIT(( "fd_progcache_load_fork: fork depth exceeded max of %lu", (ulong)FD_PROGCACHE_DEPTH_MAX ));
79 0 : }
80 141 : uint next_idx = (uint)fd_prog_txnm_idx_query_const( ljoin->txn.map, &next_xid, UINT_MAX, ljoin->txn.pool );
81 141 : if( FD_UNLIKELY( next_idx==UINT_MAX ) ) break;
82 132 : if( FD_UNLIKELY( (ulong)next_idx >= txn_max ) )
83 0 : FD_LOG_CRIT(( "progcache: corruption detected (load_fork txn_idx=%u txn_max=%lu)", next_idx, txn_max ));
84 132 : fd_progcache_txn_t * candidate = &ljoin->txn.pool[ next_idx ];
85 :
86 132 : uint parent_idx = candidate->parent_idx;
87 132 : FD_TEST( parent_idx!=next_idx );
88 132 : lineage->fork[ i ] = next_xid;
89 132 : if( parent_idx==UINT_MAX ) {
90 96 : i++;
91 96 : break;
92 96 : }
93 36 : if( FD_UNLIKELY( (ulong)parent_idx >= txn_max ) )
94 0 : FD_LOG_CRIT(( "progcache: corruption detected (load_fork parent_idx=%u txn_max=%lu)", parent_idx, txn_max ));
95 36 : next_xid = ljoin->txn.pool[ parent_idx ].xid;
96 36 : }
97 :
98 105 : lineage->fork_depth = i;
99 :
100 105 : fd_rwlock_unread( &ljoin->shmem->txn.rwlock );
101 105 : }
102 :
103 : static inline void
104 : fd_progcache_load_fork( fd_progcache_t * cache,
105 261 : fd_xid_t const * xid ) {
106 : /* Skip if already on the correct fork */
107 261 : fd_accdb_lineage_t * lineage = cache->lineage;
108 261 : if( FD_LIKELY( (!!lineage->fork_depth) & (!!fd_funk_txn_xid_eq( &lineage->fork[ 0 ], xid ) ) ) ) return;
109 105 : fd_progcache_load_fork_slow( cache, xid ); /* switch fork */
110 105 : }
111 :
112 : /* fd_progcache_query searches for a program cache entry on the current
113 : fork. Stops short of an epoch boundary. */
114 :
115 : static int
116 : fd_progcache_search_chain( fd_progcache_t const * cache,
117 : ulong chain_idx,
118 : fd_funk_rec_key_t const * key,
119 : ulong revision_slot,
120 165 : fd_progcache_rec_t ** out_rec ) { /* read locked */
121 165 : *out_rec = NULL;
122 :
123 165 : fd_progcache_join_t const * ljoin = cache->join;
124 165 : fd_accdb_lineage_t const * lineage = cache->lineage;
125 165 : fd_prog_recm_shmem_t * shmap = ljoin->rec.map->map;
126 165 : fd_prog_recm_shmem_private_chain_t const * chain_tbl = fd_prog_recm_shmem_private_chain_const( shmap, 0UL );
127 165 : fd_prog_recm_shmem_private_chain_t const * chain = chain_tbl + chain_idx;
128 165 : fd_progcache_rec_t * rec_tbl = ljoin->rec.pool->ele;
129 165 : ulong rec_max = fd_prog_recp_ele_max( ljoin->rec.pool );
130 165 : ulong ver_cnt = FD_VOLATILE_CONST( chain->ver_cnt );
131 :
132 : /* Start a speculative transaction for the chain containing revisions
133 : of the program cache key we are looking for. */
134 165 : ulong cnt = fd_prog_recm_private_vcnt_cnt( ver_cnt );
135 165 : if( FD_UNLIKELY( fd_prog_recm_private_vcnt_ver( ver_cnt )&1 ) ) {
136 0 : return FD_MAP_ERR_AGAIN; /* chain is locked */
137 0 : }
138 165 : FD_COMPILER_MFENCE();
139 165 : fd_racesan_hook( "prog_search_chain:post_ver_cnt" );
140 165 : uint ele_idx = chain->head_cidx;
141 :
142 : /* Walk the map chain, remember the best entry */
143 165 : fd_progcache_rec_t * best = NULL;
144 207 : for( ulong i=0UL; i<cnt; i++, ele_idx=FD_VOLATILE_CONST( rec_tbl[ ele_idx ].map_next ) ) {
145 96 : if( FD_UNLIKELY( (ulong)ele_idx >= rec_max ) ) return FD_MAP_ERR_AGAIN;
146 96 : fd_progcache_rec_t * rec = &rec_tbl[ ele_idx ];
147 :
148 : /* Skip over unrelated records (hash collision) */
149 96 : if( FD_UNLIKELY( !fd_funk_rec_key_eq( rec->pair.key, key ) ) ) continue;
150 :
151 : /* Skip over other revisions */
152 96 : if( FD_UNLIKELY( rec->slot!=revision_slot ) ) continue;
153 54 : fd_xid_t rec_xid[1];
154 54 : fd_funk_txn_xid_ld_atomic( rec_xid, rec->pair.xid );
155 54 : if( FD_UNLIKELY( !fd_accdb_lineage_has_xid( lineage, rec_xid ) ) ) continue;
156 :
157 54 : if( FD_UNLIKELY( rec->map_next==ele_idx ) ) return FD_MAP_ERR_AGAIN;
158 54 : if( FD_UNLIKELY( rec->map_next!=UINT_MAX && rec->map_next>=rec_max ) ) return FD_MAP_ERR_AGAIN;
159 54 : best = rec;
160 54 : break;
161 54 : }
162 165 : fd_racesan_hook( "prog_search_chain:pre_tryread" );
163 165 : if( best && FD_UNLIKELY( !fd_rwlock_tryread( &best->lock ) ) ) {
164 0 : return FD_MAP_ERR_AGAIN;
165 0 : }
166 165 : fd_racesan_hook( "prog_search_chain:post_tryread" );
167 :
168 : /* Retry if we were overrun */
169 165 : if( FD_UNLIKELY( FD_VOLATILE_CONST( chain->ver_cnt )!=ver_cnt ) ) {
170 0 : if( best ) fd_rwlock_unread( &best->lock );
171 0 : return FD_MAP_ERR_AGAIN;
172 0 : }
173 :
174 165 : *out_rec = best;
175 165 : return FD_MAP_SUCCESS;
176 165 : }
177 :
178 : static fd_progcache_rec_t * /* read locked */
179 : fd_progcache_query( fd_progcache_t * cache,
180 : fd_xid_t const * xid,
181 : fd_funk_rec_key_t const * key,
182 165 : ulong revision_slot ) {
183 : /* Hash key to chain */
184 165 : fd_funk_xid_key_pair_t pair[1];
185 165 : fd_funk_txn_xid_copy( pair->xid, xid );
186 165 : fd_funk_rec_key_copy( pair->key, key );
187 165 : fd_prog_recm_t const * rec_map = cache->join->rec.map;
188 165 : ulong hash = fd_funk_rec_key_hash( pair->key, rec_map->map->seed );
189 165 : ulong chain_idx = (hash & (rec_map->map->chain_cnt-1UL) );
190 :
191 : /* Traverse chain for candidate */
192 165 : fd_progcache_rec_t * rec = NULL;
193 165 : for(;;) {
194 165 : int err = fd_progcache_search_chain( cache, chain_idx, key, revision_slot, &rec );
195 165 : if( FD_LIKELY( err==FD_MAP_SUCCESS ) ) break;
196 0 : fd_racesan_hook( "prog_query:retry" );
197 0 : FD_SPIN_PAUSE();
198 : /* FIXME backoff */
199 0 : }
200 :
201 165 : return rec;
202 165 : }
203 :
204 : fd_progcache_rec_t * /* read locked */
205 : fd_progcache_peek( fd_progcache_t * cache,
206 : fd_xid_t const * xid,
207 : fd_pubkey_t const * prog_addr,
208 165 : ulong revision_slot ) {
209 165 : if( FD_UNLIKELY( !cache || !cache->join->shmem ) ) FD_LOG_CRIT(( "NULL progcache" ));
210 165 : fd_progcache_load_fork( cache, xid );
211 165 : fd_funk_rec_key_t key[1]; fd_memcpy( key->uc, prog_addr->hash, 32UL );
212 165 : fd_progcache_rec_t * rec = fd_progcache_query( cache, xid, key, revision_slot );
213 165 : if( FD_UNLIKELY( !rec ) ) return NULL;
214 54 : return rec;
215 165 : }
216 :
217 : static void
218 : fd_progcache_rec_push_tail( fd_progcache_rec_t * rec_pool,
219 : fd_progcache_rec_t * rec,
220 : uint * rec_head_idx, /* write locked (txn) */
221 : uint * rec_tail_idx,
222 21 : ulong rec_max ) {
223 21 : uint rec_idx = (uint)( rec - rec_pool );
224 21 : uint rec_prev_idx = *rec_tail_idx;
225 :
226 21 : if( FD_UNLIKELY( (ulong)rec_idx >= rec_max ) )
227 0 : FD_LOG_CRIT(( "progcache: corruption detected (push_tail rec_idx=%u rec_max=%lu)", rec_idx, rec_max ));
228 21 : if( FD_UNLIKELY( rec_prev_idx!=UINT_MAX && (ulong)rec_prev_idx >= rec_max ) )
229 0 : FD_LOG_CRIT(( "progcache: corruption detected (push_tail rec_prev_idx=%u rec_max=%lu)", rec_prev_idx, rec_max ));
230 :
231 21 : rec->prev_idx = rec_prev_idx;
232 21 : rec->next_idx = UINT_MAX;
233 :
234 21 : if( rec_prev_idx==UINT_MAX ) {
235 21 : *rec_head_idx = rec_idx;
236 21 : } else {
237 0 : rec_pool[ rec_prev_idx ].next_idx = rec_idx;
238 0 : }
239 21 : *rec_tail_idx = rec_idx;
240 21 : }
241 :
242 : __attribute__((warn_unused_result))
243 : static int
244 : fd_progcache_push( fd_progcache_join_t * cache,
245 : fd_progcache_txn_t * txn, /* read locked */
246 : fd_progcache_rec_t * rec,
247 : void const * prog_addr,
248 81 : ulong revision_slot ) {
249 :
250 : /* Determine record's xid-key pair */
251 :
252 81 : rec->prev_idx = UINT_MAX;
253 81 : rec->next_idx = UINT_MAX;
254 81 : memcpy( rec->pair.key, prog_addr, 32UL );
255 81 : if( FD_UNLIKELY( txn ) ) {
256 21 : fd_funk_txn_xid_copy( rec->pair.xid, &txn->xid );
257 60 : } else {
258 60 : fd_funk_txn_xid_set_root( rec->pair.xid );
259 60 : }
260 :
261 : /* Lock rec_map chain, entering critical section */
262 :
263 81 : struct {
264 81 : fd_prog_recm_txn_t txn[1];
265 81 : fd_prog_recm_txn_private_info_t info[1];
266 81 : } _map_txn;
267 81 : fd_prog_recm_txn_t * map_txn = fd_prog_recm_txn_init( _map_txn.txn, cache->rec.map, 1UL );
268 81 : fd_prog_recm_txn_add( map_txn, &rec->pair, 1 );
269 81 : int txn_err = fd_prog_recm_txn_try( map_txn, FD_MAP_FLAG_BLOCKING );
270 81 : if( FD_UNLIKELY( txn_err!=FD_MAP_SUCCESS ) ) {
271 0 : FD_LOG_CRIT(( "Failed to insert progcache record: cannot lock funk rec map chain: %i-%s", txn_err, fd_map_strerror( txn_err ) ));
272 0 : }
273 81 : fd_racesan_hook( "prog_push:post_chain_lock" );
274 :
275 : /* Check if record exists */
276 :
277 81 : fd_prog_recm_query_t query[1];
278 81 : int query_err = fd_prog_recm_txn_query( cache->rec.map, &rec->pair, NULL, query, 0 );
279 81 : if( FD_UNLIKELY( query_err==FD_MAP_SUCCESS ) ) {
280 : /* Always replace existing rooted records */
281 0 : fd_progcache_rec_t * prev_rec = query->ele;
282 0 : if( fd_funk_txn_xid_eq_root( rec->pair.xid ) && prev_rec->slot < revision_slot ) {
283 0 : fd_rwlock_write( &prev_rec->lock );
284 0 : fd_prog_recm_txn_remove( cache->rec.map, &rec->pair, NULL, query, FD_MAP_FLAG_USE_HINT );
285 0 : fd_progcache_val_free( prev_rec, cache );
286 0 : fd_rwlock_unwrite( &prev_rec->lock );
287 0 : fd_prog_clock_remove( cache->clock.bits, (ulong)( prev_rec - cache->rec.pool->ele ) );
288 0 : fd_prog_recp_release( cache->rec.pool, prev_rec, 1 );
289 0 : } else {
290 0 : fd_prog_recm_txn_test( map_txn );
291 0 : fd_prog_recm_txn_fini( map_txn );
292 0 : return 0;
293 0 : }
294 81 : } else if( FD_UNLIKELY( query_err!=FD_MAP_ERR_KEY ) ) {
295 0 : FD_LOG_CRIT(( "fd_prog_recm_txn_query failed: %i-%s", query_err, fd_map_strerror( query_err ) ));
296 0 : }
297 :
298 : /* Phase 4: Insert new record */
299 :
300 81 : int insert_err = fd_prog_recm_txn_insert( cache->rec.map, rec );
301 81 : if( FD_UNLIKELY( insert_err!=FD_MAP_SUCCESS ) ) {
302 0 : FD_LOG_CRIT(( "fd_prog_recm_txn_insert failed: %i-%s", insert_err, fd_map_strerror( insert_err ) ));
303 0 : }
304 81 : fd_racesan_hook( "prog_push:post_map_insert" );
305 :
306 : /* Phase 5: Insert rec into rec_map */
307 :
308 81 : ulong rec_max = fd_prog_recp_ele_max( cache->rec.pool );
309 81 : if( txn ) {
310 21 : fd_progcache_rec_push_tail( cache->rec.pool->ele,
311 21 : rec,
312 21 : &txn->rec_head_idx,
313 21 : &txn->rec_tail_idx,
314 21 : rec_max );
315 21 : uint txn_idx_computed = (uint)( txn - cache->txn.pool );
316 21 : ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
317 21 : if( FD_UNLIKELY( (ulong)txn_idx_computed >= txn_max ) )
318 0 : FD_LOG_CRIT(( "progcache: corruption detected (push txn_idx=%u txn_max=%lu)", txn_idx_computed, txn_max ));
319 21 : atomic_store_explicit( &rec->txn_idx, txn_idx_computed, memory_order_release );
320 21 : }
321 :
322 : /* Phase 6: Finish rec_map transaction */
323 :
324 81 : int test_err = fd_prog_recm_txn_test( map_txn );
325 81 : if( FD_UNLIKELY( test_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_txn_test failed: %i-%s", test_err, fd_map_strerror( test_err ) ));
326 81 : fd_prog_recm_txn_fini( map_txn );
327 :
328 : /* Phase 7: Mark record as recently accessed */
329 :
330 81 : ulong rec_clock_idx = (ulong)( rec - cache->rec.pool->ele );
331 81 : if( FD_UNLIKELY( rec_clock_idx >= rec_max ) )
332 0 : FD_LOG_CRIT(( "progcache: corruption detected (push rec_idx=%lu rec_max=%lu)", rec_clock_idx, rec_max ));
333 81 : fd_prog_clock_touch( cache->clock.bits, rec_clock_idx );
334 :
335 81 : return 1;
336 81 : }
337 :
338 : /* insert_params captures all environment parameters required to load a
339 : program revision into cache. */
340 :
341 : struct insert_params {
342 : void const * prog_addr;
343 : ulong revision_slot;
344 : fd_sbpf_elf_info_t elf_info;
345 : fd_sbpf_loader_config_t config;
346 : fd_features_t const * features;
347 : uchar const * bin;
348 : ulong bin_sz;
349 : int peek_err;
350 : };
351 :
352 : typedef struct insert_params insert_params_t;
353 :
354 : static insert_params_t *
355 : insert_params( insert_params_t * p,
356 : fd_xid_t const * load_xid,
357 : void const * prog_addr,
358 : fd_prog_load_env_t const * env,
359 : fd_accdb_ro_t * prog_ro,
360 81 : fd_prog_info_t const * info ) {
361 81 : memset( p, 0, sizeof(insert_params_t) );
362 :
363 : /* Derive executable info */
364 81 : uchar const * bin = (uchar const *)fd_accdb_ref_data_const( prog_ro ) + info->elf_off;
365 81 : ulong bin_sz = info->elf_sz;
366 81 : ulong revision_slot = fd_progcache_revision_slot( env->epoch_slot0, info->deploy_slot );
367 :
368 : /* Pre-flight checks, determine required buffer size */
369 :
370 81 : fd_features_t const * features = env->features;
371 81 : ulong const load_slot = load_xid->ul[0];
372 81 : fd_prog_versions_t versions = fd_prog_versions( features, load_slot );
373 81 : fd_sbpf_elf_info_t elf_info;
374 81 : fd_sbpf_loader_config_t config = {
375 81 : .sbpf_min_version = versions.min_sbpf_version,
376 81 : .sbpf_max_version = versions.max_sbpf_version,
377 81 : };
378 81 : int peek_err = fd_sbpf_elf_peek( &elf_info, bin, bin_sz, &config );
379 :
380 81 : *p = (insert_params_t) {
381 81 : .prog_addr = prog_addr,
382 81 : .revision_slot = revision_slot,
383 81 : .features = features,
384 81 : .bin = !peek_err ? bin : NULL,
385 81 : .bin_sz = !peek_err ? bin_sz : 0UL,
386 81 : .peek_err = peek_err,
387 81 : .elf_info = elf_info,
388 81 : .config = config
389 81 : };
390 81 : return p;
391 81 : }
392 :
393 : /* fd_progcache_spill_open loads a program into the cache spill buffer.
394 : The spill area is an "emergency" area for temporary program loads in
395 : case the record pool/heap are too contended. */
396 :
397 : static fd_progcache_rec_t * /* read locked */
398 : fd_progcache_spill_open( fd_progcache_t * cache,
399 0 : insert_params_t const * params ) {
400 0 : fd_progcache_join_t * join = cache->join;
401 0 : fd_progcache_shmem_t * shmem = join->shmem;
402 0 : if( !cache->spill_active ) fd_rwlock_write( &shmem->spill.lock );
403 0 : else FD_TEST( FD_VOLATILE_CONST( shmem->spill.lock.value )==FD_RWLOCK_WRITE_LOCK );
404 :
405 : /* Allocate record */
406 :
407 0 : if( FD_UNLIKELY( shmem->spill.rec_used >= FD_MAX_INSTRUCTION_STACK_DEPTH ) ) {
408 0 : FD_LOG_CRIT(( "spill buffer overflow: rec_used=%u rec_max=%lu", shmem->spill.rec_used, FD_MAX_INSTRUCTION_STACK_DEPTH ));
409 0 : }
410 0 : cache->spill_active++;
411 0 : uint rec_idx = shmem->spill.rec_used++;
412 0 : shmem->spill.spad_off[ rec_idx ] = shmem->spill.spad_used;
413 0 : fd_progcache_rec_t * rec = &shmem->spill.rec[ rec_idx ];
414 0 : memset( rec, 0, sizeof(fd_progcache_rec_t) );
415 0 : rec->lock.value = 1; /* read lock; no concurrency, don't need CAS */
416 0 : rec->exists = 1;
417 0 : rec->slot = params->revision_slot;
418 :
419 : /* Load program */
420 :
421 0 : if( !params->peek_err ) {
422 0 : ulong off0 = fd_ulong_align_up( shmem->spill.spad_used, fd_progcache_val_align() );
423 0 : ulong off1 = off0 + fd_progcache_val_footprint( ¶ms->elf_info );
424 0 : if( FD_UNLIKELY( off1 > FD_PROGCACHE_SPAD_MAX ) ) {
425 0 : FD_LOG_CRIT(( "spill buffer overflow: spad_used=%u val_sz=%lu spad_max=%lu", shmem->spill.spad_used, off1-off0, FD_PROGCACHE_SPAD_MAX ));
426 0 : }
427 0 : rec->data_gaddr = fd_wksp_gaddr_fast( join->data_base, shmem->spill.spad + off0 );
428 0 : rec->data_max = (uint)( off1 - off0 );
429 :
430 0 : long dt = -fd_tickcount();
431 0 : if( FD_LIKELY( fd_progcache_rec_load( rec, join->data_base, ¶ms->elf_info, ¶ms->config, params->revision_slot, params->features, params->bin, params->bin_sz, cache->scratch, cache->scratch_sz ) ) ) {
432 : /* Valid program, allocate data */
433 0 : shmem->spill.spad_used = (uint)off1;
434 0 : } else {
435 0 : fd_progcache_rec_nx( rec );
436 0 : }
437 0 : dt += fd_tickcount();
438 0 : cache->metrics->cum_load_ticks += (ulong)dt;
439 :
440 0 : } else {
441 0 : rec->data_gaddr = 0UL;
442 0 : rec->data_max = 0U;
443 0 : }
444 :
445 0 : cache->metrics->spill_cnt++;
446 0 : cache->metrics->spill_tot_sz += rec->rodata_sz;
447 :
448 0 : FD_TEST( rec->exists );
449 0 : return rec;
450 0 : }
451 :
452 : /* fd_progcache_insert allocates a cache entry, loads a program into it,
453 : and publishes the cache entry to the global index (recm). If an OOM
454 : condition is detected, attempts to run the cache eviction algo, and
455 : finally falls back to using the spill buffer. Returns NULL if the
456 : insertion raced with another thread (frees any previously allocated
457 : resource in that case). */
458 :
459 : static fd_progcache_rec_t * /* read locked */
460 : fd_progcache_insert( fd_progcache_t * cache,
461 81 : insert_params_t const * params ) {
462 81 : fd_progcache_join_t * ljoin = cache->join;
463 81 : void const * prog_addr = params->prog_addr;
464 81 : int peek_err = params->peek_err;
465 81 : fd_sbpf_elf_info_t const * elf_info = ¶ms->elf_info;
466 81 : fd_sbpf_loader_config_t const * config = ¶ms->config;
467 81 : ulong revision_slot = params->revision_slot;
468 81 : fd_features_t const * features = params->features;
469 81 : uchar const * bin = params->bin;
470 81 : ulong bin_sz = params->bin_sz;
471 :
472 :
473 : /* Allocate record and heap space */
474 :
475 81 : fd_progcache_rec_t * rec = fd_prog_recp_acquire( ljoin->rec.pool, NULL, 1, NULL );
476 81 : if( FD_UNLIKELY( !rec ) ) {
477 0 : cache->metrics->oom_desc_cnt++;
478 0 : fd_prog_clock_evict( cache, 4UL, 0UL );
479 0 : rec = fd_prog_recp_acquire( ljoin->rec.pool, NULL, 1, NULL );
480 0 : if( FD_UNLIKELY( !rec ) ) {
481 : /* Out of memory (record table) */
482 0 : return fd_progcache_spill_open( cache, params );
483 0 : }
484 0 : }
485 81 : memset( rec, 0, sizeof(fd_progcache_rec_t) );
486 81 : rec->exists = 1;
487 81 : rec->slot = revision_slot;
488 81 : rec->txn_idx = UINT_MAX;
489 81 : rec->reclaim_next = UINT_MAX;
490 :
491 81 : if( FD_LIKELY( peek_err==FD_SBPF_ELF_SUCCESS ) ) {
492 81 : ulong val_align = fd_progcache_val_align();
493 81 : ulong val_footprint = fd_progcache_val_footprint( elf_info );
494 81 : if( FD_UNLIKELY( !fd_progcache_val_alloc( rec, ljoin, val_align, val_footprint ) ) ) {
495 0 : cache->metrics->oom_heap_cnt++;
496 0 : fd_prog_clock_evict( cache, 0UL, 16UL<<20 );
497 0 : if( FD_UNLIKELY( !fd_progcache_val_alloc( rec, ljoin, val_align, val_footprint ) ) ) {
498 : /* Out of memory (heap) */
499 0 : rec->exists = 0;
500 0 : fd_prog_recp_release( ljoin->rec.pool, rec, 1 );
501 0 : return fd_progcache_spill_open( cache, params );
502 0 : }
503 0 : }
504 81 : } else {
505 0 : fd_progcache_rec_nx( rec );
506 0 : }
507 :
508 : /* Publish cache entry to index */
509 :
510 : /* Acquires rec->lock before txn.rwlock (inverse of the documented
511 : lock order). Safe because the record was just allocated and is not
512 : yet visible to other threads. */
513 81 : fd_rwlock_write( &rec->lock );
514 81 : fd_racesan_hook( "prog_insert:pre_push" );
515 81 : fd_xid_t const * xid = fd_lineage_xid( cache->lineage, revision_slot );
516 81 : fd_rwlock_read( &ljoin->shmem->txn.rwlock );
517 81 : fd_progcache_txn_t * txn = NULL;
518 81 : if( xid ) txn = (fd_progcache_txn_t *)fd_prog_txnm_ele_query_const( ljoin->txn.map, xid, NULL, ljoin->txn.pool );
519 81 : if( txn ) fd_rwlock_write( &txn->lock );
520 81 : int push_ok = fd_progcache_push( ljoin, txn, rec, prog_addr, revision_slot );
521 81 : if( txn ) fd_rwlock_unwrite( &txn->lock );
522 81 : if( FD_UNLIKELY( !push_ok ) ) {
523 0 : fd_rwlock_unread( &ljoin->shmem->txn.rwlock );
524 0 : fd_rwlock_unwrite( &rec->lock );
525 0 : fd_progcache_val_free( rec, ljoin );
526 0 : fd_prog_recp_release( ljoin->rec.pool, rec, 1 );
527 0 : return NULL;
528 0 : }
529 81 : fd_rwlock_unread( &ljoin->shmem->txn.rwlock );
530 81 : fd_racesan_hook( "prog_insert:pre_load" );
531 :
532 : /* Load program
533 : (The write lock was acquired before loading such that another
534 : thread trying to load the same record instead waits for us to
535 : complete) */
536 :
537 81 : if( FD_LIKELY( peek_err==FD_SBPF_ELF_SUCCESS ) ) {
538 81 : long dt = -fd_tickcount();
539 81 : if( FD_UNLIKELY( !fd_progcache_rec_load( rec, ljoin->data_base, elf_info, config, revision_slot, features, bin, bin_sz, cache->scratch, cache->scratch_sz ) ) ) {
540 : /* Not a valid program (mark cache entry as non-executable) */
541 3 : fd_progcache_val_free( rec, ljoin );
542 3 : fd_progcache_rec_nx( rec );
543 3 : }
544 81 : dt += fd_tickcount();
545 81 : cache->metrics->cum_load_ticks += (ulong)dt;
546 81 : }
547 :
548 81 : fd_rwlock_demote( &rec->lock );
549 :
550 81 : cache->metrics->fill_cnt++;
551 81 : cache->metrics->fill_tot_sz += rec->rodata_sz;
552 81 : FD_TEST( rec->exists );
553 81 : return rec;
554 81 : }
555 :
556 : fd_progcache_rec_t * /* read locked */
557 : fd_progcache_pull( fd_progcache_t * cache,
558 : fd_xid_t const * xid,
559 : fd_pubkey_t const * prog_addr,
560 : fd_prog_load_env_t const * env,
561 : fd_accdb_ro_t * prog_ro,
562 96 : fd_pubkey_t const * program_owner ) {
563 96 : if( FD_UNLIKELY( !cache || !cache->join->shmem ) ) FD_LOG_CRIT(( "NULL progcache" ));
564 96 : long dt = -fd_tickcount();
565 96 : fd_progcache_load_fork( cache, xid );
566 96 : cache->metrics->lookup_cnt++;
567 :
568 96 : fd_prog_info_t info[1];
569 96 : if( FD_UNLIKELY( !fd_prog_info( info, prog_ro, program_owner ) ) ) return NULL;
570 84 : ulong revision_slot = fd_progcache_revision_slot( env->epoch_slot0, info->deploy_slot );
571 :
572 84 : insert_params_t insert[1];
573 84 : fd_progcache_rec_t * found_rec = NULL;
574 84 : for( int attempt=0;; attempt++ ) {
575 84 : found_rec = fd_progcache_peek( cache, xid, prog_addr, revision_slot );
576 84 : if( FD_LIKELY( found_rec ) ) {
577 3 : cache->metrics->hit_cnt++;
578 3 : break;
579 3 : }
580 81 : if( attempt==0 ) insert_params( insert, xid, prog_addr, env, prog_ro, info );
581 81 : found_rec = fd_progcache_insert( cache, insert );
582 81 : if( FD_LIKELY( found_rec ) ) {
583 81 : cache->metrics->miss_cnt++;
584 81 : break;
585 81 : }
586 0 : if( FD_UNLIKELY( attempt>=4 ) ) {
587 : /* Extremely unlikely case: four separate attempts resulted in
588 : contention */
589 0 : return fd_progcache_spill_open( cache, insert );
590 0 : }
591 0 : }
592 :
593 84 : dt += fd_tickcount();
594 84 : cache->metrics->cum_pull_ticks += (ulong)dt;
595 84 : return found_rec;
596 84 : }
597 :
598 : static void
599 0 : fd_progcache_spill_close( fd_progcache_t * cache ) {
600 0 : FD_TEST( cache->spill_active );
601 0 : cache->spill_active--;
602 :
603 0 : fd_progcache_shmem_t * shmem = cache->join->shmem;
604 :
605 : /* Cascade: rewind rec_used and spad_used while the top record is
606 : closed. This reclaims spill spad memory in LIFO order. */
607 0 : while( shmem->spill.rec_used > 0 &&
608 0 : !shmem->spill.rec[ shmem->spill.rec_used-1 ].exists ) {
609 0 : shmem->spill.rec_used--;
610 0 : shmem->spill.spad_used = shmem->spill.spad_off[ shmem->spill.rec_used ];
611 0 : }
612 :
613 0 : if( cache->spill_active==0 ) {
614 0 : fd_rwlock_t * spill_lock = &shmem->spill.lock;
615 0 : FD_TEST( spill_lock->value==0xFFFF );
616 0 : FD_TEST( shmem->spill.rec_used==0 );
617 0 : FD_TEST( shmem->spill.spad_used==0 );
618 0 : fd_rwlock_unwrite( spill_lock );
619 0 : }
620 0 : }
621 :
622 : void
623 : fd_progcache_rec_close( fd_progcache_t * cache,
624 135 : fd_progcache_rec_t * rec ) {
625 135 : if( FD_UNLIKELY( !rec ) ) return;
626 135 : if( FD_UNLIKELY( !rec->exists ) ) FD_LOG_CRIT(( "use-after-free: progcache record %p is dead", (void *)rec ));
627 135 : FD_TEST( FD_VOLATILE_CONST( rec->lock.value )!=0 );
628 135 : fd_rwlock_unread( &rec->lock );
629 135 : fd_progcache_shmem_t * shmem = cache->join->shmem;
630 135 : if( rec >= shmem->spill.rec &&
631 135 : rec < shmem->spill.rec + FD_MAX_INSTRUCTION_STACK_DEPTH ) {
632 0 : rec->exists = 0;
633 0 : fd_progcache_spill_close( cache );
634 0 : }
635 135 : }
|