4
4
"context"
5
5
"errors"
6
6
"fmt"
7
+ "os"
7
8
"slices"
9
+ "strconv"
8
10
"sync"
9
11
"sync/atomic"
10
12
"time"
@@ -61,7 +63,7 @@ type Store[H header.Header[H]] struct {
61
63
syncCh chan chan struct {}
62
64
63
65
onDeleteMu sync.Mutex
64
- onDelete []func (context.Context , [] H ) error
66
+ onDelete []func (context.Context , uint64 ) error
65
67
66
68
Params Parameters
67
69
}
@@ -271,7 +273,7 @@ func (s *Store[H]) getByHeight(ctx context.Context, height uint64) (H, error) {
271
273
return h , nil
272
274
}
273
275
274
- hash , err := s .heightIndex .HashByHeight (ctx , height )
276
+ hash , err := s .heightIndex .HashByHeight (ctx , height , true )
275
277
if err != nil {
276
278
var zero H
277
279
if errors .Is (err , datastore .ErrNotFound ) {
@@ -357,18 +359,18 @@ func (s *Store[H]) HasAt(ctx context.Context, height uint64) bool {
357
359
return head .Height () >= height && height >= tail .Height ()
358
360
}
359
361
360
- func (s * Store [H ]) OnDelete (fn func (context.Context , [] H ) error ) {
362
+ func (s * Store [H ]) OnDelete (fn func (context.Context , uint64 ) error ) {
361
363
s .onDeleteMu .Lock ()
362
364
defer s .onDeleteMu .Unlock ()
363
365
364
- s .onDelete = append (s .onDelete , func (ctx context.Context , h [] H ) (rerr error ) {
366
+ s .onDelete = append (s .onDelete , func (ctx context.Context , height uint64 ) (rerr error ) {
365
367
defer func () {
366
368
err := recover ()
367
369
if err != nil {
368
- rerr = fmt .Errorf ("header/store: user provided onDelete panicked with: %s" , err )
370
+ rerr = fmt .Errorf ("header/store: user provided onDelete panicked on %d with: %s" , height , err )
369
371
}
370
372
}()
371
- return fn (ctx , h )
373
+ return fn (ctx , height )
372
374
})
373
375
}
374
376
@@ -386,13 +388,16 @@ func (s *Store[H]) DeleteTo(ctx context.Context, to uint64) error {
386
388
}
387
389
if head .Height ()+ 1 < to {
388
390
_ , err := s .getByHeight (ctx , to )
389
- if err != nil {
391
+ if errors . Is ( err , header . ErrNotFound ) {
390
392
return fmt .Errorf (
391
393
"header/store: delete to %d beyond current head(%d)" ,
392
394
to ,
393
395
head .Height (),
394
396
)
395
397
}
398
+ if err != nil {
399
+ return fmt .Errorf ("delete to potential new head: %w" , err )
400
+ }
396
401
397
402
// if `to` is bigger than the current head and is stored - allow delete, making `to` a new head
398
403
}
@@ -420,82 +425,80 @@ func (s *Store[H]) DeleteTo(ctx context.Context, to uint64) error {
420
425
return nil
421
426
}
422
427
423
- var maxHeadersLoadedPerDelete uint64 = 512
428
+ var maxHeadersLoadedPerDelete uint64 = 1024
424
429
425
- func (s * Store [H ]) deleteRange (ctx context.Context , from , to uint64 ) error {
426
- log .Infow ("deleting headers" , "from_height" , from , "to_height" , to )
427
- for from < to {
428
- amount := min (to - from , maxHeadersLoadedPerDelete )
429
- toDelete := from + amount
430
- headers := make ([]H , 0 , amount )
430
+ func init () {
431
+ v , ok := os .LookupEnv ("HEADER_MAX_LOAD_PER_DELETE" )
432
+ if ! ok {
433
+ return
434
+ }
431
435
432
- for height := from ; height < toDelete ; height ++ {
433
- // take headers individually instead of range
434
- // as getRangeByHeight can't deal with potentially missing headers
435
- h , err := s .getByHeight (ctx , height )
436
- if errors .Is (err , header .ErrNotFound ) {
437
- log .Warnf ("header/store: attempt to delete header that's not found" , height )
438
- continue
439
- }
440
- if err != nil {
441
- return fmt .Errorf ("getting header while deleting: %w" , err )
442
- }
436
+ max , err := strconv .Atoi (v )
437
+ if err != nil {
438
+ panic (err )
439
+ }
440
+
441
+ maxHeadersLoadedPerDelete = uint64 (max )
442
+ }
443
+
444
+ func (s * Store [H ]) deleteRange (ctx context.Context , from , to uint64 ) (rerr error ) {
445
+ s .onDeleteMu .Lock ()
446
+ onDelete := slices .Clone (s .onDelete )
447
+ s .onDeleteMu .Unlock ()
448
+
449
+ batch , err := s .ds .Batch (ctx )
450
+ if err != nil {
451
+ return fmt .Errorf ("new batch: %w" , err )
452
+ }
443
453
444
- headers = append (headers , h )
454
+ height := from
455
+ defer func () {
456
+ // make new context to always save progress
457
+ ctx := context .Background ()
458
+ newTailHeight := to
459
+ if rerr != nil {
460
+ newTailHeight = height
445
461
}
446
462
447
- batch , err : = s .ds . Batch (ctx )
463
+ err = s .setTail (ctx , batch , newTailHeight )
448
464
if err != nil {
449
- return fmt .Errorf ("new batch : %w" , err )
465
+ rerr = errors . Join ( rerr , fmt .Errorf ("setting tail to %d : %w" , newTailHeight , err ) )
450
466
}
451
467
452
- s .onDeleteMu .Lock ()
453
- onDelete := slices .Clone (s .onDelete )
454
- s .onDeleteMu .Unlock ()
455
- for _ , deleteFn := range onDelete {
456
- if err := deleteFn (ctx , headers ); err != nil {
457
- // abort deletion if onDelete handler fails
458
- // to ensure atomicity between stored headers and user specific data
459
- // TODO(@Wondertan): Batch is not actually atomic and could write some data at this point
460
- // but its fine for now: https://github.com/celestiaorg/go-header/issues/307
461
- // TODO2(@Wondertan): Once we move to txn, find a way to pass txn through context,
462
- // so that users can use it in their onDelete handlers
463
- // to ensure atomicity between deleted headers and user specific data
464
- return fmt .Errorf ("on delete handler: %w" , err )
465
- }
468
+ if err := batch .Commit (ctx ); err != nil {
469
+ rerr = errors .Join (rerr , fmt .Errorf ("committing delete batch [%d:%d): %w" , from , newTailHeight , err ))
466
470
}
471
+ }()
467
472
468
- for _ , h := range headers {
469
- if err := batch .Delete (ctx , hashKey (h .Hash ())); err != nil {
470
- return fmt .Errorf ("delete hash key (%X): %w" , h .Hash (), err )
471
- }
472
- if err := batch .Delete (ctx , heightKey (h .Height ())); err != nil {
473
- return fmt .Errorf ("delete height key (%d): %w" , h .Height (), err )
474
- }
473
+ for ; height < to ; height ++ {
474
+ hash , err := s .heightIndex .HashByHeight (ctx , height , false )
475
+ if errors .Is (err , datastore .ErrNotFound ) {
476
+ log .Warnf ("attempt to delete header that's not found" , "height" , height )
477
+ continue
475
478
}
476
-
477
- err = s .setTail (ctx , batch , toDelete )
478
479
if err != nil {
479
- return fmt .Errorf ("setting tail to %d: %w" , toDelete , err )
480
+ return fmt .Errorf ("hash by height %d: %w" , height , err )
480
481
}
481
482
482
- if err := batch .Commit (ctx ); err != nil {
483
- return fmt .Errorf ("committing delete batch [%d:%d): %w" , from , toDelete , err )
483
+ for _ , deleteFn := range onDelete {
484
+ if err := deleteFn (ctx , height ); err != nil {
485
+ return fmt .Errorf ("on delete handler for %d: %w" , height , err )
486
+ }
484
487
}
485
488
486
- // cleanup caches after disk is flushed
487
- for _ , h := range headers {
488
- s .cache .Remove (h .Hash ().String ())
489
- s .heightIndex .cache .Remove (h .Height ())
489
+ if err := batch .Delete (ctx , hashKey (hash )); err != nil {
490
+ return fmt .Errorf ("delete hash key (%X): %w" , hash , err )
491
+ }
492
+ if err := batch .Delete (ctx , heightKey (height )); err != nil {
493
+ return fmt .Errorf ("delete height key (%d): %w" , height , err )
490
494
}
491
- s .pending .DeleteRange (from , toDelete )
492
-
493
- log .Infow ("deleted header range" , "from_height" , from , "to_height" , toDelete )
494
495
495
- // move iterator
496
- from = toDelete
496
+ s .cache .Remove (hash .String ())
497
+ s .heightIndex .cache .Remove (height )
498
+ s .pending .DeleteRange (height , height + 1 )
497
499
}
498
500
501
+ log .Infow ("deleted headers" , "from_height" , from , "to_height" , to )
499
502
return nil
500
503
}
501
504
0 commit comments