/** * Let sleepers know about the wake-up condition. * * @param hl the list of waiting parties * @param data waking-up data to supply to callback */ static void wq_notify(hash_list_t *hl, void *data) { hash_list_iter_t *iter; size_t i, count; iter = hash_list_iterator(hl); count = hash_list_length(hl); i = 0; while (hash_list_iter_has_next(iter)) { wq_event_t *we = hash_list_iter_next(iter); wq_status_t status; wq_event_check(we); /* * Stop iteration in case callbacks have called wq_sleep() on the * same waiting queue we're iterating on and added items to the list. * This sanity check ensures we're not going to loop forever with a * callback systematically appending something. */ if (i++ >= count) { /* Something is odd, let them know about the calling stack */ s_critical("stopping after processing %zu item%s (list now has %u)", count, plural(count), hash_list_length(hl)); } status = (*we->cb)(we->arg, data); switch (status) { case WQ_SLEEP: continue; /* Still sleeping, leave in the list */ case WQ_EXCLUSIVE: case WQ_REMOVE: goto remove; } s_error("invalid status %d returned by %s()", status, stacktrace_function_name(we->cb)); remove: hash_list_iter_remove(iter); wq_event_free(we); /* * The callback may decide that we shouldn't continue notifying * other sleepers (because it knows it grabbed a resource that others * will need for instance). This is used as an early termination * of the loop. */ if (WQ_EXCLUSIVE == status) break; } hash_list_iter_release(&iter); }
/** * Get a new index in the cache, and update LRU data structures. * * @param db the database * @param num page number in the DB for which we want a cache index * * * @return -1 on error, or the allocated cache index. */ static int getidx(DBM *db, long num) { struct lru_cache *cache = db->cache; long n; /* Cache index */ /* * If we invalidated pages, reuse their indices. * If we have not used all the pages yet, get the next one. * Otherwise, use the least-recently requested page. */ if (slist_length(cache->available)) { void *v = slist_shift(cache->available); n = pointer_to_int(v); g_assert(n >= 0 && n < cache->pages); g_assert(!cache->dirty[n]); g_assert(-1 == cache->numpag[n]); hash_list_prepend(cache->used, int_to_pointer(n)); } else if (cache->next < cache->pages) { n = cache->next++; cache->dirty[n] = FALSE; hash_list_prepend(cache->used, int_to_pointer(n)); } else { void *last = hash_list_tail(cache->used); long oldnum; gboolean had_ioerr = booleanize(db->flags & DBM_IOERR_W); hash_list_moveto_head(cache->used, last); n = pointer_to_int(last); /* * This page is no longer cached as its cache index is being reused * Flush it to disk if dirty before discarding it. */ g_assert(n >= 0 && n < cache->pages); oldnum = cache->numpag[n]; if (cache->dirty[n] && !writebuf(db, oldnum, n)) { hash_list_iter_t *iter; void *item; gboolean found = FALSE; /* * Cannot flush dirty page now, probably because we ran out of * disk space. Look through the cache whether we can reuse a * non-dirty page instead, which would let us keep the dirty * page a little longer in the cache, in the hope it can then * be properly flushed later. */ iter = hash_list_iterator_tail(cache->used); while (NULL != (item = hash_list_iter_previous(iter))) { long i = pointer_to_int(item); g_assert(i >= 0 && i < cache->pages); if (!cache->dirty[i]) { found = TRUE; /* OK, reuse cache slot #i then */ n = i; oldnum = cache->numpag[i]; break; } } hash_list_iter_release(&iter); if (found) { g_assert(item != NULL); hash_list_moveto_head(cache->used, item); /* * Clear error condition if we had none prior to the flush * attempt, since we can do without it for now. */ if (!had_ioerr) db->flags &= ~DBM_IOERR_W; g_warning("sdbm: \"%s\": " "reusing cache slot used by clean page #%ld instead", sdbm_name(db), oldnum); } else { g_warning("sdbm: \"%s\": cannot discard dirty page #%ld", sdbm_name(db), oldnum); return -1; } } g_hash_table_remove(cache->pagnum, ulong_to_pointer(oldnum)); cache->dirty[n] = FALSE; } /* * Record the association between the cache index and the page number. */ g_assert(n >= 0 && n < cache->pages); cache->numpag[n] = num; g_hash_table_insert(cache->pagnum, ulong_to_pointer(num), int_to_pointer(n)); return n; }