diff --git a/include/git2/thread-utils.h b/include/git2/thread-utils.h index ecb09bb53..fb8644b93 100644 --- a/include/git2/thread-utils.h +++ b/include/git2/thread-utils.h @@ -67,6 +67,4 @@ # define GIT_TLS #endif -#define GIT_THREADS 1 - #endif /* INCLUDE_git_thread_utils_h__ */ diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 000000000..5a9b8415c --- /dev/null +++ b/src/cache.c @@ -0,0 +1,175 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "repository.h" +#include "commit.h" +#include "thread-utils.h" +#include "cache.h" + +#define GIT_CACHE_OPENADR 3 + + +GIT_INLINE(int) cached_obj_compare(git_cached_obj *obj, const git_oid *oid) +{ + return git_oid_cmp(&obj->oid, oid); +} + +GIT_INLINE(void) cached_obj_incref(git_cached_obj *obj) +{ + git_atomic_inc(&obj->refcount); +} + +GIT_INLINE(void) cached_obj_decref(git_cached_obj *obj, git_cached_obj_freeptr free_obj) +{ + if (git_atomic_dec(&obj->refcount) == 0) + free_obj(obj); +} + + +void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) +{ + size_t i; + + if (size < 8) + size = 8; + + /* round up size to closest power of 2 */ + size--; + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size |= size >> 16; + + cache->size_mask = size; + cache->lru_count = 0; + cache->free_obj = free_ptr; + + cache->nodes = git__malloc((size + 1) * sizeof(cache_node)); + + for (i = 0; i < (size + 1); ++i) { + git_mutex_init(&cache->nodes[i].lock); + cache->nodes[i].ptr = NULL; + cache->nodes[i].lru = 0; + } +} + +void git_cache_free(git_cache *cache) +{ + size_t i; + + for (i = 0; i < (cache->size_mask + 1); ++i) { + cached_obj_decref(cache->nodes[i].ptr, cache->free_obj); + git_mutex_free(&cache->nodes[i].lock); + } + + free(cache->nodes); +} + +void *git_cache_get(git_cache *cache, const git_oid *oid) +{ + const uint32_t *hash; + size_t i, pos, found = 0; + cache_node *node; + + hash = (const uint32_t *)oid->id; + + for (i = 0; !found && i < GIT_CACHE_OPENADR; ++i) { + pos = hash[i] & cache->size_mask; + node = &cache->nodes[pos]; + + git_mutex_lock(&node->lock); + { + if (cached_obj_compare(node->ptr, oid) == 0) { + cached_obj_incref(node->ptr); + node->lru = ++cache->lru_count; + found = 1; + } + } + git_mutex_unlock(&node->lock); + } + + + return found ? node->ptr : NULL; +} + +void *git_cache_try_store(git_cache *cache, void *entry) +{ + cache_node *nodes[GIT_CACHE_OPENADR], *lru_node; + const uint32_t *hash; + const git_oid *oid; + size_t i, stored = 0; + + oid = &((git_cached_obj*)entry)->oid; + hash = (const uint32_t *)oid->id; + + /* increase the refcount on this object, because + * the cache now owns it */ + cached_obj_incref(entry); + + for (i = 0; i < GIT_CACHE_OPENADR; ++i) { + size_t pos = hash[i] & cache->size_mask; + + nodes[i] = &cache->nodes[pos]; + git_mutex_lock(&nodes[i]->lock); + } + + lru_node = nodes[0]; + + for (i = 0; !stored && entry && i < GIT_CACHE_OPENADR; ++i) { + + if (nodes[i]->ptr == NULL) { + nodes[i]->ptr = entry; + nodes[i]->lru = ++cache->lru_count; + stored = 1; + } else if (cached_obj_compare(nodes[i]->ptr, oid) == 0) { + cached_obj_decref(entry, cache->free_obj); + entry = nodes[i]->ptr; + stored = 1; + } + + if (nodes[i]->lru < lru_node->lru) + lru_node = nodes[i]; + } + + if (!stored) { + void *old_entry = lru_node->ptr; + assert(old_entry); + + cached_obj_decref(old_entry, cache->free_obj); + lru_node->ptr = entry; + lru_node->lru = ++cache->lru_count; + } + + /* increase the refcount again, because we are + * returning it to the user */ + cached_obj_incref(entry); + + for (i = 0; i < GIT_CACHE_OPENADR; ++i) + git_mutex_unlock(&nodes[i]->lock); + + return entry; +} diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 000000000..9f525e68c --- /dev/null +++ b/src/cache.h @@ -0,0 +1,38 @@ +#ifndef INCLUDE_cache_h__ +#define INCLUDE_cache_h__ + +#include "git2/common.h" +#include "git2/oid.h" +#include "git2/odb.h" + +#include "thread-utils.h" + +typedef void (*git_cached_obj_freeptr)(void *); + +typedef struct { + git_oid oid; + git_atomic refcount; +} git_cached_obj; + +typedef struct { + git_cached_obj *ptr; + git_mutex lock; + unsigned int lru; +} cache_node; + +typedef struct { + cache_node *nodes; + + unsigned int lru_count; + size_t size_mask; + git_cached_obj_freeptr free_obj; +} git_cache; + + +void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr); +void git_cache_free(git_cache *cache); + +void *git_cache_try_store(git_cache *cache, void *entry); +void *git_cache_get(git_cache *cache, const git_oid *oid); + +#endif diff --git a/src/common.h b/src/common.h index 723085a2b..5ad878e26 100644 --- a/src/common.h +++ b/src/common.h @@ -31,18 +31,21 @@ # include # include "msvc-compat.h" # include "mingw-compat.h" -# include "win32/pthread.h" +# ifdef GIT_THREADS +# include "win32/pthread.h" +#endif # define snprintf _snprintf typedef SSIZE_T ssize_t; #else - # include # include -# include +# ifdef GIT_THREADS +# include +# endif #endif #include "git2/common.h" diff --git a/src/thread-utils.h b/src/thread-utils.h index e8372e731..1cf0e3407 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -2,6 +2,19 @@ #define INCLUDE_thread_utils_h__ + +/* Common operations even if threading has been disabled */ +typedef struct { + volatile int val; +} git_atomic; + +static inline void git_atomic_set(git_atomic *a, int val) +{ + a->val = val; +} + +#ifdef GIT_THREADS + #define git_thread pthread_t #define git_thread_create(thread, attr, start_routine, arg) pthread_create(thread, attr, start_routine, arg) #define git_thread_kill(thread) pthread_cancel(thread) @@ -15,13 +28,70 @@ #define git_mutex_unlock(a) pthread_mutex_unlock(a) #define git_mutex_free(a) pthread_mutex_destroy(a) +/* Pthreads condition vars -- disabled by now */ +#define git_cond unsigned int //pthread_cond_t +#define git_cond_init(c, a) (void)0 //pthread_cond_init(c, a) +#define git_cond_free(c) (void)0 //pthread_cond_destroy(c) +#define git_cond_wait(c, l) (void)0 //pthread_cond_wait(c, l) +#define git_cond_signal(c) (void)0 //pthread_cond_signal(c) +#define git_cond_broadcast(c) (void)0 //pthread_cond_broadcast(c) + +static inline int git_atomic_inc(git_atomic *a) +{ +#ifdef __GNUC__ + return __sync_add_and_fetch(&a->val, 1); +#elif defined(_MSC_VER) + return InterlockedIncrement(&a->val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +static inline int git_atomic_dec(git_atomic *a) +{ +#ifdef __GNUC__ + return __sync_sub_and_fetch(&a->val, 1); +#elif defined(_MSC_VER) + return InterlockedDecrement(&a->val); +#else +# error "Unsupported architecture for atomic operations" +#endif +} + +#else + +#define git_thread unsigned int +#define git_thread_create(thread, attr, start_routine, arg) (void)0 +#define git_thread_kill(thread) (void)0 +#define git_thread_exit(status) (void)0 +#define git_thread_join(id, status) (void)0 + +/* Pthreads Mutex */ +#define git_mutex unsigned int +#define git_mutex_init(a) (void)0 +#define git_mutex_lock(a) (void)0 +#define git_mutex_unlock(a) (void)0 +#define git_mutex_free(a) (void)0 + /* Pthreads condition vars */ -#define git_cond pthread_cond_t -#define git_cond_init(c, a) pthread_cond_init(c, a) -#define git_cond_free(c) pthread_cond_destroy(c) -#define git_cond_wait(c, l) pthread_cond_wait(c, l) -#define git_cond_signal(c) pthread_cond_signal(c) -#define git_cond_broadcast(c) pthread_cond_broadcast(c) +#define git_cond unsigned int +#define git_cond_init(c, a) (void)0 +#define git_cond_free(c) (void)0 +#define git_cond_wait(c, l) (void)0 +#define git_cond_signal(c) (void)0 +#define git_cond_broadcast(c) (void)0 + +static inline int git_atomic_inc(git_atomic *a) +{ + return ++a->val; +} + +static inline int git_atomic_dec(git_atomic *a) +{ + return --a->val; +} + +#endif extern int git_online_cpus(void); diff --git a/src/win32/pthread.c b/src/win32/pthread.c index fffff81df..f47364a76 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -36,17 +36,27 @@ int pthread_create(pthread_t *GIT_RESTRICT thread, return *thread ? GIT_SUCCESS : GIT_EOSERR; } -int pthread_cond_signal(pthread_cond_t *cond) +int pthread_join(pthread_t thread, void **value_ptr) { - WakeConditionVariable(cond); + int ret; + ret = WaitForSingleObject(thread, INFINITE); + if (ret && value_ptr) + GetExitCodeThread(thread, (void*) value_ptr); + return -(!!ret); +} + +int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex, + const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr)) +{ + GIT_UNUSED_ARG(mutexattr); + InitializeCriticalSection(mutex); return 0; } -int pthread_cond_wait(pthread_cond_t *GIT_RESTRICT cond, - pthread_mutex_t *GIT_RESTRICT mutex) +int pthread_mutex_destroy(pthread_mutex_t *mutex) { int ret; - ret = SleepConditionVariableCS(cond, mutex, INFINITE); + ret = CloseHandle(mutex); return -(!ret); } @@ -62,50 +72,6 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex) return 0; } -int pthread_join(pthread_t thread, void **value_ptr) -{ - int ret; - ret = WaitForSingleObject(thread, INFINITE); - if (ret && value_ptr) - GetExitCodeThread(thread, (void*) value_ptr); - return -(!!ret); -} - -int pthread_cond_broadcast(pthread_cond_t *cond) -{ - WakeAllConditionVariable(cond); - return 0; -} - -int pthread_mutex_destroy(pthread_mutex_t *mutex) -{ - int ret; - ret = CloseHandle(mutex); - return -(!ret); -} - -int pthread_cond_destroy(pthread_cond_t *GIT_UNUSED(cond)) -{ - GIT_UNUSED_ARG(cond); - return 0; -} - -int pthread_cond_init(pthread_cond_t *GIT_RESTRICT cond, - const pthread_condattr_t *GIT_RESTRICT GIT_UNUSED(condattr)) -{ - GIT_UNUSED_ARG(condattr); - InitializeConditionVariable(cond); - return 0; -} - -int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex, - const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr)) -{ - GIT_UNUSED_ARG(mutexattr); - InitializeCriticalSection(mutex); - return 0; -} - int pthread_num_processors_np(void) { DWORD_PTR p, s; diff --git a/src/win32/pthread.h b/src/win32/pthread.h index ff694e303..10949f1eb 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -40,7 +40,6 @@ typedef int pthread_mutexattr_t; typedef int pthread_condattr_t; typedef int pthread_attr_t; typedef CRITICAL_SECTION pthread_mutex_t; -typedef CONDITION_VARIABLE pthread_cond_t; typedef HANDLE pthread_t; #define PTHREAD_MUTEX_INITIALIZER {(void*)-1}; @@ -56,12 +55,6 @@ int pthread_mutex_destroy(pthread_mutex_t *); int pthread_mutex_lock(pthread_mutex_t *); int pthread_mutex_unlock(pthread_mutex_t *); -int pthread_cond_init(pthread_cond_t *GIT_RESTRICT, const pthread_condattr_t *GIT_RESTRICT); -int pthread_cond_destroy(pthread_cond_t *); -int pthread_cond_broadcast(pthread_cond_t *); -int pthread_cond_signal(pthread_cond_t *); -int pthread_cond_wait(pthread_cond_t *GIT_RESTRICT, pthread_mutex_t *GIT_RESTRICT); - int pthread_num_processors_np(void); -#endif \ No newline at end of file +#endif diff --git a/tests/t11-sqlite.c b/tests/t11-sqlite.c index 9e9d1b786..ba9065e76 100644 --- a/tests/t11-sqlite.c +++ b/tests/t11-sqlite.c @@ -23,11 +23,14 @@ * Boston, MA 02110-1301, USA. */ #include "test_lib.h" -#include "t03-data.h" + +#ifdef GIT2_SQLITE_BACKEND +#include "t03-data.h" #include "fileops.h" #include "git2/odb_backend.h" + static int cmp_objects(git_rawobj *o1, git_rawobj *o2) { if (o1->type != o2->type) @@ -41,7 +44,6 @@ static int cmp_objects(git_rawobj *o1, git_rawobj *o2) static git_odb *open_sqlite_odb(void) { -#ifdef GIT2_SQLITE_BACKEND git_odb *odb; git_odb_backend *sqlite; @@ -55,9 +57,6 @@ static git_odb *open_sqlite_odb(void) return NULL; return odb; -#else - return NULL; -#endif } #define TEST_WRITE(PTR) {\ @@ -105,7 +104,6 @@ END_TEST BEGIN_SUITE(sqlite) -#ifdef GIT2_SQLITE_BACKEND ADD_TEST(sqlite0); ADD_TEST(sqlite1); ADD_TEST(sqlite2); @@ -113,5 +111,13 @@ BEGIN_SUITE(sqlite) ADD_TEST(sqlite4); ADD_TEST(sqlite5); ADD_TEST(sqlite6); -#endif END_SUITE + +#else /* no sqlite builtin */ +BEGIN_SUITE(sqlite) + /* empty */ +END_SUITE +#endif + + + diff --git a/tests/t13-threads.c b/tests/t13-threads.c new file mode 100644 index 000000000..62ca73ac2 --- /dev/null +++ b/tests/t13-threads.c @@ -0,0 +1,48 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "test_lib.h" +#include "test_helpers.h" +#include "cache.h" + + +typedef struct { + git_cached_obj cached; + unsigned int __dummy; +} ttest_obj; + +void *cache0_thread(void *data) +{ + git_cache *cache = (git_cache*)data; + unsigned int num; + +} + +BEGIN_TEST(cache0, "run several threads polling the cache at the same time") + +END_TEST + +BEGIN_SUITE(threads) + ADD_TEST(cache0); +END_SUITE diff --git a/tests/test_main.c b/tests/test_main.c index 9308b8d45..f2a623a48 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -42,6 +42,7 @@ DECLARE_SUITE(tree); DECLARE_SUITE(refs); DECLARE_SUITE(sqlite); DECLARE_SUITE(repository); +DECLARE_SUITE(threads); static libgit2_suite suite_methods[]= { SUITE_NAME(core), @@ -57,6 +58,7 @@ static libgit2_suite suite_methods[]= { SUITE_NAME(refs), SUITE_NAME(sqlite), SUITE_NAME(repository), + SUITE_NAME(threads), }; #define GIT_SUITE_COUNT (ARRAY_SIZE(suite_methods)) diff --git a/wscript b/wscript index 4e8294c9f..b990e148a 100644 --- a/wscript +++ b/wscript @@ -29,8 +29,10 @@ PPC optimized version (ppc) or the SHA1 functions from OpenSSL (openssl)") help='Force a specific MSVC++ version (7.1, 8.0, 9.0, 10.0), if more than one is installed') opt.add_option('--arch', action='store', default='x86', help='Select target architecture (ia64, x64, x86, x86_amd64, x86_ia64)') - opt.add_option('--without-sqlite', action='store_false', default=True, + opt.add_option('--with-sqlite', action='store_true', default=False, dest='use_sqlite', help='Disable sqlite support') + opt.add_option('--threadsafe', action='store_true', default=False, + help='Make libgit2 thread-safe (requires pthreads)') def configure(conf): @@ -59,7 +61,11 @@ def configure(conf): else: conf.env.PLATFORM = 'unix' - conf.check_cc(lib='pthread', uselib_store='pthread') + + if conf.options.threadsafe: + if conf.env.PLATFORM == 'unix': + conf.check_cc(lib='pthread', uselib_store='pthread') + conf.env.DEFINES += ['GIT_THREADS'] # check for sqlite3 if conf.options.use_sqlite and conf.check_cc(