/*- * See the file LICENSE for redistribution information. * * Copyright (c) 1996-2001 * Sleepycat Software. All rights reserved. */ #include "db_config.h" #ifndef lint static const char revid[] = "$Id: mp_alloc.c,v 11.15 2001/07/24 18:31:28 bostic Exp $"; #endif /* not lint */ #ifndef NO_SYSTEM_INCLUDES #include #endif #include "db_int.h" #include "db_shash.h" #include "mp.h" /* * __memp_alloc -- * Allocate some space from a cache region. * * PUBLIC: int __memp_alloc __P((DB_MPOOL *, * PUBLIC: REGINFO *, MPOOLFILE *, size_t, roff_t *, void *)); */ int __memp_alloc(dbmp, memreg, mfp, len, offsetp, retp) DB_MPOOL *dbmp; REGINFO *memreg; MPOOLFILE *mfp; size_t len; roff_t *offsetp; void *retp; { BH *bhp, *nbhp; DB_ENV *dbenv; MPOOL *c_mp; MPOOLFILE *bh_mfp; int nomore, restart, ret, wrote; u_int32_t failed_writes, pages_reviewed; size_t total; void *p; dbenv = dbmp->dbenv; c_mp = memreg->primary; failed_writes = 0; /* * If we're allocating a buffer, and the one we're discarding is the * same size, we don't want to waste the time to re-integrate it into * the shared memory free list. If the DB_MPOOLFILE argument isn't * NULL, we'll compare the underlying page sizes of the two buffers * before free-ing and re-allocating buffers. */ if (mfp != NULL) len = (sizeof(BH) - sizeof(u_int8_t)) + mfp->stat.st_pagesize; nomore = 0; alloc: if ((ret = __db_shalloc(memreg->addr, len, MUTEX_ALIGN, &p)) == 0) { if (offsetp != NULL) *offsetp = R_OFFSET(memreg, p); *(void **)retp = p; return (0); } if (nomore == 1) { /* * Things are really bad, let's try to sync the mpool. * This will force any queue extent pages out. * While it could be that we just don't have enough * space for what we want, and this is rather expensive, * we are about to fail, so, why not. */ R_UNLOCK(dbenv, dbmp->reginfo); ret = dbenv->memp_sync(dbenv, NULL); R_LOCK(dbenv, dbmp->reginfo); if (ret == DB_INCOMPLETE || ret == EIO) ret = 0; else if (ret != 0) return (ret); } else if (nomore == 2) { __db_err(dbenv, "Unable to allocate %lu bytes from mpool shared region: %s", (u_long)len, db_strerror(ret)); return (ret); } retry: /* Find a buffer we can flush; pure LRU. */ total = 0; restart = 0; pages_reviewed = 0; for (bhp = SH_TAILQ_FIRST(&c_mp->bhq, __bh); bhp != NULL; bhp = nbhp) { nbhp = SH_TAILQ_NEXT(bhp, q, __bh); ++pages_reviewed; /* Ignore pinned or locked (I/O in progress) buffers. */ if (bhp->ref != 0 || F_ISSET(bhp, BH_LOCKED)) continue; /* Find the associated MPOOLFILE. */ bh_mfp = R_ADDR(dbmp->reginfo, bhp->mf_offset); /* Write the page if it's dirty. */ if (F_ISSET(bhp, BH_DIRTY)) { ++bhp->ref; ret = __memp_bhwrite(dbmp, bh_mfp, bhp, 0, &restart, &wrote); --bhp->ref; if (ret != 0) { /* * Count the number of writes that have * failed. If the number of writes that * have failed, total, plus the number * of pages we've reviewed on this pass * equals the number of buffers there * currently are, we've most likely * run out of buffers that are going to * succeed, and it's time to fail. * (We chuck failing buffers to the * end of the list.) [#0637] */ failed_writes++; if (failed_writes + pages_reviewed >= c_mp->stat.st_page_dirty + c_mp->stat.st_page_clean) return (ret); /* * Otherwise, relocate this buffer * to the end of the LRU queue * so we're less likely to encounter * it again, and try again. */ SH_TAILQ_REMOVE(&c_mp->bhq, bhp, q, __bh); SH_TAILQ_INSERT_TAIL(&c_mp->bhq, bhp, q); goto retry; } /* * Another process may have acquired this buffer and * incremented the ref count after we wrote it. */ if (bhp->ref != 0) goto retry; /* * If we wrote the page, continue and free the buffer. * We don't have to rewalk the list to acquire the * buffer because it was never available for any other * process to modify it. * * If we didn't write the page, but we discarded and * reacquired the region lock, restart the list walk. * * If we neither wrote the buffer nor discarded the * region lock, continue down the buffer list. */ if (wrote) ++c_mp->stat.st_rw_evict; else { if (restart) goto retry; continue; } } else ++c_mp->stat.st_ro_evict; /* * Check to see if the buffer is the size we're looking for. * If it is, simply reuse it. */ if (mfp != NULL && mfp->stat.st_pagesize == bh_mfp->stat.st_pagesize) { __memp_bhfree(dbmp, bhp, 0); if (offsetp != NULL) *offsetp = R_OFFSET(memreg, bhp); *(void **)retp = bhp; return (0); } /* Note how much space we've freed, and free the buffer. */ total += __db_shsizeof(bhp); __memp_bhfree(dbmp, bhp, 1); /* * Retry as soon as we've freed up sufficient space. If we * have to coalesce of memory to satisfy the request, don't * try until it's likely (possible?) that we'll succeed. */ if (total >= 3 * len) goto alloc; /* Restart the walk if we discarded the region lock. */ if (restart) goto retry; } nomore++; goto alloc; }