diff options
| author | Richard Hughes <richard@hughsie.com> | 2017-06-28 16:50:42 (GMT) |
|---|---|---|
| committer | Richard Hughes <richard@hughsie.com> | 2017-06-29 14:46:51 (GMT) |
| commit | fa7216c1284d957acbf266f879a99ffcff06abee (patch) | |
| tree | 8a10ad2f96f8462d54d1a0af46bea6f7fad223e6 | |
| parent | 3d03f8d5d73b537752683bca14fbf10da2950cd7 (diff) | |
| download | gnome-software-fa7216c1284d957acbf266f879a99ffcff06abee.zip gnome-software-fa7216c1284d957acbf266f879a99ffcff06abee.tar.xz | |
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
| -rw-r--r-- | lib/gs-plugin-job-private.h | 1 | ||||
| -rw-r--r-- | lib/gs-plugin-job.c | 29 | ||||
| -rw-r--r-- | lib/gs-plugin-job.h | 2 | ||||
| -rw-r--r-- | lib/gs-plugin-loader.c | 120 | ||||
| -rw-r--r-- | lib/gs-plugin-types.h | 2 | ||||
| -rw-r--r-- | lib/gs-plugin.c | 2 | ||||
| -rw-r--r-- | lib/gs-utils.c | 6 | ||||
| -rw-r--r-- | plugins/dummy/gs-plugin-dummy.c | 70 | ||||
| -rw-r--r-- | plugins/dummy/gs-self-test.c | 32 | ||||
| -rw-r--r-- | src/gs-search-page.c | 1 | ||||
| -rw-r--r-- | 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) { |