@@ -187,18 +187,6 @@ fd_forest_verify( fd_forest_t const * forest ) {
187
187
return 0 ;
188
188
}
189
189
190
- /* query queries for a connected ele keyed by slot. does not return
191
- orphaned ele. */
192
-
193
- static fd_forest_ele_t *
194
- ancestry_frontier_query ( fd_forest_t * forest , ulong slot ) {
195
- fd_forest_ele_t * pool = fd_forest_pool ( forest );
196
- fd_forest_ele_t * ele = NULL ;
197
- ele = fd_forest_ancestry_ele_query ( fd_forest_ancestry ( forest ), & slot , NULL , pool );
198
- ele = fd_ptr_if ( !ele , fd_forest_frontier_ele_query ( fd_forest_frontier ( forest ), & slot , NULL , pool ), ele );
199
- return ele ;
200
- }
201
-
202
190
/* remove removes and returns a connected ele from ancestry or frontier
203
191
maps. does not remove orphaned ele. does not unlink ele. */
204
192
@@ -381,9 +369,6 @@ insert( fd_forest_t * forest, ulong slot, ushort parent_off ) {
381
369
382
370
fd_forest_ele_t *
383
371
fd_forest_query ( fd_forest_t * forest , ulong slot ) {
384
- # if FD_FOREST_USE_HANDHOLDING
385
- FD_TEST ( slot > fd_forest_root_slot ( forest ) ); /* caller error - inval */
386
- # endif
387
372
return query ( forest , slot );
388
373
}
389
374
@@ -432,22 +417,39 @@ fd_forest_publish( fd_forest_t * forest, ulong new_root_slot ) {
432
417
433
418
fd_forest_ancestry_t * ancestry = fd_forest_ancestry ( forest );
434
419
fd_forest_frontier_t * frontier = fd_forest_frontier ( forest );
420
+ fd_forest_orphaned_t * orphaned = fd_forest_orphaned ( forest );
435
421
fd_forest_ele_t * pool = fd_forest_pool ( forest );
436
422
ulong null = fd_forest_pool_idx_null ( pool );
437
423
438
424
fd_forest_ele_t * old_root_ele = fd_forest_pool_ele ( pool , forest -> root );
439
- fd_forest_ele_t * new_root_ele = ancestry_frontier_query ( forest , new_root_slot );
425
+ fd_forest_ele_t * new_root_ele = query ( forest , new_root_slot );
440
426
441
- # if FD_FOREST_USE_HANDHOLDING
442
- FD_TEST ( new_root_ele ); /* caller error - not found */
443
- FD_TEST ( new_root_ele -> slot > old_root_ele -> slot ); /* caller error - inval */
444
- # endif
427
+ #if FD_FOREST_USE_HANDHOLDING
428
+ if ( FD_LIKELY ( new_root_ele ) ) {
429
+ FD_TEST ( new_root_ele -> slot > old_root_ele -> slot ); /* caller error - inval */
430
+ }
431
+ #endif
432
+
433
+ /* Edge case where if we haven't been getting repairs, and we have a
434
+ gap between the root and orphans. we publish forward to a slot that
435
+ we don't have. This only case this should be happening is when we
436
+ load a second incremental and that incremental slot lives in the
437
+ gap. In that case this isn't a bug, but we should be treating this
438
+ new root like the snapshot slot / init root. Should be happening
439
+ very rarely given a well-functioning repair. */
440
+
441
+ if ( FD_UNLIKELY ( !new_root_ele ) ) {
442
+ new_root_ele = acquire ( forest , new_root_slot );
443
+ new_root_ele -> complete_idx = 0 ;
444
+ new_root_ele -> buffered_idx = 0 ;
445
+ fd_forest_frontier_ele_insert ( frontier , new_root_ele , pool );
446
+ }
445
447
446
448
/* First, remove the previous root, and add it to a FIFO prune queue.
447
449
head points to the queue head (initialized with old_root_ele). */
448
450
449
451
fd_forest_ele_t * head = ancestry_frontier_remove ( forest , old_root_ele -> slot );
450
- head -> next = null ;
452
+ head -> prev = null ;
451
453
fd_forest_ele_t * tail = head ;
452
454
453
455
/* Second, BFS down the tree, inserting each ele into the prune queue
@@ -460,22 +462,80 @@ fd_forest_publish( fd_forest_t * forest, ulong new_root_slot ) {
460
462
if ( FD_LIKELY ( child != new_root_ele ) ) { /* do not prune new root or descendants */
461
463
ulong idx = fd_forest_ancestry_idx_remove ( ancestry , & child -> slot , null , pool );
462
464
idx = fd_ulong_if ( idx != null , idx , fd_forest_frontier_idx_remove ( frontier , & child -> slot , null , pool ) );
463
- tail -> next = idx ; /* insert prune queue */
465
+ tail -> prev = idx ; /* insert prune queue */
464
466
# if FD_FOREST_USE_HANDHOLDING
465
- FD_TEST ( tail -> next != null ); /* programming error in BFS */
467
+ FD_TEST ( tail -> prev != null ); /* programming error in BFS */
466
468
# endif
467
- tail = fd_forest_pool_ele ( pool , tail -> next ); /* advance prune queue */
468
- tail -> next = null ;
469
+ tail = fd_forest_pool_ele ( pool , tail -> prev ); /* advance prune queue */
470
+ tail -> prev = null ;
469
471
}
470
472
child = fd_forest_pool_ele ( pool , child -> sibling );
471
473
}
472
- fd_forest_ele_t * next = fd_forest_pool_ele ( pool , head -> next ); /* FIFO pop */
474
+ fd_forest_ele_t * next = fd_forest_pool_ele ( pool , head -> prev ); /* FIFO pop */
473
475
fd_forest_pool_ele_release ( pool , head ); /* free head */
474
476
head = next ;
475
477
}
476
478
479
+ /* If there is nothing on the frontier, we have hit an edge case
480
+ during catching up where all of our frontiers were < the new root.
481
+ In that case we need to continue repairing from the new root, so
482
+ add it to the frontier. */
483
+
484
+ if ( FD_UNLIKELY ( fd_forest_frontier_iter_done ( fd_forest_frontier_iter_init ( frontier , pool ), frontier , pool ) ) ) {
485
+ fd_forest_ele_t * remove = fd_forest_ancestry_ele_remove ( ancestry , & new_root_ele -> slot , NULL , pool );
486
+ if ( FD_UNLIKELY ( !remove ) ) {
487
+ /* Very rare case where during second incremental load we could publish to an orphaned slot */
488
+ remove = fd_forest_orphaned_ele_remove ( orphaned , & new_root_ele -> slot , NULL , pool );
489
+ }
490
+ FD_TEST ( remove == new_root_ele );
491
+ fd_forest_frontier_ele_insert ( frontier , new_root_ele , pool );
492
+ new_root_ele -> complete_idx = 0 ;
493
+ new_root_ele -> buffered_idx = 0 ;
494
+ advance_frontier ( forest , new_root_ele -> slot , 0 );
495
+ }
496
+
477
497
new_root_ele -> parent = null ; /* unlink new root from parent */
478
- forest -> root = fd_forest_ancestry_idx_query ( ancestry , & new_root_slot , null , pool );
498
+ forest -> root = fd_forest_pool_idx ( pool , new_root_ele );
499
+
500
+ /* Lastly, cleanup orphans if there orphan heads < new_root_slot.
501
+ First, add any relevant orphans to the prune queue. */
502
+
503
+ head = NULL ;
504
+ for ( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init ( orphaned , pool );
505
+ !fd_forest_orphaned_iter_done ( iter , orphaned , pool );
506
+ iter = fd_forest_orphaned_iter_next ( iter , orphaned , pool ) ) {
507
+ fd_forest_ele_t * ele = fd_forest_orphaned_iter_ele ( iter , orphaned , pool );
508
+ if ( FD_UNLIKELY ( ele -> slot < new_root_slot ) ) {
509
+ if ( FD_UNLIKELY ( !head ) ) {
510
+ head = ele ;
511
+ head -> prev = null ;
512
+ tail = ele ;
513
+ } else {
514
+ tail -> prev = iter .ele_idx ;
515
+ tail = fd_forest_pool_ele ( pool , tail -> prev );
516
+ tail -> prev = null ;
517
+ }
518
+ }
519
+ }
520
+
521
+ /* Now BFS and clean up children of these orphan heads */
522
+ while ( head ) {
523
+ fd_forest_ele_t * child = fd_forest_pool_ele ( pool , head -> child );
524
+ while ( FD_LIKELY ( child ) ) {
525
+ if ( FD_LIKELY ( child != new_root_ele ) ) {
526
+ tail -> prev = fd_forest_pool_idx ( pool , child ); /* insert prune queue */
527
+ tail = fd_forest_pool_ele ( pool , tail -> prev ); /* advance prune queue */
528
+ tail -> prev = null ;
529
+ }
530
+ child = fd_forest_pool_ele ( pool , child -> sibling );
531
+ }
532
+ ulong remove = fd_forest_orphaned_idx_remove ( orphaned , & head -> slot , null , pool ); /* remove myself */
533
+ remove = fd_ulong_if ( remove == null , fd_forest_ancestry_idx_remove ( ancestry , & head -> slot , null , pool ), remove );
534
+
535
+ fd_forest_ele_t * next = fd_forest_pool_ele ( pool , head -> prev ); /* FIFO pop */
536
+ fd_forest_pool_ele_release ( pool , head ); /* free head */
537
+ head = next ;
538
+ }
479
539
return new_root_ele ;
480
540
}
481
541
0 commit comments