libgit2/src/cache.c
Michael Schubert 6ee6861123 cache: fix race condition
Example: a cached node is owned only by the cache (refcount == 1).
Thread A holds the lock and determines that the entry which should get
cached equals the node (git_oid_cmp(&node->oid, &entry->oid) == 0).
It frees the given entry to instead return the cached node to the user
(entry = node). Now, before Thread A happens to increment the refcount
of the node *outside* the cache lock, Thread B tries to store another
entry and hits the slot of the node before, decrements its refcount and
frees it *before* Thread A gets a chance to increment for the user.

	git_cached_obj_incref(entry);

	git_mutex_lock(&cache->lock);
	{
		git_cached_obj *node = cache->nodes[hash & cache->size_mask];

		if (node == NULL) {
			cache->nodes[hash & cache->size_mask] = entry;
		} else if (git_oid_cmp(&node->oid, &entry->oid) == 0) {
			git_cached_obj_decref(entry, cache->free_obj);
			entry = node;
		} else {
			git_cached_obj_decref(node, cache->free_obj);

// Thread B is here

			cache->nodes[hash & cache->size_mask] = entry;
		}
	}
	git_mutex_unlock(&cache->lock);

// Thread A is here

	/* increase the refcount again, because we are
	 * returning it to the user */
	git_cached_obj_incref(entry);
2012-09-11 15:58:13 +02:00

102 lines
2.2 KiB
C

/*
* Copyright (C) 2009-2012 the libgit2 contributors
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "repository.h"
#include "commit.h"
#include "thread-utils.h"
#include "util.h"
#include "cache.h"
#include "git2/oid.h"
int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr)
{
if (size < 8)
size = 8;
size = git__size_t_powerof2(size);
cache->size_mask = size - 1;
cache->lru_count = 0;
cache->free_obj = free_ptr;
git_mutex_init(&cache->lock);
cache->nodes = git__malloc(size * sizeof(git_cached_obj *));
GITERR_CHECK_ALLOC(cache->nodes);
memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *));
return 0;
}
void git_cache_free(git_cache *cache)
{
size_t i;
for (i = 0; i < (cache->size_mask + 1); ++i) {
if (cache->nodes[i] != NULL)
git_cached_obj_decref(cache->nodes[i], cache->free_obj);
}
git__free(cache->nodes);
}
void *git_cache_get(git_cache *cache, const git_oid *oid)
{
uint32_t hash;
git_cached_obj *node = NULL, *result = NULL;
memcpy(&hash, oid->id, sizeof(hash));
git_mutex_lock(&cache->lock);
{
node = cache->nodes[hash & cache->size_mask];
if (node != NULL && git_oid_cmp(&node->oid, oid) == 0) {
git_cached_obj_incref(node);
result = node;
}
}
git_mutex_unlock(&cache->lock);
return result;
}
void *git_cache_try_store(git_cache *cache, void *_entry)
{
git_cached_obj *entry = _entry;
uint32_t hash;
memcpy(&hash, &entry->oid, sizeof(uint32_t));
/* increase the refcount on this object, because
* the cache now owns it */
git_cached_obj_incref(entry);
git_mutex_lock(&cache->lock);
{
git_cached_obj *node = cache->nodes[hash & cache->size_mask];
if (node == NULL) {
cache->nodes[hash & cache->size_mask] = entry;
} else if (git_oid_cmp(&node->oid, &entry->oid) == 0) {
git_cached_obj_decref(entry, cache->free_obj);
entry = node;
} else {
git_cached_obj_decref(node, cache->free_obj);
cache->nodes[hash & cache->size_mask] = entry;
}
/* increase the refcount again, because we are
* returning it to the user */
git_cached_obj_incref(entry);
}
git_mutex_unlock(&cache->lock);
return entry;
}