summaryrefslogtreecommitdiff
path: root/indra/llcommon/tests/llmainthreadtask_test.cpp
blob: ea4232ad78cb177b82506f7e790fddb8af9af400 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
 * @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 "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_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
            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