Make LLProcess post termination event to specified pump if desired.

This way a caller need not spin on isRunning(); we can just listen for the
requested termination event.
Post a similar event containing error message if for any reason
LLProcess::create() failed to launch the child.
Add unit tests for both cases.
meow-7.2.2
Nat Goodspeed 2012-02-20 12:40:38 -05:00
parent e98438bda7
commit 8b5d5f9652
3 changed files with 150 additions and 43 deletions

View File

@ -125,7 +125,7 @@ const LLProcess::BasePipe::size_type
class WritePipeImpl: public LLProcess::WritePipe
{
LOG_CLASS(WritePipeImpl);
LOG_CLASS(WritePipeImpl);
public:
WritePipeImpl(const std::string& desc, apr_file_t* pipe):
mDesc(desc),
@ -202,7 +202,7 @@ private:
class ReadPipeImpl: public LLProcess::ReadPipe
{
LOG_CLASS(ReadPipeImpl);
LOG_CLASS(ReadPipeImpl);
public:
ReadPipeImpl(const std::string& desc, apr_file_t* pipe):
mDesc(desc),
@ -394,6 +394,23 @@ LLProcessPtr LLProcess::create(const LLSDOrParams& params)
catch (const LLProcessError& e)
{
LL_WARNS("LLProcess") << e.what() << LL_ENDL;
// If caller is requesting an event on process termination, send one
// indicating bad launch. This may prevent someone waiting forever for
// a termination post that can't arrive because the child never
// started.
if (! std::string(params.postend).empty())
{
LLEventPumps::instance().obtain(params.postend)
.post(LLSDMap
// no "id"
("desc", std::string(params.executable))
("state", LLProcess::UNSTARTED)
// no "data"
("string", e.what())
);
}
return LLProcessPtr();
}
}
@ -425,6 +442,8 @@ LLProcess::LLProcess(const LLSDOrParams& params):
<< LLSDNotationStreamer(params)));
}
mPostend = params.postend;
apr_procattr_t *procattr = NULL;
chkapr(apr_procattr_create(&procattr, gAPRPoolp));
@ -744,6 +763,19 @@ void LLProcess::handle_status(int reason, int status)
// hand.
mStatus = interpret_status(status);
LL_INFOS("LLProcess") << getStatusString() << LL_ENDL;
// If caller requested notification on child termination, send it.
if (! mPostend.empty())
{
LLEventPumps::instance().obtain(mPostend)
.post(LLSDMap
("id", getProcessID())
("desc", mDesc)
("state", mStatus.mState)
("data", mStatus.mData)
("string", getStatusString())
);
}
}
LLProcess::id LLProcess::getProcessID() const
@ -769,72 +801,72 @@ std::string LLProcess::getPipeName(FILESLOT)
template<class PIPETYPE>
PIPETYPE* LLProcess::getPipePtr(std::string& error, FILESLOT slot)
{
if (slot >= NSLOTS)
{
error = STRINGIZE(mDesc << " has no slot " << slot);
return NULL;
}
if (mPipes.is_null(slot))
{
error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a monitored pipe");
return NULL;
}
// Make sure we dynamic_cast in pointer domain so we can test, rather than
// accepting runtime's exception.
PIPETYPE* ppipe = dynamic_cast<PIPETYPE*>(&mPipes[slot]);
if (! ppipe)
{
error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a " << typeid(PIPETYPE).name());
return NULL;
}
if (slot >= NSLOTS)
{
error = STRINGIZE(mDesc << " has no slot " << slot);
return NULL;
}
if (mPipes.is_null(slot))
{
error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a monitored pipe");
return NULL;
}
// Make sure we dynamic_cast in pointer domain so we can test, rather than
// accepting runtime's exception.
PIPETYPE* ppipe = dynamic_cast<PIPETYPE*>(&mPipes[slot]);
if (! ppipe)
{
error = STRINGIZE(mDesc << ' ' << whichfile[slot] << " not a " << typeid(PIPETYPE).name());
return NULL;
}
error.clear();
return ppipe;
error.clear();
return ppipe;
}
template <class PIPETYPE>
PIPETYPE& LLProcess::getPipe(FILESLOT slot)
{
std::string error;
PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
if (! wp)
{
throw NoPipe(error);
}
return *wp;
std::string error;
PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
if (! wp)
{
throw NoPipe(error);
}
return *wp;
}
template <class PIPETYPE>
boost::optional<PIPETYPE&> LLProcess::getOptPipe(FILESLOT slot)
{
std::string error;
PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
if (! wp)
{
LL_DEBUGS("LLProcess") << error << LL_ENDL;
return boost::optional<PIPETYPE&>();
}
return *wp;
std::string error;
PIPETYPE* wp = getPipePtr<PIPETYPE>(error, slot);
if (! wp)
{
LL_DEBUGS("LLProcess") << error << LL_ENDL;
return boost::optional<PIPETYPE&>();
}
return *wp;
}
LLProcess::WritePipe& LLProcess::getWritePipe(FILESLOT slot)
{
return getPipe<WritePipe>(slot);
return getPipe<WritePipe>(slot);
}
boost::optional<LLProcess::WritePipe&> LLProcess::getOptWritePipe(FILESLOT slot)
{
return getOptPipe<WritePipe>(slot);
return getOptPipe<WritePipe>(slot);
}
LLProcess::ReadPipe& LLProcess::getReadPipe(FILESLOT slot)
{
return getPipe<ReadPipe>(slot);
return getPipe<ReadPipe>(slot);
}
boost::optional<LLProcess::ReadPipe&> LLProcess::getOptReadPipe(FILESLOT slot)
{
return getOptPipe<ReadPipe>(slot);
return getOptPipe<ReadPipe>(slot);
}
std::ostream& operator<<(std::ostream& out, const LLProcess::Params& params)
@ -932,7 +964,7 @@ static std::string WindowsErrorString(const std::string& operation)
NULL)
!= 0)
{
// convert from wide-char string to multi-byte string
// convert from wide-char string to multi-byte string
char message[256];
wcstombs(message, error_str, sizeof(message));
message[sizeof(message)-1] = 0;

View File

@ -158,7 +158,8 @@ public:
args("args"),
cwd("cwd"),
autokill("autokill", true),
files("files")
files("files"),
postend("postend")
{}
/// pathname of executable
@ -184,6 +185,20 @@ public:
* underlying implementation library doesn't support that.
*/
Multiple<FileParam> files;
/**
* On child-process termination, if this LLProcess object still
* exists, post LLSD event to LLEventPump with specified name (default
* no event). Event contains at least:
*
* - "id" as obtained from getProcessID()
* - "desc" short string description of child (executable + pid)
* - "state" @c state enum value, from Status.mState
* - "data" if "state" is EXITED, exit code; if KILLED, on Posix,
* signal number
* - "string" English text describing "state" and "data" (e.g. "exited
* with code 0")
*/
Optional<std::string> postend;
};
typedef LLSDParamAdapter<Params> LLSDOrParams;
@ -462,6 +477,7 @@ private:
PIPETYPE* getPipePtr(std::string& error, FILESLOT slot);
std::string mDesc;
std::string mPostend;
apr_proc_t mProcess;
bool mAutokill;
Status mStatus;

View File

@ -1206,6 +1206,65 @@ namespace tut
ensure("find(\"ghi\", 27)", childout.find("ghi", 27) == LLProcess::ReadPipe::npos);
}
template<> template<>
void object::test<20>()
{
set_test_name("good postend");
PythonProcessLauncher py("postend",
"import sys\n"
"sys.exit(35)\n");
std::string pumpname("postend");
EventListener listener(LLEventPumps::instance().obtain(pumpname));
py.mParams.postend = pumpname;
py.launch();
LLProcess::id childid(py.mPy->getProcessID());
// Don't use waitfor(), which calls isRunning(); instead wait for an
// event on pumpname.
int i, timeout = 60;
for (i = 0; i < timeout && listener.mHistory.empty(); ++i)
{
yield();
}
ensure("no postend event", i < timeout);
ensure_equals("number of postend events", listener.mHistory.size(), 1);
LLSD postend(listener.mHistory.front());
ensure_equals("id", postend["id"].asInteger(), childid);
ensure("desc empty", ! postend["desc"].asString().empty());
ensure_equals("state", postend["state"].asInteger(), LLProcess::EXITED);
ensure_equals("data", postend["data"].asInteger(), 35);
std::string str(postend["string"]);
ensure_contains("string", str, "exited");
ensure_contains("string", str, "35");
}
template<> template<>
void object::test<21>()
{
set_test_name("bad postend");
std::string pumpname("postend");
EventListener listener(LLEventPumps::instance().obtain(pumpname));
LLProcess::Params params;
params.postend = pumpname;
LLProcessPtr child = LLProcess::create(params);
ensure("shouldn't have launched", ! child);
ensure_equals("number of postend events", listener.mHistory.size(), 1);
LLSD postend(listener.mHistory.front());
ensure("has id", ! postend.has("id"));
// Ha ha, in this case the implementation normally sets "desc" to
// params.executable. But as the nature of the problem is that
// params.executable is empty, expecting "desc" to be nonempty is a
// bit unreasonable!
//ensure("desc empty", ! postend["desc"].asString().empty());
ensure_equals("state", postend["state"].asInteger(), LLProcess::UNSTARTED);
ensure("has data", ! postend.has("data"));
std::string error(postend["string"]);
// All we get from canned parameter validation is a bool, so the
// "validation failed" message we ourselves generate can't mention
// "executable" by name. Just check that it's nonempty.
//ensure_contains("error", error, "executable");
ensure("string", ! error.empty());
}
// TODO:
// test EOF -- check logging