summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Chimento <philip.chimento@gmail.com>2021-11-07 00:28:47 +0000
committerPhilip Chimento <philip.chimento@gmail.com>2021-11-07 00:28:47 +0000
commit4fa8279ebff4ab462f26370b68599d6f401df8d2 (patch)
treed355adfe10fb01742c6276ec08c1bc32b1be4450
parent69ff99f62255b7c10bfdbe65f61c51790655a6f1 (diff)
parent42f8ebcb3e8a8af536816fbaac28a3470bad06ea (diff)
downloadgjs-4fa8279ebff4ab462f26370b68599d6f401df8d2.tar.gz
Merge branch 'ewlsh/implicit-mainloop' into 'master'
[Mainloop 1/3] Add custom GSource for promise queueing Closes #1 See merge request GNOME/gjs!557
-rw-r--r--doc/Custom-GSources.md22
-rw-r--r--gjs/context-private.h9
-rw-r--r--gjs/context.cpp56
-rw-r--r--gjs/promise.cpp177
-rw-r--r--gjs/promise.h54
-rw-r--r--installed-tests/js/.eslintrc.yml1
-rw-r--r--installed-tests/js/meson.build1
-rw-r--r--installed-tests/js/testAsync.js67
-rw-r--r--meson.build1
-rwxr-xr-xtools/process_iwyu.py2
-rwxr-xr-xtools/run_iwyu.sh2
11 files changed, 351 insertions, 41 deletions
diff --git a/doc/Custom-GSources.md b/doc/Custom-GSources.md
new file mode 100644
index 00000000..de625d79
--- /dev/null
+++ b/doc/Custom-GSources.md
@@ -0,0 +1,22 @@
+## Custom GSources
+
+GLib allows custom GSources to be added to the main loop.
+A custom GSource can control under what conditions it is dispatched.
+You can read more about GLib's main loop [here][glib-mainloop-docs].
+
+Within GJS, we have implemented a custom GSource to handle Promise execution.
+It dispatches whenever a Promise is queued, occurring before any other GLib
+events.
+This mimics the behavior of a [microtask queue](mdn-microtasks) in other
+JavaScript environments.
+You can read an introduction to building custom GSources within the archived
+developer documentation [here][custom-gsource-tutorial] or, if unavailable, via
+[the original source code][custom-gsource-tutorial-source].
+Another great resource is Philip Withnall's ["A detailed look at GSource"][gsource-blog-post]<sup>[[permalink]][gsource-blog-post-archive]</sup>.
+
+[gsource-blog-post]: https://tecnocode.co.uk/2015/05/05/a-detailed-look-at-gsource/
+[gsource-blog-post-archive]: https://web.archive.org/web/20201013000618/https://tecnocode.co.uk/2015/05/05/a-detailed-look-at-gsource/
+[mdn-microtasks]: https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
+[glib-mainloop-docs]: https://docs.gtk.org/glib/main-loop.html#creating-new-source-types
+[custom-gsource-tutorial]: https://developer-old.gnome.org/gnome-devel-demos/unstable/custom-gsource.c.html.en
+[custom-gsource-tutorial-source]: https://gitlab.gnome.org/Archive/gnome-devel-docs/-/blob/703816cec292293fd337b6db8520b9b0afa7b3c9/platform-demos/C/custom-gsource.c.page
diff --git a/gjs/context-private.h b/gjs/context-private.h
index 75b04bf0..0499d286 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -17,6 +17,7 @@
#include <utility> // for pair
#include <vector>
+#include <gio/gio.h>
#include <glib-object.h>
#include <glib.h>
@@ -37,6 +38,7 @@
#include "gjs/jsapi-util.h"
#include "gjs/macros.h"
#include "gjs/profiler.h"
+#include "gjs/promise.h"
namespace js {
class SystemAllocPolicy;
@@ -78,7 +80,7 @@ class GjsContextPrivate : public JS::JobQueue {
std::vector<std::string> m_args;
JobQueueStorage m_job_queue;
- unsigned m_idle_drain_handler;
+ Gjs::PromiseJobDispatcher m_dispatcher;
std::vector<std::pair<DestroyNotify, void*>> m_destroy_notifications;
std::vector<Gjs::Closure::Ptr> m_async_closures;
@@ -134,7 +136,6 @@ class GjsContextPrivate : public JS::JobQueue {
class SavedQueue;
void start_draining_job_queue(void);
void stop_draining_job_queue(void);
- static gboolean drain_job_queue_idle_handler(void* data);
uint8_t handle_exit_code(const char* type, const char* identifier,
GError** error);
@@ -236,11 +237,13 @@ class GjsContextPrivate : public JS::JobQueue {
JS::HandleObject allocation_site,
JS::HandleObject incumbent_global) override;
void runJobs(JSContext* cx) override;
+ void runJobs(JSContext* cx, GCancellable* cancellable);
[[nodiscard]] bool empty() const override { return m_job_queue.empty(); }
js::UniquePtr<JS::JobQueue::SavedJobQueue> saveJobQueue(
JSContext* cx) override;
- GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible(void);
+ GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible(
+ GCancellable* cancellable = nullptr);
void register_unhandled_promise_rejection(uint64_t id, GjsAutoChar&& stack);
void unregister_unhandled_promise_rejection(uint64_t id);
void warn_about_unhandled_promise_rejections();
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 734ac68d..9a75d596 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -81,6 +81,7 @@
#include "gjs/objectbox.h"
#include "gjs/profiler-private.h"
#include "gjs/profiler.h"
+#include "gjs/promise.h"
#include "gjs/text-encoding.h"
#include "modules/modules.h"
#include "util/log.h"
@@ -405,6 +406,8 @@ void GjsContextPrivate::unregister_notifier(DestroyNotify notify_func,
void GjsContextPrivate::dispose(void) {
if (m_cx) {
+ stop_draining_job_queue();
+
gjs_debug(GJS_DEBUG_CONTEXT,
"Notifying reference holders of GjsContext dispose");
@@ -532,8 +535,8 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
: m_public_context(public_context),
m_cx(cx),
m_owner_thread(std::this_thread::get_id()),
+ m_dispatcher(this),
m_environment_preparer(cx) {
-
JS_SetGCCallback(
cx,
[](JSContext*, JSGCStatus status, JS::GCReason reason, void* data) {
@@ -654,6 +657,8 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
cx, "resource:///org/gnome/gjs/modules/esm/_bootstrap/default.js",
"ESM bootstrap");
}
+
+ start_draining_job_queue();
}
void GjsContextPrivate::set_args(std::vector<std::string>&& args) {
@@ -895,32 +900,15 @@ bool GjsContextPrivate::should_exit(uint8_t* exit_code_p) const {
}
void GjsContextPrivate::start_draining_job_queue(void) {
- if (!m_idle_drain_handler) {
- gjs_debug(GJS_DEBUG_CONTEXT, "Starting promise job queue handler");
- m_idle_drain_handler = g_idle_add_full(
- G_PRIORITY_DEFAULT, drain_job_queue_idle_handler, this, nullptr);
- }
+ gjs_debug(GJS_DEBUG_CONTEXT, "Starting promise job dispatcher");
+ m_dispatcher.start();
}
void GjsContextPrivate::stop_draining_job_queue(void) {
m_draining_job_queue = false;
- if (m_idle_drain_handler) {
- gjs_debug(GJS_DEBUG_CONTEXT, "Stopping promise job queue handler");
- g_source_remove(m_idle_drain_handler);
- m_idle_drain_handler = 0;
- }
-}
-gboolean GjsContextPrivate::drain_job_queue_idle_handler(void* data) {
- gjs_debug(GJS_DEBUG_CONTEXT, "Promise job queue handler");
- auto* gjs = static_cast<GjsContextPrivate*>(data);
- gjs->runJobs(gjs->context());
- /* Uncatchable exceptions are swallowed here - no way to get a handle on
- * the main loop to exit it from this idle handler */
- gjs_debug(GJS_DEBUG_CONTEXT, "Promise job queue handler finished");
- g_assert(gjs->empty() && gjs->m_idle_drain_handler == 0 &&
- "GjsContextPrivate::runJobs() should have emptied queue");
- return G_SOURCE_REMOVE;
+ gjs_debug(GJS_DEBUG_CONTEXT, "Stopping promise job dispatcher");
+ m_dispatcher.stop();
}
JSObject* GjsContextPrivate::getIncumbentGlobal(JSContext* cx) {
@@ -943,27 +931,24 @@ bool GjsContextPrivate::enqueuePromiseJob(JSContext* cx [[maybe_unused]],
gjs_debug_object(job).c_str(), gjs_debug_object(promise).c_str(),
gjs_debug_object(allocation_site).c_str());
- if (m_idle_drain_handler)
- g_assert(!empty());
- else
- g_assert(empty());
-
if (!m_job_queue.append(job)) {
JS_ReportOutOfMemory(m_cx);
return false;
}
JS::JobQueueMayNotBeEmpty(m_cx);
- start_draining_job_queue();
+ 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) {
+void GjsContextPrivate::runJobs(JSContext* cx) { runJobs(cx, nullptr); }
+
+void GjsContextPrivate::runJobs(JSContext* cx, GCancellable* cancellable) {
g_assert(cx == m_cx);
g_assert(from_cx(cx) == this);
- if (!run_jobs_fallible())
+ if (!run_jobs_fallible(cancellable))
gjs_log_exception(cx);
}
@@ -979,7 +964,7 @@ void GjsContextPrivate::runJobs(JSContext* cx) {
* Returns: false if one of the jobs threw an uncatchable exception;
* otherwise true.
*/
-bool GjsContextPrivate::run_jobs_fallible(void) {
+bool GjsContextPrivate::run_jobs_fallible(GCancellable* cancellable) {
bool retval = true;
if (m_draining_job_queue || m_should_exit)
@@ -996,7 +981,7 @@ bool GjsContextPrivate::run_jobs_fallible(void) {
* it's crucial to recheck the queue length during each iteration. */
for (size_t ix = 0; ix < m_job_queue.length(); ix++) {
/* A previous job might have set this flag. e.g., System.exit(). */
- if (m_should_exit)
+ if (m_should_exit || g_cancellable_is_cancelled(cancellable))
break;
job = m_job_queue[ix];
@@ -1032,8 +1017,8 @@ bool GjsContextPrivate::run_jobs_fallible(void) {
}
}
+ m_draining_job_queue = false;
m_job_queue.clear();
- stop_draining_job_queue();
JS::JobQueueIsEmpty(m_cx);
return retval;
}
@@ -1042,14 +1027,12 @@ class GjsContextPrivate::SavedQueue : public JS::JobQueue::SavedJobQueue {
private:
GjsContextPrivate* m_gjs;
JS::PersistentRooted<JobQueueStorage> m_queue;
- bool m_idle_was_pending : 1;
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_idle_was_pending(gjs->m_idle_drain_handler != 0),
m_was_draining(gjs->m_draining_job_queue) {
gjs_debug(GJS_DEBUG_CONTEXT, "Pausing job queue");
gjs->stop_draining_job_queue();
@@ -1059,8 +1042,7 @@ class GjsContextPrivate::SavedQueue : public JS::JobQueue::SavedJobQueue {
gjs_debug(GJS_DEBUG_CONTEXT, "Unpausing job queue");
m_gjs->m_job_queue = std::move(m_queue.get());
m_gjs->m_draining_job_queue = m_was_draining;
- if (m_idle_was_pending)
- m_gjs->start_draining_job_queue();
+ m_gjs->start_draining_job_queue();
}
};
diff --git a/gjs/promise.cpp b/gjs/promise.cpp
new file mode 100644
index 00000000..ce19a780
--- /dev/null
+++ b/gjs/promise.cpp
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com>
+// SPDX-FileCopyrightText: 2021 Marco Trevisan <mail@3v1n0.net>
+
+#include <config.h>
+
+#include <stddef.h> // for size_t
+
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include "gjs/context-private.h"
+#include "gjs/jsapi-util.h"
+#include "gjs/promise.h"
+
+/**
+ * promise.cpp - This file implements a custom GSource, PromiseJobQueueSource,
+ * which handles promise dispatching within GJS. Custom GSources are able to
+ * control under which conditions they dispatch. PromiseJobQueueSource will
+ * always dispatch if even a single Promise is enqueued and will continue
+ * dispatching until all Promises (also known as "Jobs" within SpiderMonkey)
+ * are run. While this does technically mean Promises can starve the mainloop
+ * if run recursively, this is intentional. Within JavaScript Promises are
+ * considered "microtasks" and a microtask must run before any other task
+ * continues.
+ *
+ * PromiseJobQueueSource is attached to the thread's default GMainContext with
+ * a default priority of -1000. This is 10x the priority of G_PRIORITY_HIGH and
+ * no application code should attempt to override this.
+ *
+ * See doc/Custom-GSources.md for more background information on custom
+ * GSources and microtasks
+ */
+
+namespace Gjs {
+
+/**
+ * @brief a custom GSource which handles draining our job queue.
+ */
+class PromiseJobDispatcher::Source : public GSource {
+ // The private GJS context this source runs within.
+ GjsContextPrivate* m_gjs;
+ // The main context this source attaches to.
+ GjsAutoMainContext m_main_context;
+ // The cancellable that stops this source.
+ GjsAutoUnref<GCancellable> m_cancellable;
+ GjsAutoPointer<GSource, GSource, g_source_unref> m_cancellable_source;
+
+ // G_PRIORITY_HIGH is normally -100, we set 10 times that to ensure our
+ // source always has the greatest priority. This means our prepare will
+ // be called before other sources, and prepare will determine whether
+ // we dispatch.
+ static constexpr int PRIORITY = 10 * G_PRIORITY_HIGH;
+
+ // GSource custom functions
+ static GSourceFuncs source_funcs;
+
+ // Called to determine whether the source should run (dispatch) in the
+ // next event loop iteration. If the job queue is not empty we return true
+ // to schedule a dispatch.
+ gboolean prepare(int* timeout [[maybe_unused]]) { return !m_gjs->empty(); }
+
+ gboolean dispatch() {
+ if (g_cancellable_is_cancelled(m_cancellable))
+ return G_SOURCE_REMOVE;
+
+ // The ready time is sometimes set to 0 to kick us out of polling,
+ // we need to reset the value here or this source will always be the
+ // next one to execute. (it will starve the other sources)
+ g_source_set_ready_time(this, -1);
+
+ // A reference to the current cancellable is needed in case any
+ // jobs reset PromiseJobDispatcher and thus replace the cancellable.
+ GjsAutoUnref<GCancellable> cancellable(m_cancellable,
+ GjsAutoTakeOwnership{});
+ // Drain the job queue.
+ m_gjs->runJobs(m_gjs->context(), cancellable);
+
+ return G_SOURCE_CONTINUE;
+ }
+
+ public:
+ /**
+ * @brief Constructs a new GjsPromiseJobQueueSource GSource and adds a
+ * reference to the associated main context.
+ *
+ * @param cx the current JSContext
+ * @param cancellable an optional cancellable
+ */
+ Source(GjsContextPrivate* gjs, GMainContext* main_context)
+ : m_gjs(gjs),
+ m_main_context(main_context, GjsAutoTakeOwnership()),
+ m_cancellable(g_cancellable_new()),
+ m_cancellable_source(g_cancellable_source_new(m_cancellable)) {
+ g_source_set_priority(this, PRIORITY);
+#if GLIB_CHECK_VERSION(2, 70, 0)
+ g_source_set_static_name(this, "GjsPromiseJobQueueSource");
+#else
+ g_source_set_name(this, "GjsPromiseJobQueueSource");
+#endif
+
+ // Add our cancellable source to our main source,
+ // this will trigger the main source if our cancellable
+ // is cancelled.
+ g_source_add_child_source(this, m_cancellable_source);
+ }
+
+ void* operator new(size_t size) {
+ return g_source_new(&source_funcs, size);
+ }
+ void operator delete(void* p) { g_source_unref(static_cast<GSource*>(p)); }
+
+ bool is_running() { return !!g_source_get_context(this); }
+
+ /**
+ * @brief Trigger the cancellable, detaching our source.
+ */
+ void cancel() { g_cancellable_cancel(m_cancellable); }
+ /**
+ * @brief Reset the cancellable and prevent the source from stopping,
+ * overriding a previous cancel() call. Called by start() in
+ * PromiseJobDispatcher to ensure the custom source will start.
+ */
+ void reset() {
+ if (!g_cancellable_is_cancelled(m_cancellable))
+ return;
+
+ if (is_running())
+ g_source_remove_child_source(this, m_cancellable_source);
+ else
+ g_source_destroy(m_cancellable_source);
+
+ // Drop the old cancellable and create a new one, as per
+ // https://docs.gtk.org/gio/method.Cancellable.reset.html
+ m_cancellable = g_cancellable_new();
+ m_cancellable_source = g_cancellable_source_new(m_cancellable);
+ g_source_add_child_source(this, m_cancellable_source);
+ }
+};
+
+GSourceFuncs PromiseJobDispatcher::Source::source_funcs = {
+ [](GSource* source, int* timeout) {
+ return static_cast<Source*>(source)->prepare(timeout);
+ },
+ nullptr, // check
+ [](GSource* source, GSourceFunc, void*) {
+ return static_cast<Source*>(source)->dispatch();
+ },
+ [](GSource* source) { static_cast<Source*>(source)->~Source(); },
+};
+
+PromiseJobDispatcher::PromiseJobDispatcher(GjsContextPrivate* gjs)
+ // Acquire a guaranteed reference to this thread's default main context
+ : m_main_context(g_main_context_ref_thread_default()),
+ // Create and reference our custom GSource
+ m_source(std::make_unique<Source>(gjs, m_main_context)) {}
+
+PromiseJobDispatcher::~PromiseJobDispatcher() {
+ g_source_destroy(m_source.get());
+}
+
+bool PromiseJobDispatcher::is_running() { return m_source->is_running(); }
+
+void PromiseJobDispatcher::start() {
+ // Reset the cancellable
+ m_source->reset();
+
+ // Don't re-attach if the task is already running
+ if (is_running())
+ return;
+
+ g_source_attach(m_source.get(), m_main_context);
+}
+
+void PromiseJobDispatcher::stop() { m_source->cancel(); }
+
+}; // namespace Gjs
diff --git a/gjs/promise.h b/gjs/promise.h
new file mode 100644
index 00000000..8fec2aeb
--- /dev/null
+++ b/gjs/promise.h
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com>
+
+#pragma once
+
+#include <config.h>
+
+#include <memory>
+
+#include <glib.h>
+
+#include "gjs/jsapi-util.h"
+
+class GjsContextPrivate;
+
+using GjsAutoMainContext =
+ GjsAutoPointer<GMainContext, GMainContext, g_main_context_unref,
+ g_main_context_ref>;
+
+namespace Gjs {
+
+/**
+ * @brief A class which wraps a custom GSource and handles associating it with a
+ * GMainContext. While it is running, it will attach the source to the main
+ * context so that promise jobs are run at the appropriate time.
+ */
+class PromiseJobDispatcher {
+ class Source;
+ // The thread-default GMainContext
+ GjsAutoMainContext m_main_context;
+ // The custom source.
+ std::unique_ptr<Source> m_source;
+
+ public:
+ explicit PromiseJobDispatcher(GjsContextPrivate*);
+ ~PromiseJobDispatcher();
+
+ /**
+ * @brief Start (or resume) dispatching jobs from the promise job queue
+ */
+ void start();
+
+ /**
+ * @brief Stop dispatching
+ */
+ void stop();
+
+ /**
+ * @brief Whether the dispatcher is currently running
+ */
+ bool is_running();
+};
+
+}; // namespace Gjs
diff --git a/installed-tests/js/.eslintrc.yml b/installed-tests/js/.eslintrc.yml
index abc9c527..bbf09ab2 100644
--- a/installed-tests/js/.eslintrc.yml
+++ b/installed-tests/js/.eslintrc.yml
@@ -32,6 +32,7 @@ globals:
overrides:
- files:
- matchers.js
+ - testAsync.js
- testCairoModule.js
- testConsole.js
- testESModules.js
diff --git a/installed-tests/js/meson.build b/installed-tests/js/meson.build
index 5ca37103..2f007351 100644
--- a/installed-tests/js/meson.build
+++ b/installed-tests/js/meson.build
@@ -231,6 +231,7 @@ endif
# minijasmine flag
modules_tests = [
+ 'Async',
'Console',
'ESModules',
'Encoding',
diff --git a/installed-tests/js/testAsync.js b/installed-tests/js/testAsync.js
new file mode 100644
index 00000000..dbd6979c
--- /dev/null
+++ b/installed-tests/js/testAsync.js
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com>
+
+import GLib from 'gi://GLib';
+import GObject from 'gi://GObject';
+
+const PRIORITIES = [
+ 'PRIORITY_LOW',
+ 'PRIORITY_HIGH',
+ 'PRIORITY_DEFAULT',
+ 'PRIORITY_HIGH_IDLE',
+ 'PRIORITY_DEFAULT_IDLE',
+];
+
+describe('Async microtasks resolves before', function () {
+ // Generate test suites with different types of Sources
+ const tests = [
+ {
+ description: 'idle task with',
+ createSource: () => GLib.idle_source_new(),
+ },
+ {
+ description: '0-second timeout task with',
+ // A timeout of 0 tests if microtasks (promises) run before
+ // non-idle tasks which would normally execute "next" in the loop
+ createSource: () => GLib.timeout_source_new(0),
+ },
+ ];
+
+ for (const {description, createSource} of tests) {
+ describe(description, function () {
+ const CORRECT_TASKS = [
+ 'async 1',
+ 'async 2',
+ 'source task',
+ ];
+
+ for (const priority of PRIORITIES) {
+ it(`priority set to GLib.${priority}`, function (done) {
+ const tasks = [];
+
+ const source = createSource();
+ source.set_priority(GLib[priority]);
+ GObject.source_set_closure(source, () => {
+ tasks.push('source task');
+
+ expect(tasks).toEqual(jasmine.arrayWithExactContents(CORRECT_TASKS));
+
+ done();
+ source.destroy();
+
+ return GLib.SOURCE_REMOVE;
+ });
+ source.attach(null);
+
+ (async () => {
+ // Without an await async functions execute
+ // synchronously
+ tasks.push(await 'async 1');
+ })().then(() => {
+ tasks.push('async 2');
+ });
+ });
+ }
+ });
+ }
+});
diff --git a/meson.build b/meson.build
index 78bfc8a6..437b3fd3 100644
--- a/meson.build
+++ b/meson.build
@@ -424,6 +424,7 @@ libgjs_sources = [
'gjs/objectbox.cpp', 'gjs/objectbox.h',
'gjs/profiler.cpp', 'gjs/profiler-private.h',
'gjs/text-encoding.cpp', 'gjs/text-encoding.h',
+ 'gjs/promise.cpp', 'gjs/promise.h',
'gjs/stack.cpp',
'modules/console.cpp', 'modules/console.h',
'modules/modules.cpp', 'modules/modules.h',
diff --git a/tools/process_iwyu.py b/tools/process_iwyu.py
index 718349b2..2cc32e62 100755
--- a/tools/process_iwyu.py
+++ b/tools/process_iwyu.py
@@ -106,6 +106,8 @@ FALSE_POSITIVES = (
'for remove_reference<>::type'),
('gjs/profiler.cpp', '#include <type_traits>',
'for remove_reference<>::type'),
+ ('gjs/promise.cpp', '#include <type_traits>',
+ 'for remove_reference<>::type'),
('test/gjs-test-jsapi-utils.cpp', '#include <type_traits>',
'for remove_reference<>::type'),
diff --git a/tools/run_iwyu.sh b/tools/run_iwyu.sh
index 319da33e..7e1db246 100755
--- a/tools/run_iwyu.sh
+++ b/tools/run_iwyu.sh
@@ -71,7 +71,7 @@ for FILE in $SRCDIR/gi/*.cpp $SRCDIR/gjs/atoms.cpp $SRCDIR/gjs/byteArray.cpp \
$SRCDIR/gjs/deprecation.cpp $SRCDIR/gjs/error-types.cpp \
$SRCDIR/gjs/engine.cpp $SRCDIR/gjs/global.cpp $SRCDIR/gjs/importer.cpp \
$SRCDIR/gjs/jsapi-util*.cpp $SRCDIR/gjs/module.cpp $SRCDIR/gjs/native.cpp \
- $SRCDIR/gjs/objectbox.cpp $SRCDIR/gjs/stack.cpp \
+ $SRCDIR/gjs/objectbox.cpp $SRCDIR/gjs/promise.cpp $SRCDIR/gjs/stack.cpp \
$SRCDIR/modules/cairo-*.cpp $SRCDIR/modules/console.cpp \
$SRCDIR/modules/print.cpp $SRCDIR/modules/system.cpp $SRCDIR/test/*.cpp \
$SRCDIR/util/*.cpp $SRCDIR/libgjs-private/*.c