/* Implementation of PendingCall helper type for D-Bus bindings. * * Copyright (C) 2006 Collabora Ltd. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "dbus_bindings-internal.h" PyDoc_STRVAR(PendingCall_tp_doc, "Object representing a pending D-Bus call, returned by\n" "Connection.send_message_with_reply(). Cannot be instantiated directly.\n" ); static PyTypeObject PendingCallType; static inline int PendingCall_Check (PyObject *o) { return (Py_TYPE(o) == &PendingCallType) || PyObject_IsInstance(o, (PyObject *)&PendingCallType); } typedef struct { PyObject_HEAD DBusPendingCall *pc; } PendingCall; PyDoc_STRVAR(PendingCall_cancel__doc__, "cancel()\n\n" "Cancel this pending call. Its reply will be ignored and the associated\n" "reply handler will never be called.\n"); static PyObject * PendingCall_cancel(PendingCall *self, PyObject *unused UNUSED) { Py_BEGIN_ALLOW_THREADS dbus_pending_call_cancel(self->pc); Py_END_ALLOW_THREADS Py_RETURN_NONE; } PyDoc_STRVAR(PendingCall_block__doc__, "block()\n\n" "Block until this pending call has completed and the associated\n" "reply handler has been called.\n" "\n" "This can lead to a deadlock, if the called method tries to make a\n" "synchronous call to a method in this application.\n"); static PyObject * PendingCall_block(PendingCall *self, PyObject *unused UNUSED) { Py_BEGIN_ALLOW_THREADS dbus_pending_call_block(self->pc); Py_END_ALLOW_THREADS Py_RETURN_NONE; } static void _pending_call_notify_function(DBusPendingCall *pc, PyObject *list) { PyGILState_STATE gil = PyGILState_Ensure(); /* BEGIN CRITICAL SECTION * While holding the GIL, make sure the callback only gets called once * by deleting it from the 1-item list that's held by libdbus. */ PyObject *handler = PyList_GetItem(list, 0); DBusMessage *msg; if (!handler) { PyErr_Print(); goto release; } if (handler == Py_None) { /* We've already called (and thrown away) the callback */ goto release; } Py_INCREF(handler); /* previously borrowed from the list, now owned */ Py_INCREF(Py_None); /* take a ref so SetItem can steal it */ PyList_SetItem(list, 0, Py_None); /* END CRITICAL SECTION */ msg = dbus_pending_call_steal_reply(pc); if (!msg) { /* omg, what happened here? the notify should only get called * when we have a reply */ PyErr_Warn(PyExc_UserWarning, "D-Bus notify function was called " "for an incomplete pending call (shouldn't happen)"); } else { PyObject *msg_obj = DBusPyMessage_ConsumeDBusMessage(msg); if (msg_obj) { PyObject *ret = PyObject_CallFunctionObjArgs(handler, msg_obj, NULL); if (!ret) { PyErr_Print(); } Py_CLEAR(ret); Py_CLEAR(msg_obj); } /* else OOM has happened - not a lot we can do about that, * except possibly making it fatal (FIXME?) */ } release: Py_CLEAR(handler); PyGILState_Release(gil); } PyDoc_STRVAR(PendingCall_get_completed__doc__, "get_completed() -> bool\n\n" "Return true if this pending call has completed.\n\n" "If so, its associated reply handler has been called and it is no\n" "longer meaningful to cancel it.\n"); static PyObject * PendingCall_get_completed(PendingCall *self, PyObject *unused UNUSED) { dbus_bool_t ret; Py_BEGIN_ALLOW_THREADS ret = dbus_pending_call_get_completed(self->pc); Py_END_ALLOW_THREADS return PyBool_FromLong(ret); } /* Steals the reference to the pending call. */ PyObject * DBusPyPendingCall_ConsumeDBusPendingCall(DBusPendingCall *pc, PyObject *callable) { dbus_bool_t ret; PyObject *list = PyList_New(1); PendingCall *self = PyObject_New(PendingCall, &PendingCallType); if (!list || !self) { Py_CLEAR(list); Py_CLEAR(self); Py_BEGIN_ALLOW_THREADS dbus_pending_call_cancel(pc); dbus_pending_call_unref(pc); Py_END_ALLOW_THREADS return NULL; } /* INCREF because SET_ITEM steals a ref */ Py_INCREF(callable); PyList_SET_ITEM(list, 0, callable); /* INCREF so we can give a ref to set_notify and still have one */ Py_INCREF(list); Py_BEGIN_ALLOW_THREADS ret = dbus_pending_call_set_notify(pc, (DBusPendingCallNotifyFunction)_pending_call_notify_function, (void *)list, (DBusFreeFunction)dbus_py_take_gil_and_xdecref); Py_END_ALLOW_THREADS if (!ret) { PyErr_NoMemory(); /* DECREF twice - one for the INCREF and one for the allocation */ Py_DECREF(list); Py_CLEAR(list); Py_CLEAR(self); Py_BEGIN_ALLOW_THREADS dbus_pending_call_cancel(pc); dbus_pending_call_unref(pc); Py_END_ALLOW_THREADS return NULL; } /* As Alexander Larsson pointed out on dbus@lists.fd.o on 2006-11-30, * the API has a race condition if set_notify runs in one thread and a * mail loop runs in another - if the reply gets in before set_notify * runs, the notify isn't called and there is no indication of error. * * The workaround is to check for completion immediately, but this also * has a race which might lead to getting the notify called twice if * we're unlucky. So I use the list to arrange for the notify to be * deleted before it's called for the second time. The GIL protects * the critical section in which I delete the callback from the list. */ if (dbus_pending_call_get_completed(pc)) { /* the first race condition happened, so call the callable here. * FIXME: we ought to arrange for the callable to run from the * mainloop thread, like it would if the race hadn't happened... * this needs a better mainloop abstraction, though. */ _pending_call_notify_function(pc, list); } Py_CLEAR(list); self->pc = pc; return (PyObject *)self; } static void PendingCall_tp_dealloc (PendingCall *self) { if (self->pc) { Py_BEGIN_ALLOW_THREADS dbus_pending_call_unref(self->pc); Py_END_ALLOW_THREADS } PyObject_Del (self); } static PyMethodDef PendingCall_tp_methods[] = { {"block", (PyCFunction)PendingCall_block, METH_NOARGS, PendingCall_block__doc__}, {"cancel", (PyCFunction)PendingCall_cancel, METH_NOARGS, PendingCall_cancel__doc__}, {"get_completed", (PyCFunction)PendingCall_get_completed, METH_NOARGS, PendingCall_get_completed__doc__}, {NULL, NULL, 0, NULL} }; static PyTypeObject PendingCallType = { PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) "dbus.lowlevel.PendingCall", sizeof(PendingCall), 0, (destructor)PendingCall_tp_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ PendingCall_tp_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ PendingCall_tp_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ /* deliberately not callable! Use PendingCall_ConsumeDBusPendingCall */ 0, /* tp_new */ }; dbus_bool_t dbus_py_init_pending_call (void) { if (PyType_Ready (&PendingCallType) < 0) return 0; return 1; } dbus_bool_t dbus_py_insert_pending_call (PyObject *this_module) { /* PyModule_AddObject steals a ref */ Py_INCREF (&PendingCallType); if (PyModule_AddObject (this_module, "PendingCall", (PyObject *)&PendingCallType) < 0) return 0; return 1; } /* vim:set ft=c cino< sw=4 sts=4 et: */