When user sets reply_handler but not error_handler raise MissingReplyHandlerException...
[dbus-python-phuang.git] / _dbus_bindings / pending-call.c
blobad18fd7fc111cd600acef6ee0fc8ee9c1f5de563
1 /* Implementation of PendingCall helper type for D-Bus bindings.
3 * Copyright (C) 2006 Collabora Ltd. <http://www.collabora.co.uk/>
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation
7 * files (the "Software"), to deal in the Software without
8 * restriction, including without limitation the rights to use, copy,
9 * modify, merge, publish, distribute, sublicense, and/or sell copies
10 * of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
26 #include "dbus_bindings-internal.h"
28 PyDoc_STRVAR(PendingCall_tp_doc,
29 "Object representing a pending D-Bus call, returned by\n"
30 "Connection.send_message_with_reply(). Cannot be instantiated directly.\n"
33 static PyTypeObject PendingCallType;
35 static inline int PendingCall_Check (PyObject *o)
37 return (o->ob_type == &PendingCallType)
38 || PyObject_IsInstance(o, (PyObject *)&PendingCallType);
41 typedef struct {
42 PyObject_HEAD
43 DBusPendingCall *pc;
44 } PendingCall;
46 PyDoc_STRVAR(PendingCall_cancel__doc__,
47 "cancel()\n\n"
48 "Cancel this pending call. Its reply will be ignored and the associated\n"
49 "reply handler will never be called.\n");
50 static PyObject *
51 PendingCall_cancel(PendingCall *self, PyObject *unused UNUSED)
53 Py_BEGIN_ALLOW_THREADS
54 dbus_pending_call_cancel(self->pc);
55 Py_END_ALLOW_THREADS
56 Py_RETURN_NONE;
59 PyDoc_STRVAR(PendingCall_block__doc__,
60 "block()\n\n"
61 "Block until this pending call has completed and the associated\n"
62 "reply handler has been called.\n"
63 "\n"
64 "This can lead to a deadlock, if the called method tries to make a\n"
65 "synchronous call to a method in this application.\n");
66 static PyObject *
67 PendingCall_block(PendingCall *self, PyObject *unused UNUSED)
69 Py_BEGIN_ALLOW_THREADS
70 dbus_pending_call_block(self->pc);
71 Py_END_ALLOW_THREADS
72 Py_RETURN_NONE;
75 static void
76 _pending_call_notify_function(DBusPendingCall *pc,
77 PyObject *list)
79 PyGILState_STATE gil = PyGILState_Ensure();
80 /* BEGIN CRITICAL SECTION
81 * While holding the GIL, make sure the callback only gets called once
82 * by deleting it from the 1-item list that's held by libdbus.
84 PyObject *handler = PyList_GetItem(list, 0);
85 DBusMessage *msg;
87 if (!handler) {
88 PyErr_Print();
89 goto release;
91 if (handler == Py_None) {
92 /* We've already called (and thrown away) the callback */
93 goto release;
95 Py_INCREF(handler); /* previously borrowed from the list, now owned */
96 Py_INCREF(Py_None); /* take a ref so SetItem can steal it */
97 PyList_SetItem(list, 0, Py_None);
98 /* END CRITICAL SECTION */
100 msg = dbus_pending_call_steal_reply(pc);
102 if (!msg) {
103 /* omg, what happened here? the notify should only get called
104 * when we have a reply */
105 PyErr_Warn(PyExc_UserWarning, "D-Bus notify function was called "
106 "for an incomplete pending call (shouldn't happen)");
107 } else {
108 PyObject *msg_obj = DBusPyMessage_ConsumeDBusMessage(msg);
110 if (msg_obj) {
111 PyObject *ret = PyObject_CallFunctionObjArgs(handler, msg_obj, NULL);
113 if (!ret) {
114 PyErr_Print();
116 Py_XDECREF(ret);
117 Py_DECREF(msg_obj);
119 /* else OOM has happened - not a lot we can do about that,
120 * except possibly making it fatal (FIXME?) */
123 release:
124 Py_XDECREF(handler);
125 PyGILState_Release(gil);
128 PyDoc_STRVAR(PendingCall_get_completed__doc__,
129 "get_completed() -> bool\n\n"
130 "Return true if this pending call has completed.\n\n"
131 "If so, its associated reply handler has been called and it is no\n"
132 "longer meaningful to cancel it.\n");
133 static PyObject *
134 PendingCall_get_completed(PendingCall *self, PyObject *unused UNUSED)
136 dbus_bool_t ret;
138 Py_BEGIN_ALLOW_THREADS
139 ret = dbus_pending_call_get_completed(self->pc);
140 Py_END_ALLOW_THREADS
141 return PyBool_FromLong(ret);
144 /* Steals the reference to the pending call. */
145 PyObject *
146 DBusPyPendingCall_ConsumeDBusPendingCall(DBusPendingCall *pc,
147 PyObject *callable)
149 dbus_bool_t ret;
150 PyObject *list = PyList_New(1);
151 PendingCall *self = PyObject_New(PendingCall, &PendingCallType);
153 if (!list || !self) {
154 Py_XDECREF(list);
155 Py_XDECREF(self);
156 Py_BEGIN_ALLOW_THREADS
157 dbus_pending_call_cancel(pc);
158 dbus_pending_call_unref(pc);
159 Py_END_ALLOW_THREADS
160 return NULL;
163 /* INCREF because SET_ITEM steals a ref */
164 Py_INCREF(callable);
165 PyList_SET_ITEM(list, 0, callable);
167 /* INCREF so we can give a ref to set_notify and still have one */
168 Py_INCREF(list);
170 Py_BEGIN_ALLOW_THREADS
171 ret = dbus_pending_call_set_notify(pc,
172 (DBusPendingCallNotifyFunction)_pending_call_notify_function,
173 (void *)list, (DBusFreeFunction)dbus_py_take_gil_and_xdecref);
174 Py_END_ALLOW_THREADS
176 if (!ret) {
177 PyErr_NoMemory();
178 /* DECREF twice - one for the INCREF and one for the allocation */
179 Py_DECREF(list);
180 Py_DECREF(list);
181 Py_DECREF(self);
182 Py_BEGIN_ALLOW_THREADS
183 dbus_pending_call_cancel(pc);
184 dbus_pending_call_unref(pc);
185 Py_END_ALLOW_THREADS
186 return NULL;
189 /* As Alexander Larsson pointed out on dbus@lists.fd.o on 2006-11-30,
190 * the API has a race condition if set_notify runs in one thread and a
191 * mail loop runs in another - if the reply gets in before set_notify
192 * runs, the notify isn't called and there is no indication of error.
194 * The workaround is to check for completion immediately, but this also
195 * has a race which might lead to getting the notify called twice if
196 * we're unlucky. So I use the list to arrange for the notify to be
197 * deleted before it's called for the second time. The GIL protects
198 * the critical section in which I delete the callback from the list.
200 if (dbus_pending_call_get_completed(pc)) {
201 /* the first race condition happened, so call the callable here.
202 * FIXME: we ought to arrange for the callable to run from the
203 * mainloop thread, like it would if the race hadn't happened...
204 * this needs a better mainloop abstraction, though.
206 _pending_call_notify_function(pc, list);
209 Py_DECREF(list);
210 self->pc = pc;
211 return (PyObject *)self;
214 static void
215 PendingCall_tp_dealloc (PendingCall *self)
217 if (self->pc) {
218 Py_BEGIN_ALLOW_THREADS
219 dbus_pending_call_unref(self->pc);
220 Py_END_ALLOW_THREADS
222 PyObject_Del (self);
225 static PyMethodDef PendingCall_tp_methods[] = {
226 {"block", (PyCFunction)PendingCall_block, METH_NOARGS,
227 PendingCall_block__doc__},
228 {"cancel", (PyCFunction)PendingCall_cancel, METH_NOARGS,
229 PendingCall_cancel__doc__},
230 {"get_completed", (PyCFunction)PendingCall_get_completed, METH_NOARGS,
231 PendingCall_get_completed__doc__},
232 {NULL, NULL, 0, NULL}
235 static PyTypeObject PendingCallType = {
236 PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type))
238 "dbus.lowlevel.PendingCall",
239 sizeof(PendingCall),
241 (destructor)PendingCall_tp_dealloc, /* tp_dealloc */
242 0, /* tp_print */
243 0, /* tp_getattr */
244 0, /* tp_setattr */
245 0, /* tp_compare */
246 0, /* tp_repr */
247 0, /* tp_as_number */
248 0, /* tp_as_sequence */
249 0, /* tp_as_mapping */
250 0, /* tp_hash */
251 0, /* tp_call */
252 0, /* tp_str */
253 0, /* tp_getattro */
254 0, /* tp_setattro */
255 0, /* tp_as_buffer */
256 Py_TPFLAGS_DEFAULT, /* tp_flags */
257 PendingCall_tp_doc, /* tp_doc */
258 0, /* tp_traverse */
259 0, /* tp_clear */
260 0, /* tp_richcompare */
261 0, /* tp_weaklistoffset */
262 0, /* tp_iter */
263 0, /* tp_iternext */
264 PendingCall_tp_methods, /* tp_methods */
265 0, /* tp_members */
266 0, /* tp_getset */
267 0, /* tp_base */
268 0, /* tp_dict */
269 0, /* tp_descr_get */
270 0, /* tp_descr_set */
271 0, /* tp_dictoffset */
272 0, /* tp_init */
273 0, /* tp_alloc */
274 /* deliberately not callable! Use PendingCall_ConsumeDBusPendingCall */
275 0, /* tp_new */
278 dbus_bool_t
279 dbus_py_init_pending_call (void)
281 if (PyType_Ready (&PendingCallType) < 0) return 0;
282 return 1;
285 dbus_bool_t
286 dbus_py_insert_pending_call (PyObject *this_module)
288 if (PyModule_AddObject (this_module, "PendingCall",
289 (PyObject *)&PendingCallType) < 0) return 0;
290 return 1;
293 /* vim:set ft=c cino< sw=4 sts=4 et: */