Line data Source code
1 : #include "fd_txncache.h"
2 : #include "fd_txncache_private.h"
3 : #include "../../util/log/fd_log.h"
4 : #include "../../util/racesan/fd_racesan_target.h"
5 :
6 : #include <unistd.h>
7 : #include <errno.h>
8 :
9 : FD_FN_CONST ulong
10 1488 : fd_txncache_align( void ) {
11 1488 : return FD_TXNCACHE_ALIGN;
12 1488 : }
13 :
14 : FD_FN_CONST ulong
15 606 : fd_txncache_footprint( ulong max_live_slots ) {
16 606 : ulong max_active_slots = FD_TXNCACHE_MAX_BLOCKHASH_DISTANCE+max_live_slots;
17 :
18 606 : ulong l;
19 606 : l = FD_LAYOUT_INIT;
20 606 : l = FD_LAYOUT_APPEND( l, FD_TXNCACHE_ALIGN, sizeof(fd_txncache_t) );
21 606 : l = FD_LAYOUT_APPEND( l, alignof(blockcache_t), max_active_slots*sizeof(blockcache_t) );
22 606 : return FD_LAYOUT_FINI( l, FD_TXNCACHE_ALIGN );
23 606 : }
24 :
25 : void *
26 : fd_txncache_new( void * ljoin,
27 : fd_txncache_shmem_t * shmem,
28 324 : int spill_fd ) {
29 324 : if( FD_UNLIKELY( !ljoin ) ) {
30 0 : FD_LOG_WARNING(( "NULL ljoin" ));
31 0 : return NULL;
32 0 : }
33 :
34 324 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ljoin, fd_txncache_align() ) ) ) {
35 0 : FD_LOG_WARNING(( "misaligned ljoin" ));
36 0 : return NULL;
37 0 : }
38 :
39 324 : ulong max_active_slots = shmem->active_slots_max;
40 324 : ulong blockhash_map_chains = fd_ulong_pow2_up( 2UL*shmem->active_slots_max );
41 324 : ulong bucket_cnt = shmem->bucket_cnt;
42 :
43 : /* Page counts come from the shmem header rather than being
44 : re-derived, so the layout walk below cannot desync from the one in
45 : fd_txncache_shmem_new. */
46 324 : ulong _txnpages_max = shmem->txnpages_max;
47 :
48 324 : ulong _descends_footprint = descends_set_footprint( max_active_slots );
49 324 : if( FD_UNLIKELY( !_descends_footprint ) ) {
50 0 : FD_LOG_WARNING(( "invalid max_active_slots" ));
51 0 : return NULL;
52 0 : }
53 :
54 324 : FD_SCRATCH_ALLOC_INIT( l, shmem );
55 324 : fd_txncache_shmem_t * tc = FD_SCRATCH_ALLOC_APPEND( l, FD_TXNCACHE_SHMEM_ALIGN, sizeof(fd_txncache_shmem_t) );
56 324 : void * _blockhash_map = FD_SCRATCH_ALLOC_APPEND( l, blockhash_map_align(), blockhash_map_footprint( blockhash_map_chains ) );
57 324 : void * _blockcache_pool = FD_SCRATCH_ALLOC_APPEND( l, blockcache_pool_align(), blockcache_pool_footprint( max_active_slots ) );
58 324 : void * _blockcache_heads = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), max_active_slots*bucket_cnt*sizeof(uint) );
59 324 : void * _blockcache_descends = FD_SCRATCH_ALLOC_APPEND( l, descends_set_align(), max_active_slots*_descends_footprint );
60 324 : void * _txnpages_free = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), _txnpages_max*sizeof(uint) );
61 324 : void * _page_meta = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_page_meta_t), _txnpages_max*sizeof(fd_txncache_page_meta_t) );
62 324 : void * _frame_page = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), shmem->frames_max*sizeof(uint) );
63 324 : void * _frames_free = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), shmem->frames_max*sizeof(uint) );
64 324 : void * _txnpages = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_txnpage_t), shmem->frames_max*sizeof(fd_txncache_txnpage_t) );
65 324 : void * _scratch_heads = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), bucket_cnt*sizeof(uint) );
66 324 : void * _scratch_txnpage = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_txnpage_t), sizeof(fd_txncache_txnpage_t) );
67 324 : void * _scratch_input = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txncache_txnpage_t), sizeof(fd_txncache_txnpage_t) );
68 :
69 324 : FD_SCRATCH_ALLOC_INIT( l2, ljoin );
70 324 : fd_txncache_t * ltc = FD_SCRATCH_ALLOC_APPEND( l2, FD_TXNCACHE_ALIGN, sizeof(fd_txncache_t) );
71 324 : void * _local_blockcache_pool = FD_SCRATCH_ALLOC_APPEND( l2, alignof(blockcache_t), max_active_slots*sizeof(blockcache_t) );
72 :
73 324 : ltc->shmem = tc;
74 :
75 324 : ltc->blockcache_pool = (blockcache_t*)_local_blockcache_pool;
76 324 : ltc->blockcache_shmem_pool = blockcache_pool_join( _blockcache_pool );
77 :
78 56841 : for( ulong i=0UL; i<shmem->active_slots_max; i++ ) {
79 56517 : ltc->blockcache_pool[ i ].heads = (uint *)_blockcache_heads + i*bucket_cnt;
80 56517 : ltc->blockcache_pool[ i ].descends = descends_set_join( (uchar *)_blockcache_descends + i*_descends_footprint );
81 56517 : ltc->blockcache_pool[ i ].shmem = ltc->blockcache_shmem_pool + i;
82 56517 : FD_TEST( ltc->blockcache_pool[ i ].shmem );
83 56517 : }
84 :
85 324 : FD_TEST( ltc->blockcache_shmem_pool );
86 :
87 324 : ltc->blockhash_map = blockhash_map_join( _blockhash_map );
88 324 : FD_TEST( ltc->blockhash_map );
89 :
90 324 : ltc->txnpages_free = _txnpages_free;
91 324 : ltc->page_meta = _page_meta;
92 324 : ltc->frame_page = _frame_page;
93 324 : ltc->frames_free = _frames_free;
94 324 : ltc->txnpages = (fd_txncache_txnpage_t *)_txnpages;
95 324 : ltc->scratch_heads = _scratch_heads;
96 324 : ltc->scratch_txnpage = _scratch_txnpage;
97 324 : ltc->scratch_input = _scratch_input;
98 324 : ltc->spill_fd = spill_fd;
99 :
100 324 : memset( ltc->metrics, 0, sizeof(ltc->metrics) );
101 324 : fd_histf_join( fd_histf_new( ltc->metrics->query_ticks, 1UL, 1UL<<30 ) );
102 324 : fd_histf_join( fd_histf_new( ltc->metrics->insert_ticks, 1UL, 1UL<<30 ) );
103 :
104 324 : return (void *)ltc;
105 324 : }
106 :
107 : fd_txncache_join_metrics_t *
108 126 : fd_txncache_join_metrics( fd_txncache_t * tc ) {
109 126 : return tc->metrics;
110 126 : }
111 :
112 : fd_txncache_t *
113 333 : fd_txncache_join( void * ljoin ) {
114 333 : if( FD_UNLIKELY( !ljoin ) ) {
115 3 : FD_LOG_WARNING(( "NULL ljoin" ));
116 3 : return NULL;
117 3 : }
118 :
119 330 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ljoin, fd_txncache_align() ) ) ) {
120 3 : FD_LOG_WARNING(( "misaligned ljoin" ));
121 3 : return NULL;
122 3 : }
123 :
124 327 : fd_txncache_t * tc = (fd_txncache_t *)ljoin;
125 :
126 327 : return tc;
127 330 : }
128 :
129 : /* lock_read and lock_write take the txncache lock, adding the time
130 : spent waiting to the lock wait metrics. Uncontended acquisitions
131 : are not timed. */
132 :
133 : static inline void
134 32347849 : lock_read( fd_txncache_t * tc ) FD_ACQUIRE_SHARED( tc->shmem->lock ) FD_NO_THREAD_SAFETY_ANALYSIS {
135 32347849 : if( FD_LIKELY( fd_rwlock_tryread( tc->shmem->lock ) ) ) return;
136 3204 : long t0 = fd_tickcount();
137 3204 : fd_rwlock_read( tc->shmem->lock );
138 3204 : FD_ATOMIC_FETCH_AND_ADD( &tc->shmem->lock_read_wait_ticks, (ulong)(fd_tickcount()-t0) );
139 3204 : }
140 :
141 : static inline void
142 244288 : lock_write( fd_txncache_t * tc ) FD_ACQUIRE( tc->shmem->lock ) FD_NO_THREAD_SAFETY_ANALYSIS {
143 244288 : if( FD_LIKELY( fd_rwlock_trywrite( tc->shmem->lock ) ) ) return;
144 3201 : long t0 = fd_tickcount();
145 3201 : fd_rwlock_write( tc->shmem->lock );
146 3201 : tc->shmem->lock_write_wait_ticks += (ulong)(fd_tickcount()-t0);
147 3201 : }
148 :
149 : static void
150 : spill_rw( fd_txncache_t * tc,
151 : ulong off,
152 : uchar * buf,
153 : ulong sz,
154 42292 : int write ) {
155 42292 : FD_TEST( tc->spill_fd>=0 );
156 84584 : while( sz ) {
157 42292 : long n = write ? pwrite( tc->spill_fd, buf, sz, (off_t)off )
158 42292 : : pread ( tc->spill_fd, buf, sz, (off_t)off );
159 42292 : if( FD_UNLIKELY( n<0L && errno==EINTR ) ) continue;
160 42292 : if( FD_UNLIKELY( n<0L ) ) FD_LOG_CRIT(( "txncache spill %s failed at offset %lu (%ld, errno %d)", write ? "write" : "read", off, n, errno ));
161 42292 : if( FD_UNLIKELY( !n ) ) FD_LOG_CRIT(( "txncache spill %s at offset %lu", write ? "write made no progress" : "read reached unexpected EOF", off ));
162 42292 : sz -= (ulong)n;
163 42292 : if( FD_UNLIKELY( sz && !fd_ulong_is_aligned( (ulong)n, FD_TXNCACHE_TXNPAGE_ALIGN ) ) ) {
164 0 : FD_LOG_CRIT(( "txncache spill %s returned an unaligned short transfer at offset %lu", write ? "write" : "read", off ));
165 0 : }
166 42292 : buf += (ulong)n;
167 42292 : off += (ulong)n;
168 42292 : }
169 42292 : }
170 :
171 : static void
172 : spill_io( fd_txncache_t * tc,
173 : ulong page,
174 : fd_txncache_txnpage_t * data,
175 42274 : int write ) {
176 42274 : long t0 = fd_tickcount();
177 42274 : spill_rw( tc, page*sizeof(*data), (uchar *)data, sizeof(*data), write );
178 42274 : if( write ) FD_ATOMIC_FETCH_AND_ADD( &tc->shmem->page_spilled_cnt, 1UL );
179 14428 : else FD_ATOMIC_FETCH_AND_ADD( &tc->shmem->page_spill_read_cnt, 1UL );
180 42274 : FD_ATOMIC_FETCH_AND_ADD( &tc->shmem->spill_ticks, (ulong)(fd_tickcount()-t0) );
181 42274 : }
182 :
183 : static uint
184 : frame_take( fd_txncache_t * tc,
185 164052137 : uint * victim ) {
186 164052137 : fd_txncache_shmem_t * shmem = tc->shmem;
187 164052137 : *victim = UINT_MAX;
188 164052137 : if( FD_LIKELY( shmem->frames_free_cnt ) ) return tc->frames_free[ --shmem->frames_free_cnt ];
189 492116837 : for( ulong i=0UL; i<2UL*shmem->frames_max; i++ ) {
190 328103815 : uint frame = (uint)shmem->evict_next;
191 328103815 : shmem->evict_next = (shmem->evict_next+1UL)%shmem->frames_max;
192 328103815 : uint owner = tc->frame_page[ frame ];
193 328103815 : FD_TEST( owner!=UINT_MAX );
194 328103815 : fd_txncache_page_meta_t * old = &tc->page_meta[ owner ];
195 328103815 : if( FD_VOLATILE_CONST( old->io ) ) continue;
196 78179 : if( old->ref ) {
197 39085 : old->ref = 0U;
198 39085 : continue;
199 39085 : }
200 39094 : old->frame = UINT_MAX;
201 39094 : shmem->frame_evicted_cnt++;
202 39094 : *victim = owner;
203 39094 : return frame;
204 78179 : }
205 164013022 : return UINT_MAX;
206 164052116 : }
207 :
208 : static inline fd_txncache_txnpage_t *
209 : page_io( fd_txncache_t * tc,
210 : ulong page,
211 2880786478 : int write_lock ) {
212 : /* Fast path: the page is resident. A fetch may publish the frame
213 : concurrently. */
214 2880786478 : fd_txncache_page_meta_t * meta = &tc->page_meta[ page ];
215 2880786478 : uint resident = FD_VOLATILE_CONST( meta->frame );
216 2880931076 : if( FD_LIKELY( resident!=UINT_MAX ) ) {
217 2880931076 : if( FD_UNLIKELY( !meta->ref ) ) meta->ref = 1U;
218 2880931076 : return &tc->txnpages[ resident ];
219 2880931076 : }
220 :
221 : /* Loading changes the frame cache, which needs the write lock. */
222 5 : if( FD_LIKELY( !write_lock ) ) {
223 16102 : FD_ATOMIC_FETCH_AND_ADD( &tc->shmem->page_miss_cnt, 1UL );
224 16102 : return NULL;
225 16102 : }
226 5 : FD_TEST( page<tc->shmem->txnpages_max );
227 :
228 : /* A fetch may be loading or writing back the page. Fetches publish
229 : without the lock, so this does not deadlock. */
230 5 : while( FD_UNLIKELY( FD_VOLATILE_CONST( meta->io ) ) ) FD_SPIN_PAUSE();
231 5 : FD_COMPILER_MFENCE();
232 5 : if( FD_UNLIKELY( meta->frame!=UINT_MAX ) ) {
233 0 : if( FD_UNLIKELY( !meta->ref ) ) meta->ref = 1U;
234 0 : return &tc->txnpages[ meta->frame ];
235 0 : }
236 :
237 5 : uint victim;
238 5 : uint frame;
239 5 : while( FD_UNLIKELY( (frame=frame_take( tc, &victim ))==UINT_MAX ) ) FD_SPIN_PAUSE();
240 5 : if( victim!=UINT_MAX ) {
241 8999 : fd_txncache_page_meta_t * old = &tc->page_meta[ victim ];
242 8999 : if( old->dirty ) {
243 3090 : spill_io( tc, victim, &tc->txnpages[ frame ], 1 );
244 3090 : old->dirty = 0U;
245 3090 : old->disk_valid = 1;
246 3090 : }
247 8999 : }
248 :
249 : /* Read the page back, or start it empty if it was never written. */
250 5 : fd_txncache_txnpage_t * data = &tc->txnpages[ frame ];
251 5 : if( meta->disk_valid ) {
252 9014 : spill_io( tc, page, data, 0 );
253 5 : } else {
254 5 : data->free = FD_TXNCACHE_TXNS_PER_PAGE;
255 5 : }
256 5 : tc->frame_page[ frame ] = (uint)page;
257 5 : meta->frame = frame;
258 5 : meta->ref = 1U;
259 5 : return data;
260 5 : }
261 :
262 : static void
263 : page_fetch( fd_txncache_t * tc,
264 : ulong page,
265 33158 : ulong seq ) {
266 33158 : fd_txncache_page_meta_t * meta = &tc->page_meta[ page ];
267 33158 : lock_write( tc );
268 :
269 : /* page may have been freed, or is already resident. */
270 33158 : if( FD_UNLIKELY( tc->shmem->release_seq!=seq || meta->frame!=UINT_MAX ) ) {
271 0 : fd_rwlock_unwrite( tc->shmem->lock );
272 0 : return;
273 0 : }
274 :
275 : /* Another fetch is moving page. */
276 33158 : if( FD_UNLIKELY( meta->io ) ) {
277 514 : fd_rwlock_unwrite( tc->shmem->lock );
278 38949254 : while( FD_UNLIKELY( FD_VOLATILE_CONST( meta->io ) ) ) FD_SPIN_PAUSE();
279 514 : return;
280 514 : }
281 :
282 : /* Every frame is busy. */
283 32644 : uint victim;
284 32644 : uint frame = frame_take( tc, &victim );
285 32644 : if( FD_UNLIKELY( frame==UINT_MAX ) ) {
286 2549 : fd_rwlock_unwrite( tc->shmem->lock );
287 2549 : FD_SPIN_PAUSE();
288 2549 : return;
289 2549 : }
290 :
291 : /* Mark page and the evicted page busy until i/o finishes. */
292 30095 : fd_txncache_page_meta_t * old = victim!=UINT_MAX ? &tc->page_meta[ victim ] : NULL;
293 30095 : int write = old && old->dirty;
294 30095 : if( write ) {
295 24756 : old->dirty = 0U;
296 24756 : old->io = 1U;
297 24756 : }
298 30095 : int read = meta->disk_valid;
299 30095 : meta->io = 1U;
300 30095 : tc->frame_page[ frame ] = (uint)page;
301 30095 : fd_rwlock_unwrite( tc->shmem->lock );
302 :
303 : /* Write back the evicted page, then read page, without the lock. */
304 30095 : fd_racesan_hook( "txncache:fetch_io" );
305 30095 : fd_txncache_txnpage_t * data = &tc->txnpages[ frame ];
306 30095 : if( write ) spill_io( tc, victim, data, 1 );
307 30095 : if( read ) spill_io( tc, page, data, 0 );
308 24681 : else data->free = FD_TXNCACHE_TXNS_PER_PAGE;
309 :
310 : /* Publish without the lock, as write lock holders may be waiting on
311 : io. io is cleared last. */
312 30095 : if( write ) {
313 24756 : old->disk_valid = 1;
314 24756 : FD_HW_MFENCE_ST();
315 24756 : FD_VOLATILE( old->io ) = 0U;
316 24756 : }
317 30095 : meta->ref = 1U;
318 30095 : FD_HW_MFENCE_ST();
319 30095 : FD_VOLATILE( meta->frame ) = frame;
320 30095 : FD_HW_MFENCE_ST();
321 30095 : FD_VOLATILE( meta->io ) = 0U;
322 30095 : }
323 :
324 : /* A freed logical page must never reload contents from its former owner. */
325 : static void
326 : release_page( fd_txncache_t * tc,
327 48789 : ulong page ) {
328 48789 : fd_txncache_page_meta_t * meta = &tc->page_meta[ page ];
329 48789 : while( FD_UNLIKELY( FD_VOLATILE_CONST( meta->io ) ) ) FD_SPIN_PAUSE();
330 48789 : FD_COMPILER_MFENCE();
331 48789 : tc->shmem->release_seq++;
332 48789 : if( meta->frame!=UINT_MAX ) {
333 24666 : tc->frame_page[ meta->frame ] = UINT_MAX;
334 24666 : tc->frames_free[ tc->shmem->frames_free_cnt++ ] = meta->frame;
335 24666 : meta->frame = UINT_MAX;
336 24666 : }
337 48789 : meta->dirty = 0U;
338 48789 : meta->disk_valid = 0;
339 48789 : meta->next = UINT_MAX;
340 48789 : tc->txnpages_free[ tc->shmem->txnpages_free_cnt++ ] = (uint)page;
341 48789 : }
342 :
343 : void
344 4890 : fd_txncache_reset( fd_txncache_t * tc ) {
345 4890 : lock_write( tc );
346 :
347 4890 : tc->shmem->root_cnt = 0UL;
348 4890 : root_slist_remove_all( tc->shmem->root_ll, tc->blockcache_shmem_pool );
349 :
350 4890 : tc->shmem->txnpages_free_cnt = tc->shmem->txnpages_max;
351 4890 : tc->shmem->frames_free_cnt = tc->shmem->frames_max;
352 4890 : tc->shmem->evict_next = 0UL;
353 4890 : tc->shmem->release_seq++;
354 1269459 : for( ulong i=0UL; i<tc->shmem->txnpages_max; i++ ) {
355 1264569 : while( FD_UNLIKELY( FD_VOLATILE_CONST( tc->page_meta[ i ].io ) ) ) FD_SPIN_PAUSE();
356 1264569 : }
357 4890 : FD_COMPILER_MFENCE();
358 1269459 : for( ulong i=0UL; i<tc->shmem->txnpages_max; i++ ) {
359 1264569 : tc->txnpages_free[ i ] = (uint)i;
360 1264569 : tc->page_meta[ i ].frame = UINT_MAX;
361 1264569 : tc->page_meta[ i ].dirty = 0U;
362 1264569 : tc->page_meta[ i ].disk_valid = 0;
363 1264569 : tc->page_meta[ i ].next = UINT_MAX;
364 1264569 : }
365 1189038 : for( ulong i=0UL; i<tc->shmem->frames_max; i++ ) {
366 1184148 : tc->frame_page[ i ] = UINT_MAX;
367 1184148 : tc->frames_free[ i ] = (uint)i;
368 1184148 : }
369 :
370 4890 : blockcache_pool_reset( tc->blockcache_shmem_pool );
371 4890 : blockhash_map_reset( tc->blockhash_map );
372 :
373 4890 : fd_rwlock_unwrite( tc->shmem->lock );
374 4890 : }
375 :
376 : void
377 : fd_txncache_metrics_snap( fd_txncache_t const * tc,
378 0 : fd_txncache_metrics_t * out ) {
379 0 : fd_txncache_shmem_t const * shmem = tc->shmem;
380 0 : out->page_miss_cnt = FD_VOLATILE_CONST( shmem->page_miss_cnt );
381 0 : out->page_spilled_cnt = FD_VOLATILE_CONST( shmem->page_spilled_cnt );
382 0 : out->page_spill_read_cnt = FD_VOLATILE_CONST( shmem->page_spill_read_cnt );
383 0 : out->frame_evicted_cnt = FD_VOLATILE_CONST( shmem->frame_evicted_cnt );
384 0 : out->spill_ticks = FD_VOLATILE_CONST( shmem->spill_ticks );
385 0 : out->lock_read_wait_ticks = FD_VOLATILE_CONST( shmem->lock_read_wait_ticks );
386 0 : out->lock_write_wait_ticks = FD_VOLATILE_CONST( shmem->lock_write_wait_ticks );
387 0 : out->page_capacity = shmem->txnpages_max;
388 0 : out->page_used = shmem->txnpages_max-fd_ulong_min( FD_VOLATILE_CONST( shmem->txnpages_free_cnt ), shmem->txnpages_max );
389 0 : out->frame_capacity = shmem->frames_max;
390 0 : out->frame_used = shmem->frames_max-fd_ulong_min( FD_VOLATILE_CONST( shmem->frames_free_cnt ), shmem->frames_max );
391 0 : }
392 :
393 : FD_STATIC_ASSERT( !(sizeof(fd_txncache_txnpage_t)%FD_TXNCACHE_SNAPIN_STAGE_ALIGN), snapin_stage_align );
394 :
395 : void
396 : fd_txncache_snapin_stage( fd_txncache_t * tc,
397 : ulong off,
398 : void * buf,
399 : ulong sz,
400 108 : int write ) {
401 108 : FD_TEST_ERR( tc->shmem->txnpages_free_cnt==tc->shmem->txnpages_max );
402 108 : FD_TEST( fd_ulong_is_aligned( off, FD_TXNCACHE_SNAPIN_STAGE_ALIGN ) );
403 108 : FD_TEST( fd_ulong_is_aligned( sz, FD_TXNCACHE_SNAPIN_STAGE_ALIGN ) );
404 108 : FD_TEST( fd_ulong_is_aligned( (ulong)buf, FD_TXNCACHE_SNAPIN_STAGE_ALIGN ) );
405 :
406 108 : ulong mem_sz = tc->shmem->frames_max*sizeof(fd_txncache_txnpage_t);
407 108 : uchar * p = (uchar *)buf;
408 108 : if( off<mem_sz ) {
409 102 : ulong n = fd_ulong_min( sz, mem_sz-off );
410 102 : uchar * mem = (uchar *)tc->txnpages+off;
411 102 : if( write ) fd_memcpy( mem, p, n );
412 48 : else fd_memcpy( p, mem, n );
413 102 : off += n;
414 102 : p += n;
415 102 : sz -= n;
416 102 : }
417 108 : if( sz ) spill_rw( tc, off-mem_sz, p, sz, write );
418 108 : }
419 :
420 : FD_FN_PURE static inline ulong
421 : fd_txncache_bucket( fd_txncache_t const * tc,
422 34345620 : uchar const * txnhash ) {
423 34345620 : return fd_ulong_hash( FD_LOAD( ulong, txnhash )^tc->shmem->seed )%tc->shmem->bucket_cnt;
424 34345620 : }
425 :
426 : /* Returns a resident page of blockcache with room, with its index in
427 : *out_idx. Without the write lock, returns NULL with the index of a
428 : page of blockcache to fetch in *out_idx, or ULONG_MAX if the caller
429 : must retry under the write lock. */
430 :
431 : static fd_txncache_txnpage_t *
432 : fd_txncache_ensure_txnpage( fd_txncache_t * tc,
433 : blockcache_t * blockcache,
434 : int write,
435 22919586 : ulong * out_idx ) {
436 22919586 : fd_txncache_shmem_t * shmem = tc->shmem;
437 22919586 : for(;;) {
438 : /* Use the newest page if it has room. */
439 22919586 : uint head = FD_VOLATILE_CONST( blockcache->shmem->page_head );
440 22919586 : if( FD_LIKELY( head!=UINT_MAX ) ) {
441 22869534 : fd_txncache_txnpage_t * page = page_io( tc, head, write );
442 22869534 : *out_idx = head;
443 22869534 : if( FD_UNLIKELY( !page ) ) return NULL;
444 22863366 : if( FD_LIKELY( FD_VOLATILE_CONST( page->free ) ) ) return page;
445 22863366 : }
446 :
447 : /* Need a new page. Try to make sure we can safely take the lock
448 : and that there's enough capacity. */
449 53937 : if( !write ) {
450 52374 : fd_rwlock_write( shmem->alloc_lock );
451 52374 : if( FD_UNLIKELY( FD_VOLATILE_CONST( blockcache->shmem->page_head )!=head ) ) {
452 0 : fd_rwlock_unwrite( shmem->alloc_lock );
453 0 : continue;
454 0 : }
455 52374 : }
456 :
457 53937 : ulong page_cnt = blockcache->shmem->pages_cnt;
458 53937 : if( FD_UNLIKELY( page_cnt>=shmem->txnpages_per_blockhash_max || !shmem->txnpages_free_cnt ) ) {
459 3120 : if( !write ) fd_rwlock_unwrite( shmem->alloc_lock );
460 3120 : *out_idx = ULONG_MAX;
461 3120 : return NULL;
462 3120 : }
463 :
464 : /* We have safe and exclusive access to allocating a new page.
465 : Without the write lock and a free frame, the page is allocated
466 : not resident, and fetching it starts it empty. */
467 50817 : ulong idx = tc->txnpages_free[ --shmem->txnpages_free_cnt ];
468 50817 : fd_txncache_txnpage_t * page = NULL;
469 50817 : if( write ) {
470 3 : page = page_io( tc, idx, 1 );
471 50814 : } else if( FD_LIKELY( shmem->frames_free_cnt ) ) {
472 26133 : uint frame = tc->frames_free[ --shmem->frames_free_cnt ];
473 26133 : tc->frame_page[ frame ] = (uint)idx;
474 26133 : tc->page_meta[ idx ].frame = frame;
475 26133 : page = &tc->txnpages[ frame ];
476 26133 : }
477 50817 : if( FD_LIKELY( page ) ) page->free = FD_TXNCACHE_TXNS_PER_PAGE;
478 50817 : tc->page_meta[ idx ].next = head;
479 50817 : blockcache->shmem->pages_cnt = page_cnt+1UL;
480 50817 : FD_HW_MFENCE_ST();
481 50817 : FD_VOLATILE( blockcache->shmem->page_head ) = (uint)idx;
482 50817 : if( !write ) fd_rwlock_unwrite( shmem->alloc_lock );
483 50817 : *out_idx = idx;
484 50817 : return page;
485 53937 : }
486 22919586 : }
487 :
488 : static inline int
489 : fd_txncache_insert_txn( fd_txncache_t * tc,
490 : blockcache_t * blockcache,
491 : fd_txncache_txnpage_t * txnpage,
492 : ulong txnpage_idx,
493 : fd_txncache_fork_id_t fork_id,
494 22885617 : uchar const * txnhash ) {
495 22885617 : for(;;) {
496 22885617 : ushort txnpage_free = txnpage->free;
497 22885617 : if( FD_UNLIKELY( !txnpage_free ) ) return 0;
498 22885617 : if( FD_UNLIKELY( FD_ATOMIC_CAS( &txnpage->free, txnpage_free, txnpage_free-1UL )!=txnpage_free ) ) {
499 0 : FD_SPIN_PAUSE();
500 0 : continue;
501 0 : }
502 :
503 22885617 : ulong txn_idx = FD_TXNCACHE_TXNS_PER_PAGE-txnpage_free;
504 22885617 : ulong txnhash_offset = blockcache->shmem->txnhash_offset;
505 22885617 : memcpy( txnpage->txns[ txn_idx ]->txnhash, txnhash+txnhash_offset, 20UL );
506 22885617 : txnpage->txns[ txn_idx ]->fork_id = fork_id;
507 22885617 : txnpage->txns[ txn_idx ]->generation = tc->blockcache_pool[ fork_id.val ].shmem->generation;
508 22885617 : FD_COMPILER_MFENCE();
509 :
510 22885617 : ulong txn_bucket = fd_txncache_bucket( tc, txnhash+txnhash_offset );
511 22885617 : for(;;) {
512 22885617 : uint head = blockcache->heads[ txn_bucket ];
513 22885617 : txnpage->txns[ txn_idx ]->blockcache_next = head;
514 22885617 : FD_COMPILER_MFENCE();
515 22885617 : if( FD_LIKELY( FD_ATOMIC_CAS( &blockcache->heads[ txn_bucket ], head, (uint)(FD_TXNCACHE_TXNS_PER_PAGE*txnpage_idx+txn_idx) )==head ) ) break;
516 0 : FD_SPIN_PAUSE();
517 0 : }
518 :
519 : /* This races, but we expect x86 stores to be atomic. */
520 22885617 : if( FD_UNLIKELY( !tc->page_meta[ txnpage_idx ].dirty ) ) tc->page_meta[ txnpage_idx ].dirty = 1U;
521 22885617 : return 1;
522 22885617 : }
523 22885617 : }
524 :
525 : fd_txncache_fork_id_t
526 : fd_txncache_attach_child( fd_txncache_t * tc,
527 75186 : fd_txncache_fork_id_t parent_fork_id ) {
528 75186 : lock_write( tc );
529 :
530 75186 : FD_TEST( blockcache_pool_free( tc->blockcache_shmem_pool ) );
531 75186 : ulong idx = blockcache_pool_idx_acquire( tc->blockcache_shmem_pool );
532 :
533 75186 : blockcache_t * fork = &tc->blockcache_pool[ idx ];
534 75186 : fd_txncache_fork_id_t fork_id = { .val = (ushort)idx };
535 :
536 75186 : fork->shmem->generation = tc->shmem->blockcache_generation++;
537 75186 : fork->shmem->child_id = (fd_txncache_fork_id_t){ .val = USHORT_MAX };
538 :
539 75186 : if( FD_LIKELY( parent_fork_id.val==USHORT_MAX ) ) {
540 4956 : FD_TEST( blockcache_pool_free( tc->blockcache_shmem_pool )==blockcache_pool_max( tc->blockcache_shmem_pool )-1UL );
541 4956 : fork->shmem->parent_id = (fd_txncache_fork_id_t){ .val = USHORT_MAX };
542 4956 : fork->shmem->sibling_id = (fd_txncache_fork_id_t){ .val = USHORT_MAX };
543 :
544 4956 : descends_set_null( fork->descends );
545 4956 : root_slist_ele_push_tail( tc->shmem->root_ll, fork->shmem, tc->blockcache_shmem_pool );
546 70230 : } else {
547 70230 : blockcache_t * parent = &tc->blockcache_pool[ parent_fork_id.val ];
548 : /* We might be tempted to freeze the parent here, and it's valid to
549 : do this ordinarily, but not when loading from a snapshot, when
550 : we need to load many transactions into a root parent chain at
551 : once. */
552 70230 : fork->shmem->sibling_id = parent->shmem->child_id;
553 70230 : fork->shmem->parent_id = parent_fork_id;
554 70230 : parent->shmem->child_id = fork_id;
555 :
556 70230 : descends_set_copy( fork->descends, parent->descends );
557 70230 : descends_set_insert( fork->descends, parent_fork_id.val );
558 70230 : }
559 :
560 75186 : fork->shmem->txnhash_offset = 0UL;
561 75186 : fork->shmem->frozen = 0;
562 75186 : memset( fork->heads, 0xFF, tc->shmem->bucket_cnt*sizeof(uint) );
563 75186 : fork->shmem->pages_cnt = 0UL;
564 75186 : fork->shmem->page_head = UINT_MAX;
565 :
566 75186 : fd_rwlock_unwrite( tc->shmem->lock );
567 75186 : return fork_id;
568 75186 : }
569 :
570 : void
571 : fd_txncache_attach_blockhash( fd_txncache_t * tc,
572 : fd_txncache_fork_id_t fork_id,
573 54 : uchar const * blockhash ) {
574 54 : lock_write( tc );
575 :
576 54 : blockcache_t * fork = &tc->blockcache_pool[ fork_id.val ];
577 54 : FD_TEST( !fork->shmem->frozen );
578 54 : fork->shmem->frozen = 1;
579 :
580 54 : memcpy( fork->shmem->blockhash.uc, blockhash, 32UL );
581 :
582 54 : blockhash_map_ele_insert( tc->blockhash_map, fork->shmem, tc->blockcache_shmem_pool );
583 :
584 54 : fd_rwlock_unwrite( tc->shmem->lock );
585 54 : }
586 :
587 : void
588 : fd_txncache_finalize_fork( fd_txncache_t * tc,
589 : fd_txncache_fork_id_t fork_id,
590 : ulong txnhash_offset,
591 60945 : uchar const * blockhash ) {
592 60945 : lock_write( tc );
593 :
594 60945 : blockcache_t * fork = &tc->blockcache_pool[ fork_id.val ];
595 60945 : FD_TEST( fork->shmem->frozen<=1 );
596 60945 : FD_TEST( fork->shmem->frozen>=0 );
597 60945 : fork->shmem->txnhash_offset = txnhash_offset;
598 :
599 60945 : memcpy( fork->shmem->blockhash.uc, blockhash, 32UL );
600 :
601 60945 : if( FD_LIKELY( !fork->shmem->frozen ) ) blockhash_map_ele_insert( tc->blockhash_map, fork->shmem, tc->blockcache_shmem_pool );
602 60945 : fork->shmem->frozen = 2;
603 :
604 60945 : fd_rwlock_unwrite( tc->shmem->lock );
605 60945 : }
606 :
607 : static inline void
608 : remove_blockcache( fd_txncache_t * tc,
609 58875 : blockcache_t * blockcache ) {
610 58875 : FD_TEST( blockcache->shmem->frozen>=0 );
611 107169 : for( uint page=blockcache->shmem->page_head; page!=UINT_MAX; ) {
612 48294 : uint next = tc->page_meta[ page ].next;
613 48294 : release_page( tc, page );
614 48294 : page = next;
615 48294 : }
616 58875 : blockcache->shmem->page_head = UINT_MAX;
617 58875 : blockcache->shmem->pages_cnt = 0UL;
618 :
619 58875 : ulong idx = blockcache_pool_idx( tc->blockcache_shmem_pool, blockcache->shmem );
620 9267837 : for( ulong i=0UL; i<tc->shmem->active_slots_max; i++ ) descends_set_remove( tc->blockcache_pool[ i ].descends, idx );
621 :
622 58875 : if( FD_LIKELY( blockcache->shmem->frozen ) ) blockhash_map_ele_remove_fast( tc->blockhash_map, blockcache->shmem, tc->blockcache_shmem_pool );
623 58875 : blockcache->shmem->frozen = -1;
624 58875 : blockcache_pool_ele_release( tc->blockcache_shmem_pool, blockcache->shmem );
625 58875 : }
626 :
627 : static inline void
628 : remove_children( fd_txncache_t * tc,
629 : blockcache_t const * fork,
630 62868 : blockcache_t const * except ) {
631 62868 : fd_txncache_fork_id_t sibling_idx = fork->shmem->child_id;
632 118017 : while( sibling_idx.val!=USHORT_MAX ) {
633 55149 : blockcache_t * sibling = &tc->blockcache_pool[ sibling_idx.val ];
634 :
635 55149 : sibling_idx = sibling->shmem->sibling_id;
636 55149 : if( FD_UNLIKELY( sibling==except ) ) continue;
637 :
638 1998 : remove_children( tc, sibling, except );
639 1998 : remove_blockcache( tc, sibling );
640 1998 : }
641 62868 : }
642 :
643 : void
644 : fd_txncache_cancel_fork( fd_txncache_t * tc,
645 7719 : fd_txncache_fork_id_t fork_id ) {
646 7719 : lock_write( tc );
647 7719 : blockcache_t * fork = &tc->blockcache_pool[ fork_id.val ];
648 7719 : FD_TEST( fork->shmem->parent_id.val!=USHORT_MAX );
649 :
650 : /* The soon-to-be-pruned subtree must be unrooted. */
651 7719 : fd_txncache_blockcache_shmem_t const * latest_root = root_slist_ele_peek_tail_const( tc->shmem->root_ll, tc->blockcache_shmem_pool );
652 7719 : FD_TEST( latest_root );
653 7719 : FD_TEST( descends_set_test( fork->descends, blockcache_pool_idx( tc->blockcache_shmem_pool, latest_root ) ) );
654 :
655 7719 : remove_children( tc, fork, NULL );
656 7719 : remove_blockcache( tc, fork );
657 7719 : ushort * fork_id_p = &(tc->blockcache_pool[ fork->shmem->parent_id.val ].shmem->child_id.val);
658 7725 : while( *fork_id_p!=fork_id.val ) {
659 6 : fork_id_p = &(tc->blockcache_pool[ *fork_id_p ].shmem->sibling_id.val);
660 6 : }
661 7719 : *fork_id_p = fork->shmem->sibling_id.val;
662 7719 : fd_rwlock_unwrite( tc->shmem->lock );
663 7719 : }
664 :
665 : void
666 : fd_txncache_advance_root( fd_txncache_t * tc,
667 53151 : fd_txncache_fork_id_t fork_id ) {
668 53151 : lock_write( tc );
669 :
670 53151 : blockcache_t * fork = &tc->blockcache_pool[ fork_id.val ];
671 53151 : FD_TEST( fork->shmem->parent_id.val!=USHORT_MAX );
672 :
673 53151 : blockcache_t * parent_fork = &tc->blockcache_pool[ fork->shmem->parent_id.val ];
674 53151 : if( FD_UNLIKELY( root_slist_ele_peek_tail( tc->shmem->root_ll, tc->blockcache_shmem_pool )!=parent_fork->shmem ) ) {
675 0 : FD_BASE58_ENCODE_32_BYTES( parent_fork->shmem->blockhash.uc, parent_blockhash_b58 );
676 0 : FD_BASE58_ENCODE_32_BYTES( fork->shmem->blockhash.uc, fork_blockhash_b58 );
677 0 : FD_BASE58_ENCODE_32_BYTES( root_slist_ele_peek_tail( tc->shmem->root_ll, tc->blockcache_shmem_pool )->blockhash.uc, root_blockhash_b58 );
678 0 : FD_LOG_CRIT(( "advancing root from %s to %s but that is not valid, last root is %s",
679 0 : parent_blockhash_b58,
680 0 : fork_blockhash_b58,
681 0 : root_blockhash_b58 ));
682 0 : }
683 :
684 53151 : FD_BASE58_ENCODE_32_BYTES( parent_fork->shmem->blockhash.uc, parent_blockhash_b58 );
685 53151 : FD_BASE58_ENCODE_32_BYTES( fork->shmem->blockhash.uc, fork_blockhash_b58 );
686 53151 : FD_LOG_DEBUG(( "advancing root from %s to %s",
687 53151 : parent_blockhash_b58,
688 53151 : fork_blockhash_b58 ));
689 :
690 : /* When a fork is rooted, any competing forks can be immediately
691 : removed as they will not be needed again. This includes child
692 : forks of the pruned siblings as well. */
693 53151 : remove_children( tc, parent_fork, fork );
694 53151 : parent_fork->shmem->child_id = fork_id;
695 53151 : fork->shmem->sibling_id = (fd_txncache_fork_id_t){ .val = USHORT_MAX };
696 :
697 : /* Now, the earliest known rooted fork can likely be removed since its
698 : blockhashes cannot be referenced anymore (they are older than 151
699 : blockhashes away). */
700 53151 : tc->shmem->root_cnt++;
701 53151 : root_slist_ele_push_tail( tc->shmem->root_ll, fork->shmem, tc->blockcache_shmem_pool );
702 53151 : if( FD_LIKELY( tc->shmem->root_cnt>FD_TXNCACHE_MAX_BLOCKHASH_DISTANCE ) ) {
703 49158 : fd_txncache_blockcache_shmem_t * old_root_shmem = root_slist_ele_pop_head( tc->shmem->root_ll, tc->blockcache_shmem_pool );
704 49158 : FD_TEST( old_root_shmem );
705 49158 : blockcache_t * old_root = &tc->blockcache_pool[ blockcache_pool_idx( tc->blockcache_shmem_pool, old_root_shmem ) ];
706 :
707 49158 : root_slist_ele_peek_head( tc->shmem->root_ll, tc->blockcache_shmem_pool )->parent_id.val = USHORT_MAX;
708 :
709 49158 : remove_blockcache( tc, old_root );
710 49158 : tc->shmem->root_cnt--;
711 49158 : }
712 :
713 53151 : fd_rwlock_unwrite( tc->shmem->lock );
714 53151 : }
715 :
716 : static inline blockcache_t *
717 : blockhash_on_fork( fd_txncache_t * tc,
718 : blockcache_t const * fork,
719 28602573 : uchar const * blockhash ) {
720 28602573 : fd_txncache_blockcache_shmem_t const * candidate = blockhash_map_ele_query_const( tc->blockhash_map, fd_type_pun_const( blockhash ), NULL, tc->blockcache_shmem_pool );
721 28602573 : if( FD_UNLIKELY( !candidate ) ) return NULL;
722 :
723 28602573 : while( candidate ) {
724 28602573 : ulong candidate_idx = blockcache_pool_idx( tc->blockcache_shmem_pool, candidate );
725 28602573 : if( FD_LIKELY( descends_set_test( fork->descends, candidate_idx ) ) ) return &tc->blockcache_pool[ candidate_idx ];
726 0 : candidate = blockhash_map_ele_next_const( candidate, NULL, tc->blockcache_shmem_pool );
727 0 : }
728 0 : return NULL;
729 28602573 : }
730 :
731 : static void
732 : purge_stale_on_blockcache( fd_txncache_t * tc,
733 7071 : blockcache_t * blockcache ) {
734 7071 : FD_TEST( blockcache->shmem->frozen>=0 );
735 7071 : memset( tc->scratch_heads, 0xFF, tc->shmem->bucket_cnt*sizeof(tc->scratch_heads[ 0 ]) );
736 7071 : uint scratch_page_head = UINT_MAX;
737 7071 : ulong scratch_pages_cnt = 0UL;
738 7071 : ulong scratch_txnpage_idx = ULONG_MAX;
739 7071 : tc->scratch_txnpage->free = 0;
740 7071 : uint next_txnpage_idx = blockcache->shmem->page_head;
741 9822 : while( next_txnpage_idx!=UINT_MAX ) {
742 2751 : ulong curr_txnpage_idx = next_txnpage_idx;
743 2751 : next_txnpage_idx = tc->page_meta[ curr_txnpage_idx ].next;
744 2751 : fd_txncache_txnpage_t * input = tc->scratch_input;
745 2751 : memcpy( input, page_io( tc, curr_txnpage_idx, 1 ), sizeof(*input) );
746 2751 : ulong curr_txn_cnt = FD_TXNCACHE_TXNS_PER_PAGE-input->free;
747 22538943 : for( ulong j=0UL; j<curr_txn_cnt; j++ ) {
748 22536192 : fd_txncache_single_txn_t const * curr_txn = input->txns[ curr_txn_cnt-j-1UL ];
749 22536192 : blockcache_t const * txn_fork = &tc->blockcache_pool[ curr_txn->fork_id.val ];
750 22536192 : if( FD_LIKELY( txn_fork->shmem->frozen>=0 && txn_fork->shmem->generation==curr_txn->generation ) ) {
751 : /* Valid transaction. Keep. */
752 5778486 : if( FD_UNLIKELY( !tc->scratch_txnpage->free ) ) {
753 2256 : FD_TEST( scratch_txnpage_idx!=curr_txnpage_idx );
754 2256 : if( FD_LIKELY( scratch_txnpage_idx!=ULONG_MAX ) ) {
755 240 : memcpy( page_io( tc, scratch_txnpage_idx, 1 ), tc->scratch_txnpage, sizeof(fd_txncache_txnpage_t) );
756 240 : tc->page_meta[ scratch_txnpage_idx ].dirty = 1U;
757 240 : }
758 2256 : scratch_txnpage_idx = curr_txnpage_idx;
759 2256 : tc->scratch_txnpage->free = FD_TXNCACHE_TXNS_PER_PAGE;
760 2256 : tc->page_meta[ scratch_txnpage_idx ].next = scratch_page_head;
761 2256 : scratch_page_head = (uint)scratch_txnpage_idx;
762 2256 : scratch_pages_cnt++;
763 2256 : }
764 5778486 : ulong txn_idx = FD_TXNCACHE_TXNS_PER_PAGE-tc->scratch_txnpage->free;
765 5778486 : memcpy( tc->scratch_txnpage->txns[ txn_idx ], curr_txn, sizeof(*curr_txn) );
766 5778486 : ulong txn_bucket = fd_txncache_bucket( tc, curr_txn->txnhash );
767 5778486 : uint head = tc->scratch_heads[ txn_bucket ];
768 5778486 : tc->scratch_txnpage->txns[ txn_idx ]->blockcache_next = head;
769 5778486 : ulong txn_gidx = FD_TXNCACHE_TXNS_PER_PAGE*scratch_txnpage_idx+txn_idx;
770 5778486 : FD_TEST( txn_gidx<UINT_MAX );
771 5778486 : tc->scratch_heads[ txn_bucket ] = (uint)txn_gidx;
772 5778486 : tc->scratch_txnpage->free--;
773 16757706 : } else {
774 : /* Stale transaction. Drop. */
775 16757706 : continue;
776 16757706 : }
777 22536192 : }
778 2751 : if( FD_UNLIKELY( curr_txnpage_idx!=scratch_txnpage_idx ) ) {
779 : /* The txnpage is not being used for compaction, free it up. */
780 495 : release_page( tc, curr_txnpage_idx );
781 495 : }
782 2751 : }
783 7071 : if( FD_LIKELY( scratch_txnpage_idx!=ULONG_MAX ) ) {
784 2016 : memcpy( page_io( tc, scratch_txnpage_idx, 1 ), tc->scratch_txnpage, sizeof(fd_txncache_txnpage_t) );
785 2016 : tc->page_meta[ scratch_txnpage_idx ].dirty = 1U;
786 2016 : }
787 7071 : blockcache->shmem->page_head = scratch_page_head;
788 7071 : blockcache->shmem->pages_cnt = scratch_pages_cnt;
789 7071 : memcpy( blockcache->heads, tc->scratch_heads, tc->shmem->bucket_cnt*sizeof(blockcache->heads[0]) );
790 7071 : }
791 :
792 : static void
793 : purge_stale_on_fork( fd_txncache_t * tc,
794 7071 : blockcache_t * fork ) {
795 7071 : purge_stale_on_blockcache( tc, fork );
796 :
797 7071 : fd_txncache_fork_id_t sibling_idx = fork->shmem->child_id;
798 12582 : while( sibling_idx.val!=USHORT_MAX ) {
799 5511 : blockcache_t * sibling = &tc->blockcache_pool[ sibling_idx.val ];
800 5511 : purge_stale_on_fork( tc, sibling );
801 5511 : sibling_idx = sibling->shmem->sibling_id;
802 5511 : }
803 7071 : }
804 :
805 : static void
806 1560 : purge_stale( fd_txncache_t * tc ) {
807 1560 : fd_txncache_blockcache_shmem_t * root_shmem = root_slist_ele_peek_head( tc->shmem->root_ll, tc->blockcache_shmem_pool );
808 1560 : FD_TEST( root_shmem );
809 1560 : blockcache_t * root = &tc->blockcache_pool[ blockcache_pool_idx( tc->blockcache_shmem_pool, root_shmem ) ];
810 1560 : ulong free_before = tc->shmem->txnpages_free_cnt;
811 : /* One might think that an optimization here is to stop the descent on
812 : the latest rooted blockcache. There could be no pruned minority
813 : forks from that point on. As a result, there could be no stale
814 : transactions in any blockcache descending from that.
815 : Unfortunately, frontier eviction means that any blockcache in the
816 : fork tree can have stale transactions. */
817 1560 : purge_stale_on_fork( tc, root );
818 1560 : FD_LOG_WARNING(( "purge_stale: txnpages_free %lu -> %lu", free_before, tc->shmem->txnpages_free_cnt ));
819 1560 : }
820 :
821 : void
822 : fd_txncache_insert( fd_txncache_t * tc,
823 : fd_txncache_fork_id_t fork_id,
824 : uchar const * blockhash,
825 22885617 : uchar const * txnhash ) {
826 22885617 : long t0 = fd_tickcount();
827 22885617 : lock_read( tc );
828 :
829 22885617 : blockcache_t const * fork = &tc->blockcache_pool[ fork_id.val ];
830 22885617 : FD_TEST( fork->shmem->frozen<=1 );
831 22885617 : FD_TEST( fork->shmem->frozen>=0 );
832 22885617 : blockcache_t * blockcache = blockhash_on_fork( tc, fork, blockhash );
833 22885617 : FD_TEST( blockcache );
834 :
835 22885617 : ulong fetched_idx = ULONG_MAX;
836 22885617 : int miss = 0;
837 22885617 : int write = 0;
838 22913400 : for(;;) {
839 : /* First look for an in-memory page (write==0) */
840 22913400 : ulong txnpage_idx;
841 22913400 : fd_txncache_txnpage_t * txnpage = fd_txncache_ensure_txnpage( tc, blockcache, 0, &txnpage_idx );
842 22913400 : if( FD_UNLIKELY( !txnpage && txnpage_idx!=ULONG_MAX ) ) miss = 1;
843 22913400 : if( FD_UNLIKELY( !txnpage && txnpage_idx!=ULONG_MAX && txnpage_idx!=fetched_idx ) ) {
844 : /* The page is not resident. Load it without blocking others. */
845 27783 : ulong seq = tc->shmem->release_seq;
846 27783 : fd_rwlock_unread( tc->shmem->lock );
847 27783 : page_fetch( tc, txnpage_idx, seq );
848 27783 : fetched_idx = txnpage_idx;
849 27783 : lock_read( tc );
850 27783 : blockcache = blockhash_on_fork( tc, fork, blockhash );
851 27783 : FD_TEST( blockcache );
852 27783 : continue;
853 27783 : }
854 22885617 : if( FD_UNLIKELY( !txnpage ) ) {
855 : /* The page was evicted again after fetching it without the lock,
856 : or no page is available. Retry under the write lock, which
857 : loads pages in place.
858 :
859 : Because of sizing invariants when creating the structure, it is
860 : not typically possible to fill it, unless there are stale
861 : transactions from minority forks that were purged floating
862 : around, in which case we can purge them here and try again.
863 : Under the write lock there are no concurrent allocators, so a
864 : page still unavailable after the purge means the cache is
865 : undersized for the caller's usage. */
866 4626 : fd_rwlock_unread( tc->shmem->lock );
867 4626 : lock_write( tc );
868 4626 : blockcache = blockhash_on_fork( tc, fork, blockhash );
869 4626 : FD_TEST( blockcache );
870 : /* Load or allocate a frame under exclusive access. */
871 4626 : txnpage = fd_txncache_ensure_txnpage( tc, blockcache, 1, &txnpage_idx );
872 4626 : if( FD_UNLIKELY( !txnpage ) ) {
873 : /* Slow path: there is no available txnpage in-memory or on
874 : disk. Try to purge any stale entries and try one more time.
875 : If that fails, capacity has been reached. */
876 1560 : purge_stale( tc );
877 1560 : txnpage = fd_txncache_ensure_txnpage( tc, blockcache, 1, &txnpage_idx );
878 1560 : if( FD_UNLIKELY( !txnpage ) )
879 0 : FD_LOG_ERR(( "txncache full after purging stale entries: blockcache holds %lu of %lu txnpages, pool has %lu of %lu free (active_slots_max=%lu txn_per_slot_max=%lu)",
880 1560 : blockcache->shmem->pages_cnt, tc->shmem->txnpages_per_blockhash_max,
881 1560 : tc->shmem->txnpages_free_cnt, tc->shmem->txnpages_max,
882 1560 : tc->shmem->active_slots_max, tc->shmem->txn_per_slot_max ));
883 1560 : }
884 4626 : FD_TEST( fd_txncache_insert_txn( tc, blockcache, txnpage, txnpage_idx, fork_id, txnhash ) );
885 4626 : write = 1;
886 4626 : break;
887 4626 : }
888 :
889 : /* Fast path: insert the transaction into an in-memory page. */
890 22880991 : int success = fd_txncache_insert_txn( tc, blockcache, txnpage, txnpage_idx, fork_id, txnhash );
891 22880991 : if( FD_LIKELY( success ) ) break;
892 :
893 0 : FD_SPIN_PAUSE();
894 0 : }
895 :
896 22885617 : if( FD_UNLIKELY( write ) ) fd_rwlock_unwrite( tc->shmem->lock );
897 22880991 : else fd_rwlock_unread ( tc->shmem->lock );
898 :
899 22885617 : tc->metrics->insert_miss_cnt += (ulong)miss;
900 22885617 : tc->metrics->stalled_cnt += (ulong)write;
901 22885617 : fd_histf_sample( tc->metrics->insert_ticks, (ulong)(fd_tickcount()-t0) );
902 22885617 : }
903 :
904 : int
905 : fd_txncache_query( fd_txncache_t * tc,
906 : fd_txncache_fork_id_t fork_id,
907 : uchar const * blockhash,
908 5681517 : uchar const * txnhash ) {
909 5681517 : long t0 = fd_tickcount();
910 : /* Prevent fork removal and pruning while allowing inserts. */
911 5681517 : lock_read( tc );
912 :
913 : /* Find the blockhash cache visible from the requested fork. */
914 5681517 : blockcache_t const * fork = &tc->blockcache_pool[ fork_id.val ];
915 5681517 : FD_TEST( fork->shmem->frozen>=0 );
916 5681517 : blockcache_t const * blockcache = blockhash_on_fork( tc, fork, blockhash );
917 5681517 : FD_TEST( blockcache );
918 5681517 : FD_TEST( blockcache->shmem->frozen==2 );
919 :
920 5681517 : int found = 0;
921 5681517 : int write = 0;
922 5681517 : int miss = 0;
923 5681517 : ulong fetch_cnt = 0UL;
924 :
925 : /* Select the bucket using the stored portion of the txn hash. */
926 5681517 : ulong txnhash_offset = blockcache->shmem->txnhash_offset;
927 5681517 : ulong head_hash = fd_txncache_bucket( tc, txnhash+txnhash_offset );
928 : /* Walk the bucket's list. UINT_MAX marks the end. */
929 157977294 : for( uint head=blockcache->heads[ head_hash ]; head!=UINT_MAX; ) {
930 : /* If the page is not in-memory, try to load it optimistically
931 : without the lock and check again. Try fetching upto the number
932 : of pages in the blockcache, or frames if fewer; if there's a lot
933 : of contention, fall back to use the write-lock to do the i/o. On
934 : average, this means that we will try to do 1 optimsitic load
935 : before falling back to the write lock. */
936 157975536 : fd_txncache_txnpage_t * page = page_io( tc, head/FD_TXNCACHE_TXNS_PER_PAGE, write );
937 157975536 : if( FD_UNLIKELY( !page ) ) {
938 3030 : miss = 1;
939 3030 : ulong seq = tc->shmem->release_seq;
940 3030 : int fetch = fetch_cnt<fd_ulong_min( blockcache->shmem->pages_cnt, tc->shmem->frames_max );
941 3030 : fd_rwlock_unread( tc->shmem->lock );
942 3030 : if( FD_LIKELY( fetch ) ) {
943 : /* Optimistically do I/O without the lock and check again. */
944 1545 : page_fetch( tc, head/FD_TXNCACHE_TXNS_PER_PAGE, seq );
945 1545 : fetch_cnt++;
946 1545 : lock_read( tc );
947 1545 : } else {
948 : /* Acquire write lock to load the page in place. */
949 1485 : lock_write( tc );
950 1485 : write = 1;
951 1485 : }
952 3030 : blockcache = blockhash_on_fork( tc, fork, blockhash );
953 3030 : FD_TEST( blockcache );
954 3030 : head = blockcache->heads[ head_hash ];
955 3030 : continue;
956 3030 : }
957 157972506 : fd_txncache_single_txn_t const * txn = page->txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ];
958 157972506 : head = txn->blockcache_next;
959 :
960 : /* Accept transactions from this fork or its ancestors. Reject
961 : removed forks and old records whose fork ID has been reused. */
962 157972506 : blockcache_t const * txn_fork = &tc->blockcache_pool[ txn->fork_id.val ];
963 157972506 : int descends = (txn->fork_id.val==fork_id.val || descends_set_test( fork->descends, txn->fork_id.val )) && txn_fork->shmem->frozen>=0 && txn_fork->shmem->generation==txn->generation;
964 : /* Different hashes can share a bucket, so compare the stored hash. */
965 157972506 : if( FD_LIKELY( descends && !memcmp( txnhash+txnhash_offset, txn->txnhash, 20UL ) ) ) {
966 5679759 : found = 1;
967 5679759 : break;
968 5679759 : }
969 157972506 : }
970 :
971 5681517 : if( FD_UNLIKELY( write ) ) fd_rwlock_unwrite( tc->shmem->lock );
972 5680032 : else fd_rwlock_unread ( tc->shmem->lock );
973 :
974 5681517 : tc->metrics->query_miss_cnt += (ulong)miss;
975 5681517 : tc->metrics->stalled_cnt += (ulong)write;
976 5681517 : fd_histf_sample( tc->metrics->query_ticks, (ulong)(fd_tickcount()-t0) );
977 5681517 : return found;
978 5681517 : }
979 :
980 : ulong
981 : fd_txncache_snapshot_roots( fd_txncache_t * tc,
982 90599 : fd_txncache_root_t * out ) {
983 90599 : lock_read( tc );
984 90599 : ulong cnt = 0UL;
985 90599 : for( ulong it = root_slist_iter_init( tc->shmem->root_ll, tc->blockcache_shmem_pool );
986 280023 : !root_slist_iter_done( it, tc->shmem->root_ll, tc->blockcache_shmem_pool );
987 189424 : it = root_slist_iter_next( it, tc->shmem->root_ll, tc->blockcache_shmem_pool ) ) {
988 189424 : ulong blockcache_idx = root_slist_iter_idx( it, tc->shmem->root_ll, tc->blockcache_shmem_pool );
989 189424 : fd_txncache_blockcache_shmem_t const * blockcache_shmem = &tc->blockcache_shmem_pool[ blockcache_idx ];
990 189424 : FD_TEST( cnt<FD_TXNCACHE_MAX_ROOTS );
991 189424 : fd_txncache_root_t * root = &out[ cnt++ ];
992 189424 : root->blockcache_idx = blockcache_idx;
993 189424 : root->txnhash_offset = blockcache_shmem->txnhash_offset;
994 189424 : root->generation = blockcache_shmem->generation;
995 189424 : memcpy( root->blockhash, blockcache_shmem->blockhash.uc, 32UL );
996 189424 : }
997 90599 : fd_rwlock_unread( tc->shmem->lock );
998 90599 : return cnt;
999 90599 : }
1000 :
1001 : static fd_txncache_single_txn_t const *
1002 : bucket_chain_txn( fd_txncache_t * tc,
1003 : uint txn_gidx,
1004 1353732749 : int write_lock ) {
1005 1353732749 : if( FD_UNLIKELY( txn_gidx==UINT_MAX ) ) return NULL;
1006 1350075791 : fd_txncache_txnpage_t const * page = page_io( tc, txn_gidx/FD_TXNCACHE_TXNS_PER_PAGE, write_lock );
1007 1350075791 : FD_TEST( page );
1008 1350075791 : return page->txns[ txn_gidx%FD_TXNCACHE_TXNS_PER_PAGE ];
1009 1350075791 : }
1010 :
1011 : /* Locks tc and positions iter at the head of its current bucket. */
1012 :
1013 : static void
1014 3656958 : blockcache_iter_bucket_open( fd_txncache_blockcache_iter_t * iter ) {
1015 3656958 : fd_txncache_t * tc = iter->tc;
1016 3656958 : fd_rwlock_t * lock = tc->shmem->lock;
1017 3656958 : uint const * heads = tc->blockcache_pool[ iter->blockcache_idx ].heads;
1018 3656958 : ulong bucket = iter->bucket;
1019 :
1020 3656958 : fd_racesan_hook( "txncache:bucket_started" );
1021 3656958 : lock_read( tc );
1022 :
1023 3656958 : uint head;
1024 3656958 : int write_lock = 0;
1025 3660788 : for( ulong fetch_cnt=0UL;; fetch_cnt++ ) {
1026 : /* Inserts may publish a new head concurrently under the read lock.
1027 : The chain below the head loaded here is immutable until unlock. */
1028 3660788 : head = FD_VOLATILE_CONST( heads[ bucket ] );
1029 :
1030 : /* Check that every page of the chain is resident. */
1031 3660788 : ulong miss = ULONG_MAX;
1032 1353684211 : for( uint idx=head; idx!=UINT_MAX; ) {
1033 1350030327 : fd_txncache_txnpage_t const * page = page_io( tc, idx/FD_TXNCACHE_TXNS_PER_PAGE, 0 );
1034 1350030327 : if( FD_UNLIKELY( !page ) ) {
1035 6904 : miss = idx/FD_TXNCACHE_TXNS_PER_PAGE;
1036 6904 : break;
1037 6904 : }
1038 1350023423 : idx = page->txns[ idx%FD_TXNCACHE_TXNS_PER_PAGE ]->blockcache_next;
1039 1350023423 : }
1040 3660788 : if( FD_LIKELY( miss==ULONG_MAX ) ) break;
1041 :
1042 : /* Load the missing page without the lock and check again, or after
1043 : min(pages, frames) loads hold the write lock, which loads pages
1044 : in place. The chain may be compacted while unlocked, so the head
1045 : is reloaded. */
1046 6904 : ulong seq = tc->shmem->release_seq;
1047 6904 : int fetch = fetch_cnt<fd_ulong_min( tc->blockcache_pool[ iter->blockcache_idx ].shmem->pages_cnt, tc->shmem->frames_max );
1048 6904 : fd_rwlock_unread( lock );
1049 6904 : if( !fetch_cnt ) { fd_racesan_hook( "txncache:bucket_iter_miss" ); }
1050 6904 : if( FD_LIKELY( fetch ) ) {
1051 3830 : page_fetch( tc, miss, seq );
1052 3830 : lock_read( tc );
1053 3830 : continue;
1054 3830 : }
1055 3074 : lock_write( tc );
1056 3074 : write_lock = 1;
1057 3074 : head = heads[ bucket ];
1058 3074 : break;
1059 6904 : }
1060 :
1061 3656958 : iter->write_lock = write_lock;
1062 3656958 : iter->txn = bucket_chain_txn( tc, head, write_lock );
1063 3656958 : }
1064 :
1065 : static void
1066 3656958 : blockcache_iter_bucket_close( fd_txncache_blockcache_iter_t * iter ) {
1067 3656958 : if( iter->write_lock ) fd_rwlock_unwrite( iter->tc->shmem->lock );
1068 3653884 : else fd_rwlock_unread ( iter->tc->shmem->lock );
1069 3656958 : }
1070 :
1071 : /* Moves past empty buckets, holding the lock of one bucket at a time. */
1072 :
1073 : static void
1074 1350363176 : blockcache_iter_skip_empty( fd_txncache_blockcache_iter_t * iter ) {
1075 1353732749 : while( !iter->txn && iter->bucket+1UL<iter->tc->shmem->bucket_cnt ) {
1076 3369573 : blockcache_iter_bucket_close( iter );
1077 3369573 : iter->bucket++;
1078 3369573 : blockcache_iter_bucket_open( iter );
1079 3369573 : }
1080 1350363176 : }
1081 :
1082 : fd_txncache_blockcache_iter_t *
1083 : fd_txncache_blockcache_iter_init( fd_txncache_blockcache_iter_t * iter,
1084 : fd_txncache_t * tc,
1085 287385 : ulong blockcache_idx ) {
1086 287385 : iter->tc = tc;
1087 287385 : iter->blockcache_idx = blockcache_idx;
1088 287385 : iter->bucket = 0UL;
1089 287385 : blockcache_iter_bucket_open( iter );
1090 287385 : blockcache_iter_skip_empty( iter );
1091 287385 : return iter;
1092 287385 : }
1093 :
1094 : void
1095 1350075791 : fd_txncache_blockcache_iter_next( fd_txncache_blockcache_iter_t * iter ) {
1096 : /* Under the write lock, loading the next page may evict the current
1097 : transaction's frame, so read the link first. */
1098 1350075791 : uint next = iter->txn->blockcache_next;
1099 1350075791 : iter->txn = bucket_chain_txn( iter->tc, next, iter->write_lock );
1100 1350075791 : blockcache_iter_skip_empty( iter );
1101 1350075791 : }
1102 :
1103 : void
1104 287385 : fd_txncache_blockcache_iter_fini( fd_txncache_blockcache_iter_t * iter ) {
1105 287385 : blockcache_iter_bucket_close( iter );
1106 287385 : }
|