@@ -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 ;
@@ -556,10 +568,8 @@ var Aff = function () {
556
568
}
557
569
if ( bracketCount === 0 ) {
558
570
if ( status === PENDING ) {
559
- attempts = new Aff ( CONS , step ( error ) , attempts ) ;
571
+ attempts = new Aff ( CONS , new Aff ( FINALIZER , step ( error ) ) , attempts , interrupt ) ;
560
572
}
561
- bhead = null ;
562
- btail = null ;
563
573
status = RETURN ;
564
574
step = null ;
565
575
fail = null ;
@@ -571,8 +581,6 @@ var Aff = function () {
571
581
interrupt = util . left ( error ) ;
572
582
}
573
583
if ( bracketCount === 0 ) {
574
- bhead = null ;
575
- btail = null ;
576
584
status = RETURN ;
577
585
step = null ;
578
586
fail = null ;
0 commit comments