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

Improve the run-to-completion principle. #536

Merged
merged 9 commits into from
Dec 17, 2024

Conversation

jyasskin
Copy link
Contributor

@jyasskin jyasskin commented Dec 6, 2024

  • "While an event loop is running" wasn't the right period to limit changes.
  • We have a list of exceptions to this principle.

This is meant to fix #456; @jakearchibald, can you check it? Thank you for the text!

I think this also supersedes #496. @jan-ivar, have I caught everything you meant to say there?


Preview | Diff

index.bs Outdated
Comment on lines 1479 to 1488
* Functions meant to help developers interrupt synchronous work,
as in the case of the proposed {{isInputPending()}}.
Copy link
Member

Choose a reason for hiding this comment

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

This feature is somewhat controversial because it violates run-to-completion.

Copy link
Contributor

Choose a reason for hiding this comment

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

Feels reasonable to drop this reference. The other examples are enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The TAG is on record supporting this exception in w3ctag/design-reviews#415 (comment) and w3ctag/design-reviews#475 (comment) (cc @dbaron for any historical context). I can break this into a separate PR if we need to discuss it more, but this function seems to be a case of putting authors ahead of technical purity. There might also be a better phrasing to explain what makes this exception acceptable?

Copy link
Contributor

Choose a reason for hiding this comment

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

If it's historical, maybe clarify this? MDN says this isInputPending is "superseded".

This guide is for new APIs, and as it reads, suggests "Functions meant to help developers interrupt synchronous work" is a legit reason. As general advice this might lead to new APIs that through blocking turn async calls into synchronous ones, which currently isn't possible AFAIK (a design invariant?)

Copy link
Contributor Author

@jyasskin jyasskin Dec 17, 2024

Choose a reason for hiding this comment

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

Good catch. Apparently IdleDeadline.timeRemaining() also adjusts to help developers stop their idle work if other tasks appear, and it's in a WG and shipped in 2 engines. I'll switch to that example.

Suggested change
* Functions meant to help developers interrupt synchronous work,
as in the case of the proposed {{isInputPending()}}.
* Functions meant to help developers interrupt synchronous work,
as in the case of {{IdleDeadline/timeRemaining()|IdleDeadline.timeRemaining()}}.

I've filed w3c/requestidlecallback#104 because the spec doesn't call this out as clearly as I'd hope. I'm also not sure the name as clear about the unusual behavior as isInputPending was: do folks want to include advice that a better name would have been ideal, and if so, what name?

index.bs Show resolved Hide resolved
index.bs Show resolved Hide resolved
index.bs Outdated
Comment on lines 1479 to 1488
* Functions meant to help developers interrupt synchronous work,
as in the case of the proposed {{isInputPending()}}.
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels reasonable to drop this reference. The other examples are enough.

@jakearchibald
Copy link
Contributor

Generally LGTM!

Copy link
Contributor Author

@jyasskin jyasskin left a comment

Choose a reason for hiding this comment

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

Thanks for the reviews!

index.bs Show resolved Hide resolved
index.bs Outdated
Comment on lines 1479 to 1488
* Functions meant to help developers interrupt synchronous work,
as in the case of the proposed {{isInputPending()}}.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The TAG is on record supporting this exception in w3ctag/design-reviews#415 (comment) and w3ctag/design-reviews#475 (comment) (cc @dbaron for any historical context). I can break this into a separate PR if we need to discuss it more, but this function seems to be a case of putting authors ahead of technical purity. There might also be a better phrasing to explain what makes this exception acceptable?

index.bs Show resolved Hide resolved
Copy link
Member

@dbaron dbaron left a comment

Choose a reason for hiding this comment

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

For what it's worth, I'm generally supportive of these changes. I think the core of this principle is about preventing surprising behavior and preventing broken code that results from that surprise. In that vein, the clearer it is that the functions have this sort of behavior, the better. (For example, it's reasonably clear that something like Date.now() might change as slow code executes.)

For things that do violate this, we want it to be pretty clear that code (conceptually, maybe not syntactically) like this is bad:

if (window.isPurple) {
  // (1)
} else {
  // (2)
}

// ...

if (window.isPurple) {
  // assume path (1) above executed
}

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
@jyasskin jyasskin added this to the 2024-12-16-week milestone Dec 13, 2024
index.bs Outdated Show resolved Hide resolved
Comment on lines -1432 to -1442
Don't modify data accessed via JavaScript APIs
while a JavaScript <a>event loop</a> is running.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that we need a positive statement for this principle.

Maybe

Propagate changes to state
that originate outside of JavaScript execution context
between tasks, not within them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, or maybe by moving up the "a task should be queued" suggestion below...

If a change to state originates outside of the JavaScript execution context,
propagate that change to JavaScript between tasks,
for example by [[html#queuing-tasks|queuing a task]],
or as part of [=update the rendering=].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How's this? I also simplified the next paragraph, and focused it on async changes instead of claiming that https://html.spec.whatwg.org/multipage/webappapis.html#killing-scripts doesn't exist.

Copy link
Contributor

@jan-ivar jan-ivar left a comment

Choose a reason for hiding this comment

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

Thanks, this looks like a great improvement to me!

I'd like to continue to discuss #496 separately for a little bit if that's OK, but that doesn't need to block this. It may be redundant, but I'd like to double-check there isn't a principle there worth describing specific to designing promise-returning APIs that come with synchronous requirements.

index.bs Outdated Show resolved Hide resolved
* {{HTMLImageElement/width|img.width}} to change as a result of loading image data from the network
* Buttons of a {{Gamepad}} to change state
* {{Element/scrollTop}} to change, even if scrolling can visually occur

Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is "changes" refers not only to readable properties, but any state changes that might trip up the developer's code execution, like the observable behavior of a method they invoke. Should we add an example of that? Maybe something like:

Suggested change
* A method acting differently depending on which [=microtask=] it is called on.

Copy link
Contributor Author

@jyasskin jyasskin Dec 17, 2024

Choose a reason for hiding this comment

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

Yes, good point that we should specifically include changes in method behavior. I think the wording you've suggested implies that folks might be tempted to identify microtasks, which they generally aren't, so I'd like to call out a particular function. I had trouble finding a synchronous method that does actually change behavior depending on queued changes, and ones that return Promises can always return in a new task, so I think I have to use a counterfactual:

Suggested change
* A synchronous method to act differently depending on asynchronous state changes.
For example, if {{LockManager}} had synchronous methods,
their behavior would depend on concurrent calls in other windows.

Copy link
Contributor

Choose a reason for hiding this comment

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

A method acting differently depending on which [=microtask=] it is called on.

fwiw, a close example of this is requestAnimationFrame. It makes sense that it sometimes runs the callback before the next frame, and sometimes after, but it's annoying that you don't know which of these will happen. whatwg/html#10113

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I can see a similar principle that requestAnimationFrame could violate, but all of the bits of the event loop transition on task boundaries, so it's not this principle. I didn't even mis-write this part of the example, since the rendering stages don't advance asynchronously. :)

Is this maybe a new principle for the HTML section that method behavior shouldn't depend on the event loop stage? Except that's not exactly what you're looking for in that issue since you're asking for a way to detect the stage rather than a method that behaves consistently. Could this be just "design requestAnimationFrame with the benefit of hindsight" rather than a generalizable principle?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, honestly, I think my example shows that:

A method acting differently depending on which [=microtask=] it is called on.

…isn't the right terminology.

I think requestAnimationFrame is ok, it's just missing that bit that tells you in advance if it'll run before or after the next frame.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good. I think that means https://w3ctag.github.io/design-principles/#js-rtc is basically correct, and there's nothing more for me to do on this PR. Let me know or file an issue if I have that wrong. :)

index.bs Outdated
Comment on lines 1479 to 1488
* Functions meant to help developers interrupt synchronous work,
as in the case of the proposed {{isInputPending()}}.
Copy link
Contributor

Choose a reason for hiding this comment

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

If it's historical, maybe clarify this? MDN says this isInputPending is "superseded".

This guide is for new APIs, and as it reads, suggests "Functions meant to help developers interrupt synchronous work" is a legit reason. As general advice this might lead to new APIs that through blocking turn async calls into synchronous ones, which currently isn't possible AFAIK (a design invariant?)

jyasskin and others added 5 commits December 17, 2024 19:24
* "While an event loop is running" wasn't the right period to limit
  changes.
* We have a list of exceptions to this principle.
Co-authored-by: Martin Thomson <[email protected]>
jyasskin and others added 3 commits December 17, 2024 20:43
* It doesn't matter whether JS is a wrapper around code in another language.
* JS doesn't always get to complete: users can kill it.
This can be reverted once
WICG/shared-storage#212 is merged and crawled.
@martinthomson martinthomson merged commit c63b666 into w3ctag:main Dec 17, 2024
1 check passed
github-actions bot added a commit that referenced this pull request Dec 17, 2024
SHA: c63b666
Reason: push, by martinthomson

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@jyasskin jyasskin deleted the run-to-completion branch December 18, 2024 00:07
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.

"while a JavaScript event loop is running." is probably not intended
6 participants