]> SALOME platform Git repositories - modules/gui.git/blobdiff - src/PyInterp/PyInterp_Interp.cxx
Salome HOME
INT PAL 0052660: Plot2D Viewer: Plot2d_Curve can't be selected
[modules/gui.git] / src / PyInterp / PyInterp_Interp.cxx
index c2dcd254a5995972675410c15115ff6f3d8b3bfc..48c6a8eb5b5a2e415f0d7a90e9031ce9f788a40d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2013  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
 //
 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
@@ -6,7 +6,7 @@
 // This library is free software; you can redistribute it and/or
 // modify it under the terms of the GNU Lesser General Public
 // License as published by the Free Software Foundation; either
 // This library is free software; you can redistribute it and/or
 // modify it under the terms of the GNU Lesser General Public
 // License as published by the Free Software Foundation; either
-// version 2.1 of the License.
+// version 2.1 of the License, or (at your option) any later version.
 //
 // This library is distributed in the hope that it will be useful,
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 //
 // This library is distributed in the hope that it will be useful,
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 //
 
 //  File   : PyInterp_Interp.cxx
 //
 
 //  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 !!!
 //  Module : SALOME
 //
 #include "PyInterp_Interp.h"  // !!! WARNING !!! THIS INCLUDE MUST BE THE VERY FIRST !!!
+#include "PyInterp_Utils.h"
 #include <pythread.h>
 
 #include <cStringIO.h>
 #include <pythread.h>
 
 #include <cStringIO.h>
 #define TOP_HISTORY_PY   "--- top of history ---"
 #define BEGIN_HISTORY_PY "--- begin of history ---"
 
 #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<long,PyThreadState*> 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.
 /*
   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*)""};
 
 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.
 
 /*!
   \brief Basic constructor.
@@ -243,15 +167,18 @@ PyInterpreterState* PyInterp_Interp::_interp       = NULL;
   must call virtual method initalize().
 */
 PyInterp_Interp::PyInterp_Interp():
   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()
 {
 /*!
   \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
   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
   - 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();
 
   _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();
 
 
   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();
   PyObjWrapper m(PyImport_ImportModule("codeop"));
   if(!m) {
     PyErr_Print();
-    PyEval_ReleaseThread(_tstate);
     return;
   }
 
     return;
   }
 
@@ -294,17 +220,22 @@ void PyInterp_Interp::initialize()
 
   // All the initRun outputs are redirected to the standard output (console)
   initRun();
 
   // 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.
 
 }
 
 /*!
   \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.
   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()
 {
  */
 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);
     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
  */
 }
 
 /*!
   \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";
   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;
 }
 
   return aBanner;
 }
 
@@ -350,37 +273,60 @@ std::string PyInterp_Interp::getbanner()
 */
 bool PyInterp_Interp::initRun()
 {
 */
 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;
 }
 
   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
 /*!
   \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
   \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
  */
   \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;
   }
 {
   PyObject *m = PyImport_AddModule("codeop");
   if(!m) { // Fatal error. No way to go on.
     PyErr_Print();
     return -1;
   }
+
+//  PyObjWrapper v(Py_CompileString(command, "<salome_input>", Py_file_input));
   PyObjWrapper v(PyObject_CallMethod(m,(char*)"compile_command",(char*)"s",command));
   if(!v) {
     // Error encountered. It should be SyntaxError,
   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 {
     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();
     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
  */
   \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;
 {
   // 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)
   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]")
   }
   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";
     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();
   \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
   \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();
   }
 
     _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);
 
   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;
 }
 
   return ier;
 }