1/* atomic.c : perform atomic initialization
2 *
3 * ====================================================================
4 *    Licensed to the Apache Software Foundation (ASF) under one
5 *    or more contributor license agreements.  See the NOTICE file
6 *    distributed with this work for additional information
7 *    regarding copyright ownership.  The ASF licenses this file
8 *    to you under the Apache License, Version 2.0 (the
9 *    "License"); you may not use this file except in compliance
10 *    with the License.  You may obtain a copy of the License at
11 *
12 *      http://www.apache.org/licenses/LICENSE-2.0
13 *
14 *    Unless required by applicable law or agreed to in writing,
15 *    software distributed under the License is distributed on an
16 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 *    KIND, either express or implied.  See the License for the
18 *    specific language governing permissions and limitations
19 *    under the License.
20 * ====================================================================
21 */
22
23#include <assert.h>
24#include <apr_time.h>
25
26#include "svn_pools.h"
27
28#include "private/svn_atomic.h"
29#include "private/svn_mutex.h"
30
31/* Magic values for atomic initialization */
32#define SVN_ATOMIC_UNINITIALIZED 0
33#define SVN_ATOMIC_START_INIT    1
34#define SVN_ATOMIC_INIT_FAILED   2
35#define SVN_ATOMIC_INITIALIZED   3
36
37
38/* Baton used by init_funct_t and init_once(). */
39typedef struct init_baton_t init_baton_t;
40
41/* Initialization function wrapper. Hides API details from init_once().
42   The implementation must return FALSE on failure. */
43typedef svn_boolean_t (*init_func_t)(init_baton_t *init_baton);
44
45/*
46 * This is the actual atomic initialization driver.
47 * Returns FALSE on failure.
48 */
49static svn_boolean_t
50init_once(volatile svn_atomic_t *global_status,
51          init_func_t init_func, init_baton_t *init_baton)
52{
53  /* !! Don't use localizable strings in this function, because these
54     !! might cause deadlocks. This function can be used to initialize
55     !! libraries that are used for generating error messages. */
56
57  /* We have to call init_func exactly once.  Because APR
58     doesn't have statically-initialized mutexes, we implement a poor
59     man's spinlock using svn_atomic_cas. */
60
61  svn_atomic_t status = svn_atomic_cas(global_status,
62                                       SVN_ATOMIC_START_INIT,
63                                       SVN_ATOMIC_UNINITIALIZED);
64
65  for (;;)
66    {
67      switch (status)
68        {
69        case SVN_ATOMIC_UNINITIALIZED:
70          {
71            const svn_boolean_t result = init_func(init_baton);
72            const svn_atomic_t init_state = (result
73                                             ? SVN_ATOMIC_INITIALIZED
74                                             : SVN_ATOMIC_INIT_FAILED);
75
76            svn_atomic_cas(global_status, init_state,
77                           SVN_ATOMIC_START_INIT);
78            return result;
79          }
80
81        case SVN_ATOMIC_START_INIT:
82          /* Wait for the init function to complete. */
83          apr_sleep(APR_USEC_PER_SEC / 1000);
84          status = svn_atomic_cas(global_status,
85                                  SVN_ATOMIC_UNINITIALIZED,
86                                  SVN_ATOMIC_UNINITIALIZED);
87          continue;
88
89        case SVN_ATOMIC_INIT_FAILED:
90          return FALSE;
91
92        case SVN_ATOMIC_INITIALIZED:
93          return TRUE;
94
95        default:
96          /* Something went seriously wrong with the atomic operations. */
97          abort();
98        }
99    }
100}
101
102
103/* This baton structure is used by the two flavours of init-once APIs
104   to hide their differences from the init_once() driver. Each private
105   API uses only selected parts of the baton.
106
107   No part of this structure changes unless a wrapped init function is
108   actually invoked by init_once().
109*/
110struct init_baton_t
111{
112  /* Used only by svn_atomic__init_once()/err_init_func_wrapper() */
113  svn_atomic__err_init_func_t err_init_func;
114  svn_error_t *err;
115  apr_pool_t *pool;
116
117  /* Used only by svn_atomic__init_no_error()/str_init_func_wrapper() */
118  svn_atomic__str_init_func_t str_init_func;
119  const char *errstr;
120
121  /* Used by both pairs of functions */
122  void *baton;
123};
124
125/* Wrapper for the svn_atomic__init_once init function. */
126static svn_boolean_t err_init_func_wrapper(init_baton_t *init_baton)
127{
128  init_baton->err = init_baton->err_init_func(init_baton->baton,
129                                              init_baton->pool);
130  return (init_baton->err == SVN_NO_ERROR);
131}
132
133svn_error_t *
134svn_atomic__init_once(volatile svn_atomic_t *global_status,
135                      svn_atomic__err_init_func_t err_init_func,
136                      void *baton,
137                      apr_pool_t* pool)
138{
139  init_baton_t init_baton;
140  init_baton.err_init_func = err_init_func;
141  init_baton.err = NULL;
142  init_baton.pool = pool;
143  init_baton.baton = baton;
144
145  if (init_once(global_status, err_init_func_wrapper, &init_baton))
146    return SVN_NO_ERROR;
147
148  return svn_error_create(SVN_ERR_ATOMIC_INIT_FAILURE, init_baton.err,
149                          "Couldn't perform atomic initialization");
150}
151
152
153/* Wrapper for the svn_atomic__init_no_error init function. */
154static svn_boolean_t str_init_func_wrapper(init_baton_t *init_baton)
155{
156  init_baton->errstr = init_baton->str_init_func(init_baton->baton);
157  return (init_baton->errstr == NULL);
158}
159
160const char *
161svn_atomic__init_once_no_error(volatile svn_atomic_t *global_status,
162                               svn_atomic__str_init_func_t str_init_func,
163                               void *baton)
164{
165  init_baton_t init_baton;
166  init_baton.str_init_func = str_init_func;
167  init_baton.errstr = NULL;
168  init_baton.baton = baton;
169
170  if (init_once(global_status, str_init_func_wrapper, &init_baton))
171    return NULL;
172
173  /* Our init function wrapper may not have been called; make sure
174     that we return generic error message in that case. */
175  if (!init_baton.errstr)
176    return "Couldn't perform atomic initialization";
177  else
178    return init_baton.errstr;
179}
180
181/* The process-global counter that we use to produce process-wide unique
182 * values.  Since APR has no 64 bit atomics, all access to this will be
183 * serialized through COUNTER_MUTEX. */
184static apr_uint64_t uniqiue_counter = 0;
185
186/* The corresponding mutex and initialization state. */
187static volatile svn_atomic_t counter_status = SVN_ATOMIC_UNINITIALIZED;
188static svn_mutex__t *counter_mutex = NULL;
189
190/* svn_atomic__err_init_func_t implementation that initializes COUNTER_MUTEX.
191 * Note that neither argument will be used and should be NULL. */
192static svn_error_t *
193init_unique_counter(void *null_baton,
194                    apr_pool_t *null_pool)
195{
196  /* COUNTER_MUTEX is global, so it needs to live in a global pool.
197   * APR also makes those thread-safe by default. */
198  SVN_ERR(svn_mutex__init(&counter_mutex, TRUE, svn_pool_create(NULL)));
199  return SVN_NO_ERROR;
200}
201
202/* Read and increment UNIQIUE_COUNTER. Return the new value in *VALUE.
203 * Call this function only while having acquired the COUNTER_MUTEX. */
204static svn_error_t *
205read_unique_counter(apr_uint64_t *value)
206{
207  *value = ++uniqiue_counter;
208  return SVN_NO_ERROR;
209}
210
211svn_error_t *
212svn_atomic__unique_counter(apr_uint64_t *value)
213{
214  SVN_ERR(svn_atomic__init_once(&counter_status, init_unique_counter, NULL,
215                                NULL));
216  SVN_MUTEX__WITH_LOCK(counter_mutex, read_unique_counter(value));
217  return SVN_NO_ERROR;
218}
219