Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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