1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 // nsIEventTarget wrapper for throttling event dispatch.
8 
9 #ifndef mozilla_ThrottledEventQueue_h
10 #define mozilla_ThrottledEventQueue_h
11 
12 #include "nsISerialEventTarget.h"
13 
14 #define NS_THROTTLEDEVENTQUEUE_IID                   \
15   {                                                  \
16     0x8f3cf7dc, 0xfc14, 0x4ad5, {                    \
17       0x9f, 0xd5, 0xdb, 0x79, 0xbc, 0xe6, 0xd5, 0x08 \
18     }                                                \
19   }
20 
21 namespace mozilla {
22 
23 // A ThrottledEventQueue is an event target that can be used to throttle
24 // events being dispatched to another base target.  It maintains its
25 // own queue of events and only dispatches one at a time to the wrapped
26 // target.  This can be used to avoid flooding the base target.
27 //
28 // Flooding is avoided via a very simple principle.  Runnables dispatched
29 // to the ThrottledEventQueue are only dispatched to the base target
30 // one at a time.  Only once that runnable has executed will we dispatch
31 // the next runnable to the base target.  This in effect makes all
32 // runnables passing through the ThrottledEventQueue yield to other work
33 // on the base target.
34 //
35 // ThrottledEventQueue keeps runnables waiting to be dispatched to the
36 // base in its own internal queue.  Code can query the length of this
37 // queue using IsEmpty() and Length().  Further, code implement back
38 // pressure by checking the depth of the queue and deciding to stop
39 // issuing runnables if they see the ThrottledEventQueue is backed up.
40 // Code running on other threads could even use AwaitIdle() to block
41 // all operation until the ThrottledEventQueue drains.
42 //
43 // Note, this class is similar to TaskQueue, but also differs in a few
44 // ways.  First, it is a very simple nsIEventTarget implementation.  It
45 // does not use the AbstractThread API.
46 //
47 // In addition, ThrottledEventQueue currently dispatches its next
48 // runnable to the base target *before* running the current event.  This
49 // allows the event code to spin the event loop without stalling the
50 // ThrottledEventQueue.  In contrast, TaskQueue only dispatches its next
51 // runnable after running the current event.  That approach is necessary
52 // for TaskQueue in order to work with thread pool targets.
53 //
54 // So, if you are targeting a thread pool you probably want a TaskQueue.
55 // If you are targeting a single thread or other non-concurrent event
56 // target, you probably want a ThrottledEventQueue.
57 //
58 // If you drop a ThrottledEventQueue while its queue still has events to be run,
59 // they will continue to be dispatched as usual to the base. Only once the last
60 // event has run will all the ThrottledEventQueue's memory be freed.
61 class ThrottledEventQueue final : public nsISerialEventTarget {
62   class Inner;
63   RefPtr<Inner> mInner;
64 
65   explicit ThrottledEventQueue(already_AddRefed<Inner> aInner);
66   ~ThrottledEventQueue() = default;
67 
68  public:
69   // Create a ThrottledEventQueue for the given target.
70   static already_AddRefed<ThrottledEventQueue> Create(
71       nsISerialEventTarget* aBaseTarget, const char* aName,
72       uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL);
73 
74   // Determine if there are any events pending in the queue.
75   bool IsEmpty() const;
76 
77   // Determine how many events are pending in the queue.
78   uint32_t Length() const;
79 
80   already_AddRefed<nsIRunnable> GetEvent();
81 
82   // Block the current thread until the queue is empty. This may not be called
83   // on the main thread or the base target. The ThrottledEventQueue must not be
84   // paused.
85   void AwaitIdle() const;
86 
87   // If |aIsPaused| is true, pause execution of events from this queue. No
88   // events from this queue will be run until this is called with |aIsPaused|
89   // false.
90   //
91   // To un-pause a ThrottledEventQueue, we need to dispatch a runnable to the
92   // underlying event target. That operation may fail, so this method is
93   // fallible as well.
94   //
95   // Note that, although ThrottledEventQueue's behavior is descibed as queueing
96   // events on the base target, an event queued on a TEQ is never actually moved
97   // to any other queue. What is actually dispatched to the base is an
98   // "executor" event which, when run, removes an event from the TEQ and runs it
99   // immediately. This means that you can pause a TEQ even after the executor
100   // has been queued on the base target, and even so, no events from the TEQ
101   // will run. When the base target gets around to running the executor, the
102   // executor will see that the TEQ is paused, and do nothing.
103   [[nodiscard]] nsresult SetIsPaused(bool aIsPaused);
104 
105   // Return true if this ThrottledEventQueue is paused.
106   bool IsPaused() const;
107 
108   NS_DECL_THREADSAFE_ISUPPORTS
109   NS_DECL_NSIEVENTTARGET_FULL
110 
111   NS_DECLARE_STATIC_IID_ACCESSOR(NS_THROTTLEDEVENTQUEUE_IID);
112 };
113 
114 NS_DEFINE_STATIC_IID_ACCESSOR(ThrottledEventQueue, NS_THROTTLEDEVENTQUEUE_IID);
115 
116 }  // namespace mozilla
117 
118 #endif  // mozilla_ThrottledEventQueue_h
119