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

[Scheduler] Profiler Features (second try) #16542

Merged
merged 14 commits into from Aug 22, 2019

Conversation

gaearon
Copy link
Collaborator

@gaearon gaearon commented Aug 22, 2019

This is a revert of a revert of #16145 + my attempts to fix its issues.
You need to read individual commits.

The main fix has already landed independently in #16407.

However, there were a few more issues in #16145 that I'm fixing in this PR:

  • Bad copy paste for re-exports (fixed in 935526e)
  • Init time dependency on ArrayBuffer which would break IE9 (fixed in ee548c3)
  • Potential deopt for tasks due to adding a field later (fixed in 9c30b0b)

There is a bigger conceptual issue though that I'm struggling with.

(Feel free to skip these details)

#16145 added a bunch of pop() calls — for example, when a task errors or get cancelled. My understanding is that this was done as an optimization. I.e. we don't need to schedule another loop if there's no actual tasks in the loop left.

That optimization doesn't seem like it would cover all cases. For example, if we have tasks [A, B, C], and then cancel C, B, and A in sequence, only A would be popped (because it's the first item). So this optimization seems "optional" and not something we can rely on.

However, the tests do rely on this optimization being present.
Without it, the "main thread" bar never stops:

 Scheduler › marks when a task is canceled

   !!! Main thread              │      ██
    Task 1 [Normal]              │██████░░🡐 canceled
    "
    Received:
      "
    !!! Main thread              │      ██████████████████████
    Task 1 [Normal]              │██████░░🡐 canceled


Scheduler › marks when a task errors

    Expected value to equal:
      "
    !!! Main thread              │
    Task 1 [Normal]              │██████🡐 errored
    "
    Received:
      "
    !!! Main thread              │        ████████████████████
    Task 1 [Normal]              │██████🡐 errored
    "

Scheduler › marks when multiple tasks are canceled

    Expected value to equal:
      "
    !!! Main thread              │      ██
    Task 1 [Normal]              │██████░░🡐 canceled
    Task 2 [Normal]              │░░░░░░░░🡐 canceled
    "
    Received:
      "
    !!! Main thread              │      ██████████████████████
    Task 1 [Normal]              │██████░░🡐 canceled
    Task 2 [Normal]              │░░░░░░░░🡐 canceled
    "

I don't know how to interpret this.

Is this optimization essential for profiler's correctness? In that case it seems dodgy that depending on the order in which you cancel callbacks, it would either work or not.

Or is this optimization purely an optimization? Are the test asserting on implementation details too aggressive? Why does the "Main Thread" bar never stop? Or are we missing a "suspended" event that should have happened in that case?

Finally, the logic to determine whether we "suspended" or "resumed" seems fragile to me. I don't know for sure what those terms are supposed to mean. For example, if a callback errors, did we "suspend"? It seems like no from the code, but I don't know why. Conceptually, I'd think of an "error" as "suspending" the scheduler loop. Unless "suspend" means something else here.

My last commit just removes the "optimization" (9a64404). It breaks some tests but I can't move further without understanding their intent. Is it the code, the assertions, or the test setup that needs to change?

Was this more than an optimization? I'll leave it at that so someone else can pick it up. :-)

I chatted to Boone, and arrived at a solution that satisfies both of us. Basically, I wasn't sure what Suspended and Resumed events mean. Boone told me it's okay to treat them as "exit the loop" and "enter the loop".

So in the next commits I did the following:

  • Removed the pop() optimization that affected non-profiling paths to reduce the risk. (9a64404)
    • This broke tests because they seemed to assert on implementation details
  • Strengthened tests to explicitly treat Suspend and Resumed as "exit" and "enter", and added assertions for that (8482f5d)
    • This further broke tests because we didn't always emit these events in pairs
  • Finally, made Suspended and Resumed match 1:1 to "exiting" and "entering" the flush (1f5f7df)
    • I had to amend tests to match this new behavior. These changes make sense to me. We now flag "main thread" time as anything not spent in the Scheduler.

@sizebot
Copy link

sizebot commented Aug 22, 2019

React: size: 🔺+0.4%, gzip: 🔺+0.3%

Details of bundled changes.

Comparing: 8a01b50...24b1b26

react

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react.development.js +4.9% +3.7% 112.69 KB 118.24 KB 28.87 KB 29.92 KB UMD_DEV
react.production.min.js 🔺+0.4% 🔺+0.3% 12.64 KB 12.68 KB 5.02 KB 5.03 KB UMD_PROD
react.profiling.min.js +8.5% +8.8% 14.82 KB 16.08 KB 5.56 KB 6.05 KB UMD_PROFILING
react.development.js 0.0% 0.0% 71.99 KB 71.99 KB 18.95 KB 18.95 KB NODE_DEV
react.production.min.js 0.0% 0.0% 6.66 KB 6.66 KB 2.77 KB 2.77 KB NODE_PROD
React-dev.js 0.0% 0.0% 69.86 KB 69.86 KB 17.99 KB 17.99 KB FB_WWW_DEV
React-prod.js 0.0% -0.0% 17.25 KB 17.25 KB 4.52 KB 4.52 KB FB_WWW_PROD
React-profiling.js 0.0% -0.0% 17.25 KB 17.25 KB 4.52 KB 4.52 KB FB_WWW_PROFILING

scheduler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
scheduler-unstable_mock.development.js +32.4% +25.2% 16.71 KB 22.12 KB 4.1 KB 5.13 KB UMD_DEV
scheduler-tracing.profiling.min.js 0.0% +0.3% 3.25 KB 3.25 KB 991 B 994 B NODE_PROFILING
scheduler-unstable_mock.production.min.js -0.0% 🔺+0.1% 4.73 KB 4.73 KB 1.98 KB 1.98 KB UMD_PROD
Scheduler-dev.js +21.3% +14.9% 26.28 KB 31.87 KB 6.96 KB 8 KB FB_WWW_DEV
Scheduler-prod.js 🔺+0.2% 🔺+0.4% 15.95 KB 15.99 KB 3.59 KB 3.61 KB FB_WWW_PROD
Scheduler-profiling.js n/a n/a 0 B 19.62 KB 0 B 4.3 KB FB_WWW_PROFILING
scheduler-tracing.development.js 0.0% +0.1% 11.69 KB 11.69 KB 3.03 KB 3.03 KB NODE_DEV
scheduler-tracing.production.min.js 0.0% 🔺+0.3% 728 B 728 B 382 B 383 B NODE_PROD
scheduler-unstable_mock.development.js +32.7% +25.5% 16.52 KB 21.93 KB 4.04 KB 5.07 KB NODE_DEV
scheduler-unstable_mock.production.min.js 🔺+0.1% -0.2% 4.72 KB 4.72 KB 1.92 KB 1.92 KB NODE_PROD
SchedulerMock-dev.js +32.0% +25.1% 17.05 KB 22.5 KB 4.16 KB 5.21 KB FB_WWW_DEV
SchedulerMock-prod.js -0.7% 🔺+0.1% 12.14 KB 12.06 KB 2.84 KB 2.84 KB FB_WWW_PROD
scheduler.development.js +21.7% +15.1% 25.68 KB 31.26 KB 6.85 KB 7.88 KB NODE_DEV
scheduler.production.min.js 🔺+1.3% 🔺+0.9% 5.23 KB 5.29 KB 2.14 KB 2.16 KB NODE_PROD

Generated by 🚫 dangerJS

@gaearon
Copy link
Collaborator Author

gaearon commented Aug 22, 2019

Regarding "suspended" event. I'm not sure if it's supposed to mean any time the loop stops. Or if it's supposed to mean that work loop stopped but there's more work to do later.

Their definitions used to be more fuzzy. For example, Suspend didn't always fire on exit, and sometimes fired when we did _not_ exit (such as at task enqueue).

I chatted to Boone, and he's saying treating Suspend and Resume as strictly exiting and entering the loop is fine for their use case.
@gaearon
Copy link
Collaborator Author

gaearon commented Aug 22, 2019

This is ready for review. Production bundle diff: https://gist.github.com/gaearon/b3ee8dadf9d4fac5f407dfa9e3c247fc/revisions

@gaearon
Copy link
Collaborator Author

gaearon commented Aug 22, 2019

: null;

const profilingState =
enableProfiling && sharedProfilingBuffer !== null
Copy link
Collaborator Author

@gaearon gaearon Aug 22, 2019

Choose a reason for hiding this comment

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

Keeping this sharedProfilingBuffer !== null check because while new Int32Array(null) would work, IE9 which doesn't have ArrayBuffer also doesn't have Int32Array.

if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
if (enableProfiling) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I figured you'd fork the finally too but I guess this works too.

@sebmarkbage sebmarkbage merged commit 0f6e3cd into facebook:master Aug 22, 2019
@gaearon gaearon deleted the profiling-revert-revert branch August 22, 2019 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants