diff --git a/sys/vfs/ufs/ffs_softdep.c b/sys/vfs/ufs/ffs_softdep.c --- a/sys/vfs/ufs/ffs_softdep.c +++ b/sys/vfs/ufs/ffs_softdep.c @@ -348,6 +348,12 @@ static void worklist_remove(struct worklist *); static void workitem_free(struct worklist *, int); +/* + * Cached tail of softdep_workitem_pending (declared below). Kept here so the + * removal helper can invalidate it; it must be declared before worklist_remove. + */ +static struct worklist *softdep_worklist_tail; + #define WORKLIST_INSERT_BP(bp, item) do { \ (bp)->b_ops = &softdep_bioops; \ worklist_insert(&(bp)->b_dep, item); \ @@ -379,6 +385,15 @@ item->wk_state &= ~ONWORKLIST; LIST_REMOVE(item, wk_list); + /* + * If we are removing the cached tail of the pending worklist, drop + * the cached pointer so that add_to_worklist() does not insert onto + * a now-detached (and soon-to-be-freed) phantom chain. The list is + * a singly-linked LIST, so add_to_worklist() will rediscover the new + * tail by walking from the head the next time it appends. + */ + if (item == softdep_worklist_tail) + softdep_worklist_tail = NULL; } static void @@ -461,17 +476,39 @@ static void add_to_worklist(struct worklist *wk) { - static struct worklist *worklist_tail; if (wk->wk_state & ONWORKLIST) { panic("add_to_worklist: already on list"); } wk->wk_state |= ONWORKLIST; - if (LIST_FIRST(&softdep_workitem_pending) == NULL) + if (LIST_FIRST(&softdep_workitem_pending) == NULL) { LIST_INSERT_HEAD(&softdep_workitem_pending, wk, wk_list); - else - LIST_INSERT_AFTER(worklist_tail, wk, wk_list); - worklist_tail = wk; + softdep_worklist_tail = wk; + } else { + /* + * softdep_worklist_tail is cached to keep appends O(1), + * but it may have gone stale: process_worklist_item() can + * remove the tail item out-of-order (via the LK_NOWAIT scan + * in request_cleanup), after which worklist_remove() clears + * the cache. When that happens we must rediscover the real + * tail by walking from the head before appending, otherwise + * we would insert onto a phantom chain unreachable from the + * head -- leaking the item and inflating num_on_worklist, + * which eventually makes softdep_flushfiles() panic with + * "looping". + */ + if (softdep_worklist_tail == NULL || + (softdep_worklist_tail->wk_state & ONWORKLIST) == 0) { + softdep_worklist_tail = + LIST_FIRST(&softdep_workitem_pending); + while (LIST_NEXT(softdep_worklist_tail, wk_list) != NULL) + softdep_worklist_tail = + LIST_NEXT(softdep_worklist_tail, wk_list); + } + KKASSERT(softdep_worklist_tail != NULL); + LIST_INSERT_AFTER(softdep_worklist_tail, wk, wk_list); + softdep_worklist_tail = wk; + } num_on_worklist += 1; }