nscd: Skip unusable entries in first pass in prune_cache (bug 30800)

Previously, if an entry was marked unusable for any reason, but had
not timed out yet, the assert would trigger.

One way to get into such state is if a data change is detected during
re-validation of an entry.  This causes the entry to be marked as not
usable.  If exits nscd soon after that, then the clock jumps
backwards, and nscd restarted, the cache re-validation run after
startup triggers the removed assert.

The change is more complicated than just the removal of the assert
because entries marked as not usable should be garbage-collected in
the second pass.  To make this happen, it is necessary to update some
book-keeping data.

Reviewed-by: DJ Delorie <dj@redhat.com>
This commit is contained in:
Florian Weimer 2023-08-29 08:28:31 +02:00
parent 693918b6dd
commit c00b984fcd

View File

@ -370,8 +370,11 @@ prune_cache (struct database_dyn *table, time_t now, int fd)
serv2str[runp->type], str, dh->timeout); serv2str[runp->type], str, dh->timeout);
} }
/* Check whether the entry timed out. */ /* Check whether the entry timed out. Timed out entries
if (dh->timeout < now) will be revalidated. For unusable records, it is still
necessary to record that the bucket needs to be scanned
again below. */
if (dh->timeout < now || !dh->usable)
{ {
/* This hash bucket could contain entries which need to /* This hash bucket could contain entries which need to
be looked at. */ be looked at. */
@ -383,7 +386,7 @@ prune_cache (struct database_dyn *table, time_t now, int fd)
/* We only have to look at the data of the first entries /* We only have to look at the data of the first entries
since the count information is kept in the data part since the count information is kept in the data part
which is shared. */ which is shared. */
if (runp->first) if (runp->first && dh->usable)
{ {
/* At this point there are two choices: we reload the /* At this point there are two choices: we reload the
@ -399,9 +402,6 @@ prune_cache (struct database_dyn *table, time_t now, int fd)
{ {
/* Remove the value. */ /* Remove the value. */
dh->usable = false; dh->usable = false;
/* We definitely have some garbage entries now. */
any = true;
} }
else else
{ {
@ -413,18 +413,15 @@ prune_cache (struct database_dyn *table, time_t now, int fd)
time_t timeout = readdfcts[runp->type] (table, runp, dh); time_t timeout = readdfcts[runp->type] (table, runp, dh);
next_timeout = MIN (next_timeout, timeout); next_timeout = MIN (next_timeout, timeout);
/* If the entry has been replaced, we might need
cleanup. */
any |= !dh->usable;
} }
} }
/* If the entry has been replaced, we might need cleanup. */
any |= !dh->usable;
} }
else else
{ /* Entry has not timed out and is usable. */
assert (dh->usable); next_timeout = MIN (next_timeout, dh->timeout);
next_timeout = MIN (next_timeout, dh->timeout);
}
run = runp->next; run = runp->next;
} }