diff options
Diffstat (limited to 'indra/llcommon')
-rw-r--r-- | indra/llcommon/llprocess.cpp | 12 | ||||
-rw-r--r-- | indra/llcommon/llprocess.h | 29 | ||||
-rw-r--r-- | indra/llcommon/tests/llprocess_test.cpp | 89 |
3 files changed, 110 insertions, 20 deletions
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 8c321d06b9..5753efdc59 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -517,6 +517,10 @@ LLProcessPtr LLProcess::create(const LLSDOrParams& params) LLProcess::LLProcess(const LLSDOrParams& params): mAutokill(params.autokill), + // Because 'autokill' originally meant both 'autokill' and 'attached', to + // preserve existing semantics, we promise that mAttached defaults to the + // same setting as mAutokill. + mAttached(params.attached.isProvided()? params.attached : params.autokill), mPipes(NSLOTS) { // Hmm, when you construct a ptr_vector with a size, it merely reserves @@ -625,9 +629,9 @@ LLProcess::LLProcess(const LLSDOrParams& params): // std handles and the like, and that's a bit more detachment than we // want. autokill=false just means not to implicitly kill the child when // the parent terminates! -// chkapr(apr_procattr_detach_set(procattr, params.autokill? 0 : 1)); +// chkapr(apr_procattr_detach_set(procattr, mAutokill? 0 : 1)); - if (params.autokill) + if (mAutokill) { #if ! defined(APR_HAS_PROCATTR_AUTOKILL_SET) // Our special preprocessor symbol isn't even defined -- wrong APR @@ -696,7 +700,7 @@ LLProcess::LLProcess(const LLSDOrParams& params): // take steps to terminate the child. This is all suspenders-and-belt: in // theory our destructor should kill an autokill child, but in practice // that doesn't always work (e.g. VWR-21538). - if (params.autokill) + if (mAutokill) { /*==========================================================================*| // NO: There may be an APR bug, not sure -- but at least on Mac, when @@ -799,7 +803,7 @@ LLProcess::~LLProcess() sProcessListener.dropPoll(*this); } - if (mAutokill) + if (mAttached) { kill("destructor"); } diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index bfac4567a5..e3386ad88e 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -167,6 +167,7 @@ public: args("args"), cwd("cwd"), autokill("autokill", true), + attached("attached", true), files("files"), postend("postend"), desc("desc") @@ -183,9 +184,31 @@ public: Multiple<std::string> args; /// current working directory, if need it changed Optional<std::string> cwd; - /// implicitly kill process on destruction of LLProcess object - /// (default true) + /// implicitly kill child process on termination of parent, whether + /// voluntary or crash (default true) Optional<bool> autokill; + /// implicitly kill process on destruction of LLProcess object + /// (default same as autokill) + /// + /// Originally, 'autokill' conflated two concepts: kill child process on + /// - destruction of its LLProcess object, and + /// - termination of parent process, voluntary or otherwise. + /// + /// It's useful to tease these apart. Some child processes are sent a + /// "clean up and terminate" message before the associated LLProcess + /// object is destroyed. A child process launched with attached=false + /// has an extra time window from the destruction of its LLProcess + /// until parent-process termination in which to perform its own + /// orderly shutdown, yet autokill=true still guarantees that we won't + /// accumulate orphan instances of such processes indefinitely. With + /// attached=true, if a child process cannot clean up between the + /// shutdown message and LLProcess destruction (presumably very soon + /// thereafter), it's forcibly killed anyway -- which can lead to + /// distressing user-visible crash indications. + /// + /// (The usefulness of attached=true with autokill=false is less + /// clear, but we don't prohibit that combination.) + Optional<bool> attached; /** * Up to three FileParam items: for child stdin, stdout, stderr. * Passing two FileParam entries means default treatment for stderr, @@ -540,7 +563,7 @@ private: std::string mDesc; std::string mPostend; apr_proc_t mProcess; - bool mAutokill; + bool mAutokill, mAttached; Status mStatus; // explicitly want this ptr_vector to be able to store NULLs typedef boost::ptr_vector< boost::nullable<BasePipe> > PipeVector; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 5ba343b183..b27e125d2e 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -789,6 +789,69 @@ namespace tut template<> template<> void object::test<10>() { + set_test_name("attached=false"); + // almost just like autokill=false, except set autokill=true with + // attached=false. + NamedTempFile from("from", "not started"); + NamedTempFile to("to", ""); + LLProcess::handle phandle(0); + { + PythonProcessLauncher py(get_test_name(), + "from __future__ import with_statement\n" + "import sys, time\n" + "with open(sys.argv[1], 'w') as f:\n" + " f.write('ok')\n" + "# wait for 'go' from test program\n" + "for i in xrange(60):\n" + " time.sleep(1)\n" + " with open(sys.argv[2]) as f:\n" + " go = f.read()\n" + " if go == 'go':\n" + " break\n" + "else:\n" + " with open(sys.argv[1], 'w') as f:\n" + " f.write('never saw go')\n" + " sys.exit(1)\n" + "# okay, saw 'go', write 'ack'\n" + "with open(sys.argv[1], 'w') as f:\n" + " f.write('ack')\n"); + py.mParams.args.add(from.getName()); + py.mParams.args.add(to.getName()); + py.mParams.autokill = true; + py.mParams.attached = false; + py.launch(); + // Capture handle for later + phandle = py.mPy->getProcessHandle(); + // Wait for the script to wake up and do its first write + int i = 0, timeout = 60; + for ( ; i < timeout; ++i) + { + yield(); + if (readfile(from.getName(), "from autokill script") == "ok") + break; + } + // If we broke this loop because of the counter, something's wrong + ensure("script never started", i < timeout); + // Now destroy the LLProcess, which should NOT kill the child! + } + // If the destructor killed the child anyway, give it time to die + yield(2); + // How do we know it's not terminated? By making it respond to + // a specific stimulus in a specific way. + { + std::ofstream outf(to.getName().c_str()); + outf << "go"; + } // flush and close. + // now wait for the script to terminate... one way or another. + waitfor(phandle, "autokill script"); + // If the LLProcess destructor implicitly called kill(), the + // script could not have written 'ack' as we expect. + ensure_equals(get_test_name() + " script output", readfile(from.getName()), "ack"); + } + + template<> template<> + void object::test<11>() + { set_test_name("'bogus' test"); CaptureLog recorder; PythonProcessLauncher py(get_test_name(), @@ -801,7 +864,7 @@ namespace tut } template<> template<> - void object::test<11>() + void object::test<12>() { set_test_name("'file' test"); // Replace this test with one or more real 'file' tests when we @@ -815,7 +878,7 @@ namespace tut } template<> template<> - void object::test<12>() + void object::test<13>() { set_test_name("'tpipe' test"); // Replace this test with one or more real 'tpipe' tests when we @@ -832,7 +895,7 @@ namespace tut } template<> template<> - void object::test<13>() + void object::test<14>() { set_test_name("'npipe' test"); // Replace this test with one or more real 'npipe' tests when we @@ -850,7 +913,7 @@ namespace tut } template<> template<> - void object::test<14>() + void object::test<15>() { set_test_name("internal pipe name warning"); CaptureLog recorder; @@ -914,7 +977,7 @@ namespace tut } while (0) template<> template<> - void object::test<15>() + void object::test<16>() { set_test_name("get*Pipe() validation"); PythonProcessLauncher py(get_test_name(), @@ -934,7 +997,7 @@ namespace tut } template<> template<> - void object::test<16>() + void object::test<17>() { set_test_name("talk to stdin/stdout"); PythonProcessLauncher py(get_test_name(), @@ -992,7 +1055,7 @@ namespace tut } template<> template<> - void object::test<17>() + void object::test<18>() { set_test_name("listen for ReadPipe events"); PythonProcessLauncher py(get_test_name(), @@ -1052,7 +1115,7 @@ namespace tut } template<> template<> - void object::test<18>() + void object::test<19>() { set_test_name("ReadPipe \"eof\" event"); PythonProcessLauncher py(get_test_name(), @@ -1078,7 +1141,7 @@ namespace tut } template<> template<> - void object::test<19>() + void object::test<20>() { set_test_name("setLimit()"); PythonProcessLauncher py(get_test_name(), @@ -1107,7 +1170,7 @@ namespace tut } template<> template<> - void object::test<20>() + void object::test<21>() { set_test_name("peek() ReadPipe data"); PythonProcessLauncher py(get_test_name(), @@ -1160,7 +1223,7 @@ namespace tut } template<> template<> - void object::test<21>() + void object::test<22>() { set_test_name("bad postend"); std::string pumpname("postend"); @@ -1185,7 +1248,7 @@ namespace tut } template<> template<> - void object::test<22>() + void object::test<23>() { set_test_name("good postend"); PythonProcessLauncher py(get_test_name(), @@ -1241,7 +1304,7 @@ namespace tut }; template<> template<> - void object::test<23>() + void object::test<24>() { set_test_name("all data visible at postend"); PythonProcessLauncher py(get_test_name(), |