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