@@ -48,9 +48,9 @@ var Aff = function () {
4848
4949 // Various constructors used in interpretation
5050 var CONS = "Cons" ; // Cons-list, for stacks
51- var RECOVER = "Recover" ; // Continue with error handler
5251 var RESUME = "Resume" ; // Continue indiscriminately
5352 var RELEASE = "Release" ; // Continue with bracket finalizers
53+ var FINALIZER = "Finalizer" ; // A non-interruptible effect
5454 var FINALIZED = "Finalized" ; // Marker for finalization
5555 var FORKED = "Forked" ; // Reference to a forked fiber, with resumption stack
5656 var FIBER = "Fiber" ; // Actual fiber reference
@@ -239,7 +239,9 @@ var Aff = function () {
239239 var bhead = null ;
240240 var btail = null ;
241241
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.
243245 var attempts = null ;
244246
245247 // A special state is needed for Bracket, because it cannot be killed. When
@@ -335,30 +337,33 @@ var Aff = function () {
335337 return ;
336338
337339 case THROW :
338- bhead = null ;
339- btail = null ;
340340 status = RETURN ;
341341 fail = util . left ( step . _1 ) ;
342342 step = null ;
343343 break ;
344344
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.
346347 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+ }
348353 bhead = null ;
349354 btail = null ;
350355 status = CONTINUE ;
351356 step = step . _1 ;
352357 break ;
353358
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.
356361 case BRACKET :
357362 bracketCount ++ ;
358363 if ( bhead === null ) {
359- attempts = new Aff ( CONS , step , attempts ) ;
364+ attempts = new Aff ( CONS , step , attempts , interrupt ) ;
360365 } 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 ) ;
362367 }
363368 bhead = null ;
364369 btail = null ;
@@ -386,40 +391,42 @@ var Aff = function () {
386391 break ;
387392
388393 case RETURN :
394+ bhead = null ;
395+ btail = null ;
389396 // If the current stack has returned, and we have no other stacks to
390397 // resume or finalizers to run, the fiber has halted and we can
391398 // invoke all join callbacks. Otherwise we need to resume.
392399 if ( attempts === null ) {
393400 status = COMPLETED ;
394401 step = interrupt || fail || step ;
395402 } else {
403+ // The interrupt status for the enqueued item.
404+ tmp = attempts . _3 ;
396405 attempt = attempts . _1 ;
397406 attempts = attempts . _2 ;
398407
399408 switch ( attempt . tag ) {
400409 // We cannot recover from an interrupt. Otherwise we should
401410 // continue stepping, or run the exception handler if an exception
402411 // 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 ) {
405417 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 ;
417422 }
418423 break ;
419424
420425 // We cannot resume from an interrupt or exception.
421426 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 ) {
423430 status = RETURN ;
424431 } else {
425432 bhead = attempt . _1 ;
@@ -437,42 +444,47 @@ var Aff = function () {
437444 bracketCount -- ;
438445 if ( fail === null ) {
439446 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 ) {
442453 status = CONTINUE ;
443454 step = attempt . _3 ( result ) ;
444455 }
445456 }
446457 break ;
447458
448459 // Enqueue the appropriate handler. We increase the bracket count
449- // because it should be cancelled.
460+ // because it should not be cancelled.
450461 case RELEASE :
451462 bracketCount ++ ;
452- attempts = new Aff ( CONS , new Aff ( FINALIZED , step ) , attempts ) ;
463+ attempts = new Aff ( CONS , new Aff ( FINALIZED , step ) , attempts , interrupt ) ;
453464 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 ) {
455468 step = attempt . _1 . killed ( util . fromLeft ( interrupt ) ) ( attempt . _2 ) ;
456- } else if ( fail !== null ) {
469+ } else if ( fail ) {
457470 step = attempt . _1 . failed ( util . fromLeft ( fail ) ) ( attempt . _2 ) ;
458471 } else {
459472 step = attempt . _1 . completed ( util . fromRight ( step ) ) ( attempt . _2 ) ;
460473 }
461474 break ;
462475
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+
463483 case FINALIZED :
464484 bracketCount -- ;
465485 status = RETURN ;
466486 step = attempt . _1 ;
467487 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 ;
476488 }
477489 }
478490 break ;
@@ -556,10 +568,8 @@ var Aff = function () {
556568 }
557569 if ( bracketCount === 0 ) {
558570 if ( status === PENDING ) {
559- attempts = new Aff ( CONS , step ( error ) , attempts ) ;
571+ attempts = new Aff ( CONS , new Aff ( FINALIZER , step ( error ) ) , attempts , interrupt ) ;
560572 }
561- bhead = null ;
562- btail = null ;
563573 status = RETURN ;
564574 step = null ;
565575 fail = null ;
@@ -571,8 +581,6 @@ var Aff = function () {
571581 interrupt = util . left ( error ) ;
572582 }
573583 if ( bracketCount === 0 ) {
574- bhead = null ;
575- btail = null ;
576584 status = RETURN ;
577585 step = null ;
578586 fail = null ;
0 commit comments