diff --git a/apps/dsm/mods/mod_py/Makefile b/apps/dsm/mods/mod_py/Makefile index 761d7194..210479ab 100644 --- a/apps/dsm/mods/mod_py/Makefile +++ b/apps/dsm/mods/mod_py/Makefile @@ -4,6 +4,10 @@ PYTHON_VERSION ?= $(shell python -c 'import sys;print sys.version[0:3]') PY_VER = $(PYTHON_VERSION) PY_EXE = python$(PY_VER) +# for local python build, use e.g.: +#PY_VER = 2.6 +#PY_EXE = python2.6/bin/python + DSMPATH ?= ../.. PYTHON_DIR = $(shell $(PY_EXE) ./python_inc.py) diff --git a/apps/dsm/mods/mod_py/ModPy.cpp b/apps/dsm/mods/mod_py/ModPy.cpp index 7fc18072..ebea1ce7 100644 --- a/apps/dsm/mods/mod_py/ModPy.cpp +++ b/apps/dsm/mods/mod_py/ModPy.cpp @@ -33,6 +33,11 @@ #include "AmSession.h" #include "PyDSMSession.h" #include "PyDSM.h" +#include "AmArg.h" + #include + +#include "grammar.h" +#include "pythread.h" struct PythonGIL { @@ -47,14 +52,13 @@ SC_EXPORT(SCPyModule); PyObject* SCPyModule::dsm_module = NULL; PyObject* SCPyModule::session_module = NULL; +PyInterpreterState* SCPyModule::interp = NULL; +PyThreadState* SCPyModule::tstate = NULL; SCPyModule::SCPyModule() { } -SCPyModule::~SCPyModule() { -} - int SCPyModule::preload() { if(!Py_IsInitialized()){ add_env_path("PYTHONPATH",AmConfig::PlugInPath); @@ -64,6 +68,9 @@ int SCPyModule::preload() { PyEval_InitThreads(); + interp = PyThreadState_Get()->interp; + tstate = PyThreadState_Get(); + PyImport_AddModule("dsm"); dsm_module = Py_InitModule("dsm",mod_py_methods); PyModule_AddIntConstant(dsm_module, "Any", DSMCondition::Any); @@ -73,7 +80,8 @@ int SCPyModule::preload() { PyModule_AddIntConstant(dsm_module, "Timer", DSMCondition::Timer); PyModule_AddIntConstant(dsm_module, "NoAudio", DSMCondition::NoAudio); PyModule_AddIntConstant(dsm_module, "Hangup", DSMCondition::Hangup); - PyModule_AddIntConstant(dsm_module, "Hold", DSMCondition::Hold); PyModule_AddIntConstant(dsm_module, "UnHold", DSMCondition::UnHold); + PyModule_AddIntConstant(dsm_module, "Hold", DSMCondition::Hold); + PyModule_AddIntConstant(dsm_module, "UnHold", DSMCondition::UnHold); PyModule_AddIntConstant(dsm_module, "XmlrpcResponse", DSMCondition::XmlrpcResponse); PyModule_AddIntConstant(dsm_module, "DSMEvent", DSMCondition::DSMEvent); PyModule_AddIntConstant(dsm_module, "PlaylistSeparator", DSMCondition::PlaylistSeparator); @@ -129,6 +137,48 @@ DSMCondition* SCPyModule::getCondition(const string& from_str) { return NULL; } +SCPyDictArg::SCPyDictArg() + : pPyObject(NULL) { +} + +SCPyDictArg::SCPyDictArg(PyObject* pPyObject) + : pPyObject(pPyObject) { +} + +SCPyDictArg::~SCPyDictArg() { + PYLOCK; + + if (NULL != pPyObject) { + PyDict_Clear(pPyObject); + } + Py_XDECREF(pPyObject); +} + +PyObject* getPyLocals(DSMSession* sc_sess) { + map::iterator l_it; + SCPyDictArg* py_arg = NULL; + ArgObject* py_locals_obj; + + if (((l_it=sc_sess->avar.find("py_locals")) != sc_sess->avar.end()) && + (l_it->second.getType() == AmArg::AObject) && + ((py_locals_obj = l_it->second.asObject()) != NULL) && + ((py_arg = dynamic_cast(py_locals_obj)) != NULL) && + (py_arg->pPyObject != NULL) + ) { + return py_arg->pPyObject; + } + + PyObject* locals = PyDict_New(); + PyDict_SetItemString(locals, "dsm", SCPyModule::dsm_module); + PyDict_SetItemString(locals, "session", SCPyModule::session_module); + + py_arg = new SCPyDictArg(locals); + sc_sess->transferOwnership(py_arg); + sc_sess->avar["py_locals"] = AmArg(py_arg); + + return locals; +} + bool py_execute(PyCodeObject* py_func, DSMSession* sc_sess, DSMCondition::EventType event, map* event_params, bool expect_int_result) { @@ -136,17 +186,15 @@ bool py_execute(PyCodeObject* py_func, DSMSession* sc_sess, PYLOCK; bool py_res = false; - + DBG("add main \n"); PyObject* m = PyImport_AddModule("__main__"); if (m == NULL) { ERROR("getting main module\n"); return false; } - PyObject*d = PyModule_GetDict(m); - - PyObject* locals = PyDict_New(); - PyDict_SetItemString(locals, "dsm", SCPyModule::dsm_module); - PyDict_SetItemString(locals, "session", SCPyModule::session_module); + DBG("get globals \n"); + PyObject* globals = PyModule_GetDict(m); + PyObject* locals = getPyLocals(sc_sess); PyObject* params = PyDict_New(); if (NULL != event_params) { @@ -158,27 +206,31 @@ bool py_execute(PyCodeObject* py_func, DSMSession* sc_sess, } } PyDict_SetItemString(locals, "params", params); - Py_DECREF(params); PyObject *t = PyInt_FromLong(event); PyDict_SetItemString(locals, "type", t); - Py_DECREF(t); PyObject* py_sc_sess = PyCObject_FromVoidPtr(sc_sess,NULL); PyObject* ts_dict = PyThreadState_GetDict(); PyDict_SetItemString(ts_dict, "_dsm_sess_", py_sc_sess); - Py_DECREF(py_sc_sess); + Py_DECREF(py_sc_sess); // call the function - PyObject* res = PyEval_EvalCode((PyCodeObject*)py_func, d, locals); + PyObject* res = PyEval_EvalCode((PyCodeObject*)py_func, globals, locals); if(PyErr_Occurred()) PyErr_Print(); + + PyDict_DelItemString(locals, "params"); + PyDict_Clear(params); + Py_DECREF(params); + + PyDict_DelItemString(locals, "type"); + Py_DECREF(t); - ts_dict = PyThreadState_GetDict(); // should be the same as before + // ts_dict = PyThreadState_GetDict(); // should be the same as before PyDict_DelItemString(ts_dict, "_dsm_sess_"); - Py_DECREF(locals); if (NULL == res) { ERROR("evaluating python code\n"); } else if (PyBool_Check(res)) { @@ -195,7 +247,8 @@ bool py_execute(PyCodeObject* py_func, DSMSession* sc_sess, } SCPyPyAction::SCPyPyAction(const string& arg) { - py_func = Py_CompileString(arg.c_str(), "", Py_file_input); + PYLOCK; + py_func = Py_CompileString(arg.c_str(), ("").c_str(), Py_file_input); if (NULL == py_func) { ERROR("compiling python code '%s'\n", arg.c_str()); @@ -214,7 +267,8 @@ EXEC_ACTION_START(SCPyPyAction) { PyPyCondition::PyPyCondition(const string& arg) { - py_func = Py_CompileString(arg.c_str(), "", Py_eval_input); + PYLOCK; + py_func = Py_CompileString(arg.c_str(), ("").c_str(), Py_eval_input); if (NULL == py_func) { ERROR("compiling python code '%s'\n", arg.c_str()); @@ -229,3 +283,61 @@ MATCH_CONDITION_START(PyPyCondition) { return py_execute((PyCodeObject*)py_func, sc_sess, event, event_params, false); } MATCH_CONDITION_END; + + +// define PYDSM_WITH_MEM_DEBUG + +#ifndef PYDSM_WITH_MEM_DEBUG +SCPyModule::~SCPyModule() { } +#else + +void printdict(PyObject* p, char* name) { + return; + + DBG("dict %s %p -------------\n", name, p); + PyObject *key, *value; + Py_ssize_t pos = 0; + + while (PyDict_Next(p, &pos, &key, &value)) { + DBG(" obj '%s' ref %d\n", PyString_AsString(key), key->ob_refcnt); + } + DBG("dict %p end -------------\n", p); +} + +extern grammar _PyParser_Grammar; /* From graminit.c */ + +SCPyModule::~SCPyModule() { + //PYLOCK; + PyEval_AcquireThread(tstate); + FILE* f = fopen("refs.txt", "w"); + + _Py_PrintReferences(f); + + /* Disable signal handling */ + PyOS_FiniInterrupts(); + + PyInterpreterState_Clear(interp); + + + /* Delete current thread */ + PyThreadState_Swap(NULL); + PyInterpreterState_Delete(interp); + + /* Sundry finalizers */ + PyMethod_Fini(); + PyFrame_Fini(); + PyCFunction_Fini(); + PyTuple_Fini(); + PyList_Fini(); + PySet_Fini(); + PyString_Fini(); + PyInt_Fini(); + PyFloat_Fini(); + + PyGrammar_RemoveAccelerators(&_PyParser_Grammar); + + _Py_PrintReferenceAddresses(f); + + fclose(f); +} +#endif //PYDSM_WITH_MEM_DEBUG diff --git a/apps/dsm/mods/mod_py/ModPy.h b/apps/dsm/mods/mod_py/ModPy.h index f26587c5..226b3a24 100644 --- a/apps/dsm/mods/mod_py/ModPy.h +++ b/apps/dsm/mods/mod_py/ModPy.h @@ -35,7 +35,6 @@ class SCPyModule : public DSMModule { - public: SCPyModule(); @@ -47,6 +46,19 @@ class SCPyModule DSMCondition* getCondition(const string& from_str); static PyObject* dsm_module; static PyObject* session_module; + static PyInterpreterState* interp; + static PyThreadState* tstate; +}; + +/** smart ArgObject that "owns" a python dictionary reference */ +struct SCPyDictArg + : public ArgObject, + public DSMDisposable +{ + SCPyDictArg(); + SCPyDictArg(PyObject* pPyObject); + ~SCPyDictArg(); + PyObject* pPyObject; }; class SCPyPyAction @@ -57,17 +69,17 @@ class SCPyPyAction bool execute(AmSession* sess, DSMCondition::EventType event, map* event_params); - }; +}; class PyPyCondition : public DSMCondition { - - PyObject* py_func; - public: - - PyPyCondition(const string& arg); - bool match(AmSession* sess, DSMCondition::EventType event, - map* event_params); - }; + + PyObject* py_func; + public: + + PyPyCondition(const string& arg); + bool match(AmSession* sess, DSMCondition::EventType event, + map* event_params); +}; #endif diff --git a/apps/dsm/mods/mod_py/Readme.mod_py.txt b/apps/dsm/mods/mod_py/Readme.mod_py.txt index 5ecb1bcc..880606e4 100644 --- a/apps/dsm/mods/mod_py/Readme.mod_py.txt +++ b/apps/dsm/mods/mod_py/Readme.mod_py.txt @@ -11,7 +11,8 @@ The 'type' and 'params' are accessible to determine the current event's type and parameters. py() actions and conditions can access session's variables using -session.var(name) and session.setvar(name, value). +session.var(name) and session.setvar(name, value). Locals stay +across different py(...) actions/conditions. They may even directly use some media functionality implemented in DSM sessions - see session module's help below. But, @@ -170,3 +171,28 @@ DATA UnHold = 8 XmlrpcResponse = 9 + + +how to debug memory leak: +------------------------- + +Unfortunately, it seems to be not simple to get embedded +python interpreter leak free. Here is how to run with python's +mem debug: + +compile python with --with-pydebug, e.g. + +./configure --with-pydebug --prefix=/path/to/mod_dsm/python +make && make install + +set debug python in Makefile, e.g. replace PY_VER/PY_EXE: + PY_VER = 2.5 + PY_EXE = ./python/bin/python + +make mod_py with -D PYDSM_WITH_MEM_DEBUG + +run sems with -E, make calls, end sems with ctrl-c +from python do scripts/combinerefs.py refs.txt. +generate calls with e.g. sipp: +sipp -sn uac -i 192.168.5.106 -s 35 -d 500 -r 400 192.168.5.106:5070 +(sudo su; ulimit -n 100000 before starting sems)