--- ufs_dirhash.c.orig 2008-06-05 16:24:13.000000000 -0700 +++ ufs_dirhash.c 2008-06-22 18:18:39.000000000 -0700 @@ -48,6 +48,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,9 @@ static int ufs_dirhashcheck = 0; SYSCTL_INT(_vfs_ufs, OID_AUTO, dirhash_docheck, CTLFLAG_RW, &ufs_dirhashcheck, 0, "enable extra sanity tests"); +static int ufs_dirhashlowmemcount = 0; +SYSCTL_INT(_vfs_ufs, OID_AUTO, dirhash_lowmemcount, CTLFLAG_RD, + &ufs_dirhashlowmemcount, 0, "number of times low memory hook called"); static int ufsdirhash_hash(struct dirhash *dh, char *name, int namelen); @@ -88,7 +92,9 @@ static int ufsdirhash_findslot(struct dirhash *dh, char *name, int namelen, doff_t offset); static doff_t ufsdirhash_getprev(struct direct *dp, doff_t offset); +static int ufsdirhash_destroy(void); static int ufsdirhash_recycle(int wanted); +static void ufsdirhash_lowmem(void); static void ufsdirhash_free_locked(struct inode *ip); static uma_zone_t ufsdirhash_zone; @@ -1075,17 +1081,71 @@ } /* - * Try to free up `wanted' bytes by stealing memory from existing - * dirhashes. Returns zero with list locked if successful. + * Delete the first dirhash on the list and reclaim its memory. + * Assumes that ufsdirhash_list is locked, and leaves it locked. + * If unable to obtain a lock on the first dirhash, moves down + * the list until it can lock a dirhash and destroys it. Returns + * the amount of memory freed, or -1 if unable to find any + * dirhashes that can be destroyed. */ static int -ufsdirhash_recycle(int wanted) +ufsdirhash_destroy() { struct dirhash *dh; doff_t **hash; u_int8_t *blkfree; int i, mem, narrays; + dh = TAILQ_FIRST(&ufsdirhash_list); + if (dh == NULL) + return (-1); + + /* + * If we can't lock it it's in use and we don't want to + * destroy it anyway. Go on to the next in the list. + */ + while (lockmgr(&dh->dh_lock, LK_EXCLUSIVE | LK_NOWAIT, NULL)) { + dh = TAILQ_NEXT(dh, dh_list); + if (dh == NULL) + return (-1); + } + KASSERT(dh->dh_hash != NULL, ("dirhash: NULL hash on list")); + + /* Remove it from the list and detach its memory. */ + TAILQ_REMOVE(&ufsdirhash_list, dh, dh_list); + dh->dh_onlist = 0; + hash = dh->dh_hash; + dh->dh_hash = NULL; + blkfree = dh->dh_blkfree; + dh->dh_blkfree = NULL; + narrays = dh->dh_narrays; + mem = dh->dh_memreq; + dh->dh_memreq = 0; + + /* Unlock everything, free the detached memory. */ + ufsdirhash_release(dh); + DIRHASHLIST_UNLOCK(); + for (i = 0; i < narrays; i++) + DIRHASH_BLKFREE(hash[i]); + FREE(hash, M_DIRHASH); + FREE(blkfree, M_DIRHASH); + + /* Account for the returned memory. */ + DIRHASHLIST_LOCK(); + ufs_dirhashmem -= mem; + + return (mem); +} + +/* + * Try to free up `wanted' bytes by stealing memory from existing + * dirhashes. Returns zero with list locked if successful. + */ +static int +ufsdirhash_recycle(int wanted) +{ + struct dirhash *dh; + DIRHASHLIST_LOCK(); dh = TAILQ_FIRST(&ufsdirhash_list); while (wanted + ufs_dirhashmem > ufs_dirhashmaxmem) { @@ -1094,44 +1154,36 @@ DIRHASHLIST_UNLOCK(); return (-1); } - /* - * If we can't lock it it's in use and we don't want to - * recycle it anyway. - */ - if (lockmgr(&dh->dh_lock, LK_EXCLUSIVE | LK_NOWAIT, NULL)) { - dh = TAILQ_NEXT(dh, dh_list); - continue; - } - KASSERT(dh->dh_hash != NULL, ("dirhash: NULL hash on list")); - /* Remove it from the list and detach its memory. */ - TAILQ_REMOVE(&ufsdirhash_list, dh, dh_list); - dh->dh_onlist = 0; - hash = dh->dh_hash; - dh->dh_hash = NULL; - blkfree = dh->dh_blkfree; - dh->dh_blkfree = NULL; - narrays = dh->dh_narrays; - mem = dh->dh_memreq; - dh->dh_memreq = 0; + /* Try deleting a dirhash. Give up if we can't delete any. */ + if (ufsdirhash_destroy() < 0) + return (-1); - /* Unlock everything, free the detached memory. */ - ufsdirhash_release(dh); - DIRHASHLIST_UNLOCK(); - for (i = 0; i < narrays; i++) - DIRHASH_BLKFREE(hash[i]); - FREE(hash, M_DIRHASH); - FREE(blkfree, M_DIRHASH); - - /* Account for the returned memory, and repeat if necessary. */ - DIRHASHLIST_LOCK(); - ufs_dirhashmem -= mem; + /* Repeat if necessary. */ dh = TAILQ_FIRST(&ufsdirhash_list); } /* Success; return with list locked. */ return (0); } +/* + * Calback that frees some dirhashes when the system is low on virtual memory. + */ +static void +ufsdirhash_lowmem() +{ + ufs_dirhashlowmemcount++; + + DIRHASHLIST_LOCK(); + if (ufs_dirhashmem > 0) + /* + * Try deleting only one dirhash for now, and don't bother + * to check if it worked. + */ + ufsdirhash_destroy(); + DIRHASHLIST_UNLOCK(); +} + void ufsdirhash_init() @@ -1140,6 +1192,10 @@ NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, 0); mtx_init(&ufsdirhash_mtx, "dirhash list", NULL, MTX_DEF); TAILQ_INIT(&ufsdirhash_list); + + /* Register a callback function to handle low memory signals */ + EVENTHANDLER_REGISTER(vm_lowmem, ufsdirhash_lowmem, NULL, + EVENTHANDLER_PRI_FIRST); } void