Browse Source

one_process support. Some issues.

David Rose 16 years ago
parent
commit
4388b853e5

+ 5 - 3
direct/src/p3d/Packager.py

@@ -2185,10 +2185,12 @@ class Packager:
         # This is the main program that drives the runtime Python.  It
         # is responsible for loading _vfsimporter.pyd, and then
         # importing direct.p3d.AppRunner, to start an application
-        # running.  Note that the .exe extension is automatically
-        # replaced with the platform-specific extension appropriate
-        # for an executable.
+        # running.  The program comes in two parts: an executable, and
+        # an associated dynamic library.  Note that the .exe and .dll
+        # extensions are automatically replaced with the appropriate
+        # platform-specific extensions.
         self.do_file('p3dpython.exe')
+        self.do_file('libp3dpython.dll')
 
     def do_freeze(self, filename, compileToExe = False):
         """ Freezes all of the current Python code into either an

+ 17 - 3
direct/src/plugin/Sources.pp

@@ -18,6 +18,7 @@
     find_root_dir.cxx find_root_dir.h \
     get_tinyxml.h \
     binaryXml.cxx binaryXml.h \
+    fhandle.h \
     handleStream.cxx handleStream.h handleStream.I \
     handleStreamBuf.cxx handleStreamBuf.h handleStreamBuf.I \
     mkdir_complete.cxx mkdir_complete.h \
@@ -51,7 +52,8 @@
     p3dUndefinedObject.h \
     p3dWinSplashWindow.h p3dWinSplashWindow.I \
     p3dX11SplashWindow.h \
-    p3dWindowParams.h p3dWindowParams.I
+    p3dWindowParams.h p3dWindowParams.I \
+    run_p3dpython.h
 
   #define INCLUDED_SOURCES \
     p3d_plugin.cxx \
@@ -91,7 +93,7 @@
 
 #end lib_target
 
-#begin bin_target
+#begin lib_target
   #define BUILD_TARGET $[HAVE_PYTHON]
   #define USE_PACKAGES tinyxml python
   #define TARGET p3dpython
@@ -105,19 +107,31 @@
 
   #define SOURCES \
     binaryXml.cxx binaryXml.h \
+    fhandle.h \
     handleStream.cxx handleStream.h handleStream.I \
     handleStreamBuf.cxx handleStreamBuf.h handleStreamBuf.I \
     p3d_lock.h p3d_plugin.h \
     p3d_plugin_config.h \
     p3dCInstance.cxx \
     p3dCInstance.h p3dCInstance.I \
-    p3dPythonRun.cxx p3dPythonRun.h p3dPythonRun.I
+    p3dPythonRun.cxx p3dPythonRun.h p3dPythonRun.I \
+    run_p3dpython.h
 
   #define WIN_SYS_LIBS user32.lib
 
   // If you have to link with a static Python library, define it here.
   #define EXTRA_LIBS $[EXTRA_P3DPYTHON_LIBS]
 
+#end lib_target
+
+#begin bin_target
+  #define BUILD_TARGET $[HAVE_PYTHON]
+  #define TARGET p3dpython
+
+  #define SOURCES \
+    fhandle.h \
+    p3dPythonMain.cxx \
+    run_p3dpython.h
 #end bin_target
 
 #begin static_lib_target

+ 29 - 0
direct/src/plugin/fhandle.h

@@ -0,0 +1,29 @@
+// Filename: fhandle.h
+// Created by:  drose (29Aug09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef FHANDLE_H
+#define FHANDLE_H
+
+// This header file simply defines the FHandle type, which is used to
+// pass around a handle to an open file object.
+
+#ifdef _WIN32
+#include <windows.h>
+typedef HANDLE FHandle;
+#else
+// On POSIX, we use a file descriptor as a "handle".
+typedef int FHandle;
+#endif
+
+#endif

+ 1 - 8
direct/src/plugin/handleStreamBuf.h

@@ -15,14 +15,7 @@
 #ifndef HANDLESTREAMBUF_H
 #define HANDLESTREAMBUF_H
 
-#ifdef _WIN32
-#include <windows.h>
-typedef HANDLE FHandle;
-#else
-// On POSIX, we use a file descriptor as a "handle".
-typedef int FHandle;
-#endif
-
+#include "fhandle.h"
 #include <iostream>
 
 using namespace std;

+ 36 - 41
direct/src/plugin/p3dPythonRun.cxx

@@ -29,28 +29,27 @@ P3DPythonRun *P3DPythonRun::_global_ptr = NULL;
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 P3DPythonRun::
-P3DPythonRun(int argc, char *argv[]) {
+P3DPythonRun(const char *program_name, const char *archive_file,
+              FHandle input, FHandle output) {
   _read_thread_continue = false;
   _program_continue = true;
+  _session_terminated = false;
   INIT_LOCK(_commands_lock);
   INIT_THREAD(_read_thread);
 
   _session_id = 0;
   _next_sent_id = 0;
 
-  if (argc >= 1) {
-    _program_name = argv[0];
+  if (program_name != NULL) {
+    _program_name = program_name;
   }
-  if (argc >= 2) {
-    _archive_file = Filename::from_os_specific(argv[1]);
-  }
-  if (_archive_file.empty()) {
-    nout << "No archive filename specified on command line.\n";
-    exit(1);
+  if (archive_file != NULL) {
+    _archive_file = Filename::from_os_specific(archive_file);
   }
 
   _py_argc = 1;
   _py_argv = (char **)malloc(2 * sizeof(char *));
+
   _py_argv[0] = (char *)_program_name.c_str();
   _py_argv[1] = NULL;
 
@@ -66,28 +65,15 @@ P3DPythonRun(int argc, char *argv[]) {
 
   // Initialize Python.  It appears to be important to do this before
   // we open the pipe streams and spawn the thread, below.
+  PyEval_InitThreads();
   Py_SetProgramName((char *)_program_name.c_str());
   Py_Initialize();
   PySys_SetArgv(_py_argc, _py_argv);
 
   // Open the pipe streams with the input and output handles from the
   // parent.
-#ifdef _WIN32
-  HANDLE read = GetStdHandle(STD_INPUT_HANDLE);
-  HANDLE write = GetStdHandle(STD_OUTPUT_HANDLE);
-  if (!SetStdHandle(STD_INPUT_HANDLE, INVALID_HANDLE_VALUE)) {
-    nout << "unable to reset input handle\n";
-  }
-  if (!SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE)) {
-    nout << "unable to reset input handle\n";
-  }
-
-  _pipe_read.open_read(read);
-  _pipe_write.open_write(write);
-#else
-  _pipe_read.open_read(STDIN_FILENO);
-  _pipe_write.open_write(STDOUT_FILENO);
-#endif  // _WIN32
+  _pipe_read.open_read(input);
+  _pipe_write.open_write(output);
 
   if (!_pipe_read) {
     nout << "unable to open read pipe\n";
@@ -125,7 +111,6 @@ run_python() {
   // "_d", so that the Panda DLL preloader can import the correct
   // filenames.
   PyRun_SimpleString("import sys; sys.dll_suffix = '_d'");
-  
 #endif
 
   // We'll need libpandaexpress to be imported before we can load
@@ -696,6 +681,9 @@ check_comm() {
     _commands.pop_front();
     RELEASE_LOCK(_commands_lock);
     handle_command(doc);
+    if (_session_terminated) {
+      return;
+    }
     ACQUIRE_LOCK(_commands_lock);
   }
   RELEASE_LOCK(_commands_lock);
@@ -704,6 +692,7 @@ check_comm() {
     // The low-level thread detected an error, for instance pipe
     // closed.  We should exit gracefully.
     terminate_session();
+    return;
   }
 
   // Sleep to yield the timeslice, but only if we're not running in
@@ -758,6 +747,9 @@ wait_script_response(int response_id) {
           _commands.erase(ci);
           RELEASE_LOCK(_commands_lock);
           handle_command(doc);
+          if (_session_terminated) {
+            return NULL;
+          }
           ACQUIRE_LOCK(_commands_lock);
           break;
         }
@@ -791,6 +783,7 @@ wait_script_response(int response_id) {
 
     if (!_program_continue) {
       terminate_session();
+      return NULL;
     }
 
 #ifdef _WIN32
@@ -839,6 +832,9 @@ py_request_func(PyObject *args) {
     }
 
     TiXmlDocument *doc = wait_script_response(response_id);
+    if (_session_terminated) {
+      return Py_BuildValue("");
+    }
     assert(doc != NULL);
     TiXmlElement *xcommand = doc->FirstChildElement("command");
     assert(xcommand != NULL);
@@ -1230,10 +1226,7 @@ terminate_session() {
   }
   Py_DECREF(result);
 
-  // The task manager is cleaned up.  Let's exit immediately here,
-  // rather than returning all the way up.  This just makes it easier
-  // when we call terminate_session() from a deeply-nested loop.
-  exit(0);
+  _session_terminated = true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1561,16 +1554,18 @@ rt_thread_run() {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: main
-//  Description: Starts the program running.
+//     Function: run_p3dpython
+//  Description: This externally-visible function is the main entry
+//               point to this DLL, and it starts the whole thing
+//               running.  Returns true on success, false on failure.
 ////////////////////////////////////////////////////////////////////
-int
-main(int argc, char *argv[]) {
-  P3DPythonRun::_global_ptr = new P3DPythonRun(argc, argv);
-  
-  if (!P3DPythonRun::_global_ptr->run_python()) {
-    nout << "Couldn't initialize Python.\n";
-    return 1;
-  }
-  return 0;
+bool
+run_p3dpython(const char *program_name, const char *archive_file,
+              FHandle input, FHandle output) {
+  P3DPythonRun::_global_ptr = 
+    new P3DPythonRun(program_name, archive_file, input, output);
+  bool result = P3DPythonRun::_global_ptr->run_python();
+  delete P3DPythonRun::_global_ptr;
+  P3DPythonRun::_global_ptr = NULL;
+  return result;
 }

+ 4 - 1
direct/src/plugin/p3dPythonRun.h

@@ -22,6 +22,7 @@
 #include <windows.h>
 #endif
 
+#include "run_p3dpython.h"
 #include "p3d_lock.h"
 #include "handleStream.h"
 #include "p3dCInstance.h"
@@ -65,7 +66,8 @@ using namespace std;
 ////////////////////////////////////////////////////////////////////
 class P3DPythonRun {
 public:
-  P3DPythonRun(int argc, char *argv[]);
+  P3DPythonRun(const char *program_name, const char *archive_file,
+               FHandle input, FHandle output);
   ~P3DPythonRun();
 
   bool run_python();
@@ -153,6 +155,7 @@ private:
 
   bool _read_thread_continue;
   bool _program_continue;
+  bool _session_terminated;
   THREAD _read_thread;
 
 public:

+ 327 - 203
direct/src/plugin/p3dSession.cxx

@@ -26,6 +26,7 @@
 #include "p3dConcreteStruct.h"
 #include "binaryXml.h"
 #include "mkdir_complete.h"
+#include "run_p3dpython.h"
 
 #include <ctype.h>
 
@@ -34,6 +35,7 @@
 #include <sys/wait.h>
 #include <sys/select.h>
 #include <signal.h>
+#include <dlfcn.h>
 #endif
 
 ////////////////////////////////////////////////////////////////////
@@ -53,6 +55,7 @@ P3DSession(P3DInstance *inst) {
   _python_version = inst->get_python_version();
 
   _start_dir = inst_mgr->get_root_dir() + "/start";
+  _p3dpython_one_process = false;
   _p3dpython_started = false;
   _p3dpython_running = false;
 
@@ -100,67 +103,86 @@ shutdown() {
 
     static const int max_wait_ms = 2000;
 
-#ifdef _WIN32
-    // Now give the process a chance to terminate itself cleanly.
-    if (WaitForSingleObject(_p3dpython_handle, max_wait_ms) == WAIT_TIMEOUT) {
-      // It didn't shut down cleanly, so kill it the hard way.
-      nout << "Force-killing python process.\n";
-      TerminateProcess(_p3dpython_handle, 2);
-    }
+    if (_p3dpython_one_process) {
+      // Since it's running in a thread, we can't reliably force-kill
+      // it.  So, just wait.
+      nout << "Waiting for Python thread to exit\n";
+      JOIN_THREAD(_p3dpython_thread);
+      nout << "Done waiting.\n";
+      _p3dpython_one_process = false;
 
-    CloseHandle(_p3dpython_handle);
-#else  // _WIN32
+    } else {
+      // Python's running in a sub-process, the preferred way.  In
+      // this case, we can wait a brief amount of time before it
+      // closes itself; but if it doesn't, we can safely force-kill
+      // it.
 
-    // Wait for a certain amount of time for the process to stop by
-    // itself.
-    struct timeval start;
-    gettimeofday(&start, NULL);
-    int start_ms = start.tv_sec * 1000 + start.tv_usec / 1000;
-    
-    int status;
-    pid_t result = waitpid(_p3dpython_pid, &status, WNOHANG);
-    while (result != _p3dpython_pid) {
-      if (result == -1) {
-        perror("waitpid");
-        break;
+#ifdef _WIN32
+      // Wait for a certain amount of time for the process to stop by
+      // itself.
+      if (WaitForSingleObject(_p3dpython_handle, max_wait_ms) == WAIT_TIMEOUT) {
+        // It didn't shut down cleanly, so kill it the hard way.
+        nout << "Force-killing python process.\n";
+        TerminateProcess(_p3dpython_handle, 2);
       }
+      
+      CloseHandle(_p3dpython_handle);
 
-      struct timeval now;
-      gettimeofday(&now, NULL);
-      int now_ms = now.tv_sec * 1000 + now.tv_usec / 1000;
-      int elapsed = now_ms - start_ms;
-
-      if (elapsed > max_wait_ms) {
-        // Tired of waiting.  Kill the process.
-        nout << "Force-killing python process, pid " << _p3dpython_pid 
-             << "\n";
-        kill(_p3dpython_pid, SIGKILL);
-        start_ms = now_ms;
+#else  // _WIN32
+      // Wait for a certain amount of time for the process to stop by
+      // itself.
+      struct timeval start;
+      gettimeofday(&start, NULL);
+      int start_ms = start.tv_sec * 1000 + start.tv_usec / 1000;
+      
+      int status;
+      pid_t result = waitpid(_p3dpython_pid, &status, WNOHANG);
+      while (result != _p3dpython_pid) {
+        if (result == -1) {
+          perror("waitpid");
+          break;
+        }
+        
+        struct timeval now;
+        gettimeofday(&now, NULL);
+        int now_ms = now.tv_sec * 1000 + now.tv_usec / 1000;
+        int elapsed = now_ms - start_ms;
+        
+        if (elapsed > max_wait_ms) {
+          // Tired of waiting.  Kill the process.
+          nout << "Force-killing python process, pid " << _p3dpython_pid 
+               << "\n";
+          kill(_p3dpython_pid, SIGKILL);
+          start_ms = now_ms;
+        }
+        
+        // Yield the timeslice and wait some more.
+        struct timeval tv;
+        tv.tv_sec = 0;
+        tv.tv_usec = 1;
+        select(0, NULL, NULL, NULL, &tv);
+        result = waitpid(_p3dpython_pid, &status, WNOHANG);
+      }
+      
+      nout << "Python process has successfully stopped.\n";
+      if (WIFEXITED(status)) {
+        nout << "  exited normally, status = "
+             << WEXITSTATUS(status) << "\n";
+      } else if (WIFSIGNALED(status)) {
+        nout << "  signalled by " << WTERMSIG(status) << ", core = " 
+             << WCOREDUMP(status) << "\n";
+      } else if (WIFSTOPPED(status)) {
+        nout << "  stopped by " << WSTOPSIG(status) << "\n";
       }
       
-      // Yield the timeslice and wait some more.
-      struct timeval tv;
-      tv.tv_sec = 0;
-      tv.tv_usec = 1;
-      select(0, NULL, NULL, NULL, &tv);
-      result = waitpid(_p3dpython_pid, &status, WNOHANG);
-    }
-
-    nout << "Python process has successfully stopped.\n";
-    if (WIFEXITED(status)) {
-      nout << "  exited normally, status = "
-           << WEXITSTATUS(status) << "\n";
-    } else if (WIFSIGNALED(status)) {
-      nout << "  signalled by " << WTERMSIG(status) << ", core = " 
-           << WCOREDUMP(status) << "\n";
-    } else if (WIFSTOPPED(status)) {
-      nout << "  stopped by " << WSTOPSIG(status) << "\n";
-    }
-
 #endif  // _WIN32
+    }
 
     _p3dpython_running = false;
     _p3dpython_started = false;
+
+    // Close the pipe now.
+    _pipe_read.close();
   }
 
   // If there are any leftover commands in the queue (presumably
@@ -657,10 +679,9 @@ start_p3dpython(P3DInstance *inst) {
   // Change the current directory to the standard start directory, but
   // only if the runtime environment told us the original current
   // directory isn't meaningful.
-  string start_dir;
-  if (!inst_mgr->get_keep_cwd()) {
-    start_dir = _start_dir;
-    mkdir_complete(start_dir, nout);
+  _use_start_dir = !inst_mgr->get_keep_cwd();
+  if (_use_start_dir) {
+    mkdir_complete(_start_dir, nout);
   }
 
 #ifdef _WIN32
@@ -725,16 +746,30 @@ start_p3dpython(P3DInstance *inst) {
          << "PRC_PATH set to: " << prc_path << "\n";
   }
 
-  string p3dpython = P3D_PLUGIN_P3DPYTHON;
-  if (p3dpython.empty()) {
-    p3dpython = _python_root_dir + "/p3dpython";
+  // Get the name of the executable and dynamic library to run.
+  // Ideally, we'll run the executable successfully, in a sub-process;
+  // this will in turn load and run the dynamic library.  If that
+  // fails for some reason, we can fall back to loading and running
+  // the library directly.
+  _p3dpython_exe = _python_root_dir + "/p3dpython";
 #ifdef _WIN32
-    p3dpython += ".exe";
+  _p3dpython_exe += ".exe";
+#endif
+
+  _p3dpython_dll = P3D_PLUGIN_P3DPYTHON;
+  if (_p3dpython_dll.empty()) {
+    _p3dpython_dll = _python_root_dir + "/libp3dpython";
+#ifdef _WIN32
+    _p3dpython_dll += ".dll";
+#elif defined(__APPLE__)
+    _p3dpython_dll += ".dylib";
+#else
+    _p3dpython_dll += ".so";
 #endif
   }
 
   // Populate the new process' environment.
-  string env;
+  _env = string();
 
   // These are the enviroment variables we forward from the current
   // environment, if they are set.
@@ -748,41 +783,41 @@ start_p3dpython(P3DInstance *inst) {
   for (int ki = 0; keep[ki] != NULL; ++ki) {
     char *value = getenv(keep[ki]);
     if (value != NULL) {
-      env += keep[ki];
-      env += "=";
-      env += value;
-      env += '\0';
+      _env += keep[ki];
+      _env += "=";
+      _env += value;
+      _env += '\0';
     }
   }
 
   // Define some new environment variables.
-  env += "PATH=";
-  env += search_path;
-  env += '\0';
+  _env += "PATH=";
+  _env += search_path;
+  _env += '\0';
 
-  env += "LD_LIBRARY_PATH=";
-  env += search_path;
-  env += '\0';
+  _env += "LD_LIBRARY_PATH=";
+  _env += search_path;
+  _env += '\0';
 
-  env += "DYLD_LIBRARY_PATH=";
-  env += search_path;
-  env += '\0';
+  _env += "DYLD_LIBRARY_PATH=";
+  _env += search_path;
+  _env += '\0';
 
-  env += "PYTHONPATH=";
-  env += python_path;
-  env += '\0';
+  _env += "PYTHONPATH=";
+  _env += python_path;
+  _env += '\0';
 
-  env += "PYTHONHOME=";
-  env += _python_root_dir;
-  env += '\0';
+  _env += "PYTHONHOME=";
+  _env += _python_root_dir;
+  _env += '\0';
 
-  env += "PRC_PATH=";
-  env += prc_path;
-  env += '\0';
+  _env += "PRC_PATH=";
+  _env += prc_path;
+  _env += '\0';
 
-  env += "PANDA_PRC_PATH=";
-  env += prc_path;
-  env += '\0';
+  _env += "PANDA_PRC_PATH=";
+  _env += prc_path;
+  _env += '\0';
     
   // Define each package's root directory in an environment variable
   // named after the package, for the convenience of the packages in
@@ -793,11 +828,11 @@ start_p3dpython(P3DInstance *inst) {
     for (string::const_iterator si = package_name.begin();
          si != package_name.end();
          ++si) {
-      env += toupper(*si);
+      _env += toupper(*si);
     }
-    env += string("_ROOT=");
-    env += package->get_package_dir();
-    env += '\0';
+    _env += string("_ROOT=");
+    _env += package->get_package_dir();
+    _env += '\0';
   }
 
   // Get the log filename from the p3d_info.xml file.
@@ -809,6 +844,7 @@ start_p3dpython(P3DInstance *inst) {
   }
 
   bool console_output = (inst->get_fparams().lookup_token_int("console_output") != 0);
+  bool one_process = (inst->get_fparams().lookup_token_int("one_process") != 0);
 
 #ifdef P3D_PLUGIN_LOG_BASENAME3
   if (log_basename.empty()) {
@@ -840,24 +876,82 @@ start_p3dpython(P3DInstance *inst) {
     _log_pathname += ".log";
   }
 
-  string archive_file = inst->_panda3d->get_archive_file_pathname();
+  // Create the pipes for communication.
+#ifdef _WIN32
+  // Create a bi-directional pipe to communicate with the sub-process.
+  HANDLE r_to, w_to, r_from, w_from;
 
-  nout << "Attempting to start python from " << p3dpython << "\n";
+  // Create the pipe to the process.
+  if (!CreatePipe(&r_to, &w_to, NULL, 0)) {
+    nout << "failed to create pipe\n";
+  } else {
+    // Make sure the right end of the pipe is inheritable.
+    SetHandleInformation(r_to, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
+    SetHandleInformation(w_to, HANDLE_FLAG_INHERIT, 0);
+  }
+
+  // Create the pipe from the process.
+  if (!CreatePipe(&r_from, &w_from, NULL, 0)) {
+    nout << "failed to create pipe\n";
+  } else { 
+    // Make sure the right end of the pipe is inheritable.
+    SetHandleInformation(w_from, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
+    SetHandleInformation(r_from, HANDLE_FLAG_INHERIT, 0);
+  }
+
+  _output = w_from;
+  _input = r_to;
+  _pipe_read.open_read(r_from);
+  _pipe_write.open_write(w_to);
+
+#else
+  // Create a bi-directional pipe to communicate with the sub-process.
+  int to_fd[2];
+  if (pipe(to_fd) < 0) {
+    perror("failed to create pipe");
+  }
+  int from_fd[2];
+  if (pipe(from_fd) < 0) {
+    perror("failed to create pipe");
+  }
+
+  _input = to_fd[0];
+  _output = from_fd[1];
+  _pipe_read.open_read(from_fd[0]);
+  _pipe_write.open_write(to_fd[1]);
+#endif // _WIN32
+
+  // Get the filename to the Panda3D multifile.  We need to pass this
+  // to p3dpython.
+  _mf_filename = inst->_panda3d->get_archive_file_pathname();
+
+  bool started_p3dpython;
+  if (one_process) {
+    nout << "one_process is set; running Python within parent process.\n";
+    started_p3dpython = false;
+  } else {
+    nout << "Attempting to start python from " << _p3dpython_exe 
+         << " and " << _p3dpython_dll << "\n";
 #ifdef _WIN32
-  _p3dpython_handle = win_create_process
-    (p3dpython, archive_file, start_dir, env, _log_pathname,
-     _pipe_read, _pipe_write);
-  bool started_p3dpython = (_p3dpython_handle != INVALID_HANDLE_VALUE);
+    _p3dpython_handle = win_create_process();
+    started_p3dpython = (_p3dpython_handle != INVALID_HANDLE_VALUE);
 #else
-  _p3dpython_pid = posix_create_process
-    (p3dpython, archive_file, start_dir, env, _log_pathname,
-     _pipe_read, _pipe_write);
-  bool started_p3dpython = (_p3dpython_pid > 0);
+    _p3dpython_pid = posix_create_process();
+    started_p3dpython = (_p3dpython_pid > 0);
 #endif
+    if (!started_p3dpython) {
+      nout << "Failed to create process.\n";
+    }
+  }
 
   if (!started_p3dpython) {
-    nout << "Failed to create process.\n";
-    return;
+    // Well, we couldn't run python in a sub-process, for some reason.
+    // Fall back to running it in a sub-thread within the same
+    // process.  This isn't nearly as good, but I guess it's better
+    // than nothing.
+    INIT_THREAD(_p3dpython_thread);
+    SPAWN_THREAD(_p3dpython_thread, p3dpython_thread_run, this);
+    _p3dpython_one_process = true;
   }
   _p3dpython_started = true;
   _p3dpython_running = true;
@@ -1019,60 +1113,37 @@ rt_terminate() {
 #ifdef _WIN32
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DSession::win_create_process
-//       Access: Private, Static
-//  Description: Creates a sub-process to run the named program
-//               executable, with the indicated environment string.
-//               Standard error is logged to log_pathname, if that
-//               string is nonempty.
+//       Access: Private
+//  Description: Creates a sub-process to run _p3dpython_exe, with
+//               the appropriate command-line arguments, and the
+//               environment string defined in _env.  Standard error
+//               is logged to _log_pathname, if that string is
+//               nonempty.
 //
-//               Opens the two HandleStreams as the read and write
-//               pipes to the child process's standard output and
-//               standard input, respectively.
+//               Opens the two HandleStreams _pipe_read and
+//               _pipe_write as the read and write pipes to the child
+//               process's standard output and standard input,
+//               respectively.
 //
 //               Returns the handle to the created process on success,
 //               or INVALID_HANDLE_VALUE on falure.
 ////////////////////////////////////////////////////////////////////
 HANDLE P3DSession::
-win_create_process(const string &program, const string &archive_file,
-                   const string &start_dir,
-                   const string &env, const string &log_pathname,
-                   HandleStream &pipe_read, HandleStream &pipe_write) {
-
-  // Create a bi-directional pipe to communicate with the sub-process.
-  HANDLE r_to, w_to, r_from, w_from;
-
-  // Create the pipe to the process.
-  if (!CreatePipe(&r_to, &w_to, NULL, 0)) {
-    nout << "failed to create pipe\n";
-  } else {
-    // Make sure the right end of the pipe is inheritable.
-    SetHandleInformation(r_to, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
-    SetHandleInformation(w_to, HANDLE_FLAG_INHERIT, 0);
-  }
-
-  // Create the pipe from the process.
-  if (!CreatePipe(&r_from, &w_from, NULL, 0)) {
-    nout << "failed to create pipe\n";
-  } else { 
-    // Make sure the right end of the pipe is inheritable.
-    SetHandleInformation(w_from, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
-    SetHandleInformation(r_from, HANDLE_FLAG_INHERIT, 0);
-  }
-
+win_create_process() {
   HANDLE error_handle = GetStdHandle(STD_ERROR_HANDLE);
-  bool got_log_pathname = !log_pathname.empty();
+  bool got_log_pathname = !_log_pathname.empty();
   if (got_log_pathname) {
     // Open the named file for output and redirect the child's stderr
     // into it.
     HANDLE handle = CreateFile
-      (log_pathname.c_str(), GENERIC_WRITE, 
+      (_log_pathname.c_str(), GENERIC_WRITE, 
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL, CREATE_ALWAYS, 0, NULL);
     if (handle != INVALID_HANDLE_VALUE) {
       error_handle = handle;
       SetHandleInformation(error_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
     } else {
-      nout << "Unable to open " << log_pathname << "\n";
+      nout << "Unable to open " << _log_pathname << "\n";
     }
   }
 
@@ -1085,8 +1156,8 @@ win_create_process(const string &program, const string &archive_file,
   ZeroMemory(&startup_info, sizeof(STARTUPINFO));
   startup_info.cb = sizeof(startup_info); 
   startup_info.hStdError = error_handle;
-  startup_info.hStdOutput = w_from;
-  startup_info.hStdInput = r_to;
+  startup_info.hStdOutput = _output;
+  startup_info.hStdInput = _input;
   startup_info.dwFlags |= STARTF_USESTDHANDLES;
 
   // Make sure the "python" console window is hidden.
@@ -1096,15 +1167,18 @@ win_create_process(const string &program, const string &archive_file,
   // If the start directory is empty, meaning not to change the
   // current directory, then pass NULL in to CreateProcess().
   const char *start_dir_cstr = NULL;
-  if (!start_dir.empty()) {
-    start_dir_cstr = start_dir.c_str();
+  if (_use_start_dir) {
+    start_dir_cstr = _start_dir.c_str();
   }
 
+  // Construct the command-line string, containing the quoted
+  // command-line arguments.
   ostringstream stream;
-  stream << "\"" << program << "\" \"" << archive_file << "\"";
+  stream << "\"" << _p3dpython_exe << "\" \"" << _p3dpython_dll
+         << "\" \"" << _mf_filename << "\"";
 
   // I'm not sure why CreateProcess wants a non-const char pointer for
-  // the command-line argument, but I'm not taking chances.  It gets a
+  // its command-line string, but I'm not taking chances.  It gets a
   // non-const char array that it can modify.
   string command_line_str = stream.str();
   char *command_line = new char[command_line_str.size() + 1];
@@ -1112,29 +1186,26 @@ win_create_process(const string &program, const string &archive_file,
 
   PROCESS_INFORMATION process_info; 
   BOOL result = CreateProcess
-    (program.c_str(), command_line, NULL, NULL, TRUE, 0,
-     (void *)env.c_str(), start_dir_cstr,
+    (_p3dpython_exe.c_str(), command_line, NULL, NULL, TRUE, 0,
+     (void *)_env.c_str(), _start_dir_cstr,
      &startup_info, &process_info);
   bool started_program = (result != 0);
 
   delete[] command_line;
 
   // Close the pipe handles that are now owned by the child.
-  CloseHandle(w_from);
-  CloseHandle(r_to);
+  CloseHandle(_output);
+  CloseHandle(_input);
   if (got_log_pathname) {
     CloseHandle(error_handle);
   }
 
   if (!started_program) {
-    CloseHandle(r_from);
-    CloseHandle(w_to);
+    _pipe_read.close();
+    _pipe_write.close();
     return INVALID_HANDLE_VALUE;
   }
 
-  pipe_read.open_read(r_from);
-  pipe_write.open_write(w_to);
-
   CloseHandle(process_info.hThread);
   return process_info.hProcess;
 }
@@ -1144,54 +1215,40 @@ win_create_process(const string &program, const string &archive_file,
 #ifndef _WIN32
 ////////////////////////////////////////////////////////////////////
 //     Function: P3DSession::posix_create_process
-//       Access: Private, Static
-//  Description: Creates a sub-process to run the named program
-//               executable, with the indicated environment string.
+//       Access: Private
+//  Description: Creates a sub-process to run _p3dpython_exe, with
+//               the appropriate command-line arguments, and the
+//               environment string defined in _env.  Standard error
+//               is logged to _log_pathname, if that string is
+//               nonempty.
 //
-//               Opens the two HandleStreams as the read and write
-//               pipes to the child process's standard output and
-//               standard input, respectively.  Returns true on
-//               success, false on failure.
+//               Opens the two HandleStreams _pipe_read and
+//               _pipe_write as the read and write pipes to the child
+//               process's standard output and standard input,
+//               respectively.
 //
-//               Returns the pid of the created process on success, or
-//               -1 on falure.
+//               Returns the handle to the created process on success,
+//               or INVALID_HANDLE_VALUE on falure.
 ////////////////////////////////////////////////////////////////////
 int P3DSession::
-posix_create_process(const string &program, const string &archive_file,
-                     const string &start_dir,
-                     const string &env, const string &log_pathname,
-                     HandleStream &pipe_read, HandleStream &pipe_write) {
-  // Create a bi-directional pipe to communicate with the sub-process.
-  int to_fd[2];
-  if (pipe(to_fd) < 0) {
-    perror("failed to create pipe");
-  }
-  int from_fd[2];
-  if (pipe(from_fd) < 0) {
-    perror("failed to create pipe");
-  }
-
+posix_create_process() {
   // Fork and exec.
   pid_t child = fork();
   if (child < 0) {
-    close(to_fd[0]);
-    close(to_fd[1]);
-    close(from_fd[0]);
-    close(from_fd[1]);
     perror("fork");
     return -1;
   }
 
   if (child == 0) {
     // Here we are in the child process.
-    bool got_log_pathname = !log_pathname.empty();
+    bool got_log_pathname = !_log_pathname.empty();
     if (got_log_pathname) {
       // Open the named file for output and redirect the child's stderr
       // into it.
-      int logfile_fd = open(log_pathname.c_str(), 
+      int logfile_fd = open(_log_pathname.c_str(), 
                             O_WRONLY | O_CREAT | O_TRUNC, 0666);
       if (logfile_fd < 0) {
-        nout << "Unable to open " << log_pathname << "\n";
+        nout << "Unable to open " << _log_pathname << "\n";
       } else {
         dup2(logfile_fd, STDERR_FILENO);
         close(logfile_fd);
@@ -1200,41 +1257,108 @@ posix_create_process(const string &program, const string &archive_file,
 
     // Set the appropriate ends of the bi-directional pipe as the
     // standard input and standard output of the child process.
-    dup2(to_fd[0], STDIN_FILENO);
-    dup2(from_fd[1], STDOUT_FILENO);
-    close(to_fd[1]);
-    close(from_fd[0]);
-
-    if (!start_dir.empty()) {
-      if (chdir(start_dir.c_str()) < 0) {
-        nout << "Could not chdir to " << start_dir << "\n";
-        _exit(1);
+    dup2(_input, STDIN_FILENO);
+    dup2(_output, STDOUT_FILENO);
+    _pipe_read.close();
+    _pipe_write.close();
+
+    if (_use_start_dir) {
+      if (chdir(_start_dir.c_str()) < 0) {
+        nout << "Could not chdir to " << _start_dir << "\n";
+        // This is a warning, not an error.  We don't actually care
+        // that much about the starting directory.
       }
     }
 
     // build up an array of char strings for the environment.
     vector<const char *> ptrs;
     size_t p = 0;
-    size_t zero = env.find('\0', p);
+    size_t zero = _env.find('\0', p);
     while (zero != string::npos) {
-      ptrs.push_back(env.data() + p);
+      ptrs.push_back(_env.data() + p);
       p = zero + 1;
-      zero = env.find('\0', p);
+      zero = _env.find('\0', p);
     }
     ptrs.push_back((char *)NULL);
-    
-    execle(program.c_str(), 
-           program.c_str(), archive_file.c_str(), (char *)0, 
+
+    execle(_p3dpython_exe.c_str(), 
+           _p3dpython_exe.c_str(), _p3dpython_dll.c_str(), _mf_filename.c_str(), (char *)0, 
            &ptrs[0]);
-    nout << "Failed to exec " << program << "\n";
+    nout << "Failed to exec " << _p3dpython_exe << "\n";
     _exit(1);
   }
 
-  pipe_read.open_read(from_fd[0]);
-  pipe_write.open_write(to_fd[1]);
-  close(to_fd[0]);
-  close(from_fd[1]);
+  close(_input);
+  close(_output);
 
   return child;
 }
 #endif  // _WIN32
+
+////////////////////////////////////////////////////////////////////
+//     Function: P3DSession::p3dpython_thread_run
+//       Access: Private
+//  Description: This method is called in a sub-thread to fire up
+//               p3dpython within this same process, but only if the
+//               above attempt to create a sub-process failed.
+////////////////////////////////////////////////////////////////////
+void P3DSession::
+p3dpython_thread_run() {
+  nout << "running p3dpython_thread_run()\n";
+
+  // Set the environment.  Hopefully this won't be too destructive to
+  // the current process, and hopefully these changes will be read
+  // properly by Python.
+  size_t p = 0;
+  size_t zero = _env.find('\0', p);
+  while (zero != string::npos) {
+    const char *start = _env.data() + p;
+    const char *equals = strchr(start, '=');
+    if (equals != NULL) {
+      string variable(start, equals - start);
+#ifdef _WIN32
+      _putenv_s(variable.c_str(), equals + 1);
+#else
+      setenv(variable.c_str(), equals + 1, true);
+#endif  // _WIN32
+    }
+    p = zero + 1;
+    zero = _env.find('\0', p);
+  }
+
+  // Now load the library.
+#ifdef _WIN32
+  SetErrorMode(0);
+  HMODULE module = LoadLibrary(_p3dpython_dll.c_str());
+  if (module == NULL) {
+    // Couldn't load the DLL.
+    nout << "Couldn't load " << _p3dpython_dll << "\n";
+    return;
+  }
+
+  #define get_func GetProcAddress
+
+#else  // _WIN32
+  // Posix case.
+  void *module = dlopen(_p3dpython_dll.c_str(), RTLD_GLOBAL);
+  if (module == NULL) {
+    // Couldn't load the .so.
+    nout << "Couldn't load " << _p3dpython_dll << "\n";
+    return;
+  }
+
+  #define get_func dlsym
+
+#endif  // _WIN32
+
+  run_p3dpython_func *run_p3dpython = (run_p3dpython_func *)get_func(module, "run_p3dpython");
+  if (run_p3dpython == NULL) {
+    nout << "Couldn't find run_p3dpython\n";
+    return;
+  }
+
+  if (!run_p3dpython(_p3dpython_dll.c_str(), _mf_filename.c_str(),
+                     _input, _output)) {
+    nout << "Failure on startup.\n";
+  }
+}

+ 26 - 11
direct/src/plugin/p3dSession.h

@@ -75,19 +75,16 @@ private:
   void rt_handle_request(TiXmlDocument *doc);
 
 #ifdef _WIN32
-  static HANDLE 
-  win_create_process(const string &program, const string &archive_file,
-                     const string &start_dir,
-                     const string &env, const string &output_filename,
-                     HandleStream &pipe_read, HandleStream &pipe_write);
+  HANDLE win_create_process();
 #else
-  static int 
-  posix_create_process(const string &program, const string &archive_file,
-                       const string &start_dir,
-                       const string &env, const string &output_filename,
-                       HandleStream &pipe_read, HandleStream &pipe_write);
+  int posix_create_process();
 #endif
 
+  // In case we can't get a separate process, we'll run p3dpython in a
+  // sub-thread.
+  THREAD_CALLBACK_DECLARATION(P3DSession, p3dpython_thread_run);
+  void p3dpython_thread_run();
+
 private:
   int _session_id;
   string _session_key;
@@ -95,6 +92,15 @@ private:
   string _log_pathname;
   string _python_root_dir;
   string _start_dir;
+  bool _use_start_dir;
+
+  // This information is passed to create_process(), or to
+  // p3dpython_thread_run().
+  string _p3dpython_exe;
+  string _p3dpython_dll;
+  string _mf_filename;
+  string _env;
+  FHandle _input, _output;
 
   typedef map<int, P3DInstance *> Instances;
   Instances _instances;
@@ -114,12 +120,21 @@ private:
 
   P3DPackage *_panda3d;
 
-  // Members for communicating with the p3dpython child process.
+  // If this is true, then CreateProcess() or fork() failed (or we had
+  // one_process set true in the tokens), and we're forced to run
+  // p3dpython in a sub-thread within the same process, rather than in
+  // a separate process.  This means we can't have multiple sessions
+  // running simultaneously, because Python don't play that way.
+  bool _p3dpython_one_process;
+
+  // Members for communicating with the p3dpython child process (or
+  // thread, as the case may be).
 #ifdef _WIN32
   HANDLE _p3dpython_handle;
 #else
   int _p3dpython_pid;
 #endif
+  THREAD _p3dpython_thread;
   bool _p3dpython_started;
   bool _p3dpython_running;
 

+ 37 - 0
direct/src/plugin/run_p3dpython.h

@@ -0,0 +1,37 @@
+// Filename: run_p3dpython.h
+// Created by:  drose (29Aug09)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef RUN_P3DPYTHON_H
+#define RUN_P3DPYTHON_H
+
+// This header file defines the prototype for run_p3dpython(), the
+// main entry point to this DLL.
+
+#include "fhandle.h"
+
+#ifdef _WIN32
+#define EXPCL_P3DPYTHON __declspec(dllexport)
+#else
+#define EXPCL_P3DPYTHON
+#endif
+
+typedef bool run_p3dpython_func(const char *program_name, const char *archive_file,
+                                FHandle input, FHandle output);
+
+EXPCL_P3DPYTHON extern "C" bool
+run_p3dpython(const char *program_name, const char *archive_file,
+              FHandle input, FHandle output);
+
+#endif
+

+ 1 - 1
direct/src/plugin_standalone/Sources.pp

@@ -12,7 +12,7 @@
     prc:c dtoolutil:c dtoolbase:c dtool:m \
     interrogatedb:c dconfig:c dtoolconfig:m \
     express:c downloader:c pandaexpress:m \
-    pystub
+    $[if $[WINDOWS_PLATFORM],pystub,]
 
   #define OSX_SYS_FRAMEWORKS Foundation AppKit Carbon