@@ -64,7 +64,6 @@ func makeMockTipSet(height uint64) *types.TipSet {
64
64
func TestAddHandlerConcurrency (t * testing.T ) {
65
65
api := & mockNodeAPI {
66
66
notifCh : make (chan []* api.HeadChange ),
67
- head : makeMockTipSet (100 ),
68
67
}
69
68
70
69
sched := New (api )
@@ -101,7 +100,6 @@ func TestAddHandlerConcurrency(t *testing.T) {
101
100
func TestAddHandlerAfterStart (t * testing.T ) {
102
101
mockAPI := & mockNodeAPI {
103
102
notifCh : make (chan []* api.HeadChange ),
104
- head : makeMockTipSet (100 ),
105
103
}
106
104
107
105
sched := New (mockAPI )
@@ -131,7 +129,6 @@ func TestNotificationChannelResubscription(t *testing.T) {
131
129
notifCh := make (chan []* api.HeadChange )
132
130
mockAPI := & mockNodeAPI {
133
131
notifCh : notifCh ,
134
- head : makeMockTipSet (100 ),
135
132
}
136
133
137
134
// Test that closing the notification channel causes resubscription
@@ -185,7 +182,6 @@ func TestCallbackExecution(t *testing.T) {
185
182
notifCh := make (chan []* api.HeadChange , 10 )
186
183
mockAPI := & mockNodeAPI {
187
184
notifCh : notifCh ,
188
- head : makeMockTipSet (100 ),
189
185
}
190
186
191
187
sched := New (mockAPI )
@@ -253,7 +249,6 @@ func TestContextCancellation(t *testing.T) {
253
249
notifCh := make (chan []* api.HeadChange )
254
250
mockAPI := & mockNodeAPI {
255
251
notifCh : notifCh ,
256
- head : makeMockTipSet (100 ),
257
252
}
258
253
259
254
sched := New (mockAPI )
@@ -285,29 +280,220 @@ func TestContextCancellation(t *testing.T) {
285
280
}
286
281
}
287
282
283
+ func TestSubscriptionContextCancellation (t * testing.T ) {
284
+ // This test verifies that when resubscribing, the old subscription
285
+ // context is properly cancelled
286
+
287
+ testCtx , testCancel := context .WithTimeout (context .Background (), 2 * time .Second )
288
+ defer testCancel ()
289
+
290
+ var notifyCalls []context.Context
291
+ var mu sync.Mutex
292
+
293
+ mockAPI := & mockNodeAPI {}
294
+
295
+ firstCh := make (chan []* api.HeadChange , 1 )
296
+ firstCallReady := make (chan struct {})
297
+ secondCallReady := make (chan struct {})
298
+
299
+ // Custom ChainNotify that captures contexts
300
+ wrappedAPI := & mockNodeAPIWithContext {
301
+ mockNodeAPI : mockAPI ,
302
+ chainNotifyFunc : func (ctx context.Context ) (<- chan []* api.HeadChange , error ) {
303
+ mu .Lock ()
304
+ notifyCalls = append (notifyCalls , ctx )
305
+ callNum := len (notifyCalls )
306
+ mu .Unlock ()
307
+
308
+ if callNum == 1 {
309
+ defer close (firstCallReady )
310
+ // First call - return channel we control below
311
+ return firstCh , nil
312
+ }
313
+
314
+ if callNum == 2 {
315
+ defer close (secondCallReady )
316
+ }
317
+
318
+ // Subsequent calls - return a properly initialized channel
319
+ ch := make (chan []* api.HeadChange , 1 )
320
+ ch <- []* api.HeadChange {{
321
+ Type : store .HCCurrent ,
322
+ Val : makeMockTipSet (100 ),
323
+ }}
324
+ return ch , nil
325
+ },
326
+ }
327
+
328
+ sched := New (wrappedAPI )
329
+
330
+ ctx , cancel := context .WithCancel (testCtx )
331
+ defer cancel ()
332
+
333
+ go sched .Run (ctx )
334
+
335
+ // Wait for first ChainNotify call
336
+ select {
337
+ case <- firstCallReady :
338
+ case <- testCtx .Done ():
339
+ t .Fatal ("Timeout waiting for first ChainNotify call" )
340
+ }
341
+
342
+ // Send initial notification
343
+ firstCh <- []* api.HeadChange {{
344
+ Type : store .HCCurrent ,
345
+ Val : makeMockTipSet (100 ),
346
+ }}
347
+
348
+ // Verify we have the first subscription
349
+ mu .Lock ()
350
+ require .Len (t , notifyCalls , 1 )
351
+ firstCtx := notifyCalls [0 ]
352
+ mu .Unlock ()
353
+
354
+ // Close the channel to trigger resubscription
355
+ close (firstCh )
356
+
357
+ // Wait for second ChainNotify call
358
+ select {
359
+ case <- secondCallReady :
360
+ case <- testCtx .Done ():
361
+ t .Fatal ("Timeout waiting for second ChainNotify call" )
362
+ }
363
+
364
+ // Should have multiple calls now
365
+ mu .Lock ()
366
+ require .GreaterOrEqual (t , len (notifyCalls ), 2 )
367
+ mu .Unlock ()
368
+
369
+ // Verify first context was cancelled
370
+ select {
371
+ case <- firstCtx .Done ():
372
+ // Good, context was cancelled
373
+ default :
374
+ t .Fatal ("First subscription context was not cancelled on resubscription" )
375
+ }
376
+ }
377
+
378
+ type mockNodeAPIWithContext struct {
379
+ * mockNodeAPI
380
+ chainNotifyFunc func (context.Context ) (<- chan []* api.HeadChange , error )
381
+ }
382
+
383
+ func (m * mockNodeAPIWithContext ) ChainNotify (ctx context.Context ) (<- chan []* api.HeadChange , error ) {
384
+ return m .chainNotifyFunc (ctx )
385
+ }
386
+
387
+ func TestTimeoutResubscription (t * testing.T ) {
388
+ // This test verifies that the scheduler will resubscribe after
389
+ // not receiving notifications for the configured timeout period
390
+
391
+ testCtx , testCancel := context .WithTimeout (context .Background (), 2 * time .Second )
392
+ defer testCancel ()
393
+
394
+ firstCallCh := make (chan struct {})
395
+ secondCallCh := make (chan struct {})
396
+ mu := & sync.Mutex {}
397
+
398
+ // Create a channel that won't receive new notifications after initial
399
+ notifyCh := make (chan []* api.HeadChange , 1 )
400
+
401
+ // Use mockNodeAPIWithContext to have full control over ChainNotify
402
+ var callCount int
403
+ wrappedAPI := & mockNodeAPIWithContext {
404
+ mockNodeAPI : & mockNodeAPI {},
405
+ chainNotifyFunc : func (ctx context.Context ) (<- chan []* api.HeadChange , error ) {
406
+ mu .Lock ()
407
+ callCount ++
408
+ count := callCount
409
+ mu .Unlock ()
410
+
411
+ if count == 1 {
412
+ defer close (firstCallCh )
413
+ } else if count == 2 {
414
+ defer close (secondCallCh )
415
+ }
416
+
417
+ // Always return the same channel for this test
418
+ return notifyCh , nil
419
+ },
420
+ }
421
+
422
+ // Create scheduler with very short timeout for testing
423
+ sched := NewWithNotificationTimeout (wrappedAPI , 200 * time .Millisecond )
424
+
425
+ ctx , cancel := context .WithCancel (testCtx )
426
+ defer cancel ()
427
+
428
+ go sched .Run (ctx )
429
+
430
+ // Wait for first ChainNotify call
431
+ select {
432
+ case <- firstCallCh :
433
+ case <- testCtx .Done ():
434
+ t .Fatal ("Timeout waiting for first ChainNotify call" )
435
+ }
436
+
437
+ // Send initial notification
438
+ notifyCh <- []* api.HeadChange {{
439
+ Type : store .HCCurrent ,
440
+ Val : makeMockTipSet (100 ),
441
+ }}
442
+
443
+ // Verify initial call count
444
+ mu .Lock ()
445
+ require .Equal (t , 1 , callCount )
446
+ mu .Unlock ()
447
+
448
+ // Wait for timeout to trigger resubscription
449
+ // The scheduler has a 200ms timeout, so wait for the second call
450
+ select {
451
+ case <- secondCallCh :
452
+ // Good, resubscription happened
453
+ case <- testCtx .Done ():
454
+ t .Fatal ("Timeout waiting for resubscription after notification timeout" )
455
+ }
456
+
457
+ // Verify we got the second call
458
+ mu .Lock ()
459
+ finalCount := callCount
460
+ mu .Unlock ()
461
+ require .GreaterOrEqual (t , finalCount , 2 , "ChainNotify should have been called again after timeout" )
462
+ }
463
+
288
464
func TestMultipleChanges (t * testing.T ) {
465
+ testCtx , testCancel := context .WithTimeout (context .Background (), 2 * time .Second )
466
+ defer testCancel ()
467
+
289
468
notifCh := make (chan []* api.HeadChange , 10 )
290
469
mockAPI := & mockNodeAPI {
291
470
notifCh : notifCh ,
292
- head : makeMockTipSet (100 ),
293
471
}
294
472
295
473
sched := New (mockAPI )
296
474
297
475
var callbackMu sync.Mutex
298
476
var callCount int
299
477
var lastApply * types.TipSet
478
+ firstCallDone := make (chan struct {})
479
+ secondCallDone := make (chan struct {})
300
480
301
481
err := sched .AddHandler (func (ctx context.Context , revert , apply * types.TipSet ) error {
302
482
callbackMu .Lock ()
303
483
defer callbackMu .Unlock ()
304
484
callCount ++
305
485
lastApply = apply
486
+
487
+ if callCount == 1 {
488
+ close (firstCallDone )
489
+ } else if callCount == 2 {
490
+ close (secondCallDone )
491
+ }
306
492
return nil
307
493
})
308
494
require .NoError (t , err )
309
495
310
- ctx , cancel := context .WithCancel (context . Background () )
496
+ ctx , cancel := context .WithCancel (testCtx )
311
497
defer cancel ()
312
498
313
499
go sched .Run (ctx )
@@ -320,6 +506,13 @@ func TestMultipleChanges(t *testing.T) {
320
506
}
321
507
notifCh <- []* api.HeadChange {initialChange }
322
508
509
+ // Wait for first callback
510
+ select {
511
+ case <- firstCallDone :
512
+ case <- testCtx .Done ():
513
+ t .Fatal ("Timeout waiting for first callback" )
514
+ }
515
+
323
516
// Send multiple changes in one notification
324
517
ts1 := makeMockTipSet (101 )
325
518
ts2 := makeMockTipSet (102 )
@@ -332,13 +525,17 @@ func TestMultipleChanges(t *testing.T) {
332
525
}
333
526
notifCh <- changes
334
527
335
- // Wait for processing
336
- time .Sleep (200 * time .Millisecond )
528
+ // Wait for second callback
529
+ select {
530
+ case <- secondCallDone :
531
+ case <- testCtx .Done ():
532
+ t .Fatal ("Timeout waiting for second callback" )
533
+ }
337
534
338
535
callbackMu .Lock ()
536
+ defer callbackMu .Unlock ()
339
537
// Should be called with the highest tipset
340
538
require .Equal (t , ts3 , lastApply )
341
539
// Initial current + one call for the batch
342
540
require .Equal (t , 2 , callCount )
343
- callbackMu .Unlock ()
344
541
}
0 commit comments