blob: 401d3d9d7e161504bb15423717fc74698e7dd532 [file] [log] [blame]
/* atomic.c : perform atomic initialization
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#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
#define SVN_ATOMIC_START_INIT 1
#define SVN_ATOMIC_INIT_FAILED 2
#define SVN_ATOMIC_INITIALIZED 3
/* 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
!! libraries that are used for generating error messages. */
/* 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);
for (;;)
{
switch (status)
{
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();
}
}
}
/* 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;
}