/** * @file llmainthreadtask_test.cpp * @author Nat Goodspeed * @date 2019-12-05 * @brief Test for llmainthreadtask. * * $LicenseInfo:firstyear=2019&license=viewerlgpl$ * Copyright (c) 2019, Linden Research, Inc. * $/LicenseInfo$ */ // Precompiled header #include "linden_common.h" // associated header #include "llmainthreadtask.h" // STL headers // std headers #include // external library headers // other Linden headers #include "../test/lltut.h" #include "../test/sync.h" #include "llcallbacklist.h" #include "llthread.h" // on_main_thread() #include "lockstatic.h" /***************************************************************************** * TUT *****************************************************************************/ namespace tut { struct llmainthreadtask_data { // 5-second timeout Sync mSync{F32Milliseconds(5000.0f)}; llmainthreadtask_data() { // we're not testing the result; this is just to cache the // initial thread as the main thread. on_main_thread(); } }; typedef test_group llmainthreadtask_group; typedef llmainthreadtask_group::object object; llmainthreadtask_group llmainthreadtaskgrp("llmainthreadtask"); template<> template<> void object::test<1>() { set_test_name("inline"); bool ran = false; bool result = LLMainThreadTask::dispatch( [&ran]()->bool{ ran = true; return true; }); ensure("didn't run lambda", ran); ensure("didn't return result", result); } struct StaticData { std::mutex mMutex; // LockStatic looks for mMutex bool ran{false}; }; typedef llthread::LockStatic LockStatic; template<> template<> void object::test<2>() { set_test_name("cross-thread"); skip("This test is prone to build-time hangs"); std::atomic_bool result(false); // wrapping our thread lambda in a packaged_task will catch any // exceptions it might throw and deliver them via future std::packaged_task thread_work( [this, &result](){ // unblock test<2>()'s yield_until(1) mSync.set(1); // dispatch work to main thread -- should block here bool on_main( LLMainThreadTask::dispatch( []()->bool{ // have to lock static mutex to set static data LockStatic()->ran = true; // indicate whether task was run on the main thread return on_main_thread(); })); // wait for test<2>() to unblock us again mSync.yield_until(3); result = on_main; }); auto thread_result = thread_work.get_future(); std::thread thread; try { // run thread_work thread = std::thread(std::move(thread_work)); // wait for thread to set(1) mSync.yield_until(1); // try to acquire the lock, should block because thread has it LockStatic lk; // wake up when dispatch() unlocks the static mutex ensure("shouldn't have run yet", !lk->ran); ensure("shouldn't have returned yet", !result); // unlock so the task can acquire the lock lk.unlock(); // run the task -- should unblock thread, which will immediately block // on mSync LLCallbackList::instance().callFunctions(); // 'lk', having unlocked, can no longer be used to access; relock with // a new LockStatic instance ensure("should now have run", LockStatic()->ran); ensure("returned too early", !result); // okay, let thread perform the assignment mSync.set(3); } catch (...) { // A test failure exception anywhere in the try block can cause // the test program to terminate without explanation when // ~thread() finds that 'thread' is still joinable. We could // either join() or detach() it -- but since it might be blocked // waiting for something from the main thread that now can never // happen, it's safer to detach it. thread.detach(); throw; } // 'thread' should be all done now thread.join(); // deliver any exception thrown by thread_work thread_result.get(); ensure("ran changed", LockStatic()->ran); ensure("didn't run on main thread", result); } } // namespace tut