Salome HOME
INT PAL 0052660: Plot2D Viewer: Plot2d_Curve can't be selected
[modules/gui.git] / src / PyInterp / PyInterp_Interp.cxx
index 663da5b5590e958f576d974ab19d13512b029c78..48c6a8eb5b5a2e415f0d7a90e9031ce9f788a40d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2012  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
@@ -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
-// 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
 //
 
 //  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 <pythread.h>
 
 #include <cStringIO.h>
 #include <vector>
 #include <map>
 #include <iostream>
+#include <algorithm>
 
 #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 
+  The following functions are used to hook the Python
   interpreter output.
 */
 
@@ -231,35 +159,34 @@ 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.
-  
-  After construction the interpreter instance successor classes 
+
+  After construction the interpreter instance successor classes
   must call virtual method initalize().
 */
-PyInterp_Interp::PyInterp_Interp(): 
-  _tstate(0), _vout(0), _verr(0), _g(0)
+PyInterp_Interp::PyInterp_Interp():
+  _vout(0), _verr(0), _local_context(0), _global_context(0)
 {
 }
 
+
+
 /*!
   \brief Destructor.
 */
 PyInterp_Interp::~PyInterp_Interp()
 {
+  destroy();
 }
 
 /*!
   \brief Initialize embedded 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
@@ -267,43 +194,48 @@ PyInterp_Interp::~PyInterp_Interp()
 */
 void PyInterp_Interp::initialize()
 {
-  _history.clear();       // start a new list of user's commands 
+  _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;
   }
 
   // Create python objects to capture stdout and stderr
-  _vout=(PyObject*)newPyStdOut( false ); // stdout 
+  _vout=(PyObject*)newPyStdOut( false ); // stdout
   _verr=(PyObject*)newPyStdOut( true );  // stderr
 
   // 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()
 {
@@ -312,74 +244,89 @@ 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;
 }
 
 /*!
   \brief Initialize run command.
-  This method is used to prepare interpreter for running 
+
+  This method is used to prepare interpreter for running
   Python commands.
-  
+
   \return \c true on success and \c false on error
 */
 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;
 }
 
 /*!
-  \brief Compile Python command and evaluate it in the 
-         python dictionary context if possible.
+ * 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 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 compile_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, "<salome_input>", Py_file_input));
   PyObjWrapper v(PyObject_CallMethod(m,(char*)"compile_command",(char*)"s",command));
   if(!v) {
     // Error encountered. It should be SyntaxError,
@@ -395,12 +342,7 @@ static int compile_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();
@@ -411,19 +353,93 @@ static int compile_command(const char *command,PyObject *context)
   }
 }
 
+void replaceAll(std::string& str, const std::string& from, const std::string& to) {
+    if(from.empty())
+        return;
+    size_t start_pos = 0;
+    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
+        str.replace(start_pos, from.length(), to);
+        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
+    }
+}
 /*!
-  \brief Run Python command.
+  \brief Compile Python command and evaluate it in the
+         python dictionary context if possible. Command might correspond to
+         the execution of a script with optional arguments.
+         In this case, command is:
+         execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]")
+  \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 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;
+  std::string commandArgs = "";
+
+  std::size_t pos = std::string(command).find("args:");
+  if (pos != std::string::npos) {
+    commandArgs = singleCommand.substr(pos+5);
+    commandArgs = commandArgs.substr(0, commandArgs.length()-3);
+    singleCommand = singleCommand.substr(0, pos-1)+"\")";
+  }
+
+  if (commandArgs.empty()) {
+    // process command: expression
+    // process command: execfile(r"/absolute/path/to/script.py") (no args)
+    return run_command(singleCommand.c_str(), global_ctxt, local_ctxt);
+  }
+  else {
+    // process command: execfile(r"/absolute/path/to/script.py [args:arg1,...,argn]")
+    std::string script = singleCommand.substr(11); // remove leading execfile(r"
+    script = script.substr(0, script.length()-2); // remove trailing ")
+
+    std::string preCommandBegin = "import sys; save_argv = sys.argv; sys.argv=[";
+    std::string preCommandEnd = "];";
+    replaceAll(commandArgs, ",", "\",\"");
+    commandArgs = "\""+commandArgs+"\"";
+    std::string completeCommand = preCommandBegin+"\""+script+"\","+commandArgs+preCommandEnd+singleCommand+";sys.argv=save_argv";
+    return run_command(completeCommand.c_str(), global_ctxt, local_ctxt);
+  }
+}
+
+/*!
+  \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
@@ -435,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;
 }
@@ -487,7 +506,7 @@ const char * PyInterp_Interp::getNext()
   \param data callback function parameters
 */
 void PyInterp_Interp::setvoutcb(PyOutChanged* cb, void* data)
-{  
+{
   ((PyStdOut*)_vout)->_cb=cb;
   ((PyStdOut*)_vout)->_data=data;
 }
@@ -498,7 +517,7 @@ void PyInterp_Interp::setvoutcb(PyOutChanged* cb, void* data)
   \param data callback function parameters
 */
 void PyInterp_Interp::setverrcb(PyOutChanged* cb, void* data)
-{  
+{
   ((PyStdOut*)_verr)->_cb=cb;
   ((PyStdOut*)_verr)->_data=data;
 }