From: abn Date: Tue, 14 Oct 2014 14:22:11 +0000 (+0200) Subject: Prepare Python console to work in async mode with PARAVIS: X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=464a122de491ed5e3d680c57b311d77bec051402;p=modules%2Fgui.git Prepare Python console to work in async mode with PARAVIS: * exposed Python import lock access in PyLockWrapper * now also writing stdout/stderr to terminal * added context in run functions * added function to post Python in SALOME's main thread --- diff --git a/src/PyConsole/PyConsole_Editor.cxx b/src/PyConsole/PyConsole_Editor.cxx index 66aaf5b9b..300915c70 100644 --- a/src/PyConsole/PyConsole_Editor.cxx +++ b/src/PyConsole/PyConsole_Editor.cxx @@ -169,7 +169,7 @@ PyConsole_Editor::PyConsole_Editor( PyConsole_Interp* theInterp, myCmdInHistory( -1 ), myEventLoop( 0 ), myShowBanner( true ), - myIsSync( true ), + myIsSync( false ), myIsSuppressOutput( false ) { QString fntSet( "" ); diff --git a/src/PyInterp/PyInterp_Interp.cxx b/src/PyInterp/PyInterp_Interp.cxx index df95ecfb5..edc89da59 100644 --- a/src/PyInterp/PyInterp_Interp.cxx +++ b/src/PyInterp/PyInterp_Interp.cxx @@ -58,15 +58,16 @@ PyStdOut_write(PyStdOut *self, PyObject *args) int l; if (!PyArg_ParseTuple(args, "t#:write",&c, &l)) return NULL; - if(self->_cb==NULL) { - if ( self->_iscerr ) - std::cerr << c ; - else - std::cout << c ; - } - else { + + // [ABN]: changed this to have Python output on both the Python console + // and the terminal (helps to degug the multi-thread) + if ( self->_iscerr ) + std::cerr << c ; + else + std::cout << c ; + if(self->_cb != NULL) self->_cb(self->_data,c); - } + Py_INCREF(Py_None); return Py_None; } @@ -168,10 +169,7 @@ char* PyInterp_Interp::_argv[] = {(char*)""}; */ PyInterp_Interp::PyInterp_Interp(): _vout(0), _verr(0), _local_context(0), _global_context(0) -{ -} - - +{} /*! \brief Destructor. @@ -199,8 +197,6 @@ void PyInterp_Interp::initialize() initPython(); // This also inits the multi-threading for Python (but w/o acquiring GIL) - //initState(); // [ABN] OBSOLETE - // ---- The rest of the initialisation process is done hodling the GIL PyLockWrapper lck; @@ -244,10 +240,10 @@ void PyInterp_Interp::initPython() Py_SetProgramName(_argv[0]); Py_Initialize(); // Initialize the interpreter PySys_SetArgv(_argc, _argv); - PyEval_InitThreads(); // Create (and acquire) the Python global interpreter lock (GIL) PyEval_ReleaseLock(); } + PyLockWrapper::Initialize(); } /*! @@ -409,13 +405,15 @@ static int compile_command(const char *command, PyObject * global_ctxt, PyObject /*! \brief Run Python command - the command has to fit on a single line (no \n!). Use ';' if you need multiple statements evaluated at once. + The context dictionaries can also be passed (like for the Python 'exec' statement). + If not provided it will default to the current interpreter's context. \param command Python command \return command status */ -int PyInterp_Interp::run(const char *command) +int PyInterp_Interp::run(const char *command, PyObject * globals, PyObject * locals) { beforeRun(); - int ret = simpleRun(command); + int ret = simpleRun(command, false, globals, locals); afterRun(); return ret; } @@ -444,7 +442,8 @@ int PyInterp_Interp::afterRun() \param addToHistory if \c true (default), the command is added to the commands history \return command status */ -int PyInterp_Interp::simpleRun(const char *command, const bool addToHistory) +int PyInterp_Interp::simpleRun(const char *command, const bool addToHistory, + PyObject * globals, PyObject * locals) { if( addToHistory && strcmp(command,"") != 0 ) { _history.push_back(command); @@ -462,7 +461,11 @@ int PyInterp_Interp::simpleRun(const char *command, const bool addToHistory) PySys_SetObject((char*)"stderr",_verr); PySys_SetObject((char*)"stdout",_vout); - int ier = compile_command(command, _global_context, _local_context); + int ier; + if (!globals || !locals) + ier = compile_command(command, _global_context, _local_context); + else + ier = compile_command(command, globals, locals); // Outputs are redirected to what they were before PySys_SetObject((char*)"stdout",oldOut); diff --git a/src/PyInterp/PyInterp_Interp.h b/src/PyInterp/PyInterp_Interp.h index 7883cd4ec..bff27815e 100644 --- a/src/PyInterp/PyInterp_Interp.h +++ b/src/PyInterp/PyInterp_Interp.h @@ -64,12 +64,9 @@ public: void initialize(); void destroy(); - virtual int run(const char *command); + virtual int run(const char *command, PyObject * globals=NULL, PyObject * locals=NULL); virtual void initStudy(){}; - // [ABN] - the PyLockWrapper is no more attached to the interpreter - // PyLockWrapper GetLockWrapper() const; - std::string getbanner() const; void setverrcb(PyOutChanged*,void*); void setvoutcb(PyOutChanged*,void*); @@ -90,7 +87,7 @@ protected: virtual int beforeRun(); virtual int afterRun(); - int simpleRun(const char* command, const bool addToHistory = true); + int simpleRun(const char* command, const bool addToHistory=true, PyObject * globals=NULL, PyObject * locals=NULL); virtual void initPython(); diff --git a/src/PyInterp/PyInterp_Utils.cxx b/src/PyInterp/PyInterp_Utils.cxx index 4fa3bc2ae..ae7361daa 100644 --- a/src/PyInterp/PyInterp_Utils.cxx +++ b/src/PyInterp/PyInterp_Utils.cxx @@ -23,12 +23,19 @@ // #include "PyInterp_Utils.h" +#include +#include +#include +#include +#include /*! \class PyLockWrapper \brief Python GIL wrapper. */ +PyObjWrapper PyLockWrapper::_imp_module(NULL); + /*! \brief Constructor. Automatically acquires GIL. */ @@ -37,6 +44,58 @@ PyLockWrapper::PyLockWrapper() _gil_state = PyGILState_Ensure(); } +bool PyLockWrapper::Initialize() +{ + if (!_imp_module.get()) + { + PyLockWrapper lock; + PyObjWrapper m2(PyImport_ImportModule("imp")); + _imp_module = m2; + if(!_imp_module) + { + PyErr_Print(); + return false; + } + } + return true; +} + +bool PyLockWrapper::AcquireImportLock() +{ + if (!_imp_module) + return false; + + PyLockWrapper lock; + PyObject_CallMethod(_imp_module, (char *)"acquire_lock", NULL); + if (PyErr_Occurred()) + { + PyErr_Print(); + return false; + } + return true; +} + +bool PyLockWrapper::ReleaseImportLockIfLocked() +{ + if (!_imp_module) + return false; + bool ret = false; + + PyLockWrapper lock; + PyObjWrapper m(PyObject_CallMethod(_imp_module, (char *)"lock_held", NULL)); + if (PyObject_IsTrue(m)) + { + PyObject_CallMethod(_imp_module, (char *)"release_lock", NULL); + if (PyErr_Occurred()) + { + PyErr_Print(); + return false; + } + ret = true; + } + + return ret; +} /*! \brief Destructor. Automatically releases GIL. */ diff --git a/src/PyInterp/PyInterp_Utils.h b/src/PyInterp/PyInterp_Utils.h index c9866bab1..81f64163f 100644 --- a/src/PyInterp/PyInterp_Utils.h +++ b/src/PyInterp/PyInterp_Utils.h @@ -27,27 +27,9 @@ #include "PyInterp.h" #include +#include -/** - * Utility class wrapping the Python GIL acquisition. This makes use of the high level - * API (PyGILState_Ensure and PyGILState_Release), and is hence compatible with only - * one running Python interpreter (no call to Py_NewInterpreter()). - * When the class is instanciated the lock is acquired. It is released at destruction time. - * Copy construction (and hence assignation) is forbidden. - */ -class PYINTERP_EXPORT PyLockWrapper -{ - PyGILState_STATE _gil_state; -public: - PyLockWrapper(); - ~PyLockWrapper(); - -private: - // "Rule of 3" - Forbid usage of copy operator and copy-constructor - PyLockWrapper(const PyLockWrapper & another); - const PyLockWrapper & operator=(const PyLockWrapper & another); -}; - +#include /** * Utility class to properly handle the reference counting required on Python objects. @@ -74,5 +56,43 @@ public: } }; +/** + * Utility class wrapping the Python GIL acquisition. This makes use of the high level + * API (PyGILState_Ensure and PyGILState_Release), and is hence compatible with only + * one running Python interpreter (no call to Py_NewInterpreter()). + * When the class is instanciated the lock is acquired. It is released at destruction time. + * Copy construction (and hence assignation) is forbidden. + */ +class PYINTERP_EXPORT PyLockWrapper +{ +private: + PyGILState_STATE _gil_state; + +public: + PyLockWrapper(); + ~PyLockWrapper(); + + static bool Initialize(); + + /* Import lock management - see the API of the imp module: + * https://docs.python.org/2/library/imp.html?highlight=imp%20module#module-imp + * + * This is mainly needed by PARAVIS/ParaView mechanisms - see function createView() and + * also executePythonInMainThread() in SalomePyQt.cxx + */ + + //! Acquire the import lock + static bool AcquireImportLock(); + //! Release the Python import lock if it is held (and return true). Otherwise return false. + static bool ReleaseImportLockIfLocked(); +private: + // "Rule of 3" - Forbid usage of copy operator and copy-constructor + PyLockWrapper(const PyLockWrapper & another); + const PyLockWrapper & operator=(const PyLockWrapper & another); + + static PyObjWrapper _imp_module; +}; + + #endif diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx index 22099f8e4..cffbdcb05 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx @@ -52,6 +52,7 @@ #include "SUIT_ResourceMgr.h" #include "SUIT_Session.h" #include "SUIT_Tools.h" +#include "PyConsole_Interp.h" #include "PyConsole_Console.h" #include @@ -2657,7 +2658,14 @@ public: }; int SalomePyQt::createView( const QString& type, bool visible, const int width, const int height ) { - return ProcessEvent( new TCreateView( type, visible, width, height ) ); + bool impLock = PyLockWrapper::ReleaseImportLockIfLocked(); + int ret = ProcessEvent( new TCreateView( type, visible, width, height ) ); + + // restore the state of the import lock + if (impLock) + PyLockWrapper::AcquireImportLock(); + + return ret; } /*! @@ -3829,21 +3837,45 @@ void SalomePyQt::setPlot2dFitRange(const int id, const double XMin, const double ProcessVoidEvent( new TPlot2dFitRange(id, XMin, XMax, YMin, YMax) ); } -//class TInitParaview: public SALOME_Event -//{ -//public: -// TInitParaview() {} -// virtual void Execute() { -// LightApp_Application* anApp = getApplication(); -// // Create PVViewer_ViewManager, which will initialize ParaView stuff -// PVViewer_ViewManager* viewMgr = -// dynamic_cast( anApp->getViewManager( PVViewer_Viewer::Type(), true ) ); -// } -//}; -//void SalomePyQt::initializeParaViewGUI() -//{ -// ProcessVoidEvent( new TInitParaview() ); -//} +/*! + * Execute a Python command in the embedded console context, and in the main thread. + */ +class TExecutePython: public SALOME_Event +{ +public: + QString myCmd; + PyObject * myGlobals; + PyObject * myLocals; + TExecutePython(const QString& cmd, PyObject * globals, PyObject * locals): + myGlobals(globals), + myLocals(locals) + { + myCmd = cmd; + } + virtual void Execute() { + // Get console interpreter + LightApp_Application* anApp = getApplication(); + PyConsole_Console * console = anApp->pythonConsole(false); + if (console) + { + PyLockWrapper lock; + console->getInterp()->run( myCmd.toUtf8().data(), myGlobals, myLocals ); + } + } +}; + +/**! + * Execute Python code in SALOME's main thread. + * This is very similar to the native "exec" statement of Python, with the specification + * of an execution context. + */ +void SalomePyQt::executePythonInMainThread(const QString& cmd, PyObject * globals, PyObject * locals) +{ + bool importLock = PyLockWrapper::ReleaseImportLockIfLocked(); + ProcessVoidEvent( new TExecutePython(cmd, globals, locals) ); + if (importLock) + PyLockWrapper::AcquireImportLock(); +} void SalomePyQt::processEvents() { diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h index ba63049a2..0c82d7cba 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h @@ -295,8 +295,8 @@ public: static QList getPlot2dFitRangeCurrent(const int); static void setPlot2dFitRange(const int, const double XMin, const double XMax, const double YMin, const double YMax); -// static void initializeParaViewGUI(); static void processEvents(); + static void executePythonInMainThread(const QString& cmd, PyObject * globals, PyObject * locals); // the following methods are obsolete static void addStringSetting( const QString&, const QString&, bool = true ); diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip index 634465781..47170384d 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip @@ -38,6 +38,7 @@ #include %End + class SALOME_Selection : QObject { %TypeHeaderCode @@ -466,8 +467,8 @@ public: static void setPlot2dFitRange(const int, const double XMin, const double XMax, const double YMin, const double YMax ) /ReleaseGIL/ ; static void processEvents(); + static void executePythonInMainThread(const QString& cmd, PyObject * globals, PyObject * locals); - static void startPyLog(const QString&) /ReleaseGIL/ ; static void stopPyLog() /ReleaseGIL/ ; }; diff --git a/src/SUITApp/CMakeLists.txt b/src/SUITApp/CMakeLists.txt index 1262d614e..137705ea2 100755 --- a/src/SUITApp/CMakeLists.txt +++ b/src/SUITApp/CMakeLists.txt @@ -30,6 +30,7 @@ INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}/src/Qtx ${PROJECT_SOURCE_DIR}/src/SUIT ${PROJECT_SOURCE_DIR}/src/Style + ${PROJECT_SOURCE_DIR}/src/PyInterp ) # additional preprocessor / compiler flags @@ -39,7 +40,7 @@ IF(ENABLE_TESTRECORDER) ENDIF() # libraries to link to -SET(_link_LIBRARIES ${PLATFORM_LIBS} ${QT_LIBRARIES} qtx suit SalomeStyle) +SET(_link_LIBRARIES ${PLATFORM_LIBS} ${QT_LIBRARIES} qtx suit SalomeStyle PyInterp) IF(SALOME_USE_PYCONSOLE) LIST(APPEND _link_LIBRARIES ${PYTHON_LIBRARIES}) ENDIF() diff --git a/src/SUITApp/SUITApp_init_python.cxx b/src/SUITApp/SUITApp_init_python.cxx index 6bf01ab0e..19af19b0d 100644 --- a/src/SUITApp/SUITApp_init_python.cxx +++ b/src/SUITApp/SUITApp_init_python.cxx @@ -21,6 +21,7 @@ // Date : 22/06/2007 // #include "SUITApp_init_python.hxx" +#include "PyInterp_Utils.h" bool SUIT_PYTHON::initialized = false; @@ -28,6 +29,7 @@ void SUIT_PYTHON::init_python(int argc, char **argv) { if (Py_IsInitialized()) { + PyLockWrapper::Initialize(); return; } Py_SetProgramName(argv[0]); @@ -35,6 +37,7 @@ void SUIT_PYTHON::init_python(int argc, char **argv) PySys_SetArgv(argc, argv); PyEval_InitThreads(); // Create (and acquire) the interpreter lock - can be called many times + PyLockWrapper::Initialize(); // Py_InitThreads acquires the GIL PyThreadState *pts = PyGILState_GetThisThreadState(); PyEval_ReleaseThread(pts); diff --git a/src/SalomeApp/SalomeApp_PyInterp.cxx b/src/SalomeApp/SalomeApp_PyInterp.cxx index ad228cb3e..7bb709e68 100755 --- a/src/SalomeApp/SalomeApp_PyInterp.cxx +++ b/src/SalomeApp/SalomeApp_PyInterp.cxx @@ -48,16 +48,6 @@ SalomeApp_PyInterp::~SalomeApp_PyInterp() { } -/*! - Do nothing (we could rely on the test done in the implementation of this method in the super - class PyInterp_Interp, but in this context we are sure the initialization has been done in main() - of SALOME_Session_Server) - */ -void SalomeApp_PyInterp::initPython() -{ - MESSAGE("SalomeApp_PyInterp::initPython - does nothing"); -} - /*! Called before each Python command running. */ diff --git a/src/SalomeApp/SalomeApp_PyInterp.h b/src/SalomeApp/SalomeApp_PyInterp.h index bf7bd8d50..4fe412741 100755 --- a/src/SalomeApp/SalomeApp_PyInterp.h +++ b/src/SalomeApp/SalomeApp_PyInterp.h @@ -35,7 +35,6 @@ public: SalomeApp_PyInterp(); virtual ~SalomeApp_PyInterp(); - virtual void initPython(); virtual void initStudy(); virtual void closeContext();