diff options
Diffstat (limited to 'libglusterfs/src/gidcache.c')
-rw-r--r-- | libglusterfs/src/gidcache.c | 306 |
1 files changed, 155 insertions, 151 deletions
diff --git a/libglusterfs/src/gidcache.c b/libglusterfs/src/gidcache.c index f2d98abefc..b9fdb9a39b 100644 --- a/libglusterfs/src/gidcache.c +++ b/libglusterfs/src/gidcache.c @@ -16,37 +16,39 @@ * out after a configurable interval. Hardly rocket science, but lots of * details to worry about. */ -#define BUCKET_START(p,n) ((p) + ((n) * AUX_GID_CACHE_ASSOC)) +#define BUCKET_START(p, n) ((p) + ((n)*AUX_GID_CACHE_ASSOC)) /* * Initialize the cache. */ -int gid_cache_init(gid_cache_t *cache, uint32_t timeout) +int +gid_cache_init(gid_cache_t *cache, uint32_t timeout) { - if (!cache) - return -1; + if (!cache) + return -1; - LOCK_INIT(&cache->gc_lock); - cache->gc_max_age = timeout; - cache->gc_nbuckets = AUX_GID_CACHE_BUCKETS; - memset(cache->gc_cache, 0, sizeof(gid_list_t) * AUX_GID_CACHE_SIZE); + LOCK_INIT(&cache->gc_lock); + cache->gc_max_age = timeout; + cache->gc_nbuckets = AUX_GID_CACHE_BUCKETS; + memset(cache->gc_cache, 0, sizeof(gid_list_t) * AUX_GID_CACHE_SIZE); - return 0; + return 0; } /* * Reconfigure the cache timeout. */ -int gid_cache_reconf(gid_cache_t *cache, uint32_t timeout) +int +gid_cache_reconf(gid_cache_t *cache, uint32_t timeout) { - if (!cache) - return -1; + if (!cache) + return -1; - LOCK(&cache->gc_lock); - cache->gc_max_age = timeout; - UNLOCK(&cache->gc_lock); + LOCK(&cache->gc_lock); + cache->gc_max_age = timeout; + UNLOCK(&cache->gc_lock); - return 0; + return 0; } /* @@ -54,153 +56,155 @@ int gid_cache_reconf(gid_cache_t *cache, uint32_t timeout) * an additional allocation and memory copy. The caller should copy the data and * release (unlock) the cache as soon as possible. */ -const gid_list_t *gid_cache_lookup(gid_cache_t *cache, uint64_t id, - uint64_t uid, uint64_t gid) +const gid_list_t * +gid_cache_lookup(gid_cache_t *cache, uint64_t id, uint64_t uid, uint64_t gid) { - int bucket; - int i; - time_t now; - const gid_list_t *agl; - - LOCK(&cache->gc_lock); - now = time(NULL); - bucket = id % cache->gc_nbuckets; - agl = BUCKET_START(cache->gc_cache, bucket); - for (i = 0; i < AUX_GID_CACHE_ASSOC; i++, agl++) { - if (!agl->gl_list) - continue; - if (agl->gl_id != id) - continue; - - /* - @uid and @gid reflect the latest UID/GID of the - process performing the syscall (taken from frame->root). - - If the UID and GID has changed for the PID since the - time we cached it, we should treat the cache as having - stale values and query them freshly. - */ - if (agl->gl_uid != uid || agl->gl_gid != gid) - break; - - /* - * We don't put new entries in the cache when expiration=0, but - * there might be entries still in there if expiration was - * changed very recently. Writing the check this way ensures - * that they're not used. - */ - if (now < agl->gl_deadline) { - return agl; - } - - /* - * We're not going to find any more UID matches, and reaping - * is handled further down to maintain LRU order. - */ - break; - } - UNLOCK(&cache->gc_lock); - return NULL; + int bucket; + int i; + time_t now; + const gid_list_t *agl; + + LOCK(&cache->gc_lock); + now = time(NULL); + bucket = id % cache->gc_nbuckets; + agl = BUCKET_START(cache->gc_cache, bucket); + for (i = 0; i < AUX_GID_CACHE_ASSOC; i++, agl++) { + if (!agl->gl_list) + continue; + if (agl->gl_id != id) + continue; + + /* + @uid and @gid reflect the latest UID/GID of the + process performing the syscall (taken from frame->root). + + If the UID and GID has changed for the PID since the + time we cached it, we should treat the cache as having + stale values and query them freshly. + */ + if (agl->gl_uid != uid || agl->gl_gid != gid) + break; + + /* + * We don't put new entries in the cache when expiration=0, but + * there might be entries still in there if expiration was + * changed very recently. Writing the check this way ensures + * that they're not used. + */ + if (now < agl->gl_deadline) { + return agl; + } + + /* + * We're not going to find any more UID matches, and reaping + * is handled further down to maintain LRU order. + */ + break; + } + UNLOCK(&cache->gc_lock); + return NULL; } /* * Release an entry found via lookup. */ -void gid_cache_release(gid_cache_t *cache, const gid_list_t *agl) +void +gid_cache_release(gid_cache_t *cache, const gid_list_t *agl) { - UNLOCK(&cache->gc_lock); + UNLOCK(&cache->gc_lock); } /* * Add a new list entry to the cache. If an entry for this ID already exists, * update it. */ -int gid_cache_add(gid_cache_t *cache, gid_list_t *gl) +int +gid_cache_add(gid_cache_t *cache, gid_list_t *gl) { - gid_list_t *agl; - int bucket; - int i; - time_t now; - - if (!gl || !gl->gl_list) - return -1; - - if (!cache->gc_max_age) - return 0; - - LOCK(&cache->gc_lock); - now = time(NULL); - - /* - * Scan for the first free entry or one that matches this id. The id - * check is added to address a bug where the cache might contain an - * expired entry for this id. Since lookup occurs in LRU order and - * does not reclaim entries, it will always return failure on discovery - * of an expired entry. This leads to duplicate entries being added, - * which still do not satisfy lookups until the expired entry (and - * everything before it) is reclaimed. - * - * We address this through reuse of an entry already allocated to this - * id, whether expired or not, since we have obviously already received - * more recent data. The entry is repopulated with the new data and a new - * deadline and is pushed forward to reside as the last populated entry in - * the bucket. - */ - bucket = gl->gl_id % cache->gc_nbuckets; - agl = BUCKET_START(cache->gc_cache, bucket); - for (i = 0; i < AUX_GID_CACHE_ASSOC; ++i, ++agl) { - if (agl->gl_id == gl->gl_id) - break; - if (!agl->gl_list) - break; - } - - /* - * The way we allocate free entries naturally places the newest - * ones at the highest indices, so evicting the lowest makes - * sense, but that also means we can't just replace it with the - * one that caused the eviction. That would cause us to thrash - * the first entry while others remain idle. Therefore, we - * need to slide the other entries down and add the new one at - * the end just as if the *last* slot had been free. - * - * Deadline expiration is also handled here, since the oldest - * expired entry will be in the first position. This does mean - * the bucket can stay full of expired entries if we're idle - * but, if the small amount of extra memory or scan time before - * we decide to evict someone ever become issues, we could - * easily add a reaper thread. - */ - - if (i >= AUX_GID_CACHE_ASSOC) { - /* cache full, evict the first (LRU) entry */ - i = 0; - agl = BUCKET_START(cache->gc_cache, bucket); - GF_FREE(agl->gl_list); - } else if (agl->gl_list) { - /* evict the old entry we plan to reuse */ - GF_FREE(agl->gl_list); - } - - /* - * If we have evicted an entry, slide the subsequent populated entries - * back and populate the last entry. - */ - for (; i < AUX_GID_CACHE_ASSOC - 1; i++) { - if (!agl[1].gl_list) - break; - agl[0] = agl[1]; - agl++; - } - - agl->gl_id = gl->gl_id; - agl->gl_uid = gl->gl_uid; - agl->gl_gid = gl->gl_gid; - agl->gl_count = gl->gl_count; - agl->gl_list = gl->gl_list; - agl->gl_deadline = now + cache->gc_max_age; - - UNLOCK(&cache->gc_lock); - - return 1; + gid_list_t *agl; + int bucket; + int i; + time_t now; + + if (!gl || !gl->gl_list) + return -1; + + if (!cache->gc_max_age) + return 0; + + LOCK(&cache->gc_lock); + now = time(NULL); + + /* + * Scan for the first free entry or one that matches this id. The id + * check is added to address a bug where the cache might contain an + * expired entry for this id. Since lookup occurs in LRU order and + * does not reclaim entries, it will always return failure on discovery + * of an expired entry. This leads to duplicate entries being added, + * which still do not satisfy lookups until the expired entry (and + * everything before it) is reclaimed. + * + * We address this through reuse of an entry already allocated to this + * id, whether expired or not, since we have obviously already received + * more recent data. The entry is repopulated with the new data and a new + * deadline and is pushed forward to reside as the last populated entry in + * the bucket. + */ + bucket = gl->gl_id % cache->gc_nbuckets; + agl = BUCKET_START(cache->gc_cache, bucket); + for (i = 0; i < AUX_GID_CACHE_ASSOC; ++i, ++agl) { + if (agl->gl_id == gl->gl_id) + break; + if (!agl->gl_list) + break; + } + + /* + * The way we allocate free entries naturally places the newest + * ones at the highest indices, so evicting the lowest makes + * sense, but that also means we can't just replace it with the + * one that caused the eviction. That would cause us to thrash + * the first entry while others remain idle. Therefore, we + * need to slide the other entries down and add the new one at + * the end just as if the *last* slot had been free. + * + * Deadline expiration is also handled here, since the oldest + * expired entry will be in the first position. This does mean + * the bucket can stay full of expired entries if we're idle + * but, if the small amount of extra memory or scan time before + * we decide to evict someone ever become issues, we could + * easily add a reaper thread. + */ + + if (i >= AUX_GID_CACHE_ASSOC) { + /* cache full, evict the first (LRU) entry */ + i = 0; + agl = BUCKET_START(cache->gc_cache, bucket); + GF_FREE(agl->gl_list); + } else if (agl->gl_list) { + /* evict the old entry we plan to reuse */ + GF_FREE(agl->gl_list); + } + + /* + * If we have evicted an entry, slide the subsequent populated entries + * back and populate the last entry. + */ + for (; i < AUX_GID_CACHE_ASSOC - 1; i++) { + if (!agl[1].gl_list) + break; + agl[0] = agl[1]; + agl++; + } + + agl->gl_id = gl->gl_id; + agl->gl_uid = gl->gl_uid; + agl->gl_gid = gl->gl_gid; + agl->gl_count = gl->gl_count; + agl->gl_list = gl->gl_list; + agl->gl_deadline = now + cache->gc_max_age; + + UNLOCK(&cache->gc_lock); + + return 1; } |