Commit 74708a81 by lhchavez

Homogenize semantics for atomic-related functions

There were some subtle semantic differences between the various
implementations of atomic functions. Now they behave the same, have
tests and are better documented to avoid this from happening again in
the future.

Of note:

* The semantics chosen for `git_atomic_compare_and_swap` match
  `InterlockedCompareExchangePointer`/`__sync_cal_compare_and_swap` now.
* The semantics chosen for `git_atomic_add` match
  `InterlockedAdd`/`__atomic_add_fetch`.
* `git_atomic_swap` and `git_atomic_load` still have a bit of semantic
  difference with the gcc builtins / msvc interlocked operations, since
  they require an l-value (not a pointer). If desired, this can be
  homogenized.
parent fabacb7c
...@@ -127,7 +127,7 @@ static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file) ...@@ -127,7 +127,7 @@ static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
{ {
int error = 0; int error = 0;
git_attr_file_entry *entry; git_attr_file_entry *entry;
git_attr_file *old = NULL; git_attr_file *oldfile = NULL;
if (!file) if (!file)
return 0; return 0;
...@@ -136,13 +136,13 @@ static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file) ...@@ -136,13 +136,13 @@ static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
return error; return error;
if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL) if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL)
old = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL); oldfile = git_atomic_compare_and_swap(&entry->file[file->source.type], file, NULL);
attr_cache_unlock(cache); attr_cache_unlock(cache);
if (old) { if (oldfile == file) {
GIT_REFCOUNT_OWN(old, NULL); GIT_REFCOUNT_OWN(file, NULL);
git_attr_file__free(old); git_attr_file__free(file);
} }
return error; return error;
...@@ -401,8 +401,7 @@ int git_attr_cache__init(git_repository *repo) ...@@ -401,8 +401,7 @@ int git_attr_cache__init(git_repository *repo)
(ret = git_pool_init(&cache->pool, 1)) < 0) (ret = git_pool_init(&cache->pool, 1)) < 0)
goto cancel; goto cancel;
cache = git_atomic_compare_and_swap(&repo->attrcache, NULL, cache); if (git_atomic_compare_and_swap(&repo->attrcache, NULL, cache) != NULL)
if (cache)
goto cancel; /* raced with another thread, free this but no error */ goto cancel; /* raced with another thread, free this but no error */
git_config_free(cfg); git_config_free(cfg);
......
...@@ -141,18 +141,23 @@ static int diff_driver_funcname(const git_config_entry *entry, void *payload) ...@@ -141,18 +141,23 @@ static int diff_driver_funcname(const git_config_entry *entry, void *payload)
static git_diff_driver_registry *git_repository_driver_registry( static git_diff_driver_registry *git_repository_driver_registry(
git_repository *repo) git_repository *repo)
{ {
if (!repo->diff_drivers) { git_diff_driver_registry *reg = git_atomic_load(repo->diff_drivers), *newreg;
git_diff_driver_registry *reg = git_diff_driver_registry_new(); if (reg)
reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, reg); return reg;
if (reg != NULL) /* if we race, free losing allocation */
git_diff_driver_registry_free(reg);
}
if (!repo->diff_drivers) newreg = git_diff_driver_registry_new();
if (!newreg) {
git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry"); git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry");
return newreg;
return repo->diff_drivers; }
reg = git_atomic_compare_and_swap(&repo->diff_drivers, NULL, newreg);
if (!reg) {
reg = newreg;
} else {
/* if we race, free losing allocation */
git_diff_driver_registry_free(newreg);
}
return reg;
} }
static int diff_driver_alloc( static int diff_driver_alloc(
......
...@@ -1093,8 +1093,7 @@ int git_repository_config__weakptr(git_config **out, git_repository *repo) ...@@ -1093,8 +1093,7 @@ int git_repository_config__weakptr(git_config **out, git_repository *repo)
if (!error) { if (!error) {
GIT_REFCOUNT_OWN(config, repo); GIT_REFCOUNT_OWN(config, repo);
config = git_atomic_compare_and_swap(&repo->_config, NULL, config); if (git_atomic_compare_and_swap(&repo->_config, NULL, config) != NULL) {
if (config != NULL) {
GIT_REFCOUNT_OWN(config, NULL); GIT_REFCOUNT_OWN(config, NULL);
git_config_free(config); git_config_free(config);
} }
...@@ -1164,8 +1163,7 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) ...@@ -1164,8 +1163,7 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo)
return error; return error;
} }
odb = git_atomic_compare_and_swap(&repo->_odb, NULL, odb); if (git_atomic_compare_and_swap(&repo->_odb, NULL, odb) != NULL) {
if (odb != NULL) {
GIT_REFCOUNT_OWN(odb, NULL); GIT_REFCOUNT_OWN(odb, NULL);
git_odb_free(odb); git_odb_free(odb);
} }
...@@ -1209,8 +1207,7 @@ int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) ...@@ -1209,8 +1207,7 @@ int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo)
if (!error) { if (!error) {
GIT_REFCOUNT_OWN(refdb, repo); GIT_REFCOUNT_OWN(refdb, repo);
refdb = git_atomic_compare_and_swap(&repo->_refdb, NULL, refdb); if (git_atomic_compare_and_swap(&repo->_refdb, NULL, refdb) != NULL) {
if (refdb != NULL) {
GIT_REFCOUNT_OWN(refdb, NULL); GIT_REFCOUNT_OWN(refdb, NULL);
git_refdb_free(refdb); git_refdb_free(refdb);
} }
...@@ -1257,8 +1254,7 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo) ...@@ -1257,8 +1254,7 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo)
if (!error) { if (!error) {
GIT_REFCOUNT_OWN(index, repo); GIT_REFCOUNT_OWN(index, repo);
index = git_atomic_compare_and_swap(&repo->_index, NULL, index); if (git_atomic_compare_and_swap(&repo->_index, NULL, index) != NULL) {
if (index != NULL) {
GIT_REFCOUNT_OWN(index, NULL); GIT_REFCOUNT_OWN(index, NULL);
git_index_free(index); git_index_free(index);
} }
......
...@@ -74,6 +74,9 @@ typedef git_atomic32 git_atomic_ssize; ...@@ -74,6 +74,9 @@ typedef git_atomic32 git_atomic_ssize;
# include "unix/pthread.h" # include "unix/pthread.h"
#endif #endif
/*
* Atomically sets the contents of *a to be val.
*/
GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
...@@ -87,6 +90,10 @@ GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val) ...@@ -87,6 +90,10 @@ GIT_INLINE(void) git_atomic32_set(git_atomic32 *a, int val)
#endif #endif
} }
/*
* Atomically increments the contents of *a by 1, and stores the result back into *a.
* @return the result of the operation.
*/
GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
...@@ -100,10 +107,14 @@ GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a) ...@@ -100,10 +107,14 @@ GIT_INLINE(int) git_atomic32_inc(git_atomic32 *a)
#endif #endif
} }
/*
* Atomically adds the contents of *a and addend, and stores the result back into *a.
* @return the result of the operation.
*/
GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
return InterlockedExchangeAdd(&a->val, addend); return InterlockedAdd(&a->val, addend);
#elif defined(GIT_BUILTIN_ATOMIC) #elif defined(GIT_BUILTIN_ATOMIC)
return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST);
#elif defined(GIT_BUILTIN_SYNC) #elif defined(GIT_BUILTIN_SYNC)
...@@ -113,6 +124,10 @@ GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend) ...@@ -113,6 +124,10 @@ GIT_INLINE(int) git_atomic32_add(git_atomic32 *a, int32_t addend)
#endif #endif
} }
/*
* Atomically decrements the contents of *a by 1, and stores the result back into *a.
* @return the result of the operation.
*/
GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
...@@ -126,6 +141,10 @@ GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a) ...@@ -126,6 +141,10 @@ GIT_INLINE(int) git_atomic32_dec(git_atomic32 *a)
#endif #endif
} }
/*
* Atomically gets the contents of *a.
* @return the contents of *a.
*/
GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) GIT_INLINE(int) git_atomic32_get(git_atomic32 *a)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
...@@ -143,16 +162,13 @@ GIT_INLINE(void *) git_atomic__compare_and_swap( ...@@ -143,16 +162,13 @@ GIT_INLINE(void *) git_atomic__compare_and_swap(
void * volatile *ptr, void *oldval, void *newval) void * volatile *ptr, void *oldval, void *newval)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
volatile void *foundval; return InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
return (foundval == oldval) ? oldval : newval;
#elif defined(GIT_BUILTIN_ATOMIC) #elif defined(GIT_BUILTIN_ATOMIC)
bool success = __atomic_compare_exchange(ptr, &oldval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); void *foundval = oldval;
return success ? oldval : newval; __atomic_compare_exchange(ptr, &foundval, &newval, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
return foundval;
#elif defined(GIT_BUILTIN_SYNC) #elif defined(GIT_BUILTIN_SYNC)
volatile void *foundval; return __sync_val_compare_and_swap(ptr, oldval, newval);
foundval = __sync_val_compare_and_swap(ptr, oldval, newval);
return (foundval == oldval) ? oldval : newval;
#else #else
# error "Unsupported architecture for atomic operations" # error "Unsupported architecture for atomic operations"
#endif #endif
...@@ -164,11 +180,11 @@ GIT_INLINE(volatile void *) git_atomic__swap( ...@@ -164,11 +180,11 @@ GIT_INLINE(volatile void *) git_atomic__swap(
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
return InterlockedExchangePointer(ptr, newval); return InterlockedExchangePointer(ptr, newval);
#elif defined(GIT_BUILTIN_ATOMIC) #elif defined(GIT_BUILTIN_ATOMIC)
void * volatile foundval; void * volatile foundval = NULL;
__atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST); __atomic_exchange(ptr, &newval, &foundval, __ATOMIC_SEQ_CST);
return foundval; return foundval;
#elif defined(GIT_BUILTIN_SYNC) #elif defined(GIT_BUILTIN_SYNC)
return __sync_lock_test_and_set(ptr, newval); return (volatile void *)__sync_lock_test_and_set(ptr, newval);
#else #else
# error "Unsupported architecture for atomic operations" # error "Unsupported architecture for atomic operations"
#endif #endif
...@@ -178,9 +194,7 @@ GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) ...@@ -178,9 +194,7 @@ GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
void *newval = NULL, *oldval = NULL; void *newval = NULL, *oldval = NULL;
volatile void *foundval = NULL; return (volatile void *)InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
foundval = InterlockedCompareExchangePointer((volatile PVOID *)ptr, newval, oldval);
return foundval;
#elif defined(GIT_BUILTIN_ATOMIC) #elif defined(GIT_BUILTIN_ATOMIC)
return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST); return (volatile void *)__atomic_load_n(ptr, __ATOMIC_SEQ_CST);
#elif defined(GIT_BUILTIN_SYNC) #elif defined(GIT_BUILTIN_SYNC)
...@@ -192,10 +206,14 @@ GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr) ...@@ -192,10 +206,14 @@ GIT_INLINE(volatile void *) git_atomic__load(void * volatile *ptr)
#ifdef GIT_ARCH_64 #ifdef GIT_ARCH_64
/*
* Atomically adds the contents of *a and addend, and stores the result back into *a.
* @return the result of the operation.
*/
GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
return InterlockedExchangeAdd64(&a->val, addend); return InterlockedAdd64(&a->val, addend);
#elif defined(GIT_BUILTIN_ATOMIC) #elif defined(GIT_BUILTIN_ATOMIC)
return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST); return __atomic_add_fetch(&a->val, addend, __ATOMIC_SEQ_CST);
#elif defined(GIT_BUILTIN_SYNC) #elif defined(GIT_BUILTIN_SYNC)
...@@ -205,6 +223,9 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) ...@@ -205,6 +223,9 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend)
#endif #endif
} }
/*
* Atomically sets the contents of *a to be val.
*/
GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
...@@ -218,6 +239,10 @@ GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val) ...@@ -218,6 +239,10 @@ GIT_INLINE(void) git_atomic64_set(git_atomic64 *a, int64_t val)
#endif #endif
} }
/*
* Atomically gets the contents of *a.
* @return the contents of *a.
*/
GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a)
{ {
#if defined(GIT_WIN32) #if defined(GIT_WIN32)
...@@ -297,11 +322,10 @@ GIT_INLINE(int) git_atomic32_get(git_atomic32 *a) ...@@ -297,11 +322,10 @@ GIT_INLINE(int) git_atomic32_get(git_atomic32 *a)
GIT_INLINE(void *) git_atomic__compare_and_swap( GIT_INLINE(void *) git_atomic__compare_and_swap(
void * volatile *ptr, void *oldval, void *newval) void * volatile *ptr, void *oldval, void *newval)
{ {
if (*ptr == oldval) void *foundval = *ptr;
if (foundval == oldval)
*ptr = newval; *ptr = newval;
else return foundval;
oldval = newval;
return oldval;
} }
GIT_INLINE(volatile void *) git_atomic__swap( GIT_INLINE(volatile void *) git_atomic__swap(
...@@ -339,17 +363,50 @@ GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a) ...@@ -339,17 +363,50 @@ GIT_INLINE(int64_t) git_atomic64_get(git_atomic64 *a)
#endif #endif
/* Atomically replace oldval with newval /*
* @return oldval if it was replaced or newval if it was not * Atomically replace the contents of *ptr (if they are equal to oldval) with
* newval. ptr must point to a pointer or a value that is the same size as a
* pointer. This is semantically compatible with:
*
* #define git_atomic_compare_and_swap(ptr, oldval, newval) \
* ({ \
* void *foundval = *ptr; \
* if (foundval == oldval) \
* *ptr = newval; \
* foundval; \
* })
*
* @return the original contents of *ptr.
*/ */
#define git_atomic_compare_and_swap(P,O,N) \ #define git_atomic_compare_and_swap(ptr, oldval, newval) \
git_atomic__compare_and_swap((void * volatile *)P, O, N) git_atomic__compare_and_swap((void * volatile *)ptr, oldval, newval)
#define git_atomic_swap(ptr, val) \ /*
(void *)git_atomic__swap((void * volatile *)&ptr, val) * Atomically replace the contents of v with newval. v must be the same size as
* a pointer. This is semantically compatible with:
*
* #define git_atomic_swap(v, newval) \
* ({ \
* volatile void *old = v; \
* v = newval; \
* old; \
* })
*
* @return the original contents of v.
*/
#define git_atomic_swap(v, newval) \
(void *)git_atomic__swap((void * volatile *)&(v), newval)
#define git_atomic_load(ptr) \ /*
(void *)git_atomic__load((void * volatile *)&ptr) * Atomically reads the contents of v. v must be the same size as a pointer.
* This is semantically compatible with:
*
* #define git_atomic_load(v) v
*
* @return the contents of v.
*/
#define git_atomic_load(v) \
(void *)git_atomic__load((void * volatile *)&(v))
#if defined(GIT_THREADS) #if defined(GIT_THREADS)
......
#include "clar_libgit2.h"
void test_threads_atomic__atomic32_set(void)
{
git_atomic32 v = {0};
git_atomic32_set(&v, 1);
cl_assert_equal_i(v.val, 1);
}
void test_threads_atomic__atomic32_get(void)
{
git_atomic32 v = {1};
cl_assert_equal_i(git_atomic32_get(&v), 1);
}
void test_threads_atomic__atomic32_inc(void)
{
git_atomic32 v = {0};
cl_assert_equal_i(git_atomic32_inc(&v), 1);
cl_assert_equal_i(v.val, 1);
}
void test_threads_atomic__atomic32_add(void)
{
git_atomic32 v = {0};
cl_assert_equal_i(git_atomic32_add(&v, 1), 1);
cl_assert_equal_i(v.val, 1);
}
void test_threads_atomic__atomic32_dec(void)
{
git_atomic32 v = {1};
cl_assert_equal_i(git_atomic32_dec(&v), 0);
cl_assert_equal_i(v.val, 0);
}
void test_threads_atomic__atomic64_set(void)
{
#ifndef GIT_ARCH_64
cl_skip();
#else
git_atomic64 v = {0};
git_atomic64_set(&v, 1);
cl_assert_equal_i(v.val, 1);
#endif
}
void test_threads_atomic__atomic64_get(void)
{
#ifndef GIT_ARCH_64
cl_skip();
#else
git_atomic64 v = {1};
cl_assert_equal_i(git_atomic64_get(&v), 1);
#endif
}
void test_threads_atomic__atomic64_add(void)
{
#ifndef GIT_ARCH_64
cl_skip();
#else
git_atomic64 v = {0};
cl_assert_equal_i(git_atomic64_add(&v, 1), 1);
cl_assert_equal_i(v.val, 1);
#endif
}
void test_threads_atomic__cas_pointer(void)
{
int *value = NULL;
int newvalue1 = 1, newvalue2 = 2;
/* value is updated */
cl_assert_equal_p(git_atomic_compare_and_swap(&value, NULL, &newvalue1), NULL);
cl_assert_equal_p(value, &newvalue1);
/* value is not updated */
cl_assert_equal_p(git_atomic_compare_and_swap(&value, NULL, &newvalue2), &newvalue1);
cl_assert_equal_p(value, &newvalue1);
}
void test_threads_atomic__cas_intptr(void)
{
intptr_t value = 0;
intptr_t oldvalue;
intptr_t newvalue;
/* value is updated */
oldvalue = 0;
newvalue = 1;
cl_assert_equal_i((intptr_t)git_atomic_compare_and_swap(&value, (void *)oldvalue, (void *)newvalue), 0);
cl_assert_equal_i(value, 1);
/* value is not updated */
oldvalue = 0;
newvalue = 2;
cl_assert_equal_i((intptr_t)git_atomic_compare_and_swap(&value, (void *)oldvalue, (void *)newvalue), 1);
cl_assert_equal_i(value, 1);
}
void test_threads_atomic__swap(void)
{
int *value = NULL;
int newvalue = 1;
cl_assert_equal_p(git_atomic_swap(value, &newvalue), NULL);
cl_assert_equal_p(value, &newvalue);
cl_assert_equal_p(git_atomic_swap(value, NULL), &newvalue);
cl_assert_equal_p(value, NULL);
}
void test_threads_atomic__load_ptr(void)
{
int value = 1;
int *ptr = &value;
cl_assert_equal_p(git_atomic_load(ptr), &value);
}
void test_threads_atomic__load_intptr(void)
{
intptr_t value = 1;
cl_assert_equal_i((intptr_t)git_atomic_load(value), 1);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment