360 lines
12 KiB
C++
360 lines
12 KiB
C++
/**
|
|
* @file llcoros.h
|
|
* @author Nat Goodspeed
|
|
* @date 2009-06-02
|
|
* @brief Manage running boost::coroutine instances
|
|
*
|
|
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#if ! defined(LL_LLCOROS_H)
|
|
#define LL_LLCOROS_H
|
|
|
|
#include "llexception.h"
|
|
#include <boost/fiber/fss.hpp>
|
|
#include <boost/fiber/future/promise.hpp>
|
|
#include <boost/fiber/future/future.hpp>
|
|
#include "mutex.h"
|
|
#include "llsingleton.h"
|
|
#include "llinstancetracker.h"
|
|
#include <boost/function.hpp>
|
|
#include <string>
|
|
#include <exception>
|
|
#include <queue>
|
|
|
|
// e.g. #include LLCOROS_MUTEX_HEADER
|
|
#define LLCOROS_MUTEX_HEADER <boost/fiber/mutex.hpp>
|
|
#define LLCOROS_CONDVAR_HEADER <boost/fiber/condition_variable.hpp>
|
|
|
|
namespace boost {
|
|
namespace fibers {
|
|
class mutex;
|
|
enum class cv_status;
|
|
class condition_variable;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registry of named Boost.Coroutine instances
|
|
*
|
|
* The Boost.Coroutine library supports the general case of a coroutine
|
|
* accepting arbitrary parameters and yielding multiple (sets of) results. For
|
|
* such use cases, it's natural for the invoking code to retain the coroutine
|
|
* instance: the consumer repeatedly calls into the coroutine, perhaps passing
|
|
* new parameter values, prompting it to yield its next result.
|
|
*
|
|
* Our typical coroutine usage is different, though. For us, coroutines
|
|
* provide an alternative to the @c Responder pattern. Our typical coroutine
|
|
* has @c void return, invoked in fire-and-forget mode: the handler for some
|
|
* user gesture launches the coroutine and promptly returns to the main loop.
|
|
* The coroutine initiates some action that will take multiple frames (e.g. a
|
|
* capability request), waits for its result, processes it and silently steals
|
|
* away.
|
|
*
|
|
* This usage poses two (related) problems:
|
|
*
|
|
* # Who should own the coroutine instance? If it's simply local to the
|
|
* handler code that launches it, return from the handler will destroy the
|
|
* coroutine object, terminating the coroutine.
|
|
* # Once the coroutine terminates, in whatever way, who's responsible for
|
|
* cleaning up the coroutine object?
|
|
*
|
|
* LLCoros is a Singleton collection of currently-active coroutine instances.
|
|
* Each has a name. You ask LLCoros to launch a new coroutine with a suggested
|
|
* name prefix; from your prefix it generates a distinct name, registers the
|
|
* new coroutine and returns the actual name.
|
|
*
|
|
* The name
|
|
* can provide diagnostic info: we can look up the name of the
|
|
* currently-running coroutine.
|
|
*/
|
|
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
|
|
{
|
|
LLSINGLETON(LLCoros);
|
|
~LLCoros();
|
|
|
|
void cleanupSingleton() override;
|
|
public:
|
|
/// The viewer's use of the term "coroutine" became deeply embedded before
|
|
/// the industry term "fiber" emerged to distinguish userland threads from
|
|
/// simpler, more transient kinds of coroutines. Semantically they've
|
|
/// always been fibers. But at this point in history, we're pretty much
|
|
/// stuck with the term "coroutine."
|
|
typedef boost::fibers::fiber coro;
|
|
/// Canonical callable type
|
|
typedef boost::function<void()> callable_t;
|
|
|
|
/**
|
|
* Create and start running a new coroutine with specified name. The name
|
|
* string you pass is a suggestion; it will be tweaked for uniqueness. The
|
|
* actual name is returned to you.
|
|
*
|
|
* Usage looks like this, for (e.g.) two coroutine parameters:
|
|
* @code
|
|
* class MyClass
|
|
* {
|
|
* public:
|
|
* ...
|
|
* // Do NOT NOT NOT accept reference params!
|
|
* // Pass by value only!
|
|
* void myCoroutineMethod(std::string, LLSD);
|
|
* ...
|
|
* };
|
|
* ...
|
|
* std::string name = LLCoros::instance().launch(
|
|
* "mycoro", boost::bind(&MyClass::myCoroutineMethod, this,
|
|
* "somestring", LLSD(17));
|
|
* @endcode
|
|
*
|
|
* Your function/method can accept any parameters you want -- but ONLY BY
|
|
* VALUE! Reference parameters are a BAD IDEA! You Have Been Warned. See
|
|
* DEV-32777 comments for an explanation.
|
|
*
|
|
* Pass a nullary callable. It works to directly pass a nullary free
|
|
* function (or static method); for other cases use a lambda expression,
|
|
* std::bind() or boost::bind(). Of course, for a non-static class method,
|
|
* the first parameter must be the class instance. Any other parameters
|
|
* should be passed via the enclosing expression.
|
|
*
|
|
* launch() tweaks the suggested name so it won't collide with any
|
|
* existing coroutine instance, creates the coroutine instance, registers
|
|
* it with the tweaked name and runs it until its first wait. At that
|
|
* point it returns the tweaked name.
|
|
*/
|
|
std::string launch(const std::string& prefix, const callable_t& callable);
|
|
|
|
/**
|
|
* Abort a running coroutine by name. Normally, when a coroutine either
|
|
* runs to completion or terminates with an exception, LLCoros quietly
|
|
* cleans it up. This is for use only when you must explicitly interrupt
|
|
* one prematurely. Returns @c true if the specified name was found and
|
|
* still running at the time.
|
|
*/
|
|
// bool kill(const std::string& name);
|
|
|
|
/**
|
|
* From within a coroutine, look up the (tweaked) name string by which
|
|
* this coroutine is registered. Returns the empty string if not found
|
|
* (e.g. if the coroutine was launched by hand rather than using
|
|
* LLCoros::launch()).
|
|
*/
|
|
static std::string getName();
|
|
|
|
/**
|
|
* rethrow() is called by the thread's main fiber to propagate an
|
|
* exception from any coroutine into the main fiber, where it can engage
|
|
* the normal unhandled-exception machinery, up to and including crash
|
|
* reporting.
|
|
*
|
|
* LLCoros maintains a queue of otherwise-uncaught exceptions from
|
|
* terminated coroutines. Each call to rethrow() pops the first of those
|
|
* and rethrows it. When the queue is empty (normal case), rethrow() is a
|
|
* no-op.
|
|
*/
|
|
void rethrow();
|
|
|
|
/**
|
|
* This variation returns a name suitable for log messages: the explicit
|
|
* name for an explicitly-launched coroutine, or "mainN" for the default
|
|
* coroutine on a thread.
|
|
*/
|
|
static std::string logname();
|
|
|
|
/**
|
|
* For delayed initialization. To be clear, this will only affect
|
|
* coroutines launched @em after this point. The underlying facility
|
|
* provides no way to alter the stack size of any running coroutine.
|
|
*/
|
|
void setStackSize(S32 stacksize);
|
|
|
|
/// diagnostic
|
|
void printActiveCoroutines(const std::string& when=std::string());
|
|
|
|
/// get the current coro::id for those who really really care
|
|
static coro::id get_self();
|
|
|
|
/**
|
|
* Most coroutines, most of the time, don't "consume" the events for which
|
|
* they're suspending. This way, an arbitrary number of listeners (whether
|
|
* coroutines or simple callbacks) can be registered on a particular
|
|
* LLEventPump, every listener responding to each of the events on that
|
|
* LLEventPump. But a particular coroutine can assert that it will consume
|
|
* each event for which it suspends. (See also llcoro::postAndSuspend(),
|
|
* llcoro::VoidListener)
|
|
*/
|
|
static void set_consuming(bool consuming);
|
|
static bool get_consuming();
|
|
|
|
/**
|
|
* RAII control of the consuming flag
|
|
*/
|
|
class OverrideConsuming
|
|
{
|
|
public:
|
|
OverrideConsuming(bool consuming):
|
|
mPrevConsuming(get_consuming())
|
|
{
|
|
set_consuming(consuming);
|
|
}
|
|
OverrideConsuming(const OverrideConsuming&) = delete;
|
|
~OverrideConsuming()
|
|
{
|
|
set_consuming(mPrevConsuming);
|
|
}
|
|
|
|
private:
|
|
bool mPrevConsuming;
|
|
};
|
|
|
|
/// set string coroutine status for diagnostic purposes
|
|
static void setStatus(const std::string& status);
|
|
static std::string getStatus();
|
|
|
|
/// RAII control of status
|
|
class TempStatus
|
|
{
|
|
public:
|
|
TempStatus(const std::string& status):
|
|
mOldStatus(getStatus())
|
|
{
|
|
setStatus(status);
|
|
}
|
|
TempStatus(const TempStatus&) = delete;
|
|
~TempStatus()
|
|
{
|
|
setStatus(mOldStatus);
|
|
}
|
|
|
|
private:
|
|
std::string mOldStatus;
|
|
};
|
|
|
|
/// thrown by checkStop()
|
|
// It may sound ironic that Stop is derived from LLContinueError, but the
|
|
// point is that LLContinueError is the category of exception that should
|
|
// not immediately crash the viewer. Stop and its subclasses are to notify
|
|
// coroutines that the viewer intends to shut down. The expected response
|
|
// is to terminate the coroutine, rather than abort the viewer.
|
|
struct Stop: public LLContinueError
|
|
{
|
|
Stop(const std::string& what): LLContinueError(what) {}
|
|
};
|
|
|
|
/// early stages
|
|
struct Stopping: public Stop
|
|
{
|
|
Stopping(const std::string& what): Stop(what) {}
|
|
};
|
|
|
|
/// cleaning up
|
|
struct Stopped: public Stop
|
|
{
|
|
Stopped(const std::string& what): Stop(what) {}
|
|
};
|
|
|
|
/// cleaned up -- not much survives!
|
|
struct Shutdown: public Stop
|
|
{
|
|
Shutdown(const std::string& what): Stop(what) {}
|
|
};
|
|
|
|
/// Call this intermittently if there's a chance your coroutine might
|
|
/// continue running into application shutdown. Throws Stop if LLCoros has
|
|
/// been cleaned up.
|
|
static void checkStop();
|
|
|
|
/**
|
|
* Aliases for promise and future. An older underlying future implementation
|
|
* required us to wrap future; that's no longer needed. However -- if it's
|
|
* important to restore kill() functionality, we might need to provide a
|
|
* proxy, so continue using the aliases.
|
|
*/
|
|
template <typename T>
|
|
using Promise = boost::fibers::promise<T>;
|
|
template <typename T>
|
|
using Future = boost::fibers::future<T>;
|
|
template <typename T>
|
|
static Future<T> getFuture(Promise<T>& promise) { return promise.get_future(); }
|
|
|
|
// use mutex, lock, condition_variable suitable for coroutines
|
|
using Mutex = boost::fibers::mutex;
|
|
using LockType = std::unique_lock<Mutex>;
|
|
using cv_status = boost::fibers::cv_status;
|
|
using ConditionVariable = boost::fibers::condition_variable;
|
|
|
|
/// for data local to each running coroutine
|
|
template <typename T>
|
|
using local_ptr = boost::fibers::fiber_specific_ptr<T>;
|
|
|
|
private:
|
|
std::string generateDistinctName(const std::string& prefix) const;
|
|
void toplevel(std::string name, callable_t callable);
|
|
struct CoroData;
|
|
static CoroData& get_CoroData(const std::string& caller);
|
|
void saveException(const std::string& name, std::exception_ptr exc);
|
|
|
|
struct ExceptionData
|
|
{
|
|
ExceptionData(const std::string& nm, std::exception_ptr exc):
|
|
name(nm),
|
|
exception(exc)
|
|
{}
|
|
// name of coroutine that originally threw this exception
|
|
std::string name;
|
|
// the thrown exception
|
|
std::exception_ptr exception;
|
|
};
|
|
std::queue<ExceptionData> mExceptionQueue;
|
|
|
|
S32 mStackSize;
|
|
|
|
// coroutine-local storage, as it were: one per coro we track
|
|
struct CoroData: public LLInstanceTracker<CoroData, std::string>
|
|
{
|
|
CoroData(const std::string& name);
|
|
CoroData(int n);
|
|
|
|
// tweaked name of the current coroutine
|
|
const std::string mName;
|
|
// set_consuming() state
|
|
bool mConsuming;
|
|
// setStatus() state
|
|
std::string mStatus;
|
|
F64 mCreationTime; // since epoch
|
|
};
|
|
|
|
// Identify the current coroutine's CoroData. This local_ptr isn't static
|
|
// because it's a member of an LLSingleton, and we rely on it being
|
|
// cleaned up in proper dependency order.
|
|
local_ptr<CoroData> mCurrent;
|
|
};
|
|
|
|
namespace llcoro
|
|
{
|
|
|
|
inline
|
|
std::string logname() { return LLCoros::logname(); }
|
|
|
|
} // llcoro
|
|
|
|
#endif /* ! defined(LL_LLCOROS_H) */
|