phoenix-firestorm/indra/llcommon/tests/llmainthreadtask_test.cpp

138 lines
4.8 KiB
C++

/**
* @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 <atomic>
// external library headers
// other Linden headers
#include "../test/lltut.h"
#include "../test/sync.h"
#include "llthread.h" // on_main_thread()
#include "lleventtimer.h"
#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_data> 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<StaticData> 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<void()> 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
LLEventTimer::updateClass();
// '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