Improve nsIProcess, fix nsIProcess.kill(). b=442393 r=bsmedberg
authorJames Boston <jamesboston@gmail.com>
Fri, 06 Mar 2009 16:19:09 -0600
changeset 25817 a632978b885f1c1ceb80cf6c4a115107b869dc27
parent 25816 3331b6e4bcf737b51997040c13e8cef600296d4a
child 25818 2b9ed1c47f4640984390f517acd2054ad9418bc1
push id5760
push userjosh@mozilla.com
push dateFri, 06 Mar 2009 22:17:23 +0000
treeherdermozilla-central@a632978b885f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg
bugs442393
milestone1.9.2a1pre
Improve nsIProcess, fix nsIProcess.kill(). b=442393 r=bsmedberg
xpcom/tests/Makefile.in
xpcom/tests/TestArguments.cpp
xpcom/tests/TestBlockingProcess.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/Makefile.in
+++ b/xpcom/tests/Makefile.in
@@ -67,16 +67,19 @@ CPPSRCS		= \
 		TestCallTemplates.cpp \
 		TestINIParser.cpp \
 		TestVersionComparator.cpp \
 		TestRacingServiceManager.cpp \
 		TestRegistrationOrder.cpp \
 		TestThreadPoolListener.cpp \
 		TestTimers.cpp \
 		TestOOM.cpp \
+		TestBlockingProcess.cpp \
+		TestQuickReturn.cpp \
+		TestArguments.cpp \
 		$(NULL)
 
 ifndef MOZ_ENABLE_LIBXUL
 CPPSRCS += \
 		TestAtoms.cpp \
 		TestPermanentAtoms.cpp \
 		$(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestArguments.cpp
@@ -0,0 +1,14 @@
+#include <iostream>
+#include <string>
+
+using namespace std;
+
+int main(int argc, char* argv[]) {
+
+  string test = "mozilla";
+
+  if (test.compare(argv[1]) != 0)
+      return -1;
+  
+  return 0;
+} 
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestBlockingProcess.cpp
@@ -0,0 +1,9 @@
+#include <stdio.h>
+ 
+int main()
+{
+   char text[20];
+   fgets(text, sizeof text, stdin);
+   return 0;
+}
+
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/TestQuickReturn.cpp
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main () {
+  
+  return 42;
+}
new file mode 100644
--- /dev/null
+++ b/xpcom/tests/unit/test_nsIProcess.js
@@ -0,0 +1,154 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is XPCOM unit tests.
+ *
+ * The Initial Developer of the Original Code is
+ * James Boston <mozilla@jamesboston.ca>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+// nsIProcess unit test
+
+// get the path to {objdir}/dist/bin
+var bindir = Components.classes["@mozilla.org/file/directory_service;1"]
+.getService(Components.interfaces.nsIProperties)
+.get("CurProcD", Components.interfaces.nsIFile);
+
+// the the os
+var isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes);
+
+var filePrefix = "";
+var fileSuffix = "";
+
+if (isWindows) {
+  filePrefix = bindir.path + "\\";
+  fileSuffix = ".exe";
+} else {
+  filePrefix = bindir.path + "/";
+}
+
+
+// test if a process can be started, polled for its running status
+// and then killed
+function test_kill()
+{
+  var testapp = filePrefix + "TestBlockingProcess" +fileSuffix;
+  print(testapp);
+ 
+  var file = Components.classes["@mozilla.org/file/local;1"]
+                       .createInstance(Components.interfaces.nsILocalFile);
+  file.initWithPath(testapp);
+ 
+  var process = Components.classes["@mozilla.org/process/util;1"]
+                          .createInstance(Components.interfaces.nsIProcess);
+  process.init(file);
+  
+  var pid = process.run(false, [], 0);
+
+  if (!pid) {
+    return false;
+  }
+
+  var rv = process.isRunning;
+
+  if (!rv) {    
+    return false;
+  }
+
+  process.kill();
+
+  rv = process.isRunning;
+
+  if (rv){
+    return false;
+  }
+  return true;
+
+}
+
+// test if we can get an exit value from an application that is
+// guaranteed to return an exit value of 42
+function test_quick()
+{
+  var testapp = filePrefix + "TestQuickReturn" + fileSuffix;
+  
+  var file = Components.classes["@mozilla.org/file/local;1"]
+  .createInstance(Components.interfaces.nsILocalFile);
+  file.initWithPath(testapp);
+  
+  var process = Components.classes["@mozilla.org/process/util;1"]
+  .createInstance(Components.interfaces.nsIProcess);
+  process.init(file);
+  
+  // to get an exit value it must be a blocking process
+  var pid = process.run(true, [], 0);
+  
+  if (!pid) {
+    return false;
+  }
+  
+  if (process.exitValue != 42) {
+    return false;
+  }
+  return true;
+}
+
+// test if an argument can be successfully passed to an application
+// that will return -1 if "mozilla" is not the first argument
+function test_arguments()
+{
+  var testapp = filePrefix + "TestArguments" + fileSuffix;
+  
+  var file = Components.classes["@mozilla.org/file/local;1"]
+  .createInstance(Components.interfaces.nsILocalFile);
+  file.initWithPath(testapp);
+  
+  var process = Components.classes["@mozilla.org/process/util;1"]
+  .createInstance(Components.interfaces.nsIProcess);
+  process.init(file);
+  
+  var args= ["mozilla"];
+  
+  var pid = process.run(true, args, args.length);
+  
+  if (!pid) {
+    return false;
+  }
+    
+  if (process.exitValue) {
+    return false;
+  }
+  return true;
+}
+
+function run_test() {
+  do_check_true(test_kill());
+  do_check_true(test_quick());
+  do_check_true(test_arguments());
+}
--- a/xpcom/threads/nsIProcess.idl
+++ b/xpcom/threads/nsIProcess.idl
@@ -1,12 +1,12 @@
 #include "nsIFile.idl"
 #include "nsISupports.idl"
 
-[scriptable, uuid(9da0b650-d07e-4617-a18a-250035572ac8)]
+[scriptable, uuid(d573f1f3-fcdd-4dbe-980b-4ba79e6718dc)]
 
 interface nsIProcess : nsISupports
 {
 	void init(in nsIFile executable);
 	void initWithPid(in unsigned long pid);
 	
 	void kill();
 
@@ -18,15 +18,16 @@ interface nsIProcess : nsISupports
          * @return the PID of the newly spawned process */
         unsigned long run(in boolean blocking, [array, size_is(count)] in string args, in unsigned long count);
 
 	readonly attribute nsIFile location;
 	readonly attribute unsigned long pid;
 	readonly attribute string processName;
 	readonly attribute unsigned long processSignature;
 	readonly attribute long exitValue;
+	readonly attribute unsigned long isRunning;
 };
 
 %{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
@@ -35,37 +35,47 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef _nsPROCESSWIN_H_
 #define _nsPROCESSWIN_H_
 
+#if defined(XP_WIN) && !defined (WINCE) /* wince uses nspr */
+#define PROCESSMODEL_WINAPI
+#endif
+
 #include "nsIProcess.h"
 #include "nsIFile.h"
 #include "nsString.h"
 #include "prproces.h"
+#if defined(PROCESSMODEL_WINAPI) 
+#include <windows.h>
+#endif
 
 #define NS_PROCESS_CID \
 {0x7b4eeb20, 0xd781, 0x11d4, \
    {0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca}}
 
 class nsProcess : public nsIProcess
 {
 public:
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPROCESS
 
   nsProcess();
 
 private:
-  ~nsProcess() {}
+  ~nsProcess();
 
   nsCOMPtr<nsIFile> mExecutable;
   PRInt32 mExitValue;
   nsCString mTargetPath;
   PRProcess *mProcess;
 
+#if defined(PROCESSMODEL_WINAPI) 
+  PROCESS_INFORMATION procInfo;
+#endif
 };
 
 #endif
--- a/xpcom/threads/nsProcessCommon.cpp
+++ b/xpcom/threads/nsProcessCommon.cpp
@@ -56,38 +56,62 @@
 #include <stdlib.h>
 
 #if defined(XP_WIN)
 #include "prmem.h"
 #include "nsString.h"
 #include "nsLiteralString.h"
 #include "nsReadableUtils.h"
 #include <windows.h>
-#endif
-
-#if defined(XP_MACOSX)
-#include <Processes.h>
-#include "nsILocalFileMac.h"
+#else
+#include <sys/types.h>
+#include <signal.h>
 #endif
 
 //-------------------------------------------------------------------//
 // nsIProcess implementation
 //-------------------------------------------------------------------//
 NS_IMPL_ISUPPORTS1(nsProcess, nsIProcess)
 
 //Constructor
 nsProcess::nsProcess()
     : mExitValue(-1),
       mProcess(nsnull)
 {
+#if defined(PROCESSMODEL_WINAPI)
+    procInfo.dwProcessId = nsnull;
+#endif
+}
+
+//Destructor
+nsProcess::~nsProcess()
+{
+#if defined(PROCESSMODEL_WINAPI)
+    if (procInfo.dwProcessId) {
+        CloseHandle( procInfo.hProcess );
+        CloseHandle( procInfo.hThread );
+    }
+#else
+    if (mProcess) 
+        PR_DetachProcess(mProcess);
+#endif
 }
 
 NS_IMETHODIMP
 nsProcess::Init(nsIFile* executable)
 {
+    //Prevent re-initializing if already attached to process
+#if defined(PROCESSMODEL_WINAPI)
+    if (procInfo.dwProcessId)
+        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)
         return NS_ERROR_FAILURE;
@@ -236,19 +260,18 @@ nsProcess::Run(PRBool blocking, const ch
     for (i=0; i < count; i++) {
         my_argv[i+1] = const_cast<char*>(args[i]);
     }
     // we need to set argv[0] to the program name.
     my_argv[0] = mTargetPath.BeginWriting();
     // null terminate the array
     my_argv[count+1] = NULL;
 
-#if defined(XP_WIN) && !defined (WINCE) /* wince uses nspr */
+#if defined(PROCESSMODEL_WINAPI)
     STARTUPINFOW startupInfo;
-    PROCESS_INFORMATION procInfo;
     BOOL retVal;
     PRUnichar *cmdLine;
 
     if (assembleCmdLine(my_argv, &cmdLine) == -1) {
         nsMemory::Free(my_argv);
         return NS_ERROR_FILE_EXECUTION_FAILED;    
     }
 
@@ -270,16 +293,17 @@ nsProcess::Run(PRBool blocking, const ch
                                     * thread in the new process */
                             FALSE,  /* inherit handles */
                             CREATE_NO_WINDOW, /* creation flags */
                             NULL,  /* env */
                             NULL,  /* current drive and directory */
                             &startupInfo,
                             &procInfo
                            );
+    *pid = procInfo.dwProcessId;
     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 ) {
@@ -294,125 +318,108 @@ nsProcess::Run(PRBool blocking, const ch
             if (GetExitCodeProcess(procInfo.hProcess, &exitCode) == FALSE) {
                 mExitValue = exitCode;
                 nsMemory::Free(my_argv);
                return NS_ERROR_FAILURE;
             }
             mExitValue = exitCode;
             CloseHandle(procInfo.hProcess);
             CloseHandle(procInfo.hThread);
+            procInfo.dwProcessId = nsnull;
         }
         else
             status = PR_FAILURE;
     } 
     else {
 
         // map return value into success code
 
         if (retVal == TRUE) 
             status = PR_SUCCESS;
         else
             status = PR_FAILURE;
     }
 
 #else // Note, this must not be an #elif ...!
-
-#if defined(XP_MACOSX)
-    if (count == 0) {
-        FSSpec resolvedSpec;
-        OSErr err = noErr;
-
-        nsCOMPtr<nsILocalFileMac> macExecutable =
-            do_QueryInterface(mExecutable);
-        macExecutable->GetFSSpec(&resolvedSpec);
-
-        LaunchParamBlockRec launchPB;
-        launchPB.launchAppSpec = &resolvedSpec;
-        launchPB.launchAppParameters = NULL;
-        launchPB.launchBlockID = extendedBlock;
-        launchPB.launchEPBLength = extendedBlockLen;
-        launchPB.launchFileFlags = NULL;
-        launchPB.launchControlFlags =
-            launchContinue + launchNoFileFlags + launchUseMinimum;
-        if (!blocking)
-            launchPB.launchControlFlags += launchDontSwitch;
-
-        err = LaunchApplication(&launchPB);
-
-        // NOTE: blocking mode assumes you are running on a thread
-        //       other than the UI thread that has the main event loop
-        if (blocking && err == noErr) {
-            while (1) {
-                ProcessInfoRec info;
-                info.processInfoLength = sizeof(ProcessInfoRec);
-                info.processName = NULL;
-                info.processAppSpec = NULL;
-
-                err = GetProcessInformation(&launchPB.launchProcessSN, &info);
-
-                if (err != noErr) {
-                    // The process is no longer in the process
-                    // manager's internal list, assume the process is
-                    // done.
-                    err = noErr;
-
-                    break;
-                }
-
-                // still running so sleep some more (200 msecs)
-                PR_Sleep(200);
-            }
-        }
-
-        if (err != noErr) {
-            status = PR_FAILURE;
-        }
-
+    
+    mProcess = PR_CreateProcess(mTargetPath.get(), my_argv, NULL, NULL);
+    if (mProcess) {
+        GetPid(pid);
+        status = PR_SUCCESS;
         if (blocking) {
-            mExitValue = err;
-        }
-    } else
-#endif
-    {
-        if (blocking) {
-            mProcess = PR_CreateProcess(mTargetPath.get(), my_argv, NULL,
-                                        NULL);
-            if (mProcess)
-                status = PR_WaitProcess(mProcess, &mExitValue);
-        } else {
-            status = PR_CreateProcessDetached(mTargetPath.get(), my_argv, NULL,
-                                          NULL);
-        }
+            status = PR_WaitProcess(mProcess, &mExitValue);
+            mProcess = nsnull;
+        } 
     }
 #endif
 
     // free up our argv
     nsMemory::Free(my_argv);
 
     if (status != PR_SUCCESS)
         return NS_ERROR_FILE_EXECUTION_FAILED;
 
     return NS_OK;
 }
 
+NS_IMETHODIMP nsProcess::GetIsRunning(PRUint32 *aIsRunning)
+{
+#if defined(PROCESSMODEL_WINAPI)
+    DWORD ec = 0;
+    BOOL br = GetExitCodeProcess(procInfo.hProcess, &ec);
+    if (!br) {
+        aIsRunning = 0;
+        return NS_OK;
+    }
+    *aIsRunning = (ec == STILL_ACTIVE ? 1 : 0); 
+    return NS_OK;
+#elif defined WINCE
+    return NS_ERROR_NOT_IMPLEMENTED;   
+#else
+    PRUint32 pid;
+    GetPid(&pid);
+    if (pid)
+        *aIsRunning = (kill(pid, 0) != -1) ? 1 : 0;
+    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 (!procInfo.dwProcessId)
+        return NS_ERROR_FAILURE;
+    *aPid = procInfo.dwProcessId;
+    return NS_OK;
+#elif defined WINCE
     return NS_ERROR_NOT_IMPLEMENTED;
+#else
+    if (!mProcess) {
+        *aPid = 0;
+        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;
 }
 
@@ -421,19 +428,30 @@ nsProcess::GetProcessSignature(PRUint32 
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 nsProcess::Kill()
 {
     nsresult rv = NS_OK;
+#if defined(PROCESSMODEL_WINAPI)
+    if ( TerminateProcess(procInfo.hProcess, NULL) == 0 ) {
+        rv = NS_ERROR_FAILURE;
+    }
+    else {
+        CloseHandle( procInfo.hProcess );
+        procInfo.dwProcessId = nsnull;
+    }
+#else
     if (mProcess)
         rv = PR_KillProcess(mProcess) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE;
-    
+    if (rv == NS_OK)
+        mProcess = nsnull;
+#endif  
     return rv;
 }
 
 NS_IMETHODIMP
 nsProcess::GetExitValue(PRInt32 *aExitValue)
 {
     *aExitValue = mExitValue;