Per Richard, replace LLProcessLauncher with LLProcess.

LLProcessLauncher had the somewhat fuzzy mandate of (1) accumulating
parameters with which to launch a child process and (2) sometimes tracking the
lifespan of the ensuing child process. But a valid LLProcessLauncher object
might or might not have ever been associated with an actual child process.
LLProcess specifically tracks a child process. In effect, it's a fairly thin
wrapper around a process HANDLE (on Windows) or pid_t (elsewhere), with
lifespan management thrown in. A static LLProcess::create() method launches a
new child; create() accepts an LLSD bundle with child parameters. So building
up a parameter bundle is deferred to LLSD rather than conflated with the
process management object.
Reconcile all known LLProcessLauncher consumers in the viewer code base,
notably the class unit tests.
meow-7.2.2
Nat Goodspeed 2012-01-20 18:10:40 -05:00
parent 083a9e0927
commit f0dbb87833
12 changed files with 627 additions and 694 deletions

View File

@ -74,7 +74,7 @@ set(llcommon_SOURCE_FILES
llmortician.cpp
lloptioninterface.cpp
llptrto.cpp
llprocesslauncher.cpp
llprocess.cpp
llprocessor.cpp
llqueuedthread.cpp
llrand.cpp
@ -197,7 +197,7 @@ set(llcommon_HEADER_FILES
llpointer.h
llpreprocessor.h
llpriqueuemap.h
llprocesslauncher.h
llprocess.h
llprocessor.h
llptrskiplist.h
llptrskipmap.h
@ -328,7 +328,7 @@ if (LL_TESTS)
LL_ADD_INTEGRATION_TEST(reflection "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(stringize "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lleventdispatcher "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llprocesslauncher "" "${test_libs}"
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}"
"${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/setpython.py")
LL_ADD_INTEGRATION_TEST(llstreamqueue "" "${test_libs}")

View File

@ -0,0 +1,338 @@
/**
* @file llprocess.cpp
* @brief Utility class for launching, terminating, and tracking the state of processes.
*
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llprocess.h"
#include "llsd.h"
#include "llsdserialize.h"
#include "stringize.h"
#include <boost/foreach.hpp>
#include <iostream>
#include <stdexcept>
/// Need an exception to avoid constructing an invalid LLProcess object, but
/// internal use only
struct LLProcessError: public std::runtime_error
{
LLProcessError(const std::string& msg): std::runtime_error(msg) {}
};
LLProcessPtr LLProcess::create(const LLSD& params)
{
try
{
return LLProcessPtr(new LLProcess(params));
}
catch (const LLProcessError& e)
{
LL_WARNS("LLProcess") << e.what() << LL_ENDL;
return LLProcessPtr();
}
}
LLProcess::LLProcess(const LLSD& params):
mProcessID(0),
mAutokill(params["autokill"].asBoolean())
{
// nonstandard default bool value
if (! params.has("autokill"))
mAutokill = true;
if (! params.has("executable"))
{
throw LLProcessError(STRINGIZE("not launched: missing 'executable'\n"
<< LLSDNotationStreamer(params)));
}
launch(params);
}
LLProcess::~LLProcess()
{
if (mAutokill)
{
kill();
}
}
bool LLProcess::isRunning(void)
{
mProcessID = isRunning(mProcessID);
return (mProcessID != 0);
}
#if LL_WINDOWS
static std::string quote(const std::string& str)
{
std::string::size_type len(str.length());
// If the string is already quoted, assume user knows what s/he's doing.
if (len >= 2 && str[0] == '"' && str[len-1] == '"')
{
return str;
}
// Not already quoted: do it.
std::string result("\"");
for (std::string::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
{
if (*ci == '"')
{
result.append("\\");
}
result.push_back(*ci);
}
return result + "\"";
}
void LLProcess::launch(const LLSD& params)
{
PROCESS_INFORMATION pinfo;
STARTUPINFOA sinfo;
memset(&sinfo, 0, sizeof(sinfo));
std::string args = quote(params["executable"]);
BOOST_FOREACH(const std::string& arg, llsd::inArray(params["args"]))
{
args += " ";
args += quote(arg);
}
// So retarded. Windows requires that the second parameter to
// CreateProcessA be a writable (non-const) string...
std::vector<char> args2(args.begin(), args.end());
args2.push_back('\0');
// Convert wrapper to a real std::string so we can use c_str(); but use a
// named variable instead of a temporary so c_str() pointer remains valid.
std::string cwd(params["cwd"]);
const char * working_directory = 0;
if (! cwd.empty())
working_directory = cwd.c_str();
if( ! CreateProcessA( NULL, &args2[0], NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) )
{
int result = GetLastError();
LPTSTR error_str = 0;
if(
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
result,
0,
(LPTSTR)&error_str,
0,
NULL)
!= 0)
{
char message[256];
wcstombs(message, error_str, sizeof(message));
message[sizeof(message)-1] = 0;
LocalFree(error_str);
throw LLProcessError(STRINGIZE("CreateProcessA failed (" << result << "): "
<< message));
}
throw LLProcessError(STRINGIZE("CreateProcessA failed (" << result
<< "), but FormatMessage() did not explain"));
}
// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
// CloseHandle(pinfo.hProcess); // stops leaks - nothing else
mProcessID = pinfo.hProcess;
CloseHandle(pinfo.hThread); // stops leaks - nothing else
}
LLProcess::id LLProcess::isRunning(id handle)
{
if (! handle)
return 0;
DWORD waitresult = WaitForSingleObject(handle, 0);
if(waitresult == WAIT_OBJECT_0)
{
// the process has completed.
return 0;
}
return handle;
}
bool LLProcess::kill(void)
{
if (! mProcessID)
return false;
TerminateProcess(mProcessID, 0);
return ! isRunning();
}
#else // Mac and linux
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>
// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
static bool reap_pid(pid_t pid)
{
pid_t wait_result = ::waitpid(pid, NULL, WNOHANG);
if (wait_result == pid)
{
return true;
}
if (wait_result == -1 && errno == ECHILD)
{
// No such process -- this may mean we're ignoring SIGCHILD.
return true;
}
return false;
}
void LLProcess::launch(const LLSD& params)
{
// flush all buffers before the child inherits them
::fflush(NULL);
pid_t child = vfork();
if (child == 0)
{
// child process
std::string cwd(params["cwd"]);
if (! cwd.empty())
{
// change to the desired child working directory
if (::chdir(cwd.c_str()))
{
// chdir failed
LL_WARNS("LLProcess") << "could not chdir(\"" << cwd << "\")" << LL_ENDL;
// pointless to throw; this is child process...
_exit(248);
}
}
// create an argv vector for the child process
std::vector<const char*> fake_argv;
// add the executable path
std::string executable(params["executable"]);
fake_argv.push_back(executable.c_str());
// and any arguments
const LLSD& params_args(params["args"]);
std::vector<std::string> args(params_args.beginArray(), params_args.endArray());
BOOST_FOREACH(const std::string& arg, args)
{
fake_argv.push_back(arg.c_str());
}
// terminate with a null pointer
fake_argv.push_back(NULL);
::execv(executable.c_str(), const_cast<char* const*>(&fake_argv[0]));
// If we reach this point, the exec failed.
LL_WARNS("LLProcess") << "failed to launch: ";
BOOST_FOREACH(const char* arg, fake_argv)
{
LL_CONT << arg << ' ';
}
LL_CONT << LL_ENDL;
// Use _exit() instead of exit() per the vfork man page. Exit with a
// distinctive rc: someday soon we'll be able to retrieve it, and it
// would be nice to be able to tell that the child process failed!
_exit(249);
}
// parent process
mProcessID = child;
}
LLProcess::id LLProcess::isRunning(id pid)
{
if (! pid)
return 0;
// Check whether the process has exited, and reap it if it has.
if(reap_pid(pid))
{
// the process has exited.
return 0;
}
return pid;
}
bool LLProcess::kill(void)
{
if (! mProcessID)
return false;
// Try to kill the process. We'll do approximately the same thing whether
// the kill returns an error or not, so we ignore the result.
(void)::kill(mProcessID, SIGTERM);
// This will have the side-effect of reaping the zombie if the process has exited.
return ! isRunning();
}
/*==========================================================================*|
static std::list<pid_t> sZombies;
void LLProcess::orphan(void)
{
// Disassociate the process from this object
if(mProcessID != 0)
{
// We may still need to reap the process's zombie eventually
sZombies.push_back(mProcessID);
mProcessID = 0;
}
}
// static
void LLProcess::reap(void)
{
// Attempt to real all saved process ID's.
std::list<pid_t>::iterator iter = sZombies.begin();
while(iter != sZombies.end())
{
if(reap_pid(*iter))
{
iter = sZombies.erase(iter);
}
else
{
iter++;
}
}
}
|*==========================================================================*/
#endif

106
indra/llcommon/llprocess.h Normal file
View File

@ -0,0 +1,106 @@
/**
* @file llprocess.h
* @brief Utility class for launching, terminating, and tracking child processes.
*
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLPROCESS_H
#define LL_LLPROCESS_H
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
#if LL_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
class LLSD;
class LLProcess;
/// LLProcess instances are created on the heap by static factory methods and
/// managed by ref-counted pointers.
typedef boost::shared_ptr<LLProcess> LLProcessPtr;
/**
* LLProcess handles launching external processes with specified command line arguments.
* It also keeps track of whether the process is still running, and can kill it if required.
*/
class LL_COMMON_API LLProcess: public boost::noncopyable
{
LOG_CLASS(LLProcess);
public:
/**
* Factory accepting LLSD::Map.
* MAY RETURN DEFAULT-CONSTRUCTED LLProcessPtr if params invalid!
*
* executable (required, string): executable pathname
* args (optional, string array): extra command-line arguments
* cwd (optional, string, dft no chdir): change to this directory before executing
* autokill (optional, bool, dft true): implicit kill() on ~LLProcess
*/
static LLProcessPtr create(const LLSD& params);
virtual ~LLProcess();
// isRunning isn't const because, if child isn't running, it clears stored
// process ID
bool isRunning(void);
// Attempt to kill the process -- returns true if the process is no longer running when it returns.
// Note that even if this returns false, the process may exit some time after it's called.
bool kill(void);
#if LL_WINDOWS
typedef HANDLE id;
#else
typedef pid_t id;
#endif
/// Get platform-specific process ID
id getProcessID() const { return mProcessID; };
/**
* Test if a process (id obtained from getProcessID()) is still
* running. Return is same nonzero id value if still running, else
* zero, so you can test it like a bool. But if you want to update a
* stored variable as a side effect, you can write code like this:
* @code
* childpid = LLProcess::isRunning(childpid);
* @endcode
* @note This method is intended as a unit-test hook, not as the first of
* a whole set of operations supported on freestanding @c id values. New
* functionality should be added as nonstatic members operating on
* mProcessID.
*/
static id isRunning(id);
private:
/// constructor is private: use create() instead
LLProcess(const LLSD& params);
void launch(const LLSD& params);
id mProcessID;
bool mAutokill;
};
#endif // LL_LLPROCESS_H

View File

@ -1,394 +0,0 @@
/**
* @file llprocesslauncher.cpp
* @brief Utility class for launching, terminating, and tracking the state of processes.
*
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llprocesslauncher.h"
#include <iostream>
#if LL_DARWIN || LL_LINUX
// not required or present on Win32
#include <sys/wait.h>
#endif
LLProcessLauncher::LLProcessLauncher()
{
#if LL_WINDOWS
mProcessHandle = 0;
#else
mProcessID = 0;
#endif
}
LLProcessLauncher::~LLProcessLauncher()
{
kill();
}
void LLProcessLauncher::setExecutable(const std::string &executable)
{
mExecutable = executable;
}
void LLProcessLauncher::setWorkingDirectory(const std::string &dir)
{
mWorkingDir = dir;
}
const std::string& LLProcessLauncher::getExecutable() const
{
return mExecutable;
}
void LLProcessLauncher::clearArguments()
{
mLaunchArguments.clear();
}
void LLProcessLauncher::addArgument(const std::string &arg)
{
mLaunchArguments.push_back(arg);
}
#if LL_WINDOWS
static std::string quote(const std::string& str)
{
std::string::size_type len(str.length());
// If the string is already quoted, assume user knows what s/he's doing.
if (len >= 2 && str[0] == '"' && str[len-1] == '"')
{
return str;
}
// Not already quoted: do it.
std::string result("\"");
for (std::string::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci)
{
if (*ci == '"')
{
result.append("\\");
}
result.push_back(*ci);
}
return result + "\"";
}
int LLProcessLauncher::launch(void)
{
// If there was already a process associated with this object, kill it.
kill();
orphan();
int result = 0;
PROCESS_INFORMATION pinfo;
STARTUPINFOA sinfo;
memset(&sinfo, 0, sizeof(sinfo));
std::string args = quote(mExecutable);
for(int i = 0; i < (int)mLaunchArguments.size(); i++)
{
args += " ";
args += quote(mLaunchArguments[i]);
}
// So retarded. Windows requires that the second parameter to CreateProcessA be a writable (non-const) string...
char *args2 = new char[args.size() + 1];
strcpy(args2, args.c_str());
const char * working_directory = 0;
if(!mWorkingDir.empty()) working_directory = mWorkingDir.c_str();
if( ! CreateProcessA( NULL, args2, NULL, NULL, FALSE, 0, NULL, working_directory, &sinfo, &pinfo ) )
{
result = GetLastError();
LPTSTR error_str = 0;
if(
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
result,
0,
(LPTSTR)&error_str,
0,
NULL)
!= 0)
{
char message[256];
wcstombs(message, error_str, 256);
message[255] = 0;
llwarns << "CreateProcessA failed: " << message << llendl;
LocalFree(error_str);
}
if(result == 0)
{
// Make absolutely certain we return a non-zero value on failure.
result = -1;
}
}
else
{
// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
// CloseHandle(pinfo.hProcess); // stops leaks - nothing else
mProcessHandle = pinfo.hProcess;
CloseHandle(pinfo.hThread); // stops leaks - nothing else
}
delete[] args2;
return result;
}
bool LLProcessLauncher::isRunning(void)
{
mProcessHandle = isRunning(mProcessHandle);
return (mProcessHandle != 0);
}
LLProcessLauncher::ll_pid_t LLProcessLauncher::isRunning(ll_pid_t handle)
{
if (! handle)
return 0;
DWORD waitresult = WaitForSingleObject(handle, 0);
if(waitresult == WAIT_OBJECT_0)
{
// the process has completed.
return 0;
}
return handle;
}
bool LLProcessLauncher::kill(void)
{
bool result = true;
if(mProcessHandle != 0)
{
TerminateProcess(mProcessHandle,0);
if(isRunning())
{
result = false;
}
}
return result;
}
void LLProcessLauncher::orphan(void)
{
// Forget about the process
mProcessHandle = 0;
}
// static
void LLProcessLauncher::reap(void)
{
// No actions necessary on Windows.
}
#else // Mac and linux
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
static std::list<pid_t> sZombies;
// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise.
static bool reap_pid(pid_t pid)
{
bool result = false;
pid_t wait_result = ::waitpid(pid, NULL, WNOHANG);
if(wait_result == pid)
{
result = true;
}
else if(wait_result == -1)
{
if(errno == ECHILD)
{
// No such process -- this may mean we're ignoring SIGCHILD.
result = true;
}
}
return result;
}
int LLProcessLauncher::launch(void)
{
// If there was already a process associated with this object, kill it.
kill();
orphan();
int result = 0;
int current_wd = -1;
// create an argv vector for the child process
const char ** fake_argv = new const char *[mLaunchArguments.size() + 2]; // 1 for the executable path, 1 for the NULL terminator
int i = 0;
// add the executable path
fake_argv[i++] = mExecutable.c_str();
// and any arguments
for(int j=0; j < mLaunchArguments.size(); j++)
fake_argv[i++] = mLaunchArguments[j].c_str();
// terminate with a null pointer
fake_argv[i] = NULL;
if(!mWorkingDir.empty())
{
// save the current working directory
current_wd = ::open(".", O_RDONLY);
// and change to the one the child will be executed in
if (::chdir(mWorkingDir.c_str()))
{
// chdir failed
}
}
// flush all buffers before the child inherits them
::fflush(NULL);
pid_t id = vfork();
if(id == 0)
{
// child process
::execv(mExecutable.c_str(), (char * const *)fake_argv);
// If we reach this point, the exec failed.
LL_WARNS("LLProcessLauncher") << "failed to launch: ";
for (const char * const * ai = fake_argv; *ai; ++ai)
{
LL_CONT << *ai << ' ';
}
LL_CONT << LL_ENDL;
// Use _exit() instead of exit() per the vfork man page. Exit with a
// distinctive rc: someday soon we'll be able to retrieve it, and it
// would be nice to be able to tell that the child process failed!
_exit(249);
}
// parent process
if(current_wd >= 0)
{
// restore the previous working directory
if (::fchdir(current_wd))
{
// chdir failed
}
::close(current_wd);
}
delete[] fake_argv;
mProcessID = id;
return result;
}
bool LLProcessLauncher::isRunning(void)
{
mProcessID = isRunning(mProcessID);
return (mProcessID != 0);
}
LLProcessLauncher::ll_pid_t LLProcessLauncher::isRunning(ll_pid_t pid)
{
if (! pid)
return 0;
// Check whether the process has exited, and reap it if it has.
if(reap_pid(pid))
{
// the process has exited.
return 0;
}
return pid;
}
bool LLProcessLauncher::kill(void)
{
bool result = true;
if(mProcessID != 0)
{
// Try to kill the process. We'll do approximately the same thing whether the kill returns an error or not, so we ignore the result.
(void)::kill(mProcessID, SIGTERM);
// This will have the side-effect of reaping the zombie if the process has exited.
if(isRunning())
{
result = false;
}
}
return result;
}
void LLProcessLauncher::orphan(void)
{
// Disassociate the process from this object
if(mProcessID != 0)
{
// We may still need to reap the process's zombie eventually
sZombies.push_back(mProcessID);
mProcessID = 0;
}
}
// static
void LLProcessLauncher::reap(void)
{
// Attempt to real all saved process ID's.
std::list<pid_t>::iterator iter = sZombies.begin();
while(iter != sZombies.end())
{
if(reap_pid(*iter))
{
iter = sZombies.erase(iter);
}
else
{
iter++;
}
}
}
#endif

View File

@ -1,107 +0,0 @@
/**
* @file llprocesslauncher.h
* @brief Utility class for launching, terminating, and tracking the state of processes.
*
* $LicenseInfo:firstyear=2008&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLPROCESSLAUNCHER_H
#define LL_LLPROCESSLAUNCHER_H
#if LL_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
/*
LLProcessLauncher handles launching external processes with specified command line arguments.
It also keeps track of whether the process is still running, and can kill it if required.
*/
class LL_COMMON_API LLProcessLauncher
{
LOG_CLASS(LLProcessLauncher);
public:
LLProcessLauncher();
virtual ~LLProcessLauncher();
void setExecutable(const std::string &executable);
void setWorkingDirectory(const std::string &dir);
const std::string& getExecutable() const;
void clearArguments();
void addArgument(const std::string &arg);
int launch(void);
// isRunning isn't const because, if child isn't running, it clears stored
// process ID
bool isRunning(void);
// Attempt to kill the process -- returns true if the process is no longer running when it returns.
// Note that even if this returns false, the process may exit some time after it's called.
bool kill(void);
// Use this if you want the external process to continue execution after the LLProcessLauncher instance controlling it is deleted.
// Normally, the destructor will attempt to kill the process and wait for termination.
// This should only be used if the viewer is about to exit -- otherwise, the child process will become a zombie after it exits.
void orphan(void);
// This needs to be called periodically on Mac/Linux to clean up zombie processes.
// (However, as of 2012-01-12 there are no such calls in the viewer code base. :-P )
static void reap(void);
// Accessors for platform-specific process ID
#if LL_WINDOWS
// (Windows flavor unused as of 2012-01-12)
typedef HANDLE ll_pid_t;
HANDLE getProcessHandle() const { return mProcessHandle; }
ll_pid_t getProcessID() const { return mProcessHandle; }
#else
typedef pid_t ll_pid_t;
ll_pid_t getProcessID() const { return mProcessID; };
#endif
/**
* Test if a process (ll_pid_t obtained from getProcessID()) is still
* running. Return is same nonzero ll_pid_t value if still running, else
* zero, so you can test it like a bool. But if you want to update a
* stored variable as a side effect, you can write code like this:
* @code
* childpid = LLProcessLauncher::isRunning(childpid);
* @endcode
*/
static ll_pid_t isRunning(ll_pid_t);
private:
std::string mExecutable;
std::string mWorkingDir;
std::vector<std::string> mLaunchArguments;
#if LL_WINDOWS
HANDLE mProcessHandle;
#else
pid_t mProcessID;
#endif
};
#endif // LL_LLPROCESSLAUNCHER_H

View File

@ -1,8 +1,8 @@
/**
* @file llprocesslauncher_test.cpp
* @file llprocess_test.cpp
* @author Nat Goodspeed
* @date 2011-12-19
* @brief Test for llprocesslauncher.
* @brief Test for llprocess.
*
* $LicenseInfo:firstyear=2011&license=viewerlgpl$
* Copyright (c) 2011, Linden Research, Inc.
@ -12,7 +12,7 @@
// Precompiled header
#include "linden_common.h"
// associated header
#include "llprocesslauncher.h"
#include "llprocess.h"
// STL headers
#include <vector>
#include <list>
@ -32,6 +32,7 @@
#include "../test/manageapr.h"
#include "../test/namedtempfile.h"
#include "stringize.h"
#include "llsdutil.h"
#if defined(LL_WINDOWS)
#define sleep(secs) _sleep((secs) * 1000)
@ -88,7 +89,7 @@ static std::string readfile(const std::string& pathname, const std::string& desc
}
/**
* Construct an LLProcessLauncher to run a Python script.
* Construct an LLProcess to run a Python script.
*/
struct PythonProcessLauncher
{
@ -106,30 +107,30 @@ struct PythonProcessLauncher
const char* PYTHON(getenv("PYTHON"));
tut::ensure("Set $PYTHON to the Python interpreter", PYTHON);
mPy.setExecutable(PYTHON);
mPy.addArgument(mScript.getName());
mParams["executable"] = PYTHON;
mParams["args"].append(mScript.getName());
}
/// Run Python script and wait for it to complete.
void run()
{
tut::ensure_equals(STRINGIZE("Couldn't launch " << mDesc << " script"),
mPy.launch(), 0);
// One of the irritating things about LLProcessLauncher is that
mPy = LLProcess::create(mParams);
tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), mPy);
// One of the irritating things about LLProcess is that
// there's no API to wait for the child to terminate -- but given
// its use in our graphics-intensive interactive viewer, it's
// understandable.
while (mPy.isRunning())
while (mPy->isRunning())
{
sleep(1);
}
}
/**
* Run a Python script using LLProcessLauncher, expecting that it will
* Run a Python script using LLProcess, expecting that it will
* write to the file passed as its sys.argv[1]. Retrieve that output.
*
* Until January 2012, LLProcessLauncher provided distressingly few
* Until January 2012, LLProcess provided distressingly few
* mechanisms for a child process to communicate back to its caller --
* not even its return code. We've introduced a convention by which we
* create an empty temp file, pass the name of that file to our child
@ -141,13 +142,14 @@ struct PythonProcessLauncher
{
NamedTempFile out("out", ""); // placeholder
// pass name of this temporary file to the script
mPy.addArgument(out.getName());
mParams["args"].append(out.getName());
run();
// assuming the script wrote to that file, read it
return readfile(out.getName(), STRINGIZE("from " << mDesc << " script"));
}
LLProcessLauncher mPy;
LLSD mParams;
LLProcessPtr mPy;
std::string mDesc;
NamedTempFile mScript;
};
@ -203,13 +205,13 @@ private:
*****************************************************************************/
namespace tut
{
struct llprocesslauncher_data
struct llprocess_data
{
LLAPRPool pool;
};
typedef test_group<llprocesslauncher_data> llprocesslauncher_group;
typedef llprocesslauncher_group::object object;
llprocesslauncher_group llprocesslaunchergrp("llprocesslauncher");
typedef test_group<llprocess_data> llprocess_group;
typedef llprocess_group::object object;
llprocess_group llprocessgrp("llprocess");
struct Item
{
@ -501,17 +503,6 @@ namespace tut
template<> template<>
void object::test<2>()
{
set_test_name("set/getExecutable()");
LLProcessLauncher child;
child.setExecutable("nonsense string");
ensure_equals("setExecutable() 0", child.getExecutable(), "nonsense string");
child.setExecutable("python");
ensure_equals("setExecutable() 1", child.getExecutable(), "python");
}
template<> template<>
void object::test<3>()
{
set_test_name("setWorkingDirectory()");
// We want to test setWorkingDirectory(). But what directory is
@ -524,14 +515,14 @@ namespace tut
"with open(sys.argv[1], 'w') as f:\n"
" f.write(os.getcwd())\n");
// Before running, call setWorkingDirectory()
py.mPy.setWorkingDirectory(tempdir.getName());
py.mParams["cwd"] = tempdir.getName();
ensure_equals("os.getcwd()", py.run_read(), tempdir.getName());
}
template<> template<>
void object::test<4>()
void object::test<3>()
{
set_test_name("clearArguments()");
set_test_name("arguments");
PythonProcessLauncher py("args",
"from __future__ import with_statement\n"
"import sys\n"
@ -539,15 +530,11 @@ namespace tut
"with open(sys.argv[3], 'w') as f:\n"
" for arg in sys.argv[1:]:\n"
" print >>f, arg\n");
// We expect that PythonProcessLauncher has already called
// addArgument() with the name of its own NamedTempFile. But let's
// change it up.
py.mPy.clearArguments();
// re-add script pathname
py.mPy.addArgument(py.mScript.getName()); // sys.argv[0]
py.mPy.addArgument("first arg"); // sys.argv[1]
py.mPy.addArgument("second arg"); // sys.argv[2]
// run_read() calls addArgument() one more time, hence [3]
// We expect that PythonProcessLauncher has already appended
// its own NamedTempFile to mParams["args"] (sys.argv[0]).
py.mParams["args"].append("first arg"); // sys.argv[1]
py.mParams["args"].append("second arg"); // sys.argv[2]
// run_read() appends() one more argument, hence [3]
std::string output(py.run_read());
boost::split_iterator<std::string::const_iterator>
li(output, boost::first_finder("\n")), lend;
@ -567,7 +554,7 @@ namespace tut
}
template<> template<>
void object::test<5>()
void object::test<4>()
{
set_test_name("explicit kill()");
PythonProcessLauncher py("kill()",
@ -581,8 +568,9 @@ namespace tut
"with open(sys.argv[1], 'w') as f:\n"
" f.write('bad')\n");
NamedTempFile out("out", "not started");
py.mPy.addArgument(out.getName());
ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
py.mParams["args"].append(out.getName());
py.mPy = LLProcess::create(py.mParams);
ensure("couldn't launch kill() script", py.mPy);
// Wait for the script to wake up and do its first write
int i = 0, timeout = 60;
for ( ; i < timeout; ++i)
@ -594,9 +582,55 @@ namespace tut
// If we broke this loop because of the counter, something's wrong
ensure("script never started", i < timeout);
// script has performed its first write and should now be sleeping.
py.mPy.kill();
py.mPy->kill();
// wait for the script to terminate... one way or another.
while (py.mPy.isRunning())
while (py.mPy->isRunning())
{
sleep(1);
}
// If kill() failed, the script would have woken up on its own and
// overwritten the file with 'bad'. But if kill() succeeded, it should
// not have had that chance.
ensure_equals("kill() script output", readfile(out.getName()), "ok");
}
template<> template<>
void object::test<5>()
{
set_test_name("implicit kill()");
NamedTempFile out("out", "not started");
LLProcess::id pid(0);
{
PythonProcessLauncher py("kill()",
"from __future__ import with_statement\n"
"import sys, time\n"
"with open(sys.argv[1], 'w') as f:\n"
" f.write('ok')\n"
"# now sleep; expect caller to kill\n"
"time.sleep(120)\n"
"# if caller hasn't managed to kill by now, bad\n"
"with open(sys.argv[1], 'w') as f:\n"
" f.write('bad')\n");
py.mParams["args"].append(out.getName());
py.mPy = LLProcess::create(py.mParams);
ensure("couldn't launch kill() script", py.mPy);
// Capture id for later
pid = py.mPy->getProcessID();
// Wait for the script to wake up and do its first write
int i = 0, timeout = 60;
for ( ; i < timeout; ++i)
{
sleep(1);
if (readfile(out.getName(), "from kill() script") == "ok")
break;
}
// If we broke this loop because of the counter, something's wrong
ensure("script never started", i < timeout);
// Script has performed its first write and should now be sleeping.
// Destroy the LLProcess, which should kill the child.
}
// wait for the script to terminate... one way or another.
while (LLProcess::isRunning(pid))
{
sleep(1);
}
@ -609,57 +643,12 @@ namespace tut
template<> template<>
void object::test<6>()
{
set_test_name("implicit kill()");
NamedTempFile out("out", "not started");
LLProcessLauncher::ll_pid_t pid(0);
{
PythonProcessLauncher py("kill()",
"from __future__ import with_statement\n"
"import sys, time\n"
"with open(sys.argv[1], 'w') as f:\n"
" f.write('ok')\n"
"# now sleep; expect caller to kill\n"
"time.sleep(120)\n"
"# if caller hasn't managed to kill by now, bad\n"
"with open(sys.argv[1], 'w') as f:\n"
" f.write('bad')\n");
py.mPy.addArgument(out.getName());
ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
// Capture ll_pid_t for later
pid = py.mPy.getProcessID();
// Wait for the script to wake up and do its first write
int i = 0, timeout = 60;
for ( ; i < timeout; ++i)
{
sleep(1);
if (readfile(out.getName(), "from kill() script") == "ok")
break;
}
// If we broke this loop because of the counter, something's wrong
ensure("script never started", i < timeout);
// Script has performed its first write and should now be sleeping.
// Destroy the LLProcessLauncher, which should kill the child.
}
// wait for the script to terminate... one way or another.
while (LLProcessLauncher::isRunning(pid))
{
sleep(1);
}
// If kill() failed, the script would have woken up on its own and
// overwritten the file with 'bad'. But if kill() succeeded, it should
// not have had that chance.
ensure_equals("kill() script output", readfile(out.getName()), "ok");
}
template<> template<>
void object::test<7>()
{
set_test_name("orphan()");
set_test_name("autokill");
NamedTempFile from("from", "not started");
NamedTempFile to("to", "");
LLProcessLauncher::ll_pid_t pid(0);
LLProcess::id pid(0);
{
PythonProcessLauncher py("orphan()",
PythonProcessLauncher py("autokill",
"from __future__ import with_statement\n"
"import sys, time\n"
"with open(sys.argv[1], 'w') as f:\n"
@ -678,25 +667,24 @@ namespace tut
"# okay, saw 'go', write 'ack'\n"
"with open(sys.argv[1], 'w') as f:\n"
" f.write('ack')\n");
py.mPy.addArgument(from.getName());
py.mPy.addArgument(to.getName());
ensure_equals("couldn't launch kill() script", py.mPy.launch(), 0);
// Capture ll_pid_t for later
pid = py.mPy.getProcessID();
py.mParams["args"].append(from.getName());
py.mParams["args"].append(to.getName());
py.mParams["autokill"] = false;
py.mPy = LLProcess::create(py.mParams);
ensure("couldn't launch kill() script", py.mPy);
// Capture id for later
pid = py.mPy->getProcessID();
// Wait for the script to wake up and do its first write
int i = 0, timeout = 60;
for ( ; i < timeout; ++i)
{
sleep(1);
if (readfile(from.getName(), "from orphan() script") == "ok")
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);
// Script has performed its first write and should now be waiting
// for us. Orphan it.
py.mPy.orphan();
// Now destroy the LLProcessLauncher, which should NOT kill the child!
// Now destroy the LLProcess, which should NOT kill the child!
}
// If the destructor killed the child anyway, give it time to die
sleep(2);
@ -707,12 +695,12 @@ namespace tut
outf << "go";
} // flush and close.
// now wait for the script to terminate... one way or another.
while (LLProcessLauncher::isRunning(pid))
while (LLProcess::isRunning(pid))
{
sleep(1);
}
// If the LLProcessLauncher destructor implicitly called kill(), the
// If the LLProcess destructor implicitly called kill(), the
// script could not have written 'ack' as we expect.
ensure_equals("orphan() script output", readfile(from.getName()), "ack");
ensure_equals("autokill script output", readfile(from.getName()), "ack");
}
} // namespace tut

View File

@ -40,7 +40,7 @@ typedef U32 uint32_t;
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "llprocesslauncher.h"
#include "llprocess.h"
#endif
#include "boost/range.hpp"
@ -1557,14 +1557,15 @@ namespace tut
}
#else // LL_DARWIN, LL_LINUX
LLProcessLauncher py;
py.setExecutable(PYTHON);
py.addArgument(scriptfile.getName());
ensure_equals(STRINGIZE("Couldn't launch " << desc << " script"), py.launch(), 0);
LLSD params;
params["executable"] = PYTHON;
params["args"].append(scriptfile.getName());
LLProcessPtr py(LLProcess::create(params));
ensure(STRINGIZE("Couldn't launch " << desc << " script"), py);
// Implementing timeout would mean messing with alarm() and
// catching SIGALRM... later maybe...
int status(0);
if (waitpid(py.getProcessID(), &status, 0) == -1)
if (waitpid(py->getProcessID(), &status, 0) == -1)
{
int waitpid_errno(errno);
ensure_equals(STRINGIZE("Couldn't retrieve rc from " << desc << " script: "

View File

@ -31,6 +31,7 @@
#include "llpluginprocessparent.h"
#include "llpluginmessagepipe.h"
#include "llpluginmessageclasses.h"
#include "stringize.h"
#include "llapr.h"
@ -134,7 +135,10 @@ LLPluginProcessParent::~LLPluginProcessParent()
mSharedMemoryRegions.erase(iter);
}
mProcess.kill();
if (mProcess)
{
mProcess->kill();
}
killSockets();
}
@ -159,8 +163,8 @@ void LLPluginProcessParent::errorState(void)
void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug)
{
mProcess.setExecutable(launcher_filename);
mProcess.setWorkingDirectory(plugin_dir);
mProcessParams["executable"] = launcher_filename;
mProcessParams["cwd"] = plugin_dir;
mPluginFile = plugin_filename;
mPluginDir = plugin_dir;
mCPUUsage = 0.0f;
@ -371,10 +375,8 @@ void LLPluginProcessParent::idle(void)
// Launch the plugin process.
// Only argument to the launcher is the port number we're listening on
std::stringstream stream;
stream << mBoundPort;
mProcess.addArgument(stream.str());
if(mProcess.launch() != 0)
mProcessParams["args"].append(stringize(mBoundPort));
if (! (mProcess = LLProcess::create(mProcessParams)))
{
errorState();
}
@ -388,19 +390,18 @@ void LLPluginProcessParent::idle(void)
// The command we're constructing would look like this on the command line:
// osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell'
std::stringstream cmd;
mDebugger.setExecutable("/usr/bin/osascript");
mDebugger.addArgument("-e");
mDebugger.addArgument("tell application \"Terminal\"");
mDebugger.addArgument("-e");
cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\"";
mDebugger.addArgument(cmd.str());
mDebugger.addArgument("-e");
mDebugger.addArgument("do script \"continue\" in win");
mDebugger.addArgument("-e");
mDebugger.addArgument("end tell");
mDebugger.launch();
LLSD params;
params["executable"] = "/usr/bin/osascript";
params["args"].append("-e");
params["args"].append("tell application \"Terminal\"");
params["args"].append("-e");
params["args"].append(STRINGIZE("set win to do script \"gdb -pid "
<< mProcess->getProcessID() << "\""));
params["args"].append("-e");
params["args"].append("do script \"continue\" in win");
params["args"].append("-e");
params["args"].append("end tell");
mDebugger = LLProcess::create(params);
#endif
}
@ -470,7 +471,7 @@ void LLPluginProcessParent::idle(void)
break;
case STATE_EXITING:
if(!mProcess.isRunning())
if (! mProcess->isRunning())
{
setState(STATE_CLEANUP);
}
@ -498,7 +499,7 @@ void LLPluginProcessParent::idle(void)
break;
case STATE_CLEANUP:
mProcess.kill();
mProcess->kill();
killSockets();
setState(STATE_DONE);
break;
@ -1077,7 +1078,7 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
{
bool result = false;
if(!mProcess.isRunning())
if (! mProcess->isRunning())
{
LL_WARNS("Plugin") << "child exited" << LL_ENDL;
result = true;

View File

@ -30,13 +30,14 @@
#define LL_LLPLUGINPROCESSPARENT_H
#include "llapr.h"
#include "llprocesslauncher.h"
#include "llprocess.h"
#include "llpluginmessage.h"
#include "llpluginmessagepipe.h"
#include "llpluginsharedmemory.h"
#include "lliosocket.h"
#include "llthread.h"
#include "llsd.h"
class LLPluginProcessParentOwner
{
@ -148,8 +149,9 @@ private:
LLSocket::ptr_t mListenSocket;
LLSocket::ptr_t mSocket;
U32 mBoundPort;
LLProcessLauncher mProcess;
LLSD mProcessParams;
LLProcessPtr mProcess;
std::string mPluginFile;
std::string mPluginDir;
@ -171,7 +173,7 @@ private:
bool mBlocked;
bool mPolledInput;
LLProcessLauncher mDebugger;
LLProcessPtr mDebugger;
F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.

View File

@ -29,6 +29,9 @@
#include "lltrans.h"
#include "llui.h"
#include "llprocess.h"
#include "llsdutil.h"
#include <boost/foreach.hpp>
// static
const std::string LLExternalEditor::sFilenameMarker = "%s";
@ -45,19 +48,8 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env
return EC_NOT_SPECIFIED;
}
// Add the filename marker if missing.
if (cmd.find(sFilenameMarker) == std::string::npos)
{
cmd += " \"" + sFilenameMarker + "\"";
llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl;
}
string_vec_t tokens;
if (tokenize(tokens, cmd) < 2) // 2 = bin + at least one arg (%s)
{
llwarns << "Error parsing editor command" << llendl;
return EC_PARSE_ERROR;
}
tokenize(tokens, cmd);
// Check executable for existence.
std::string bin_path = tokens[0];
@ -68,51 +60,60 @@ LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env
}
// Save command.
mProcess.setExecutable(bin_path);
mArgs.clear();
mProcessParams["executable"] = bin_path;
mProcessParams["args"].clear();
for (size_t i = 1; i < tokens.size(); ++i)
{
if (i > 1) mArgs += " ";
mArgs += "\"" + tokens[i] + "\"";
mProcessParams["args"].append(tokens[i]);
}
llinfos << "Setting command [" << bin_path << " " << mArgs << "]" << llendl;
// Add the filename marker if missing.
if (cmd.find(sFilenameMarker) == std::string::npos)
{
mProcessParams["args"].append(sFilenameMarker);
llinfos << "Adding the filename marker (" << sFilenameMarker << ")" << llendl;
}
llinfos << "Setting command [" << bin_path;
BOOST_FOREACH(const std::string& arg, llsd::inArray(mProcessParams["args"]))
{
llcont << " \"" << arg << "\"";
}
llcont << "]" << llendl;
return EC_SUCCESS;
}
LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path)
{
std::string args = mArgs;
if (mProcess.getExecutable().empty() || args.empty())
if (mProcessParams["executable"].asString().empty() || ! mProcessParams["args"].size())
{
llwarns << "Editor command not set" << llendl;
return EC_NOT_SPECIFIED;
}
// Copy params block so we can replace sFilenameMarker
LLSD params(mProcessParams);
// Substitute the filename marker in the command with the actual passed file name.
LLStringUtil::replaceString(args, sFilenameMarker, file_path);
// Split command into separate tokens.
string_vec_t tokens;
tokenize(tokens, args);
// Set process arguments taken from the command.
mProcess.clearArguments();
for (string_vec_t::const_iterator arg_it = tokens.begin(); arg_it != tokens.end(); ++arg_it)
LLSD& args(params["args"]);
for (LLSD::array_iterator ai(args.beginArray()), aend(args.endArray()); ai != aend; ++ai)
{
mProcess.addArgument(*arg_it);
std::string sarg(*ai);
LLStringUtil::replaceString(sarg, sFilenameMarker, file_path);
*ai = sarg;
}
// Run the editor.
llinfos << "Running editor command [" << mProcess.getExecutable() + " " + args << "]" << llendl;
int result = mProcess.launch();
if (result == 0)
llinfos << "Running editor command [" << params["executable"];
BOOST_FOREACH(const std::string& arg, llsd::inArray(params["args"]))
{
// Prevent killing the process in destructor (will add it to the zombies list).
mProcess.orphan();
llcont << " \"" << arg << "\"";
}
return result == 0 ? EC_SUCCESS : EC_FAILED_TO_RUN;
llcont << "]" << llendl;
// Prevent killing the process in destructor.
params["autokill"] = false;
return LLProcess::create(params) ? EC_SUCCESS : EC_FAILED_TO_RUN;
}
// static

View File

@ -27,7 +27,7 @@
#ifndef LL_LLEXTERNALEDITOR_H
#define LL_LLEXTERNALEDITOR_H
#include <llprocesslauncher.h>
#include "llsd.h"
/**
* Usage:
@ -98,8 +98,7 @@ private:
static const std::string sSetting;
std::string mArgs;
LLProcessLauncher mProcess;
LLSD mProcessParams;
};
#endif // LL_LLEXTERNALEDITOR_H

View File

@ -26,10 +26,10 @@
#include "linden_common.h"
#include <apr_file_io.h>
#include "llapr.h"
#include "llprocesslauncher.h"
#include "llprocess.h"
#include "llupdateinstaller.h"
#include "lldir.h"
#include "llsd.h"
#if defined(LL_WINDOWS)
#pragma warning(disable: 4702) // disable 'unreachable code' so we can use lexical_cast (really!).
@ -78,15 +78,13 @@ int ll_install_update(std::string const & script,
llinfos << "UpdateInstaller: installing " << updatePath << " using " <<
actualScriptPath << LL_ENDL;
LLProcessLauncher launcher;
launcher.setExecutable(actualScriptPath);
launcher.addArgument(updatePath);
launcher.addArgument(ll_install_failed_marker_path());
launcher.addArgument(boost::lexical_cast<std::string>(required));
int result = launcher.launch();
launcher.orphan();
return result;
LLSD params;
params["executable"] = actualScriptPath;
params["args"].append(updatePath);
params["args"].append(ll_install_failed_marker_path());
params["args"].append(boost::lexical_cast<std::string>(required));
params["autokill"] = false;
return LLProcess::create(params)? 0 : -1;
}