commit 95e81323519c31c407a0eb4adfdfe948f30f787c
parent c1654ddf391dd0ff7263ca2deccc5219c9433267
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date: Tue, 14 Oct 2025 21:29:31 +0000
Bug 1990870 - Fix flow markers for JS owned microtasks r=arai,profiler-reviewers,jrmuizel,canaltinova
Differential Revision: https://phabricator.services.mozilla.com/D268185
Diffstat:
3 files changed, 50 insertions(+), 5 deletions(-)
diff --git a/js/public/friend/MicroTask.h b/js/public/friend/MicroTask.h
@@ -158,5 +158,11 @@ JS_PUBLIC_API JSObject* MaybeGetHostDefinedGlobalFromJSMicroTask(
JS_PUBLIC_API JSObject* MaybeGetPromiseFromJSMicroTask(const MicroTask& entry);
+// Get the flow ID from a JS microtask for profiler markers.
+// This only returns false if entry has become a dead wrapper,
+// in which case the microtask doesn't run anyhow.
+JS_PUBLIC_API bool GetFlowIdFromJSMicroTask(const MicroTask& entry,
+ uint64_t* uid);
+
} // namespace JS
#endif /* js_friend_MicroTask_h */
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
@@ -35,6 +35,7 @@
#include "vm/Warnings.h" // js::WarnNumberASCII
#include "debugger/DebugAPI-inl.h"
+#include "gc/StableCellHasher-inl.h"
#include "vm/Compartment-inl.h"
#include "vm/ErrorObject-inl.h"
#include "vm/JSContext-inl.h" // JSContext::check
@@ -1679,6 +1680,15 @@ static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) {
static bool EnqueueJob(JSContext* cx, JS::MicroTask&& job) {
MOZ_ASSERT(cx->realm());
+ GeckoProfilerRuntime& profiler = cx->runtime()->geckoProfiler();
+ if (profiler.enabled()) {
+ // Emit a flow start marker here.
+ uint64_t uid = 0;
+ if (JS::GetFlowIdFromJSMicroTask(job, &uid)) {
+ profiler.markFlow("JS::EnqueueJob", uid,
+ JS::ProfilingCategoryPair::OTHER);
+ }
+ }
// Only check if we need to use the debug queue when we're not on main thread.
if (MOZ_UNLIKELY(!cx->runtime()->isMainRuntime() &&
@@ -7794,6 +7804,23 @@ JS_PUBLIC_API JSObject* JS::MaybeGetPromiseFromJSMicroTask(
return nullptr;
}
+JS_PUBLIC_API bool JS::GetFlowIdFromJSMicroTask(const MicroTask& entry,
+ uint64_t* uid) {
+ MOZ_RELEASE_ASSERT(entry.isObject(), "Only use on JSMicroTasks");
+
+ // We want to make sure we get the flow id from the target object
+ // not the wrapper.
+ JSObject* unwrapped = UncheckedUnwrap(&entry.toObject());
+ if (JS_IsDeadWrapper(unwrapped)) {
+ return false;
+ }
+
+ MOZ_ASSERT(unwrapped->is<MicroTaskEntry>(), "Only use on JSMicroTasks");
+
+ *uid = js::gc::GetUniqueIdInfallible(unwrapped);
+ return true;
+}
+
JS_PUBLIC_API bool JS::IsJSMicroTask(Handle<JS::Value> hv) {
if (!hv.isObject()) {
return false;
diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp
@@ -981,11 +981,28 @@ static bool MOZ_CAN_RUN_SCRIPT
RunMicroTask(JSContext* aCx, JS::MutableHandle<JS::MicroTask> task) {
if (RefPtr<MicroTaskRunnable> runnable =
MaybeUnwrapTaskToOwnedRunnable(task)) {
+ AUTO_PROFILER_TERMINATING_FLOW_MARKER_FLOW_ONLY(
+ "RunMicroTaskRunnable", OTHER, Flow::FromPointer(runnable.get()));
AutoSlowOperation aso;
runnable->Run(aso);
return true;
}
+ // Avoid the overhead of GetFlowIdFromJSMicroTask in the common case
+ // of not having the profiler enabled.
+ mozilla::Maybe<AutoProfilerTerminatingFlowMarkerFlowOnly> terminatingMarker;
+ if (profiler_is_active_and_unpaused() &&
+ profiler_feature_active(ProfilerFeature::Flows)) {
+ uint64_t flowId = 0;
+ // Since this only returns false when the microtask won't run (dead wrapper)
+ // we can elide the marker if it does fail.
+ if (JS::GetFlowIdFromJSMicroTask(task.get(), &flowId)) {
+ terminatingMarker.emplace("RunMicroTask",
+ mozilla::baseprofiler::category::OTHER,
+ Flow::ProcessScoped(flowId));
+ }
+ }
+
JS::Rooted<JSObject*> maybePromise(aCx,
JS::MaybeGetPromiseFromJSMicroTask(task));
auto state = maybePromise
@@ -1190,11 +1207,6 @@ bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) {
// Bug 1991164: Need to support LogMicroTaskQueue Entry for
// LogMicroTaskQueueEntry::Run log(job.get().get());
- // Bug 1990870: Need to support flow markers with JS Micro Tasks.
- // AUTO_PROFILER_TERMINATING_FLOW_MARKER_FLOW_ONLY(
- // "CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint",
- // OTHER, Flow::FromPointer(runnable.get()));
-
// Note: We're dropping the return value on the floor here. This is
// consistent with the previous implementation, which left the
// exception if it was there pending on the context, but likely should