diff --git a/src/Effect/Aff.js b/src/Effect/Aff.js index ff8bca2..8db9ea8 100644 --- a/src/Effect/Aff.js +++ b/src/Effect/Aff.js @@ -56,6 +56,12 @@ var Aff = function () { var FIBER = "Fiber"; // Actual fiber reference var THUNK = "Thunk"; // Primed effect, ready to invoke + // Error used for early cancelation on Alt branches. + // This is initialized here (rather than in the Fiber constructor) because + // otherwise, in V8, this Error object indefinitely hangs on to memory that + // otherwise would be garbage collected. + var early = new Error("[ParAff] Early exit"); + function Aff(tag, _1, _2, _3) { this.tag = tag; this._1 = _1; @@ -160,7 +166,7 @@ var Aff = function () { delete fibers[fid]; }; } - }); + })(); fibers[fid] = fiber; count++; }, @@ -320,24 +326,41 @@ var Aff = function () { case ASYNC: status = PENDING; - step = runAsync(util.left, step._1, function (result) { - return function () { - if (runTick !== localRunTick) { - return; - } - runTick++; - Scheduler.enqueue(function () { - // It's possible to interrupt the fiber between enqueuing and - // resuming, so we need to check that the runTick is still - // valid. - if (runTick !== localRunTick + 1) { + tmp = step._1; + step = nonCanceler; + Scheduler.enqueue(function () { + if (runTick !== localRunTick) { + return; + } + var skipRun = true; + var canceler = runAsync(util.left, tmp, function (result) { + return function () { + if (runTick !== localRunTick) { return; } + ++runTick; status = STEP_RESULT; - step = result; - run(runTick); - }); - }; + step = result; + // Do not recurse on run if we are synchronous with runAsync. + if (skipRun) { + skipRun = false; + } else { + run(runTick); + } + }; + }); + // Only update the canceler if the asynchronous action has not + // resolved synchronously. If it has, then the next status and + // step have already been set. + if (skipRun) { + step = canceler; + skipRun = false; + } + // If runAsync already resolved then the next step needs to be + // run. + else { + run(runTick); + } }); return; @@ -643,9 +666,6 @@ var Aff = function () { var killId = 0; var kills = {}; - // Error used for early cancelation on Alt branches. - var early = new Error("[ParAff] Early exit"); - // Error used to kill the entire tree. var interrupt = null; diff --git a/src/Effect/Aff.purs b/src/Effect/Aff.purs index 6dbef67..37c7ae0 100644 --- a/src/Effect/Aff.purs +++ b/src/Effect/Aff.purs @@ -86,8 +86,11 @@ instance monoidAff ∷ Monoid a ⇒ Monoid (Aff a) where instance altAff ∷ Alt Aff where alt a1 a2 = catchError a1 (const a2) +alwaysFailsError ∷ Error +alwaysFailsError = error "Always fails" + instance plusAff ∷ Plus Aff where - empty = throwError (error "Always fails") + empty = throwError alwaysFailsError -- | This instance is provided for compatibility. `Aff` is always stack-safe -- | within a given fiber. This instance will just result in unnecessary @@ -306,6 +309,9 @@ type Supervised a = , supervisor ∷ Supervisor } +parentOutlivedError ∷ Error +parentOutlivedError = error "[Aff] Child fiber outlived parent" + -- | Creates a new supervision context for some `Aff`, guaranteeing fiber -- | cleanup when the parent completes. Any pending fibers forked within -- | the context will be killed and have their cancelers run. @@ -313,14 +319,11 @@ supervise ∷ ∀ a. Aff a → Aff a supervise aff = generalBracket (liftEffect acquire) { killed: \err sup → parSequence_ [ killFiber err sup.fiber, killAll err sup ] - , failed: const (killAll killError) - , completed: const (killAll killError) + , failed: const (killAll parentOutlivedError) + , completed: const (killAll parentOutlivedError) } (joinFiber <<< _.fiber) where - killError ∷ Error - killError = - error "[Aff] Child fiber outlived parent" killAll ∷ Error → Supervised a → Aff Unit killAll err sup = makeAff \k →