aboutsummaryrefslogtreecommitdiff
path: root/subversion/libsvn_subr/atomic.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_subr/atomic.c')
-rw-r--r--subversion/libsvn_subr/atomic.c203
1 files changed, 168 insertions, 35 deletions
diff --git a/subversion/libsvn_subr/atomic.c b/subversion/libsvn_subr/atomic.c
index b760da4fe156..401d3d9d7e16 100644
--- a/subversion/libsvn_subr/atomic.c
+++ b/subversion/libsvn_subr/atomic.c
@@ -20,8 +20,13 @@
* ====================================================================
*/
+#include <assert.h>
#include <apr_time.h>
+
+#include "svn_pools.h"
+
#include "private/svn_atomic.h"
+#include "private/svn_mutex.h"
/* Magic values for atomic initialization */
#define SVN_ATOMIC_UNINITIALIZED 0
@@ -29,11 +34,21 @@
#define SVN_ATOMIC_INIT_FAILED 2
#define SVN_ATOMIC_INITIALIZED 3
-svn_error_t*
-svn_atomic__init_once(volatile svn_atomic_t *global_status,
- svn_error_t *(*init_func)(void*,apr_pool_t*),
- void *baton,
- apr_pool_t* pool)
+
+/* Baton used by init_funct_t and init_once(). */
+typedef struct init_baton_t init_baton_t;
+
+/* Initialization function wrapper. Hides API details from init_once().
+ The implementation must return FALSE on failure. */
+typedef svn_boolean_t (*init_func_t)(init_baton_t *init_baton);
+
+/*
+ * This is the actual atomic initialization driver.
+ * Returns FALSE on failure.
+ */
+static svn_boolean_t
+init_once(volatile svn_atomic_t *global_status,
+ init_func_t init_func, init_baton_t *init_baton)
{
/* !! Don't use localizable strings in this function, because these
!! might cause deadlocks. This function can be used to initialize
@@ -42,44 +57,162 @@ svn_atomic__init_once(volatile svn_atomic_t *global_status,
/* We have to call init_func exactly once. Because APR
doesn't have statically-initialized mutexes, we implement a poor
man's spinlock using svn_atomic_cas. */
+
svn_atomic_t status = svn_atomic_cas(global_status,
SVN_ATOMIC_START_INIT,
SVN_ATOMIC_UNINITIALIZED);
- if (status == SVN_ATOMIC_UNINITIALIZED)
+ for (;;)
{
- svn_error_t *err = init_func(baton, pool);
- if (err)
+ switch (status)
{
-#if APR_HAS_THREADS
- /* Tell other threads that the initialization failed. */
- svn_atomic_cas(global_status,
- SVN_ATOMIC_INIT_FAILED,
- SVN_ATOMIC_START_INIT);
-#endif
- return svn_error_create(SVN_ERR_ATOMIC_INIT_FAILURE, err,
- "Couldn't perform atomic initialization");
+ case SVN_ATOMIC_UNINITIALIZED:
+ {
+ const svn_boolean_t result = init_func(init_baton);
+ const svn_atomic_t init_state = (result
+ ? SVN_ATOMIC_INITIALIZED
+ : SVN_ATOMIC_INIT_FAILED);
+
+ svn_atomic_cas(global_status, init_state,
+ SVN_ATOMIC_START_INIT);
+ return result;
+ }
+
+ case SVN_ATOMIC_START_INIT:
+ /* Wait for the init function to complete. */
+ apr_sleep(APR_USEC_PER_SEC / 1000);
+ status = svn_atomic_cas(global_status,
+ SVN_ATOMIC_UNINITIALIZED,
+ SVN_ATOMIC_UNINITIALIZED);
+ continue;
+
+ case SVN_ATOMIC_INIT_FAILED:
+ return FALSE;
+
+ case SVN_ATOMIC_INITIALIZED:
+ return TRUE;
+
+ default:
+ /* Something went seriously wrong with the atomic operations. */
+ abort();
}
- svn_atomic_cas(global_status,
- SVN_ATOMIC_INITIALIZED,
- SVN_ATOMIC_START_INIT);
- }
-#if APR_HAS_THREADS
- /* Wait for whichever thread is performing initialization to finish. */
- /* XXX FIXME: Should we have a maximum wait here, like we have in
- the Windows file IO spinner? */
- else while (status != SVN_ATOMIC_INITIALIZED)
- {
- if (status == SVN_ATOMIC_INIT_FAILED)
- return svn_error_create(SVN_ERR_ATOMIC_INIT_FAILURE, NULL,
- "Couldn't perform atomic initialization");
-
- apr_sleep(APR_USEC_PER_SEC / 1000);
- status = svn_atomic_cas(global_status,
- SVN_ATOMIC_UNINITIALIZED,
- SVN_ATOMIC_UNINITIALIZED);
}
-#endif /* APR_HAS_THREADS */
+}
+
+
+/* This baton structure is used by the two flavours of init-once APIs
+ to hide their differences from the init_once() driver. Each private
+ API uses only selected parts of the baton.
+
+ No part of this structure changes unless a wrapped init function is
+ actually invoked by init_once().
+*/
+struct init_baton_t
+{
+ /* Used only by svn_atomic__init_once()/err_init_func_wrapper() */
+ svn_atomic__err_init_func_t err_init_func;
+ svn_error_t *err;
+ apr_pool_t *pool;
+
+ /* Used only by svn_atomic__init_no_error()/str_init_func_wrapper() */
+ svn_atomic__str_init_func_t str_init_func;
+ const char *errstr;
+ /* Used by both pairs of functions */
+ void *baton;
+};
+
+/* Wrapper for the svn_atomic__init_once init function. */
+static svn_boolean_t err_init_func_wrapper(init_baton_t *init_baton)
+{
+ init_baton->err = init_baton->err_init_func(init_baton->baton,
+ init_baton->pool);
+ return (init_baton->err == SVN_NO_ERROR);
+}
+
+svn_error_t *
+svn_atomic__init_once(volatile svn_atomic_t *global_status,
+ svn_atomic__err_init_func_t err_init_func,
+ void *baton,
+ apr_pool_t* pool)
+{
+ init_baton_t init_baton;
+ init_baton.err_init_func = err_init_func;
+ init_baton.err = NULL;
+ init_baton.pool = pool;
+ init_baton.baton = baton;
+
+ if (init_once(global_status, err_init_func_wrapper, &init_baton))
+ return SVN_NO_ERROR;
+
+ return svn_error_create(SVN_ERR_ATOMIC_INIT_FAILURE, init_baton.err,
+ "Couldn't perform atomic initialization");
+}
+
+
+/* Wrapper for the svn_atomic__init_no_error init function. */
+static svn_boolean_t str_init_func_wrapper(init_baton_t *init_baton)
+{
+ init_baton->errstr = init_baton->str_init_func(init_baton->baton);
+ return (init_baton->errstr == NULL);
+}
+
+const char *
+svn_atomic__init_once_no_error(volatile svn_atomic_t *global_status,
+ svn_atomic__str_init_func_t str_init_func,
+ void *baton)
+{
+ init_baton_t init_baton;
+ init_baton.str_init_func = str_init_func;
+ init_baton.errstr = NULL;
+ init_baton.baton = baton;
+
+ if (init_once(global_status, str_init_func_wrapper, &init_baton))
+ return NULL;
+
+ /* Our init function wrapper may not have been called; make sure
+ that we return generic error message in that case. */
+ if (!init_baton.errstr)
+ return "Couldn't perform atomic initialization";
+ else
+ return init_baton.errstr;
+}
+
+/* The process-global counter that we use to produce process-wide unique
+ * values. Since APR has no 64 bit atomics, all access to this will be
+ * serialized through COUNTER_MUTEX. */
+static apr_uint64_t uniqiue_counter = 0;
+
+/* The corresponding mutex and initialization state. */
+static volatile svn_atomic_t counter_status = SVN_ATOMIC_UNINITIALIZED;
+static svn_mutex__t *counter_mutex = NULL;
+
+/* svn_atomic__err_init_func_t implementation that initializes COUNTER_MUTEX.
+ * Note that neither argument will be used and should be NULL. */
+static svn_error_t *
+init_unique_counter(void *null_baton,
+ apr_pool_t *null_pool)
+{
+ /* COUNTER_MUTEX is global, so it needs to live in a global pool.
+ * APR also makes those thread-safe by default. */
+ SVN_ERR(svn_mutex__init(&counter_mutex, TRUE, svn_pool_create(NULL)));
+ return SVN_NO_ERROR;
+}
+
+/* Read and increment UNIQIUE_COUNTER. Return the new value in *VALUE.
+ * Call this function only while having acquired the COUNTER_MUTEX. */
+static svn_error_t *
+read_unique_counter(apr_uint64_t *value)
+{
+ *value = ++uniqiue_counter;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_atomic__unique_counter(apr_uint64_t *value)
+{
+ SVN_ERR(svn_atomic__init_once(&counter_status, init_unique_counter, NULL,
+ NULL));
+ SVN_MUTEX__WITH_LOCK(counter_mutex, read_unique_counter(value));
return SVN_NO_ERROR;
}