mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-25 23:16:14 +01:00
Bug 626748 – Use async methods for writing and handle EAGAIN
If sending a lot of data and/or the other peer is not reading it, then socket buffers can overflow. This is communicated from the kernel by returning EAGAIN. In GIO, it is modelled by g_output_stream_write() and g_socket_send_message() returning G_IO_ERROR_WOULD_BLOCK. It is also problematic that that we're using synchronous IO in the shared GDBus IO thread. It means that one GDBusConnection can lock up others. It turns out that by porting from g_output_stream_write() to g_output_stream_write_async() we fix the EAGAIN issue. For GSocket, we still need to handle things manually (by creating a GSource) as g_socket_send_message() is used. We check the new behavior in Michael's producer/consumer test case (at /gdbus/overflow in gdbus-peer.c) added in the last commit. Also add a test case that sends and receives a 20 MiB message. Also add a new `transport' G_DBUS_DEBUG option so it is easy to inspect partial writes: $ G_DBUS_DEBUG=transport ./gdbus-connection -p /gdbus/connection/large_message [...] ======================================================================== GDBus-debug:Transport: >>>> WROTE 128000 bytes of message with serial 4 and size 20971669 from offset 0 on a GSocketOutputStream ======================================================================== GDBus-debug:Transport: >>>> WROTE 128000 bytes of message with serial 4 and size 20971669 from offset 128000 on a GSocketOutputStream ======================================================================== GDBus-debug:Transport: >>>> WROTE 128000 bytes of message with serial 4 and size 20971669 from offset 256000 on a GSocketOutputStream [...] ======================================================================== GDBus-debug:Transport: >>>> WROTE 43669 bytes of message with serial 4 and size 20971669 from offset 20928000 on a GSocketOutputStream [...] ======================================================================== GDBus-debug:Transport: <<<< READ 16 bytes of message with serial 3 and size 20971620 to offset 0 from a GSocketInputStream ======================================================================== GDBus-debug:Transport: <<<< READ 15984 bytes of message with serial 3 and size 20971620 to offset 16 from a GSocketInputStream ======================================================================== GDBus-debug:Transport: <<<< READ 16000 bytes of message with serial 3 and size 20971620 to offset 16000 from a GSocketInputStream [...] ======================================================================== GDBus-debug:Transport: <<<< READ 144000 bytes of message with serial 3 and size 20971620 to offset 20720000 from a GSocketInputStream ======================================================================== GDBus-debug:Transport: <<<< READ 107620 bytes of message with serial 3 and size 20971620 to offset 20864000 from a GSocketInputStream OK https://bugzilla.gnome.org/show_bug.cgi?id=626748 Signed-off-by: David Zeuthen <davidz@redhat.com>
This commit is contained in:
parent
a6264a3a19
commit
8a3a4596e2
@ -339,6 +339,10 @@
|
||||
cause GLib to print out different types of debugging
|
||||
information when using the D-Bus routines.
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>transport</term>
|
||||
<listitem><para>Show IO activity (e.g. reads and writes)</para></listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>message</term>
|
||||
<listitem><para>Show all sent and received D-Bus messages</para></listitem>
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "giostream.h"
|
||||
#include "gsocketcontrolmessage.h"
|
||||
#include "gsocketconnection.h"
|
||||
#include "gsocketoutputstream.h"
|
||||
|
||||
#ifdef G_OS_UNIX
|
||||
#include "gunixfdmessage.h"
|
||||
@ -386,16 +387,19 @@ struct GDBusWorker
|
||||
/* used for writing */
|
||||
GMutex *write_lock;
|
||||
GQueue *write_queue;
|
||||
gboolean write_is_pending;
|
||||
gint num_writes_pending;
|
||||
guint64 write_num_messages_written;
|
||||
GList *write_pending_flushes;
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GMutex *mutex;
|
||||
GCond *cond;
|
||||
guint64 number_to_wait_for;
|
||||
GError *error;
|
||||
} FlushData;
|
||||
|
||||
struct _MessageToWriteData ;
|
||||
@ -403,6 +407,14 @@ typedef struct _MessageToWriteData MessageToWriteData;
|
||||
|
||||
static void message_to_write_data_free (MessageToWriteData *data);
|
||||
|
||||
static void read_message_print_transport_debug (gssize bytes_read,
|
||||
GDBusWorker *worker);
|
||||
|
||||
static void write_message_print_transport_debug (gssize bytes_written,
|
||||
MessageToWriteData *data);
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
static GDBusWorker *
|
||||
_g_dbus_worker_ref (GDBusWorker *worker)
|
||||
{
|
||||
@ -646,6 +658,8 @@ _g_dbus_worker_do_read_cb (GInputStream *input_stream,
|
||||
goto out;
|
||||
}
|
||||
|
||||
read_message_print_transport_debug (bytes_read, worker);
|
||||
|
||||
worker->read_buffer_cur_size += bytes_read;
|
||||
if (worker->read_buffer_bytes_wanted == worker->read_buffer_cur_size)
|
||||
{
|
||||
@ -803,14 +817,20 @@ _g_dbus_worker_do_read (GDBusWorker *worker)
|
||||
|
||||
struct _MessageToWriteData
|
||||
{
|
||||
GDBusWorker *worker;
|
||||
GDBusMessage *message;
|
||||
gchar *blob;
|
||||
gsize blob_size;
|
||||
|
||||
gsize total_written;
|
||||
GSimpleAsyncResult *simple;
|
||||
|
||||
};
|
||||
|
||||
static void
|
||||
message_to_write_data_free (MessageToWriteData *data)
|
||||
{
|
||||
_g_dbus_worker_unref (data->worker);
|
||||
g_object_unref (data->message);
|
||||
g_free (data->blob);
|
||||
g_free (data);
|
||||
@ -818,115 +838,316 @@ message_to_write_data_free (MessageToWriteData *data)
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
static void write_message_continue_writing (MessageToWriteData *data);
|
||||
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static void
|
||||
write_message_async_cb (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
MessageToWriteData *data = user_data;
|
||||
GSimpleAsyncResult *simple;
|
||||
gssize bytes_written;
|
||||
GError *error;
|
||||
|
||||
/* Note: we can't access data->simple after calling g_async_result_complete () because the
|
||||
* callback can free @data and we're not completing in idle. So use a copy of the pointer.
|
||||
*/
|
||||
simple = data->simple;
|
||||
|
||||
error = NULL;
|
||||
bytes_written = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object),
|
||||
res,
|
||||
&error);
|
||||
if (bytes_written == -1)
|
||||
{
|
||||
g_simple_async_result_set_from_error (simple, error);
|
||||
g_error_free (error);
|
||||
g_simple_async_result_complete (simple);
|
||||
g_object_unref (simple);
|
||||
goto out;
|
||||
}
|
||||
g_assert (bytes_written > 0); /* zero is never returned */
|
||||
|
||||
write_message_print_transport_debug (bytes_written, data);
|
||||
|
||||
data->total_written += bytes_written;
|
||||
g_assert (data->total_written <= data->blob_size);
|
||||
if (data->total_written == data->blob_size)
|
||||
{
|
||||
g_simple_async_result_complete (simple);
|
||||
g_object_unref (simple);
|
||||
goto out;
|
||||
}
|
||||
|
||||
write_message_continue_writing (data);
|
||||
|
||||
out:
|
||||
;
|
||||
}
|
||||
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static gboolean
|
||||
write_message (GDBusWorker *worker,
|
||||
MessageToWriteData *data,
|
||||
GError **error)
|
||||
on_socket_ready (GSocket *socket,
|
||||
GIOCondition condition,
|
||||
gpointer user_data)
|
||||
{
|
||||
gboolean ret;
|
||||
GList *l;
|
||||
GList *ll;
|
||||
MessageToWriteData *data = user_data;
|
||||
write_message_continue_writing (data);
|
||||
return FALSE; /* remove source */
|
||||
}
|
||||
|
||||
g_return_val_if_fail (data->blob_size > 16, FALSE);
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static void
|
||||
write_message_continue_writing (MessageToWriteData *data)
|
||||
{
|
||||
GOutputStream *ostream;
|
||||
GSimpleAsyncResult *simple;
|
||||
#ifdef G_OS_UNIX
|
||||
GUnixFDList *fd_list;
|
||||
#endif
|
||||
|
||||
ret = FALSE;
|
||||
|
||||
/* First, the initial 16 bytes - special case UNIX sockets here
|
||||
* since it may involve writing an ancillary message with file
|
||||
* descriptors
|
||||
/* Note: we can't access data->simple after calling g_async_result_complete () because the
|
||||
* callback can free @data and we're not completing in idle. So use a copy of the pointer.
|
||||
*/
|
||||
simple = data->simple;
|
||||
|
||||
ostream = g_io_stream_get_output_stream (data->worker->stream);
|
||||
#ifdef G_OS_UNIX
|
||||
fd_list = g_dbus_message_get_unix_fd_list (data->message);
|
||||
#endif
|
||||
|
||||
g_assert (!g_output_stream_has_pending (ostream));
|
||||
g_assert_cmpint (data->total_written, <, data->blob_size);
|
||||
|
||||
if (FALSE)
|
||||
{
|
||||
}
|
||||
#ifdef G_OS_UNIX
|
||||
else if (worker->socket != NULL)
|
||||
else if (G_IS_SOCKET_OUTPUT_STREAM (ostream) && data->total_written == 0)
|
||||
{
|
||||
GOutputVector vector;
|
||||
GSocketControlMessage *message;
|
||||
GUnixFDList *fd_list;
|
||||
GSocketControlMessage *control_message;
|
||||
gssize bytes_written;
|
||||
|
||||
fd_list = g_dbus_message_get_unix_fd_list (data->message);
|
||||
|
||||
message = NULL;
|
||||
if (fd_list != NULL)
|
||||
{
|
||||
if (!G_IS_UNIX_CONNECTION (worker->stream))
|
||||
{
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_ARGUMENT,
|
||||
"Tried sending a file descriptor on unsupported stream of type %s",
|
||||
g_type_name (G_TYPE_FROM_INSTANCE (worker->stream)));
|
||||
goto out;
|
||||
}
|
||||
else if (!(worker->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING))
|
||||
{
|
||||
g_set_error_literal (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_ARGUMENT,
|
||||
"Tried sending a file descriptor but remote peer does not support this capability");
|
||||
goto out;
|
||||
}
|
||||
message = g_unix_fd_message_new_with_fd_list (fd_list);
|
||||
}
|
||||
GError *error;
|
||||
|
||||
vector.buffer = data->blob;
|
||||
vector.size = 16;
|
||||
vector.size = data->blob_size;
|
||||
|
||||
bytes_written = g_socket_send_message (worker->socket,
|
||||
control_message = NULL;
|
||||
if (fd_list != NULL && g_unix_fd_list_get_length (fd_list) > 0)
|
||||
{
|
||||
if (!(data->worker->capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING))
|
||||
{
|
||||
g_simple_async_result_set_error (simple,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"Tried sending a file descriptor but remote peer does not support this capability");
|
||||
g_simple_async_result_complete (simple);
|
||||
g_object_unref (simple);
|
||||
goto out;
|
||||
}
|
||||
control_message = g_unix_fd_message_new_with_fd_list (fd_list);
|
||||
}
|
||||
|
||||
error = NULL;
|
||||
bytes_written = g_socket_send_message (data->worker->socket,
|
||||
NULL, /* address */
|
||||
&vector,
|
||||
1,
|
||||
message != NULL ? &message : NULL,
|
||||
message != NULL ? 1 : 0,
|
||||
control_message != NULL ? &control_message : NULL,
|
||||
control_message != NULL ? 1 : 0,
|
||||
G_SOCKET_MSG_NONE,
|
||||
worker->cancellable,
|
||||
error);
|
||||
data->worker->cancellable,
|
||||
&error);
|
||||
if (control_message != NULL)
|
||||
g_object_unref (control_message);
|
||||
|
||||
if (bytes_written == -1)
|
||||
{
|
||||
g_prefix_error (error, _("Error writing first 16 bytes of message to socket: "));
|
||||
if (message != NULL)
|
||||
g_object_unref (message);
|
||||
/* 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)
|
||||
{
|
||||
GSource *source;
|
||||
source = g_socket_create_source (data->worker->socket,
|
||||
G_IO_OUT | G_IO_HUP | G_IO_ERR,
|
||||
data->worker->cancellable);
|
||||
g_source_set_callback (source,
|
||||
(GSourceFunc) on_socket_ready,
|
||||
data,
|
||||
NULL); /* GDestroyNotify */
|
||||
g_source_attach (source, g_main_context_get_thread_default ());
|
||||
g_source_unref (source);
|
||||
goto out;
|
||||
}
|
||||
g_simple_async_result_set_from_error (simple, error);
|
||||
g_error_free (error);
|
||||
g_simple_async_result_complete (simple);
|
||||
g_object_unref (simple);
|
||||
goto out;
|
||||
}
|
||||
if (message != NULL)
|
||||
g_object_unref (message);
|
||||
g_assert (bytes_written > 0); /* zero is never returned */
|
||||
|
||||
if (bytes_written < 16)
|
||||
write_message_print_transport_debug (bytes_written, data);
|
||||
|
||||
data->total_written += bytes_written;
|
||||
g_assert (data->total_written <= data->blob_size);
|
||||
if (data->total_written == data->blob_size)
|
||||
{
|
||||
/* TODO: I think this needs to be handled ... are we guaranteed that the ancillary
|
||||
* messages are sent?
|
||||
*/
|
||||
g_assert_not_reached ();
|
||||
g_simple_async_result_complete (simple);
|
||||
g_object_unref (simple);
|
||||
goto out;
|
||||
}
|
||||
|
||||
write_message_continue_writing (data);
|
||||
}
|
||||
#endif /* #ifdef G_OS_UNIX */
|
||||
#endif
|
||||
else
|
||||
{
|
||||
/* write the first 16 bytes (guaranteed to return an error if everything can't be written) */
|
||||
if (!g_output_stream_write_all (g_io_stream_get_output_stream (worker->stream),
|
||||
(const gchar *) data->blob,
|
||||
16,
|
||||
NULL, /* bytes_written */
|
||||
worker->cancellable, /* cancellable */
|
||||
error))
|
||||
goto out;
|
||||
if (fd_list != NULL)
|
||||
{
|
||||
g_simple_async_result_set_error (simple,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"Tried sending a file descriptor on unsupported stream of type %s",
|
||||
g_type_name (G_TYPE_FROM_INSTANCE (ostream)));
|
||||
g_simple_async_result_complete (simple);
|
||||
g_object_unref (simple);
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_output_stream_write_async (ostream,
|
||||
(const gchar *) data->blob + data->total_written,
|
||||
data->blob_size - data->total_written,
|
||||
G_PRIORITY_DEFAULT,
|
||||
data->worker->cancellable,
|
||||
write_message_async_cb,
|
||||
data);
|
||||
}
|
||||
out:
|
||||
;
|
||||
}
|
||||
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static void
|
||||
write_message_async (GDBusWorker *worker,
|
||||
MessageToWriteData *data,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
data->simple = g_simple_async_result_new (NULL,
|
||||
callback,
|
||||
user_data,
|
||||
write_message_async);
|
||||
data->total_written = 0;
|
||||
write_message_continue_writing (data);
|
||||
}
|
||||
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static gboolean
|
||||
write_message_finish (GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
g_warn_if_fail (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (res)) == write_message_async);
|
||||
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
||||
return FALSE;
|
||||
else
|
||||
return TRUE;
|
||||
}
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
static void maybe_write_next_message (GDBusWorker *worker);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GDBusWorker *worker;
|
||||
GList *flushers;
|
||||
} FlushAsyncData;
|
||||
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static void
|
||||
ostream_flush_cb (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
FlushAsyncData *data = user_data;
|
||||
GError *error;
|
||||
GList *l;
|
||||
|
||||
error = NULL;
|
||||
g_output_stream_flush_finish (G_OUTPUT_STREAM (source_object),
|
||||
res,
|
||||
&error);
|
||||
|
||||
if (error == NULL)
|
||||
{
|
||||
if (G_UNLIKELY (_g_dbus_debug_transport ()))
|
||||
{
|
||||
_g_dbus_debug_print_lock ();
|
||||
g_print ("========================================================================\n"
|
||||
"GDBus-debug:Transport:\n"
|
||||
" ---- FLUSHED stream of type %s\n",
|
||||
g_type_name (G_TYPE_FROM_INSTANCE (g_io_stream_get_output_stream (data->worker->stream))));
|
||||
_g_dbus_debug_print_unlock ();
|
||||
}
|
||||
}
|
||||
|
||||
/* Then write the rest of the message (guaranteed to return an error if everything can't be written) */
|
||||
if (!g_output_stream_write_all (g_io_stream_get_output_stream (worker->stream),
|
||||
(const gchar *) data->blob + 16,
|
||||
data->blob_size - 16,
|
||||
NULL, /* bytes_written */
|
||||
worker->cancellable, /* cancellable */
|
||||
error))
|
||||
goto out;
|
||||
g_assert (data->flushers != NULL);
|
||||
for (l = data->flushers; l != NULL; l = l->next)
|
||||
{
|
||||
FlushData *f = l->data;
|
||||
|
||||
ret = TRUE;
|
||||
f->error = error != NULL ? g_error_copy (error) : NULL;
|
||||
|
||||
/* wake up pending flushes */
|
||||
g_mutex_lock (f->mutex);
|
||||
g_cond_signal (f->cond);
|
||||
g_mutex_unlock (f->mutex);
|
||||
}
|
||||
g_list_free (data->flushers);
|
||||
|
||||
if (error != NULL)
|
||||
g_error_free (error);
|
||||
|
||||
/* OK, cool, finally kick off the next write */
|
||||
maybe_write_next_message (data->worker);
|
||||
|
||||
_g_dbus_worker_unref (data->worker);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static void
|
||||
message_written (GDBusWorker *worker,
|
||||
MessageToWriteData *message_data)
|
||||
{
|
||||
GList *l;
|
||||
GList *ll;
|
||||
GList *flushers;
|
||||
|
||||
/* first log the fact that we wrote a message */
|
||||
if (G_UNLIKELY (_g_dbus_debug_message ()))
|
||||
{
|
||||
gchar *s;
|
||||
_g_dbus_debug_print_lock ();
|
||||
g_print ("========================================================================\n"
|
||||
"GDBus-debug:Message:\n"
|
||||
" >>>> SENT D-Bus message (%" G_GSIZE_FORMAT " bytes)\n",
|
||||
message_data->blob_size);
|
||||
s = g_dbus_message_print (message_data->message, 2);
|
||||
g_print ("%s", s);
|
||||
g_free (s);
|
||||
if (G_UNLIKELY (_g_dbus_debug_payload ()))
|
||||
{
|
||||
s = _g_dbus_hexdump (message_data->blob, message_data->blob_size, 2);
|
||||
g_print ("%s\n", s);
|
||||
g_free (s);
|
||||
}
|
||||
_g_dbus_debug_print_unlock ();
|
||||
}
|
||||
|
||||
/* then first wake up pending flushes and, if needed, flush the stream */
|
||||
flushers = NULL;
|
||||
g_mutex_lock (worker->write_lock);
|
||||
worker->write_num_messages_written += 1;
|
||||
for (l = worker->write_pending_flushes; l != NULL; l = ll)
|
||||
@ -936,81 +1157,114 @@ write_message (GDBusWorker *worker,
|
||||
|
||||
if (f->number_to_wait_for == worker->write_num_messages_written)
|
||||
{
|
||||
g_mutex_lock (f->mutex);
|
||||
g_cond_signal (f->cond);
|
||||
g_mutex_unlock (f->mutex);
|
||||
flushers = g_list_append (flushers, f);
|
||||
worker->write_pending_flushes = g_list_delete_link (worker->write_pending_flushes, l);
|
||||
}
|
||||
}
|
||||
g_mutex_unlock (worker->write_lock);
|
||||
|
||||
if (G_UNLIKELY (_g_dbus_debug_message ()))
|
||||
if (flushers != NULL)
|
||||
{
|
||||
gchar *s;
|
||||
_g_dbus_debug_print_lock ();
|
||||
g_print ("========================================================================\n"
|
||||
"GDBus-debug:Message:\n"
|
||||
" >>>> SENT D-Bus message (%" G_GSIZE_FORMAT " bytes)\n",
|
||||
data->blob_size);
|
||||
s = g_dbus_message_print (data->message, 2);
|
||||
g_print ("%s", s);
|
||||
g_free (s);
|
||||
if (G_UNLIKELY (_g_dbus_debug_payload ()))
|
||||
{
|
||||
s = _g_dbus_hexdump (data->blob, data->blob_size, 2);
|
||||
g_print ("%s\n", s);
|
||||
g_free (s);
|
||||
}
|
||||
_g_dbus_debug_print_unlock ();
|
||||
FlushAsyncData *data;
|
||||
data = g_new0 (FlushAsyncData, 1);
|
||||
data->worker = _g_dbus_worker_ref (worker);
|
||||
data->flushers = flushers;
|
||||
/* flush the stream before writing the next message */
|
||||
g_output_stream_flush_async (g_io_stream_get_output_stream (worker->stream),
|
||||
G_PRIORITY_DEFAULT,
|
||||
worker->cancellable,
|
||||
ostream_flush_cb,
|
||||
data);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* kick off the next write! */
|
||||
maybe_write_next_message (worker);
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static void
|
||||
write_message_cb (GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
MessageToWriteData *data = user_data;
|
||||
GError *error;
|
||||
|
||||
g_mutex_lock (data->worker->write_lock);
|
||||
data->worker->num_writes_pending -= 1;
|
||||
g_mutex_unlock (data->worker->write_lock);
|
||||
|
||||
error = NULL;
|
||||
if (!write_message_finish (res, &error))
|
||||
{
|
||||
/* TODO: handle */
|
||||
_g_dbus_worker_emit_disconnected (data->worker, TRUE, error);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
/* this function will also kick of the next write (it might need to
|
||||
* flush so writing the next message might happen much later
|
||||
* e.g. async)
|
||||
*/
|
||||
message_written (data->worker, data);
|
||||
|
||||
message_to_write_data_free (data);
|
||||
}
|
||||
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static gboolean
|
||||
write_message_in_idle_cb (gpointer user_data)
|
||||
static void
|
||||
maybe_write_next_message (GDBusWorker *worker)
|
||||
{
|
||||
GDBusWorker *worker = user_data;
|
||||
gboolean more_writes_are_pending;
|
||||
MessageToWriteData *data;
|
||||
gboolean message_was_dropped;
|
||||
GError *error;
|
||||
|
||||
write_next:
|
||||
|
||||
g_mutex_lock (worker->write_lock);
|
||||
data = g_queue_pop_head (worker->write_queue);
|
||||
g_assert (data != NULL);
|
||||
more_writes_are_pending = (g_queue_get_length (worker->write_queue) > 0);
|
||||
worker->write_is_pending = more_writes_are_pending;
|
||||
if (data != NULL)
|
||||
worker->num_writes_pending += 1;
|
||||
g_mutex_unlock (worker->write_lock);
|
||||
|
||||
/* Note that write_lock is only used for protecting the @write_queue
|
||||
* and @write_is_pending fields of the GDBusWorker struct ... which we
|
||||
* and @num_writes_pending fields of the GDBusWorker struct ... which we
|
||||
* need to modify from arbitrary threads in _g_dbus_worker_send_message().
|
||||
*
|
||||
* Therefore, it's fine to drop it here when calling back into user
|
||||
* code and then writing the message out onto the GIOStream since this
|
||||
* function only runs on the worker thread.
|
||||
*/
|
||||
message_was_dropped = _g_dbus_worker_emit_message_about_to_be_sent (worker, data->message);
|
||||
if (G_LIKELY (!message_was_dropped))
|
||||
if (data != NULL)
|
||||
{
|
||||
error = NULL;
|
||||
if (!write_message (worker,
|
||||
data,
|
||||
&error))
|
||||
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))
|
||||
{
|
||||
/* TODO: handle */
|
||||
_g_dbus_worker_emit_disconnected (worker, TRUE, error);
|
||||
g_error_free (error);
|
||||
g_mutex_lock (worker->write_lock);
|
||||
worker->num_writes_pending -= 1;
|
||||
g_mutex_unlock (worker->write_lock);
|
||||
message_to_write_data_free (data);
|
||||
goto write_next;
|
||||
}
|
||||
else
|
||||
{
|
||||
write_message_async (worker,
|
||||
data,
|
||||
write_message_cb,
|
||||
data);
|
||||
}
|
||||
}
|
||||
message_to_write_data_free (data);
|
||||
}
|
||||
|
||||
return more_writes_are_pending;
|
||||
/* called in private thread shared by all GDBusConnection instances (without write-lock held) */
|
||||
static gboolean
|
||||
write_message_in_idle_cb (gpointer user_data)
|
||||
{
|
||||
GDBusWorker *worker = user_data;
|
||||
if (worker->num_writes_pending == 0)
|
||||
maybe_write_next_message (worker);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
@ -1029,18 +1283,16 @@ _g_dbus_worker_send_message (GDBusWorker *worker,
|
||||
g_return_if_fail (blob_len > 16);
|
||||
|
||||
data = g_new0 (MessageToWriteData, 1);
|
||||
data->worker = _g_dbus_worker_ref (worker);
|
||||
data->message = g_object_ref (message);
|
||||
data->blob = blob; /* steal! */
|
||||
data->blob_size = blob_len;
|
||||
|
||||
g_mutex_lock (worker->write_lock);
|
||||
g_queue_push_tail (worker->write_queue, data);
|
||||
if (!worker->write_is_pending)
|
||||
if (worker->num_writes_pending == 0)
|
||||
{
|
||||
GSource *idle_source;
|
||||
|
||||
worker->write_is_pending = TRUE;
|
||||
|
||||
idle_source = g_idle_source_new ();
|
||||
g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
|
||||
g_source_set_callback (idle_source,
|
||||
@ -1145,6 +1397,7 @@ _g_dbus_worker_flush_sync (GDBusWorker *worker,
|
||||
FlushData *data;
|
||||
|
||||
data = NULL;
|
||||
ret = TRUE;
|
||||
|
||||
/* if the queue is empty, there's nothing to wait for */
|
||||
g_mutex_lock (worker->write_lock);
|
||||
@ -1164,29 +1417,32 @@ _g_dbus_worker_flush_sync (GDBusWorker *worker,
|
||||
g_cond_wait (data->cond, data->mutex);
|
||||
g_mutex_unlock (data->mutex);
|
||||
|
||||
/* note:the element is removed from worker->write_pending_flushes in write_message() */
|
||||
/* note:the element is removed from worker->write_pending_flushes in flush_cb() above */
|
||||
g_cond_free (data->cond);
|
||||
g_mutex_free (data->mutex);
|
||||
if (data->error != NULL)
|
||||
{
|
||||
ret = FALSE;
|
||||
g_propagate_error (error, data->error);
|
||||
}
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
ret = g_output_stream_flush (g_io_stream_get_output_stream (worker->stream),
|
||||
cancellable,
|
||||
error);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
#define G_DBUS_DEBUG_AUTHENTICATION (1<<0)
|
||||
#define G_DBUS_DEBUG_MESSAGE (1<<1)
|
||||
#define G_DBUS_DEBUG_PAYLOAD (1<<2)
|
||||
#define G_DBUS_DEBUG_CALL (1<<3)
|
||||
#define G_DBUS_DEBUG_SIGNAL (1<<4)
|
||||
#define G_DBUS_DEBUG_INCOMING (1<<5)
|
||||
#define G_DBUS_DEBUG_RETURN (1<<6)
|
||||
#define G_DBUS_DEBUG_EMISSION (1<<7)
|
||||
#define G_DBUS_DEBUG_ADDRESS (1<<8)
|
||||
#define G_DBUS_DEBUG_TRANSPORT (1<<1)
|
||||
#define G_DBUS_DEBUG_MESSAGE (1<<2)
|
||||
#define G_DBUS_DEBUG_PAYLOAD (1<<3)
|
||||
#define G_DBUS_DEBUG_CALL (1<<4)
|
||||
#define G_DBUS_DEBUG_SIGNAL (1<<5)
|
||||
#define G_DBUS_DEBUG_INCOMING (1<<6)
|
||||
#define G_DBUS_DEBUG_RETURN (1<<7)
|
||||
#define G_DBUS_DEBUG_EMISSION (1<<8)
|
||||
#define G_DBUS_DEBUG_ADDRESS (1<<9)
|
||||
|
||||
static gint _gdbus_debug_flags = 0;
|
||||
|
||||
@ -1197,6 +1453,13 @@ _g_dbus_debug_authentication (void)
|
||||
return (_gdbus_debug_flags & G_DBUS_DEBUG_AUTHENTICATION) != 0;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_g_dbus_debug_transport (void)
|
||||
{
|
||||
_g_dbus_initialize ();
|
||||
return (_gdbus_debug_flags & G_DBUS_DEBUG_TRANSPORT) != 0;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_g_dbus_debug_message (void)
|
||||
{
|
||||
@ -1292,6 +1555,7 @@ _g_dbus_initialize (void)
|
||||
{
|
||||
const GDebugKey keys[] = {
|
||||
{ "authentication", G_DBUS_DEBUG_AUTHENTICATION },
|
||||
{ "transport", G_DBUS_DEBUG_TRANSPORT },
|
||||
{ "message", G_DBUS_DEBUG_MESSAGE },
|
||||
{ "payload", G_DBUS_DEBUG_PAYLOAD },
|
||||
{ "call", G_DBUS_DEBUG_CALL },
|
||||
@ -1448,3 +1712,76 @@ _g_dbus_enum_to_string (GType enum_type, gint value)
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
static void
|
||||
write_message_print_transport_debug (gssize bytes_written,
|
||||
MessageToWriteData *data)
|
||||
{
|
||||
if (G_LIKELY (!_g_dbus_debug_transport ()))
|
||||
goto out;
|
||||
|
||||
_g_dbus_debug_print_lock ();
|
||||
g_print ("========================================================================\n"
|
||||
"GDBus-debug:Transport:\n"
|
||||
" >>>> WROTE %" G_GSIZE_FORMAT " bytes of message with serial %d and\n"
|
||||
" size %" G_GSIZE_FORMAT " from offset %" G_GSIZE_FORMAT " on a %s\n",
|
||||
bytes_written,
|
||||
g_dbus_message_get_serial (data->message),
|
||||
data->blob_size,
|
||||
data->total_written,
|
||||
g_type_name (G_TYPE_FROM_INSTANCE (g_io_stream_get_output_stream (data->worker->stream))));
|
||||
_g_dbus_debug_print_unlock ();
|
||||
out:
|
||||
;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
static void
|
||||
read_message_print_transport_debug (gssize bytes_read,
|
||||
GDBusWorker *worker)
|
||||
{
|
||||
gsize size;
|
||||
gint32 serial;
|
||||
gint32 message_length;
|
||||
|
||||
if (G_LIKELY (!_g_dbus_debug_transport ()))
|
||||
goto out;
|
||||
|
||||
size = bytes_read + worker->read_buffer_cur_size;
|
||||
serial = 0;
|
||||
message_length = 0;
|
||||
if (size >= 16)
|
||||
message_length = g_dbus_message_bytes_needed ((guchar *) worker->read_buffer, size, NULL);
|
||||
if (size >= 1)
|
||||
{
|
||||
switch (worker->read_buffer[0])
|
||||
{
|
||||
case 'l':
|
||||
if (size >= 12)
|
||||
serial = GUINT32_FROM_LE (((guint32 *) worker->read_buffer)[2]);
|
||||
break;
|
||||
case 'B':
|
||||
if (size >= 12)
|
||||
serial = GUINT32_FROM_BE (((guint32 *) worker->read_buffer)[2]);
|
||||
break;
|
||||
default:
|
||||
/* an error will be set elsewhere if this happens */
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
_g_dbus_debug_print_lock ();
|
||||
g_print ("========================================================================\n"
|
||||
"GDBus-debug:Transport:\n"
|
||||
" <<<< READ %" G_GSIZE_FORMAT " bytes of message with serial %d and\n"
|
||||
" size %d to offset %" G_GSIZE_FORMAT " from a %s\n",
|
||||
bytes_read,
|
||||
serial,
|
||||
message_length,
|
||||
worker->read_buffer_cur_size,
|
||||
g_type_name (G_TYPE_FROM_INSTANCE (g_io_stream_get_input_stream (worker->stream))));
|
||||
_g_dbus_debug_print_unlock ();
|
||||
out:
|
||||
;
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ gboolean _g_dbus_worker_flush_sync (GDBusWorker *worker,
|
||||
|
||||
void _g_dbus_initialize (void);
|
||||
gboolean _g_dbus_debug_authentication (void);
|
||||
gboolean _g_dbus_debug_transport (void);
|
||||
gboolean _g_dbus_debug_message (void);
|
||||
gboolean _g_dbus_debug_payload (void);
|
||||
gboolean _g_dbus_debug_call (void);
|
||||
|
@ -917,6 +917,85 @@ test_connection_basic (void)
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
/* Message size > 20MiB ... should be enough to make sure the message
|
||||
* is fragmented when shoved across any transport
|
||||
*/
|
||||
#define LARGE_MESSAGE_STRING_LENGTH (20*1024*1024)
|
||||
|
||||
static void
|
||||
large_message_on_name_appeared (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
const gchar *name_owner,
|
||||
gpointer user_data)
|
||||
{
|
||||
GError *error;
|
||||
gchar *request;
|
||||
const gchar *reply;
|
||||
GVariant *result;
|
||||
guint n;
|
||||
|
||||
request = g_new (gchar, LARGE_MESSAGE_STRING_LENGTH + 1);
|
||||
for (n = 0; n < LARGE_MESSAGE_STRING_LENGTH; n++)
|
||||
request[n] = '0' + (n%10);
|
||||
request[n] = '\0';
|
||||
|
||||
error = NULL;
|
||||
result = g_dbus_connection_call_sync (connection,
|
||||
"com.example.TestService", /* bus name */
|
||||
"/com/example/TestObject", /* object path */
|
||||
"com.example.Frob", /* interface name */
|
||||
"HelloWorld", /* method name */
|
||||
g_variant_new ("(s)", request), /* parameters */
|
||||
G_VARIANT_TYPE ("(s)"), /* return type */
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (result != NULL);
|
||||
g_variant_get (result, "(&s)", &reply);
|
||||
g_assert_cmpint (strlen (reply), >, LARGE_MESSAGE_STRING_LENGTH);
|
||||
g_assert (g_str_has_prefix (reply, "You greeted me with '01234567890123456789012"));
|
||||
g_assert (g_str_has_suffix (reply, "6789'. Thanks!"));
|
||||
g_variant_unref (result);
|
||||
|
||||
g_free (request);
|
||||
|
||||
g_main_loop_quit (loop);
|
||||
}
|
||||
|
||||
static void
|
||||
large_message_on_name_vanished (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
test_connection_large_message (void)
|
||||
{
|
||||
guint watcher_id;
|
||||
|
||||
session_bus_up ();
|
||||
|
||||
/* this is safe; testserver will exit once the bus goes away */
|
||||
g_assert (g_spawn_command_line_async (SRCDIR "/gdbus-testserver.py", NULL));
|
||||
|
||||
watcher_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
||||
"com.example.TestService",
|
||||
G_BUS_NAME_WATCHER_FLAGS_NONE,
|
||||
large_message_on_name_appeared,
|
||||
large_message_on_name_vanished,
|
||||
NULL, /* user_data */
|
||||
NULL); /* GDestroyNotify */
|
||||
g_main_loop_run (loop);
|
||||
g_bus_unwatch_name (watcher_id);
|
||||
|
||||
session_bus_down ();
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
@ -939,5 +1018,6 @@ main (int argc,
|
||||
g_test_add_func ("/gdbus/connection/signals", test_connection_signals);
|
||||
g_test_add_func ("/gdbus/connection/filter", test_connection_filter);
|
||||
g_test_add_func ("/gdbus/connection/flush", test_connection_flush);
|
||||
g_test_add_func ("/gdbus/connection/large_message", test_connection_large_message);
|
||||
return g_test_run();
|
||||
}
|
||||
|
@ -1250,30 +1250,42 @@ test_credentials (void)
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
|
||||
#if 0 /* def G_OS_UNIX disabled while it fails */
|
||||
#ifdef G_OS_UNIX
|
||||
|
||||
/* Chosen to be big enough to overflow the socket buffer */
|
||||
#define OVERFLOW_NUM_SIGNALS 5000
|
||||
#define OVERFLOW_TIMEOUT_SEC 10
|
||||
|
||||
static gboolean
|
||||
signal_count_cb (GDBusConnection *connection,
|
||||
GDBusMessage *message,
|
||||
gboolean incoming,
|
||||
gpointer user_data)
|
||||
overflow_filter_func (GDBusConnection *connection,
|
||||
GDBusMessage *message,
|
||||
gboolean incoming,
|
||||
gpointer user_data)
|
||||
{
|
||||
volatile int *p = user_data;
|
||||
(*p)++;
|
||||
return TRUE;
|
||||
volatile gint *counter = user_data;
|
||||
*counter += 1;
|
||||
return FALSE; /* don't drop the message */
|
||||
}
|
||||
|
||||
static gboolean
|
||||
overflow_on_500ms_later_func (gpointer user_data)
|
||||
{
|
||||
g_main_loop_quit (loop);
|
||||
return FALSE; /* don't keep the idle */
|
||||
}
|
||||
|
||||
static void
|
||||
test_overflow (void)
|
||||
{
|
||||
gint sv[2], i;
|
||||
gint sv[2];
|
||||
gint n;
|
||||
GSocket *socket;
|
||||
GSocketConnection *socket_connection;
|
||||
GDBusConnection *producer, *consumer;
|
||||
GError *error;
|
||||
gchar *guid;
|
||||
pid_t child;
|
||||
GTimer *timer;
|
||||
volatile int counter = 0;
|
||||
volatile gint n_messages_received;
|
||||
volatile gint n_messages_sent;
|
||||
|
||||
g_assert_cmpint (socketpair (AF_UNIX, SOCK_STREAM, 0, sv), ==, 0);
|
||||
|
||||
@ -1287,14 +1299,16 @@ test_overflow (void)
|
||||
NULL, /* guid */
|
||||
G_DBUS_CONNECTION_FLAGS_NONE,
|
||||
NULL, /* GDBusAuthObserver */
|
||||
NULL,
|
||||
NULL, /* GCancellable */
|
||||
&error);
|
||||
g_dbus_connection_set_exit_on_close (producer, TRUE);
|
||||
g_assert_no_error (error);
|
||||
g_object_unref (socket_connection);
|
||||
n_messages_sent = 0;
|
||||
g_dbus_connection_add_filter (producer, overflow_filter_func, (gpointer) &n_messages_sent, NULL);
|
||||
|
||||
/* send enough data that we get an EAGAIN */
|
||||
for (i = 0; i < 1000; i++)
|
||||
for (n = 0; n < OVERFLOW_NUM_SIGNALS; n++)
|
||||
{
|
||||
error = NULL;
|
||||
g_dbus_connection_emit_signal (producer,
|
||||
@ -1304,41 +1318,46 @@ test_overflow (void)
|
||||
"Member",
|
||||
g_variant_new ("(s)", "a string"),
|
||||
&error);
|
||||
/* run the main event loop - otherwise GDBusConnection::closed won't be fired */
|
||||
g_main_context_iteration (NULL, FALSE);
|
||||
g_assert_no_error (error);
|
||||
static gint count = 0;
|
||||
g_print ("%d ", count++);
|
||||
}
|
||||
|
||||
/* sleep for 0.5 sec (to allow the GDBus IO thread to fill up the
|
||||
* kernel buffers) and verify that n_messages_sent <
|
||||
* OVERFLOW_NUM_SIGNALS
|
||||
*
|
||||
* This is to verify that not all the submitted messages have been
|
||||
* sent to the underlying transport.
|
||||
*/
|
||||
g_timeout_add (500, overflow_on_500ms_later_func, NULL);
|
||||
g_main_loop_run (loop);
|
||||
g_assert_cmpint (n_messages_sent, <, OVERFLOW_NUM_SIGNALS);
|
||||
|
||||
/* now suck it all out as a client, and add it up */
|
||||
socket = g_socket_new_from_fd (sv[1], &error);
|
||||
g_assert_no_error (error);
|
||||
socket_connection = g_socket_connection_factory_create_connection (socket);
|
||||
g_assert (socket_connection != NULL);
|
||||
g_object_unref (socket);
|
||||
guid = g_dbus_generate_guid ();
|
||||
consumer = g_dbus_connection_new_sync (G_IO_STREAM (socket_connection),
|
||||
guid,
|
||||
NULL, /* guid */
|
||||
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
|
||||
NULL, /* GDBusAuthObserver */
|
||||
NULL,
|
||||
NULL, /* GCancellable */
|
||||
&error);
|
||||
g_dbus_connection_add_filter (consumer, signal_count_cb, &counter, NULL);
|
||||
g_dbus_connection_start_message_processing (consumer);
|
||||
|
||||
g_free (guid);
|
||||
g_assert_no_error (error);
|
||||
g_object_unref (socket_connection);
|
||||
n_messages_received = 0;
|
||||
g_dbus_connection_add_filter (consumer, overflow_filter_func, (gpointer) &n_messages_received, NULL);
|
||||
g_dbus_connection_start_message_processing (consumer);
|
||||
|
||||
timer = g_timer_new ();
|
||||
g_timer_start (timer);
|
||||
|
||||
while (counter < 1000 &&
|
||||
g_timer_elapsed (timer, NULL) < 5.0)
|
||||
while (n_messages_received < OVERFLOW_NUM_SIGNALS && g_timer_elapsed (timer, NULL) < OVERFLOW_TIMEOUT_SEC)
|
||||
g_main_context_iteration (NULL, FALSE);
|
||||
|
||||
g_assert (counter == 1000);
|
||||
g_assert_cmpint (n_messages_sent, ==, OVERFLOW_NUM_SIGNALS);
|
||||
g_assert_cmpint (n_messages_received, ==, OVERFLOW_NUM_SIGNALS);
|
||||
|
||||
g_timer_destroy (timer);
|
||||
g_object_unref (consumer);
|
||||
@ -1348,6 +1367,7 @@ test_overflow (void)
|
||||
static void
|
||||
test_overflow (void)
|
||||
{
|
||||
/* TODO: test this with e.g. GWin32InputStream/GWin32OutputStream */
|
||||
}
|
||||
#endif
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user