Bug 480427: Add a way to run a process in a background threadd. r=bsmedberg
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 25 Mar 2009 08:57:21 +0000
changeset 26584 c352ad4174bb820bbdd71811b4ff3948d12eb768
parent 26583 931671e83e44f7bd1cb1df28b533a87014c03d58
child 26585 94673272aeab8bab601581b74567472e2985484d
push id6123
push userdtownsend@mozilla.com
push dateWed, 25 Mar 2009 08:57:45 +0000
treeherdermozilla-central@c352ad4174bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg
bugs480427
milestone1.9.2a1pre
Bug 480427: Add a way to run a process in a background threadd. r=bsmedberg
xpcom/tests/TestArguments.cpp
xpcom/tests/TestQuickReturn.cpp
xpcom/tests/unit/test_nsIProcess.js
xpcom/threads/nsIProcess.idl
xpcom/threads/nsProcess.h
xpcom/threads/nsProcessCommon.cpp
--- a/xpcom/tests/TestArguments.cpp
+++ b/xpcom/tests/TestArguments.cpp
@@ -1,14 +1,16 @@
 #include <iostream>
 #include <string>
 
 using namespace std;
 
 int main(int argc, char* argv[]) {
+  if (argc != 2)
+      return -1;
 
   string test = "mozilla";
 
   if (test.compare(argv[1]) != 0)
       return -1;
   
   return 0;
 } 
--- a/xpcom/tests/TestQuickReturn.cpp
+++ b/xpcom/tests/TestQuickReturn.cpp
@@ -1,6 +1,8 @@
 #include <stdio.h>
 
-int main () {
-  
+int main (int argc, char* argv[]) {
+  if (argc != 1)
+      return -1;
+
   return 42;
 }
--- a/xpcom/tests/unit/test_nsIProcess.js
+++ b/xpcom/tests/unit/test_nsIProcess.js
@@ -34,22 +34,21 @@
  *
  * ***** END LICENSE BLOCK ***** */
 // nsIProcess unit test
 
 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()
 {
   var envSvc = Components.classes["@mozilla.org/process/environment;1"].
     getService(Components.interfaces.nsIEnvironment);
   var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"].
     getService(Components.interfaces.nsIProperties);
@@ -107,62 +106,90 @@ function test_quick()
   
   // to get an exit value it must be a blocking process
   process.run(true, [], 0);
 
   do_check_eq(process.exitValue, 42);
 }
 
 // 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");
   
   var process = Components.classes["@mozilla.org/process/util;1"]
                           .createInstance(Components.interfaces.nsIProcess);
   process.init(file);
   
   var args= ["mozilla"];
   
   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");
+
+  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();
+    }
+  });
+}
 
-  do_check_eq(gProcess.exitValue, 42);
-  do_test_finished();
+// 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() {
   set_environment();
   test_kill();
   test_quick();
   test_arguments();
-  if (isWindows)
-    test_nonblocking();
+  do_test_pending();
+  test_notify_blocking();
 }
--- a/xpcom/threads/nsIProcess.idl
+++ b/xpcom/threads/nsIProcess.idl
@@ -1,32 +1,94 @@
 #include "nsIFile.idl"
 #include "nsISupports.idl"
 
+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);
+
+  /**
+   * Not implemented. Will be removed in a future version of this interface.
+   */
+  void initWithPid(in unsigned long pid);
+
+  /**
+   * 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;
 
-    /** 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);
+  /**
+   * 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;
 
-	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;
+  /**
+   * 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++
 
 #define NS_PROCESS_CONTRACTID "@mozilla.org/process/util;1"
 #define NS_PROCESS_CLASSNAME "Process Specification"
 %}
--- a/xpcom/threads/nsProcess.h
+++ b/xpcom/threads/nsProcess.h
@@ -41,43 +41,64 @@
 #define _nsPROCESSWIN_H_
 
 #if defined(XP_WIN) && !defined (WINCE) /* wince uses nspr */
 #define PROCESSMODEL_WINAPI
 #endif
 
 #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) 
 #include <windows.h>
 #include <shellapi.h>
 #endif
 
 #define NS_PROCESS_CID \
 {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;
+  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;
-  nsCString mTargetPath;
-
 #if defined(PROCESSMODEL_WINAPI) 
   typedef DWORD (WINAPI*GetProcessIdPtr)(HANDLE process);
   HANDLE mProcess;
 #else
   PRProcess *mProcess;
 #endif
 };
 
--- a/xpcom/threads/nsProcessCommon.cpp
+++ b/xpcom/threads/nsProcessCommon.cpp
@@ -41,22 +41,26 @@
  * 
  * nsProcess is used to execute new processes and specify if you want to
  * wait (blocking) or continue (non-blocking).
  *
  *****************************************************************************
  */
 
 #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>
 
 #if defined(PROCESSMODEL_WINAPI)
 #include "prmem.h"
 #include "nsString.h"
 #include "nsLiteralString.h"
 #include "nsReadableUtils.h"
@@ -68,48 +72,44 @@
 #ifdef WINCE
 #include <windows.h> // for MultiByteToWideChar
 #include "prmem.h"
 #endif
 
 //-------------------------------------------------------------------//
 // 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)
 {
 }
 
 //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;
 
     //First make sure the file exists
     nsresult rv = executable->IsFile(&isFile);
     if (NS_FAILED(rv)) return rv;
     if (!isFile)
@@ -232,23 +232,128 @@ static int assembleCmdLine(char *const *
     PRInt32 numChars = MultiByteToWideChar(CP_ACP, 0, cmdLine, -1, NULL, 0); 
     *wideCmdLine = (PRUnichar *) PR_MALLOC(numChars*sizeof(PRUnichar));
     MultiByteToWideChar(CP_ACP, 0, cmdLine, -1, *wideCmdLine, numChars); 
     PR_Free(cmdLine);
     return 0;
 }
 #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
     // pass into PR_CreateProcess
     char **my_argv = NULL;
     my_argv = (char **)nsMemory::Alloc(sizeof(char *) * (count + 2) );
     if (!my_argv) {
         return NS_ERROR_OUT_OF_MEMORY;
@@ -276,172 +381,120 @@ nsProcess::Run(PRBool blocking, const ch
     /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows
      * from appearing. This makes behavior the same on all platforms. The flag
      * will not have any effect on non-console applications.
      */
     PRInt32 numChars = MultiByteToWideChar(CP_ACP, 0, my_argv[0], -1, NULL, 0); 
     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);
     sinfo.hwnd   = NULL;
     sinfo.lpFile = wideFile;
     sinfo.nShow  = SW_SHOWNORMAL;
     sinfo.fMask  = SEE_MASK_FLAG_DDEWAIT |
                    SEE_MASK_NO_CONSOLE |
                    SEE_MASK_NOCLOSEPROCESS;
 
     if (count > 0)
         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)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsProcess::GetLocation(nsIFile** aLocation)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 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
 nsProcess::GetProcessName(char** aProcessName)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
@@ -449,50 +502,62 @@ NS_IMETHODIMP
 nsProcess::GetProcessSignature(PRUint32 *aProcessSignature)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsProcess::Kill()
 {
-#if defined(PROCESSMODEL_WINAPI)
-    if (!mProcess)
-        return NS_ERROR_NOT_INITIALIZED;
-
-    if ( TerminateProcess(mProcess, NULL) == 0 )
+    if (!mThread)
         return NS_ERROR_FAILURE;
 
-    CloseHandle( mProcess );
-    mProcess = NULL;
+    {
+        nsAutoLock lock(mLock);
+#if defined(PROCESSMODEL_WINAPI)
+        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;
+}