New facility for removing pending toplevel callbacks.

This is used when you're about to destroy an object that is
(potentially) the context parameter for some still-pending toplevel
callback. It causes callbacks.c to go through its pending list and
delete any callback records referring to that context parameter, so
that when you destroy the object those callbacks aren't still waiting
to cause stale-pointer dereferences.
This commit is contained in:
Simon Tatham 2017-11-25 17:17:21 +00:00
Родитель 99bdaa7752
Коммит afa9734b7d
2 изменённых файлов: 51 добавлений и 15 удалений

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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,