Bug 480427: Add a way to run a process in a background threadd. r=bsmedberg

This commit is contained in:
Dave Townsend 2009-03-25 08:57:21 +00:00
Родитель 7d56e0bc14
Коммит a839ae272c
6 изменённых файлов: 375 добавлений и 196 удалений

Просмотреть файл

@ -4,6 +4,8 @@
using namespace std;
int main(int argc, char* argv[]) {
if (argc != 2)
return -1;
string test = "mozilla";

Просмотреть файл

@ -1,6 +1,8 @@
#include <stdio.h>
int main () {
int main (int argc, char* argv[]) {
if (argc != 1)
return -1;
return 42;
}

Просмотреть файл

@ -39,12 +39,11 @@ var isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes);
function get_test_program(prog)
{
var progPath = do_get_cwd();
progPath.append(prog);
if (isWindows) {
progPath.leafName = progPath.leafName + ".exe";
}
return progPath;
var progPath = do_get_cwd();
progPath.append(prog);
if (isWindows)
progPath.leafName = progPath.leafName + ".exe";
return progPath;
}
function set_environment()
@ -112,7 +111,7 @@ function test_quick()
}
// test if an argument can be successfully passed to an application
// that will return -1 if "mozilla" is not the first argument
// that will return 0 if "mozilla" is the only argument
function test_arguments()
{
var file = get_test_program("TestArguments");
@ -125,37 +124,65 @@ function test_arguments()
process.run(true, args, args.length);
// exit codes actually seem to be unsigned bytes...
do_check_neq(process.exitValue, 255);
do_check_eq(process.exitValue, 0);
}
var gProcess;
// test if we can get an exit value from an application that is
// run non-blocking
function test_nonblocking()
// test if we get notified about a blocking process
function test_notify_blocking()
{
var file = get_test_program("TestQuickReturn");
gProcess = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
gProcess.init(file);
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess2);
process.init(file);
gProcess.run(false, [], 0);
do_test_pending();
do_timeout(100, "check_nonblocking()");
process.runAsync([], 0, {
observe: function(subject, topic, data) {
process = subject.QueryInterface(Components.interfaces.nsIProcess);
do_check_eq(topic, "process-finished");
do_check_eq(process.exitValue, 42);
test_notify_nonblocking();
}
});
}
function check_nonblocking()
// test if we get notified about a non-blocking process
function test_notify_nonblocking()
{
if (gProcess.isRunning) {
do_timeout(100, "check_nonblocking()");
return;
}
var file = get_test_program("TestArguments");
do_check_eq(gProcess.exitValue, 42);
do_test_finished();
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess2);
process.init(file);
process.runAsync(["mozilla"], 1, {
observe: function(subject, topic, data) {
process = subject.QueryInterface(Components.interfaces.nsIProcess);
do_check_eq(topic, "process-finished");
do_check_eq(process.exitValue, 0);
test_notify_killed();
}
});
}
// test if we get notified about a killed process
function test_notify_killed()
{
var file = get_test_program("TestBlockingProcess");
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess2);
process.init(file);
process.runAsync([], 0, {
observe: function(subject, topic, data) {
process = subject.QueryInterface(Components.interfaces.nsIProcess);
do_check_eq(topic, "process-finished");
do_test_finished();
}
});
process.kill();
}
function run_test() {
@ -163,6 +190,6 @@ function run_test() {
test_kill();
test_quick();
test_arguments();
if (isWindows)
test_nonblocking();
do_test_pending();
test_notify_blocking();
}

Просмотреть файл

@ -1,28 +1,90 @@
#include "nsIFile.idl"
#include "nsISupports.idl"
[scriptable, uuid(d573f1f3-fcdd-4dbe-980b-4ba79e6718dc)]
interface nsIObserver;
[scriptable, uuid(d573f1f3-fcdd-4dbe-980b-4ba79e6718dc)]
interface nsIProcess : nsISupports
{
void init(in nsIFile executable);
void initWithPid(in unsigned long pid);
void kill();
/**
* Initialises the process with an executable to be run. Call the run method
* to run the executable.
* @param executable The executable to run.
*/
void init(in nsIFile executable);
/** XXX what charset? **/
/** Executes the file this object was initialized with
* @param blocking Whether to wait until the process terminates before returning or not
* @param args An array of arguments to pass to the process
* @param count The length of the args array */
void run(in boolean blocking, [array, size_is(count)] in string args, in unsigned long count);
/**
* Not implemented. Will be removed in a future version of this interface.
*/
void initWithPid(in unsigned long pid);
readonly attribute nsIFile location;
readonly attribute unsigned long pid;
readonly attribute string processName;
readonly attribute unsigned long processSignature;
readonly attribute long exitValue;
readonly attribute boolean isRunning;
/**
* Kills the running process. After exiting the process will either have
* been killed or a failure will have been returned.
*/
void kill();
/**
* Executes the file this object was initialized with
* @param blocking Whether to wait until the process terminates before
returning or not.
* @param args An array of arguments to pass to the process in the
* native character set.
* @param count The length of the args array.
*/
void run(in boolean blocking, [array, size_is(count)] in string args,
in unsigned long count);
/**
* Not implemented. Will be removed in a future version of this interface.
*/
readonly attribute nsIFile location;
/**
* The process identifier of the currently running process. This will only
* be available after the process has started and may not be available on
* some platforms.
*/
readonly attribute unsigned long pid;
/**
* Not implemented. Will be removed in a future version of this interface.
*/
readonly attribute string processName;
/**
* Not implemented. Will be removed in a future version of this interface.
*/
readonly attribute unsigned long processSignature;
/**
* The exit value of the process. This is only valid after the process has
* exited.
*/
readonly attribute long exitValue;
/**
* Returns whether the process is currently running or not.
*/
readonly attribute boolean isRunning;
};
[scriptable, uuid(7d362c71-308e-4724-b1eb-8451fe133026)]
interface nsIProcess2 : nsIProcess
{
/**
* Executes the file this object was initialized with optionally calling
* an observer after the process has finished running.
* @param args An array of arguments to pass to the process in the
* native character set.
* @param count The length of the args array.
* @param observer An observer to notify when the process has completed. It
* will receive this process instance as the subject and
* "process-finished" or "process-failed" as the topic. The
* observer will be notified on the main thread.
* @param holdWeak Whether to use a weak reference to hold the observer.
*/
void runAsync([array, size_is(count)] in string args, in unsigned long count,
[optional] in nsIObserver observer, [optional] in boolean holdWeak);
};
%{C++

Просмотреть файл

@ -46,6 +46,11 @@
#include "nsIProcess.h"
#include "nsIFile.h"
#include "nsIThread.h"
#include "nsIObserver.h"
#include "nsIWeakReference.h"
#include "nsIWeakReferenceUtils.h"
#include "nsIObserver.h"
#include "nsString.h"
#include "prproces.h"
#if defined(PROCESSMODEL_WINAPI)
@ -57,22 +62,38 @@
{0x7b4eeb20, 0xd781, 0x11d4, \
{0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca}}
class nsProcess : public nsIProcess
class nsProcess : public nsIProcess2,
public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIPROCESS
NS_DECL_NSIPROCESS2
NS_DECL_NSIOBSERVER
nsProcess();
private:
~nsProcess();
static void PR_CALLBACK Monitor(void *arg);
void ProcessComplete();
NS_IMETHOD RunProcess(PRBool blocking, const char **args, PRUint32 count,
nsIObserver* observer, PRBool holdWeak);
PRThread* mThread;
PRLock* mLock;
PRBool mShutdown;
nsCOMPtr<nsIFile> mExecutable;
PRInt32 mExitValue;
nsCString mTargetPath;
PRInt32 mPid;
nsCOMPtr<nsIObserver> mObserver;
nsWeakPtr mWeakObserver;
// These members are modified by multiple threads, any accesses should be
// protected with mLock.
PRInt32 mExitValue;
#if defined(PROCESSMODEL_WINAPI)
typedef DWORD (WINAPI*GetProcessIdPtr)(HANDLE process);
HANDLE mProcess;

Просмотреть файл

@ -46,12 +46,16 @@
*/
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsMemory.h"
#include "nsProcess.h"
#include "prtypes.h"
#include "prio.h"
#include "prenv.h"
#include "nsCRT.h"
#include "nsAutoLock.h"
#include "nsThreadUtils.h"
#include "nsIObserverService.h"
#include <stdlib.h>
@ -73,11 +77,19 @@
//-------------------------------------------------------------------//
// nsIProcess implementation
//-------------------------------------------------------------------//
NS_IMPL_ISUPPORTS1(nsProcess, nsIProcess)
NS_IMPL_THREADSAFE_ISUPPORTS3(nsProcess, nsIProcess,
nsIProcess2,
nsIObserver)
//Constructor
nsProcess::nsProcess()
: mExitValue(-1),
: mThread(nsnull),
mLock(PR_NewLock()),
mShutdown(PR_FALSE),
mPid(-1),
mObserver(nsnull),
mWeakObserver(nsnull),
mExitValue(-1),
mProcess(nsnull)
{
}
@ -85,26 +97,14 @@ nsProcess::nsProcess()
//Destructor
nsProcess::~nsProcess()
{
#if defined(PROCESSMODEL_WINAPI)
if (mProcess)
CloseHandle(mProcess);
#else
if (mProcess)
PR_DetachProcess(mProcess);
#endif
PR_DestroyLock(mLock);
}
NS_IMETHODIMP
nsProcess::Init(nsIFile* executable)
{
//Prevent re-initializing if already attached to process
#if defined(PROCESSMODEL_WINAPI)
if (mProcess)
if (mExecutable)
return NS_ERROR_ALREADY_INITIALIZED;
#else
if (mProcess)
return NS_ERROR_ALREADY_INITIALIZED;
#endif
NS_ENSURE_ARG_POINTER(executable);
PRBool isFile;
@ -237,13 +237,118 @@ static int assembleCmdLine(char *const *argv, PRUnichar **wideCmdLine)
}
#endif
void PR_CALLBACK nsProcess::Monitor(void *arg)
{
nsRefPtr<nsProcess> process = dont_AddRef(static_cast<nsProcess*>(arg));
#if defined(PROCESSMODEL_WINAPI)
DWORD dwRetVal;
unsigned long exitCode = -1;
dwRetVal = WaitForSingleObject(process->mProcess, INFINITE);
if (dwRetVal != WAIT_FAILED) {
if (GetExitCodeProcess(process->mProcess, &exitCode) == FALSE)
exitCode = -1;
}
// Lock in case Kill or GetExitCode are called during this
{
nsAutoLock lock(process->mLock);
CloseHandle(process->mProcess);
process->mProcess = NULL;
process->mExitValue = exitCode;
if (process->mShutdown)
return;
}
#else
PRInt32 exitCode = -1;
if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS)
exitCode = -1;
// Lock in case Kill or GetExitCode are called during this
{
nsAutoLock lock(process->mLock);
process->mProcess = nsnull;
process->mExitValue = exitCode;
if (process->mShutdown)
return;
}
#endif
// If we ran a background thread for the monitor then notify on the main
// thread
if (NS_IsMainThread()) {
process->ProcessComplete();
}
else {
nsCOMPtr<nsIRunnable> event = new nsRunnableMethod<nsProcess>(process, &nsProcess::ProcessComplete);
NS_DispatchToMainThread(event);
}
}
void nsProcess::ProcessComplete()
{
if (mThread) {
nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
if (os)
os->RemoveObserver(this, "xpcom-shutdown");
PR_JoinThread(mThread);
mThread = nsnull;
}
char* topic;
if (mExitValue < 0)
topic = "process-failed";
else
topic = "process-finished";
mPid = -1;
nsCOMPtr<nsIObserver> observer;
if (mWeakObserver)
observer = do_QueryReferent(mWeakObserver);
else if (mObserver)
observer = mObserver;
mObserver = nsnull;
mWeakObserver = nsnull;
if (observer)
observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nsnull);
}
// XXXldb |args| has the wrong const-ness
NS_IMETHODIMP
nsProcess::Run(PRBool blocking, const char **args, PRUint32 count)
{
return RunProcess(blocking, args, count, nsnull, PR_FALSE);
}
// XXXldb |args| has the wrong const-ness
NS_IMETHODIMP
nsProcess::RunAsync(const char **args, PRUint32 count,
nsIObserver* observer, PRBool holdWeak)
{
return RunProcess(PR_FALSE, args, count, observer, holdWeak);
}
NS_IMETHODIMP
nsProcess::RunProcess(PRBool blocking, const char **args, PRUint32 count,
nsIObserver* observer, PRBool holdWeak)
{
NS_ENSURE_TRUE(mExecutable, NS_ERROR_NOT_INITIALIZED);
PRStatus status = PR_SUCCESS;
NS_ENSURE_FALSE(mThread, NS_ERROR_ALREADY_INITIALIZED);
if (observer) {
if (holdWeak) {
mWeakObserver = do_GetWeakReference(observer);
if (!mWeakObserver)
return NS_NOINTERFACE;
}
else {
mObserver = observer;
}
}
mExitValue = -1;
mPid = -1;
// make sure that when we allocate we have 1 greater than the
// count since we need to null terminate the list for the argv to
@ -281,6 +386,8 @@ nsProcess::Run(PRBool blocking, const char **args, PRUint32 count)
PRUnichar* wideFile = (PRUnichar *) PR_MALLOC(numChars * sizeof(PRUnichar));
MultiByteToWideChar(CP_ACP, 0, my_argv[0], -1, wideFile, numChars);
nsMemory::Free(my_argv);
SHELLEXECUTEINFOW sinfo;
memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
@ -295,105 +402,72 @@ nsProcess::Run(PRBool blocking, const char **args, PRUint32 count)
sinfo.lpParameters = cmdLine;
retVal = ShellExecuteExW(&sinfo);
if (!retVal) {
return NS_ERROR_FILE_EXECUTION_FAILED;
}
mProcess = sinfo.hProcess;
PR_Free(wideFile);
if (count > 0)
PR_Free( cmdLine );
if (blocking) {
// if success, wait for process termination. the early returns and such
// are a bit ugly but preserving the logic of the nspr code I copied to
// minimize our risk abit.
if ( retVal == TRUE ) {
DWORD dwRetVal;
unsigned long exitCode;
dwRetVal = WaitForSingleObject(mProcess, INFINITE);
if (dwRetVal == WAIT_FAILED) {
nsMemory::Free(my_argv);
return NS_ERROR_FAILURE;
}
if (GetExitCodeProcess(mProcess, &exitCode) == FALSE) {
mExitValue = exitCode;
nsMemory::Free(my_argv);
return NS_ERROR_FAILURE;
}
mExitValue = exitCode;
CloseHandle(mProcess);
mProcess = NULL;
}
else
status = PR_FAILURE;
}
else {
// map return value into success code
if (retVal == TRUE)
status = PR_SUCCESS;
else
status = PR_FAILURE;
HMODULE kernelDLL = ::LoadLibraryW(L"kernel32.dll");
if (kernelDLL) {
GetProcessIdPtr getProcessId = (GetProcessIdPtr)GetProcAddress(kernelDLL, "GetProcessId");
if (getProcessId)
mPid = getProcessId(mProcess);
FreeLibrary(kernelDLL);
}
#else // Note, this must not be an #elif ...!
mProcess = PR_CreateProcess(mTargetPath.get(), my_argv, NULL, NULL);
if (mProcess) {
status = PR_SUCCESS;
if (blocking) {
status = PR_WaitProcess(mProcess, &mExitValue);
mProcess = nsnull;
}
}
nsMemory::Free(my_argv);
if (!mProcess)
return NS_ERROR_FAILURE;
#if not defined WINCE
struct MYProcess {
PRUint32 pid;
};
MYProcess* ptrProc = (MYProcess *) mProcess;
mPid = ptrProc->pid;
#endif
#endif
// free up our argv
nsMemory::Free(my_argv);
NS_ADDREF_THIS();
if (blocking) {
Monitor(this);
if (mExitValue < 0)
return NS_ERROR_FILE_EXECUTION_FAILED;
}
else {
mThread = PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this,
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
PR_JOINABLE_THREAD, 0);
if (!mThread) {
NS_RELEASE_THIS();
return NS_ERROR_FAILURE;
}
if (status != PR_SUCCESS)
return NS_ERROR_FILE_EXECUTION_FAILED;
// It isn't a failure if we just can't watch for shutdown
nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
if (os)
os->AddObserver(this, "xpcom-shutdown", PR_FALSE);
}
return NS_OK;
}
NS_IMETHODIMP nsProcess::GetIsRunning(PRBool *aIsRunning)
{
#if defined(PROCESSMODEL_WINAPI)
if (!mProcess) {
*aIsRunning = PR_FALSE;
return NS_OK;
}
DWORD ec = 0;
BOOL br = GetExitCodeProcess(mProcess, &ec);
if (!br)
return NS_ERROR_FAILURE;
if (ec == STILL_ACTIVE) {
if (mThread)
*aIsRunning = PR_TRUE;
}
else {
else
*aIsRunning = PR_FALSE;
mExitValue = ec;
CloseHandle(mProcess);
mProcess = NULL;
}
return NS_OK;
#elif defined WINCE
return NS_ERROR_NOT_IMPLEMENTED;
#else
if (!mProcess) {
*aIsRunning = PR_FALSE;
return NS_OK;
}
PRUint32 pid;
nsresult rv = GetPid(&pid);
NS_ENSURE_SUCCESS(rv, rv);
if (pid)
*aIsRunning = (kill(pid, 0) != -1) ? PR_TRUE : PR_FALSE;
return NS_OK;
#endif
}
NS_IMETHODIMP nsProcess::InitWithPid(PRUint32 pid)
@ -410,33 +484,12 @@ nsProcess::GetLocation(nsIFile** aLocation)
NS_IMETHODIMP
nsProcess::GetPid(PRUint32 *aPid)
{
#if defined(PROCESSMODEL_WINAPI)
if (!mProcess)
if (!mThread)
return NS_ERROR_FAILURE;
HMODULE kernelDLL = ::LoadLibraryW(L"kernel32.dll");
if (!kernelDLL)
if (mPid < 0)
return NS_ERROR_NOT_IMPLEMENTED;
GetProcessIdPtr getProcessId = (GetProcessIdPtr)GetProcAddress(kernelDLL, "GetProcessId");
if (!getProcessId) {
FreeLibrary(kernelDLL);
return NS_ERROR_NOT_IMPLEMENTED;
}
*aPid = getProcessId(mProcess);
FreeLibrary(kernelDLL);
*aPid = mPid;
return NS_OK;
#elif defined WINCE
return NS_ERROR_NOT_IMPLEMENTED;
#else
if (!mProcess)
return NS_ERROR_FAILURE;
struct MYProcess {
PRUint32 pid;
};
MYProcess* ptrProc = (MYProcess *) mProcess;
*aPid = ptrProc->pid;
return NS_OK;
#endif
}
NS_IMETHODIMP
@ -454,45 +507,57 @@ nsProcess::GetProcessSignature(PRUint32 *aProcessSignature)
NS_IMETHODIMP
nsProcess::Kill()
{
if (!mThread)
return NS_ERROR_FAILURE;
{
nsAutoLock lock(mLock);
#if defined(PROCESSMODEL_WINAPI)
if (!mProcess)
return NS_ERROR_NOT_INITIALIZED;
if ( TerminateProcess(mProcess, NULL) == 0 )
return NS_ERROR_FAILURE;
CloseHandle( mProcess );
mProcess = NULL;
if (TerminateProcess(mProcess, NULL) == 0)
return NS_ERROR_FAILURE;
#else
if (!mProcess)
return NS_ERROR_NOT_INITIALIZED;
if (PR_KillProcess(mProcess) != PR_SUCCESS)
return NS_ERROR_FAILURE;
#endif
}
if (PR_KillProcess(mProcess) != PR_SUCCESS)
return NS_ERROR_FAILURE;
// We must null out mThread if we want IsRunning to return false immediately
// after this call.
nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
if (os)
os->RemoveObserver(this, "xpcom-shutdown");
PR_JoinThread(mThread);
mThread = nsnull;
mProcess = nsnull;
#endif
return NS_OK;
}
NS_IMETHODIMP
nsProcess::GetExitValue(PRInt32 *aExitValue)
{
#if defined(PROCESSMODEL_WINAPI)
if (mProcess) {
DWORD ec = 0;
BOOL br = GetExitCodeProcess(mProcess, &ec);
if (!br)
return NS_ERROR_FAILURE;
// If we have an exit code then the process has ended, clean it up.
if (ec != STILL_ACTIVE) {
mExitValue = ec;
CloseHandle(mProcess);
mProcess = NULL;
}
}
#endif
nsAutoLock lock(mLock);
*aExitValue = mExitValue;
return NS_OK;
}
NS_IMETHODIMP
nsProcess::Observe(nsISupports* subject, const char* topic, const PRUnichar* data)
{
// Shutting down, drop all references
if (mThread) {
nsCOMPtr<nsIObserverService> os = do_GetService("@mozilla.org/observer-service;1");
if (os)
os->RemoveObserver(this, "xpcom-shutdown");
mThread = nsnull;
}
mObserver = nsnull;
mWeakObserver = nsnull;
nsAutoLock lock(mLock);
mShutdown = PR_TRUE;
return NS_OK;
}