mirror of https://gitee.com/Nocallback/glibc.git
6 changed files with 363 additions and 4 deletions
@ -0,0 +1,95 @@ |
|||
/* Allocate and initialize an object once, in a thread-safe fashion.
|
|||
Copyright (C) 2018 Free Software Foundation, Inc. |
|||
This file is part of the GNU C Library. |
|||
|
|||
The GNU C Library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2.1 of the License, or (at your option) any later version. |
|||
|
|||
The GNU C Library 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 |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with the GNU C Library; if not, see |
|||
<http://www.gnu.org/licenses/>. */
|
|||
|
|||
#ifndef _ALLOCATE_ONCE_H |
|||
#define _ALLOCATE_ONCE_H |
|||
|
|||
#include <atomic.h> |
|||
|
|||
/* Slow path for allocate_once; see below. */ |
|||
void *__libc_allocate_once_slow (void **__place, |
|||
void *(*__allocate) (void *__closure), |
|||
void (*__deallocate) (void *__closure, |
|||
void *__ptr), |
|||
void *__closure); |
|||
|
|||
/* Return an a pointer to an allocated and initialized data structure.
|
|||
If this function returns a non-NULL value, the caller can assume |
|||
that pointed-to data has been initialized according to the ALLOCATE |
|||
function. |
|||
|
|||
It is expected that callers define an inline helper function which |
|||
adds type safety, like this. |
|||
|
|||
struct foo { ... }; |
|||
struct foo *global_foo; |
|||
static void *allocate_foo (void *closure); |
|||
static void *deallocate_foo (void *closure, void *ptr); |
|||
|
|||
static inline struct foo * |
|||
get_foo (void) |
|||
{ |
|||
return allocate_once (&global_foo, allocate_foo, free_foo, NULL); |
|||
} |
|||
|
|||
(Note that the global_foo variable is initialized to zero.) |
|||
Usage of this helper function looks like this: |
|||
|
|||
struct foo *local_foo = get_foo (); |
|||
if (local_foo == NULL) |
|||
report_allocation_failure (); |
|||
|
|||
allocate_once first performs an acquire MO load on *PLACE. If the |
|||
result is not null, it is returned. Otherwise, ALLOCATE (CLOSURE) |
|||
is called, yielding a value RESULT. If RESULT equals NULL, |
|||
allocate_once returns NULL, and does not modify *PLACE (but another |
|||
thread may concurrently perform an allocation which succeeds, |
|||
updating *PLACE). If RESULT does not equal NULL, the function uses |
|||
a CAS with acquire-release MO to update the NULL value in *PLACE |
|||
with the RESULT value. If it turns out that *PLACE was updated |
|||
concurrently, allocate_once calls DEALLOCATE (CLOSURE, RESULT) to |
|||
undo the effect of ALLOCATE, and returns the new value of *PLACE |
|||
(after an acquire MO load). If DEALLOCATE is NULL, free (RESULT) |
|||
is called instead. |
|||
|
|||
Compared to __libc_once, allocate_once has the advantage that it |
|||
does not need separate space for a control variable, and that it is |
|||
safe with regards to cancellation and other forms of exception |
|||
handling if the supplied callback functions are safe in that |
|||
regard. allocate_once passes a closure parameter to the allocation |
|||
function, too. */ |
|||
static inline void * |
|||
allocate_once (void **__place, void *(*__allocate) (void *__closure), |
|||
void (*__deallocate) (void *__closure, void *__ptr), |
|||
void *__closure) |
|||
{ |
|||
/* Synchronizes with the release MO CAS in
|
|||
__allocate_once_slow. */ |
|||
void *__result = atomic_load_acquire (__place); |
|||
if (__result != NULL) |
|||
return __result; |
|||
else |
|||
return __libc_allocate_once_slow (__place, __allocate, __deallocate, |
|||
__closure); |
|||
} |
|||
|
|||
#ifndef _ISOMAC |
|||
libc_hidden_proto (__libc_allocate_once_slow) |
|||
#endif |
|||
|
|||
#endif /* _ALLOCATE_ONCE_H */ |
|||
@ -0,0 +1,59 @@ |
|||
/* Concurrent allocation and initialization of a pointer.
|
|||
Copyright (C) 2018 Free Software Foundation, Inc. |
|||
This file is part of the GNU C Library. |
|||
|
|||
The GNU C Library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2.1 of the License, or (at your option) any later version. |
|||
|
|||
The GNU C Library 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 |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with the GNU C Library; if not, see |
|||
<http://www.gnu.org/licenses/>. */
|
|||
|
|||
#include <allocate_once.h> |
|||
#include <stdlib.h> |
|||
#include <stdbool.h> |
|||
|
|||
void * |
|||
__libc_allocate_once_slow (void **place, void *(*allocate) (void *closure), |
|||
void (*deallocate) (void *closure, void *ptr), |
|||
void *closure) |
|||
{ |
|||
void *result = allocate (closure); |
|||
if (result == NULL) |
|||
return NULL; |
|||
|
|||
/* This loop implements a strong CAS on *place, with acquire-release
|
|||
MO semantics, from a weak CAS with relaxed-release MO. */ |
|||
while (true) |
|||
{ |
|||
/* Synchronizes with the acquire MO load in allocate_once. */ |
|||
void *expected = NULL; |
|||
if (atomic_compare_exchange_weak_release (place, &expected, result)) |
|||
return result; |
|||
|
|||
/* The failed CAS has relaxed MO semantics, so perform another
|
|||
acquire MO load. */ |
|||
void *other_result = atomic_load_acquire (place); |
|||
if (other_result == NULL) |
|||
/* Spurious failure. Try again. */ |
|||
continue; |
|||
|
|||
/* We lost the race. Free what we allocated and return the
|
|||
other result. */ |
|||
if (deallocate == NULL) |
|||
free (result); |
|||
else |
|||
deallocate (closure, result); |
|||
return other_result; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
libc_hidden_def (__libc_allocate_once_slow) |
|||
@ -0,0 +1,181 @@ |
|||
/* Test the allocate_once function.
|
|||
Copyright (C) 2018 Free Software Foundation, Inc. |
|||
This file is part of the GNU C Library. |
|||
|
|||
The GNU C Library is free software; you can redistribute it and/or |
|||
modify it under the terms of the GNU Lesser General Public |
|||
License as published by the Free Software Foundation; either |
|||
version 2.1 of the License, or (at your option) any later version. |
|||
|
|||
The GNU C Library 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 |
|||
Lesser General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU Lesser General Public |
|||
License along with the GNU C Library; if not, see |
|||
<http://www.gnu.org/licenses/>. */
|
|||
|
|||
#include <allocate_once.h> |
|||
#include <mcheck.h> |
|||
#include <string.h> |
|||
#include <support/check.h> |
|||
#include <support/support.h> |
|||
|
|||
/* Allocate a new string. */ |
|||
static void * |
|||
allocate_string (void *closure) |
|||
{ |
|||
return xstrdup (closure); |
|||
} |
|||
|
|||
/* Allocation and deallocation functions which are not expected to be
|
|||
called. */ |
|||
|
|||
static void * |
|||
allocate_not_called (void *closure) |
|||
{ |
|||
FAIL_EXIT1 ("allocation function called unexpectedly (%p)", closure); |
|||
} |
|||
|
|||
static void |
|||
deallocate_not_called (void *closure, void *ptr) |
|||
{ |
|||
FAIL_EXIT1 ("deallocate function called unexpectedly (%p, %p)", |
|||
closure, ptr); |
|||
} |
|||
|
|||
/* Counter for various function calls. */ |
|||
static int function_called; |
|||
|
|||
/* An allocation function which returns NULL and records that it has
|
|||
been called. */ |
|||
static void * |
|||
allocate_return_null (void *closure) |
|||
{ |
|||
/* The function should only be called once. */ |
|||
TEST_COMPARE (function_called, 0); |
|||
++function_called; |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
/* The following is used to check the retry logic, by causing a fake
|
|||
race condition. */ |
|||
static void *fake_race_place; |
|||
static char fake_race_region[3]; /* To obtain unique addresses. */ |
|||
|
|||
static void * |
|||
fake_race_allocate (void *closure) |
|||
{ |
|||
TEST_VERIFY (closure == &fake_race_region[0]); |
|||
TEST_COMPARE (function_called, 0); |
|||
++function_called; |
|||
/* Fake allocation by another thread. */ |
|||
fake_race_place = &fake_race_region[1]; |
|||
return &fake_race_region[2]; |
|||
} |
|||
|
|||
static void |
|||
fake_race_deallocate (void *closure, void *ptr) |
|||
{ |
|||
/* Check that the pointer returned from fake_race_allocate is
|
|||
deallocated (and not the one stored in fake_race_place). */ |
|||
TEST_VERIFY (ptr == &fake_race_region[2]); |
|||
|
|||
TEST_VERIFY (fake_race_place == &fake_race_region[1]); |
|||
TEST_VERIFY (closure == &fake_race_region[0]); |
|||
TEST_COMPARE (function_called, 1); |
|||
++function_called; |
|||
} |
|||
|
|||
/* Similar to fake_race_allocate, but expects to be paired with free
|
|||
as the deallocation function. */ |
|||
static void * |
|||
fake_race_allocate_for_free (void *closure) |
|||
{ |
|||
TEST_VERIFY (closure == &fake_race_region[0]); |
|||
TEST_COMPARE (function_called, 0); |
|||
++function_called; |
|||
/* Fake allocation by another thread. */ |
|||
fake_race_place = &fake_race_region[1]; |
|||
return xstrdup ("to be freed"); |
|||
} |
|||
|
|||
static int |
|||
do_test (void) |
|||
{ |
|||
mtrace (); |
|||
|
|||
/* Simple allocation. */ |
|||
void *place1 = NULL; |
|||
char *string1 = allocate_once (&place1, allocate_string, |
|||
deallocate_not_called, |
|||
(char *) "test string 1"); |
|||
TEST_VERIFY_EXIT (string1 != NULL); |
|||
TEST_VERIFY (strcmp ("test string 1", string1) == 0); |
|||
/* Second call returns the first pointer, without calling any
|
|||
callbacks. */ |
|||
TEST_VERIFY (string1 |
|||
== allocate_once (&place1, allocate_not_called, |
|||
deallocate_not_called, |
|||
(char *) "test string 1a")); |
|||
|
|||
/* Different place should result in another call. */ |
|||
void *place2 = NULL; |
|||
char *string2 = allocate_once (&place2, allocate_string, |
|||
deallocate_not_called, |
|||
(char *) "test string 2"); |
|||
TEST_VERIFY_EXIT (string2 != NULL); |
|||
TEST_VERIFY (strcmp ("test string 2", string2) == 0); |
|||
TEST_VERIFY (string1 != string2); |
|||
|
|||
/* Check error reporting (NULL return value from the allocation
|
|||
function). */ |
|||
void *place3 = NULL; |
|||
char *string3 = allocate_once (&place3, allocate_return_null, |
|||
deallocate_not_called, NULL); |
|||
TEST_VERIFY (string3 == NULL); |
|||
TEST_COMPARE (function_called, 1); |
|||
|
|||
/* Check that the deallocation function is called if the race is
|
|||
lost. */ |
|||
function_called = 0; |
|||
TEST_VERIFY (allocate_once (&fake_race_place, |
|||
fake_race_allocate, |
|||
fake_race_deallocate, |
|||
&fake_race_region[0]) |
|||
== &fake_race_region[1]); |
|||
TEST_COMPARE (function_called, 2); |
|||
function_called = 3; |
|||
TEST_VERIFY (allocate_once (&fake_race_place, |
|||
fake_race_allocate, |
|||
fake_race_deallocate, |
|||
&fake_race_region[0]) |
|||
== &fake_race_region[1]); |
|||
TEST_COMPARE (function_called, 3); |
|||
|
|||
/* Similar, but this time rely on that free is called. */ |
|||
function_called = 0; |
|||
fake_race_place = NULL; |
|||
TEST_VERIFY (allocate_once (&fake_race_place, |
|||
fake_race_allocate_for_free, |
|||
NULL, |
|||
&fake_race_region[0]) |
|||
== &fake_race_region[1]); |
|||
TEST_COMPARE (function_called, 1); |
|||
function_called = 3; |
|||
TEST_VERIFY (allocate_once (&fake_race_place, |
|||
fake_race_allocate_for_free, |
|||
NULL, |
|||
&fake_race_region[0]) |
|||
== &fake_race_region[1]); |
|||
TEST_COMPARE (function_called, 3); |
|||
|
|||
free (place2); |
|||
free (place1); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
#include <support/test-driver.c> |
|||
Loading…
Reference in new issue