From 960593fd5eedcc63632fe4e0e3b71ac4afc91d11 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 9 Dec 2019 11:37:36 -0500 Subject: DRTVWR-494: Add LLMainThreadTask to perform work on the main thread. If already running on the main thread, LLMaintThreadTask simply runs the work inline. Otherwise it queues it for the main thread using LLEventTimer, using std::future to retrieve the result. --- indra/llcommon/llmainthreadtask.h | 102 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 indra/llcommon/llmainthreadtask.h (limited to 'indra/llcommon/llmainthreadtask.h') diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h new file mode 100644 index 0000000000..526374981a --- /dev/null +++ b/indra/llcommon/llmainthreadtask.h @@ -0,0 +1,102 @@ +/** + * @file llmainthreadtask.h + * @author Nat Goodspeed + * @date 2019-12-04 + * @brief LLMainThreadTask dispatches work to the main thread. When invoked on + * the main thread, it performs the work inline. + * + * $LicenseInfo:firstyear=2019&license=viewerlgpl$ + * Copyright (c) 2019, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLMAINTHREADTASK_H) +#define LL_LLMAINTHREADTASK_H + +#include "lleventtimer.h" +#include "llthread.h" +#include "lockstatic.h" +#include "llmake.h" +#include +#include // std::result_of +#include + +class LLMainThreadTask +{ +private: + // Don't instantiate this class -- use dispatch() instead. + LLMainThreadTask() {} + // If our caller doesn't explicitly pass a LockStatic, make a + // fake one. + struct Static + { + boost::signals2::dummy_mutex mMutex; + }; + typedef llthread::LockStatic LockStatic; + +public: + /// dispatch() is the only way to invoke this functionality. + /// If you call it with a LockStatic, dispatch() unlocks it + /// before blocking for the result. + template + static auto dispatch(llthread::LockStatic& lk, CALLABLE&& callable) + -> decltype(callable()) + { + if (on_main_thread()) + { + // we're already running on the main thread, perfect + return callable(); + } + else + { + // It's essential to construct LLEventTimer subclass instances on + // the heap because, on completion, LLEventTimer deletes them. + // Once we enable C++17, we can use Class Template Argument + // Deduction. Until then, use llmake_heap(). + auto* task = llmake_heap(std::forward(callable)); + // The moment we construct a new LLEventTimer subclass object, its + // tick() method might get called. However, its tick() method + // might depend on something locked by the passed LockStatic. + // Unlock it so tick() can proceed. + lk.unlock(); + auto future = task->mTask.get_future(); + // Now simply block on the future. + return future.get(); + } + } + + /// You can call dispatch() without a LockStatic. + template + static auto dispatch(CALLABLE&& callable) -> decltype(callable()) + { + LockStatic lk; + return dispatch(lk, std::forward(callable)); + } + +private: + template + struct Task: public LLEventTimer + { + Task(CALLABLE&& callable): + // no wait time: call tick() next chance we get + LLEventTimer(0), + mTask(std::forward(callable)) + {} + BOOL tick() override + { + // run the task on the main thread, will populate the future + // obtained by get_future() + mTask(); + // tell LLEventTimer we're done (one shot) + return TRUE; + } + // Given arbitrary CALLABLE, which might be a lambda, how are we + // supposed to obtain its signature for std::packaged_task? It seems + // redundant to have to add an argument list to engage result_of, then + // add the argument list again to complete the signature. At least we + // only support a nullary CALLABLE. + std::packaged_task::type()> mTask; + }; +}; + +#endif /* ! defined(LL_LLMAINTHREADTASK_H) */ -- cgit v1.2.3 From dd98717caa712c17ef6a8f187754670b614ab253 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Dec 2019 11:51:38 -0500 Subject: DRTVWR-494: Document LLMainThreadTask class. --- indra/llcommon/llmainthreadtask.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'indra/llcommon/llmainthreadtask.h') diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index 526374981a..2e0583d104 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -21,6 +21,34 @@ #include // std::result_of #include +/** + * LLMainThreadTask provides a way to perform some task specifically on the + * main thread, waiting for it to complete. A task consists of a C++ nullary + * invocable (i.e. any callable that requires no arguments) with arbitrary + * return type. + * + * Instead of instantiating LLMainThreadTask, pass your invocable to its + * static dispatch() method. dispatch() returns the result of calling your + * task. (Or, if your task throws an exception, dispatch() throws that + * exception. See std::packaged_task.) + * + * When you call dispatch() on the main thread (as determined by + * on_main_thread() in llthread.h), it simply calls your task and returns the + * result. + * + * When you call dispatch() on a secondary thread, it instantiates an + * LLEventTimer subclass scheduled immediately. Next time the main loop calls + * LLEventTimer::updateClass(), your task will be run, and LLMainThreadTask + * will fulfill a future with its result. Meanwhile the requesting thread + * blocks on that future. As soon as it is set, the requesting thread wakes up + * with the task result. + * + * Under some circumstances it's necessary for the calling thread to hold a + * lock until the task has been scheduled -- yet important to release the lock + * while waiting for the result. If you pass a LockStatic to dispatch(), + * a secondary thread will unlock it before blocking on the future. (The main + * thread simply holds the lock for the duration of the task.) + */ class LLMainThreadTask { private: -- cgit v1.2.3 From 6586918df0039f60c1c02c134a6a0e0762997d56 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Dec 2019 10:35:47 -0500 Subject: DRTVWR-494: Remove LLMainThreadTask::dispatch(LockStatic&, ...) Monty's code review reveals that conflating dispatch() with [un]lock functionality is inconsistent and unnecessary. --- indra/llcommon/llmainthreadtask.h | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) (limited to 'indra/llcommon/llmainthreadtask.h') diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index 2e0583d104..d509b687c0 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -15,11 +15,9 @@ #include "lleventtimer.h" #include "llthread.h" -#include "lockstatic.h" #include "llmake.h" #include #include // std::result_of -#include /** * LLMainThreadTask provides a way to perform some task specifically on the @@ -42,33 +40,17 @@ * will fulfill a future with its result. Meanwhile the requesting thread * blocks on that future. As soon as it is set, the requesting thread wakes up * with the task result. - * - * Under some circumstances it's necessary for the calling thread to hold a - * lock until the task has been scheduled -- yet important to release the lock - * while waiting for the result. If you pass a LockStatic to dispatch(), - * a secondary thread will unlock it before blocking on the future. (The main - * thread simply holds the lock for the duration of the task.) */ class LLMainThreadTask { private: // Don't instantiate this class -- use dispatch() instead. LLMainThreadTask() {} - // If our caller doesn't explicitly pass a LockStatic, make a - // fake one. - struct Static - { - boost::signals2::dummy_mutex mMutex; - }; - typedef llthread::LockStatic LockStatic; public: /// dispatch() is the only way to invoke this functionality. - /// If you call it with a LockStatic, dispatch() unlocks it - /// before blocking for the result. - template - static auto dispatch(llthread::LockStatic& lk, CALLABLE&& callable) - -> decltype(callable()) + template + static auto dispatch(CALLABLE&& callable) -> decltype(callable()) { if (on_main_thread()) { @@ -82,25 +64,12 @@ public: // Once we enable C++17, we can use Class Template Argument // Deduction. Until then, use llmake_heap(). auto* task = llmake_heap(std::forward(callable)); - // The moment we construct a new LLEventTimer subclass object, its - // tick() method might get called. However, its tick() method - // might depend on something locked by the passed LockStatic. - // Unlock it so tick() can proceed. - lk.unlock(); auto future = task->mTask.get_future(); // Now simply block on the future. return future.get(); } } - /// You can call dispatch() without a LockStatic. - template - static auto dispatch(CALLABLE&& callable) -> decltype(callable()) - { - LockStatic lk; - return dispatch(lk, std::forward(callable)); - } - private: template struct Task: public LLEventTimer -- cgit v1.2.3