From 8c1e157d853a4a0f9fb08716525425a942bf4094 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 30 Nov 2013 18:04:57 +0000 Subject: [PATCH] Make GTK idle and quit function setup idempotent. I found last week that when a local proxy process terminated unexpectedly, Unix PuTTY went into a tight loop calling quit functions, because if idle_toplevel_callback_func is called from inside a subsidiary gtk_main then it will schedule a quit function and _not_ disable itself, so that that quit function keeps being rescheduled on subsequent calls. To fix, I've tried to make the whole handling of idle and quit functions more sensibly robust: we keep our own boolean flag indicating whether each of our functions has already been scheduled with GTK, and if so, we don't schedule the same one again. Also, when idle_toplevel_callback_func schedules a quit function, it should unschedule itself since it's now done everything it can until a gtk_main instance quits. git-svn-id: http://svn.tartarus.org/sgt/putty@10100 cda61777-01e9-0310-a592-d414129be87e --- unix/gtkwin.c | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 812e4faf..8996b2e5 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -94,6 +94,7 @@ struct gui_data { int mouseptr_visible; int busy_status; guint toplevel_callback_idle_id; + int idle_fn_scheduled, quit_fn_scheduled; int alt_keycode; int alt_digits; char *wintitle; @@ -1403,6 +1404,8 @@ static gint quit_toplevel_callback_func(gpointer data) notify_toplevel_callback(inst); + inst->quit_fn_scheduled = FALSE; + return 0; } @@ -1414,16 +1417,36 @@ static gint idle_toplevel_callback_func(gpointer data) /* * We don't run the callbacks if we're in the middle of a * subsidiary gtk_main. Instead, ask for a callback when we - * get back out of the subsidiary main loop, so we can - * reschedule ourself then. + * get back out of the subsidiary main loop (if we haven't + * already arranged one), so we can reschedule ourself then. */ - gtk_quit_add(2, quit_toplevel_callback_func, inst); + if (!inst->quit_fn_scheduled) { + gtk_quit_add(2, quit_toplevel_callback_func, inst); + inst->quit_fn_scheduled = TRUE; + } + /* + * And unschedule this idle function, since we've now done + * everything we can until the innermost gtk_main has quit and + * can reschedule us with a chance of actually taking action. + */ + if (inst->idle_fn_scheduled) { /* double-check, just in case */ + gtk_idle_remove(inst->toplevel_callback_idle_id); + inst->idle_fn_scheduled = FALSE; + } } else { run_toplevel_callbacks(); } - if (!toplevel_callback_pending()) + /* + * If we've emptied our toplevel callback queue, unschedule + * ourself. Otherwise, leave ourselves pending so we'll be called + * again to deal with more callbacks after another round of the + * event loop. + */ + if (!toplevel_callback_pending() && inst->idle_fn_scheduled) { gtk_idle_remove(inst->toplevel_callback_idle_id); + inst->idle_fn_scheduled = FALSE; + } return TRUE; } @@ -1432,8 +1455,11 @@ static void notify_toplevel_callback(void *frontend) { struct gui_data *inst = (struct gui_data *)frontend; - inst->toplevel_callback_idle_id = - gtk_idle_add(idle_toplevel_callback_func, inst); + if (!inst->idle_fn_scheduled) { + inst->toplevel_callback_idle_id = + gtk_idle_add(idle_toplevel_callback_func, inst); + inst->idle_fn_scheduled = TRUE; + } } static gint timer_trigger(gpointer data) @@ -3604,6 +3630,8 @@ int pt_main(int argc, char **argv) inst->busy_status = BUSY_NOT; inst->conf = conf_new(); inst->wintitle = inst->icontitle = NULL; + inst->quit_fn_scheduled = FALSE; + inst->idle_fn_scheduled = FALSE; /* defer any child exit handling until we're ready to deal with * it */ -- 2.45.2