diff --git a/callback.c b/callback.c index c70dc53f..84ea0c80 100644 --- a/callback.c +++ b/callback.c @@ -14,7 +14,7 @@ struct callback { void *ctx; }; -struct callback *cbhead = NULL, *cbtail = NULL; +struct callback *cbcurr = NULL, *cbhead = NULL, *cbtail = NULL; toplevel_callback_notify_fn_t notify_frontend = NULL; void *frontend = NULL; @@ -26,6 +26,30 @@ void request_callback_notifications(toplevel_callback_notify_fn_t fn, frontend = fr; } +void delete_callbacks_for_context(void *ctx) +{ + struct callback *newhead, *newtail; + + newhead = newtail = NULL; + while (cbhead) { + struct callback *cb = cbhead; + cbhead = cbhead->next; + if (cb->ctx == ctx) { + sfree(cb); + } else { + if (!newhead) + newhead = cb; + else + newtail->next = cb; + + newtail = cb; + } + } + + cbhead = newhead; + cbtail = newtail; +} + void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) { struct callback *cb; @@ -34,10 +58,18 @@ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) cb->fn = fn; cb->ctx = ctx; - /* If the front end has requested notification of pending + /* + * If the front end has requested notification of pending * callbacks, and we didn't already have one queued, let it know - * we do have one now. */ - if (notify_frontend && !cbhead) + * we do have one now. + * + * If cbcurr is non-NULL, i.e. we are actually in the middle of + * executing a callback right now, then we count that as the queue + * already having been non-empty. That saves the front end getting + * a constant stream of needless re-notifications if the last + * callback keeps re-scheduling itself. + */ + if (notify_frontend && !cbhead && !cbcurr) notify_frontend(frontend); if (cbtail) @@ -51,24 +83,27 @@ void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx) void run_toplevel_callbacks(void) { if (cbhead) { - struct callback *cb = cbhead; /* - * Careful ordering here. We call the function _before_ - * advancing cbhead (though, of course, we must free cb - * _after_ advancing it). This means that if the very last - * callback schedules another callback, cbhead does not become - * NULL at any point, and so the frontend notification - * function won't be needlessly pestered. + * Transfer the head callback into cbcurr to indicate that + * it's being executed. Then operations which transform the + * queue, like delete_callbacks_for_context, can proceed as if + * it's not there. */ - cb->fn(cb->ctx); - cbhead = cb->next; - sfree(cb); + cbcurr = cbhead; + cbhead = cbhead->next; if (!cbhead) cbtail = NULL; + + /* + * Now run the callback, and then clear it out of cbcurr. + */ + cbcurr->fn(cbcurr->ctx); + sfree(cbcurr); + cbcurr = NULL; } } int toplevel_callback_pending(void) { - return cbhead != NULL; + return cbcurr != NULL || cbhead != NULL; } diff --git a/putty.h b/putty.h index ac35eb73..0dd48a5a 100644 --- a/putty.h +++ b/putty.h @@ -1534,6 +1534,7 @@ typedef void (*toplevel_callback_fn_t)(void *ctx); void queue_toplevel_callback(toplevel_callback_fn_t fn, void *ctx); void run_toplevel_callbacks(void); int toplevel_callback_pending(void); +void delete_callbacks_for_context(void *ctx); typedef void (*toplevel_callback_notify_fn_t)(void *frontend); void request_callback_notifications(toplevel_callback_notify_fn_t notify,