X-Git-Url: http://git.salome-platform.org/gitweb/?a=blobdiff_plain;f=src%2FPyInterp%2FPyInterp_Interp.cxx;h=48c6a8eb5b5a2e415f0d7a90e9031ce9f788a40d;hb=e6caa123c65e3c4a3017364ec5bb4225fd898465;hp=2f8a1db3e45810598076a9d20689e49c1f62469b;hpb=034a705024b224972c148e1e3834c5ee38df184b;p=modules%2Fgui.git diff --git a/src/PyInterp/PyInterp_Interp.cxx b/src/PyInterp/PyInterp_Interp.cxx index 2f8a1db3e..48c6a8eb5 100644 --- a/src/PyInterp/PyInterp_Interp.cxx +++ b/src/PyInterp/PyInterp_Interp.cxx @@ -1,4 +1,4 @@ -// Copyright (C) 2007-2014 CEA/DEN, EDF R&D, OPEN CASCADE +// Copyright (C) 2007-2015 CEA/DEN, EDF R&D, OPEN CASCADE // // Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS @@ -21,10 +21,11 @@ // // File : PyInterp_Interp.cxx -// Author : Christian CAREMOLI, Paul RASCLE, EDF +// Author : Christian CAREMOLI, Paul RASCLE, Adrien BRUNETON // Module : SALOME // #include "PyInterp_Interp.h" // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!! +#include "PyInterp_Utils.h" #include #include @@ -39,80 +40,6 @@ #define TOP_HISTORY_PY "--- top of history ---" #define BEGIN_HISTORY_PY "--- begin of history ---" -// a map to store python thread states that have been created for a given system thread (key=thread id,value=thread state) -std::map currentThreadMap; - -/*! - \class PyLockWrapper - \brief Python GIL wrapper. -*/ - -/*! - \brief Constructor. Automatically acquires GIL. - \param theThreadState python thread state -*/ -PyLockWrapper::PyLockWrapper(PyThreadState* theThreadState): - myThreadState(theThreadState), - mySaveThreadState(0) -{ - if (myThreadState->interp == PyInterp_Interp::_interp) - _savestate = PyGILState_Ensure(); - else - PyEval_AcquireThread(myThreadState); -} - -/*! - \brief Destructor. Automatically releases GIL. -*/ -PyLockWrapper::~PyLockWrapper() -{ - if (myThreadState->interp == PyInterp_Interp::_interp) - PyGILState_Release(_savestate); - else - PyEval_ReleaseThread(myThreadState); -} - -/*! - \brief Get Python GIL wrapper. - \return GIL lock wrapper (GIL is automatically acquired here) -*/ -PyLockWrapper PyInterp_Interp::GetLockWrapper() -{ - if (_tstate->interp == PyInterp_Interp::_interp) - return _tstate; - - // If we are here, we have a secondary python interpreter. Try to get a thread state synchronized with the system thread - long currentThreadid=PyThread_get_thread_ident(); // the system thread id - PyThreadState* theThreadState; - if(currentThreadMap.count(currentThreadid) != 0) - { - //a thread state exists for this thread id - PyThreadState* oldThreadState=currentThreadMap[currentThreadid]; - if(_tstate->interp ==oldThreadState->interp) - { - //The old thread state has the same python interpreter as this one : reuse the threadstate - theThreadState=oldThreadState; - } - else - { - //The old thread state has not the same python interpreter as this one : delete the old threadstate and create a new one - PyEval_AcquireLock(); - PyThreadState_Clear(oldThreadState); - PyThreadState_Delete(oldThreadState); - PyEval_ReleaseLock(); - theThreadState=PyThreadState_New(_tstate->interp); - currentThreadMap[currentThreadid]=theThreadState; - } - } - else - { - // no old thread state for this thread id : create a new one - theThreadState=PyThreadState_New(_tstate->interp); - currentThreadMap[currentThreadid]=theThreadState; - } - return theThreadState; -} - /* The following functions are used to hook the Python interpreter output. @@ -232,9 +159,6 @@ static PyStdOut* newPyStdOut( bool iscerr ) int PyInterp_Interp::_argc = 1; char* PyInterp_Interp::_argv[] = {(char*)""}; -PyObject* PyInterp_Interp::builtinmodule = NULL; -PyThreadState* PyInterp_Interp::_gtstate = NULL; -PyInterpreterState* PyInterp_Interp::_interp = NULL; /*! \brief Basic constructor. @@ -243,15 +167,18 @@ PyInterpreterState* PyInterp_Interp::_interp = NULL; must call virtual method initalize(). */ PyInterp_Interp::PyInterp_Interp(): - _tstate(0), _vout(0), _verr(0), _g(0) + _vout(0), _verr(0), _local_context(0), _global_context(0) { } + + /*! \brief Destructor. */ PyInterp_Interp::~PyInterp_Interp() { + destroy(); } /*! @@ -260,7 +187,6 @@ PyInterp_Interp::~PyInterp_Interp() This method shoud be called after construction of the interpreter. The method initialize() calls virtuals methods - initPython() to initialize global Python interpreter - - initState() to initialize embedded interpreter state - initContext() to initialize interpreter internal context - initRun() to prepare interpreter for running commands which should be implemented in the successor classes, according to the @@ -271,20 +197,20 @@ void PyInterp_Interp::initialize() _history.clear(); // start a new list of user's commands _ith = _history.begin(); - initPython(); - // Here the global lock is released + initPython(); // This also inits the multi-threading for Python (but w/o acquiring GIL) - initState(); + //initState(); // [ABN] OBSOLETE - PyEval_AcquireThread(_tstate); + // ---- The rest of the initialisation process is done hodling the GIL + PyLockWrapper lck; initContext(); - // used to interpret & compile commands + // used to interpret & compile commands - this is really imported here + // and only added again (with PyImport_AddModule) later on PyObjWrapper m(PyImport_ImportModule("codeop")); if(!m) { PyErr_Print(); - PyEval_ReleaseThread(_tstate); return; } @@ -294,17 +220,22 @@ void PyInterp_Interp::initialize() // All the initRun outputs are redirected to the standard output (console) initRun(); - PyEval_ReleaseThread(_tstate); +} + +void PyInterp_Interp::destroy() +{ + PyLockWrapper lck; + closeContext(); } /*! \brief Initialize Python interpreter. - In case if Python is not initialized, it sets program name, initializes the interpreter, sets program arguments, - initializes threads. - Otherwise, it just obtains the global interpreter and thread states. This is important for light SALOME configuration, + In case if Python is not initialized, it sets program name, initializes the single true Python + interpreter, sets program arguments, and initializes threads. + Otherwise, does nothing. This is important for light SALOME configuration, as in full SALOME this is done at SalomeApp level. - \sa SalomeApp_PyInterp class + \sa SalomeApp_PyInterp class and main() in SALOME_Session_Server */ void PyInterp_Interp::initPython() { @@ -313,30 +244,22 @@ void PyInterp_Interp::initPython() Py_SetProgramName(_argv[0]); Py_Initialize(); // Initialize the interpreter PySys_SetArgv(_argc, _argv); - PyEval_InitThreads(); // Create (and acquire) the interpreter lock - } - if ( _interp == NULL ) - _interp = PyThreadState_Get()->interp; - if (PyType_Ready(&PyStdOut_Type) < 0) { - PyErr_Print(); + PyEval_InitThreads(); // Create (and acquire) the Python global interpreter lock (GIL) + PyEval_ReleaseLock(); } - if ( _gtstate == NULL ) - _gtstate = PyEval_SaveThread(); // Release global thread state } /*! \brief Get embedded Python interpreter banner. \return banner string */ -std::string PyInterp_Interp::getbanner() +std::string PyInterp_Interp::getbanner() const { - // Should we take the lock ? - // PyEval_RestoreThread(_tstate); + PyLockWrapper lck; std::string aBanner("Python "); aBanner = aBanner + Py_GetVersion() + " on " + Py_GetPlatform() ; aBanner = aBanner + "\ntype help to get general information on environment\n"; - //PyEval_SaveThread(); return aBanner; } @@ -350,37 +273,60 @@ std::string PyInterp_Interp::getbanner() */ bool PyInterp_Interp::initRun() { - // - // probably all below code isn't required - // - /* - PySys_SetObject("stderr",_verr); - PySys_SetObject("stdout",_vout); - - //PyObject *m = PyImport_GetModuleDict(); - - PySys_SetObject("stdout",PySys_GetObject("__stdout__")); - PySys_SetObject("stderr",PySys_GetObject("__stderr__")); - */ return true; } +/*! + * Initialize context dictionaries. GIL is held already. + * The code executed in an embedded interpreter is expected to be run at the module + * level, in which case local and global context have to be the same dictionary. + * See: http://stackoverflow.com/questions/12265756/c-python-running-python-code-within-a-context + * for an explanation. + */ +bool PyInterp_Interp::initContext() +{ + PyObject *m = PyImport_AddModule("__main__"); // interpreter main module (module context) + if(!m){ + PyErr_Print(); + return false; + } + _global_context = PyModule_GetDict(m); // get interpreter global variable context + Py_INCREF(_global_context); + _local_context = _global_context; + + int ret = PyRun_SimpleString("import salome_iapp;salome_iapp.IN_SALOME_GUI=True"); + + return ret == 0; +} + +/*! + * Destroy context dictionaries. GIL is held already. + */ +void PyInterp_Interp::closeContext() +{ + Py_XDECREF(_global_context); + // both _global and _local point to the same Python object: + // Py_XDECREF(_local_context); +} + /*! \brief Compile Python command and evaluate it in the - python dictionary context if possible. + python dictionary contexts if possible. This is not thread-safe. + This is the caller's responsability to make this thread-safe. \internal \param command Python command string - \param context Python context (dictionary) \return -1 on fatal error, 1 if command is incomplete and 0 if command is executed successfully */ -static int run_command(const char *command, PyObject *context) +static int run_command(const char *command, PyObject * global_ctxt, PyObject * local_ctxt) { PyObject *m = PyImport_AddModule("codeop"); if(!m) { // Fatal error. No way to go on. PyErr_Print(); return -1; } + +// PyObjWrapper v(Py_CompileString(command, "", Py_file_input)); PyObjWrapper v(PyObject_CallMethod(m,(char*)"compile_command",(char*)"s",command)); if(!v) { // Error encountered. It should be SyntaxError, @@ -396,12 +342,7 @@ static int run_command(const char *command, PyObject *context) return 1; } else { - // Complete and correct text. We evaluate it. - //#if PY_VERSION_HEX < 0x02040000 // python version earlier than 2.4.0 - // PyObjWrapper r(PyEval_EvalCode(v,context,context)); - //#else - PyObjWrapper r(PyEval_EvalCode((PyCodeObject *)(void *)v,context,context)); - //#endif + PyObjWrapper r(PyEval_EvalCode((PyCodeObject *)(void *)v,global_ctxt, local_ctxt)); if(!r) { // Execution error. We return -1 PyErr_Print(); @@ -433,7 +374,7 @@ void replaceAll(std::string& str, const std::string& from, const std::string& to \return -1 on fatal error, 1 if command is incomplete and 0 if command is executed successfully */ -static int compile_command(const char *command,PyObject *context) +static int compile_command(const char *command, PyObject * global_ctxt, PyObject * local_ctxt) { // First guess if command is execution of a script with args, or a simple Python command std::string singleCommand = command; @@ -449,7 +390,7 @@ static int compile_command(const char *command,PyObject *context) if (commandArgs.empty()) { // process command: expression // process command: execfile(r"/absolute/path/to/script.py") (no args) - return run_command(singleCommand.c_str(), context); + return run_command(singleCommand.c_str(), global_ctxt, local_ctxt); } else { // process command: execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]") @@ -461,23 +402,44 @@ static int compile_command(const char *command,PyObject *context) replaceAll(commandArgs, ",", "\",\""); commandArgs = "\""+commandArgs+"\""; std::string completeCommand = preCommandBegin+"\""+script+"\","+commandArgs+preCommandEnd+singleCommand+";sys.argv=save_argv"; - return run_command(completeCommand.c_str(), context); + return run_command(completeCommand.c_str(), global_ctxt, local_ctxt); } } /*! - \brief Run Python command. + \brief Run Python command - the command has to fit on a single line (no \n!). + Use ';' if you need multiple statements evaluated at once. \param command Python command \return command status */ int PyInterp_Interp::run(const char *command) { beforeRun(); - return simpleRun(command); + int ret = simpleRun(command); + afterRun(); + return ret; +} + +/** + * Called before a command is run (when calling run() method). Not thread-safe. Caller's responsability + * to acquire GIL if needed. + */ +int PyInterp_Interp::beforeRun() +{ + return 0; +} + +/** + * Called after a command is run (when calling run() method). Not thread-safe. Caller's responsability + * to acquire GIL if needed. + */ +int PyInterp_Interp::afterRun() +{ + return 0; } /*! - \brief Run Python command (used internally). + \brief Run Python command (used internally). Not thread-safe. GIL acquisition is caller's responsability. \param command Python command \param addToHistory if \c true (default), the command is added to the commands history \return command status @@ -489,19 +451,22 @@ int PyInterp_Interp::simpleRun(const char *command, const bool addToHistory) _ith = _history.end(); } - // We come from C++ to enter Python world - // We need to acquire the Python global lock - //PyLockWrapper aLock(_tstate); // san - lock is centralized now + // Current stdout and stderr are saved + PyObject * oldOut = PySys_GetObject((char*)"stdout"); + PyObject * oldErr = PySys_GetObject((char*)"stderr"); + // Keep them alive (PySys_GetObject returned a *borrowed* ref!) + Py_INCREF(oldOut); + Py_INCREF(oldErr); - // Reset redirected outputs before treatment + // Redirect outputs to SALOME Python console before treatment PySys_SetObject((char*)"stderr",_verr); PySys_SetObject((char*)"stdout",_vout); - int ier = compile_command(command,_g); + int ier = compile_command(command, _global_context, _local_context); - // Outputs are redirected on standards outputs (console) - PySys_SetObject((char*)"stdout",PySys_GetObject((char*)"__stdout__")); - PySys_SetObject((char*)"stderr",PySys_GetObject((char*)"__stderr__")); + // Outputs are redirected to what they were before + PySys_SetObject((char*)"stdout",oldOut); + PySys_SetObject((char*)"stderr",oldErr); return ier; }