From 4388b853e55ce1df56807d8b89e16d6c3d3714de Mon Sep 17 00:00:00 2001 From: David Rose Date: Sat, 29 Aug 2009 23:08:00 +0000 Subject: [PATCH] one_process support. Some issues. --- direct/src/p3d/Packager.py | 8 +- direct/src/plugin/Sources.pp | 20 +- direct/src/plugin/fhandle.h | 29 ++ direct/src/plugin/handleStreamBuf.h | 9 +- direct/src/plugin/p3dPythonRun.cxx | 77 ++-- direct/src/plugin/p3dPythonRun.h | 5 +- direct/src/plugin/p3dSession.cxx | 534 +++++++++++++++--------- direct/src/plugin/p3dSession.h | 37 +- direct/src/plugin/run_p3dpython.h | 37 ++ direct/src/plugin_standalone/Sources.pp | 2 +- 10 files changed, 485 insertions(+), 273 deletions(-) create mode 100644 direct/src/plugin/fhandle.h create mode 100644 direct/src/plugin/run_p3dpython.h diff --git a/direct/src/p3d/Packager.py b/direct/src/p3d/Packager.py index 9892b96471..57ede12ff3 100644 --- a/direct/src/p3d/Packager.py +++ b/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 diff --git a/direct/src/plugin/Sources.pp b/direct/src/plugin/Sources.pp index 48bfef0f73..c82f39ccf5 100644 --- a/direct/src/plugin/Sources.pp +++ b/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 diff --git a/direct/src/plugin/fhandle.h b/direct/src/plugin/fhandle.h new file mode 100644 index 0000000000..aae6113fac --- /dev/null +++ b/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 +typedef HANDLE FHandle; +#else +// On POSIX, we use a file descriptor as a "handle". +typedef int FHandle; +#endif + +#endif diff --git a/direct/src/plugin/handleStreamBuf.h b/direct/src/plugin/handleStreamBuf.h index bb7c1e200b..661564570a 100644 --- a/direct/src/plugin/handleStreamBuf.h +++ b/direct/src/plugin/handleStreamBuf.h @@ -15,14 +15,7 @@ #ifndef HANDLESTREAMBUF_H #define HANDLESTREAMBUF_H -#ifdef _WIN32 -#include -typedef HANDLE FHandle; -#else -// On POSIX, we use a file descriptor as a "handle". -typedef int FHandle; -#endif - +#include "fhandle.h" #include using namespace std; diff --git a/direct/src/plugin/p3dPythonRun.cxx b/direct/src/plugin/p3dPythonRun.cxx index ca2ad9ec73..7622031a53 100755 --- a/direct/src/plugin/p3dPythonRun.cxx +++ b/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; } diff --git a/direct/src/plugin/p3dPythonRun.h b/direct/src/plugin/p3dPythonRun.h index b1606a298a..af8b9332dd 100755 --- a/direct/src/plugin/p3dPythonRun.h +++ b/direct/src/plugin/p3dPythonRun.h @@ -22,6 +22,7 @@ #include #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: diff --git a/direct/src/plugin/p3dSession.cxx b/direct/src/plugin/p3dSession.cxx index 71a61fafb2..a7d35869db 100644 --- a/direct/src/plugin/p3dSession.cxx +++ b/direct/src/plugin/p3dSession.cxx @@ -26,6 +26,7 @@ #include "p3dConcreteStruct.h" #include "binaryXml.h" #include "mkdir_complete.h" +#include "run_p3dpython.h" #include @@ -34,6 +35,7 @@ #include #include #include +#include #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; + 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; + + } 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. + #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); - } - - CloseHandle(_p3dpython_handle); -#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; + // 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); } - // 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"; - } + CloseHandle(_p3dpython_handle); +#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"; + } + #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(); - - nout << "Attempting to start python from " << p3dpython << "\n"; + // Create the pipes for communication. #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); + // 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); + } + + _output = w_from; + _input = r_to; + _pipe_read.open_read(r_from); + _pipe_write.open_write(w_to); + #else - _p3dpython_pid = posix_create_process - (p3dpython, archive_file, start_dir, env, _log_pathname, - _pipe_read, _pipe_write); - bool started_p3dpython = (_p3dpython_pid > 0); + // 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(); + started_p3dpython = (_p3dpython_handle != INVALID_HANDLE_VALUE); +#else + _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]); + dup2(_input, STDIN_FILENO); + dup2(_output, STDOUT_FILENO); + _pipe_read.close(); + _pipe_write.close(); - if (!start_dir.empty()) { - if (chdir(start_dir.c_str()) < 0) { - nout << "Could not chdir to " << start_dir << "\n"; - _exit(1); + 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 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"; + } +} diff --git a/direct/src/plugin/p3dSession.h b/direct/src/plugin/p3dSession.h index 51e385ca7e..37c1ce5c6e 100644 --- a/direct/src/plugin/p3dSession.h +++ b/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 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; diff --git a/direct/src/plugin/run_p3dpython.h b/direct/src/plugin/run_p3dpython.h new file mode 100644 index 0000000000..7e1b7200c3 --- /dev/null +++ b/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 + diff --git a/direct/src/plugin_standalone/Sources.pp b/direct/src/plugin_standalone/Sources.pp index aa18233d22..aad6db9c74 100644 --- a/direct/src/plugin_standalone/Sources.pp +++ b/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