GIOStream: support for unemulated async close()
authorRyan Lortie <desrt@desrt.ca>
Tue, 20 Jan 2015 13:11:02 +0000 (08:11 -0500)
committerRyan Lortie <desrt@desrt.ca>
Tue, 17 Feb 2015 21:17:01 +0000 (16:17 -0500)
Add an implementation of non-thread-emulated async close of a GIOStream
if either of the underlying stream objects support it.

This prevents us from calling close() functions from another thread on
an object that may not be expecting that.  It also allows us to skip the
thread entirely in case our objects support a pure async close.

https://bugzilla.gnome.org/show_bug.cgi?id=741630

gio/giostream.c

index cd177ac..185062f 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "giostream.h"
 #include "gasyncresult.h"
+#include "gioprivate.h"
 #include "gtask.h"
 
 /**
@@ -542,6 +543,53 @@ close_async_thread (GTask        *task,
   g_task_return_boolean (task, TRUE);
 }
 
+typedef struct
+{
+  GError *error;
+  gint pending;
+} CloseAsyncData;
+
+static void
+stream_close_complete (GObject      *source,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  GTask *task = user_data;
+  CloseAsyncData *data;
+
+  data = g_task_get_task_data (task);
+  data->pending--;
+
+  if (G_IS_OUTPUT_STREAM (source))
+    {
+      GError *error = NULL;
+
+      /* Match behaviour with the sync route and give precedent to the
+       * error returned from closing the output stream.
+       */
+      g_output_stream_close_finish (G_OUTPUT_STREAM (source), result, &error);
+      if (error)
+        {
+          if (data->error)
+            g_error_free (data->error);
+          data->error = error;
+        }
+    }
+  else
+    g_input_stream_close_finish (G_INPUT_STREAM (source), result, data->error ? NULL : &data->error);
+
+  if (data->pending == 0)
+    {
+      if (data->error)
+        g_task_return_error (task, data->error);
+      else
+        g_task_return_boolean (task, TRUE);
+
+      g_slice_free (CloseAsyncData, data);
+      g_object_unref (task);
+    }
+}
+
 static void
 g_io_stream_real_close_async (GIOStream           *stream,
                              int                  io_priority,
@@ -549,14 +597,41 @@ g_io_stream_real_close_async (GIOStream           *stream,
                              GAsyncReadyCallback  callback,
                              gpointer             user_data)
 {
+  GInputStream *input;
+  GOutputStream *output;
   GTask *task;
 
   task = g_task_new (stream, cancellable, callback, user_data);
   g_task_set_check_cancellable (task, FALSE);
   g_task_set_priority (task, io_priority);
-  
-  g_task_run_in_thread (task, close_async_thread);
-  g_object_unref (task);
+
+  input = g_io_stream_get_input_stream (stream);
+  output = g_io_stream_get_output_stream (stream);
+
+  if (g_input_stream_async_close_is_via_threads (input) && g_output_stream_async_close_is_via_threads (output))
+    {
+      /* No sense in dispatching to the thread twice -- just do it all
+       * in one go.
+       */
+      g_task_run_in_thread (task, close_async_thread);
+      g_object_unref (task);
+    }
+  else
+    {
+      CloseAsyncData *data;
+
+      /* We should avoid dispatching to another thread in case either
+       * object that would not do it for itself because it may not be
+       * threadsafe.
+       */
+      data = g_slice_new (CloseAsyncData);
+      data->error = NULL;
+      data->pending = 2;
+
+      g_task_set_task_data (task, data, NULL);
+      g_input_stream_close_async (input, io_priority, cancellable, stream_close_complete, task);
+      g_output_stream_close_async (output, io_priority, cancellable, stream_close_complete, task);
+    }
 }
 
 static gboolean