summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/ThrottledEventQueue.h
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/threads/ThrottledEventQueue.h')
-rw-r--r--xpcom/threads/ThrottledEventQueue.h118
1 files changed, 118 insertions, 0 deletions
diff --git a/xpcom/threads/ThrottledEventQueue.h b/xpcom/threads/ThrottledEventQueue.h
new file mode 100644
index 0000000000..cf37a10a6d
--- /dev/null
+++ b/xpcom/threads/ThrottledEventQueue.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// nsIEventTarget wrapper for throttling event dispatch.
+
+#ifndef mozilla_ThrottledEventQueue_h
+#define mozilla_ThrottledEventQueue_h
+
+#include "nsISerialEventTarget.h"
+
+#define NS_THROTTLEDEVENTQUEUE_IID \
+ { \
+ 0x8f3cf7dc, 0xfc14, 0x4ad5, { \
+ 0x9f, 0xd5, 0xdb, 0x79, 0xbc, 0xe6, 0xd5, 0x08 \
+ } \
+ }
+
+namespace mozilla {
+
+// A ThrottledEventQueue is an event target that can be used to throttle
+// events being dispatched to another base target. It maintains its
+// own queue of events and only dispatches one at a time to the wrapped
+// target. This can be used to avoid flooding the base target.
+//
+// Flooding is avoided via a very simple principle. Runnables dispatched
+// to the ThrottledEventQueue are only dispatched to the base target
+// one at a time. Only once that runnable has executed will we dispatch
+// the next runnable to the base target. This in effect makes all
+// runnables passing through the ThrottledEventQueue yield to other work
+// on the base target.
+//
+// ThrottledEventQueue keeps runnables waiting to be dispatched to the
+// base in its own internal queue. Code can query the length of this
+// queue using IsEmpty() and Length(). Further, code implement back
+// pressure by checking the depth of the queue and deciding to stop
+// issuing runnables if they see the ThrottledEventQueue is backed up.
+// Code running on other threads could even use AwaitIdle() to block
+// all operation until the ThrottledEventQueue drains.
+//
+// Note, this class is similar to TaskQueue, but also differs in a few
+// ways. First, it is a very simple nsIEventTarget implementation. It
+// does not use the AbstractThread API.
+//
+// In addition, ThrottledEventQueue currently dispatches its next
+// runnable to the base target *before* running the current event. This
+// allows the event code to spin the event loop without stalling the
+// ThrottledEventQueue. In contrast, TaskQueue only dispatches its next
+// runnable after running the current event. That approach is necessary
+// for TaskQueue in order to work with thread pool targets.
+//
+// So, if you are targeting a thread pool you probably want a TaskQueue.
+// If you are targeting a single thread or other non-concurrent event
+// target, you probably want a ThrottledEventQueue.
+//
+// If you drop a ThrottledEventQueue while its queue still has events to be run,
+// they will continue to be dispatched as usual to the base. Only once the last
+// event has run will all the ThrottledEventQueue's memory be freed.
+class ThrottledEventQueue final : public nsISerialEventTarget {
+ class Inner;
+ RefPtr<Inner> mInner;
+
+ explicit ThrottledEventQueue(already_AddRefed<Inner> aInner);
+ ~ThrottledEventQueue() = default;
+
+ public:
+ // Create a ThrottledEventQueue for the given target.
+ static already_AddRefed<ThrottledEventQueue> Create(
+ nsISerialEventTarget* aBaseTarget, const char* aName,
+ uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL);
+
+ // Determine if there are any events pending in the queue.
+ bool IsEmpty() const;
+
+ // Determine how many events are pending in the queue.
+ uint32_t Length() const;
+
+ already_AddRefed<nsIRunnable> GetEvent();
+
+ // Block the current thread until the queue is empty. This may not be called
+ // on the main thread or the base target. The ThrottledEventQueue must not be
+ // paused.
+ void AwaitIdle() const;
+
+ // If |aIsPaused| is true, pause execution of events from this queue. No
+ // events from this queue will be run until this is called with |aIsPaused|
+ // false.
+ //
+ // To un-pause a ThrottledEventQueue, we need to dispatch a runnable to the
+ // underlying event target. That operation may fail, so this method is
+ // fallible as well.
+ //
+ // Note that, although ThrottledEventQueue's behavior is descibed as queueing
+ // events on the base target, an event queued on a TEQ is never actually moved
+ // to any other queue. What is actually dispatched to the base is an
+ // "executor" event which, when run, removes an event from the TEQ and runs it
+ // immediately. This means that you can pause a TEQ even after the executor
+ // has been queued on the base target, and even so, no events from the TEQ
+ // will run. When the base target gets around to running the executor, the
+ // executor will see that the TEQ is paused, and do nothing.
+ [[nodiscard]] nsresult SetIsPaused(bool aIsPaused);
+
+ // Return true if this ThrottledEventQueue is paused.
+ bool IsPaused() const;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIEVENTTARGET_FULL
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_THROTTLEDEVENTQUEUE_IID);
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(ThrottledEventQueue, NS_THROTTLEDEVENTQUEUE_IID);
+
+} // namespace mozilla
+
+#endif // mozilla_ThrottledEventQueue_h