summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2023-09-12 09:32:32 -0400
committerNat Goodspeed <nat@lindenlab.com>2023-09-12 09:32:32 -0400
commit796085fc537e6bac7999e4b6624f02196eeaf4ad (patch)
tree89d1e91bc2433f2165984b4b626f1f9a6ecb178e /indra/llcommon
parent5ed44adec58ba2ecb8dd073122bd8882fed30638 (diff)
DRTVWR-588: Reimplement LLMainThreadTask based on WorkQueue
instead of on LLEventTimer. LLEventTimer takes cycles from the main loop to run through the collection of pending LLEventTimers, checking each to see if we've reached its timestamp. But LLMainThreadTask does not require delay timing; it wants the main loop to service it ASAP. That's what the "mainloop" WorkQueue is for. But WorkQueue::waitForResult() forbids calls from a thread's default coroutine. While that restriction may still make sense in general, we specifically want to be able to pause LLMainThreadTask's caller, no matter what coroutine it's running on. Introduce WorkQueue::waitForResult_() that bypasses the check.
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/llmainthreadtask.h58
-rw-r--r--indra/llcommon/workqueue.h40
2 files changed, 43 insertions, 55 deletions
diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h
index d509b687c0..dde6c20210 100644
--- a/indra/llcommon/llmainthreadtask.h
+++ b/indra/llcommon/llmainthreadtask.h
@@ -13,11 +13,8 @@
#if ! defined(LL_LLMAINTHREADTASK_H)
#define LL_LLMAINTHREADTASK_H
-#include "lleventtimer.h"
#include "llthread.h"
-#include "llmake.h"
-#include <future>
-#include <type_traits> // std::result_of
+#include "workqueue.h"
/**
* LLMainThreadTask provides a way to perform some task specifically on the
@@ -28,18 +25,17 @@
* 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.)
+ * exception.)
*
* 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.
+ * When you call dispatch() on a secondary thread, it posts your task to
+ * gMainloopWork, the WorkQueue serviced by the main thread, using
+ * WorkQueue::waitForResult() to block the caller. Next time the main loop
+ * calls gMainloopWork.runFor(), your task will be run, and waitForResult()
+ * will return its result.
*/
class LLMainThreadTask
{
@@ -59,41 +55,15 @@ public:
}
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<Task>(std::forward<CALLABLE>(callable));
- auto future = task->mTask.get_future();
- // Now simply block on the future.
- return future.get();
+ auto queue{ LL::WorkQueue::getInstance("mainloop") };
+ // If this needs a null check and a message, please introduce a
+ // method in the .cpp file so consumers of this header don't drag
+ // in llerror.h.
+ // Use waitForResult_() so dispatch() can be used even from the
+ // calling thread's default coroutine.
+ return queue->waitForResult_(std::forward<CALLABLE>(callable));
}
}
-
-private:
- template <typename CALLABLE>
- struct Task: public LLEventTimer
- {
- Task(CALLABLE&& callable):
- // no wait time: call tick() next chance we get
- LLEventTimer(0),
- mTask(std::forward<CALLABLE>(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<typename std::result_of<CALLABLE()>::type()> mTask;
- };
};
#endif /* ! defined(LL_LLMAINTHREADTASK_H) */
diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h
index 70fd65bd0c..20fd15d0d5 100644
--- a/indra/llcommon/workqueue.h
+++ b/indra/llcommon/workqueue.h
@@ -12,7 +12,6 @@
#if ! defined(LL_WORKQUEUE_H)
#define LL_WORKQUEUE_H
-#include "llcoros.h"
#include "llexception.h"
#include "llinstancetracker.h"
#include "threadsafeschedule.h"
@@ -200,31 +199,51 @@ namespace LL
}
/**
- * Post work to another WorkQueue to be run at a specified time,
- * blocking the calling coroutine until then, returning the result to
- * caller on completion.
+ * Post work, blocking the calling coroutine until then, returning the
+ * result to caller on completion.
*
* In general, we assume that each thread's default coroutine is busy
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
* forbid calling waitForResult() from a thread's default coroutine.
*/
template <typename CALLABLE>
- auto waitForResult(const TimePoint& time, CALLABLE&& callable);
+ auto waitForResult(CALLABLE&& callable)
+ {
+ return waitForResult(TimePoint::clock::now(), std::forward<CALLABLE>(callable));
+ }
/**
- * Post work to another WorkQueue, blocking the calling coroutine
- * until then, returning the result to caller on completion.
+ * Post work to be run at a specified time, blocking the calling
+ * coroutine until then, returning the result to caller on completion.
*
* In general, we assume that each thread's default coroutine is busy
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
* forbid calling waitForResult() from a thread's default coroutine.
*/
template <typename CALLABLE>
- auto waitForResult(CALLABLE&& callable)
+ auto waitForResult(const TimePoint& time, CALLABLE&& callable)
{
- return waitForResult(TimePoint::clock::now(), std::move(callable));
+ checkCoroutine("waitForResult()");
+ return waitForResult_(time, std::forward<CALLABLE>(callable));
}
+ /**
+ * Post work, blocking the calling coroutine until then, returning the
+ * result to caller on completion.
+ */
+ template <typename CALLABLE>
+ auto waitForResult_(CALLABLE&& callable)
+ {
+ return waitForResult_(TimePoint::clock::now(), std::forward<CALLABLE>(callable));
+ }
+
+ /**
+ * Post work to be run at a specified time, blocking the calling
+ * coroutine until then, returning the result to caller on completion.
+ */
+ template <typename CALLABLE>
+ auto waitForResult_(const TimePoint& time, CALLABLE&& callable);
+
/*--------------------------- worker API ---------------------------*/
/**
@@ -561,9 +580,8 @@ namespace LL
};
template <typename CALLABLE>
- auto WorkQueue::waitForResult(const TimePoint& time, CALLABLE&& callable)
+ auto WorkQueue::waitForResult_(const TimePoint& time, CALLABLE&& callable)
{
- checkCoroutine("waitForResult()");
// derive callable's return type so we can specialize for void
return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>()
(this, time, std::forward<CALLABLE>(callable));