TEST(AsyncTask, RequestCoalescingMultithreaded) { RunLoop loop; unsigned count = 0; AsyncTask async([&count] { ++count; }); std::vector<std::unique_ptr<Thread<TestWorker>>> threads; ThreadContext context = {"Test"}; unsigned numThreads = 25; for (unsigned i = 0; i < numThreads; ++i) { std::unique_ptr<Thread<TestWorker>> thread = std::make_unique<Thread<TestWorker>>(context, &async); thread->invoke(&TestWorker::run); threads.push_back(std::move(thread)); } // Join all the threads threads.clear(); loop.runOnce(); EXPECT_EQ(count, 1u); }
TEST(AsyncTask, ThreadSafety) { RunLoop loop; unsigned count = 0, numThreads = 25; std::atomic_uint completed(numThreads); AsyncTask async([&count] { ++count; }); auto retainer = Scheduler::GetBackground(); auto mailbox = std::make_shared<Mailbox>(*retainer); TestWorker worker(&async); ActorRef<TestWorker> workerRef(worker, mailbox); for (unsigned i = 0; i < numThreads; ++i) { // The callback runs on the worker, thus the atomic type. workerRef.invoke(&TestWorker::runWithCallback, [&] { if (!--completed) loop.stop(); }); } loop.run(); // We expect here more than 1 but 1 would also be // a valid result, although very unlikely (I hope). EXPECT_GT(count, 0u); }
TEST(AsyncTask, DestroyAfterSignaling) { RunLoop loop; // We're creating two tasks and signal both of them; the one that gets fired first destroys // the other one. Make sure that the second one we destroyed doesn't fire. std::unique_ptr<AsyncTask> task1, task2; task1 = std::make_unique<AsyncTask>([&] { task2.reset(); if (!task1) { FAIL() << "Task was destroyed but invoked anyway"; } }); task2 = std::make_unique<AsyncTask>([&] { task1.reset(); if (!task2) { FAIL() << "Task was destroyed but invoked anyway"; } }); task1->send(); task2->send(); loop.runOnce(); }
TEST(AsyncTask, ThreadSafety) { RunLoop loop; unsigned count = 0; AsyncTask async([&count] { ++count; }); unsigned numThreads = 25; auto callback = [&] { if (!--numThreads) { loop.stop(); } }; std::vector<std::unique_ptr<Thread<TestWorker>>> threads; std::vector<std::unique_ptr<mbgl::AsyncRequest>> requests; ThreadContext context = {"Test"}; for (unsigned i = 0; i < numThreads; ++i) { std::unique_ptr<Thread<TestWorker>> thread = std::make_unique<Thread<TestWorker>>(context, &async); requests.push_back( thread->invokeWithCallback(&TestWorker::runWithCallback, callback)); threads.push_back(std::move(thread)); } loop.run(); // We expect here more than 1 but 1 would also be // a valid result, although very unlikely (I hope). EXPECT_GT(count, 0u); }
TEST(AsyncTask, RequestCoalescingMultithreaded) { RunLoop loop; unsigned count = 0, numThreads = 25; AsyncTask async([&count] { ++count; }); auto retainer = Scheduler::GetBackground(); auto mailbox = std::make_shared<Mailbox>(*retainer); TestWorker worker(&async); ActorRef<TestWorker> workerRef(worker, mailbox); for (unsigned i = 0; i < numThreads; ++i) { workerRef.invoke(&TestWorker::run); } std::promise<void> barrier; std::future<void> barrierFuture = barrier.get_future(); workerRef.invoke(&TestWorker::sync, std::move(barrier)); barrierFuture.wait(); loop.runOnce(); EXPECT_EQ(count, 1u); }
TEST(Timer, Repeat) { RunLoop loop; Timer timer; unsigned count = 10; auto callback = [&] { if (!--count) { loop.stop(); } }; auto interval = std::chrono::milliseconds(50); auto expectedTotalTime = interval * count; auto first = mbgl::Clock::now(); timer.start(interval, interval, callback); loop.run(); using namespace std::chrono; auto totalTime = duration_cast<milliseconds>(mbgl::Clock::now() - first); EXPECT_GE(totalTime, expectedTotalTime * 0.8); EXPECT_LE(totalTime, expectedTotalTime * 1.2); }
TEST(Timer, StartOverrides) { RunLoop loop; Timer timer; auto interval1 = std::chrono::milliseconds(50); auto interval2 = std::chrono::milliseconds(250); auto expectedTotalTime = interval1 + interval2; int count = 0; auto callback2 = [&] { ++count; loop.stop(); }; auto callback1 = [&] { ++count; timer.start(interval2, mbgl::Duration::zero(), callback2); }; auto first = mbgl::Clock::now(); timer.start(interval1, mbgl::Duration::zero(), callback1); loop.run(); using namespace std::chrono; auto totalTime = duration_cast<milliseconds>(mbgl::Clock::now() - first); EXPECT_EQ(count, 2); EXPECT_GE(totalTime, expectedTotalTime * 0.8); EXPECT_LE(totalTime, expectedTotalTime * 1.2); }
void RunLoop::wakeUpEvent(void* data, void*, unsigned int) { RunLoop* loop = static_cast<RunLoop*>(data); { MutexLocker locker(loop->m_wakeUpEventRequestedLock); loop->m_wakeUpEventRequested = false; } loop->performWork(); }
void RunLoop::run() { RunLoop* mainRunLoop = RunLoop::current(); GMainLoop* innermostLoop = mainRunLoop->innermostLoop(); if (!g_main_loop_is_running(innermostLoop)) { g_main_loop_run(innermostLoop); return; } // Create and run a nested loop if the innermost one was already running. GMainLoop* nestedMainLoop = g_main_loop_new(0, FALSE); mainRunLoop->pushNestedMainLoop(nestedMainLoop); g_main_loop_run(nestedMainLoop); mainRunLoop->popNestedMainLoop(); }
TEST(AsyncTask, RequestCoalescing) { RunLoop loop; unsigned count = 0; AsyncTask async([&count] { ++count; }); async.send(); async.send(); async.send(); async.send(); async.send(); loop.runOnce(); EXPECT_EQ(count, 1u); }
TEST(Timer, DestroyShouldStop) { RunLoop loop; auto timer1 = std::make_unique<Timer>(); Timer timer2; auto interval1 = std::chrono::milliseconds(50); auto interval2 = std::chrono::milliseconds(250); auto expectedTotalTime = interval2; int count = 0; auto callback1 = [&] { ++count; timer1.reset(); }; auto callback2 = [&] { ++count; loop.stop(); }; auto first = mbgl::Clock::now(); timer1->start(interval1, interval1, callback1); timer2.start(interval2, mbgl::Duration::zero(), callback2); loop.run(); using namespace std::chrono; auto totalTime = duration_cast<milliseconds>(mbgl::Clock::now() - first); EXPECT_EQ(count, 2); EXPECT_GE(totalTime, expectedTotalTime * 0.8); EXPECT_LE(totalTime, expectedTotalTime * 1.2); }
TEST(Timer, Basic) { RunLoop loop; Timer timer; auto callback = [&loop] { loop.stop(); }; auto interval = std::chrono::milliseconds(300); auto expectedTotalTime = interval; auto first = mbgl::Clock::now(); timer.start(interval, mbgl::Duration::zero(), callback); loop.run(); using namespace std::chrono; auto totalTime = duration_cast<milliseconds>(mbgl::Clock::now() - first); // These are not high precision timers. Especially libuv uses // cached time from the beginning of of the main loop iteration // and it is very prone to fire earlier, which is, odd. EXPECT_GE(totalTime, expectedTotalTime * 0.8); EXPECT_LE(totalTime, expectedTotalTime * 1.2); }
int RunLoop::_ReceiveLoopEvent(int fd, int events, void* data) { RunLoop* p = reinterpret_cast<RunLoop*>(data); auto iter = p->_handlers.find(fd); if ( iter == p->_handlers.end() ) return 0; // unregister fd from the looper if ( !iter->second ) return 0; // consume something char buf[12]; ::read(fd, buf, 12); // what type is it? Timer* pTimer = (Timer*)(iter->second); if (pTimer != nullptr) { // it's a Timer! // keep the timer around past any Cancel() or Remove() calls made by the callout RefCounted<Timer> timer(pTimer); if ( (events & ALOOPER_EVENT_HANGUP) == ALOOPER_EVENT_HANGUP ) { // remove it from the runloop p->RemoveTimer(timer); return 0; } timer->_fn(*timer); ///////// DO CALLOUT if ( !timer->Repeats() || timer->IsCancelled() ) { // the underlying Linux timer_t is already disarmed p->RemoveTimer(timer); return 0; } // it'll repeat, so keep processing please return 1; } // not a Timer? Must be an EventSource then EventSource *pSource = (EventSource*)(iter->second); if ( pSource != nullptr ) { // it *is* an EventSource! // keep it around so we can check even if the callout removes it from the runloop RefCounted<EventSource> source(pSource); if ( (events & ALOOPER_EVENT_HANGUP) == ALOOPER_EVENT_HANGUP ) { // calcelled, so remove it from the runloop p->RemoveEventSource(source); return 0; } source->_fn(*source); /////////// DO CALLOUT if ( source->IsCancelled() ) { // now we want to remove it p->RemoveEventSource(source); return 0; } return 1; } // errrrr... who to the what, now? return 0; }
void Invocation1::invokeInRunLoop(RunLoop& rl, float delay) { rl.addTask(etCreateObject<InvocationTask>(_target->copy()), delay); }