Update NEWS, README for 0.80.0
[dbus-python-phuang.git] / _dbus_bindings / pending-call.c
blobf1c8db8077c2cc751dfd2ac037fd49a30a4cc62d
1 /* Implementation of PendingCall helper type for D-Bus bindings.
2  *
3  * Copyright (C) 2006 Collabora Ltd.
4  *
5  * Licensed under the Academic Free License version 2.1
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
25 #include "dbus_bindings-internal.h"
27 PyDoc_STRVAR(PendingCall_tp_doc,
28 "Object representing a pending D-Bus call, returned by\n"
29 "Connection.send_message_with_reply(). Cannot be instantiated directly.\n"
32 static PyTypeObject PendingCallType;
34 static inline int PendingCall_Check (PyObject *o)
36     return (o->ob_type == &PendingCallType)
37             || PyObject_IsInstance(o, (PyObject *)&PendingCallType);
40 typedef struct {
41     PyObject_HEAD
42     DBusPendingCall *pc;
43 } PendingCall;
45 PyDoc_STRVAR(PendingCall_cancel__doc__,
46 "cancel()\n\n"
47 "Cancel this pending call. Its reply will be ignored and the associated\n"
48 "reply handler will never be called.\n");
49 static PyObject *
50 PendingCall_cancel(PendingCall *self, PyObject *unused UNUSED)
52     Py_BEGIN_ALLOW_THREADS
53     dbus_pending_call_cancel(self->pc);
54     Py_END_ALLOW_THREADS
55     Py_RETURN_NONE;
58 PyDoc_STRVAR(PendingCall_block__doc__,
59 "block()\n\n"
60 "Block until this pending call has completed and the associated\n"
61 "reply handler has been called.\n"
62 "\n"
63 "This can lead to a deadlock, if the called method tries to make a\n"
64 "synchronous call to a method in this application.\n");
65 static PyObject *
66 PendingCall_block(PendingCall *self, PyObject *unused UNUSED)
68     Py_BEGIN_ALLOW_THREADS
69     dbus_pending_call_block(self->pc);
70     Py_END_ALLOW_THREADS
71     Py_RETURN_NONE;
74 static void 
75 _pending_call_notify_function(DBusPendingCall *pc,
76                               PyObject *list)
78     PyGILState_STATE gil = PyGILState_Ensure();
79     /* BEGIN CRITICAL SECTION
80      * While holding the GIL, make sure the callback only gets called once
81      * by deleting it from the 1-item list that's held by libdbus.
82      */
83     PyObject *handler = PyList_GetItem(list, 0);
84     DBusMessage *msg;
86     if (!handler) {
87         PyErr_Print();
88         goto release;
89     }
90     if (handler == Py_None) {
91         /* We've already called (and thrown away) the callback */
92         goto release;
93     }
94     Py_INCREF(handler);     /* previously borrowed from the list, now owned */
95     Py_INCREF(Py_None);     /* take a ref so SetItem can steal it */
96     PyList_SetItem(list, 0, Py_None);
97     /* END CRITICAL SECTION */
99     msg = dbus_pending_call_steal_reply(pc);
101     if (!msg) {
102         /* omg, what happened here? the notify should only get called
103          * when we have a reply */
104         PyErr_Warn(PyExc_UserWarning, "D-Bus notify function was called "
105                    "for an incomplete pending call (shouldn't happen)");
106     } else {
107         PyObject *msg_obj = DBusPyMessage_ConsumeDBusMessage(msg);
109         if (msg_obj) {
110             PyObject *ret = PyObject_CallFunctionObjArgs(handler, msg_obj, NULL);
112             if (!ret) {
113                 PyErr_Print();
114             }
115             Py_XDECREF(ret);
116         }
117         /* else OOM has happened - not a lot we can do about that,
118          * except possibly making it fatal (FIXME?) */
119     }
121     Py_XDECREF(handler);
122 release:
123     PyGILState_Release(gil);
126 PyDoc_STRVAR(PendingCall_get_completed__doc__,
127 "get_completed() -> bool\n\n"
128 "Return true if this pending call has completed.\n\n"
129 "If so, its associated reply handler has been called and it is no\n"
130 "longer meaningful to cancel it.\n");
131 static PyObject *
132 PendingCall_get_completed(PendingCall *self, PyObject *unused UNUSED)
134     dbus_bool_t ret;
136     Py_BEGIN_ALLOW_THREADS
137     ret = dbus_pending_call_get_completed(self->pc);
138     Py_END_ALLOW_THREADS
139     return PyBool_FromLong(ret);
142 /* Steals the reference to the pending call. */
143 PyObject *
144 DBusPyPendingCall_ConsumeDBusPendingCall(DBusPendingCall *pc,
145                                          PyObject *callable)
147     dbus_bool_t ret;
148     PyObject *list = PyList_New(1);
149     PendingCall *self = PyObject_New(PendingCall, &PendingCallType);
151     if (!list || !self) {
152         Py_XDECREF(list);
153         Py_XDECREF(self);
154         Py_BEGIN_ALLOW_THREADS
155         dbus_pending_call_cancel(pc);
156         dbus_pending_call_unref(pc);
157         Py_END_ALLOW_THREADS
158         return NULL;
159     }
161     /* INCREF because SET_ITEM steals a ref */
162     Py_INCREF(callable);
163     PyList_SET_ITEM(list, 0, callable);
165     /* INCREF so we can give a ref to set_notify and still have one */
166     Py_INCREF(list);    
168     Py_BEGIN_ALLOW_THREADS
169     ret = dbus_pending_call_set_notify(pc,
170         (DBusPendingCallNotifyFunction)_pending_call_notify_function,
171         (void *)list, (DBusFreeFunction)dbus_py_take_gil_and_xdecref);
172     Py_END_ALLOW_THREADS
174     if (!ret) {
175         PyErr_NoMemory();
176         /* DECREF twice - one for the INCREF and one for the allocation */
177         Py_DECREF(list);
178         Py_DECREF(list);
179         Py_DECREF(self);
180         Py_BEGIN_ALLOW_THREADS
181         dbus_pending_call_cancel(pc);
182         dbus_pending_call_unref(pc);
183         Py_END_ALLOW_THREADS
184         return NULL;
185     }
187     /* As Alexander Larsson pointed out on dbus@lists.fd.o on 2006-11-30,
188      * the API has a race condition if set_notify runs in one thread and a
189      * mail loop runs in another - if the reply gets in before set_notify
190      * runs, the notify isn't called and there is no indication of error.
191      *
192      * The workaround is to check for completion immediately, but this also
193      * has a race which might lead to getting the notify called twice if
194      * we're unlucky. So I use the list to arrange for the notify to be
195      * deleted before it's called for the second time. The GIL protects
196      * the critical section in which I delete the callback from the list.
197      */
198     if (dbus_pending_call_get_completed(pc)) {
199         /* the first race condition happened, so call the callable here.
200          * FIXME: we ought to arrange for the callable to run from the
201          * mainloop thread, like it would if the race hadn't happened...
202          * this needs a better mainloop abstraction, though.
203          */
204         _pending_call_notify_function(pc, list);
205     }
207     Py_DECREF(list);
208     self->pc = pc;
209     return (PyObject *)self;
212 static void
213 PendingCall_tp_dealloc (PendingCall *self)
215     if (self->pc) {
216         Py_BEGIN_ALLOW_THREADS
217         dbus_pending_call_unref(self->pc);
218         Py_END_ALLOW_THREADS
219     }
220     PyObject_Del (self);
223 static PyMethodDef PendingCall_tp_methods[] = {
224     {"block", (PyCFunction)PendingCall_block, METH_NOARGS,
225      PendingCall_block__doc__},
226     {"cancel", (PyCFunction)PendingCall_cancel, METH_NOARGS,
227      PendingCall_cancel__doc__},
228     {"get_completed", (PyCFunction)PendingCall_get_completed, METH_NOARGS,
229      PendingCall_get_completed__doc__},
230     {NULL, NULL, 0, NULL}
233 static PyTypeObject PendingCallType = {
234     PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type))
235     0,
236     "dbus.lowlevel.PendingCall",
237     sizeof(PendingCall),
238     0,
239     (destructor)PendingCall_tp_dealloc,     /* tp_dealloc */
240     0,                                      /* tp_print */
241     0,                                      /* tp_getattr */
242     0,                                      /* tp_setattr */
243     0,                                      /* tp_compare */
244     0,                                      /* tp_repr */
245     0,                                      /* tp_as_number */
246     0,                                      /* tp_as_sequence */
247     0,                                      /* tp_as_mapping */
248     0,                                      /* tp_hash */
249     0,                                      /* tp_call */
250     0,                                      /* tp_str */
251     0,                                      /* tp_getattro */
252     0,                                      /* tp_setattro */
253     0,                                      /* tp_as_buffer */
254     Py_TPFLAGS_DEFAULT,                     /* tp_flags */
255     PendingCall_tp_doc,                     /* tp_doc */
256     0,                                      /* tp_traverse */
257     0,                                      /* tp_clear */
258     0,                                      /* tp_richcompare */
259     0,                                      /* tp_weaklistoffset */
260     0,                                      /* tp_iter */
261     0,                                      /* tp_iternext */
262     PendingCall_tp_methods,                 /* tp_methods */
263     0,                                      /* tp_members */
264     0,                                      /* tp_getset */
265     0,                                      /* tp_base */
266     0,                                      /* tp_dict */
267     0,                                      /* tp_descr_get */
268     0,                                      /* tp_descr_set */
269     0,                                      /* tp_dictoffset */
270     0,                                      /* tp_init */
271     0,                                      /* tp_alloc */
272     /* deliberately not callable! Use PendingCall_ConsumeDBusPendingCall */
273     0,                                      /* tp_new */
276 dbus_bool_t
277 dbus_py_init_pending_call (void)
279     if (PyType_Ready (&PendingCallType) < 0) return 0;
280     return 1;
283 dbus_bool_t
284 dbus_py_insert_pending_call (PyObject *this_module)
286     if (PyModule_AddObject (this_module, "PendingCall",
287                             (PyObject *)&PendingCallType) < 0) return 0;
288     return 1;
291 /* vim:set ft=c cino< sw=4 sts=4 et: */