[gnome-software] Cancel plugin jobs if they take too much time
- From: Richard Hughes <rhughes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software] Cancel plugin jobs if they take too much time
- Date: Thu, 29 Jun 2017 14:51:56 +0000 (UTC)
commit fa7216c1284d957acbf266f879a99ffcff06abee
Author: Richard Hughes <richard hughsie com>
Date: Wed Jun 28 17:50:42 2017 +0100
Cancel plugin jobs if they take too much time
Create an event and show a message in the shell to make the plugin look bad.
Fixes: https://bugzilla.gnome.org/show_bug.cgi?id=784251
lib/gs-plugin-job-private.h | 1 +
lib/gs-plugin-job.c | 29 +++++++++
lib/gs-plugin-job.h | 2 +
lib/gs-plugin-loader.c | 120 ++++++++++++++++++++++++++++++++++++---
lib/gs-plugin-types.h | 2 +
lib/gs-plugin.c | 2 +
lib/gs-utils.c | 6 +-
plugins/dummy/gs-plugin-dummy.c | 70 ++++++++++++++++++++++-
plugins/dummy/gs-self-test.c | 32 ++++++++++
src/gs-search-page.c | 1 +
src/gs-shell.c | 9 +++-
11 files changed, 261 insertions(+), 13 deletions(-)
---
diff --git a/lib/gs-plugin-job-private.h b/lib/gs-plugin-job-private.h
index d1dd62d..447e21d 100644
--- a/lib/gs-plugin-job-private.h
+++ b/lib/gs-plugin-job-private.h
@@ -39,6 +39,7 @@ void gs_plugin_job_remove_refine_flags (GsPluginJob *self,
GsPluginRefineFlags refine_flags);
GsPluginFailureFlags gs_plugin_job_get_failure_flags (GsPluginJob *self);
guint gs_plugin_job_get_max_results (GsPluginJob *self);
+guint gs_plugin_job_get_timeout (GsPluginJob *self);
guint64 gs_plugin_job_get_age (GsPluginJob *self);
GsAppListSortFunc gs_plugin_job_get_sort_func (GsPluginJob *self);
gpointer gs_plugin_job_get_sort_func_data (GsPluginJob *self);
diff --git a/lib/gs-plugin-job.c b/lib/gs-plugin-job.c
index 4fc13ca..a3ca361 100644
--- a/lib/gs-plugin-job.c
+++ b/lib/gs-plugin-job.c
@@ -33,6 +33,7 @@ struct _GsPluginJob
GsPluginRefreshFlags refresh_flags;
GsPluginFailureFlags failure_flags;
guint max_results;
+ guint timeout;
guint64 age;
GsPlugin *plugin;
GsPluginAction action;
@@ -64,6 +65,7 @@ enum {
PROP_REVIEW,
PROP_MAX_RESULTS,
PROP_PRICE,
+ PROP_TIMEOUT,
PROP_LAST
};
@@ -83,6 +85,8 @@ gs_plugin_job_to_string (GsPluginJob *self)
g_autofree gchar *tmp = gs_plugin_failure_flags_to_string (self->failure_flags);
g_string_append_printf (str, " with failure-flags=%s", tmp);
}
+ if (self->timeout > 0)
+ g_string_append_printf (str, " with timeout=%u", self->timeout);
if (self->age != 0) {
if (self->age == G_MAXUINT) {
g_string_append (str, " with cache age=any");
@@ -218,6 +222,20 @@ gs_plugin_job_get_max_results (GsPluginJob *self)
}
void
+gs_plugin_job_set_timeout (GsPluginJob *self, guint timeout)
+{
+ g_return_if_fail (GS_IS_PLUGIN_JOB (self));
+ self->timeout = timeout;
+}
+
+guint
+gs_plugin_job_get_timeout (GsPluginJob *self)
+{
+ g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
+ return self->timeout;
+}
+
+void
gs_plugin_job_set_age (GsPluginJob *self, guint64 age)
{
g_return_if_fail (GS_IS_PLUGIN_JOB (self));
@@ -454,6 +472,9 @@ gs_plugin_job_get_property (GObject *obj, guint prop_id, GValue *value, GParamSp
case PROP_MAX_RESULTS:
g_value_set_uint (value, self->max_results);
break;
+ case PROP_TIMEOUT:
+ g_value_set_uint (value, self->timeout);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
@@ -505,6 +526,9 @@ gs_plugin_job_set_property (GObject *obj, guint prop_id, const GValue *value, GP
case PROP_MAX_RESULTS:
gs_plugin_job_set_max_results (self, g_value_get_uint (value));
break;
+ case PROP_TIMEOUT:
+ gs_plugin_job_set_timeout (self, g_value_get_uint (value));
+ break;
case PROP_PRICE:
gs_plugin_job_set_price (self, g_value_get_object (value));
break;
@@ -606,6 +630,11 @@ gs_plugin_job_class_init (GsPluginJobClass *klass)
G_PARAM_READWRITE);
g_object_class_install_property (object_class, PROP_MAX_RESULTS, pspec);
+ pspec = g_param_spec_uint ("timeout", NULL, NULL,
+ 0, G_MAXUINT, 60,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+ g_object_class_install_property (object_class, PROP_TIMEOUT, pspec);
+
pspec = g_param_spec_object ("price", NULL, NULL,
GS_TYPE_PRICE,
G_PARAM_READWRITE);
diff --git a/lib/gs-plugin-job.h b/lib/gs-plugin-job.h
index 6d2f050..a7d72af 100644
--- a/lib/gs-plugin-job.h
+++ b/lib/gs-plugin-job.h
@@ -44,6 +44,8 @@ void gs_plugin_job_set_failure_flags (GsPluginJob *self,
GsPluginFailureFlags failure_flags);
void gs_plugin_job_set_max_results (GsPluginJob *self,
guint max_results);
+void gs_plugin_job_set_timeout (GsPluginJob *self,
+ guint timeout);
void gs_plugin_job_set_age (GsPluginJob *self,
guint64 age);
void gs_plugin_job_set_sort_func (GsPluginJob *self,
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 902621b..2ff5a00 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -178,11 +178,16 @@ typedef void (*GsPluginAdoptAppFunc) (GsPlugin *plugin,
/* async helper */
typedef struct {
GsPluginLoader *plugin_loader;
+ GCancellable *cancellable;
+ GCancellable *cancellable_caller;
+ gulong cancellable_id;
const gchar *function_name;
const gchar *function_name_parent;
GPtrArray *catlist;
GsPluginJob *plugin_job;
gboolean anything_ran;
+ guint timeout_id;
+ gboolean timeout_triggered;
gchar **tokens;
} GsPluginLoaderHelper;
@@ -200,9 +205,19 @@ gs_plugin_loader_helper_new (GsPluginLoader *plugin_loader, GsPluginJob *plugin_
static void
gs_plugin_loader_helper_free (GsPluginLoaderHelper *helper)
{
+ if (helper->cancellable_id > 0) {
+ g_cancellable_disconnect (helper->cancellable_caller,
+ helper->cancellable_id);
+ }
g_object_unref (helper->plugin_loader);
+ if (helper->timeout_id != 0)
+ g_source_remove (helper->timeout_id);
if (helper->plugin_job != NULL)
g_object_unref (helper->plugin_job);
+ if (helper->cancellable != NULL)
+ g_object_unref (helper->cancellable);
+ if (helper->cancellable_caller != NULL)
+ g_object_unref (helper->cancellable_caller);
if (helper->catlist != NULL)
g_ptr_array_unref (helper->catlist);
g_strfreev (helper->tokens);
@@ -359,6 +374,8 @@ gs_plugin_loader_is_error_fatal (GsPluginFailureFlags failure_flags,
if (g_error_matches (err, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID))
return TRUE;
}
+ if (g_error_matches (err, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_TIMED_OUT))
+ return TRUE;
return FALSE;
}
@@ -378,15 +395,8 @@ gs_plugin_error_handle_failure (GsPluginLoaderHelper *helper,
return TRUE;
}
- /* abort early to allow main thread to process */
- flags = gs_plugin_job_get_failure_flags (helper->plugin_job);
- if (gs_plugin_loader_is_error_fatal (flags, error_local)) {
- if (error != NULL)
- *error = g_error_copy (error_local);
- return FALSE;
- }
-
/* create event which is handled by the GsShell */
+ flags = gs_plugin_job_get_failure_flags (helper->plugin_job);
if (flags & GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS) {
gs_plugin_loader_create_event_from_error (helper->plugin_loader,
gs_plugin_job_get_action (helper->plugin_job),
@@ -395,6 +405,13 @@ gs_plugin_error_handle_failure (GsPluginLoaderHelper *helper,
error_local);
}
+ /* abort early to allow main thread to process */
+ if (gs_plugin_loader_is_error_fatal (flags, error_local)) {
+ if (error != NULL)
+ *error = g_error_copy (error_local);
+ return FALSE;
+ }
+
/* fallback to console warning */
if ((flags & GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE) == 0) {
if (!g_error_matches (error_local,
@@ -689,7 +706,29 @@ gs_plugin_loader_call_vfunc (GsPluginLoaderHelper *helper,
break;
}
gs_plugin_loader_action_stop (helper->plugin_loader, plugin);
+
+ /* plugin did not return error on cancellable abort */
+ if (ret && g_cancellable_set_error_if_cancelled (cancellable, &error_local)) {
+ g_debug ("plugin did not return error with cancellable set");
+ gs_utils_error_convert_gio (&error_local);
+ ret = FALSE;
+ }
+
+ /* failed */
if (!ret) {
+ /* we returned cancelled, but this was because of a timeout,
+ * so re-create error, throwing the plugin under the bus */
+ if (helper->timeout_triggered &&
+ g_error_matches (error_local, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_CANCELLED)) {
+ g_debug ("converting cancelled to timeout");
+ g_clear_error (&error_local);
+ g_set_error (&error_local,
+ GS_PLUGIN_ERROR,
+ GS_PLUGIN_ERROR_TIMED_OUT,
+ "Timeout was reached as %s took "
+ "too long to return results",
+ gs_plugin_get_name (plugin));
+ }
return gs_plugin_error_handle_failure (helper,
plugin,
error_local,
@@ -2999,6 +3038,7 @@ gs_plugin_loader_process_thread_cb (GTask *task,
gs_app_set_state_recover (gs_plugin_job_get_app (helper->plugin_job));
gs_plugin_loader_pending_apps_remove (plugin_loader, helper);
}
+ gs_utils_error_convert_gio (&error);
g_task_return_error (task, error);
return;
}
@@ -3009,6 +3049,7 @@ gs_plugin_loader_process_thread_cb (GTask *task,
helper->function_name = "gs_plugin_update_app";
if (!gs_plugin_loader_generic_update (plugin_loader, helper,
cancellable, &error)) {
+ gs_utils_error_convert_gio (&error);
g_task_return_error (task, error);
return;
}
@@ -3023,6 +3064,7 @@ gs_plugin_loader_process_thread_cb (GTask *task,
!g_settings_get_boolean (priv->settings, "download-updates")) {
helper->function_name = "gs_plugin_add_updates_pending";
if (!gs_plugin_loader_run_results (helper, cancellable, &error)) {
+ gs_utils_error_convert_gio (&error);
g_task_return_error (task, error);
return;
}
@@ -3106,6 +3148,7 @@ gs_plugin_loader_process_thread_cb (GTask *task,
/* run refine() on each one if required */
if (gs_plugin_job_get_refine_flags (helper->plugin_job) != 0) {
if (!gs_plugin_loader_run_refine (helper, list, cancellable, &error)) {
+ gs_utils_error_convert_gio (&error);
g_task_return_error (task, error);
return;
}
@@ -3145,6 +3188,7 @@ gs_plugin_loader_process_thread_cb (GTask *task,
gs_plugin_job_set_refine_flags (helper->plugin_job,
GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON);
if (!gs_plugin_loader_run_refine (helper, list, cancellable, &error)) {
+ gs_utils_error_convert_gio (&error);
g_task_return_error (task, error);
return;
}
@@ -3248,6 +3292,29 @@ gs_plugin_loader_process_thread_cb (GTask *task,
g_task_return_pointer (task, g_object_ref (list), (GDestroyNotify) g_object_unref);
}
+static gboolean
+gs_plugin_loader_job_timeout_cb (gpointer user_data)
+{
+ GsPluginLoaderHelper *helper = (GsPluginLoaderHelper *) user_data;
+
+ /* call the cancellable */
+ g_debug ("cancelling job as it took too long");
+ if (!g_cancellable_is_cancelled (helper->cancellable))
+ g_cancellable_cancel (helper->cancellable);
+
+ /* failed */
+ helper->timeout_triggered = TRUE;
+ helper->timeout_id = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gs_plugin_loader_cancelled_cb (GCancellable *cancellable, GsPluginLoaderHelper *helper)
+{
+ /* just proxy this forward */
+ g_cancellable_cancel (helper->cancellable);
+}
+
/**
* gs_plugin_loader_job_process_async:
*
@@ -3264,6 +3331,7 @@ gs_plugin_loader_job_process_async (GsPluginLoader *plugin_loader,
GsPluginLoaderHelper *helper;
GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
g_autoptr(GTask) task = NULL;
+ g_autoptr(GCancellable) cancellable_job = g_cancellable_new ();
g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
g_return_if_fail (GS_IS_PLUGIN_JOB (plugin_job));
@@ -3337,7 +3405,7 @@ gs_plugin_loader_job_process_async (GsPluginLoader *plugin_loader,
}
/* check required args */
- task = g_task_new (plugin_loader, cancellable, callback, user_data);
+ task = g_task_new (plugin_loader, cancellable_job, callback, user_data);
switch (action) {
case GS_PLUGIN_ACTION_SEARCH:
case GS_PLUGIN_ACTION_SEARCH_FILES:
@@ -3398,6 +3466,10 @@ gs_plugin_loader_job_process_async (GsPluginLoader *plugin_loader,
g_task_set_task_data (task, helper, (GDestroyNotify) gs_plugin_loader_helper_free);
gs_plugin_loader_job_debug (helper);
+ /* let the task cancel itself */
+ g_task_set_check_cancellable (task, FALSE);
+ g_task_set_return_on_cancel (task, FALSE);
+
/* pre-tokenize search */
if (action == GS_PLUGIN_ACTION_SEARCH) {
const gchar *search = gs_plugin_job_get_search (plugin_job);
@@ -3411,6 +3483,36 @@ gs_plugin_loader_job_process_async (GsPluginLoader *plugin_loader,
}
}
+ /* jobs always have a valid cancellable, so proxy the caller */
+ helper->cancellable = g_object_ref (cancellable_job);
+ if (cancellable != NULL) {
+ helper->cancellable_caller = g_object_ref (cancellable);
+ helper->cancellable_id =
+ g_cancellable_connect (helper->cancellable_caller,
+ G_CALLBACK (gs_plugin_loader_cancelled_cb),
+ helper, NULL);
+ }
+
+ /* set up a hang handler */
+ switch (action) {
+ case GS_PLUGIN_ACTION_GET_CATEGORY_APPS:
+ case GS_PLUGIN_ACTION_GET_FEATURED:
+ case GS_PLUGIN_ACTION_GET_INSTALLED:
+ case GS_PLUGIN_ACTION_GET_POPULAR:
+ case GS_PLUGIN_ACTION_GET_RECENT:
+ case GS_PLUGIN_ACTION_GET_UPDATES:
+ case GS_PLUGIN_ACTION_SEARCH:
+ case GS_PLUGIN_ACTION_SEARCH_FILES:
+ case GS_PLUGIN_ACTION_SEARCH_PROVIDES:
+ helper->timeout_id =
+ g_timeout_add_seconds (gs_plugin_job_get_timeout (plugin_job),
+ gs_plugin_loader_job_timeout_cb,
+ helper);
+ break;
+ default:
+ break;
+ }
+
/* run in a thread */
g_task_run_in_thread (task, gs_plugin_loader_process_thread_cb);
}
diff --git a/lib/gs-plugin-types.h b/lib/gs-plugin-types.h
index 960455e..244b323 100644
--- a/lib/gs-plugin-types.h
+++ b/lib/gs-plugin-types.h
@@ -91,6 +91,7 @@ typedef guint64 GsPluginFlags;
* @GS_PLUGIN_ERROR_DELETE_FAILED: The delete action failed
* @GS_PLUGIN_ERROR_RESTART_REQUIRED: A restart is required
* @GS_PLUGIN_ERROR_AC_POWER_REQUIRED: AC power is required
+ * @GS_PLUGIN_ERROR_TIMED_OUT: The job timed out
*
* The failure error types.
**/
@@ -113,6 +114,7 @@ typedef enum {
GS_PLUGIN_ERROR_DELETE_FAILED,
GS_PLUGIN_ERROR_RESTART_REQUIRED,
GS_PLUGIN_ERROR_AC_POWER_REQUIRED,
+ GS_PLUGIN_ERROR_TIMED_OUT,
/*< private >*/
GS_PLUGIN_ERROR_LAST
} GsPluginError;
diff --git a/lib/gs-plugin.c b/lib/gs-plugin.c
index 6c098f1..2e2bc57 100644
--- a/lib/gs-plugin.c
+++ b/lib/gs-plugin.c
@@ -1664,6 +1664,8 @@ gs_plugin_error_to_string (GsPluginError error)
return "restart-required";
if (error == GS_PLUGIN_ERROR_AC_POWER_REQUIRED)
return "ac-power-required";
+ if (error == GS_PLUGIN_ERROR_TIMED_OUT)
+ return "timed-out";
return NULL;
}
diff --git a/lib/gs-utils.c b/lib/gs-utils.c
index 359e30f..0e051e3 100644
--- a/lib/gs-utils.c
+++ b/lib/gs-utils.c
@@ -1,6 +1,6 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
*
* Licensed under the GNU General Public License Version 2
*
@@ -678,11 +678,13 @@ gs_utils_error_convert_gio (GError **perror)
return FALSE;
switch (error->code) {
case G_IO_ERROR_FAILED:
- case G_IO_ERROR_TIMED_OUT:
case G_IO_ERROR_NOT_FOUND:
case G_IO_ERROR_EXISTS:
error->code = GS_PLUGIN_ERROR_FAILED;
break;
+ case G_IO_ERROR_TIMED_OUT:
+ error->code = GS_PLUGIN_ERROR_TIMED_OUT;
+ break;
case G_IO_ERROR_NOT_SUPPORTED:
error->code = GS_PLUGIN_ERROR_NOT_SUPPORTED;
break;
diff --git a/plugins/dummy/gs-plugin-dummy.c b/plugins/dummy/gs-plugin-dummy.c
index 481e974..6962852 100644
--- a/plugins/dummy/gs-plugin-dummy.c
+++ b/plugins/dummy/gs-plugin-dummy.c
@@ -1,6 +1,6 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
- * Copyright (C) 2011-2013 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2011-2017 Richard Hughes <richard hughsie com>
*
* Licensed under the GNU General Public License Version 2
*
@@ -224,6 +224,64 @@ gs_plugin_url_to_app (GsPlugin *plugin,
return TRUE;
}
+typedef struct {
+ GMainLoop *loop;
+ GCancellable *cancellable;
+ guint timer_id;
+ gulong cancellable_id;
+} GsPluginDummyTimeoutHelper;
+
+static gboolean
+gs_plugin_dummy_timeout_hang_cb (gpointer user_data)
+{
+ GsPluginDummyTimeoutHelper *helper = (GsPluginDummyTimeoutHelper *) user_data;
+ helper->timer_id = 0;
+ g_debug ("timeout hang");
+ g_main_loop_quit (helper->loop);
+ return FALSE;
+}
+
+static void
+gs_plugin_dummy_timeout_cancelled_cb (GCancellable *cancellable, gpointer user_data)
+{
+ GsPluginDummyTimeoutHelper *helper = (GsPluginDummyTimeoutHelper *) user_data;
+ g_debug ("calling cancel");
+ g_main_loop_quit (helper->loop);
+}
+
+static void
+gs_plugin_dummy_timeout_helper_free (GsPluginDummyTimeoutHelper *helper)
+{
+ if (helper->cancellable_id != 0)
+ g_signal_handler_disconnect (helper->cancellable, helper->cancellable_id);
+ if (helper->timer_id != 0)
+ g_source_remove (helper->timer_id);
+ if (helper->cancellable != NULL)
+ g_object_unref (helper->cancellable);
+ g_main_loop_unref (helper->loop);
+ g_free (helper);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsPluginDummyTimeoutHelper, gs_plugin_dummy_timeout_helper_free)
+
+static void
+gs_plugin_dummy_timeout_add (guint timeout_ms, GCancellable *cancellable)
+{
+ g_autoptr(GsPluginDummyTimeoutHelper) helper = g_new0 (GsPluginDummyTimeoutHelper, 1);
+ helper->loop = g_main_loop_new (NULL, TRUE);
+ if (cancellable != NULL) {
+ helper->cancellable = g_object_ref (cancellable);
+ helper->cancellable_id =
+ g_signal_connect (cancellable, "cancelled",
+ G_CALLBACK (gs_plugin_dummy_timeout_cancelled_cb),
+ helper);
+ }
+ helper->timer_id = g_timeout_add (timeout_ms,
+ gs_plugin_dummy_timeout_hang_cb,
+ helper);
+ g_main_loop_run (helper->loop);
+}
+
gboolean
gs_plugin_add_search (GsPlugin *plugin,
gchar **values,
@@ -235,6 +293,16 @@ gs_plugin_add_search (GsPlugin *plugin,
g_autoptr(GsApp) app = NULL;
g_autoptr(AsIcon) ic = NULL;
+ /* hang the plugin for 5 seconds */
+ if (g_strcmp0 (values[0], "hang") == 0) {
+ gs_plugin_dummy_timeout_add (5000, cancellable);
+ if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
+ gs_utils_error_convert_gio (error);
+ return FALSE;
+ }
+ return TRUE;
+ }
+
/* we're very specific */
if (g_strcmp0 (values[0], "chiron") != 0)
return TRUE;
diff --git a/plugins/dummy/gs-self-test.c b/plugins/dummy/gs-self-test.c
index 56dd3a5..3222ca4 100644
--- a/plugins/dummy/gs-self-test.c
+++ b/plugins/dummy/gs-self-test.c
@@ -371,6 +371,35 @@ gs_plugins_dummy_search_func (GsPluginLoader *plugin_loader)
}
static void
+gs_plugins_dummy_hang_func (GsPluginLoader *plugin_loader)
+{
+ g_autoptr(GCancellable) cancellable = g_cancellable_new ();
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GPtrArray) events = NULL;
+ g_autoptr(GsAppList) list = NULL;
+ g_autoptr(GsPluginJob) plugin_job = NULL;
+
+ /* drop all caches */
+ gs_plugin_loader_setup_again (plugin_loader);
+
+ /* get search result based on addon keyword */
+ plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
+ "search", "hang",
+ "failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS |
+ GS_PLUGIN_FAILURE_FLAGS_NO_CONSOLE,
+ "timeout", 1, /* seconds */
+ NULL);
+ list = gs_plugin_loader_job_process (plugin_loader, plugin_job, cancellable, &error);
+ gs_test_flush_main_context ();
+ g_assert_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_TIMED_OUT);
+ g_assert (list == NULL);
+
+ /* ensure one event (plugin may also return error) */
+ events = gs_plugin_loader_get_events (plugin_loader);
+ g_assert_cmpint (events->len, ==, 1);
+}
+
+static void
gs_plugins_dummy_search_invalid_func (GsPluginLoader *plugin_loader)
{
g_autoptr(GError) error = NULL;
@@ -705,6 +734,9 @@ main (int argc, char **argv)
g_test_add_data_func ("/gnome-software/plugins/dummy/search",
plugin_loader,
(GTestDataFunc) gs_plugins_dummy_search_func);
+ g_test_add_data_func ("/gnome-software/plugins/dummy/hang",
+ plugin_loader,
+ (GTestDataFunc) gs_plugins_dummy_hang_func);
g_test_add_data_func ("/gnome-software/plugins/dummy/search{invalid}",
plugin_loader,
(GTestDataFunc) gs_plugins_dummy_search_invalid_func);
diff --git a/src/gs-search-page.c b/src/gs-search-page.c
index a97209e..93ca1ad 100644
--- a/src/gs-search-page.c
+++ b/src/gs-search-page.c
@@ -271,6 +271,7 @@ gs_search_page_load (GsSearchPage *self)
plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_SEARCH,
"search", self->value,
"max-results", GS_SEARCH_PAGE_MAX_RESULTS,
+ "timeout", 10,
"failure-flags", GS_PLUGIN_FAILURE_FLAGS_USE_EVENTS,
"refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON |
GS_PLUGIN_REFINE_FLAGS_REQUIRE_VERSION |
diff --git a/src/gs-shell.c b/src/gs-shell.c
index 2b1808c..5dd7d72 100644
--- a/src/gs-shell.c
+++ b/src/gs-shell.c
@@ -1,6 +1,6 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
- * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
* Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
*
* Licensed under the GNU General Public License Version 2
@@ -1498,6 +1498,13 @@ gs_shell_show_event (GsShell *shell, GsPluginEvent *event)
if (error == NULL)
return FALSE;
+ /* name and shame the plugin */
+ if (g_error_matches (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_TIMED_OUT)) {
+ gs_shell_show_event_app_notify (shell, error->message,
+ GS_SHELL_EVENT_BUTTON_NONE);
+ return TRUE;
+ }
+
/* split up the events by action */
action = gs_plugin_event_get_action (event);
switch (action) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]