efl/src/lib/eldbus/eldbus_pending.c

320 lines
10 KiB
C
Raw Normal View History

#include "eldbus_private.h"
#include "eldbus_private_types.h"
#include <dbus/dbus.h>
/* TODO: mempool of Eldbus_Pending */
#define ELDBUS_PENDING_CHECK(pending) \
do \
{ \
EINA_SAFETY_ON_NULL_RETURN(pending); \
if (!EINA_MAGIC_CHECK(pending, ELDBUS_PENDING_MAGIC)) \
{ \
EINA_MAGIC_FAIL(pending, ELDBUS_PENDING_MAGIC); \
return; \
} \
} \
while (0)
#define ELDBUS_PENDING_CHECK_RETVAL(pending, retval) \
do \
{ \
EINA_SAFETY_ON_NULL_RETURN_VAL(pending, retval); \
if (!EINA_MAGIC_CHECK(pending, ELDBUS_PENDING_MAGIC)) \
{ \
EINA_MAGIC_FAIL(pending, ELDBUS_PENDING_MAGIC); \
return retval; \
} \
} \
while (0)
static void eldbus_pending_dispatch(Eldbus_Pending *pending, Eldbus_Message *msg);
Eina_Bool
eldbus_pending_init(void)
{
return EINA_TRUE;
}
void
eldbus_pending_shutdown(void)
{
}
static void
cb_pending(DBusPendingCall *dbus_pending, void *user_data)
{
Eldbus_Message *msg;
Eldbus_Pending *pending = user_data;
if (!dbus_pending_call_get_completed(dbus_pending))
{
INF("timeout to pending %p", pending);
dbus_pending_call_cancel(dbus_pending);
msg = eldbus_message_error_new(pending->msg_sent,
ELDBUS_ERROR_PENDING_TIMEOUT,
"This call was not completed in time.");
eldbus_pending_dispatch(pending, msg);
return;
}
msg = eldbus_message_new(EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN(msg);
msg->dbus_msg = dbus_pending_call_steal_reply(dbus_pending);
if (!msg->dbus_msg)
{
EINA_SAFETY_ON_NULL_GOTO(pending->cb, cleanup);
msg->dbus_msg = dbus_message_new_error(NULL,
"org.enlightenment.DBus.NoReply",
"There was no reply to this method call.");
EINA_SAFETY_ON_NULL_GOTO(msg->dbus_msg, cleanup);
}
dbus_message_iter_init(msg->dbus_msg, &msg->iterator->dbus_iterator);
eldbus_pending_dispatch(pending, msg);
return;
cleanup:
eldbus_message_unref(msg);
}
static void
eldbus: Fix crash when removing the last reference of the message container inside of the message callback If user try to remove the last reference of proxy, object, connection or eldbus(lib) inside of message callback it was causing the eldbus_pending_dispatch() being called 2 times, one because of the eldbus_cancel() that is triggered when the last reference of the message parent is removed and another after the return of the user callback. ==6545== Invalid read of size 8 ==6545== at 0x52F784E: eldbus_cbs_free_dispatch (eldbus_core.c:266) ==6545== by 0x53064AA: eldbus_pending_dispatch (eldbus_pending.c:227) ==6545== by 0x5305961: cb_pending (eldbus_pending.c:74) ==6545== by 0x6B29DB1: ??? (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x6B2D280: dbus_connection_dispatch (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x52F93B4: eldbus_idler (eldbus_core.c:773) ==6545== by 0x4E4B300: _ecore_call_task_cb (ecore_private.h:305) ==6545== by 0x4E4B78F: _ecore_idler_all_call (ecore_idler.c:143) ==6545== by 0x4E4EA73: _ecore_main_loop_spin_core (ecore_main.c:1768) ==6545== by 0x4E4EAF1: _ecore_main_loop_spin_timers (ecore_main.c:1802) ==6545== by 0x4E4ED01: _ecore_main_loop_iterate_internal (ecore_main.c:1925) ==6545== by 0x4E4D03B: ecore_main_loop_begin (ecore_main.c:983) ==6545== Address 0x701aa78 is 104 bytes inside a block of size 128 free'd ==6545== at 0x4C2B200: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==6545== by 0x530655B: eldbus_pending_dispatch (eldbus_pending.c:241) ==6545== by 0x5306763: eldbus_pending_cancel (eldbus_pending.c:259) ==6545== by 0x52F29DB: _eldbus_proxy_clear (eldbus_proxy.c:146) ==6545== by 0x52F3057: _eldbus_proxy_unref (eldbus_proxy.c:244) ==6545== by 0x52F3393: eldbus_proxy_unref (eldbus_proxy.c:264) ==6545== by 0x401039: on_get_playlists (banshee.c:53) ==6545== by 0x5306493: eldbus_pending_dispatch (eldbus_pending.c:225) ==6545== by 0x5305961: cb_pending (eldbus_pending.c:74) ==6545== by 0x6B29DB1: ??? (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x6B2D280: dbus_connection_dispatch (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x52F93B4: eldbus_idler (eldbus_core.c:773) Now we will remove the pending from parent pending list before call the user callback, this way only the pending messages will be canceled. Also we need increase the eldbus reference before call dbus_connection_dispatch() or user could remove the last reference of eldbus inside of a message callback when we still are holding one reference of the connection. @fix ref T1908
2014-12-17 10:57:03 -08:00
_on_conn_message_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending)
{
eldbus: Fix crash when removing the last reference of the message container inside of the message callback If user try to remove the last reference of proxy, object, connection or eldbus(lib) inside of message callback it was causing the eldbus_pending_dispatch() being called 2 times, one because of the eldbus_cancel() that is triggered when the last reference of the message parent is removed and another after the return of the user callback. ==6545== Invalid read of size 8 ==6545== at 0x52F784E: eldbus_cbs_free_dispatch (eldbus_core.c:266) ==6545== by 0x53064AA: eldbus_pending_dispatch (eldbus_pending.c:227) ==6545== by 0x5305961: cb_pending (eldbus_pending.c:74) ==6545== by 0x6B29DB1: ??? (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x6B2D280: dbus_connection_dispatch (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x52F93B4: eldbus_idler (eldbus_core.c:773) ==6545== by 0x4E4B300: _ecore_call_task_cb (ecore_private.h:305) ==6545== by 0x4E4B78F: _ecore_idler_all_call (ecore_idler.c:143) ==6545== by 0x4E4EA73: _ecore_main_loop_spin_core (ecore_main.c:1768) ==6545== by 0x4E4EAF1: _ecore_main_loop_spin_timers (ecore_main.c:1802) ==6545== by 0x4E4ED01: _ecore_main_loop_iterate_internal (ecore_main.c:1925) ==6545== by 0x4E4D03B: ecore_main_loop_begin (ecore_main.c:983) ==6545== Address 0x701aa78 is 104 bytes inside a block of size 128 free'd ==6545== at 0x4C2B200: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==6545== by 0x530655B: eldbus_pending_dispatch (eldbus_pending.c:241) ==6545== by 0x5306763: eldbus_pending_cancel (eldbus_pending.c:259) ==6545== by 0x52F29DB: _eldbus_proxy_clear (eldbus_proxy.c:146) ==6545== by 0x52F3057: _eldbus_proxy_unref (eldbus_proxy.c:244) ==6545== by 0x52F3393: eldbus_proxy_unref (eldbus_proxy.c:264) ==6545== by 0x401039: on_get_playlists (banshee.c:53) ==6545== by 0x5306493: eldbus_pending_dispatch (eldbus_pending.c:225) ==6545== by 0x5305961: cb_pending (eldbus_pending.c:74) ==6545== by 0x6B29DB1: ??? (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x6B2D280: dbus_connection_dispatch (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x52F93B4: eldbus_idler (eldbus_core.c:773) Now we will remove the pending from parent pending list before call the user callback, this way only the pending messages will be canceled. Also we need increase the eldbus reference before call dbus_connection_dispatch() or user could remove the last reference of eldbus inside of a message callback when we still are holding one reference of the connection. @fix ref T1908
2014-12-17 10:57:03 -08:00
Eldbus_Message_Cb cb = eldbus_pending_data_del(pending, "__user_cb");
Eldbus_Connection *conn = eldbus_pending_data_del(pending, "__connection");
EINA_SAFETY_ON_NULL_RETURN(conn);
eldbus_connection_pending_del(conn, pending);
cb(data, msg, pending);
}
EAPI Eldbus_Pending *
eldbus_connection_send(Eldbus_Connection *conn, Eldbus_Message *msg, Eldbus_Message_Cb cb, const void *cb_data, double timeout)
{
Eldbus_Pending *pending;
EINA_SAFETY_ON_NULL_RETURN_VAL(conn, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL);
eldbus: Fix crash when removing the last reference of the message container inside of the message callback If user try to remove the last reference of proxy, object, connection or eldbus(lib) inside of message callback it was causing the eldbus_pending_dispatch() being called 2 times, one because of the eldbus_cancel() that is triggered when the last reference of the message parent is removed and another after the return of the user callback. ==6545== Invalid read of size 8 ==6545== at 0x52F784E: eldbus_cbs_free_dispatch (eldbus_core.c:266) ==6545== by 0x53064AA: eldbus_pending_dispatch (eldbus_pending.c:227) ==6545== by 0x5305961: cb_pending (eldbus_pending.c:74) ==6545== by 0x6B29DB1: ??? (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x6B2D280: dbus_connection_dispatch (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x52F93B4: eldbus_idler (eldbus_core.c:773) ==6545== by 0x4E4B300: _ecore_call_task_cb (ecore_private.h:305) ==6545== by 0x4E4B78F: _ecore_idler_all_call (ecore_idler.c:143) ==6545== by 0x4E4EA73: _ecore_main_loop_spin_core (ecore_main.c:1768) ==6545== by 0x4E4EAF1: _ecore_main_loop_spin_timers (ecore_main.c:1802) ==6545== by 0x4E4ED01: _ecore_main_loop_iterate_internal (ecore_main.c:1925) ==6545== by 0x4E4D03B: ecore_main_loop_begin (ecore_main.c:983) ==6545== Address 0x701aa78 is 104 bytes inside a block of size 128 free'd ==6545== at 0x4C2B200: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==6545== by 0x530655B: eldbus_pending_dispatch (eldbus_pending.c:241) ==6545== by 0x5306763: eldbus_pending_cancel (eldbus_pending.c:259) ==6545== by 0x52F29DB: _eldbus_proxy_clear (eldbus_proxy.c:146) ==6545== by 0x52F3057: _eldbus_proxy_unref (eldbus_proxy.c:244) ==6545== by 0x52F3393: eldbus_proxy_unref (eldbus_proxy.c:264) ==6545== by 0x401039: on_get_playlists (banshee.c:53) ==6545== by 0x5306493: eldbus_pending_dispatch (eldbus_pending.c:225) ==6545== by 0x5305961: cb_pending (eldbus_pending.c:74) ==6545== by 0x6B29DB1: ??? (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x6B2D280: dbus_connection_dispatch (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x52F93B4: eldbus_idler (eldbus_core.c:773) Now we will remove the pending from parent pending list before call the user callback, this way only the pending messages will be canceled. Also we need increase the eldbus reference before call dbus_connection_dispatch() or user could remove the last reference of eldbus inside of a message callback when we still are holding one reference of the connection. @fix ref T1908
2014-12-17 10:57:03 -08:00
if (!cb)
{
_eldbus_connection_send(conn, msg, NULL, NULL, timeout);
return NULL;
}
pending = _eldbus_connection_send(conn, msg, _on_conn_message_cb, cb_data,
timeout);
EINA_SAFETY_ON_NULL_RETURN_VAL(pending, NULL);
eldbus: Fix crash when removing the last reference of the message container inside of the message callback If user try to remove the last reference of proxy, object, connection or eldbus(lib) inside of message callback it was causing the eldbus_pending_dispatch() being called 2 times, one because of the eldbus_cancel() that is triggered when the last reference of the message parent is removed and another after the return of the user callback. ==6545== Invalid read of size 8 ==6545== at 0x52F784E: eldbus_cbs_free_dispatch (eldbus_core.c:266) ==6545== by 0x53064AA: eldbus_pending_dispatch (eldbus_pending.c:227) ==6545== by 0x5305961: cb_pending (eldbus_pending.c:74) ==6545== by 0x6B29DB1: ??? (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x6B2D280: dbus_connection_dispatch (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x52F93B4: eldbus_idler (eldbus_core.c:773) ==6545== by 0x4E4B300: _ecore_call_task_cb (ecore_private.h:305) ==6545== by 0x4E4B78F: _ecore_idler_all_call (ecore_idler.c:143) ==6545== by 0x4E4EA73: _ecore_main_loop_spin_core (ecore_main.c:1768) ==6545== by 0x4E4EAF1: _ecore_main_loop_spin_timers (ecore_main.c:1802) ==6545== by 0x4E4ED01: _ecore_main_loop_iterate_internal (ecore_main.c:1925) ==6545== by 0x4E4D03B: ecore_main_loop_begin (ecore_main.c:983) ==6545== Address 0x701aa78 is 104 bytes inside a block of size 128 free'd ==6545== at 0x4C2B200: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==6545== by 0x530655B: eldbus_pending_dispatch (eldbus_pending.c:241) ==6545== by 0x5306763: eldbus_pending_cancel (eldbus_pending.c:259) ==6545== by 0x52F29DB: _eldbus_proxy_clear (eldbus_proxy.c:146) ==6545== by 0x52F3057: _eldbus_proxy_unref (eldbus_proxy.c:244) ==6545== by 0x52F3393: eldbus_proxy_unref (eldbus_proxy.c:264) ==6545== by 0x401039: on_get_playlists (banshee.c:53) ==6545== by 0x5306493: eldbus_pending_dispatch (eldbus_pending.c:225) ==6545== by 0x5305961: cb_pending (eldbus_pending.c:74) ==6545== by 0x6B29DB1: ??? (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x6B2D280: dbus_connection_dispatch (in /usr/lib/libdbus-1.so.3.8.9) ==6545== by 0x52F93B4: eldbus_idler (eldbus_core.c:773) Now we will remove the pending from parent pending list before call the user callback, this way only the pending messages will be canceled. Also we need increase the eldbus reference before call dbus_connection_dispatch() or user could remove the last reference of eldbus inside of a message callback when we still are holding one reference of the connection. @fix ref T1908
2014-12-17 10:57:03 -08:00
eldbus_pending_data_set(pending, "__user_cb", cb);
eldbus_pending_data_set(pending, "__connection", conn);
eldbus_connection_pending_add(conn, pending);
return pending;
}
/*
* On success @param msg is unref'd or its ref is stolen by the returned
* Eldbus_Pending.
*/
Eldbus_Pending *
_eldbus_connection_send(Eldbus_Connection *conn, Eldbus_Message *msg, Eldbus_Message_Cb cb, const void *cb_data, double timeout)
{
Eldbus_Pending *pending;
Eldbus_Message *error_msg;
DBG("conn=%p, msg=%p, cb=%p, cb_data=%p, timeout=%f",
conn, msg, cb, cb_data, timeout);
if (!cb)
{
dbus_connection_send(conn->dbus_conn, msg->dbus_msg, NULL);
eldbus_message_unref(msg);
return NULL;
}
pending = calloc(1, sizeof(Eldbus_Pending));
EINA_SAFETY_ON_NULL_RETURN_VAL(pending, NULL);
pending->cb = cb;
pending->cb_data = cb_data;
pending->conn = conn;
pending->dest = eina_stringshare_add(dbus_message_get_destination(msg->dbus_msg));
pending->interface = eina_stringshare_add(dbus_message_get_interface(msg->dbus_msg));
pending->method = eina_stringshare_add(dbus_message_get_member(msg->dbus_msg));
pending->path = eina_stringshare_add(dbus_message_get_path(msg->dbus_msg));
/* Steal the reference */
pending->msg_sent = msg;
EINA_MAGIC_SET(pending, ELDBUS_PENDING_MAGIC);
if (!dbus_connection_send_with_reply(conn->dbus_conn,
msg->dbus_msg,
&pending->dbus_pending, timeout))
{
error_msg = eldbus_message_error_new(msg, "org.enlightenment.DBus.NoConnection",
"Eldbus_Connection was closed.");
eldbus_pending_dispatch(pending, error_msg);
return NULL;
}
if (!pending->dbus_pending)
{
error_msg = eldbus_message_error_new(msg, "org.enlightenment.DBus.Error",
"dbus_pending is NULL.");
eldbus_pending_dispatch(pending, error_msg);
return NULL;
}
if (dbus_pending_call_set_notify(pending->dbus_pending, cb_pending, pending, NULL))
return pending;
dbus_pending_call_cancel(pending->dbus_pending);
error_msg = eldbus_message_error_new(pending->msg_sent,
"org.enlightenment.DBus.Error",
"Error when try set callback to message.");
eldbus_pending_dispatch(pending, error_msg);
return NULL;
}
Eldbus_Message *
_eldbus_connection_send_and_block(Eldbus_Connection *conn, Eldbus_Message *msg, double timeout)
{
Eldbus_Message *reply = NULL;
DBusError err;
DBusMessage *dbus_msg;
if (ecore_main_loop_nested_get())
WRN("Calling this function may result in dropped frames because the main loop is running");
dbus_error_init(&err);
dbus_msg =
dbus_connection_send_with_reply_and_block(conn->dbus_conn,
msg->dbus_msg, timeout, &err);
EINA_SAFETY_ON_TRUE_GOTO(dbus_error_is_set(&err), dbus_error_set);
dbus_error_free(&err);
reply = eldbus_message_new(EINA_FALSE);
EINA_SAFETY_ON_NULL_GOTO(reply, fail);
reply->dbus_msg = dbus_msg;
dbus_message_iter_init(reply->dbus_msg, &reply->iterator->dbus_iterator);
eldbus_message_unref(msg);
return reply;
dbus_error_set:
reply = eldbus_message_error_new(msg, err.name, err.message);
dbus_error_free(&err);
fail:
eldbus_message_unref(msg);
return reply;
}
EAPI void
eldbus_pending_data_set(Eldbus_Pending *pending, const char *key, const void *data)
{
ELDBUS_PENDING_CHECK(pending);
EINA_SAFETY_ON_NULL_RETURN(key);
EINA_SAFETY_ON_NULL_RETURN(data);
eldbus_data_set(&(pending->data), key, data);
}
EAPI void *
eldbus_pending_data_get(const Eldbus_Pending *pending, const char *key)
{
ELDBUS_PENDING_CHECK_RETVAL(pending, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(key, NULL);
return eldbus_data_get(&(((Eldbus_Pending *)pending)->data), key);
}
EAPI void *
eldbus_pending_data_del(Eldbus_Pending *pending, const char *key)
{
ELDBUS_PENDING_CHECK_RETVAL(pending, NULL);
EINA_SAFETY_ON_NULL_RETURN_VAL(key, NULL);
return eldbus_data_del(&(((Eldbus_Pending *)pending)->data), key);
}
static void
eldbus_pending_dispatch(Eldbus_Pending *pending, Eldbus_Message *msg)
{
DBG("pending=%p msg=%p", pending, msg);
if (pending->cb)
pending->cb((void *)pending->cb_data, msg, pending);
eldbus_cbs_free_dispatch(&(pending->cbs_free), pending);
eldbus_data_del_all(&(pending->data));
if (msg) eldbus_message_unref(msg);
eldbus_message_unref(pending->msg_sent);
if (pending->dbus_pending)
dbus_pending_call_unref(pending->dbus_pending);
pending->cb = NULL;
pending->dbus_pending = NULL;
eina_stringshare_del(pending->dest);
eina_stringshare_del(pending->path);
eina_stringshare_del(pending->interface);
eina_stringshare_del(pending->method);
EINA_MAGIC_SET(pending, EINA_MAGIC_NONE);
free(pending);
}
EAPI void
eldbus_pending_cancel(Eldbus_Pending *pending)
{
Eldbus_Message *error_message;
ELDBUS_PENDING_CHECK(pending);
EINA_SAFETY_ON_NULL_RETURN(pending->dbus_pending);
DBG("pending=%p", pending);
dbus_pending_call_cancel(pending->dbus_pending);
error_message = eldbus_message_error_new(pending->msg_sent,
ELDBUS_ERROR_PENDING_CANCELED,
"Canceled by user.");
eldbus_pending_dispatch(pending, error_message);
}
EAPI void
eldbus_pending_free_cb_add(Eldbus_Pending *pending, Eldbus_Free_Cb cb, const void *data)
{
ELDBUS_PENDING_CHECK(pending);
EINA_SAFETY_ON_NULL_RETURN(cb);
pending->cbs_free = eldbus_cbs_free_add(pending->cbs_free, cb, data);
}
EAPI void
eldbus_pending_free_cb_del(Eldbus_Pending *pending, Eldbus_Free_Cb cb, const void *data)
{
ELDBUS_PENDING_CHECK(pending);
EINA_SAFETY_ON_NULL_RETURN(cb);
pending->cbs_free = eldbus_cbs_free_del(pending->cbs_free, cb, data);
}
EAPI const char *
eldbus_pending_destination_get(const Eldbus_Pending *pending)
{
ELDBUS_PENDING_CHECK_RETVAL(pending, NULL);
return pending->dest;
}
EAPI const char *
eldbus_pending_path_get(const Eldbus_Pending *pending)
{
ELDBUS_PENDING_CHECK_RETVAL(pending, NULL);
return pending->path;
}
EAPI const char *
eldbus_pending_interface_get(const Eldbus_Pending *pending)
{
ELDBUS_PENDING_CHECK_RETVAL(pending, NULL);
return pending->interface;
}
EAPI const char *
eldbus_pending_method_get(const Eldbus_Pending *pending)
{
ELDBUS_PENDING_CHECK_RETVAL(pending, NULL);
return pending->method;
}