/* -*- 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/. */
using mozilla::CondVar; using mozilla::MakeRefPtr; using mozilla::Mutex; using mozilla::MutexAutoLock; using mozilla::ThrottledEventQueue; using std::function; using std::string;
namespace TestThrottledEventQueue {
// A simple queue of runnables, to serve as the base target of // ThrottledEventQueues in tests. // // This is much simpler than mozilla::TaskQueue, and so better for unit tests. // It's about the same as mozilla::EventQueue, but that doesn't implement // nsIEventTarget, so it can't be the base target of a ThrottledEventQueue. struct RunnableQueue : nsISerialEventTarget {
std::queue<nsCOMPtr<nsIRunnable>> runnables;
auto base = MakeRefPtr<RunnableQueue>();
RefPtr<ThrottledEventQueue> throttled =
ThrottledEventQueue::Create(base, "test queue 2");
// A ThrottledEventQueue limits its impact on the base target by only queuing // its next event on the base once the prior event has been run. What it // actually queues on the base is a sort of proxy event called an // "executor": the base running the executor draws an event from the // ThrottledEventQueue and runs that. If the ThrottledEventQueue has further // events, it re-queues the executor on the base, effectively "going to the // back of the line".
// Queue an event on the ThrottledEventQueue. This also queues the "executor" // event on the base.
Enqueue(throttled, [&]() { log += 'a'; });
ASSERT_EQ(throttled->Length(), 1U);
ASSERT_EQ(base->Length(), 1U);
// Add a second event to the throttled queue. The executor is already queued.
Enqueue(throttled, [&]() { log += 'b'; });
ASSERT_EQ(throttled->Length(), 2U);
ASSERT_EQ(base->Length(), 1U);
// Add an event directly to the base, after the executor.
Enqueue(base, [&]() { log += 'c'; });
ASSERT_EQ(throttled->Length(), 2U);
ASSERT_EQ(base->Length(), 2U);
// Run the base target. This runs: // - the executor, which runs the first event from the ThrottledEventQueue, // and re-enqueues itself // - the event queued directly on the base // - the executor again, which runs the second event from the // ThrottledEventQueue.
ASSERT_EQ(log, "");
ASSERT_NS_SUCCEEDED(base->Run());
ASSERT_EQ(log, "acb");
auto base = MakeRefPtr<RunnableQueue>();
RefPtr<ThrottledEventQueue> throttled =
ThrottledEventQueue::Create(base, "test queue 3");
// When an event from the throttled queue dispatches a new event directly to // the base target, it is queued after the executor, so the next event from // the throttled queue will run before it.
Enqueue(base, [&]() { log += 'a'; });
Enqueue(throttled, [&]() {
log += 'b';
Enqueue(base, [&]() { log += 'c'; });
});
Enqueue(throttled, [&]() { log += 'd'; });
auto base = MakeRefPtr<RunnableQueue>();
RefPtr<ThrottledEventQueue> throttled =
ThrottledEventQueue::Create(base, "test queue 4");
// Running the event queue from within an event (i.e., a nested event loop) // does not stall the ThrottledEventQueue.
Enqueue(throttled, [&]() {
log += '('; // This should run subsequent events from throttled.
ASSERT_NS_SUCCEEDED(base->Run());
log += ')';
});
// If we drop the event queue while it still has events, they still run.
{
RefPtr<ThrottledEventQueue> throttled =
ThrottledEventQueue::Create(base, "test queue 5");
Enqueue(throttled, [&]() { log += 'a'; });
}
string dequeue_await; // mutex bool threadFinished = false; // mutex & cond bool runnableFinished = false; // main thread only
auto base = MakeRefPtr<RunnableQueue>();
RefPtr<ThrottledEventQueue> throttled =
ThrottledEventQueue::Create(base, "test queue 6");
// Put an event in the queue so the AwaitIdle might block.
Enqueue(throttled, [&]() { runnableFinished = true; });
// Create a separate thread that waits for the queue to become idle, and // then takes observable action.
nsCOMPtr<nsIRunnable> await = NS_NewRunnableFunction("TEQ AwaitIdle", [&]() {
throttled->AwaitIdle();
MutexAutoLock lock(mutex);
dequeue_await += " await";
threadFinished = true;
cond.Notify();
});
// We can't guarantee that the thread has reached the AwaitIdle call, but we // can get pretty close. Either way, it shouldn't affect the behavior of the // test.
PR_Sleep(PR_MillisecondsToInterval(100));
// Wait for the thread to finish.
{
MutexAutoLock lock(mutex); while (!threadFinished) cond.Wait();
ASSERT_EQ(dequeue_await, "dequeue await");
}
ASSERT_NS_SUCCEEDED(thread->Shutdown());
}
TEST(ThrottledEventQueue, AwaitIdleMixed)
{ // Create a separate thread that waits for the queue to become idle, and // then takes observable action.
nsCOMPtr<nsIThread> thread;
ASSERT_TRUE(NS_SUCCEEDED(
NS_NewNamedThread("AwaitIdleMixed", getter_AddRefs(thread))));
// Note that we are about to begin awaiting. When the main thread sees // this notification, it will begin draining the queue.
log += '(';
threadStarted = true;
cond.Notify();
}
// Wait for the main thread to drain the TEQ.
throttled->AwaitIdle();
{
MutexAutoLock lock(mutex);
// Note that we have finished awaiting.
log += ')';
threadFinished = true;
cond.Notify();
}
});
// Wait for the thread to be ready to await. We can't be sure it will actually // be blocking before we get around to draining the event queue, but that's // the nature of the API; this test should work even if we drain the queue // before it awaits.
{
MutexAutoLock lock(mutex); while (!threadStarted) cond.Wait();
ASSERT_EQ(log, "(");
}
// Let the queue drain.
ASSERT_NS_SUCCEEDED(base->Run());
{
MutexAutoLock lock(mutex); // The first runnable must always finish before AwaitIdle returns. But the // TEQ notifies the condition variable as soon as it dequeues the last // runnable, without waiting for that runnable to complete. So the thread // and the last runnable could run in either order. Or, we might beat the // thread to the mutex. // // (The only combination excluded here is "(a)": the 'b' runnable should // definitely have run.)
ASSERT_TRUE(log == "(ab" || log == "(a)b" || log == "(ab)"); while (!threadFinished) cond.Wait();
ASSERT_TRUE(log == "(a)b" || log == "(ab)");
}
ASSERT_EQ(log, "");
ASSERT_NS_SUCCEEDED(base->Run()); // Since the 'b' event paused the throttled queue, 'c' should not have run. // but 'D' was enqueued directly on the base, and should have run.
ASSERT_EQ(log, "AbD");
ASSERT_TRUE(base->IsEmpty());
ASSERT_FALSE(throttled->IsEmpty());
ASSERT_TRUE(throttled->IsPaused());
ASSERT_NS_SUCCEEDED(base->Run()); // Since we've unpaused, 'c' should be able to run now. The executor should // have been enqueued between 'E' and 'F'.
ASSERT_EQ(log, "AbDEcF");
// Put an event in the queue so the AwaitIdle might block. Since throttled is // paused, this should not enqueue an executor in the base target.
Enqueue(throttled, [&]() { runnableFinished = true; });
ASSERT_TRUE(base->IsEmpty());
// Create a separate thread that waits for the queue to become idle, and // then takes observable action.
nsCOMPtr<nsIRunnable> await =
NS_NewRunnableFunction("AwaitIdlePaused", [&]() {
throttled->AwaitIdle();
MutexAutoLock lock(mutex);
dequeue_await += " await";
threadFinished = true;
cond.Notify();
});
// We can't guarantee that the thread has reached the AwaitIdle call, but we // can get pretty close. Either way, it shouldn't affect the behavior of the // test.
PR_Sleep(PR_MillisecondsToInterval(100));
// The AwaitIdle call should be blocked, even though there is no executor, // because throttled is paused.
{
MutexAutoLock lock(mutex);
ASSERT_EQ(dequeue_await, "");
dequeue_await += "dequeue";
ASSERT_FALSE(threadFinished);
}
// A paused TEQ contributes no events to its base target. (This is covered by // other tests...)
ASSERT_NS_SUCCEEDED(base->Run());
ASSERT_TRUE(base->IsEmpty());
ASSERT_FALSE(throttled->IsEmpty());
// Resume and drain the queue.
ASSERT_FALSE(runnableFinished);
ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
ASSERT_NS_SUCCEEDED(base->Run());
ASSERT_TRUE(base->IsEmpty());
ASSERT_TRUE(throttled->IsEmpty());
ASSERT_TRUE(runnableFinished);
// Wait for the thread to finish.
{
MutexAutoLock lock(mutex); while (!threadFinished) cond.Wait();
ASSERT_EQ(dequeue_await, "dequeue await");
}
// Since we're paused, queueing an event on throttled shouldn't queue the // executor on the base target.
Enqueue(throttled, [&]() { log += 'a'; });
ASSERT_EQ(throttled->Length(), 1U);
ASSERT_EQ(base->Length(), 0U);
// Resuming throttled should create the executor, since throttled is not // empty.
ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
ASSERT_EQ(throttled->Length(), 1U);
ASSERT_EQ(base->Length(), 1U);
// Pausing can't remove the executor from the base target since we've already // queued it there, but it can ensure that it doesn't do anything.
ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true));
// As before, resuming must create the executor, since throttled is not empty.
ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
ASSERT_EQ(throttled->Length(), 1U);
ASSERT_EQ(base->Length(), 1U);
// Since throttled is empty, pausing and resuming now should not enqueue an // executor.
ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true));
ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false));
ASSERT_EQ(throttled->Length(), 0U);
ASSERT_EQ(base->Length(), 0U);
}
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.