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

Implement requestPaint in the actual scheduler #31784

Merged
merged 1 commit into from
Dec 14, 2024

Conversation

sebmarkbage
Copy link
Collaborator

When implementing passive effects we did a pretty massive oversight. While the passive effect is scheduled into its own scheduler task, the scheduler doesn't always yield to the browser if it has time left. That means that if you have a fast commit phase, it might try to squeeze in the passive effects in the same frame but those then might end being very heavy.

We had requestPaint() for this but that was only implemented for the isInputPending experiment. It wasn't thought we needed it for the regular scheduler because it yields "every frame" anyway - but it doesn't yield every task. While the isInputPending experiment showed that it wasn't actually any significant impact, and it was better to keep shorter yield time anyway. Which is why we deleted the code. Whatever small win it did see in some cases might have been actually due to this issue rather than anything to do with isInputPending at all.

As you can see in #31782 we do have this implemented in the mock scheduler and a lot of behavior that we assert assumes that this works.

So this just implements yielding after requestPaint is called.

Before:

Screenshot 2024-12-14 at 3 40 24 PM

After:

Screenshot 2024-12-14 at 3 41 25 PM

Notice how in after the native task is split into two. It might not always actually paint and the native scheduler might make the same mistake and think it has enough time left but it's at least less likely to.

We do have another way to do this. When we yield a continuation we also yield to the native browser. This is to enable the Suspense Optimization (currently disabled) to work. We could do the same for passive effects and, in fact, I have a branch that does but because that requires a lot more tests to be fixed it's a lot more invasive of a change. The nice thing about this approach is that this is not even running in tests at all and the tests we do have assert that this is the behavior already. 😬

Copy link

vercel bot commented Dec 14, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
react-compiler-playground ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 14, 2024 9:02pm

@react-sizebot
Copy link

react-sizebot commented Dec 14, 2024

Comparing: c32780e...6bd1b00

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB +0.11% 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 510.72 kB 510.72 kB = 91.48 kB 91.48 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB +0.05% 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 515.66 kB 515.66 kB = 92.19 kB 92.19 kB
facebook-www/ReactDOM-prod.classic.js = 595.04 kB 595.04 kB = 105.00 kB 105.00 kB
facebook-www/ReactDOM-prod.modern.js = 585.31 kB 585.31 kB = 103.44 kB 103.44 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
facebook-react-native/scheduler/cjs/Scheduler-dev.js +1.04% 12.26 kB 12.39 kB +1.27% 2.68 kB 2.72 kB
oss-experimental/scheduler/cjs/scheduler.development.js +1.04% 12.30 kB 12.43 kB +1.26% 2.71 kB 2.74 kB
oss-stable-semver/scheduler/cjs/scheduler.development.js +1.04% 12.30 kB 12.43 kB +1.26% 2.71 kB 2.74 kB
oss-stable/scheduler/cjs/scheduler.development.js +1.04% 12.30 kB 12.43 kB +1.26% 2.71 kB 2.74 kB
oss-experimental/scheduler/cjs/scheduler.native.development.js +1.03% 12.39 kB 12.51 kB +1.02% 2.56 kB 2.58 kB
oss-stable-semver/scheduler/cjs/scheduler.native.development.js +1.03% 12.39 kB 12.51 kB +1.02% 2.56 kB 2.58 kB
oss-stable/scheduler/cjs/scheduler.native.development.js +1.03% 12.39 kB 12.51 kB +1.02% 2.56 kB 2.58 kB
facebook-react-native/scheduler/cjs/Scheduler-prod.js +0.93% 10.34 kB 10.44 kB +1.18% 2.54 kB 2.57 kB
oss-experimental/scheduler/cjs/scheduler.production.js +0.93% 10.35 kB 10.44 kB +1.14% 2.54 kB 2.57 kB
oss-stable-semver/scheduler/cjs/scheduler.production.js +0.93% 10.35 kB 10.44 kB +1.14% 2.54 kB 2.57 kB
oss-stable/scheduler/cjs/scheduler.production.js +0.93% 10.35 kB 10.44 kB +1.14% 2.54 kB 2.57 kB
facebook-react-native/scheduler/cjs/Scheduler-profiling.js +0.89% 10.77 kB 10.87 kB +1.21% 2.64 kB 2.67 kB
facebook-www/Scheduler-prod.classic.js +0.89% 10.84 kB 10.94 kB +1.13% 2.66 kB 2.69 kB
facebook-www/Scheduler-prod.modern.js +0.89% 10.84 kB 10.94 kB +1.13% 2.66 kB 2.69 kB
facebook-www/Scheduler-profiling.classic.js +0.85% 11.28 kB 11.37 kB +1.01% 2.76 kB 2.79 kB
facebook-www/Scheduler-profiling.modern.js +0.85% 11.28 kB 11.37 kB +1.01% 2.76 kB 2.79 kB
facebook-www/Scheduler-dev.classic.js +0.78% 16.39 kB 16.52 kB +0.92% 3.48 kB 3.51 kB
facebook-www/Scheduler-dev.modern.js +0.78% 16.39 kB 16.52 kB +0.92% 3.48 kB 3.51 kB
oss-experimental/scheduler/cjs/scheduler.native.production.js +0.69% 10.97 kB 11.04 kB +0.89% 2.46 kB 2.48 kB
oss-stable-semver/scheduler/cjs/scheduler.native.production.js +0.69% 10.97 kB 11.04 kB +0.89% 2.46 kB 2.48 kB
oss-stable/scheduler/cjs/scheduler.native.production.js +0.69% 10.97 kB 11.04 kB +0.89% 2.46 kB 2.48 kB

Generated by 🚫 dangerJS against 2d264e3

@sebmarkbage sebmarkbage merged commit c80b336 into facebook:main Dec 14, 2024
187 checks passed
github-actions bot pushed a commit that referenced this pull request Dec 14, 2024
When implementing passive effects we did a pretty massive oversight.
While the passive effect is scheduled into its own scheduler task, the
scheduler doesn't always yield to the browser if it has time left. That
means that if you have a fast commit phase, it might try to squeeze in
the passive effects in the same frame but those then might end being
very heavy.

We had `requestPaint()` for this but that was only implemented for the
`isInputPending` experiment. It wasn't thought we needed it for the
regular scheduler because it yields "every frame" anyway - but it
doesn't yield every task. While the `isInputPending` experiment showed
that it wasn't actually any significant impact, and it was better to
keep shorter yield time anyway. Which is why we deleted the code.
Whatever small win it did see in some cases might have been actually due
to this issue rather than anything to do with `isInputPending` at all.

As you can see in #31782 we do
have this implemented in the mock scheduler and a lot of behavior that
we assert assumes that this works.

So this just implements yielding after `requestPaint` is called.

Before:

<img width="1023" alt="Screenshot 2024-12-14 at 3 40 24 PM"
src="https://github.com/user-attachments/assets/d60f4bb2-c8f8-4f91-a402-9ac25b278450"
/>

After:

<img width="1108" alt="Screenshot 2024-12-14 at 3 41 25 PM"
src="https://github.com/user-attachments/assets/170cdb90-a049-436f-9501-be3fb9bc04ca"
/>

Notice how in after the native task is split into two. It might not
always actually paint and the native scheduler might make the same
mistake and think it has enough time left but it's at least less likely
to.

We do have another way to do this. When we yield a continuation we also
yield to the native browser. This is to enable the Suspense Optimization
(currently disabled) to work. We could do the same for passive effects
and, in fact, I have a branch that does but because that requires a lot
more tests to be fixed it's a lot more invasive of a change. The nice
thing about this approach is that this is not even running in tests at
all and the tests we do have assert that this is the behavior already. 😬

DiffTrain build for [c80b336](c80b336)
github-actions bot pushed a commit that referenced this pull request Dec 14, 2024
When implementing passive effects we did a pretty massive oversight.
While the passive effect is scheduled into its own scheduler task, the
scheduler doesn't always yield to the browser if it has time left. That
means that if you have a fast commit phase, it might try to squeeze in
the passive effects in the same frame but those then might end being
very heavy.

We had `requestPaint()` for this but that was only implemented for the
`isInputPending` experiment. It wasn't thought we needed it for the
regular scheduler because it yields "every frame" anyway - but it
doesn't yield every task. While the `isInputPending` experiment showed
that it wasn't actually any significant impact, and it was better to
keep shorter yield time anyway. Which is why we deleted the code.
Whatever small win it did see in some cases might have been actually due
to this issue rather than anything to do with `isInputPending` at all.

As you can see in #31782 we do
have this implemented in the mock scheduler and a lot of behavior that
we assert assumes that this works.

So this just implements yielding after `requestPaint` is called.

Before:

<img width="1023" alt="Screenshot 2024-12-14 at 3 40 24 PM"
src="https://github.com/user-attachments/assets/d60f4bb2-c8f8-4f91-a402-9ac25b278450"
/>

After:

<img width="1108" alt="Screenshot 2024-12-14 at 3 41 25 PM"
src="https://github.com/user-attachments/assets/170cdb90-a049-436f-9501-be3fb9bc04ca"
/>

Notice how in after the native task is split into two. It might not
always actually paint and the native scheduler might make the same
mistake and think it has enough time left but it's at least less likely
to.

We do have another way to do this. When we yield a continuation we also
yield to the native browser. This is to enable the Suspense Optimization
(currently disabled) to work. We could do the same for passive effects
and, in fact, I have a branch that does but because that requires a lot
more tests to be fixed it's a lot more invasive of a change. The nice
thing about this approach is that this is not even running in tests at
all and the tests we do have assert that this is the behavior already. 😬

DiffTrain build for [c80b336](c80b336)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Dec 15, 2024
When implementing passive effects we did a pretty massive oversight.
While the passive effect is scheduled into its own scheduler task, the
scheduler doesn't always yield to the browser if it has time left. That
means that if you have a fast commit phase, it might try to squeeze in
the passive effects in the same frame but those then might end being
very heavy.

We had `requestPaint()` for this but that was only implemented for the
`isInputPending` experiment. It wasn't thought we needed it for the
regular scheduler because it yields "every frame" anyway - but it
doesn't yield every task. While the `isInputPending` experiment showed
that it wasn't actually any significant impact, and it was better to
keep shorter yield time anyway. Which is why we deleted the code.
Whatever small win it did see in some cases might have been actually due
to this issue rather than anything to do with `isInputPending` at all.

As you can see in facebook#31782 we do
have this implemented in the mock scheduler and a lot of behavior that
we assert assumes that this works.

So this just implements yielding after `requestPaint` is called.

Before:

<img width="1023" alt="Screenshot 2024-12-14 at 3 40 24 PM"
src="https://github.com/user-attachments/assets/d60f4bb2-c8f8-4f91-a402-9ac25b278450"
/>

After:

<img width="1108" alt="Screenshot 2024-12-14 at 3 41 25 PM"
src="https://github.com/user-attachments/assets/170cdb90-a049-436f-9501-be3fb9bc04ca"
/>

Notice how in after the native task is split into two. It might not
always actually paint and the native scheduler might make the same
mistake and think it has enough time left but it's at least less likely
to.

We do have another way to do this. When we yield a continuation we also
yield to the native browser. This is to enable the Suspense Optimization
(currently disabled) to work. We could do the same for passive effects
and, in fact, I have a branch that does but because that requires a lot
more tests to be fixed it's a lot more invasive of a change. The nice
thing about this approach is that this is not even running in tests at
all and the tests we do have assert that this is the behavior already. 😬

DiffTrain build for [c80b336](facebook@c80b336)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Dec 15, 2024
When implementing passive effects we did a pretty massive oversight.
While the passive effect is scheduled into its own scheduler task, the
scheduler doesn't always yield to the browser if it has time left. That
means that if you have a fast commit phase, it might try to squeeze in
the passive effects in the same frame but those then might end being
very heavy.

We had `requestPaint()` for this but that was only implemented for the
`isInputPending` experiment. It wasn't thought we needed it for the
regular scheduler because it yields "every frame" anyway - but it
doesn't yield every task. While the `isInputPending` experiment showed
that it wasn't actually any significant impact, and it was better to
keep shorter yield time anyway. Which is why we deleted the code.
Whatever small win it did see in some cases might have been actually due
to this issue rather than anything to do with `isInputPending` at all.

As you can see in facebook#31782 we do
have this implemented in the mock scheduler and a lot of behavior that
we assert assumes that this works.

So this just implements yielding after `requestPaint` is called.

Before:

<img width="1023" alt="Screenshot 2024-12-14 at 3 40 24 PM"
src="https://github.com/user-attachments/assets/d60f4bb2-c8f8-4f91-a402-9ac25b278450"
/>

After:

<img width="1108" alt="Screenshot 2024-12-14 at 3 41 25 PM"
src="https://github.com/user-attachments/assets/170cdb90-a049-436f-9501-be3fb9bc04ca"
/>

Notice how in after the native task is split into two. It might not
always actually paint and the native scheduler might make the same
mistake and think it has enough time left but it's at least less likely
to.

We do have another way to do this. When we yield a continuation we also
yield to the native browser. This is to enable the Suspense Optimization
(currently disabled) to work. We could do the same for passive effects
and, in fact, I have a branch that does but because that requires a lot
more tests to be fixed it's a lot more invasive of a change. The nice
thing about this approach is that this is not even running in tests at
all and the tests we do have assert that this is the behavior already. 😬

DiffTrain build for [c80b336](facebook@c80b336)
sebmarkbage added a commit that referenced this pull request Dec 17, 2024
…mpletes (#31787)

As an alternative to #31784.

We should really just always yield each virtual task to a native task.
So that it's 1:1 with native tasks. This affects when microtasks within
each task happens. This brings us closer to native `postTask` semantics
which makes it more seamless to just use that when available.

This still doesn't yield when a task expires to protect against
starvation.
github-actions bot pushed a commit that referenced this pull request Dec 17, 2024
…mpletes (#31787)

As an alternative to #31784.

We should really just always yield each virtual task to a native task.
So that it's 1:1 with native tasks. This affects when microtasks within
each task happens. This brings us closer to native `postTask` semantics
which makes it more seamless to just use that when available.

This still doesn't yield when a task expires to protect against
starvation.

DiffTrain build for [f5077bc](f5077bc)
sebmarkbage added a commit that referenced this pull request Dec 17, 2024
…led flow (#31785)

This treats workInProgressRoot work and rootWithPendingPassiveEffects
the same way. Basically as long as there's some work on the root, yield
the current task. Including passive effects. This means that passive
effects are now a continuation instead of a separate callback. This can
mean they're earlier or later than before. Later for Idle in case
there's other non-React work. Earlier for same Default if there's other
Default priority work.

This makes sense since increasing priority of the passive effects beyond
Idle doesn't really make sense for an Idle render.

However, for any given render at same priority it's more important to
complete this work than start something new.

Since we special case continuations to always yield to the browser, this
has the same effect as #31784 without implementing `requestPaint`. At
least assuming nothing else calls `requestPaint`.

<img width="587" alt="Screenshot 2024-12-14 at 5 37 37 PM"
src="https://github.com/user-attachments/assets/8641b172-8842-4191-8bf0-50cbe263a30c"
/>
github-actions bot pushed a commit that referenced this pull request Dec 17, 2024
…led flow (#31785)

This treats workInProgressRoot work and rootWithPendingPassiveEffects
the same way. Basically as long as there's some work on the root, yield
the current task. Including passive effects. This means that passive
effects are now a continuation instead of a separate callback. This can
mean they're earlier or later than before. Later for Idle in case
there's other non-React work. Earlier for same Default if there's other
Default priority work.

This makes sense since increasing priority of the passive effects beyond
Idle doesn't really make sense for an Idle render.

However, for any given render at same priority it's more important to
complete this work than start something new.

Since we special case continuations to always yield to the browser, this
has the same effect as #31784 without implementing `requestPaint`. At
least assuming nothing else calls `requestPaint`.

<img width="587" alt="Screenshot 2024-12-14 at 5 37 37 PM"
src="https://github.com/user-attachments/assets/8641b172-8842-4191-8bf0-50cbe263a30c"
/>

DiffTrain build for [facec3e](facec3e)
github-actions bot pushed a commit that referenced this pull request Dec 17, 2024
…led flow (#31785)

This treats workInProgressRoot work and rootWithPendingPassiveEffects
the same way. Basically as long as there's some work on the root, yield
the current task. Including passive effects. This means that passive
effects are now a continuation instead of a separate callback. This can
mean they're earlier or later than before. Later for Idle in case
there's other non-React work. Earlier for same Default if there's other
Default priority work.

This makes sense since increasing priority of the passive effects beyond
Idle doesn't really make sense for an Idle render.

However, for any given render at same priority it's more important to
complete this work than start something new.

Since we special case continuations to always yield to the browser, this
has the same effect as #31784 without implementing `requestPaint`. At
least assuming nothing else calls `requestPaint`.

<img width="587" alt="Screenshot 2024-12-14 at 5 37 37 PM"
src="https://github.com/user-attachments/assets/8641b172-8842-4191-8bf0-50cbe263a30c"
/>

DiffTrain build for [facec3e](facec3e)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Dec 18, 2024
…led flow (facebook#31785)

This treats workInProgressRoot work and rootWithPendingPassiveEffects
the same way. Basically as long as there's some work on the root, yield
the current task. Including passive effects. This means that passive
effects are now a continuation instead of a separate callback. This can
mean they're earlier or later than before. Later for Idle in case
there's other non-React work. Earlier for same Default if there's other
Default priority work.

This makes sense since increasing priority of the passive effects beyond
Idle doesn't really make sense for an Idle render.

However, for any given render at same priority it's more important to
complete this work than start something new.

Since we special case continuations to always yield to the browser, this
has the same effect as facebook#31784 without implementing `requestPaint`. At
least assuming nothing else calls `requestPaint`.

<img width="587" alt="Screenshot 2024-12-14 at 5 37 37 PM"
src="https://github.com/user-attachments/assets/8641b172-8842-4191-8bf0-50cbe263a30c"
/>

DiffTrain build for [facec3e](facebook@facec3e)
github-actions bot pushed a commit to code/lib-react that referenced this pull request Dec 18, 2024
…led flow (facebook#31785)

This treats workInProgressRoot work and rootWithPendingPassiveEffects
the same way. Basically as long as there's some work on the root, yield
the current task. Including passive effects. This means that passive
effects are now a continuation instead of a separate callback. This can
mean they're earlier or later than before. Later for Idle in case
there's other non-React work. Earlier for same Default if there's other
Default priority work.

This makes sense since increasing priority of the passive effects beyond
Idle doesn't really make sense for an Idle render.

However, for any given render at same priority it's more important to
complete this work than start something new.

Since we special case continuations to always yield to the browser, this
has the same effect as facebook#31784 without implementing `requestPaint`. At
least assuming nothing else calls `requestPaint`.

<img width="587" alt="Screenshot 2024-12-14 at 5 37 37 PM"
src="https://github.com/user-attachments/assets/8641b172-8842-4191-8bf0-50cbe263a30c"
/>

DiffTrain build for [facec3e](facebook@facec3e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants