@@ -48,9 +48,9 @@ var Aff = function () {
48
48
49
49
// Various constructors used in interpretation
50
50
var CONS = "Cons" ; // Cons-list, for stacks
51
- var RECOVER = "Recover" ; // Continue with error handler
52
51
var RESUME = "Resume" ; // Continue indiscriminately
53
52
var RELEASE = "Release" ; // Continue with bracket finalizers
53
+ var FINALIZER = "Finalizer" ; // A non-interruptible effect
54
54
var FINALIZED = "Finalized" ; // Marker for finalization
55
55
var FORKED = "Forked" ; // Reference to a forked fiber, with resumption stack
56
56
var FIBER = "Fiber" ; // Actual fiber reference
@@ -239,7 +239,9 @@ var Aff = function () {
239
239
var bhead = null ;
240
240
var btail = null ;
241
241
242
- // Stack of attempts and finalizers for error recovery.
242
+ // Stack of attempts and finalizers for error recovery. Every `Cons` is also
243
+ // tagged with current `interrupt` state. We use this to track which items
244
+ // should be ignored or evaluated as a result of a kill.
243
245
var attempts = null ;
244
246
245
247
// A special state is needed for Bracket, because it cannot be killed. When
@@ -335,30 +337,33 @@ var Aff = function () {
335
337
return ;
336
338
337
339
case THROW :
338
- bhead = null ;
339
- btail = null ;
340
340
status = RETURN ;
341
341
fail = util . left ( step . _1 ) ;
342
342
step = null ;
343
343
break ;
344
344
345
- // Enqueue the current stack of binds and continue
345
+ // Enqueue the Catch so that we can call the error handler later on
346
+ // in case of an exception.
346
347
case CATCH :
347
- attempts = new Aff ( CONS , new Aff ( RECOVER , step . _2 , bhead , btail ) , attempts ) ;
348
+ if ( bhead === null ) {
349
+ attempts = new Aff ( CONS , step , attempts , interrupt ) ;
350
+ } else {
351
+ attempts = new Aff ( CONS , step , new Aff ( CONS , new Aff ( RESUME , bhead , btail ) , attempts , interrupt ) , interrupt ) ;
352
+ }
348
353
bhead = null ;
349
354
btail = null ;
350
355
status = CONTINUE ;
351
356
step = step . _1 ;
352
357
break ;
353
358
354
- // When we evaluate a Bracket, we also enqueue the instruction so we
355
- // can fullfill it later once we return from the acquisition.
359
+ // Enqueue the Bracket so that we can call the appropriate handlers
360
+ // after resource acquisition.
356
361
case BRACKET :
357
362
bracketCount ++ ;
358
363
if ( bhead === null ) {
359
- attempts = new Aff ( CONS , step , attempts ) ;
364
+ attempts = new Aff ( CONS , step , attempts , interrupt ) ;
360
365
} else {
361
- attempts = new Aff ( CONS , step , new Aff ( CONS , new Aff ( RESUME , bhead , btail ) , attempts ) ) ;
366
+ attempts = new Aff ( CONS , step , new Aff ( CONS , new Aff ( RESUME , bhead , btail ) , attempts , interrupt ) , interrupt ) ;
362
367
}
363
368
bhead = null ;
364
369
btail = null ;
@@ -386,40 +391,42 @@ var Aff = function () {
386
391
break ;
387
392
388
393
case RETURN :
394
+ bhead = null ;
395
+ btail = null ;
389
396
// If the current stack has returned, and we have no other stacks to
390
397
// resume or finalizers to run, the fiber has halted and we can
391
398
// invoke all join callbacks. Otherwise we need to resume.
392
399
if ( attempts === null ) {
393
400
status = COMPLETED ;
394
401
step = interrupt || fail || step ;
395
402
} else {
403
+ // The interrupt status for the enqueued item.
404
+ tmp = attempts . _3 ;
396
405
attempt = attempts . _1 ;
397
406
attempts = attempts . _2 ;
398
407
399
408
switch ( attempt . tag ) {
400
409
// We cannot recover from an interrupt. Otherwise we should
401
410
// continue stepping, or run the exception handler if an exception
402
411
// was raised.
403
- case RECOVER :
404
- if ( interrupt ) {
412
+ case CATCH :
413
+ // We should compare the interrupt status as well because we
414
+ // only want it to apply if there has been an interrupt since
415
+ // enqueuing the catch.
416
+ if ( interrupt && interrupt !== tmp ) {
405
417
status = RETURN ;
406
- } else {
407
- bhead = attempt . _2 ;
408
- btail = attempt . _3 ;
409
- if ( fail ) {
410
- status = CONTINUE ;
411
- step = attempt . _1 ( util . fromLeft ( fail ) ) ;
412
- fail = null ;
413
- } else {
414
- status = STEP_BIND ;
415
- step = util . fromRight ( step ) ;
416
- }
418
+ } else if ( fail ) {
419
+ status = CONTINUE ;
420
+ step = attempt . _2 ( util . fromLeft ( fail ) ) ;
421
+ fail = null ;
417
422
}
418
423
break ;
419
424
420
425
// We cannot resume from an interrupt or exception.
421
426
case RESUME :
422
- if ( interrupt || fail ) {
427
+ // As with Catch, we only want to ignore in the case of an
428
+ // interrupt since enqueing the item.
429
+ if ( interrupt && interrupt !== tmp || fail ) {
423
430
status = RETURN ;
424
431
} else {
425
432
bhead = attempt . _1 ;
@@ -437,42 +444,47 @@ var Aff = function () {
437
444
bracketCount -- ;
438
445
if ( fail === null ) {
439
446
result = util . fromRight ( step ) ;
440
- attempts = new Aff ( CONS , new Aff ( RELEASE , attempt . _2 , result ) , attempts ) ;
441
- if ( interrupt === null || bracketCount > 0 ) {
447
+ // We need to enqueue the Release with the same interrupt
448
+ // status as the Bracket that is initiating it.
449
+ attempts = new Aff ( CONS , new Aff ( RELEASE , attempt . _2 , result ) , attempts , tmp ) ;
450
+ // We should only coninue as long as the interrupt status has not changed or
451
+ // we are currently within a non-interruptable finalizer.
452
+ if ( interrupt === tmp || bracketCount > 0 ) {
442
453
status = CONTINUE ;
443
454
step = attempt . _3 ( result ) ;
444
455
}
445
456
}
446
457
break ;
447
458
448
459
// Enqueue the appropriate handler. We increase the bracket count
449
- // because it should be cancelled.
460
+ // because it should not be cancelled.
450
461
case RELEASE :
451
462
bracketCount ++ ;
452
- attempts = new Aff ( CONS , new Aff ( FINALIZED , step ) , attempts ) ;
463
+ attempts = new Aff ( CONS , new Aff ( FINALIZED , step ) , attempts , interrupt ) ;
453
464
status = CONTINUE ;
454
- if ( interrupt !== null ) {
465
+ // It has only been killed if the interrupt status has changed
466
+ // since we enqueued the item.
467
+ if ( interrupt && interrupt !== tmp ) {
455
468
step = attempt . _1 . killed ( util . fromLeft ( interrupt ) ) ( attempt . _2 ) ;
456
- } else if ( fail !== null ) {
469
+ } else if ( fail ) {
457
470
step = attempt . _1 . failed ( util . fromLeft ( fail ) ) ( attempt . _2 ) ;
458
471
} else {
459
472
step = attempt . _1 . completed ( util . fromRight ( step ) ) ( attempt . _2 ) ;
460
473
}
461
474
break ;
462
475
476
+ case FINALIZER :
477
+ bracketCount ++ ;
478
+ attempts = new Aff ( CONS , new Aff ( FINALIZED , step ) , attempts , interrupt ) ;
479
+ status = CONTINUE ;
480
+ step = attempt . _1 ;
481
+ break ;
482
+
463
483
case FINALIZED :
464
484
bracketCount -- ;
465
485
status = RETURN ;
466
486
step = attempt . _1 ;
467
487
break ;
468
-
469
- // Otherwise we need to run a finalizer, which cannot be interrupted.
470
- // We insert a FINALIZED marker to know when we can release it.
471
- default :
472
- bracketCount ++ ;
473
- attempts = new Aff ( CONS , new Aff ( FINALIZED , step ) , attempts ) ;
474
- status = CONTINUE ;
475
- step = attempt ;
476
488
}
477
489
}
478
490
break ;
@@ -485,9 +497,15 @@ var Aff = function () {
485
497
}
486
498
}
487
499
joins = null ;
500
+ // If we have an interrupt and a fail, then the thread threw while
501
+ // running finalizers. This should always rethrow in a fresh stack.
502
+ if ( interrupt && fail ) {
503
+ setTimeout ( function ( ) {
504
+ throw util . fromLeft ( fail ) ;
505
+ } , 0 ) ;
488
506
// If we have an unhandled exception, and no other fiber has joined
489
507
// then we need to throw the exception in a fresh stack.
490
- if ( util . isLeft ( step ) && rethrow ) {
508
+ } else if ( util . isLeft ( step ) && rethrow ) {
491
509
setTimeout ( function ( ) {
492
510
// Guard on reathrow because a completely synchronous fiber can
493
511
// still have an observer which was added after-the-fact.
@@ -532,12 +550,8 @@ var Aff = function () {
532
550
533
551
var canceler = onComplete ( {
534
552
rethrow : false ,
535
- handler : function ( result ) {
536
- if ( fail ) {
537
- return cb ( fail ) ;
538
- } else {
539
- return cb ( util . right ( void 0 ) ) ;
540
- }
553
+ handler : function ( /* unused */ ) {
554
+ return cb ( util . right ( void 0 ) ) ;
541
555
}
542
556
} ) ( ) ;
543
557
@@ -554,10 +568,8 @@ var Aff = function () {
554
568
}
555
569
if ( bracketCount === 0 ) {
556
570
if ( status === PENDING ) {
557
- attempts = new Aff ( CONS , step ( error ) , attempts ) ;
571
+ attempts = new Aff ( CONS , new Aff ( FINALIZER , step ( error ) ) , attempts , interrupt ) ;
558
572
}
559
- bhead = null ;
560
- btail = null ;
561
573
status = RETURN ;
562
574
step = null ;
563
575
fail = null ;
@@ -569,8 +581,6 @@ var Aff = function () {
569
581
interrupt = util . left ( error ) ;
570
582
}
571
583
if ( bracketCount === 0 ) {
572
- bhead = null ;
573
- btail = null ;
574
584
status = RETURN ;
575
585
step = null ;
576
586
fail = null ;
@@ -634,7 +644,6 @@ var Aff = function () {
634
644
// cancellation fibers.
635
645
function kill ( error , par , cb ) {
636
646
var step = par ;
637
- var fail = null ;
638
647
var head = null ;
639
648
var tail = null ;
640
649
var count = 0 ;
@@ -651,11 +660,8 @@ var Aff = function () {
651
660
kills [ count ++ ] = tmp . kill ( error , function ( result ) {
652
661
return function ( ) {
653
662
count -- ;
654
- if ( fail === null && util . isLeft ( result ) ) {
655
- fail = result ;
656
- }
657
663
if ( count === 0 ) {
658
- cb ( fail || util . right ( void 0 ) ) ( ) ;
664
+ cb ( result ) ( ) ;
659
665
}
660
666
} ;
661
667
} ) ;
@@ -688,7 +694,7 @@ var Aff = function () {
688
694
}
689
695
690
696
if ( count === 0 ) {
691
- cb ( fail || util . right ( void 0 ) ) ( ) ;
697
+ cb ( util . right ( void 0 ) ) ( ) ;
692
698
} else {
693
699
// Run the cancelation effects. We alias `count` because it's mutable.
694
700
kid = 0 ;
@@ -795,19 +801,15 @@ var Aff = function () {
795
801
kid = killId ++ ;
796
802
// Once a side has resolved, we need to cancel the side that is still
797
803
// pending before we can continue.
798
- kills [ kid ] = kill ( early , step === lhs ? head . _2 : head . _1 , function ( killResult ) {
804
+ kills [ kid ] = kill ( early , step === lhs ? head . _2 : head . _1 , function ( /* unused */ ) {
799
805
return function ( ) {
800
806
delete kills [ kid ] ;
801
- if ( util . isLeft ( killResult ) ) {
802
- fail = killResult ;
803
- step = null ;
804
- }
805
807
if ( tmp ) {
806
808
tmp = false ;
807
809
} else if ( tail === null ) {
808
- join ( fail || step , null , null ) ;
810
+ join ( step , null , null ) ;
809
811
} else {
810
- join ( fail || step , tail . _1 , tail . _2 ) ;
812
+ join ( step , tail . _1 , tail . _2 ) ;
811
813
}
812
814
} ;
813
815
} ) ;
0 commit comments