From 0ef99cd33b4b450712e105e8ab448c2dab7081d2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 17:46:44 -0500 Subject: Add LLLeap class, initial implementation, initial unit tests. Instantiating LLLeap with a command to execute a particular child process sets up machinery to speak LLSD Event API Plugin protocol with that child process. LLLeap is an LLInstanceTracker subclass, so the code that instantiates need not hold the pointer. LLLeap monitors child-process termination and deletes itself when done. --- indra/llcommon/llleap.cpp | 379 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) create mode 100644 indra/llcommon/llleap.cpp (limited to 'indra/llcommon/llleap.cpp') diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp new file mode 100644 index 0000000000..dddf1286ac --- /dev/null +++ b/indra/llcommon/llleap.cpp @@ -0,0 +1,379 @@ +/** + * @file llleap.cpp + * @author Nat Goodspeed + * @date 2012-02-20 + * @brief Implementation for llleap. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llleap.h" +// STL headers +#include +#include +// std headers +// external library headers +#include +#include +#include +// other Linden headers +#include "llerror.h" +#include "llstring.h" +#include "llprocess.h" +#include "llevents.h" +#include "stringize.h" +#include "llsdutil.h" +#include "llsdserialize.h" + +LLLeap::LLLeap() {} +LLLeap::~LLLeap() {} + +class LLLeapImpl: public LLLeap +{ +public: + // Called only by LLLeap::create() + LLLeapImpl(const std::string& desc, const std::vector& plugin): + // We might reassign mDesc in the constructor body if it's empty here. + mDesc(desc), + // We expect multiple LLLeapImpl instances. Definitely tweak + // mDonePump's name for uniqueness. + mDonePump("LLLeap", true), + // Troubling thought: what if one plugin intentionally messes with + // another plugin? LLEventPump names are in a single global namespace. + // Try to make that more difficult by generating a UUID for the reply- + // pump name -- so it should NOT need tweaking for uniqueness. + mReplyPump(LLUUID::generateNewID().asString()), + mExpect(0) + { + // Rule out empty vector + if (plugin.empty()) + { + throw Error("no plugin command"); + } + + // Don't leave desc empty either, but in this case, if we weren't + // given one, we'll fake one. + if (desc.empty()) + { + mDesc = LLProcess::basename(plugin[0]); + // how about a toLower() variant that returns the transformed string?! + std::string desclower(mDesc); + LLStringUtil::toLower(desclower); + // If we're running a Python script, use the script name for the + // desc instead of just 'python'. Arguably we should check for + // more different interpreters as well, but there's a reason to + // notice Python specially: we provide Python LLSD serialization + // support, so there's a pretty good reason to implement plugins + // in that language. + if (plugin.size() >= 2 && (desclower == "python" || desclower == "python.exe")) + { + mDesc = LLProcess::basename(plugin[1]); + } + } + + // Listen for child "termination" right away to catch launch errors. + mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1)); + + // Okay, launch child. + LLProcess::Params params; + params.desc = mDesc; + std::vector::const_iterator pi(plugin.begin()), pend(plugin.end()); + params.executable = *pi++; + for ( ; pi != pend; ++pi) + { + params.args.add(*pi); + } + params.files.add(LLProcess::FileParam("pipe")); // stdin + params.files.add(LLProcess::FileParam("pipe")); // stdout + params.files.add(LLProcess::FileParam("pipe")); // stderr + params.postend = mDonePump.getName(); + mChild = LLProcess::create(params); + // If that didn't work, no point in keeping this LLLeap object. + if (! mChild) + { + throw Error(STRINGIZE("failed to run " << mDesc)); + } + + // Okay, launch apparently worked. Change our mDonePump listener. + mDonePump.stopListening("LLLeap"); + mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::done, this, _1)); + + // Child might pump large volumes of data through either stdout or + // stderr. Don't bother copying all that data into notification event. + LLProcess::ReadPipe + &childout(mChild->getReadPipe(LLProcess::STDOUT)), + &childerr(mChild->getReadPipe(LLProcess::STDERR)); + childout.setLimit(20); + childerr.setLimit(20); + + // Serialize any event received on mReplyPump to our child's stdin, + // suitably enriched with the pump name on which it was received. + mStdinConnection = mReplyPump + .listen("LLLeap", + boost::bind(&LLLeapImpl::wstdin, this, mReplyPump.getName(), _1)); + + // Listening on stdout is stateful. In general, we're either waiting + // for the length prefix or waiting for the specified length of data. + // We address that with two different listener methods -- one of which + // is blocked at any given time. + mStdoutConnection = childout.getPump() + .listen("prefix", boost::bind(&LLLeapImpl::rstdout, this, _1)); + mStdoutDataConnection = childout.getPump() + .listen("data", boost::bind(&LLLeapImpl::rstdoutData, this, _1)); + mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection)); + + // Log anything sent up through stderr. When a typical program + // encounters an error, it writes its error message to stderr and + // terminates with nonzero exit code. In particular, the Python + // interpreter behaves that way. More generally, though, a plugin + // author can log whatever s/he wants to the viewer log using stderr. + mStderrConnection = childerr.getPump() + .listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1)); + + // Send child a preliminary event reporting our own reply-pump name -- + // which would otherwise be pretty tricky to guess! +// TODO TODO inject name of command pump here. + wstdin(mReplyPump.getName(), + LLSDMap + ("command", LLSD()) + // Include LLLeap features -- this may be important for child to + // construct (or recognize) current protocol. + ("features", LLSD::emptyMap())); + } + + // Normally we'd expect to arrive here only via done() + virtual ~LLLeapImpl() + { + LL_DEBUGS("LLLeap") << "destroying LLLeap(\"" << mDesc << "\")" << LL_ENDL; + } + + // Listener for failed launch attempt + bool bad_launch(const LLSD& data) + { + LL_WARNS("LLLeap") << data["string"].asString() << LL_ENDL; + return false; + } + + // Listener for child-process termination + bool done(const LLSD& data) + { + // Log the termination + LL_INFOS("LLLeap") << data["string"].asString() << LL_ENDL; + + // Any leftover data at this moment are because protocol was not + // satisfied. Possibly the child was interrupted in the middle of + // sending a message, possibly the child didn't flush stdout before + // terminating, possibly it's just garbage. Log its existence but + // discard it. + LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT)); + if (childout.size()) + { + LLProcess::ReadPipe::size_type + peeklen((std::min)(LLProcess::ReadPipe::size_type(50), childout.size())); + LL_WARNS("LLLeap") << "Discarding final " << childout.size() << " bytes: " + << childout.peek(0, peeklen) << "..." << LL_ENDL; + } + + // Kill this instance. MUST BE LAST before return! + delete this; + return false; + } + + // Listener for events on mReplyPump: send to child stdin + bool wstdin(const std::string& pump, const LLSD& data) + { + LLSD packet(LLSDMap("pump", pump)("data", data)); + + std::ostringstream buffer; + buffer << LLSDNotationStreamer(packet); + + LL_DEBUGS("EventHost") << "Sending: " << buffer.tellp() << ':'; + std::string::size_type truncate(80); + if (buffer.tellp() <= truncate) + { + LL_CONT << buffer.str(); + } + else + { + LL_CONT << buffer.str().substr(0, truncate) << "..."; + } + LL_CONT << LL_ENDL; + + LLProcess::WritePipe& childin(mChild->getWritePipe(LLProcess::STDIN)); + childin.get_ostream() << buffer.tellp() << ':' << buffer.str() << std::flush; + return false; + } + + // Initial state of stateful listening on child stdout: wait for a length + // prefix, followed by ':'. + bool rstdout(const LLSD& data) + { + LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT)); + // It's possible we got notified of a couple digit characters without + // seeing the ':' -- unlikely, but still. Until we see ':', keep + // waiting. + if (childout.contains(':')) + { + std::istream& childstream(childout.get_istream()); + // Saw ':', read length prefix and store in mExpect. + size_t expect; + childstream >> expect; + int colon(childstream.get()); + if (colon != ':') + { + // Protocol failure. Clear out the rest of the pending data in + // childout (well, up to a max length) to log what was wrong. + LLProcess::ReadPipe::size_type + readlen((std::min)(childout.size(), LLProcess::ReadPipe::size_type(80))); + std::vector buffer(readlen + 1); + childstream.read(&buffer[0], readlen); + buffer[childstream.gcount()] = '\0'; + bad_protocol(STRINGIZE(expect << char(colon) << &buffer[0])); + } + else + { + // Saw length prefix, saw colon, life is good. Now wait for + // that length of data to arrive. + mExpect = expect; + // Block calls to this method; resetting mBlocker unblocks + // calls to the other method. + mBlocker.reset(new LLEventPump::Blocker(mStdoutConnection)); + // Go check if we've already received all the advertised data. + if (childout.size()) + { + LLSD updata(data); + updata["len"] = LLSD::Integer(childout.size()); + rstdoutData(updata); + } + } + } + else if (childout.contains('\n')) + { + // Since this is the initial listening state, this is where we'd + // arrive if the child isn't following protocol at all -- say + // because the user specified 'ls' or some darn thing. + bad_protocol(childout.getline()); + } + return false; + } + + // State in which we listen on stdout for the specified length of data to + // arrive. + bool rstdoutData(const LLSD& data) + { + LLProcess::ReadPipe& childout(mChild->getReadPipe(LLProcess::STDOUT)); + // Until we've accumulated the promised length of data, keep waiting. + if (childout.size() >= mExpect) + { + // Ready to rock and roll. + LLSD data; + LLPointer parser(new LLSDNotationParser()); + S32 parse_status(parser->parse(childout.get_istream(), data, mExpect)); + if (parse_status == LLSDParser::PARSE_FAILURE) + { + bad_protocol("unparseable LLSD data"); + } + else if (! (data.isMap() && data["pump"].isString() && data.has("data"))) + { + // we got an LLSD object, but it lacks required keys + bad_protocol("missing 'pump' or 'data'"); + } + 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. + mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection)); + // Go check for any more pending events in the buffer. + if (childout.size()) + { + LLSD updata(data); + data["len"] = LLSD::Integer(childout.size()); + rstdout(updata); + } + } + } + return false; + } + + void bad_protocol(const std::string& data) + { + LL_WARNS("LLLeap") << mDesc << ": invalid protocol: " << data << LL_ENDL; + // No point in continuing to run this child. + mChild->kill(); + } + + // Listen on child stderr and log everything that arrives + bool rstderr(const LLSD& data) + { + LLProcess::ReadPipe& childerr(mChild->getReadPipe(LLProcess::STDERR)); + // We might have gotten a notification involving only a partial line + // -- or multiple lines. Read all complete lines; stop when there's + // only a partial line left. + while (childerr.contains('\n')) + { + // DO NOT make calls with side effects in a logging statement! If + // that log level is suppressed, your side effects WON'T HAPPEN. + std::string line(childerr.getline()); + // Log the received line. Prefix it with the desc so we know which + // plugin it's from. This method name rstderr() is intentionally + // chosen to further qualify the log output. + LL_INFOS("LLLeap") << mDesc << ": " << line << LL_ENDL; + } + // What if child writes a final partial line to stderr? + if (data["eof"].asBoolean() && childerr.size()) + { + std::string rest(childerr.read(childerr.size())); + // Read all remaining bytes and log. + LL_INFOS("LLLeap") << mDesc << ": " << rest << LL_ENDL; + } + return false; + } + +private: + std::string mDesc; + LLEventStream mDonePump; + LLEventStream mReplyPump; + LLProcessPtr mChild; + LLTempBoundListener + mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection; + boost::scoped_ptr mBlocker; + LLProcess::ReadPipe::size_type mExpect; +}; + +// This must follow the declaration of LLLeapImpl, so it may as well be last. +LLLeap* LLLeap::create(const std::string& desc, const std::vector& plugin, bool exc) +{ + // If caller is willing to permit exceptions, just instantiate. + if (exc) + return new LLLeapImpl(desc, plugin); + + // Caller insists on suppressing LLLeap::Error. Very well, catch it. + try + { + return new LLLeapImpl(desc, plugin); + } + catch (const LLLeap::Error&) + { + return NULL; + } +} + +LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc) +{ + // Use LLStringUtil::getTokens() to parse the command line + return create(desc, + LLStringUtil::getTokens(plugin, + " \t\r\n", // drop_delims + "", // no keep_delims + "\"'", // either kind of quotes + "\\"), // backslash escape + exc); +} -- cgit v1.2.3 From e8f463ef7a9cddda3813d20935957708d3b4aa3b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Mar 2012 14:54:24 -0500 Subject: Use LLProcess::ReadPipe::read() in LLLeap. The code was using LLProcess::ReadPipe::get_istream().read(), but that's much uglier, as it requires constructing a char* buffer etc. etc. --- indra/llcommon/llleap.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'indra/llcommon/llleap.cpp') diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index dddf1286ac..beb7fa8333 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -230,10 +230,7 @@ public: // childout (well, up to a max length) to log what was wrong. LLProcess::ReadPipe::size_type readlen((std::min)(childout.size(), LLProcess::ReadPipe::size_type(80))); - std::vector buffer(readlen + 1); - childstream.read(&buffer[0], readlen); - buffer[childstream.gcount()] = '\0'; - bad_protocol(STRINGIZE(expect << char(colon) << &buffer[0])); + bad_protocol(STRINGIZE(expect << char(colon) << childout.read(readlen))); } else { -- cgit v1.2.3 From e7d129875a4ccdc09f921e08fea1dccbb025b705 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 3 Mar 2012 06:28:47 -0500 Subject: Add a couple LLLeap DEBUG messages for incoming-events control flow. --- indra/llcommon/llleap.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra/llcommon/llleap.cpp') diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index beb7fa8333..34f77c3f3a 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -237,6 +237,8 @@ public: // Saw length prefix, saw colon, life is good. Now wait for // that length of data to arrive. mExpect = expect; + LL_DEBUGS("LLLeap") << "got length, waiting for " + << mExpect << " bytes of data" << LL_ENDL; // Block calls to this method; resetting mBlocker unblocks // calls to the other method. mBlocker.reset(new LLEventPump::Blocker(mStdoutConnection)); @@ -268,6 +270,8 @@ public: if (childout.size() >= mExpect) { // Ready to rock and roll. + LL_DEBUGS("LLLeap") << "needed " << mExpect << " bytes, got " + << childout.size() << ", parsing LLSD" << LL_ENDL; LLSD data; LLPointer parser(new LLSDNotationParser()); S32 parse_status(parser->parse(childout.get_istream(), data, mExpect)); -- cgit v1.2.3 From 63b393da31e9fb972428fe957a05955a3d903113 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Mar 2012 18:49:27 -0500 Subject: Introduce (disabled) LLLeap debugging code to validate stdin writes. While debugging mysterious problem on Windows, one potential failure mode to rule out was the possibility that streaming std::ostringstream << LLSDNotationStreamer(large_LLSD) might itself cause trouble -- even before attempting to write to the LLProcess::WritePipe. The debugging code validated that the correct length is being reported, and that deserializing the resulting buffer produces equivalent LLSD. This code verified correct operation, and so has been disabled, as it's expensive at runtime. --- indra/llcommon/llleap.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'indra/llcommon/llleap.cpp') diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 34f77c3f3a..034c809330 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -192,6 +192,31 @@ public: std::ostringstream buffer; buffer << LLSDNotationStreamer(packet); +/*==========================================================================*| + // DEBUGGING ONLY: don't copy str() if we can avoid it. + std::string strdata(buffer.str()); + if (std::size_t(buffer.tellp()) != strdata.length()) + { + LL_ERRS("LLLeap") << "tellp() -> " << buffer.tellp() << " != " + << "str().length() -> " << strdata.length() << LL_ENDL; + } + // DEBUGGING ONLY: reading back is terribly inefficient. + std::istringstream readback(strdata); + LLSD echo; + LLPointer parser(new LLSDNotationParser()); + S32 parse_status(parser->parse(readback, echo, strdata.length())); + if (parse_status == LLSDParser::PARSE_FAILURE) + { + LL_ERRS("LLLeap") << "LLSDNotationParser() cannot parse output of " + << "LLSDNotationStreamer()" << LL_ENDL; + } + if (! llsd_equals(echo, packet)) + { + LL_ERRS("LLLeap") << "LLSDNotationParser() produced different LLSD " + << "than passed to LLSDNotationStreamer()" << LL_ENDL; + } +|*==========================================================================*/ + LL_DEBUGS("EventHost") << "Sending: " << buffer.tellp() << ':'; std::string::size_type truncate(80); if (buffer.tellp() <= truncate) -- cgit v1.2.3 From cf39274b640e983a5fcc2d03e4c47947a2b36732 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 15 Mar 2012 23:35:19 -0400 Subject: Make LLLeap intercept LL_ERRS termination and notify LEAP plugin. Have to pump "mainloop" a few times to flush the buffer to the pipe, a potentially risky strategy: we have to trust that whatever condition led to the LL_ERRS fatal error didn't break anything that listens on "mainloop". But the worst that could happen is that the plugin won't be notified -- just as if we didn't try in the first place. In other words, no harm in trying. --- indra/llcommon/llleap.cpp | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/llleap.cpp') diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 034c809330..07880bd818 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -29,12 +29,15 @@ #include "stringize.h" #include "llsdutil.h" #include "llsdserialize.h" +#include "llerrorcontrol.h" +#include "lltimer.h" LLLeap::LLLeap() {} LLLeap::~LLLeap() {} class LLLeapImpl: public LLLeap { + LOG_CLASS(LLLeap); public: // Called only by LLLeap::create() LLLeapImpl(const std::string& desc, const std::vector& plugin): @@ -48,7 +51,8 @@ public: // Try to make that more difficult by generating a UUID for the reply- // pump name -- so it should NOT need tweaking for uniqueness. mReplyPump(LLUUID::generateNewID().asString()), - mExpect(0) + mExpect(0), + mPrevFatalFunction(LLError::getFatalFunction()) { // Rule out empty vector if (plugin.empty()) @@ -135,6 +139,9 @@ public: mStderrConnection = childerr.getPump() .listen("LLLeap", boost::bind(&LLLeapImpl::rstderr, this, _1)); + // For our lifespan, intercept any LL_ERRS so we can notify plugin + LLError::setFatalFunction(boost::bind(&LLLeapImpl::fatalFunction, this, _1)); + // Send child a preliminary event reporting our own reply-pump name -- // which would otherwise be pretty tricky to guess! // TODO TODO inject name of command pump here. @@ -150,6 +157,8 @@ public: virtual ~LLLeapImpl() { LL_DEBUGS("LLLeap") << "destroying LLLeap(\"" << mDesc << "\")" << LL_ENDL; + // Restore original FatalFunction + LLError::setFatalFunction(mPrevFatalFunction); } // Listener for failed launch attempt @@ -363,6 +372,30 @@ public: return false; } + void fatalFunction(const std::string& error) + { + // Notify plugin + LLSD event; + event["type"] = "error"; + event["error"] = error; + mReplyPump.post(event); + + // All the above really accomplished was to buffer the serialized + // event in our WritePipe. Have to pump mainloop a couple times to + // really write it out there... but time out in case we can't write. + LLProcess::WritePipe& childin(mChild->getWritePipe(LLProcess::STDIN)); + LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); + LLSD nop; + F64 until(LLTimer::getElapsedSeconds() + 2); + while (childin.size() && LLTimer::getElapsedSeconds() < until) + { + mainloop.post(nop); + } + + // forward the call to the previous FatalFunction + mPrevFatalFunction(error); + } + private: std::string mDesc; LLEventStream mDonePump; @@ -372,6 +405,7 @@ private: mStdinConnection, mStdoutConnection, mStdoutDataConnection, mStderrConnection; boost::scoped_ptr mBlocker; LLProcess::ReadPipe::size_type mExpect; + LLError::FatalFunction mPrevFatalFunction; }; // This must follow the declaration of LLLeapImpl, so it may as well be last. -- cgit v1.2.3 From 0c8fac147d2baed8d8ef0e8c9bdcc47cb3082854 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 16 Mar 2012 15:34:21 -0400 Subject: Introduce LLLeapListener, associating one with each LLLeap object. Every LEAP plugin gets its own LLLeapListener, managing its own collection of listeners to various LLEventPumps. LLLeapListener's command LLEventPump now has a UUID for a name, both for uniqueness and to make it tough for a plugin to mess with any other. --- indra/llcommon/llleap.cpp | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) (limited to 'indra/llcommon/llleap.cpp') diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 07880bd818..0a57ef1c48 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -31,6 +31,12 @@ #include "llsdserialize.h" #include "llerrorcontrol.h" #include "lltimer.h" +#include "lluuid.h" +#include "llleaplistener.h" + +#if LL_MSVC +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif LLLeap::LLLeap() {} LLLeap::~LLLeap() {} @@ -52,7 +58,13 @@ public: // pump name -- so it should NOT need tweaking for uniqueness. mReplyPump(LLUUID::generateNewID().asString()), mExpect(0), - mPrevFatalFunction(LLError::getFatalFunction()) + mPrevFatalFunction(LLError::getFatalFunction()), + // Instantiate a distinct LLLeapListener for this plugin. (Every + // plugin will want its own collection of managed listeners, etc.) + // Pass it a callback to our connect() method, so it can send events + // from a particular LLEventPump to the plugin without having to know + // this class or method name. + mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2))) { // Rule out empty vector if (plugin.empty()) @@ -115,11 +127,8 @@ public: childout.setLimit(20); childerr.setLimit(20); - // Serialize any event received on mReplyPump to our child's stdin, - // suitably enriched with the pump name on which it was received. - mStdinConnection = mReplyPump - .listen("LLLeap", - boost::bind(&LLLeapImpl::wstdin, this, mReplyPump.getName(), _1)); + // Serialize any event received on mReplyPump to our child's stdin. + mStdinConnection = connect(mReplyPump, "LLLeap"); // Listening on stdout is stateful. In general, we're either waiting // for the length prefix or waiting for the specified length of data. @@ -144,13 +153,12 @@ public: // Send child a preliminary event reporting our own reply-pump name -- // which would otherwise be pretty tricky to guess! -// TODO TODO inject name of command pump here. wstdin(mReplyPump.getName(), LLSDMap - ("command", LLSD()) + ("command", mListener->getName()) // Include LLLeap features -- this may be important for child to // construct (or recognize) current protocol. - ("features", LLSD::emptyMap())); + ("features", LLLeapListener::getFeatures())); } // Normally we'd expect to arrive here only via done() @@ -397,6 +405,17 @@ public: } private: + /// We always want to listen on mReplyPump with wstdin(); under some + /// circumstances we'll also echo other LLEventPumps to the plugin. + LLBoundListener connect(LLEventPump& pump, const std::string& listener) + { + // Serialize any event received on the specified LLEventPump to our + // child's stdin, suitably enriched with the pump name on which it was + // received. + return pump.listen(listener, + boost::bind(&LLLeapImpl::wstdin, this, pump.getName(), _1)); + } + std::string mDesc; LLEventStream mDonePump; LLEventStream mReplyPump; @@ -406,6 +425,7 @@ private: boost::scoped_ptr mBlocker; LLProcess::ReadPipe::size_type mExpect; LLError::FatalFunction mPrevFatalFunction; + boost::scoped_ptr mListener; }; // This must follow the declaration of LLLeapImpl, so it may as well be last. -- cgit v1.2.3