DRTVWR-558: Nail down LLDispatchListener exception handling

for exceptions other than those thrown by base-class LLEventDispatcher.

Explain in LLDispatchListener Doxygen comments that for a request lacking a
"reply" key, any exception is allowed to propagate because it's likely to
reach the post() call that triggered the exception in the first place.

For batch LLDispatchListener operations, catch not only LLEventDispatcher::
DispatchError exceptions but any std::exception, so we can collect them to
report to the invoker. "Gotta catch 'em all!"

Make LLLeap catch any std::exception thrown by processing a request from the
plugin child process, log it and send a reply to the plugin. No plugin should
be allowed to crash the viewer.

(cherry picked from commit 94e10fd039b79f71ed8d7e10807b6e4eebd1928c)
master
Nat Goodspeed 2023-01-23 10:51:33 -05:00
parent c747ff0925
commit 2eb0ea9593
3 changed files with 36 additions and 11 deletions

View File

@ -848,10 +848,12 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const
// which request keys succeeded.
result[name] = (*this)(name, args);
}
catch (const DispatchError& err)
catch (const std::exception& err)
{
// collect message in 'errors'
errors << delim << err.what();
// Catch not only DispatchError, but any C++ exception thrown by
// the target callable. Collect exception name and message in
// 'errors'.
errors << delim << LLError::Log::classname(err) << ": " << err.what();
delim = "\n";
}
}
@ -921,9 +923,12 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con
// With this form, capture return value even if undefined
results.append((*this)(name, args));
}
catch (const DispatchError& err)
catch (const std::exception& err)
{
error = err.what();
// Catch not only DispatchError, but any C++ exception thrown by
// the target callable. Report the exception class as well as the
// error string.
error = stringize(LLError::Log::classname(err), ": ", err.what());
break;
}
}

View File

@ -747,7 +747,10 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter)
* "reply" is present, LLDispatchListener will send a response map to the
* specified LLEventPump containing an "error" key whose value is the relevant
* error message. If "reply" is not present, the DispatchError exception will
* propagate.
* propagate. Since LLDispatchListener bundles an LLEventStream, which
* attempts the call immediately on receiving the post() call, there's a
* reasonable chance that the exception will highlight the post() call that
* triggered the error.
*
* If LLDispatchListener successfully calls the target callable, but no
* "reply" key is present, any value returned by that callable is discarded.

View File

@ -327,11 +327,28 @@ public:
}
else
{
// The LLSD object we got from our stream contains the keys we
// need.
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
// Block calls to this method; resetting mBlocker unblocks calls
// to the other method.
try
{
// The LLSD object we got from our stream contains the
// keys we need.
LLEventPumps::instance().obtain(data["pump"]).post(data["data"]);
}
catch (const std::exception& err)
{
// No plugin should be allowed to crash the viewer by
// driving an exception -- intentionally or not.
LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data));
// Whether or not the plugin added a "reply" key to the
// request, send a reply. We happen to know who originated
// this request, and the reply LLEventPump of interest.
// Not our problem if the plugin ignores the reply event.
data["reply"] = mReplyPump.getName();
sendReply(llsd::map("error",
stringize(LLError::Log::classname(err), ": ", err.what())),
data);
}
// Block calls to this method; resetting mBlocker unblocks
// calls to the other method.
mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection));
// Go check for any more pending events in the buffer.
if (childout.size())