-// 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
// 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.
*/
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
*/
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()
{
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,
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();
}
}
+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
_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;
}
\param data callback function parameters
*/
void PyInterp_Interp::setvoutcb(PyOutChanged* cb, void* data)
-{
+{
((PyStdOut*)_vout)->_cb=cb;
((PyStdOut*)_vout)->_data=data;
}
\param data callback function parameters
*/
void PyInterp_Interp::setverrcb(PyOutChanged* cb, void* data)
-{
+{
((PyStdOut*)_verr)->_cb=cb;
((PyStdOut*)_verr)->_data=data;
}