Blame _dbus_bindings/pending-call.c

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: */