@@ -54,6 +54,12 @@ const (
54
54
PinnedSync
55
55
)
56
56
57
+ const (
58
+ // defaultTimestampQueueSize is the size of the timestamp range queue
59
+ // used.
60
+ defaultTimestampQueueSize = 1
61
+ )
62
+
57
63
// String returns a human readable string describing the target SyncerType.
58
64
func (t SyncerType ) String () string {
59
65
switch t {
@@ -285,6 +291,10 @@ type gossipSyncerCfg struct {
285
291
// updates for a channel and returns true if the channel should be
286
292
// considered a zombie based on these timestamps.
287
293
isStillZombieChannel func (time.Time , time.Time ) bool
294
+
295
+ // timestampQueueSize is the size of the timestamp range queue. If not
296
+ // set, defaults to the global timestampQueueSize constant.
297
+ timestampQueueSize int
288
298
}
289
299
290
300
// GossipSyncer is a struct that handles synchronizing the channel graph state
@@ -381,6 +391,16 @@ type GossipSyncer struct {
381
391
// respond to gossip timestamp range messages.
382
392
syncerSema chan struct {}
383
393
394
+ // timestampRangeQueue is a buffered channel for queuing timestamp range
395
+ // messages that need to be processed asynchronously. This prevents the
396
+ // gossiper from blocking when ApplyGossipFilter is called.
397
+ timestampRangeQueue chan * lnwire.GossipTimestampRange
398
+
399
+ // isSendingBacklog is an atomic flag that indicates whether a goroutine
400
+ // is currently sending the backlog of messages. This ensures only one
401
+ // goroutine is active at a time.
402
+ isSendingBacklog atomic.Bool
403
+
384
404
sync.Mutex
385
405
386
406
// cg is a helper that encapsulates a wait group and quit channel and
@@ -392,14 +412,23 @@ type GossipSyncer struct {
392
412
// newGossipSyncer returns a new instance of the GossipSyncer populated using
393
413
// the passed config.
394
414
func newGossipSyncer (cfg gossipSyncerCfg , sema chan struct {}) * GossipSyncer {
415
+ // Use the configured queue size if set, otherwise use the default.
416
+ queueSize := cfg .timestampQueueSize
417
+ if queueSize == 0 {
418
+ queueSize = defaultTimestampQueueSize
419
+ }
420
+
395
421
return & GossipSyncer {
396
422
cfg : cfg ,
397
423
syncTransitionReqs : make (chan * syncTransitionReq ),
398
424
historicalSyncReqs : make (chan * historicalSyncReq ),
399
425
gossipMsgs : make (chan lnwire.Message , syncerBufferSize ),
400
426
queryMsgs : make (chan lnwire.Message , syncerBufferSize ),
401
- syncerSema : sema ,
402
- cg : fn .NewContextGuard (),
427
+ timestampRangeQueue : make (
428
+ chan * lnwire.GossipTimestampRange , queueSize ,
429
+ ),
430
+ syncerSema : sema ,
431
+ cg : fn .NewContextGuard (),
403
432
}
404
433
}
405
434
@@ -422,6 +451,13 @@ func (g *GossipSyncer) Start() {
422
451
g .cg .WgAdd (1 )
423
452
go g .replyHandler (ctx )
424
453
}
454
+
455
+ // Start the timestamp range queue processor to handle gossip
456
+ // filter applications asynchronously.
457
+ if ! g .cfg .noTimestampQueryOption {
458
+ g .cg .WgAdd (1 )
459
+ go g .processTimestampRangeQueue (ctx )
460
+ }
425
461
})
426
462
}
427
463
@@ -672,6 +708,63 @@ func (g *GossipSyncer) replyHandler(ctx context.Context) {
672
708
}
673
709
}
674
710
711
+ // processTimestampRangeQueue handles timestamp range messages from the queue
712
+ // asynchronously. This prevents blocking the gossiper when rate limiting is
713
+ // active and multiple peers are trying to apply gossip filters.
714
+ func (g * GossipSyncer ) processTimestampRangeQueue (ctx context.Context ) {
715
+ defer g .cg .WgDone ()
716
+
717
+ for {
718
+ select {
719
+ case msg := <- g .timestampRangeQueue :
720
+ // Process the timestamp range message. If we hit an
721
+ // error, log it but continue processing to avoid
722
+ // blocking the queue.
723
+ err := g .ApplyGossipFilter (ctx , msg )
724
+ switch {
725
+ case errors .Is (err , ErrGossipSyncerExiting ):
726
+ return
727
+
728
+ case errors .Is (err , lnpeer .ErrPeerExiting ):
729
+ return
730
+
731
+ case err != nil :
732
+ log .Errorf ("Unable to apply gossip filter: %v" ,
733
+ err )
734
+ }
735
+
736
+ case <- g .cg .Done ():
737
+ return
738
+
739
+ case <- ctx .Done ():
740
+ return
741
+ }
742
+ }
743
+ }
744
+
745
+ // QueueTimestampRange attempts to queue a timestamp range message for
746
+ // asynchronous processing. If the queue is full, it returns false to indicate
747
+ // the message was dropped.
748
+ func (g * GossipSyncer ) QueueTimestampRange (
749
+ msg * lnwire.GossipTimestampRange ) bool {
750
+
751
+ // If timestamp queries are disabled, don't queue the message.
752
+ if g .cfg .noTimestampQueryOption {
753
+ return false
754
+ }
755
+
756
+ select {
757
+ case g .timestampRangeQueue <- msg :
758
+ return true
759
+
760
+ // Queue is full, drop the message to prevent blocking.
761
+ default :
762
+ log .Warnf ("Timestamp range queue full for peer %x, " +
763
+ "dropping message" , g .cfg .peerPub [:])
764
+ return false
765
+ }
766
+ }
767
+
675
768
// sendGossipTimestampRange constructs and sets a GossipTimestampRange for the
676
769
// syncer and sends it to the remote peer.
677
770
func (g * GossipSyncer ) sendGossipTimestampRange (ctx context.Context ,
@@ -1308,6 +1401,14 @@ func (g *GossipSyncer) ApplyGossipFilter(ctx context.Context,
1308
1401
return nil
1309
1402
}
1310
1403
1404
+ // Check if a goroutine is already sending the backlog. If so, return
1405
+ // early without attempting to acquire the semaphore.
1406
+ if g .isSendingBacklog .Load () {
1407
+ log .Debugf ("GossipSyncer(%x): skipping ApplyGossipFilter, " +
1408
+ "backlog send already in progress" , g .cfg .peerPub [:])
1409
+ return nil
1410
+ }
1411
+
1311
1412
select {
1312
1413
case <- g .syncerSema :
1313
1414
case <- g .cg .Done ():
@@ -1342,11 +1443,23 @@ func (g *GossipSyncer) ApplyGossipFilter(ctx context.Context,
1342
1443
return nil
1343
1444
}
1344
1445
1446
+ // Set the atomic flag to indicate we're starting to send the backlog.
1447
+ // If the swap fails, it means another goroutine is already active, so
1448
+ // we return early.
1449
+ if ! g .isSendingBacklog .CompareAndSwap (false , true ) {
1450
+ returnSema ()
1451
+ log .Debugf ("GossipSyncer(%x): another goroutine already " +
1452
+ "sending backlog, skipping" , g .cfg .peerPub [:])
1453
+
1454
+ return nil
1455
+ }
1456
+
1345
1457
// We'll conclude by launching a goroutine to send out any updates.
1346
1458
g .cg .WgAdd (1 )
1347
1459
go func () {
1348
1460
defer g .cg .WgDone ()
1349
1461
defer returnSema ()
1462
+ defer g .isSendingBacklog .Store (false )
1350
1463
1351
1464
for _ , msg := range newUpdatestoSend {
1352
1465
err := g .cfg .sendToPeerSync (ctx , msg )
0 commit comments