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