goutputstream: Check individual close operations after splice

After a splice operation is finished, it attempts to 1) close input/output
streams, as per the given flags, and 2) return the operation result (maybe
an error, too).

However, if the operation gets cancelled early and the streams indirectly
closed, the splice operation will try to close both descriptors and return
on the task when both are already closed. The catch here is that getting the
streams closed under its feet is possible, so the completion callback would
find both streams closed after returning on the first close operation and
return the error, but then the second operation could be able to trigger
a second error which would be returned as well.

What happens here is up to further race conditions, if the task didn't
return yet, the returned error will be simply replaced (but the old one not
freed...), if it did already return, it'll result in:

GLib-GIO-FATAL-CRITICAL: g_task_return_error: assertion '!task->ever_returned' failed

Fix this by flagging the close_async() callbacks, and checking that both
close operations did return, instead of checking that both streams are
closed by who knows.

This error triggers a semi-frequent CI failure in tracker, see the summary at
https://gitlab.gnome.org/GNOME/tracker/-/issues/240
This commit is contained in:
Carlos Garnacho 2020-08-24 12:00:44 +02:00
parent 9674d14175
commit c033450f93

View File

@ -2643,6 +2643,8 @@ g_output_stream_real_writev_finish (GOutputStream *stream,
typedef struct {
GInputStream *source;
GOutputStreamSpliceFlags flags;
guint istream_closed : 1;
guint ostream_closed : 1;
gssize n_read;
gssize n_written;
gsize bytes_copied;
@ -2665,11 +2667,11 @@ real_splice_async_complete_cb (GTask *task)
SpliceData *op = g_task_get_task_data (task);
if (op->flags & G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE &&
!g_input_stream_is_closed (op->source))
!op->istream_closed)
return;
if (op->flags & G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET &&
!g_output_stream_is_closed (g_task_get_source_object (task)))
!op->ostream_closed)
return;
if (op->error != NULL)
@ -2691,8 +2693,10 @@ real_splice_async_close_input_cb (GObject *source,
gpointer user_data)
{
GTask *task = user_data;
SpliceData *op = g_task_get_task_data (task);
g_input_stream_close_finish (G_INPUT_STREAM (source), res, NULL);
op->istream_closed = TRUE;
real_splice_async_complete_cb (task);
}
@ -2707,6 +2711,7 @@ real_splice_async_close_output_cb (GObject *source,
GError **error = (op->error == NULL) ? &op->error : NULL;
g_output_stream_internal_close_finish (G_OUTPUT_STREAM (source), res, error);
op->ostream_closed = TRUE;
real_splice_async_complete_cb (task);
}