Skip to content

Instantly share code, notes, and snippets.

@mgaudet
Created January 15, 2026 16:46
Show Gist options
  • Select an option

  • Save mgaudet/ae38a457d7d26b07f599e3f13f3b57e0 to your computer and use it in GitHub Desktop.

Select an option

Save mgaudet/ae38a457d7d26b07f599e3f13f3b57e0 to your computer and use it in GitHub Desktop.
A rough approximation of the edits I expect to be made to GJS
commit 124a7eb2820a07b98ec8d109fb57cbeb7f83962a
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date: Tue Jan 13 15:06:36 2026 -0700
An example of how modifications to use the new microtask queue
diff --git a/gjs/context-private.h b/gjs/context-private.h
index 9e9a96a1..a5e1f3f5 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -92,7 +92,6 @@ class GjsContextPrivate : public JS::JobQueue {
std::vector<std::string> m_args;
- JobQueueStorage m_job_queue;
Gjs::PromiseJobDispatcher m_dispatcher;
Gjs::MainLoop m_main_loop;
Gjs::AutoUnref<GMemoryMonitor> m_memory_monitor;
@@ -275,12 +274,9 @@ class GjsContextPrivate : public JS::JobQueue {
GJS_JSAPI_RETURN_CONVENTION
bool getHostDefinedData(JSContext*, JS::MutableHandleObject) const override;
GJS_JSAPI_RETURN_CONVENTION
- bool enqueuePromiseJob(JSContext*, JS::HandleObject promise,
- JS::HandleObject job,
- JS::HandleObject allocation_site,
- JS::HandleObject incumbent_global) override;
+
void runJobs(JSContext*) override;
- [[nodiscard]] bool empty() const override { return m_job_queue.empty(); }
+
[[nodiscard]]
bool isDrainingStopped() const override {
return !m_draining_job_queue;
diff --git a/gjs/context.cpp b/gjs/context.cpp
index fb0eaba7..0f3832cd 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -65,6 +65,7 @@
#include <js/Utility.h> // for DeletePolicy via WeakCache
#include <js/Value.h>
#include <js/ValueArray.h>
+#include <js/friend/MicroTask.h>
#include <js/friend/DumpFunctions.h>
#include <jsapi.h> // for JS_GetFunctionObject, JS_Ge...
#include <jsfriendapi.h> // for ScriptEnvironmentPreparer
@@ -365,7 +366,6 @@ void GjsContextPrivate::trace(JSTracer* trc, void* data) {
"GJS internal global object");
JS::TraceEdge<JSObject*>(trc, &gjs->m_main_loop_hook, "GJS main loop hook");
gjs->m_atoms->trace(trc);
- gjs->m_job_queue.trace(trc);
gjs->m_cleanup_tasks.trace(trc);
gjs->m_object_init_list.trace(trc);
}
@@ -482,7 +482,6 @@ void GjsContextPrivate::dispose() {
delete m_gtype_table;
delete m_atoms;
- m_job_queue.clear();
m_object_init_list.clear();
// Tear down JS
@@ -979,31 +978,6 @@ bool GjsContextPrivate::getHostDefinedData(JSContext* cx,
return true;
}
-// See engine.cpp and JS::SetJobQueue().
-bool GjsContextPrivate::enqueuePromiseJob(JSContext* cx [[maybe_unused]],
- JS::HandleObject promise,
- JS::HandleObject job,
- JS::HandleObject allocation_site,
- JS::HandleObject incumbent_global
- [[maybe_unused]]) {
- g_assert(cx == m_cx);
- g_assert(from_cx(cx) == this);
-
- gjs_debug(GJS_DEBUG_MAINLOOP,
- "Enqueue job %s, promise=%s, allocation site=%s",
- gjs_debug_object(job).c_str(), gjs_debug_object(promise).c_str(),
- gjs_debug_object(allocation_site).c_str());
-
- if (!m_job_queue.append(job)) {
- JS_ReportOutOfMemory(m_cx);
- return false;
- }
-
- JS::JobQueueMayNotBeEmpty(m_cx);
- m_dispatcher.start();
- return true;
-}
-
// Override of JobQueue::runJobs(). Called by js::RunJobs(), and when execution
// of the job queue was interrupted by the debugger and is resuming.
void GjsContextPrivate::runJobs(JSContext* cx) {
@@ -1036,11 +1010,11 @@ bool GjsContextPrivate::run_jobs_fallible() {
JS::RootedObject job(m_cx);
JS::HandleValueArray args(JS::HandleValueArray::empty());
JS::RootedValue rval(m_cx);
+ JS::RootedObject executionGlobal(m_cx);
- if (m_job_queue.length() == 0) {
+ if (!JS::HasAnyMicroTasks(m_cx)) {
// Check FinalizationRegistry cleanup tasks at least once if there are
- // no microtasks queued. This may enqueue more microtasks, which will be
- // appended to m_job_queue.
+ // no microtasks queued. This may enqueue more microtasks
if (!run_finalization_registry_cleanup())
retval = false;
}
@@ -1048,7 +1022,7 @@ bool GjsContextPrivate::run_jobs_fallible() {
/* Execute jobs in a loop until we've reached the end of the queue. Since
* executing a job can trigger enqueueing of additional jobs, it's crucial
* to recheck the queue length during each iteration. */
- for (size_t ix = 0; ix < m_job_queue.length(); ix++) {
+ while (JS::HasAnyMicroTasks(m_cx)) {
// A previous job might have set this flag. e.g., System.exit().
if (m_should_exit || !m_dispatcher.is_running()) {
gjs_debug(GJS_DEBUG_MAINLOOP, "Stopping jobs because of %s",
@@ -1056,7 +1030,7 @@ bool GjsContextPrivate::run_jobs_fallible() {
break;
}
- job = m_job_queue[ix];
+ job = JS::DequeueNextMicroTask(m_cx);
/* It's possible that job draining was interrupted prematurely, leaving
* the queue partly processed. In that case, slots for already-executed
@@ -1064,12 +1038,18 @@ bool GjsContextPrivate::run_jobs_fallible() {
if (!job)
continue;
- m_job_queue[ix] = nullptr;
+ if (!JS::IsJSMicroTask(m_cx)) {
+ // crash!
+ }
+
+ executionGlobal = JS::GetExecutionGlobalFromJSMicroTask(job);
+
+
{
- JSAutoRealm ar(m_cx, job);
+ JSAutoRealm ar(m_cx, executionGlobal);
gjs_debug(GJS_DEBUG_MAINLOOP, "handling job %zu, %s", ix,
gjs_debug_object(job).c_str());
- if (!JS::Call(m_cx, JS::UndefinedHandleValue, job, args, &rval)) {
+ if (!JS::RunJSMicroTask(m_cx, job)){
/* Uncatchable exception - return false so that System.exit()
* works in the interactive shell and when exiting the
* interpreter. */
@@ -1091,13 +1071,12 @@ bool GjsContextPrivate::run_jobs_fallible() {
gjs_debug(GJS_DEBUG_MAINLOOP, "Completed job %zu", ix);
// Run FinalizationRegistry cleanup tasks after each job. Cleanup tasks
- // may enqueue more microtasks, which will be appended to m_job_queue.
+ // may enqueue more microtasks
if (!run_finalization_registry_cleanup())
retval = false;
}
m_draining_job_queue = false;
- m_job_queue.clear();
warn_about_unhandled_promise_rejections();
JS::JobQueueIsEmpty(m_cx);
return retval;
@@ -1141,46 +1120,6 @@ bool GjsContextPrivate::run_finalization_registry_cleanup() {
return retval;
}
-class GjsContextPrivate::SavedQueue : public JS::JobQueue::SavedJobQueue {
- private:
- GjsContextPrivate* m_gjs;
- JS::PersistentRooted<JobQueueStorage> m_queue;
- bool m_was_draining : 1;
-
- public:
- explicit SavedQueue(GjsContextPrivate* gjs)
- : m_gjs(gjs),
- m_queue(gjs->m_cx, std::move(gjs->m_job_queue)),
- m_was_draining(gjs->m_draining_job_queue) {
- gjs_debug(GJS_DEBUG_CONTEXT, "Pausing job queue");
- gjs->stop_draining_job_queue();
- }
-
- ~SavedQueue() {
- gjs_debug(GJS_DEBUG_CONTEXT, "Unpausing job queue");
- g_assert(m_gjs->m_job_queue.empty() &&
- "Current queue should be empty when restoring saved queue");
- m_gjs->m_job_queue = std::move(m_queue.get());
- m_gjs->m_draining_job_queue = m_was_draining;
- m_gjs->start_draining_job_queue();
- }
-};
-
-js::UniquePtr<JS::JobQueue::SavedJobQueue> GjsContextPrivate::saveJobQueue(
- JSContext* cx) {
- g_assert(cx == m_cx);
- g_assert(from_cx(cx) == this);
-
- auto saved_queue = js::MakeUnique<SavedQueue>(this);
- if (!saved_queue) {
- JS_ReportOutOfMemory(cx);
- return nullptr;
- }
-
- g_assert(m_job_queue.empty());
- return saved_queue;
-}
-
void GjsContextPrivate::register_unhandled_promise_rejection(
uint64_t id, JS::UniqueChars&& stack) {
m_unhandled_rejection_stacks[id] = std::move(stack);
diff --git a/subprojects/.wraplock b/subprojects/.wraplock
new file mode 100644
index 00000000..e69de29b
diff --git a/subprojects/gvdb.wrap b/subprojects/gvdb.wrap
new file mode 100644
index 00000000..ff8b15fd
--- /dev/null
+++ b/subprojects/gvdb.wrap
@@ -0,0 +1,2 @@
+[wrap-redirect]
+filename = glib/subprojects/gvdb.wrap
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment