From 6c02df28ffe592dda901fd29a924906c648baa54 Mon Sep 17 00:00:00 2001 From: Scott Haseley Date: Wed, 17 May 2023 05:19:34 -0700 Subject: [PATCH] Add AbortSignal.any() - This implements an optimization that puts all children on non-dependent signals (i.e., those associated with a controller). This allows "intermediate" nodes (e.g., B in A follows B follows C) to be garbage collected if they are being kept alive to propagate aborts. - This removes the follow algorithm, so callsites will need to be updated. - The "create a composite abort signal" algorithm takes an interface so that TaskSignal.any() can easily hook into it, but create a TaskSignal. - Some algorithms that invoke "follow" create an AbortSignal in a particular realm. This enables doing that, but borrows some language from elsewhere in the spec w.r.t. doing the default thing. Neither of the other two static members specify a realm. Follow-up PRs: - https://github.com/whatwg/fetch/pull/1646 - https://github.com/w3c/ServiceWorker/pull/1678 - https://github.com/whatwg/streams/pull/1277 This also sets the stage to make AbortSignal's "signal abort" fully internal. #1194 tracks the remainder. Tests: https://github.com/web-platform-tests/wpt/pull/37434 and https://github.com/web-platform-tests/wpt/pull/39785. Fixes #920. --- dom.bs | 167 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 129 insertions(+), 38 deletions(-) diff --git a/dom.bs b/dom.bs index 9f4f3955..c547de19 100644 --- a/dom.bs +++ b/dom.bs @@ -30,6 +30,7 @@ type: interface urlPrefix: https://tc39.es/ecma262/#; spec: ECMASCRIPT text: Construct; url: sec-construct; type: abstract-op type: dfn + text: current realm; url: current-realm text: realm; url: realm text: surrounding agent; url: surrounding-agent urlPrefix: https://w3c.github.io/hr-time/#; spec: HR-TIME @@ -1768,6 +1769,7 @@ interface AbortController {

An {{AbortController}} object has an associated signal (an {{AbortSignal}} object). +

The new AbortController() constructor steps are: @@ -1777,13 +1779,22 @@ constructor steps are:

  • Set this's signal to signal. +

  • The signal getter steps are to return this's signal. +

    The abort(reason) method steps are -to signal abort on this's signal with -reason if it is given. +to signal abort on this with reason if it is given. +

    + +
    +

    To signal abort on an {{AbortController}} +controller with an optional reason, signal abort on +controller's signal with reason if it is given. +

    +

    Interface {{AbortSignal}}

    @@ -1792,6 +1803,7 @@ to signal abort on this's s interface AbortSignal : EventTarget { [NewObject] static AbortSignal abort(optional any reason); [Exposed=(Window,Worker), NewObject] static AbortSignal timeout([EnforceRange] unsigned long long milliseconds); + [NewObject] static AbortSignal _any(sequence<AbortSignal> signals); readonly attribute boolean aborted; readonly attribute any reason; @@ -1805,6 +1817,11 @@ interface AbortSignal : EventTarget {
    Returns an {{AbortSignal}} instance whose abort reason is set to reason if not undefined; otherwise to an "{{AbortError!!exception}}" {{DOMException}}. +
    AbortSignal . any(signals) +
    Returns an {{AbortSignal}} instance which will be aborted once any of signals is + aborted. Its abort reason will be set to whichever one of signals + caused it to be aborted. +
    AbortSignal . timeout(milliseconds)
    Returns an {{AbortSignal}} instance which will be aborted in milliseconds milliseconds. Its abort reason will be set to a @@ -1821,35 +1838,32 @@ interface AbortSignal : EventTarget { {{AbortController}} has signaled to abort; otherwise, does nothing. -

    An {{AbortSignal}} object has an associated abort reason, which is a -JavaScript value. It is undefined unless specified otherwise. - -

    An {{AbortSignal}} object is aborted when its -[=AbortSignal/abort reason=] is not undefined. - -

    An {{AbortSignal}} object has associated abort algorithms, which is a -set of algorithms which are to be executed when it is [=AbortSignal/aborted=]. Unless -specified otherwise, its value is the empty set. - -

    To add an algorithm algorithm to an {{AbortSignal}} -object signal, run these steps: - -

      -
    1. If signal is [=AbortSignal/aborted=], then return. - -

    2. Append algorithm to signal's - abort algorithms. -

    +

    An {{AbortSignal}} object has an associated abort reason (a +JavaScript value), which is initially undefined. -

    To remove an algorithm algorithm from an -{{AbortSignal}} signal, remove algorithm from -signal's abort algorithms. +

    An {{AbortSignal}} object has associated abort algorithms, (a +set of algorithms which are to be executed when it is [=AbortSignal/aborted=]), +which is initially empty.

    The [=AbortSignal/abort algorithms=] enable APIs with complex requirements to react in a reasonable way to {{AbortController/abort()}}. For example, a given API's [=AbortSignal/abort reason=] might need to be propagated to a cross-thread environment, such as a service worker. +

    An {{AbortSignal}} object has a dependent (a boolean), which is +initially false. + +

    An {{AbortSignal}} object has associated source signals (a weak +set of {{AbortSignal}} objects that the object is dependent on for its +[=AbortSignal/aborted=] state), which is initially empty. + +

    An {{AbortSignal}} object has associated dependent signals (a weak +set of {{AbortSignal}} objects that are dependent on the object for their +[=AbortSignal/aborted=] state), which is initially empty. + +


    + +

    The static abort(reason) method steps are: @@ -1861,7 +1875,9 @@ are:

  • Return signal. +
  • +

    The static timeout(milliseconds) method steps are: @@ -1886,6 +1902,13 @@ steps are:

  • Return signal. +

  • + +
    +

    The static any(signals) method +steps are to return the result of creating a dependent abort signal from signals +using {{AbortSignal}} and the current realm. +

    The aborted getter steps are to return true if this is [=AbortSignal/aborted=]; otherwise false. @@ -1924,11 +1947,35 @@ is [=AbortSignal/aborted=]; otherwise false. abort.

    Changes to an {{AbortSignal}} object represent the wishes of the corresponding -{{AbortController}} object, but an API observing the {{AbortSignal}} object can chose to ignore +{{AbortController}} object, but an API observing the {{AbortSignal}} object can choose to ignore them. For instance, if the operation has already completed. +


    + +

    An {{AbortSignal}} object is aborted when its +[=AbortSignal/abort reason=] is not undefined. + +

    +

    To add an algorithm algorithm to an {{AbortSignal}} +object signal: + +

      +
    1. If signal is [=AbortSignal/aborted=], then return. + +

    2. Append algorithm to signal's + abort algorithms. +

    +
    + +
    +

    To remove an algorithm algorithm from an +{{AbortSignal}} signal, remove algorithm from +signal's abort algorithms. +

    + +

    To signal abort, given an {{AbortSignal}} object -signal and an optional reason, run these steps: +signal and an optional reason:

    1. If signal is [=AbortSignal/aborted=], then return. @@ -1936,34 +1983,78 @@ them. For instance, if the operation has already completed.

    2. Set signal's [=AbortSignal/abort reason=] to reason if it is given; otherwise to a new "{{AbortError!!exception}}" {{DOMException}}. -

    3. For each algorithm in signal's +

    4. For each algorithm of signal's [=AbortSignal/abort algorithms=]: run algorithm.

    5. Empty signal's abort algorithms.

    6. [=Fire an event=] named {{AbortSignal/abort}} at signal. + +

    7. For each dependentSignal of signal's + [=AbortSignal/dependent signals=], [=AbortSignal/signal abort=] on dependentSignal with + signal's [=AbortSignal/abort reason=].

    +
    -

    A followingSignal (an {{AbortSignal}}) is made to -follow a parentSignal (an {{AbortSignal}}) by running -these steps: +

    +

    To create a dependent abort signal from a list of {{AbortSignal}} objects +signals, using signalInterface, which must be either {{AbortSignal}} or an +interface that inherits from it, and a realm:

      -
    1. If followingSignal is [=AbortSignal/aborted=], then return. +

    2. Let resultSignal be a new object implementing + signalInterface using realm. -

    3. If parentSignal is [=AbortSignal/aborted=], then - signal abort on followingSignal with parentSignal's - [=AbortSignal/abort reason=]. +

    4. For each signal of signals: if signal is + [=AbortSignal/aborted=], then set resultSignal's [=AbortSignal/abort reason=] to + signal's [=AbortSignal/abort reason=] and return resultSignal. + +

    5. Set resultSignal's [=AbortSignal/dependent=] to true.

    6. -

      Otherwise, add the following abort steps to - parentSignal: +

      For each signal of signals:

        -
      1. Signal abort on followingSignal with - parentSignal's [=AbortSignal/abort reason=]. +

      2. +

        If signal's [=AbortSignal/dependent=] is false, then: + +

          +
        1. Append signal to resultSignal's + [=AbortSignal/source signals=]. + +

        2. Append resultSignal to signal's + [=AbortSignal/dependent signals=]. +

        + +
      3. +

        Otherwise, for each sourceSignal of signal's + [=AbortSignal/source signals=]: + +

          +
        1. Assert: sourceSignal is not [=AbortSignal/aborted=] and not + [=AbortSignal/dependent=]. + +

        2. If resultSignal's [=AbortSignal/source signals=] contains + sourceSignal, then continue. + +

        3. Append sourceSignal to resultSignal's + [=AbortSignal/source signals=]. + +

        4. Append resultSignal to sourceSignal's + [=AbortSignal/dependent signals=]. +

      + +
    7. Return resultSignal.

    +
    + + +

    Garbage collection

    + +

    A non-[=AbortSignal/aborted=] [=AbortSignal/dependent=] {{AbortSignal}} object must not be +garbage collected while its [=AbortSignal/source signals=] is non-empty and it has registered event +listeners for its {{AbortSignal/abort}} event or its [=AbortSignal/abort algorithms=] is non-empty.

    Using {{AbortController}} and {{AbortSignal}} objects in