Skip to content

Commit 390857f

Browse files
authored
Fix interrupt bugs wrt bracket masking (#171)
Currently when we check interrupt status, we are not considering the bracket masking status. When we enter a masked state (`bracketCount > 0`), it should be impossible for an interrupt to terminate evaluation, but without the bracket check in CATCH, RESUME, and RELEASE, this is violated. * In a nested async generalBracket, the wrong handler is enqueued. Since we are in a masked state, it should be impossible for the `killed` branch to be invoked. However, if an interrupt occurs it is currently _always_ invoking `killed` when the branch is actually `completed`. * In a nested bracket, an interrupt will terminate evaluation of the inner bracket even though we are in a masked state. This can also happen with an inner `catch` in a masked state. We need to always consider the masked state (`bracketCount > 0`) when we discriminate the interrupt state. Fixes #170
1 parent b5240af commit 390857f

File tree

2 files changed

+66
-7
lines changed

2 files changed

+66
-7
lines changed

src/Effect/Aff.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -415,14 +415,14 @@ var Aff = function () {
415415
attempts = attempts._2;
416416

417417
switch (attempt.tag) {
418-
// We cannot recover from an interrupt. Otherwise we should
418+
// We cannot recover from an unmasked interrupt. Otherwise we should
419419
// continue stepping, or run the exception handler if an exception
420420
// was raised.
421421
case CATCH:
422422
// We should compare the interrupt status as well because we
423423
// only want it to apply if there has been an interrupt since
424424
// enqueuing the catch.
425-
if (interrupt && interrupt !== tmp) {
425+
if (interrupt && interrupt !== tmp && bracketCount === 0) {
426426
status = RETURN;
427427
} else if (fail) {
428428
status = CONTINUE;
@@ -431,11 +431,11 @@ var Aff = function () {
431431
}
432432
break;
433433

434-
// We cannot resume from an interrupt or exception.
434+
// We cannot resume from an unmasked interrupt or exception.
435435
case RESUME:
436436
// As with Catch, we only want to ignore in the case of an
437437
// interrupt since enqueing the item.
438-
if (interrupt && interrupt !== tmp || fail) {
438+
if (interrupt && interrupt !== tmp && bracketCount === 0 || fail) {
439439
status = RETURN;
440440
} else {
441441
bhead = attempt._1;
@@ -468,19 +468,21 @@ var Aff = function () {
468468
// Enqueue the appropriate handler. We increase the bracket count
469469
// because it should not be cancelled.
470470
case RELEASE:
471-
bracketCount++;
472471
attempts = new Aff(CONS, new Aff(FINALIZED, step, fail), attempts, interrupt);
473472
status = CONTINUE;
474473
// It has only been killed if the interrupt status has changed
475-
// since we enqueued the item.
476-
if (interrupt && interrupt !== tmp) {
474+
// since we enqueued the item, and the bracket count is 0. If the
475+
// bracket count is non-zero then we are in a masked state so it's
476+
// impossible to be killed.
477+
if (interrupt && interrupt !== tmp && bracketCount === 0) {
477478
step = attempt._1.killed(util.fromLeft(interrupt))(attempt._2);
478479
} else if (fail) {
479480
step = attempt._1.failed(util.fromLeft(fail))(attempt._2);
480481
} else {
481482
step = attempt._1.completed(util.fromRight(step))(attempt._2);
482483
}
483484
fail = null;
485+
bracketCount++;
484486
break;
485487

486488
case FINALIZER:

test/Test/Main.purs

+57
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,39 @@ test_kill_bracket_nested = assert "kill/bracket/nested" do
344344
, "foo/bar/run/release/bar/release"
345345
]
346346

347+
test_kill_general_bracket_nested Aff Unit
348+
test_kill_general_bracket_nested = assert "kill/bracket/general/nested" do
349+
ref <- newRef []
350+
let
351+
action s = do
352+
_ ← modifyRef ref (_ <> [ s ])
353+
pure unit
354+
355+
bracketAction s acq =
356+
generalBracket acq
357+
{ killed: \_ _ → action (s <> "/killed")
358+
, failed: \_ _ → action (s <> "/failed")
359+
, completed: \_ _ → action (s <> "/completed")
360+
}
361+
(\_ → do
362+
delay (Milliseconds 10.0)
363+
action (s <> "/run"))
364+
fiber ← forkAff do
365+
bracketAction "outer" do
366+
action "outer/acquire"
367+
bracketAction "inner" do
368+
action "inner/acquire"
369+
delay (Milliseconds 10.0)
370+
delay (Milliseconds 5.0)
371+
killFiber (error "nope") fiber
372+
readRef ref <#> eq
373+
[ "outer/acquire"
374+
, "inner/acquire"
375+
, "inner/run"
376+
, "inner/completed"
377+
, "outer/killed"
378+
]
379+
347380
test_kill_supervise Aff Unit
348381
test_kill_supervise = assert "kill/supervise" do
349382
ref ← newRef ""
@@ -667,6 +700,28 @@ test_regression_kill_sync_async = assert "regression/kill-sync-async" do
667700
killFiber (error "Nope.") f1
668701
pure true
669702

703+
test_regression_bracket_kill_mask Aff Unit
704+
test_regression_bracket_kill_mask = assert "regression/kill-bracket-mask" do
705+
ref ← newRef ""
706+
let
707+
action s = do
708+
_ <- modifyRef ref (_ <> s)
709+
pure unit
710+
fiber ← forkAff do
711+
bracket
712+
do
713+
action "a"
714+
bracket
715+
(pure unit)
716+
(const (pure unit))
717+
(\_ -> delay (Milliseconds 10.0))
718+
action "b"
719+
(const (pure unit))
720+
(\_ -> delay (Milliseconds 10.0))
721+
delay (Milliseconds 5.0)
722+
killFiber (error "nope") fiber
723+
readRef ref <#> eq "ab"
724+
670725
test_regression_kill_empty_supervisor Aff Unit
671726
test_regression_kill_empty_supervisor = assert "regression/kill-empty-supervisor" do
672727
f1 ← forkAff $ supervise $ delay $ Milliseconds 10.0
@@ -700,6 +755,7 @@ main = do
700755
test_kill_canceler
701756
test_kill_bracket
702757
test_kill_bracket_nested
758+
test_kill_general_bracket_nested
703759
test_kill_supervise
704760
test_kill_finalizer_catch
705761
test_kill_finalizer_bracket
@@ -723,4 +779,5 @@ main = do
723779
test_regression_par_apply_async_canceler
724780
test_regression_bracket_catch_cleanup
725781
test_regression_kill_sync_async
782+
test_regression_bracket_kill_mask
726783
test_regression_kill_empty_supervisor

0 commit comments

Comments
 (0)