Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

editorial: Tidy up uses of "in parallel". #299

Merged
merged 1 commit into from Mar 22, 2021

Conversation

rakuco
Copy link
Member

@rakuco rakuco commented Feb 5, 2021

Steps that run "in parallel" need to take several extra restrictions into
account: we do not have access to documents or global objects, cannot create
IDL interfaces or manipulate promises, for example, all of which we had been
doing, along with needlessly running nested "in parallel" steps.

This change attempts to rectify the situation by only running things in
parallel when absolutely necessary (i.e. when reaching out to the OS), and
queuing global tasks (emphasis on "global" given whatwg/html#4980 and
whatwg/html#5653) from there when we need to manipulate promises or create
objects.

"Obtain permission" algorithm:

  • Stop running in parallel. Callers should be responsible for choosing
    whether it should be run in parallel or not.

WakeLock.request():

  • Separate returning a promise and running steps in parallel. This style is
    more usual.
  • Refer to the "relevant settings object" rather than the "current settings
    object", as we are inside a method of an IDL interface and can rely on it
    being defined. I do not think this is a user-visible change, and it looks
    cleaner.
  • Queue a global task to reject |promise| when the permission request run
    in parallel is denied.
  • Manipulate the [[ActiveLocks]] internal slot, check page visibility,
    invoke "acquire a wake lock", create a new WakeLockSentinel and resolve
    the returned promise in a queued global task.

WakeLockSentinel.release():

  • Do not run the "release a wake lock" algorithm in parallel (see the
    changes to the algorithm itself below).
  • Just return a resolved promise once the rest of the steps run. The
    returned promise does not have much use, but has been kept to avoid
    breaking API compatibility.
  • One user-visible consequence is that the "release" event is fired
    synchronously and before the function returns.

"Acquire wake lock" algorithm:

  • Stop running everything in parallel.
  • Move all [[ActiveLocks]] manipulation to WakeLock.request() itself,
    and make the algorithm just ask the platform for a wake lock.

"Release wake lock" algorithm:

  • Stop running everything in parallel. The only steps that still need to run
    in parallel are the ones asking the platform to release the wake lock.
  • Consequently, stop queueing a task to change [[Released]] and fire the
    "release" event, and do it directly in the algorithm instead.

Big thanks to Anne van Kesteren, Domenic Denicola and Marcos Cáceres for
their input.

Fixes #293.


Open items:

  • How does one check if |document| is hidden in WakeLock.request()'s parallel steps? Or, more generally speaking, how can we avoid reaching the steps where we ask the platform for a lock if the page was hidden, for example?
  • WakeLock.request(): does it make sense to have to separate "in parallel" steps to first acquire a platform lock and then manipulate [[ActiveLocks]]?
  • WakeLock.request(): does manipulating [[ActiveLocks]] from a queued task leave the algorithm open to race conditions?
  • WakeLockSentinel.release(): the Web Platform Design Principles say "events should fire before Promises resolve". Do the changes make sense here? We are changing [[Released]] and firing the "released" event "synchronously" in the event loop before even creating the resolved promise we are returning. This feels a bit odd, but on the other hand I'm not sure how to avoid running the "release wake lock" algorithm in parallel if we create the promise first.
    • This one of the things that led to "Release a wake lock": should we queue a task to fire the "release" event? #293: if we queue a task to fire the event, it might not be delivered at all. Is that OK? Should [[Released]] be changed synchronously though, so that e.g. when we are navigating to another page, should .released is true in the "visibilitychange" event handler even though the "release" event has not been dispatched?

The following tasks have been completed:

Implementation commitment:


Preview | Diff

@rakuco
Copy link
Member Author

rakuco commented Feb 5, 2021

@annevk @domenic this is a first attempt to tackle #293 after the discussion we've had there. I'd appreciate it if you could see if this makes sense and look at the "open items" in the PR message with things that are still unclear to me.

index.html Outdated Show resolved Hide resolved
index.html Outdated Show resolved Hide resolved
@reillyeon
Copy link
Member

While you are here fixing the "in parallel" sections this specification should be updated to define a task source onto which the tasks returning to main thread are queued.

@rakuco
Copy link
Member Author

rakuco commented Feb 6, 2021

While you are here fixing the "in parallel" sections this specification should be updated to define a task source onto which the tasks returning to main thread are queued.

Do you think it's better to come up with a new task source, or would it make sense to use the user interaction task source?

@rakuco
Copy link
Member Author

rakuco commented Feb 8, 2021

Do you think it's better to come up with a new task source, or would it make sense to use the user interaction task source?

Per w3c/presentation-api#360 and w3ctag/design-principles#38 it looks like it makes more sense to define another task source.

@rakuco
Copy link
Member Author

rakuco commented Feb 11, 2021

@annevk @domenic friendly ping, it'd be great if you could take a look at the proposed changes here and/or the questions in the PR message which I couldn't answer yet.

@annevk
Copy link
Member

annevk commented Feb 11, 2021

Some thoughts:

  • obtain permission shouldn't run in parallel, it should be invoked from in parallel, but it doesn't make sense for it itself to then also go in parallel again (in parallel steps cannot return things)
  • A slightly clearer style is to say "run these steps in parallel" and then have a subsequent step return the promise.
  • You really want to use "queue a global task" and always pass it the global and task source to use explicitly. That's the way forward.

In parallel usage otherwise seems reasonable from a quick skim. (If you're still not too sure about it, HTML has a bunch of guidance language for it, it's basically akin to a thread/process. Also happy to chat at https://whatwg.org/irc or equivalent.)

Questions:

  • I'm not sure about hidden documents. There are some issues about this against HTML as well. (Checking the hidden attribute is definitely wrong though. You don't want to invoke IDL members in your algorithms as a general rule.)
  • I'm not sure I understand this.
  • Yeah, it seems "acquire a wake lock" could be run twice if you invoke the method twice.
  • The order of event/promise in release() seems fine. It might be clearer to run "release a wake lock" when the slot is false and then have a single return step at the end.
    • If you don't want release() to fire an event directly (though not sure if it matters much), you could make it queue a task to run "release a wake lock" and resolve the promise with undefined. That way other callers of "release a wake lock" would still dispatch the event in whatever task they find themselves in.

@rakuco
Copy link
Member Author

rakuco commented Feb 11, 2021

Thanks for the review, @annevk. I've pushed some new commits addressing some of the points you've brought up.

Some thoughts:

  • obtain permission shouldn't run in parallel, it should be invoked from in parallel, but it doesn't make sense for it itself to then also go in parallel again (in parallel steps cannot return things)

I was hoping to get rid of the algorithm altogether in #298, but in the meantime I've dropped the "in parallel" bit in this PR.

  • A slightly clearer style is to say "run these steps in parallel" and then have a subsequent step return the promise.

Done.

  • You really want to use "queue a global task" and always pass it the global and task source to use explicitly. That's the way forward.

Is there any particular reason for using "global" here? I recently defined a task source in #301 and was using the regular "queue a task" language with it.

Questions:

  • I'm not sure about hidden documents. There are some issues about this against HTML as well. (Checking the hidden attribute is definitely wrong though. You don't want to invoke IDL members in your algorithms as a general rule.)

Hmm, I wonder if the algorithms in "handling document loss of {full activity, visibility}" could set some flag that could be checked in the parallel steps of WakeLock.request(). Some sort of "abort when some-flag is set // if aborted reject promise".

  • Yeah, it seems "acquire a wake lock" could be run twice if you invoke the method twice.

Would it be OK (from an idiomatic perspective) to remove the check altogether and sort of under-specify the behavior by leaving it up to each implementation to determine whether to ask the platform for a lock?

  • The order of event/promise in release() seems fine. It might be clearer to run "release a wake lock" when the slot is false and then have a single return step at the end.

Thanks, done.

  • If you don't want release() to fire an event directly (though not sure if it matters much), you could make it queue a task to run "release a wake lock" and resolve the promise with undefined. That way other callers of "release a wake lock" would still dispatch the event in whatever task they find themselves in.

I'm trying to determine if there's some general guidance here. If it's basically "fire events away and only queue a task to fire them if necessary", then I'm fine with the changes being done here.

@annevk
Copy link
Member

annevk commented Feb 11, 2021

Is there any particular reason for using "global" here?

Yes, it helps ensure the task is queued on the correct event loop and all the relevant bits are set correctly. (Though @domenic has been suggesting that we have some kind of ambient global available for constructing objects we could also repurpose here perhaps...)

Would it be OK (from an idiomatic perspective) to remove the check altogether and sort of under-specify the behavior by leaving it up to each implementation to determine whether to ask the platform for a lock?

Maybe, not sure. It seems easy enough to add some boolean that you can use to prevent this, no?

If it's basically "fire events away and only queue a task to fire them if necessary", then I'm fine with the changes being done here.

It depends on the cause and the target, I think here it's fine. If it was in response to a DOM mutation I'd ask for a change.

rakuco added a commit to rakuco/wake-lock that referenced this pull request Feb 15, 2021
* @annevk says in w3c#299 that, as a general rule, invoking IDL members from
  algorithms, like we were doing with `document.hidden`, is not recommended.
  Instead, invoke the "determine the visibility state" steps defined in the
  Page Visibility spec directly.

* Clean up the "Dependencies" section; for now we only need one term that is
  not exported by the latest Page Visibility TR (this is fixed in the ED):
  the "now hidden algorithm". The steps described in the "Handling document
  loss of visibility" steps now explicitly run after that algorithm, which
  runs when the top-level browsing context's Document becomes hidden.

More generally, these changes should also make it more clear that the
visibility state checks only apply to the top-level browsing
context (according to the Page Visibility spec itself). Although we were
previously checking if a given `Document` was `hidden` even if it did not
belong to a top-level browsing context, that was simply invalid.
@rakuco
Copy link
Member Author

rakuco commented Feb 15, 2021

@annevk I've pushed a few more commits. I think I've managed to solve everything, but I am not entirely sure doing things from a queued global task is enough to avoid race conditions and not being allowed to refer to global objects:

  • Switched to "queue a global task"
  • Switched to manipulating the [[ActiveLocks]] list only in the main event loop (from a queued global task in WakeLock.request() and without doing anything special in the "Release a wake lock" algorithm)
  • I think I've solved the page visibility issue by checking it inside the queued global task in WakeLock.request() (I'm adjusting the wording more broadly in editorial: Improve references to terms from the Page Visibility spec. #306).

Copy link
Member

@marcoscaceres marcoscaceres left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some drive by feedback...

index.html Outdated Show resolved Hide resolved
index.html Outdated Show resolved Hide resolved
index.html Outdated Show resolved Hide resolved
index.html Outdated Show resolved Hide resolved
index.html Show resolved Hide resolved
rakuco added a commit that referenced this pull request Feb 24, 2021
…#306)

* @annevk says in #299 that, as a general rule, invoking IDL members from
  algorithms, like we were doing with `document.hidden`, is not recommended.
  Instead, invoke the "determine the visibility state" steps defined in the
  Page Visibility spec directly.

* Clean up the "Dependencies" section; for now we only need one term that is
  not exported by the latest Page Visibility TR (this is fixed in the ED):
  the "now hidden algorithm". The steps described in the "Handling document
  loss of visibility" steps now explicitly run after that algorithm, which
  runs when the top-level browsing context's Document becomes hidden.

More generally, these changes should also make it more clear that the
visibility state checks only apply to the top-level browsing
context (according to the Page Visibility spec itself). Although we were
previously checking if a given `Document` was `hidden` even if it did not
belong to a top-level browsing context, that was simply invalid.
<li>Let |document:Document| be the [=environment settings object /
responsible document=] of the <a>current settings object</a>.
responsible document=] of <a>this</a>' <a>relevant settings
object</a>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Domenic just used "

  • Let |document:Document| be the [=current settings object=]'s [=associated Document=]."

  • Copy link
    Member Author

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    I think it makes sense to clean this up in #307 rather than here, as it's part of the effort to stop mentioning "responsible document" altogether.

    @rakuco rakuco marked this pull request as ready for review February 25, 2021 14:46
    @rakuco
    Copy link
    Member Author

    rakuco commented Feb 25, 2021

    @annevk @domenic @kenchris @marcoscaceres I think this PR is finally ready. All uses of "in parallel" have been audited, I think WakeLock.request() is free of race conditions when manipulating [[ActiveLocks]] and we are not checking anything from the main event loop when in parallel. The questions that seems to remain is there should be a queued task to fire the "release" event and change [[Released]] to true, which is what motivated #293 in the first place. My understanding based on this comment above is that in this case there is no need to queue a task and we can do things directly instead.

    There are further cleanups that @domenic is doing in #307 related to the use of "responsible document"; I think it makes sense to land them separately there.

    Copy link
    Contributor

    @domenic domenic left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Looking very solid; just some minor issues with nesting.

    index.html Outdated Show resolved Hide resolved
    index.html Show resolved Hide resolved
    index.html Outdated Show resolved Hide resolved
    index.html Show resolved Hide resolved
    index.html Show resolved Hide resolved
    index.html Outdated Show resolved Hide resolved
    index.html Outdated Show resolved Hide resolved
    rakuco added a commit to rakuco/wake-lock that referenced this pull request Feb 27, 2021
    Incorporate suggestions made by @domenic in w3c#299:
    * Steps 1.1 and 1.2 which checked `document` should not be substeps of step
      1, but siblings just like other subsequent steps that also check
      `document`.
    * Checking the value of `state` should be done at the same nesting level of
      the step that sets `state`, not as a nested substep.
    
    These changes should have no user-visible effects.
    rakuco added a commit to rakuco/wake-lock that referenced this pull request Feb 27, 2021
    Incorporate suggestions made by @domenic in w3c#299:
    * Steps 1.1 and 1.2 which checked `document` should not be substeps of step
      1, but siblings just like other subsequent steps that also check
      `document`.
    * Checking the value of `state` should be done at the same nesting level of
      the step that sets `state`, not as a nested substep.
    
    These changes should have no user-visible effects.
    rakuco added a commit to rakuco/wake-lock that referenced this pull request Feb 27, 2021
    This is the preferred form as found e.g. in the Web IDL and Fetch specs.
    Originally spotted by @domenic while reviewing w3c#299.
    rakuco added a commit that referenced this pull request Feb 27, 2021
    This is the preferred form as found e.g. in the Web IDL and Fetch specs.
    Originally spotted by @domenic while reviewing #299.
    rakuco added a commit that referenced this pull request Feb 27, 2021
    …#308)
    
    Incorporate suggestions made by @domenic in #299:
    * Steps 1.1 and 1.2 which checked `document` should not be substeps of step
      1, but siblings just like other subsequent steps that also check
      `document`.
    * Checking the value of `state` should be done at the same nesting level of
      the step that sets `state`, not as a nested substep.
    
    These changes should have no user-visible effects.
    Copy link
    Member

    @marcoscaceres marcoscaceres left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    LGTM.

    @marcoscaceres
    Copy link
    Member

    @rakuco, any blockers or anything you need help with here?

    @rakuco
    Copy link
    Member Author

    rakuco commented Mar 18, 2021

    @rakuco, any blockers or anything you need help with here?

    Nope, thanks a lot for reviewing and accepting the PR, and sorry I haven't merged it yet. I've been dragged into other stuff but will try to check if any tests need to be adjusted and merge it today or tomorrow.

    @marcoscaceres
    Copy link
    Member

    Great to hear! thanks for the update, @rakuco!

    rakuco added a commit to rakuco/wpt that referenced this pull request Mar 19, 2021
    Besides tidying up uses of "in parallel" in algorithms, that spec change
    also makes the "release wake lock" algorithm fire the "release" event and
    change the `[[Released]]` slot immediately rather than in a queued task.
    
    Adjust some tests to make sure the new behavior is properly verified here.
    rakuco added a commit to rakuco/wpt that referenced this pull request Mar 22, 2021
    Besides tidying up uses of "in parallel" in algorithms, that spec change
    also makes the "release wake lock" algorithm fire the "release" event and
    change the `[[Released]]` slot immediately rather than in a queued task.
    
    Adjust some tests to make sure the new behavior is properly verified here.
    Steps that run "in parallel" need to take several extra restrictions into
    account: we do not have access to documents or global objects, cannot create
    IDL interfaces or manipulate promises, for example, all of which we had been
    doing, along with needlessly running nested "in parallel" steps.
    
    This change attempts to rectify the situation by only running things in
    parallel when absolutely necessary (i.e. when reaching out to the OS), and
    queuing global tasks (emphasis on "global" given whatwg/html#4980 and
    whatwg/html#5653) from there when we need to manipulate promises or create
    objects.
    
    "Obtain permission" algorithm:
    * Stop running in parallel. Callers should be responsible for choosing
      whether it should be run in parallel or not.
    
    `WakeLock.request()`:
    * Separate returning a promise and running steps in parallel. This style is
      more usual.
    * Refer to the "relevant settings object" rather than the "current settings
      object", as we are inside a method of an IDL interface and can rely on it
      being defined. I do not think this is a user-visible change, and it looks
      cleaner.
    * Queue a global task to reject `|promise|` when the permission request run
      in parallel is denied.
    * Manipulate the `[[ActiveLocks]]` internal slot, check page visibility,
      invoke "acquire a wake lock", create a new WakeLockSentinel and resolve
      the returned promise in a queued global task.
    
    `WakeLockSentinel.release()`:
    * Do not run the "release a wake lock" algorithm in parallel (see the
      changes to the algorithm itself below).
    * Just return a resolved promise once the rest of the steps run. The
      returned promise does not have much use, but has been kept to avoid
      breaking API compatibility.
    * One user-visible consequence is that the "release" event is fired
      synchronously and before the function returns.
    
    "Acquire wake lock" algorithm:
    * Stop running everything in parallel.
    * Move all `[[ActiveLocks]]` manipulation to `WakeLock.request()` itself,
      and make the algorithm just ask the platform for a wake lock.
    
    "Release wake lock" algorithm:
    * Stop running everything in parallel. The only steps that still need to run
      in parallel are the ones asking the platform to release the wake lock.
    * Consequently, stop queueing a task to change `[[Released]]` and fire the
      "release" event, and do it directly in the algorithm instead.
    
    Big thanks to Anne van Kesteren, Domenic Denicola and Marcos Cáceres for
    their input.
    
    Fixes w3c#293.
    @rakuco
    Copy link
    Member Author

    rakuco commented Mar 22, 2021

    I'd like to thank everyone once again for reviewing this patch and for all the discussions. I've updated the PR message and included links to the Chromium bug and WPT PR that this change has originated. Since there have been no objections and the PR in WPT is close to being merged, I'm finally going to merge this one.

    @rakuco rakuco merged commit 1e380dc into w3c:gh-pages Mar 22, 2021
    @rakuco rakuco deleted the in-parallel-rework branch March 22, 2021 11:44
    rakuco added a commit to web-platform-tests/wpt that referenced this pull request Mar 23, 2021
    Besides tidying up uses of "in parallel" in algorithms, that spec change
    also makes the "release wake lock" algorithm fire the "release" event and
    change the `[[Released]]` slot immediately rather than in a queued task.
    
    Adjust some tests to make sure the new behavior is properly verified here.
    moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this pull request Mar 27, 2021
    …ke-lock#299, a=testonly
    
    Automatic update from web-platform-tests
    screen-wake-lock: Adapt to w3c/screen-wake-lock#299 (#28148)
    
    Besides tidying up uses of "in parallel" in algorithms, that spec change
    also makes the "release wake lock" algorithm fire the "release" event and
    change the `[[Released]]` slot immediately rather than in a queued task.
    
    Adjust some tests to make sure the new behavior is properly verified here.
    --
    
    wpt-commits: 36aa2a490d8aabdb15436e62910fe87111fddbdf
    wpt-pr: 28148
    pull bot pushed a commit to Mu-L/chromium that referenced this pull request Mar 30, 2021
    This spec change reworked the way "in parallel" is used in normative
    text. In terms of implementation, the only thing that needs to change is
    the way we set WakeLockSentinel::released_ and fire the "release" event.
    This is now done synchronously rather than from a queued task, which
    means WakeLockSentinel.release() will immediately perform those actions
    rather than return a promise first. The promise is not really necessary,
    but it has been kept in the spec to avoid making a
    backwards-incompatible change.
    
    The corresponding test changes are being landed directly in WPT and once
    they are imported the new -expected.txt will be removed automatically:
    web-platform-tests/wpt#28148
    
    Bug: 1190110
    Change-Id: Ia9fc0174d36f70a7f7c8ede90c5992b07e1c69ec
    Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2778411
    Reviewed-by: Reilly Grant <reillyg@chromium.org>
    Commit-Queue: Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
    Cr-Commit-Position: refs/heads/master@{#867272}
    @rakuco rakuco mentioned this pull request Sep 27, 2022
    4 tasks
    mjfroman pushed a commit to mjfroman/moz-libwebrtc-third-party that referenced this pull request Oct 14, 2022
    This spec change reworked the way "in parallel" is used in normative
    text. In terms of implementation, the only thing that needs to change is
    the way we set WakeLockSentinel::released_ and fire the "release" event.
    This is now done synchronously rather than from a queued task, which
    means WakeLockSentinel.release() will immediately perform those actions
    rather than return a promise first. The promise is not really necessary,
    but it has been kept in the spec to avoid making a
    backwards-incompatible change.
    
    The corresponding test changes are being landed directly in WPT and once
    they are imported the new -expected.txt will be removed automatically:
    web-platform-tests/wpt#28148
    
    Bug: 1190110
    Change-Id: Ia9fc0174d36f70a7f7c8ede90c5992b07e1c69ec
    Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2778411
    Reviewed-by: Reilly Grant <reillyg@chromium.org>
    Commit-Queue: Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
    Cr-Commit-Position: refs/heads/master@{#867272}
    GitOrigin-RevId: dcfc63084215ecd3292e911eefe07fddcf30aed0
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    None yet
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    "Release a wake lock": should we queue a task to fire the "release" event?
    6 participants