diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2014-02-25 11:28:04 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2014-02-25 11:57:52 +0800 |
commit | e15c81b8ee34fff9aa1da9085587e568e59da854 (patch) | |
tree | 83223b929f88524971f2764693bfcf3a88185877 | |
parent | 2b01f18ecc396387004ba1e8a69430637b47f381 (diff) | |
download | gsoc2013-evolution-e15c81b8ee34fff9aa1da9085587e568e59da854.tar gsoc2013-evolution-e15c81b8ee34fff9aa1da9085587e568e59da854.tar.gz gsoc2013-evolution-e15c81b8ee34fff9aa1da9085587e568e59da854.tar.bz2 gsoc2013-evolution-e15c81b8ee34fff9aa1da9085587e568e59da854.tar.lz gsoc2013-evolution-e15c81b8ee34fff9aa1da9085587e568e59da854.tar.xz gsoc2013-evolution-e15c81b8ee34fff9aa1da9085587e568e59da854.tar.zst gsoc2013-evolution-e15c81b8ee34fff9aa1da9085587e568e59da854.zip |
Bug 724909 - Highlight module hangs on large attachments
The previous code was writing the entire MIME part content to the
highlight utility's stdin pipe before reading the converted result.
With enough content, this caused the write operation to get stuck.
What's worse is this all happens synchronously in the UI thread.
Not sure exactly what was going on, but my hunch proved correct that
we need to simultaneously write to the stdin pipe and read from the
stdout pipe to avoid the deadlock.
Still not happy about this blocking the UI, but that would require
some major refactoring in libevolution-mail-formatter.
-rw-r--r-- | modules/text-highlight/e-mail-formatter-text-highlight.c | 209 |
1 files changed, 182 insertions, 27 deletions
diff --git a/modules/text-highlight/e-mail-formatter-text-highlight.c b/modules/text-highlight/e-mail-formatter-text-highlight.c index d43c01e576..cbf56ad817 100644 --- a/modules/text-highlight/e-mail-formatter-text-highlight.c +++ b/modules/text-highlight/e-mail-formatter-text-highlight.c @@ -22,6 +22,10 @@ #include "e-mail-formatter-text-highlight.h" #include "languages.h" +/* FIXME Delete these once we can use GSubprocess. */ +#include <gio/gunixinputstream.h> +#include <gio/gunixoutputstream.h> + #include <em-format/e-mail-formatter-extension.h> #include <em-format/e-mail-formatter.h> #include <em-format/e-mail-part-utils.h> @@ -40,6 +44,14 @@ typedef EMailFormatterExtensionClass EMailFormatterTextHighlightClass; typedef EExtension EMailFormatterTextHighlightLoader; typedef EExtensionClass EMailFormatterTextHighlightLoaderClass; +typedef struct _TextHighlightClosure TextHighlightClosure; + +struct _TextHighlightClosure { + GMainLoop *main_loop; + GError *input_error; + GError *output_error; +}; + GType e_mail_formatter_text_highlight_get_type (void); G_DEFINE_DYNAMIC_TYPE ( @@ -109,6 +121,155 @@ get_syntax (EMailPart *part, return syntax; } +static void +text_highlight_input_spliced (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + TextHighlightClosure *closure = user_data; + + g_output_stream_splice_finish ( + G_OUTPUT_STREAM (source_object), + result, &closure->input_error); +} + +static void +text_highlight_output_spliced (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + TextHighlightClosure *closure = user_data; + + g_output_stream_splice_finish ( + G_OUTPUT_STREAM (source_object), + result, &closure->output_error); + + g_main_loop_quit (closure->main_loop); +} + +static gboolean +text_highlight_feed_data (CamelStream *stream, + CamelDataWrapper *data_wrapper, + gint pipe_stdin, + gint pipe_stdout, + GCancellable *cancellable, + GError **error) +{ + TextHighlightClosure closure; + GInputStream *input_stream = NULL; + GOutputStream *output_stream = NULL; + GInputStream *stdout_stream = NULL; + GOutputStream *stdin_stream = NULL; + GMainContext *main_context; + gchar *utf8_data; + gconstpointer data; + gssize bytes_written; + gsize size; + gboolean success; + + /* We need to dump CamelDataWrapper to a buffer, force the content + * to valid UTF-8, feed the UTF-8 data to the 'highlight' process, + * read the converted data back and feed it to the CamelStream. */ + + /* FIXME Use GSubprocess once we can require GLib 2.40. */ + + output_stream = g_memory_output_stream_new_resizable (); + + success = camel_data_wrapper_decode_to_output_stream_sync ( + data_wrapper, output_stream, cancellable, error); + + if (!success) + goto exit; + + main_context = g_main_context_new (); + + closure.main_loop = g_main_loop_new (main_context, FALSE); + closure.input_error = NULL; + closure.output_error = NULL; + + g_main_context_push_thread_default (main_context); + + data = g_memory_output_stream_get_data ( + G_MEMORY_OUTPUT_STREAM (output_stream)); + size = g_memory_output_stream_get_data_size ( + G_MEMORY_OUTPUT_STREAM (output_stream)); + + /* FIXME Write a GConverter that does this so we can decode + * straight to the stdin pipe and skip all this extra + * buffering. */ + utf8_data = e_util_utf8_data_make_valid ((gchar *) data, size); + + g_clear_object (&output_stream); + + /* Takes ownership of the UTF-8 string. */ + input_stream = g_memory_input_stream_new_from_data ( + utf8_data, -1, (GDestroyNotify) g_free); + + output_stream = g_memory_output_stream_new_resizable (); + + stdin_stream = g_unix_output_stream_new (pipe_stdin, TRUE); + stdout_stream = g_unix_input_stream_new (pipe_stdout, TRUE); + + /* Splice the streams together. */ + + /* GCancellable is only supposed to be used in one operation + * at a time. Skip it here and use it for reading converted + * data, since that operation terminates the main loop. */ + g_output_stream_splice_async ( + stdin_stream, input_stream, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + G_PRIORITY_DEFAULT, NULL, + text_highlight_input_spliced, + &closure); + + g_output_stream_splice_async ( + output_stream, stdout_stream, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + G_PRIORITY_DEFAULT, cancellable, + text_highlight_output_spliced, + &closure); + + g_main_loop_run (closure.main_loop); + + g_main_context_pop_thread_default (main_context); + + g_main_context_unref (main_context); + g_main_loop_unref (closure.main_loop); + + g_clear_object (&input_stream); + g_clear_object (&stdin_stream); + g_clear_object (&stdout_stream); + + if (closure.input_error != NULL) { + g_propagate_error (error, closure.input_error); + g_clear_error (&closure.output_error); + success = FALSE; + goto exit; + } + + if (closure.output_error != NULL) { + g_propagate_error (error, closure.output_error); + success = FALSE; + goto exit; + } + + data = g_memory_output_stream_get_data ( + G_MEMORY_OUTPUT_STREAM (output_stream)); + size = g_memory_output_stream_get_data_size ( + G_MEMORY_OUTPUT_STREAM (output_stream)); + + bytes_written = camel_stream_write ( + stream, data, size, cancellable, error); + success = (bytes_written >= 0); + +exit: + g_clear_object (&output_stream); + + return success; +} + static gboolean emfe_text_highlight_format (EMailFormatterExtension *extension, EMailFormatter *formatter, @@ -212,36 +373,30 @@ emfe_text_highlight_format (EMailFormatterExtension *extension, &pid, &pipe_stdin, &pipe_stdout, NULL, NULL); if (success) { - CamelStream *read; - CamelStream *write; - CamelStream *utf8; - GByteArray *ba; - gchar *tmp; - - write = camel_stream_fs_new_with_fd (pipe_stdin); - read = camel_stream_fs_new_with_fd (pipe_stdout); - - /* Decode the content of mime part to the 'utf8' stream */ - utf8 = camel_stream_mem_new (); - camel_data_wrapper_decode_to_stream_sync ( - dw, utf8, cancellable, NULL); - - /* Convert the binary data do someting displayable */ - ba = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (utf8)); - tmp = e_util_utf8_data_make_valid ((gchar *) ba->data, ba->len); - - /* Send the sanitized data to the highlighter */ - camel_stream_write_string (write, tmp, cancellable, NULL); - g_free (tmp); - g_object_unref (utf8); - g_object_unref (write); + GError *local_error = NULL; + + success = text_highlight_feed_data ( + stream, dw, + pipe_stdin, pipe_stdout, + cancellable, &local_error); + + if (g_error_matches ( + local_error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + /* Do nothing. */ + + } else if (local_error != NULL) { + g_warning ( + "%s: %s", G_STRFUNC, + local_error->message); + } + + g_clear_error (&local_error); g_spawn_close_pid (pid); + } - g_seekable_seek (G_SEEKABLE (read), 0, G_SEEK_SET, cancellable, NULL); - camel_stream_write_to_stream (read, stream, cancellable, NULL); - g_object_unref (read); - } else { + if (!success) { /* We can't call e_mail_formatter_format_as on text/plain, * because text-highlight is registered as an handler for * text/plain, so we would end up in an endless recursion. |