MarshallOfSound

#50418: fix: use fresh LazyNow for OnEndWorkItemImpl to fix TimeKeeper DCHECK

Merged
Created: Mar 22, 2026, 2:23:29 PM
Merged: Mar 22, 2026, 9:54:31 PM
5 comments
Target: main

Description of Change

Fixes a flaky DCHECK on Linux CI:

FATAL:base/task/sequence_manager/thread_controller.cc:643] DCHECK failed: !delta.is_negative(). -0.000642 s

Root cause: In ThreadControllerWithMessagePumpImpl::DoWorkImpl, the same LazyNow instance is passed to both DidRunTask() and OnEndWorkItemImpl(). Inside DidRunTask, the LazyNow gets cached early (via BrowserUIThreadScheduler::OnTaskCompletedRecordTaskEnd) before task observers run. Electron's MicrotasksRunner::DidProcessTask observer then drains V8 microtasks via node::CallbackScope, which can execute arbitrary JS. If that JS triggers nested pump activity (nested RunLoop, sync IPC, etc.), TimeKeeper::last_phase_end_ advances past the cached value. When OnEndWorkItemImpl then uses the stale cached time, it computes a negative delta and hits the DCHECK.

Fix: Use a fresh LazyNow for OnEndWorkItemImpl that samples the clock after all observers have run. This matches the existing comment's intent that "microtasks are extensions of the RunTask" — if so, the work item ends after them, so the time should be sampled then.

Upstreamable: This bug exists whenever any TaskObserver::DidProcessTask triggers nested pump activity, which is not forbidden by the TaskObserver contract. Electron happens to trigger it via MicrotasksRunner, but the fix is generally correct and should be upstreamed to Chromium.

Cost: One extra clock_gettime(CLOCK_MONOTONIC) per task when DidRunTask caches its LazyNow (~20-30ns via VDSO on Linux). When it doesn't cache, no added cost.

Checklist

  • PR description included and stakeholders cc'd
  • npm test passes
  • Relevant documentation is changed or added (N/A — internal fix)
  • PR title follows semantic commit guidelines

Release Notes

Notes: none

Backports

No Backports Requested

This pull request doesn't have any backports requested or created for older release branches.

What are backports?

Backports are copies of changes made to the main branch that are applied to older release branches. They ensure that bug fixes and important changes are available in maintained older versions of Electron.

Semver Impact

Major
Breaking changes
Minor
New features
Patch
Bug fixes
None
Docs, tests, etc.

Semantic Versioning helps users understand the impact of updates:

  • Major (X.y.z): Breaking changes that may require code modifications
  • Minor (x.Y.z): New features that maintain backward compatibility
  • Patch (x.y.Z): Bug fixes that don't change the API
  • None: Changes that don't affect using facing parts of Electron