#include "freeswitch_python.h" #define sanity_check(x) do { if (!session) { switch_log_printf(SWITCH_CHANNEL_LOG,SWITCH_LOG_ERROR, "session is not initalized\n"); return x;}} while(0) #define init_vars() do { caller_profile.source = "mod_python"; swapstate = S_SWAPPED_IN; } while(0) PySession::PySession():CoreSession() { init_vars(); } PySession::PySession(char *uuid):CoreSession(uuid) { init_vars(); } PySession::PySession(switch_core_session_t *new_session):CoreSession(new_session) { init_vars(); } void PySession::setDTMFCallback(PyObject * pyfunc, char *funcargs) { sanity_check(); if (!PyCallable_Check(pyfunc)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "DTMF function is not a python function.\n"); return; } Py_XINCREF(pyfunc); CoreSession::setDTMFCallback((void *) pyfunc, funcargs); } void PySession::setHangupHook(PyObject * pyfunc) { if (!PyCallable_Check(pyfunc)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Hangup hook is not a python function.\n"); return; } // without this Py_XINCREF, there will be segfaults. basically the python // interpreter will not know that it should not GC this object. // callback example: http://docs.python.org/ext/callingPython.html Py_XINCREF(pyfunc); CoreSession::setHangupHook((void *) pyfunc); } void PySession::check_hangup_hook() { PyObject *func; PyObject *result; char *resultStr; bool did_swap_in = false; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "check_hangup_hook has been DISABLED, please do not use hangup hooks in python code until further notice!\n"); if (!session) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No valid session\n"); return; } return; /*! NEEDS TO BE FIXED: // The did_swap_in boolean was added to fix the following problem: // Design flaw - we swap in threadstate based on the assumption that thread state // is currently _swapped out_ when this hangup hook is called. However, nothing known to // guarantee that, and if thread state is already swapped in when this is invoked, // bad things will happen. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "check hangup hook end_allow_threads\n"); did_swap_in = end_allow_threads(); if (on_hangup == NULL) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "on_hangup is null\n"); return; } func = (PyObject *) on_hangup; // TODO: to match js implementation, should pass the _python_ PySession // object instance wrapping this C++ PySession instance. but how do we do that? // for now, pass the uuid since its better than nothing PyObject* func_arg = Py_BuildValue("(s)", uuid); result = PyEval_CallObject(func, func_arg); Py_XDECREF(func_arg); if (result) { resultStr = (char *) PyString_AsString(result); // currently just ignore the result } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to call python hangup callback\n"); PyErr_Print(); PyErr_Clear(); } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "check hangup hook begin_allow_threads\n"); if (did_swap_in) { begin_allow_threads(); } Py_XDECREF(result); */ } switch_status_t PySession::run_dtmf_callback(void *input, switch_input_type_t itype) { PyObject *func, *arglist; PyObject *pyresult; PyObject *headerdict; char *resultStr; char *funcargs; switch_file_handle_t *fh = NULL; bool did_swap_in = false; switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "run_dtmf_callback\n"); if (!cb_state.function) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "cb_state->function is null\n"); return SWITCH_STATUS_FALSE; } func = (PyObject *) cb_state.function; if (!func) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "cb_state->function is null\n"); return SWITCH_STATUS_FALSE; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "cb_state->function is NOT null\n"); } if (!PyCallable_Check(func)) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "function not callable\n"); return SWITCH_STATUS_FALSE; } funcargs = (char *) cb_state.funcargs; if (itype == SWITCH_INPUT_TYPE_DTMF) { arglist = Py_BuildValue("(sis)", input, itype, funcargs); } else if (itype == SWITCH_INPUT_TYPE_EVENT) { // DUNNO if this is correct in the case we have an event // will be of type switch_event_t *event; // http://www.freeswitch.org/docs/structswitch__event.html switch_event_t *event = (switch_event_t *) input; arglist = Py_BuildValue("({s:s}is)", "body", event->body, itype, funcargs); // build a dictionary with all the headers switch_event_header_t *hp; headerdict = PyDict_New(); for (hp = event->headers; hp; hp = hp->next) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Adding event header to result"); // TODO: create PyObject pointers for name and value // and explicitly decref them. all ref counting stuff is // a mess and needs to be tested and looked at closer. PyDict_SetItem(headerdict, Py_BuildValue("s", hp->name), Py_BuildValue("s", hp->value)); } // add it to the main event dictionary (first arg in list) // under key 'headers' PyObject *dict = PyTuple_GetItem(arglist, 0); PyDict_SetItemString(dict, "headers", headerdict); Py_XDECREF(headerdict); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unknown input type: %d\n", itype); return SWITCH_STATUS_FALSE; } if (!arglist) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "error building arglist"); return SWITCH_STATUS_FALSE; } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "run_dtmf_callback end_allow_threads\n"); did_swap_in = end_allow_threads(); pyresult = PyEval_CallObject(func, arglist); Py_XDECREF(arglist); // Trash arglist if (pyresult && pyresult != Py_None) { resultStr = (char *) PyString_AsString(pyresult); switch_status_t cbresult = process_callback_result(resultStr); return cbresult; } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Python callback\n returned None"); PyErr_Print(); PyErr_Clear(); } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "run_dtmf_callback begin_allow_threads\n"); if (did_swap_in) { begin_allow_threads(); } Py_XDECREF(pyresult); return SWITCH_STATUS_SUCCESS; } bool PySession::begin_allow_threads(void) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "PySession::begin_allow_threads() called\n"); if (!session) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No valid session\n"); return false; } // swap out threadstate and store in instance variable switch_channel_t *channel = switch_core_session_get_channel(session); PyThreadState *swapin_tstate = (PyThreadState *) switch_channel_get_private(channel, "SwapInThreadState"); // so lets assume the thread state was swapped in when the python script was started, // therefore swapin_tstate will be NULL (because there is nothing to swap in, since its // _already_ swapped in.) if (swapin_tstate == NULL) { // currently swapped in switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Threadstate swap-out!\n"); swapin_tstate = PyEval_SaveThread(); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "swapin_tstate: %p\n", swapin_tstate); // give future swapper-inners something to actually swap in switch_channel_set_private(channel, "SwapInThreadState", (void *) swapin_tstate); cb_state.threadState = threadState; // TODO: get rid of this args.buf = &cb_state; ap = &args; return true; } else { // currently swapped out return false; } } bool PySession::end_allow_threads(void) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "PySession::end_allow_threads() called\n"); // swap in threadstate from instance variable saved earlier if (!session) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No valid session\n"); return false; } switch_channel_t *channel = switch_core_session_get_channel(session); PyThreadState *swapin_tstate = (PyThreadState *) switch_channel_get_private(channel, "SwapInThreadState"); if (swapin_tstate == NULL) { // currently swapped in return false; } else { // currently swapped out switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Threadstate swap-in!\n"); switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "swapin_tstate: %p\n", swapin_tstate); PyEval_RestoreThread(swapin_tstate); // dont give any swapper-inners the opportunity to do a double swap switch_channel_set_private(channel, "SwapInThreadState", NULL); return true; } } void PySession::hangup(char *cause) { // since we INCREF'd this function pointer earlier (so the py gc didnt reclaim it) // we have to DECREF it, or else the PySession dtor will never get called and // a zombie channel will be left over using up resources if (cb_state.function != NULL) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "xdecref on cb_state_function\n"); PyObject *func = (PyObject *) cb_state.function; Py_XDECREF(func); } else { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "cb_state.function is null\n"); } CoreSession::hangup(cause); } PySession::~PySession() { // Should we do any cleanup here? switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "PySession::~PySession started\n"); if (on_hangup) { PyObject *func = (PyObject *) on_hangup; Py_XDECREF(func); } if (cb_state.function != NULL) { PyObject *func = (PyObject *) cb_state.function; Py_XDECREF(func); } switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "PySession::~PySession finished\n"); }