summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2023-01-13 00:10:23 -0500
committerNat Goodspeed <nat@lindenlab.com>2023-07-13 12:49:08 -0400
commitd637229beeb3b7fa2bc7adb850ce9337b119037c (patch)
tree4d55981d96ea87a5c1af24723d623ea5ca627367 /indra
parent27f19826b6ef9b4af5db8613c44988b5c973618b (diff)
DRTVWR-558: Introduce LLDispatchListener batched requests.
Now the value of the incoming event's dispatch key may be an LLSD::String (as before), a map or an array, as documented in the augmented Doxygen class comments. LLDispatchListener will attempt multiple calls before sending a reply. (cherry picked from commit 7671b37285c6cdf1afcddb0019311a822c8a4dc5)
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/lleventdispatcher.cpp183
-rw-r--r--indra/llcommon/lleventdispatcher.h93
2 files changed, 216 insertions, 60 deletions
diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp
index 8e20d7184a..d10cf16b88 100644
--- a/indra/llcommon/lleventdispatcher.cpp
+++ b/indra/llcommon/lleventdispatcher.cpp
@@ -744,32 +744,44 @@ std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self)
*****************************************************************************/
std::string LLDispatchListener::mReplyKey{ "reply" };
-/*==========================================================================*|
- TODO:
-* When process() finds name.isArray(), construct response array from
- dispatching each call -- args must also be (array of args structures)
- (could also construct response map, IF array contains unique names)
-* When process() finds name.isMap(), construct response map from dispatching
- each call -- value of each key is its args struct -- argskey ignored --
- note, caller can't care about order
-* Possible future transactional behavior: look up all names before calling any
-|*==========================================================================*/
bool LLDispatchListener::process(const LLSD& event) const
{
- // Collecting errors is only meaningful with a reply key. Without one, if
- // an error occurs, let the exception propagate.
- auto returned = call("", event, (! event.has(mReplyKey)));
- std::string& error{ returned.first };
- LLSD& result{ returned.second };
+ // Decide what to do based on the incoming value of the specified dispatch
+ // key.
+ LLSD name{ event[getDispatchKey()] };
+ if (name.isMap())
+ {
+ call_map(name, event);
+ }
+ else if (name.isArray())
+ {
+ call_array(name, event);
+ }
+ else
+ {
+ call_one(name, event);
+ }
+ return false;
+}
- if (! error.empty())
+void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const
+{
+ LLSD result;
+ try
{
- // Here there was an error and the incoming event has mReplyKey --
- // else DispatchError would already have propagated out of the call()
- // above. Reply with a map containing an "error" key explaining the
- // problem.
- reply(llsd::map("error", error), event);
- return false;
+ result = (*this)(event);
+ }
+ catch (const DispatchError& err)
+ {
+ if (! event.has(mReplyKey))
+ {
+ // Without a reply key, let the exception propagate.
+ throw;
+ }
+
+ // Here there was an error and the incoming event has mReplyKey. Reply
+ // with a map containing an "error" key explaining the problem.
+ return reply(llsd::map("error", err.what()), event);
}
// We seem to have gotten a valid result. But we don't know whether the
@@ -785,40 +797,127 @@ bool LLDispatchListener::process(const LLSD& event) const
}
reply(result, event);
}
- return false;
}
-// Pass empty name to call LLEventDispatcher::operator()(const LLSD&),
-// non-empty name to call operator()(const std::string&, const LLSD&).
-// Returns (empty string, return value) on successful call.
-// Returns (error message, undefined) if error and 'exception' is false.
-// Throws DispatchError if error and 'exception' is true.
-std::pair<std::string, LLSD> LLDispatchListener::call(const std::string& name, const LLSD& event,
- bool exception) const
+void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const
{
- try
+ // LLSD map containing returned values
+ LLSD result;
+ // collect any error messages here
+ std::ostringstream errors;
+ const char* delim = "";
+
+ for (const auto& pair : llsd::inMap(reqmap))
+ {
+ const LLSD::String& name{ pair.first };
+ const LLSD& args{ pair.second };
+ try
+ {
+ // With this form, capture return value even if undefined:
+ // presence of the key in the response map can be used to detect
+ // which request keys succeeded.
+ result[name] = (*this)(name, args);
+ }
+ catch (const DispatchError& err)
+ {
+ // collect message in 'errors', with hint as to which request map
+ // entry failed
+ errors << delim << err.what() << " (" << getDispatchKey()
+ << '[' << name << "])";
+ delim = "\n";
+ }
+ }
+
+ // so, were there any errors?
+ std::string error = errors.str();
+ if (! error.empty())
{
- if (name.empty())
+ if (! event.has(mReplyKey))
{
- // unless this throws, return (empty string, real return value)
- return { {}, (*this)(event) };
+ // can't send reply, throw
+ sCallFail<DispatchError>(error);
}
else
{
- // unless this throws, return (empty string, real return value)
- return { {}, (*this)(name, event) };
+ // reply key present
+ result["error"] = error;
}
}
- catch (const DispatchError& err)
+
+ reply(result, event);
+}
+
+void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const
+{
+ // LLSD array containing returned values
+ LLSD results;
+ // arguments array, if present -- const because, if it's shorter than
+ // reqarray, we don't want to grow it
+ const LLSD argsarray{ event[getArgsKey()] };
+ // error message, if any
+ std::string error;
+
+ // classic index loop because we need the index
+ for (size_t i = 0, size = reqarray.size(); i < size; ++i)
+ {
+ const auto& reqentry{ reqarray[i] };
+ std::string name;
+ LLSD args;
+ if (reqentry.isString())
+ {
+ name = reqentry.asString();
+ args = argsarray[i];
+ }
+ else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString())
+ {
+ name = reqentry[0].asString();
+ args = reqentry[1];
+ }
+ else
+ {
+ // reqentry isn't in either of the documented forms
+ error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ",
+ reqentry, " unsupported");
+ break;
+ }
+
+ // reqentry is one of the valid forms, got name and args
+ try
+ {
+ // With this form, capture return value even if undefined
+ results.append((*this)(name, args));
+ }
+ catch (const DispatchError& err)
+ {
+ // append hint as to which requentry produced the error
+ error = stringize(err.what(), " (", getDispatchKey(), '[', i, ']');
+ break;
+ }
+ }
+
+ LLSD result;
+ // was there an error?
+ if (! error.empty())
{
- if (exception)
+ if (! event.has(mReplyKey))
{
- // Caller asked for an exception on error. Oblige.
- throw;
+ // can't send reply, throw
+ sCallFail<DispatchError>(error);
+ }
+ else
+ {
+ // reply key present
+ result["error"] = error;
}
- // Caller does NOT want an exception: return (error message, undefined)
- return { err.what(), LLSD() };
}
+
+ // wrap the results array as response map "data" key, as promised
+ if (results.isDefined())
+ {
+ result["data"] = results;
+ }
+
+ reply(result, event);
}
void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const
diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h
index a348a6660f..5ab860b6dd 100644
--- a/indra/llcommon/lleventdispatcher.h
+++ b/indra/llcommon/lleventdispatcher.h
@@ -341,6 +341,8 @@ public:
/// Retrieve the LLSD key we use for one-arg <tt>operator()</tt> method
std::string getDispatchKey() const { return mKey; }
+ /// Retrieve the LLSD key we use for non-map arguments
+ std::string getArgsKey() const { return mArgskey; }
/// description of this instance's leaf class and description
friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&);
@@ -363,6 +365,8 @@ private:
void addFail(const std::string& name, const std::string& classname) const;
LLSD try_call(const std::string& key, const std::string& name,
const LLSD& event) const;
+
+protected:
// raise specified EXCEPTION with specified stringize(ARGS)
template <typename EXCEPTION, typename... ARGS>
void callFail(ARGS&&... args) const;
@@ -370,6 +374,7 @@ private:
static
void sCallFail(ARGS&&... args);
+private:
std::string mDesc, mKey, mArgskey;
DispatchMap mDispatch;
@@ -521,12 +526,12 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter)
/**
* Bundle an LLEventPump and a listener with an LLEventDispatcher. A class
* that contains (or derives from) LLDispatchListener need only specify the
- * LLEventPump name and dispatch key, and add() its methods. Incoming events
- * will automatically be dispatched.
+ * LLEventPump name and dispatch key, and add() its methods. Each incoming
+ * event ("request") will automatically be dispatched.
*
- * If the incoming event contains a "reply" key specifying the LLSD::String
- * name of an LLEventPump to which to respond, LLDispatchListener will attempt
- * to send a response to that LLEventPump.
+ * If the request contains a "reply" key specifying the LLSD::String name of
+ * an LLEventPump to which to respond, LLDispatchListener will attempt to send
+ * a response to that LLEventPump.
*
* If some error occurs (e.g. nonexistent callable name, wrong params) and
* "reply" is present, LLDispatchListener will send a response map to the
@@ -542,14 +547,70 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter)
*
* If the target callable returns a type convertible to LLSD (and, if it
* directly returns LLSD, the return value isDefined()), and if a "reply" key
- * is present in the incoming event, LLDispatchListener will post the returned
- * value to the "reply" LLEventPump. If the returned value is an LLSD map, it
- * will merge the echoed "reqid" key into the map and send that. Otherwise, it
- * will send an LLSD map containing "reqid" and a "data" key whose value is
- * the value returned by the target callable.
+ * is present in the request, LLDispatchListener will post the returned value
+ * to the "reply" LLEventPump. If the returned value is an LLSD map, it will
+ * merge the echoed "reqid" key into the map and send that. Otherwise, it will
+ * send an LLSD map containing "reqid" and a "data" key whose value is the
+ * value returned by the target callable.
*
* (It is inadvisable for a target callable to return an LLSD map containing
- * keys "reqid" or "error", as that will confuse the invoker.)
+ * keys "data", "reqid" or "error", as that will confuse the invoker.)
+ *
+ * Normally the request will specify the value of the dispatch key as an
+ * LLSD::String naming the target callable. Alternatively, several such calls
+ * may be "batched" as described below.
+ *
+ * If the value of the dispatch key is itself an LLSD map (a "request map"),
+ * each map key must name a target callable, and the value of that key must
+ * contain the parameters to pass to that callable. If a "reply" key is
+ * present in the request, the response map will contain a key for each of the
+ * keys in the request map. The value of every such key is the value returned
+ * by the target callable.
+ *
+ * (Avoid naming any target callable in the LLDispatchListener "data", "reqid"
+ * or "error" to avoid confusion.)
+ *
+ * Since LLDispatchListener calls the target callables specified by a request
+ * map in arbitrary order, this form assumes that the batched operations are
+ * independent of each other. LLDispatchListener will attempt every call, even
+ * if some attempts produce errors. If any keys in the request map produce
+ * errors, LLDispatchListener builds a composite error message string
+ * collecting the relevant messages. The corresponding keys will be missing
+ * from the response map. As in the single-callable case, absent a "reply" key
+ * in the request, this error message will be thrown as a DispatchError. With
+ * a "reply" key, it will be returned as the value of the "error" key. This
+ * form can indicate partial success: some request keys might have
+ * return-value keys in the response, others might have message text in the
+ * "error" key.
+ *
+ * If a specific call sequence is required, the value of the dispatch key may
+ * instead be an LLSD array (a "request array"). Each entry in the request
+ * array ("request entry") names a target callable, to be called in
+ * array-index sequence. Arguments for that callable may be specified in
+ * either of two ways.
+ *
+ * The request entry may itself be a two-element array, whose [0] is an
+ * LLSD::String naming the target callable and whose [1] contains the
+ * arguments to pass to that callable.
+ *
+ * Alternatively, the request entry may be an LLSD::String naming the target
+ * callable, in which case the request must contain an arguments key (optional
+ * third constructor argument) whose value is an array matching the request
+ * array. The arguments for the request entry's target callable are found at
+ * the same index in the arguments key array.
+ *
+ * If a "reply" key is present in the request, the response map will contain a
+ * "data" key whose value is an array. Each entry in that response array will
+ * contain the result from the corresponding request entry.
+ *
+ * This form assumes that any of the batched operations might depend on the
+ * success of a previous operation in the same batch. The @emph first error
+ * encountered will terminate the sequence. The error message might either be
+ * thrown as DispatchError or, given a "reply" key, returned as the "error"
+ * key in the response map. This form can indicate partial success: the first
+ * few request entries might have return-value entries in the "data" response
+ * array, along with an "error" key whose value is the error message that
+ * stopped the sequence.
*/
// Instead of containing an LLEventStream, LLDispatchListener derives from it.
// This allows an LLEventPumps::PumpFactory to return a pointer to an
@@ -568,13 +629,9 @@ public:
private:
bool process(const LLSD& event) const;
- // Pass empty name to call LLEventDispatcher::operator()(const LLSD&),
- // non-empty name to call operator()(const std::string&, const LLSD&).
- // Returns (empty string, return value) on successful call.
- // Returns (error message, undefined) if error and 'exception' is false.
- // Throws DispatchError if error and 'exception' is true.
- std::pair<std::string, LLSD> call(const std::string& name, const LLSD& event,
- bool exception) const;
+ void call_one(const LLSD& name, const LLSD& event) const;
+ void call_map(const LLSD& reqmap, const LLSD& event) const;
+ void call_array(const LLSD& reqarray, const LLSD& event) const;
void reply(const LLSD& reply, const LLSD& request) const;
LLTempBoundListener mBoundListener;