diff --git a/dbus/Makefile.am b/dbus/Makefile.am index 668acd3..f674957 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -31,7 +31,9 @@ DBUS_GLIB_INTERNALS = \ dbus-gsignature.h \ dbus-gvalue.h \ dbus-gvalue-utils.c \ - dbus-gvalue-utils.h + dbus-gvalue-utils.h \ + dbus-gasync.c \ + dbus-gasync.h libdbus_glib_1_la_SOURCES = \ dbus-glib-error-switch.h \ diff --git a/dbus/dbus-binding-tool-glib.c b/dbus/dbus-binding-tool-glib.c index 95c6d73..04a9fa8 100644 --- a/dbus/dbus-binding-tool-glib.c +++ b/dbus/dbus-binding-tool-glib.c @@ -139,10 +139,20 @@ compute_gsignature (MethodInfo *method, GType *rettype, GArray **params, GError GType retval_type; GArray *ret; gboolean is_async; + gboolean is_thread; const char *arg_type; gboolean retval_signals_error; is_async = method_info_get_annotation (method, DBUS_GLIB_ANNOTATION_ASYNC) != NULL; + is_thread = method_info_get_annotation (method, DBUS_GLIB_ANNOTATION_THREAD) != NULL; + if (is_async && is_thread) + { + g_set_error (error, + DBUS_BINDING_TOOL_ERROR, + DBUS_BINDING_TOOL_ERROR_INVALID_ANNOTATION, + "Asynchronous methods can not be threaded"); + return FALSE; + } retval_signals_error = FALSE; ret = g_array_new (TRUE, TRUE, sizeof (GType)); @@ -548,6 +558,7 @@ generate_glue (BaseInfo *base, DBusBindingToolCData *data, GError **error) char *marshaller_name; char *method_c_name; gboolean async = FALSE; + gboolean thread = FALSE; GSList *args; gboolean found_retval = FALSE; @@ -582,6 +593,9 @@ generate_glue (BaseInfo *base, DBusBindingToolCData *data, GError **error) if (method_info_get_annotation (method, DBUS_GLIB_ANNOTATION_ASYNC) != NULL) async = TRUE; + if (method_info_get_annotation (method, DBUS_GLIB_ANNOTATION_THREAD) != NULL) + thread = TRUE; + /* Object method data blob format: * \0\0(\0\0\0)*\0 */ @@ -592,7 +606,7 @@ generate_glue (BaseInfo *base, DBusBindingToolCData *data, GError **error) g_string_append (object_introspection_data_blob, method_info_get_name (method)); g_string_append_c (object_introspection_data_blob, '\0'); - g_string_append_c (object_introspection_data_blob, async ? 'A' : 'S'); + g_string_append_c (object_introspection_data_blob, async ? 'A' : (thread ? 'T' : 'S')); g_string_append_c (object_introspection_data_blob, '\0'); for (args = method_info_get_args (method); args; args = args->next) diff --git a/dbus/dbus-binding-tool-glib.h b/dbus/dbus-binding-tool-glib.h index 7a2f0e9..71c823d 100644 --- a/dbus/dbus-binding-tool-glib.h +++ b/dbus/dbus-binding-tool-glib.h @@ -27,6 +27,7 @@ G_BEGIN_DECLS #define DBUS_GLIB_ANNOTATION_C_SYMBOL "org.freedesktop.DBus.GLib.CSymbol" #define DBUS_GLIB_ANNOTATION_CLIENT_C_SYMBOL "org.freedesktop.DBus.GLib.ClientCSymbol" +#define DBUS_GLIB_ANNOTATION_THREAD "org.freedesktop.DBus.GLib.Thread" #define DBUS_GLIB_ANNOTATION_ASYNC "org.freedesktop.DBus.GLib.Async" #define DBUS_GLIB_ANNOTATION_CONST "org.freedesktop.DBus.GLib.Const" #define DBUS_GLIB_ANNOTATION_RETURNVAL "org.freedesktop.DBus.GLib.ReturnVal" diff --git a/dbus/dbus-gasync.c b/dbus/dbus-gasync.c new file mode 100644 index 0000000..9713a74 --- /dev/null +++ b/dbus/dbus-gasync.c @@ -0,0 +1,128 @@ +#include +#include + +#include "dbus-gasync.h" + +/* --- variables --- */ +static GThreadPool *dbus_pool = NULL; + +typedef struct _DBusTask DBusTask; + +struct _DBusTask { + GThreadedFunc callback; + gpointer data; +}; + +/* --- prototypes --- */ + +static void delete_task (DBusTask *task); +static DBusTask* create_new_task (GThreadedFunc callback, + gpointer data); +static void exec_task (DBusTask *task); +static void async_dbus_func (gpointer data, + gpointer user_data); +static gboolean init_thread_pool (); +static gboolean push_task_to_pool (DBusTask *task); + +/* --- functions --- */ + +static DBusTask* +create_new_task (GThreadedFunc callback, + gpointer data) +{ + DBusTask *task = g_slice_new (DBusTask); + + if (NULL == task) + { + g_error ("Could not create memory slice"); + return NULL; + } + + task->callback = callback; + task->data = data; + + return task; +} + +static void +delete_task (DBusTask *task) +{ + g_slice_free (DBusTask, task); +} + +static void +exec_task (DBusTask *task) +{ + task->callback (task->data); +} + +static gboolean +push_task_to_pool (DBusTask *task) +{ + GError *err = NULL; + + g_thread_pool_push (dbus_pool, task, &err); + if (err) + { + g_error ("Could not push the task to the pool: %s", err->message); + g_error_free (err); + return FALSE; + } + + return TRUE; +} + +static void +async_dbus_func (gpointer data, + gpointer user_data) +{ + DBusTask *task = (DBusTask *) data; + exec_task (task); + delete_task (task); +} + +static gboolean +init_thread_pool () +{ + dbus_pool = g_thread_pool_new (async_dbus_func, NULL, -1, FALSE, NULL); + if (!dbus_pool) + { + g_error ("Could not create thread pool"); + return FALSE; + } + + return TRUE; +} + +/** + * _dbus_gasync_thread_pool_start: + * @callback: callback function which is called in a thread + * @data: user data submitted to the callback function + * + * Calls the callback function asynchronously in a thread pool. + * + * Returns: TRUE on success and FALSE on failure + */ +gboolean +_dbus_gasync_thread_pool_start (GThreadedFunc callback, + gpointer data) +{ + gboolean res = TRUE; + + if (G_UNLIKELY (!dbus_pool)) + { + g_return_val_if_fail (init_thread_pool (), FALSE); + } + + /* Create a new task */ + DBusTask *task = create_new_task (callback, + data); + + g_return_val_if_fail (NULL != task, FALSE); + + /* Execute the task asynchronously */ + res = push_task_to_pool (task); + + return res; +} + diff --git a/dbus/dbus-gasync.h b/dbus/dbus-gasync.h new file mode 100644 index 0000000..ce4ba48 --- /dev/null +++ b/dbus/dbus-gasync.h @@ -0,0 +1,13 @@ +#ifndef __DBUS_GLIB_ASYNC_THREAD_POOL_H__ +#define __DBUS_GLIB_ASYNC_THREAD_POOL_H__ + +#include + +typedef void (*GThreadedFunc) (gpointer data); + +gboolean +_dbus_gasync_thread_pool_start (GThreadedFunc callback, + gpointer data); + +#endif /* __DBUS_GLIB_ASYNC_THREAD_POOL_H__ */ + diff --git a/dbus/dbus-gobject.c b/dbus/dbus-gobject.c index 95f062e..df9db63 100644 --- a/dbus/dbus-gobject.c +++ b/dbus/dbus-gobject.c @@ -33,14 +33,23 @@ #include "dbus-gvalue.h" #include "dbus-gmarshal.h" #include "dbus-gvalue-utils.h" +#include "dbus-gasync.h" #include +typedef enum { + SYNCHRONOUS, + SYNCHRONOUS_THREADED, + ASYNCHRONOUS +} GMethodCallType; + typedef struct { char *default_iface; GType code_enum; } DBusGErrorInfo; +typedef struct _DBusGSyncThreadedData DBusGSyncThreadedData; + static GStaticRWLock globals_lock = G_STATIC_RW_LOCK_INIT; static GHashTable *marshal_table = NULL; static GData *error_metadata = NULL; @@ -1111,6 +1120,160 @@ struct _DBusGMethodInvocation { gboolean send_reply; }; +/** + * The data provided to the callback function which calls synchronous + * threaded dbus methods + */ +struct _DBusGSyncThreadedData { + const DBusGMethodInfo *method; + GClosure *closure; + GValueArray *value_array; + GArray *out_param_values; + GValueArray *out_param_gvalues; +}; + +static gboolean +add_output_arguments (DBusMessageIter *iter, + const DBusGObjectInfo *object_info, + const DBusGMethodInfo *method, + GArray *out_param_values, + GValueArray *out_param_gvalues); + +/** + * Creates a return message for a given method invocation, with arguments. + * The arguments are the elements in the value array. + */ +static DBusMessage* +method_create_response (DBusGMethodInvocation *context, + GArray *out_param_values, + GValueArray *out_param_gvalues) +{ + DBusMessage *reply; + DBusMessageIter iter; + + reply = dbus_message_new_method_return (dbus_g_message_get_message (context->message)); + if (!reply) + { + g_error ("Out of memory"); + return NULL; + } + + dbus_message_iter_init_append (reply, &iter); + + if (!add_output_arguments (&iter, + context->object, + context->method, + out_param_values, + out_param_gvalues)) + { + g_error ("Out of memory"); + dbus_message_unref (reply); + return NULL; + } + + return reply; +} + +/** + * This function is called in a thread and invokes regular synchronous + * dbus method. + * + * The context which is supposed to be the last element in the value_array + * is used for sending dbus messages. Messages are sent using the + * dbus_g_method_send_reply() function which also unrefs the context. + * + * This function will free all the three arrays used for sending/getting + * arguments: value_array, out_param_values and out_param_gvalues. + * + * Note that the dbus method called by the marshaller is a regular syncronous + * method so the context is hidden from it! + */ +static void +invoke_marshaller_threaded (gpointer data) +{ + GError *error = NULL; + guint last_element; + DBusGMethodInvocation *context; + GValue return_value = {0,}; + DBusMessage *reply = NULL; + DBusGSyncThreadedData *method_data; + gboolean send_reply; + + method_data = (DBusGSyncThreadedData *)data; + + g_assert (method_data != NULL); + + const DBusGMethodInfo *method = method_data->method; + GClosure *closure = method_data->closure; + GValueArray *value_array = method_data->value_array; + GArray *out_param_values = method_data->out_param_values; + GValueArray *out_param_gvalues = method_data->out_param_gvalues; + + g_assert (method != NULL); + g_assert (closure != NULL); + g_assert (value_array != NULL); + g_assert (out_param_values != NULL); + g_assert (out_param_gvalues != NULL); + + /* + * Init return_value, it is needed by the marshaller function + * if the dbus function(the one described in the xml file) is not void + * Actually its value is never considered, perhaps that should + * be fixed? + */ + g_value_init (&return_value, G_TYPE_BOOLEAN); + + /* Get the index of the last element in the value_array */ + last_element = value_array->n_values - 1; + + /* Get the context */ + context = g_value_get_pointer (value_array->values + last_element); + g_assert (context != NULL); + + /* + * This field was initialized inside invoke_object_method; we + * carry it over through the sync threaded invocation to here. + */ + send_reply = context->send_reply; + + /* Remove the context and add the error instead */ + g_value_unset (g_value_array_get_nth (value_array, last_element)); + g_value_init (g_value_array_get_nth (value_array, last_element), G_TYPE_POINTER); + g_value_set_pointer (g_value_array_get_nth (value_array, last_element), &error); + + /* Invoke marshaller */ + method->marshaller (closure, + &return_value, + value_array->n_values, + value_array->values, + NULL, + method->function); + + /* Return a message with arguments or error */ + if (error) + { + if (send_reply) + reply = gerror_to_dbus_error_message (context->object, dbus_g_message_get_message (context->message), error); + g_error_free (error); + } + else + { + if (send_reply) + reply = method_create_response (context, out_param_values, out_param_gvalues); + } + + g_array_free (out_param_values, TRUE); + g_value_array_free (out_param_gvalues); + g_value_array_free (value_array); + + g_slice_free (DBusGSyncThreadedData, method_data); + + if (reply) + { + dbus_g_method_send_reply (context, reply); + } +} + static DBusHandlerResult invoke_object_method (GObject *object, const DBusGObjectInfo *object_info, @@ -1118,11 +1281,12 @@ invoke_object_method (GObject *object, DBusConnection *connection, DBusMessage *message) { - gboolean had_error, is_async, send_reply; + gboolean send_reply; + GMethodCallType call_type; + gboolean had_error; GError *gerror; GValueArray *value_array; GValue return_value = {0,}; - GClosure closure; char *in_signature; GArray *out_param_values = NULL; GValueArray *out_param_gvalues = NULL; @@ -1135,35 +1299,52 @@ invoke_object_method (GObject *object, gboolean retval_is_synthetic; gboolean retval_is_constant; const char *arg_metadata; + const char *call_type_str; + DBusGSyncThreadedData *method_data; + + /* This is evil. We do this to work around the fact that + * the generated glib marshallers check a flag in the closure object + * which we don't care about. We don't need/want to create + * a new closure for each invocation. + */ + static GClosure closure = {0}; gerror = NULL; - /* This flag says whether invokee is handed a special DBusGMethodInvocation structure, - * instead of being required to fill out all return values in the context of the function. - * Some additional data is also exposed, such as the message sender. + /* Determine how the method is to be called. 3 possible ways: + * + * synchronously - method_call_type 'S' + * synchronously in a thread - method_call_type 'T' + * asynchronously - method_call_type 'A' */ - is_async = strcmp (string_table_lookup (get_method_data (object_info, method), 2), "A") == 0; - + call_type_str = string_table_lookup (get_method_data (object_info, method), 2); + switch (*call_type_str) + { + case 'A': /* call method asynchronously */ + call_type = ASYNCHRONOUS; + break; + case 'T': /* call synchronous method in a thread */ + call_type = SYNCHRONOUS_THREADED; + break; + case 'S': /* call fully synchronous method */ + default: + call_type = SYNCHRONOUS; + break; + } + /* Messages can be sent with a flag that says "I don't need a reply". This is an optimization * normally, but in the context of the system bus it's important to not send a reply * to these kinds of messages, because they will be unrequested replies, and thus subject * to denial and logging. We don't want to fill up logs. * http://bugs.freedesktop.org/show_bug.cgi?id=19441 */ - send_reply = !dbus_message_get_no_reply (message); + send_reply = !dbus_message_get_no_reply (message); have_retval = FALSE; retval_signals_error = FALSE; retval_is_synthetic = FALSE; retval_is_constant = FALSE; - /* This is evil. We do this to work around the fact that - * the generated glib marshallers check a flag in the closure object - * which we don't care about. We don't need/want to create - * a new closure for each invocation. - */ - memset (&closure, 0, sizeof (closure)); - in_signature = method_input_signature_from_object_info (object_info, method); /* Convert method IN parameters to GValueArray */ @@ -1200,22 +1381,8 @@ invoke_object_method (GObject *object, g_value_array_prepend (value_array, NULL); g_value_init (g_value_array_get_nth (value_array, 0), G_TYPE_OBJECT); g_value_set_object (g_value_array_get_nth (value_array, 0), object); - - if (is_async) - { - GValue context_value = {0,}; - DBusGMethodInvocation *context; - context = g_new (DBusGMethodInvocation, 1); - context->connection = dbus_g_connection_ref (DBUS_G_CONNECTION_FROM_CONNECTION (connection)); - context->message = dbus_g_message_ref (DBUS_G_MESSAGE_FROM_MESSAGE (message)); - context->object = object_info; - context->method = method; - context->send_reply = send_reply; - g_value_init (&context_value, G_TYPE_POINTER); - g_value_set_pointer (&context_value, context); - g_value_array_append (value_array, &context_value); - } - else + + if (ASYNCHRONOUS!=call_type) { RetvalType retval; gboolean arg_in; @@ -1271,7 +1438,7 @@ invoke_object_method (GObject *object, * is a "synthetic" return value; i.e. we aren't going to be * sending it over the bus, it's just to signal an error. */ - if (!have_retval) + if (!have_retval && SYNCHRONOUS_THREADED!=call_type) { have_retval = TRUE; retval_is_synthetic = TRUE; @@ -1334,21 +1501,69 @@ invoke_object_method (GObject *object, } } - /* Append GError as final argument if necessary */ + /* + * Append GError as final argument if necessary + * Note: we never do this for asynchronous and synchronous threaded calls! + */ if (retval_signals_error) { g_assert (have_retval); + g_assert (SYNCHRONOUS==call_type); g_value_array_append (value_array, NULL); g_value_init (g_value_array_get_nth (value_array, value_array->n_values - 1), G_TYPE_POINTER); g_value_set_pointer (g_value_array_get_nth (value_array, value_array->n_values - 1), &gerror); } - + + /* + * Append DBusGMethodInvocation as final argument in case of + * asynchronous or synchronous threaded calls + */ + if (ASYNCHRONOUS==call_type || SYNCHRONOUS_THREADED==call_type) + { + GValue context_value = {0,}; + DBusGMethodInvocation *context; + context = g_new (DBusGMethodInvocation, 1); + context->connection = dbus_g_connection_ref (DBUS_G_CONNECTION_FROM_CONNECTION (connection)); + context->message = dbus_g_message_ref (DBUS_G_MESSAGE_FROM_MESSAGE (message)); + context->object = object_info; + context->method = method; + context->send_reply = send_reply; + g_value_init (&context_value, G_TYPE_POINTER); + g_value_set_pointer (&context_value, context); + g_value_array_append (value_array, &context_value); + } + /* Actually invoke method */ - method->marshaller (&closure, have_retval ? &return_value : NULL, - value_array->n_values, - value_array->values, - NULL, method->function); - if (is_async) + switch (call_type) + { + case SYNCHRONOUS_THREADED: + method_data = g_slice_new (DBusGSyncThreadedData); + if (!method_data) + goto nomem; + + method_data->method = method; + method_data->closure = &closure; + method_data->value_array = value_array; + method_data->out_param_values = out_param_values; + method_data->out_param_gvalues = out_param_gvalues; + + if (!_dbus_gasync_thread_pool_start (invoke_marshaller_threaded, + method_data)) + { + g_slice_free (DBusGSyncThreadedData, method_data); + goto nomem; + } + break; + case ASYNCHRONOUS: + case SYNCHRONOUS: + method->marshaller (&closure, have_retval ? &return_value : NULL, + value_array->n_values, + value_array->values, + NULL, method->function); + break; + } + + if (ASYNCHRONOUS==call_type || SYNCHRONOUS_THREADED==call_type) { result = DBUS_HANDLER_RESULT_HANDLED; goto done; @@ -1379,68 +1594,21 @@ invoke_object_method (GObject *object, /* First, append the return value, unless it's synthetic */ if (have_retval && !retval_is_synthetic) - { + { if (send_reply && !_dbus_gvalue_marshal (&iter, &return_value)) goto nomem; if (!retval_is_constant) g_value_unset (&return_value); } - /* Grab the argument metadata and iterate over it */ - arg_metadata = method_arg_info_from_object_info (object_info, method); - /* Now append any remaining return values */ - out_param_pos = 0; - out_param_gvalue_pos = 0; - while (*arg_metadata) - { - GValue gvalue = {0, }; - const char *arg_name; - gboolean arg_in; - gboolean constval; - RetvalType retval; - const char *arg_signature; - DBusSignatureIter argsigiter; - - do - { - /* Iterate over only output values; skip over input - arguments and the return value */ - arg_metadata = arg_iterate (arg_metadata, &arg_name, &arg_in, &constval, &retval, &arg_signature); - } - while ((arg_in || retval != RETVAL_NONE) && *arg_metadata); - - /* If the last argument we saw was input or the return - * value, we must be done iterating over output arguments. - */ - if (arg_in || retval != RETVAL_NONE) - break; - - dbus_signature_iter_init (&argsigiter, arg_signature); - - g_value_init (&gvalue, _dbus_gtype_from_signature_iter (&argsigiter, FALSE)); - if (G_VALUE_TYPE (&gvalue) != G_TYPE_VALUE) - { - if (!_dbus_gvalue_take (&gvalue, - &(g_array_index (out_param_values, GTypeCValue, out_param_pos)))) - g_assert_not_reached (); - out_param_pos++; - } - else - { - g_value_set_static_boxed (&gvalue, out_param_gvalues->values + out_param_gvalue_pos); - out_param_gvalue_pos++; - } - - if (send_reply && !_dbus_gvalue_marshal (&iter, &gvalue)) - goto nomem; - /* Here we actually free the allocated value; we - * took ownership of it with _dbus_gvalue_take, unless - * an annotation has specified this value as constant. - */ - if (!constval) - g_value_unset (&gvalue); - } + if (send_reply && + !add_output_arguments (&iter, + object_info, + method, + out_param_values, + out_param_gvalues)) + goto nomem; } else if (send_reply) reply = gerror_to_dbus_error_message (object_info, message, gerror); @@ -1454,18 +1622,98 @@ invoke_object_method (GObject *object, result = DBUS_HANDLER_RESULT_HANDLED; done: g_free (in_signature); - if (!is_async) + if (SYNCHRONOUS_THREADED!=call_type) { - g_array_free (out_param_values, TRUE); - g_value_array_free (out_param_gvalues); + /* + * if call_type is synchronous threaded the arrays will be freed + * within the thread_pool by invoke_marshaller_method() + */ + if (out_param_values) + g_array_free (out_param_values, TRUE); + if (out_param_gvalues) + g_value_array_free (out_param_gvalues); + g_value_array_free (value_array); } - g_value_array_free (value_array); return result; nomem: result = DBUS_HANDLER_RESULT_NEED_MEMORY; goto done; } +static gboolean +add_output_arguments (DBusMessageIter *iter, + const DBusGObjectInfo *object_info, + const DBusGMethodInfo *method, + GArray *out_param_values, + GValueArray *out_param_gvalues) +{ + const char *arg_metadata; + int out_param_pos, out_param_gvalue_pos; + gboolean res = TRUE; + + /* Grab the argument metadata and iterate over it */ + arg_metadata = method_arg_info_from_object_info (object_info, method); + + /* Append the remaining return values */ + out_param_pos = 0; + out_param_gvalue_pos = 0; + while (*arg_metadata) + { + GValue gvalue = {0, }; + const char *arg_name; + gboolean arg_in; + gboolean constval; + RetvalType retval; + const char *arg_signature; + DBusSignatureIter argsigiter; + + do + { + /* Iterate over only output values; skip over input + arguments and the return value */ + arg_metadata = arg_iterate (arg_metadata, &arg_name, &arg_in, &constval, &retval, &arg_signature); + } + while ((arg_in || retval != RETVAL_NONE) && *arg_metadata); + + /* If the last argument we saw was input or the return + * value, we must be done iterating over output arguments. + */ + if (arg_in || retval != RETVAL_NONE) + break; + + dbus_signature_iter_init (&argsigiter, arg_signature); + + g_value_init (&gvalue, _dbus_gtype_from_signature_iter (&argsigiter, FALSE)); + if (G_VALUE_TYPE (&gvalue) != G_TYPE_VALUE) + { + if (!_dbus_gvalue_take (&gvalue, + &(g_array_index (out_param_values, GTypeCValue, out_param_pos)))) + g_assert_not_reached (); + out_param_pos++; + } + else + { + g_value_set_static_boxed (&gvalue, out_param_gvalues->values + out_param_gvalue_pos); + out_param_gvalue_pos++; + } + + if (!_dbus_gvalue_marshal (iter, &gvalue)) + { + res = FALSE; + break; + } + + /* Here we actually free the allocated value; we + * took ownership of it with _dbus_gvalue_take, unless + * an annotation has specified this value as constant. + */ + if (!constval) + g_value_unset (&gvalue); + } + + return res; +} + static DBusHandlerResult gobject_message_function (DBusConnection *connection, DBusMessage *message, @@ -2327,7 +2575,7 @@ dbus_g_method_return (DBusGMethodInvocation *context, ...) char *out_sig; GArray *argsig; guint i; - + /* This field was initialized inside invoke_object_method; we * carry it over through the async invocation to here. */ @@ -2379,11 +2627,11 @@ void dbus_g_method_return_error (DBusGMethodInvocation *context, GError *error) { DBusMessage *reply; - + /* See comment in dbus_g_method_return */ if (!context->send_reply) - return; - + return; + reply = gerror_to_dbus_error_message (context->object, dbus_g_message_get_message (context->message), error); dbus_connection_send (dbus_g_connection_get_connection (context->connection), reply, NULL); dbus_message_unref (reply);