GDBusConnection: Avoid callbacks on finalized connection
[platform/upstream/glib.git] / gio / gdbusprivate.c
index 60f9bbf..442d5e1 100644 (file)
@@ -37,6 +37,7 @@
 #include "gasyncresult.h"
 #include "gsimpleasyncresult.h"
 #include "ginputstream.h"
+#include "gmemoryinputstream.h"
 #include "giostream.h"
 #include "gsocketcontrolmessage.h"
 #include "gsocketconnection.h"
@@ -229,6 +230,34 @@ _g_socket_read_with_control_messages_finish (GSocket       *socket,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+/* Work-around for https://bugzilla.gnome.org/show_bug.cgi?id=627724 */
+
+static GPtrArray *ensured_classes = NULL;
+
+static void
+ensure_type (GType gtype)
+{
+  g_ptr_array_add (ensured_classes, g_type_class_ref (gtype));
+}
+
+static void
+released_required_types (void)
+{
+  g_ptr_array_foreach (ensured_classes, (GFunc) g_type_class_unref, NULL);
+  g_ptr_array_unref (ensured_classes);
+  ensured_classes = NULL;
+}
+
+static void
+ensure_required_types (void)
+{
+  g_assert (ensured_classes == NULL);
+  ensured_classes = g_ptr_array_new ();
+  ensure_type (G_TYPE_SIMPLE_ASYNC_RESULT);
+  ensure_type (G_TYPE_MEMORY_INPUT_STREAM);
+}
+/* ---------------------------------------------------------------------------------------------------- */
+
 G_LOCK_DEFINE_STATIC (shared_thread_lock);
 
 typedef struct
@@ -242,7 +271,7 @@ typedef struct
 static SharedThreadData *shared_thread_data = NULL;
 
 static gpointer
-shared_thread_func (gpointer data)
+gdbus_shared_thread_func (gpointer data)
 {
   g_main_context_push_thread_default (shared_thread_data->context);
   g_main_loop_run (shared_thread_data->loop);
@@ -268,6 +297,8 @@ invoke_caller (gpointer user_data)
   return FALSE;
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
 static void
 _g_dbus_shared_thread_ref (GDBusSharedThreadFunc func,
                            gpointer              user_data)
@@ -275,9 +306,12 @@ _g_dbus_shared_thread_ref (GDBusSharedThreadFunc func,
   GError *error;
   GSource *idle_source;
   CallerData *data;
+  gboolean release_types;
 
   G_LOCK (shared_thread_lock);
 
+  release_types = FALSE;
+
   if (shared_thread_data != NULL)
     {
       shared_thread_data->num_users += 1;
@@ -287,10 +321,14 @@ _g_dbus_shared_thread_ref (GDBusSharedThreadFunc func,
   shared_thread_data = g_new0 (SharedThreadData, 1);
   shared_thread_data->num_users = 1;
 
+  /* Work-around for https://bugzilla.gnome.org/show_bug.cgi?id=627724 */
+  ensure_required_types ();
+  release_types = TRUE;
+
   error = NULL;
   shared_thread_data->context = g_main_context_new ();
   shared_thread_data->loop = g_main_loop_new (shared_thread_data->context, FALSE);
-  shared_thread_data->thread = g_thread_create (shared_thread_func,
+  shared_thread_data->thread = g_thread_create (gdbus_shared_thread_func,
                                                 NULL,
                                                 TRUE,
                                                 &error);
@@ -316,6 +354,9 @@ _g_dbus_shared_thread_ref (GDBusSharedThreadFunc func,
   while (!data->done)
     g_thread_yield ();
 
+  if (release_types)
+    released_required_types ();
+
   g_free (data);
 
   G_UNLOCK (shared_thread_lock);
@@ -445,6 +486,8 @@ _g_dbus_worker_unref (GDBusWorker *worker)
       g_queue_foreach (worker->write_queue, (GFunc) message_to_write_data_free, NULL);
       g_queue_free (worker->write_queue);
 
+      g_free (worker->read_buffer);
+
       g_free (worker);
     }
 }
@@ -466,14 +509,15 @@ _g_dbus_worker_emit_message_received (GDBusWorker  *worker,
     worker->message_received_callback (worker, message, worker->user_data);
 }
 
-static gboolean
+static GDBusMessage *
 _g_dbus_worker_emit_message_about_to_be_sent (GDBusWorker  *worker,
                                               GDBusMessage *message)
 {
-  gboolean ret;
-  ret = FALSE;
+  GDBusMessage *ret;
   if (!worker->stopped)
     ret = worker->message_about_to_be_sent_callback (worker, message, worker->user_data);
+  else
+    ret = message;
   return ret;
 }
 
@@ -715,6 +759,7 @@ _g_dbus_worker_do_read_cb (GInputStream  *input_stream,
           if (worker->read_fd_list != NULL)
             {
               g_dbus_message_set_unix_fd_list (message, worker->read_fd_list);
+              g_object_unref (worker->read_fd_list);
               worker->read_fd_list = NULL;
             }
 #endif
@@ -967,7 +1012,7 @@ write_message_continue_writing (MessageToWriteData *data)
       if (bytes_written == -1)
         {
           /* Handle WOULD_BLOCK by waiting until there's room in the buffer */
-          if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_WOULD_BLOCK)
+          if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
             {
               GSource *source;
               source = g_socket_create_source (data->worker->socket,
@@ -979,6 +1024,7 @@ write_message_continue_writing (MessageToWriteData *data)
                                      NULL); /* GDestroyNotify */
               g_source_attach (source, g_main_context_get_thread_default ());
               g_source_unref (source);
+              g_error_free (error);
               goto out;
             }
           g_simple_async_result_set_from_error (simple, error);
@@ -1005,6 +1051,7 @@ write_message_continue_writing (MessageToWriteData *data)
 #endif
   else
     {
+#ifdef G_OS_UNIX
       if (fd_list != NULL)
         {
           g_simple_async_result_set_error (simple,
@@ -1016,6 +1063,7 @@ write_message_continue_writing (MessageToWriteData *data)
           g_object_unref (simple);
           goto out;
         }
+#endif
 
       g_output_stream_write_async (ostream,
                                    (const gchar *) data->blob + data->total_written,
@@ -1237,10 +1285,20 @@ maybe_write_next_message (GDBusWorker *worker)
    */
   if (data != NULL)
     {
-      gboolean message_was_dropped;
-      message_was_dropped = _g_dbus_worker_emit_message_about_to_be_sent (worker, data->message);
-      if (G_UNLIKELY (message_was_dropped))
+      GDBusMessage *old_message;
+      guchar *new_blob;
+      gsize new_blob_size;
+      GError *error;
+
+      old_message = data->message;
+      data->message = _g_dbus_worker_emit_message_about_to_be_sent (worker, data->message);
+      if (data->message == old_message)
         {
+          /* filters had no effect - do nothing */
+        }
+      else if (data->message == NULL)
+        {
+          /* filters dropped message */
           g_mutex_lock (worker->write_lock);
           worker->num_writes_pending -= 1;
           g_mutex_unlock (worker->write_lock);
@@ -1249,11 +1307,34 @@ maybe_write_next_message (GDBusWorker *worker)
         }
       else
         {
-          write_message_async (worker,
-                               data,
-                               write_message_cb,
-                               data);
+          /* filters altered the message -> reencode */
+          error = NULL;
+          new_blob = g_dbus_message_to_blob (data->message,
+                                             &new_blob_size,
+                                             worker->capabilities,
+                                             &error);
+          if (new_blob == NULL)
+            {
+              /* if filter make the GDBusMessage unencodeable, just complain on stderr and send
+               * the old message instead
+               */
+              g_warning ("Error encoding GDBusMessage with serial %d altered by filter function: %s",
+                         g_dbus_message_get_serial (data->message),
+                         error->message);
+              g_error_free (error);
+            }
+          else
+            {
+              g_free (data->blob);
+              data->blob = (gchar *) new_blob;
+              data->blob_size = new_blob_size;
+            }
         }
+
+      write_message_async (worker,
+                           data,
+                           write_message_cb,
+                           data);
     }
 }
 
@@ -1362,22 +1443,14 @@ _g_dbus_worker_new (GIOStream                              *stream,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-/* This can be called from any thread - frees worker - guarantees no callbacks
- * will ever be issued again
+/* This can be called from any thread - frees worker. Note that
+ * callbacks might still happen if called from another thread than the
+ * worker - use your own synchronization primitive in the callbacks.
  */
 void
 _g_dbus_worker_stop (GDBusWorker *worker)
 {
-  /* If we're called in the worker thread it means we are called from
-   * a worker callback. This is fine, we just can't lock in that case since
-   * we're already holding the lock...
-   */
-  if (g_thread_self () != worker->thread)
-    g_mutex_lock (worker->read_lock);
   worker->stopped = TRUE;
-  if (g_thread_self () != worker->thread)
-    g_mutex_unlock (worker->read_lock);
-
   g_cancellable_cancel (worker->cancellable);
   _g_dbus_worker_unref (worker);
 }