Commit 9ce3dae6 authored by Vladislav Vaintroub's avatar Vladislav Vaintroub

merge

parents 7377c50c 23159440
......@@ -1750,23 +1750,29 @@ then
fi
AC_ARG_WITH([atomic-ops],
AC_HELP_STRING([--with-atomic-ops=rwlocks|smp|up],
AS_HELP_STRING([--with-atomic-ops=rwlocks|smp|up],
[Implement atomic operations using pthread rwlocks or atomic CPU
instructions for multi-processor (default) or uniprocessor
configuration]), , [with_atomic_ops=smp])
instructions for multi-processor or uniprocessor
configuration. By default gcc built-in sync functions are used,
if available and 'smp' configuration otherwise.]))
case "$with_atomic_ops" in
"up") AC_DEFINE([MY_ATOMIC_MODE_DUMMY], [1],
[Assume single-CPU mode, no concurrency]) ;;
"rwlocks") AC_DEFINE([MY_ATOMIC_MODE_RWLOCKS], [1],
[Use pthread rwlocks for atomic ops]) ;;
"smp") ;;
"")
;;
*) AC_MSG_ERROR(["$with_atomic_ops" is not a valid value for --with-atomic-ops]) ;;
esac
AC_CACHE_CHECK([whether the compiler provides atomic builtins],
[mysql_cv_gcc_atomic_builtins], [AC_TRY_RUN([
int main()
{
[mysql_cv_gcc_atomic_builtins],
[AC_RUN_IFELSE(
[AC_LANG_PROGRAM(
[
],
[[
int foo= -10; int bar= 10;
if (!__sync_fetch_and_add(&foo, bar) || foo)
return -1;
......@@ -1777,22 +1783,25 @@ AC_CACHE_CHECK([whether the compiler provides atomic builtins],
if (bar)
return -1;
return 0;
}
], [mysql_cv_gcc_atomic_builtins=yes],
]]
)],
[mysql_cv_gcc_atomic_builtins=yes],
[mysql_cv_gcc_atomic_builtins=no],
[mysql_cv_gcc_atomic_builtins=no])])
[mysql_cv_gcc_atomic_builtins=no]
)])
if test "x$mysql_cv_gcc_atomic_builtins" = xyes; then
AC_DEFINE(HAVE_GCC_ATOMIC_BUILTINS, 1,
[Define to 1 if compiler provides atomic builtins.])
fi
AC_CACHE_CHECK([whether the OS provides atomic_* functions like Solaris],
[mysql_cv_solaris_atomic], [AC_TRY_RUN([
#include <atomic.h>
int
main()
{
[mysql_cv_solaris_atomic],
[AC_RUN_IFELSE(
[AC_LANG_PROGRAM(
[
#include <atomic.h>
]
[[
int foo = -10; int bar = 10;
if (atomic_add_int_nv((uint_t *)&foo, bar) || foo)
return -1;
......@@ -1803,11 +1812,12 @@ main()
if (bar)
return -1;
return 0;
}
], [mysql_cv_solaris_atomic=yes],
]]
)],
[mysql_cv_solaris_atomic=yes],
[mysql_cv_solaris_atomic=no],
[mysql_cv_solaris_atomic=no])])
[mysql_cv_solaris_atomic=no]
)])
if test "x$mysql_cv_solaris_atomic" = xyes; then
AC_DEFINE(HAVE_SOLARIS_ATOMIC, 1,
[Define to 1 if OS provides atomic_* functions like Solaris.])
......@@ -2104,7 +2114,7 @@ AC_CHECK_FUNCS(alarm bcmp bfill bmove bsearch bzero \
pthread_setprio_np pthread_setschedparam pthread_sigmask readlink \
realpath rename rint rwlock_init setupterm \
shmget shmat shmdt shmctl sigaction sigemptyset sigaddset \
sighold sigset sigthreadmask port_create sleep \
sighold sigset sigthreadmask port_create sleep thr_yield \
snprintf socket stpcpy strcasecmp strerror strsignal strnlen strpbrk strstr \
strtol strtoll strtoul strtoull tell tempnam thr_setconcurrency vidattr \
posix_fallocate backtrace backtrace_symbols backtrace_symbols_fd)
......
......@@ -30,7 +30,7 @@ pkginclude_HEADERS = $(HEADERS_ABI) my_dbug.h m_string.h my_sys.h \
m_ctype.h my_attribute.h $(HEADERS_GEN_CONFIGURE) \
$(HEADERS_GEN_MAKE) probes_mysql.h probes_mysql_nodtrace.h
noinst_HEADERS = config-win.h config-netware.h my_bit.h \
noinst_HEADERS = config-win.h config-netware.h lf.h my_bit.h \
heap.h my_bitmap.h my_uctype.h \
myisam.h myisampack.h myisammrg.h ft_global.h\
mysys_err.h my_base.h help_start.h help_end.h \
......@@ -39,9 +39,9 @@ noinst_HEADERS = config-win.h config-netware.h my_bit.h \
thr_lock.h t_ctype.h violite.h my_md5.h base64.h \
my_handler.h my_time.h service_versions.h \
my_vle.h my_user.h my_atomic.h atomic/nolock.h \
atomic/rwlock.h atomic/x86-gcc.h atomic/x86-msvc.h \
atomic/solaris.h \
atomic/gcc_builtins.h my_libwrap.h my_stacktrace.h
atomic/rwlock.h atomic/x86-gcc.h atomic/generic-msvc.h \
atomic/gcc_builtins.h my_libwrap.h my_stacktrace.h \
atomic/solaris.h
EXTRA_DIST = mysql.h.pp mysql/plugin.h.pp probes_mysql.d.base \
CMakeLists.txt
......
......@@ -18,7 +18,7 @@
#define make_atomic_add_body(S) \
v= __sync_fetch_and_add(a, v);
#define make_atomic_swap_body(S) \
#define make_atomic_fas_body(S) \
v= __sync_lock_test_and_set(a, v);
#define make_atomic_cas_body(S) \
int ## S sav; \
......@@ -28,7 +28,10 @@
#ifdef MY_ATOMIC_MODE_DUMMY
#define make_atomic_load_body(S) ret= *a
#define make_atomic_store_body(S) *a= v
#define MY_ATOMIC_MODE "gcc-builtins-up"
#else
#define MY_ATOMIC_MODE "gcc-builtins-smp"
#define make_atomic_load_body(S) \
ret= __sync_fetch_and_or(a, 0);
#define make_atomic_store_body(S) \
......
/* Copyright (C) 2006-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifndef _atomic_h_cleanup_
#define _atomic_h_cleanup_ "atomic/generic-msvc.h"
/*
We don't implement anything specific for MY_ATOMIC_MODE_DUMMY, always use
intrinsics.
8 and 16-bit atomics are not implemented, but it can be done if necessary.
*/
#undef MY_ATOMIC_HAS_8_16
/*
x86 compilers (both VS2003 or VS2005) never use instrinsics, but generate
function calls to kernel32 instead, even in the optimized build.
We force intrinsics as described in MSDN documentation for
_InterlockedCompareExchange.
*/
#ifdef _M_IX86
#if (_MSC_VER >= 1500)
#include <intrin.h>
#else
C_MODE_START
/*Visual Studio 2003 and earlier do not have prototypes for atomic intrinsics*/
LONG _InterlockedExchange (LONG volatile *Target,LONG Value);
LONG _InterlockedCompareExchange (LONG volatile *Target, LONG Value, LONG Comp);
LONG _InterlockedExchangeAdd (LONG volatile *Addend, LONG Value);
C_MODE_END
#pragma intrinsic(_InterlockedExchangeAdd)
#pragma intrinsic(_InterlockedCompareExchange)
#pragma intrinsic(_InterlockedExchange)
#endif
#define InterlockedExchange _InterlockedExchange
#define InterlockedExchangeAdd _InterlockedExchangeAdd
#define InterlockedCompareExchange _InterlockedCompareExchange
/*
No need to do something special for InterlockedCompareExchangePointer
as it is a #define to InterlockedCompareExchange. The same applies to
InterlockedExchangePointer.
*/
#endif /*_M_IX86*/
#define MY_ATOMIC_MODE "msvc-intrinsics"
#define IL_EXCHG_ADD32(X,Y) InterlockedExchangeAdd((volatile LONG *)(X),(Y))
#define IL_COMP_EXCHG32(X,Y,Z) InterlockedCompareExchange((volatile LONG *)(X),(Y),(Z))
#define IL_COMP_EXCHGptr InterlockedCompareExchangePointer
#define IL_EXCHG32(X,Y) InterlockedExchange((volatile LONG *)(X),(Y))
#define IL_EXCHGptr InterlockedExchangePointer
#define make_atomic_add_body(S) \
v= IL_EXCHG_ADD ## S (a, v)
#define make_atomic_cas_body(S) \
int ## S initial_cmp= *cmp; \
int ## S initial_a= IL_COMP_EXCHG ## S (a, set, initial_cmp); \
if (!(ret= (initial_a == initial_cmp))) *cmp= initial_a;
#define make_atomic_swap_body(S) \
v= IL_EXCHG ## S (a, v)
#define make_atomic_load_body(S) \
ret= 0; /* avoid compiler warning */ \
ret= IL_COMP_EXCHG ## S (a, ret, ret);
/*
my_yield_processor (equivalent of x86 PAUSE instruction) should be used
to improve performance on hyperthreaded CPUs. Intel recommends to use it in
spin loops also on non-HT machines to reduce power consumption (see e.g
http://softwarecommunity.intel.com/articles/eng/2004.htm)
Running benchmarks for spinlocks implemented with InterlockedCompareExchange
and YieldProcessor shows that much better performance is achieved by calling
YieldProcessor in a loop - that is, yielding longer. On Intel boxes setting
loop count in the range 200-300 brought best results.
*/
#ifndef YIELD_LOOPS
#define YIELD_LOOPS 200
#endif
static __inline int my_yield_processor()
{
int i;
for(i=0; i<YIELD_LOOPS; i++)
{
#if (_MSC_VER <= 1310)
/* On older compilers YieldProcessor is not available, use inline assembly*/
__asm { rep nop }
#else
YieldProcessor();
#endif
}
return 1;
}
#define LF_BACKOFF my_yield_processor()
#else /* cleanup */
#undef IL_EXCHG_ADD32
#undef IL_COMP_EXCHG32
#undef IL_COMP_EXCHGptr
#undef IL_EXCHG32
#undef IL_EXCHGptr
#endif
......@@ -16,43 +16,36 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#if defined(__i386__) || defined(_M_IX86) || defined(HAVE_GCC_ATOMIC_BUILTINS)
#ifdef MY_ATOMIC_MODE_DUMMY
# define LOCK ""
#else
# define LOCK "lock"
#endif
#ifdef HAVE_GCC_ATOMIC_BUILTINS
#include "gcc_builtins.h"
#elif __GNUC__
#include "x86-gcc.h"
#elif defined(_MSC_VER)
#include "x86-msvc.h"
#endif
#if defined(__i386__) || defined(_MSC_VER) || defined(__x86_64__) \
|| defined(HAVE_GCC_ATOMIC_BUILTINS)
# ifdef MY_ATOMIC_MODE_DUMMY
# define LOCK_prefix ""
# else
# define LOCK_prefix "lock"
# endif
# ifdef HAVE_GCC_ATOMIC_BUILTINS
# include "gcc_builtins.h"
# elif __GNUC__
# include "x86-gcc.h"
# elif defined(_MSC_VER)
# include "generic-msvc.h"
# endif
#elif defined(HAVE_SOLARIS_ATOMIC)
#include "solaris.h"
#endif /* __i386__ || _M_IX86 || HAVE_GCC_ATOMIC_BUILTINS */
#endif
#if defined(make_atomic_cas_body) || defined(MY_ATOMICS_MADE)
/*
* We have atomics that require no locking
*/
#define MY_ATOMIC_NOLOCK
#ifdef __SUNPRO_C
/*
* Sun Studio 12 (and likely earlier) does not accept a typedef struct {}
*/
typedef char my_atomic_rwlock_t;
#else
typedef struct { } my_atomic_rwlock_t;
#endif
Type not used so minimal size (emptry struct has different size between C
and C++, zero-length array is gcc-specific).
*/
typedef char my_atomic_rwlock_t __attribute__ ((unused));
#define my_atomic_rwlock_destroy(name)
#define my_atomic_rwlock_init(name)
#define my_atomic_rwlock_rdlock(name)
......
#ifndef ATOMIC_RWLOCK_INCLUDED
#define ATOMIC_RWLOCK_INCLUDED
/* Copyright (C) 2006 MySQL AB
/* Copyright (C) 2006 MySQL AB, 2009 Sun Microsystems, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -16,7 +16,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
typedef struct {pthread_rwlock_t rw;} my_atomic_rwlock_t;
#define MY_ATOMIC_MODE_RWLOCKS 1
#ifdef MY_ATOMIC_MODE_DUMMY
/*
......@@ -26,6 +26,9 @@ typedef struct {pthread_rwlock_t rw;} my_atomic_rwlock_t;
implementations (another way is to run a UP build on an SMP box).
*/
#warning MY_ATOMIC_MODE_DUMMY and MY_ATOMIC_MODE_RWLOCKS are incompatible
typedef char my_atomic_rwlock_t;
#define my_atomic_rwlock_destroy(name)
#define my_atomic_rwlock_init(name)
#define my_atomic_rwlock_rdlock(name)
......@@ -33,18 +36,63 @@ typedef struct {pthread_rwlock_t rw;} my_atomic_rwlock_t;
#define my_atomic_rwlock_rdunlock(name)
#define my_atomic_rwlock_wrunlock(name)
#define MY_ATOMIC_MODE "dummy (non-atomic)"
#else
#define my_atomic_rwlock_destroy(name) pthread_rwlock_destroy(& (name)->rw)
#define my_atomic_rwlock_init(name) pthread_rwlock_init(& (name)->rw, 0)
#define my_atomic_rwlock_rdlock(name) pthread_rwlock_rdlock(& (name)->rw)
#define my_atomic_rwlock_wrlock(name) pthread_rwlock_wrlock(& (name)->rw)
#define my_atomic_rwlock_rdunlock(name) pthread_rwlock_unlock(& (name)->rw)
#define my_atomic_rwlock_wrunlock(name) pthread_rwlock_unlock(& (name)->rw)
#define MY_ATOMIC_MODE "rwlocks"
#else /* not MY_ATOMIC_MODE_DUMMY */
typedef struct {pthread_mutex_t rw;} my_atomic_rwlock_t;
#ifndef SAFE_MUTEX
/*
we're using read-write lock macros but map them to mutex locks, and they're
faster. Still, having semantically rich API we can change the
underlying implementation, if necessary.
*/
#define my_atomic_rwlock_destroy(name) pthread_mutex_destroy(& (name)->rw)
#define my_atomic_rwlock_init(name) pthread_mutex_init(& (name)->rw, 0)
#define my_atomic_rwlock_rdlock(name) pthread_mutex_lock(& (name)->rw)
#define my_atomic_rwlock_wrlock(name) pthread_mutex_lock(& (name)->rw)
#define my_atomic_rwlock_rdunlock(name) pthread_mutex_unlock(& (name)->rw)
#define my_atomic_rwlock_wrunlock(name) pthread_mutex_unlock(& (name)->rw)
#else /* SAFE_MUTEX */
/*
SAFE_MUTEX pollutes the compiling name space with macros
that alter pthread_mutex_t, pthread_mutex_init, etc.
Atomic operations should never use the safe mutex wrappers.
Unfortunately, there is no way to have both:
- safe mutex macros expanding pthread_mutex_lock to safe_mutex_lock
- my_atomic macros expanding to unmodified pthread_mutex_lock
inlined in the same compilation unit.
So, in case of SAFE_MUTEX, a function call is required.
Given that SAFE_MUTEX is a debugging facility,
this extra function call is not a performance concern for
production builds.
*/
C_MODE_START
extern void plain_pthread_mutex_init(safe_mutex_t *);
extern void plain_pthread_mutex_destroy(safe_mutex_t *);
extern void plain_pthread_mutex_lock(safe_mutex_t *);
extern void plain_pthread_mutex_unlock(safe_mutex_t *);
C_MODE_END
#define my_atomic_rwlock_destroy(name) plain_pthread_mutex_destroy(&(name)->rw)
#define my_atomic_rwlock_init(name) plain_pthread_mutex_init(&(name)->rw)
#define my_atomic_rwlock_rdlock(name) plain_pthread_mutex_lock(&(name)->rw)
#define my_atomic_rwlock_wrlock(name) plain_pthread_mutex_lock(&(name)->rw)
#define my_atomic_rwlock_rdunlock(name) plain_pthread_mutex_unlock(&(name)->rw)
#define my_atomic_rwlock_wrunlock(name) plain_pthread_mutex_unlock(&(name)->rw)
#endif /* SAFE_MUTEX */
#define MY_ATOMIC_MODE "mutex"
#ifndef MY_ATOMIC_MODE_RWLOCKS
#define MY_ATOMIC_MODE_RWLOCKS 1
#endif
#endif
#define make_atomic_add_body(S) int ## S sav; sav= *a; *a+= v; v=sav;
#define make_atomic_swap_body(S) int ## S sav; sav= *a; *a= v; v=sav;
#define make_atomic_fas_body(S) int ## S sav; sav= *a; *a= v; v=sav;
#define make_atomic_cas_body(S) if ((ret= (*a == *cmp))) *a= set; else *cmp=*a;
#define make_atomic_load_body(S) ret= *a;
#define make_atomic_store_body(S) *a= v;
......
......@@ -186,25 +186,25 @@ my_atomic_storeptr(void * volatile *a, void *v)
/* ------------------------------------------------------------------------ */
STATIC_INLINE int8
my_atomic_swap8(int8 volatile *a, int8 v)
my_atomic_fas8(int8 volatile *a, int8 v)
{
return ((int8) atomic_swap_8((volatile uint8_t *)a, (uint8_t)v));
}
STATIC_INLINE int16
my_atomic_swap16(int16 volatile *a, int16 v)
my_atomic_fas16(int16 volatile *a, int16 v)
{
return ((int16) atomic_swap_16((volatile uint16_t *)a, (uint16_t)v));
}
STATIC_INLINE int32
my_atomic_swap32(int32 volatile *a, int32 v)
my_atomic_fas32(int32 volatile *a, int32 v)
{
return ((int32) atomic_swap_32((volatile uint32_t *)a, (uint32_t)v));
}
STATIC_INLINE void *
my_atomic_swapptr(void * volatile *a, void *v)
my_atomic_fasptr(void * volatile *a, void *v)
{
return (atomic_swap_ptr(a, v));
}
......@@ -22,10 +22,18 @@
architectures support double-word (128-bit) cas.
*/
#ifdef MY_ATOMIC_NO_XADD
#define MY_ATOMIC_MODE "gcc-x86" LOCK "-no-xadd"
#ifdef __x86_64__
# ifdef MY_ATOMIC_NO_XADD
# define MY_ATOMIC_MODE "gcc-amd64" LOCK_prefix "-no-xadd"
# else
# define MY_ATOMIC_MODE "gcc-amd64" LOCK_prefix
# endif
#else
#define MY_ATOMIC_MODE "gcc-x86" LOCK
# ifdef MY_ATOMIC_NO_XADD
# define MY_ATOMIC_MODE "gcc-x86" LOCK_prefix "-no-xadd"
# else
# define MY_ATOMIC_MODE "gcc-x86" LOCK_prefix
# endif
#endif
/* fix -ansi errors while maintaining readability */
......@@ -35,12 +43,12 @@
#ifndef MY_ATOMIC_NO_XADD
#define make_atomic_add_body(S) \
asm volatile (LOCK "; xadd %0, %1;" : "+r" (v) , "+m" (*a))
asm volatile (LOCK_prefix "; xadd %0, %1;" : "+r" (v) , "+m" (*a))
#endif
#define make_atomic_swap_body(S) \
asm volatile ("; xchg %0, %1;" : "+q" (v) , "+m" (*a))
#define make_atomic_fas_body(S) \
asm volatile ("xchg %0, %1;" : "+q" (v) , "+m" (*a))
#define make_atomic_cas_body(S) \
asm volatile (LOCK "; cmpxchg %3, %0; setz %2;" \
asm volatile (LOCK_prefix "; cmpxchg %3, %0; setz %2;" \
: "+m" (*a), "+a" (*cmp), "=q" (ret): "r" (set))
#ifdef MY_ATOMIC_MODE_DUMMY
......@@ -49,14 +57,14 @@
#else
/*
Actually 32-bit reads/writes are always atomic on x86
But we add LOCK here anyway to force memory barriers
But we add LOCK_prefix here anyway to force memory barriers
*/
#define make_atomic_load_body(S) \
ret=0; \
asm volatile (LOCK "; cmpxchg %2, %0" \
asm volatile (LOCK_prefix "; cmpxchg %2, %0" \
: "+m" (*a), "+a" (ret): "r" (ret))
#define make_atomic_store_body(S) \
asm volatile ("; xchg %0, %1;" : "+m" (*a) : "r" (v))
asm volatile ("; xchg %0, %1;" : "+m" (*a), "+r" (v))
#endif
#endif /* ATOMIC_X86_GCC_INCLUDED */
/* Copyright (C) 2006 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*
XXX 64-bit atomic operations can be implemented using
cmpxchg8b, if necessary
*/
// Would it be better to use intrinsics ?
// (InterlockedCompareExchange, InterlockedCompareExchange16
// InterlockedExchangeAdd, InterlockedExchange)
#ifndef _atomic_h_cleanup_
#define _atomic_h_cleanup_ "atomic/x86-msvc.h"
#define MY_ATOMIC_MODE "msvc-x86" LOCK
#define make_atomic_add_body(S) \
_asm { \
_asm mov reg_ ## S, v \
_asm LOCK xadd *a, reg_ ## S \
_asm movzx v, reg_ ## S \
}
#define make_atomic_cas_body(S) \
_asm { \
_asm mov areg_ ## S, *cmp \
_asm mov reg2_ ## S, set \
_asm LOCK cmpxchg *a, reg2_ ## S \
_asm mov *cmp, areg_ ## S \
_asm setz al \
_asm movzx ret, al \
}
#define make_atomic_swap_body(S) \
_asm { \
_asm mov reg_ ## S, v \
_asm xchg *a, reg_ ## S \
_asm mov v, reg_ ## S \
}
#ifdef MY_ATOMIC_MODE_DUMMY
#define make_atomic_load_body(S) ret=*a
#define make_atomic_store_body(S) *a=v
#else
/*
Actually 32-bit reads/writes are always atomic on x86
But we add LOCK here anyway to force memory barriers
*/
#define make_atomic_load_body(S) \
_asm { \
_asm mov areg_ ## S, 0 \
_asm mov reg2_ ## S, areg_ ## S \
_asm LOCK cmpxchg *a, reg2_ ## S \
_asm mov ret, areg_ ## S \
}
#define make_atomic_store_body(S) \
_asm { \
_asm mov reg_ ## S, v \
_asm xchg *a, reg_ ## S \
}
#endif
#define reg_8 al
#define reg_16 ax
#define reg_32 eax
#define areg_8 al
#define areg_16 ax
#define areg_32 eax
#define reg2_8 bl
#define reg2_16 bx
#define reg2_32 ebx
#else /* cleanup */
#undef reg_8
#undef reg_16
#undef reg_32
#undef areg_8
#undef areg_16
#undef areg_32
#undef reg2_8
#undef reg2_16
#undef reg2_32
#endif
/* Copyright (C) 2007-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifndef _lf_h
#define _lf_h
#include <my_atomic.h>
C_MODE_START
/*
Helpers to define both func() and _func(), where
func() is a _func() protected by my_atomic_rwlock_wrlock()
*/
#define lock_wrap(f, t, proto_args, args, lock) \
t _ ## f proto_args; \
static inline t f proto_args \
{ \
t ret; \
my_atomic_rwlock_wrlock(lock); \
ret= _ ## f args; \
my_atomic_rwlock_wrunlock(lock); \
return ret; \
}
#define lock_wrap_void(f, proto_args, args, lock) \
void _ ## f proto_args; \
static inline void f proto_args \
{ \
my_atomic_rwlock_wrlock(lock); \
_ ## f args; \
my_atomic_rwlock_wrunlock(lock); \
}
#define nolock_wrap(f, t, proto_args, args) \
t _ ## f proto_args; \
static inline t f proto_args \
{ \
return _ ## f args; \
}
#define nolock_wrap_void(f, proto_args, args) \
void _ ## f proto_args; \
static inline void f proto_args \
{ \
_ ## f args; \
}
/*
wait-free dynamic array, see lf_dynarray.c
4 levels of 256 elements each mean 4311810304 elements in an array - it
should be enough for a while
*/
#define LF_DYNARRAY_LEVEL_LENGTH 256
#define LF_DYNARRAY_LEVELS 4
typedef struct {
void * volatile level[LF_DYNARRAY_LEVELS];
uint size_of_element;
my_atomic_rwlock_t lock;
} LF_DYNARRAY;
typedef int (*lf_dynarray_func)(void *, void *);
void lf_dynarray_init(LF_DYNARRAY *array, uint element_size);
void lf_dynarray_destroy(LF_DYNARRAY *array);
nolock_wrap(lf_dynarray_value, void *,
(LF_DYNARRAY *array, uint idx),
(array, idx))
lock_wrap(lf_dynarray_lvalue, void *,
(LF_DYNARRAY *array, uint idx),
(array, idx),
&array->lock)
nolock_wrap(lf_dynarray_iterate, int,
(LF_DYNARRAY *array, lf_dynarray_func func, void *arg),
(array, func, arg))
/*
pin manager for memory allocator, lf_alloc-pin.c
*/
#define LF_PINBOX_PINS 4
#define LF_PURGATORY_SIZE 10
typedef void lf_pinbox_free_func(void *, void *, void*);
typedef struct {
LF_DYNARRAY pinarray;
lf_pinbox_free_func *free_func;
void *free_func_arg;
uint free_ptr_offset;
uint32 volatile pinstack_top_ver; /* this is a versioned pointer */
uint32 volatile pins_in_array; /* number of elements in array */
} LF_PINBOX;
typedef struct {
void * volatile pin[LF_PINBOX_PINS];
LF_PINBOX *pinbox;
void **stack_ends_here;
void *purgatory;
uint32 purgatory_count;
uint32 volatile link;
/* we want sizeof(LF_PINS) to be 64 to avoid false sharing */
#if SIZEOF_INT*2+SIZEOF_CHARP*(LF_PINBOX_PINS+3) != 64
char pad[64-sizeof(uint32)*2-sizeof(void*)*(LF_PINBOX_PINS+3)];
#endif
} LF_PINS;
/*
shortcut macros to do an atomic_wrlock on a structure that uses pins
(e.g. lf_hash).
*/
#define lf_rwlock_by_pins(PINS) \
my_atomic_rwlock_wrlock(&(PINS)->pinbox->pinarray.lock)
#define lf_rwunlock_by_pins(PINS) \
my_atomic_rwlock_wrunlock(&(PINS)->pinbox->pinarray.lock)
/*
compile-time assert, to require "no less than N" pins
it's enough if it'll fail on at least one compiler, so
we'll enable it on GCC only, which supports zero-length arrays.
*/
#if defined(__GNUC__) && defined(MY_LF_EXTRA_DEBUG)
#define LF_REQUIRE_PINS(N) \
static const char require_pins[LF_PINBOX_PINS-N] \
__attribute__ ((unused)); \
static const int LF_NUM_PINS_IN_THIS_FILE= N;
#define _lf_pin(PINS, PIN, ADDR) \
( \
assert(PIN < LF_NUM_PINS_IN_THIS_FILE), \
my_atomic_storeptr(&(PINS)->pin[PIN], (ADDR)) \
)
#else
#define LF_REQUIRE_PINS(N)
#define _lf_pin(PINS, PIN, ADDR) my_atomic_storeptr(&(PINS)->pin[PIN], (ADDR))
#endif
#define _lf_unpin(PINS, PIN) _lf_pin(PINS, PIN, NULL)
#define lf_pin(PINS, PIN, ADDR) \
do { \
lf_rwlock_by_pins(PINS); \
_lf_pin(PINS, PIN, ADDR); \
lf_rwunlock_by_pins(PINS); \
} while (0)
#define lf_unpin(PINS, PIN) lf_pin(PINS, PIN, NULL)
#define _lf_assert_pin(PINS, PIN) assert((PINS)->pin[PIN] != 0)
#define _lf_assert_unpin(PINS, PIN) assert((PINS)->pin[PIN] == 0)
void lf_pinbox_init(LF_PINBOX *pinbox, uint free_ptr_offset,
lf_pinbox_free_func *free_func, void * free_func_arg);
void lf_pinbox_destroy(LF_PINBOX *pinbox);
lock_wrap(lf_pinbox_get_pins, LF_PINS *,
(LF_PINBOX *pinbox),
(pinbox),
&pinbox->pinarray.lock)
lock_wrap_void(lf_pinbox_put_pins,
(LF_PINS *pins),
(pins),
&pins->pinbox->pinarray.lock)
lock_wrap_void(lf_pinbox_free,
(LF_PINS *pins, void *addr),
(pins, addr),
&pins->pinbox->pinarray.lock)
/*
memory allocator, lf_alloc-pin.c
*/
typedef struct st_lf_allocator {
LF_PINBOX pinbox;
uchar * volatile top;
uint element_size;
uint32 volatile mallocs;
void (*constructor)(uchar *); /* called, when an object is malloc()'ed */
void (*destructor)(uchar *); /* called, when an object is free()'d */
} LF_ALLOCATOR;
void lf_alloc_init(LF_ALLOCATOR *allocator, uint size, uint free_ptr_offset);
void lf_alloc_destroy(LF_ALLOCATOR *allocator);
uint lf_alloc_pool_count(LF_ALLOCATOR *allocator);
/*
shortcut macros to access underlying pinbox functions from an LF_ALLOCATOR
see _lf_pinbox_get_pins() and _lf_pinbox_put_pins()
*/
#define _lf_alloc_free(PINS, PTR) _lf_pinbox_free((PINS), (PTR))
#define lf_alloc_free(PINS, PTR) lf_pinbox_free((PINS), (PTR))
#define _lf_alloc_get_pins(A) _lf_pinbox_get_pins(&(A)->pinbox)
#define lf_alloc_get_pins(A) lf_pinbox_get_pins(&(A)->pinbox)
#define _lf_alloc_put_pins(PINS) _lf_pinbox_put_pins(PINS)
#define lf_alloc_put_pins(PINS) lf_pinbox_put_pins(PINS)
#define lf_alloc_direct_free(ALLOC, ADDR) my_free((uchar*)(ADDR), MYF(0))
lock_wrap(lf_alloc_new, void *,
(LF_PINS *pins),
(pins),
&pins->pinbox->pinarray.lock)
C_MODE_END
/*
extendible hash, lf_hash.c
*/
#include <hash.h>
C_MODE_START
#define LF_HASH_UNIQUE 1
/* lf_hash overhead per element (that is, sizeof(LF_SLIST) */
extern const int LF_HASH_OVERHEAD;
typedef struct {
LF_DYNARRAY array; /* hash itself */
LF_ALLOCATOR alloc; /* allocator for elements */
my_hash_get_key get_key; /* see HASH */
CHARSET_INFO *charset; /* see HASH */
uint key_offset, key_length; /* see HASH */
uint element_size; /* size of memcpy'ed area on insert */
uint flags; /* LF_HASH_UNIQUE, etc */
int32 volatile size; /* size of array */
int32 volatile count; /* number of elements in the hash */
} LF_HASH;
void lf_hash_init(LF_HASH *hash, uint element_size, uint flags,
uint key_offset, uint key_length, my_hash_get_key get_key,
CHARSET_INFO *charset);
void lf_hash_destroy(LF_HASH *hash);
int lf_hash_insert(LF_HASH *hash, LF_PINS *pins, const void *data);
void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen);
int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen);
/*
shortcut macros to access underlying pinbox functions from an LF_HASH
see _lf_pinbox_get_pins() and _lf_pinbox_put_pins()
*/
#define _lf_hash_get_pins(HASH) _lf_alloc_get_pins(&(HASH)->alloc)
#define lf_hash_get_pins(HASH) lf_alloc_get_pins(&(HASH)->alloc)
#define _lf_hash_put_pins(PINS) _lf_pinbox_put_pins(PINS)
#define lf_hash_put_pins(PINS) lf_pinbox_put_pins(PINS)
#define lf_hash_search_unpin(PINS) lf_unpin((PINS), 2)
/*
cleanup
*/
#undef lock_wrap_void
#undef lock_wrap
#undef nolock_wrap_void
#undef nolock_wrap
C_MODE_END
#endif
This diff is collapsed.
......@@ -154,6 +154,7 @@ int pthread_cancel(pthread_t thread);
#define pthread_detach_this_thread()
#define pthread_condattr_init(A)
#define pthread_condattr_destroy(A)
#define pthread_yield() SwitchToThread()
#define my_pthread_getprio(thread_id) pthread_dummy(0)
#define my_sigset(A,B) signal(A,B)
......@@ -388,6 +389,17 @@ void my_pthread_attr_getstacksize(pthread_attr_t *attrib, size_t *size);
int my_pthread_mutex_trylock(pthread_mutex_t *mutex);
#endif
#if !defined(HAVE_PTHREAD_YIELD_ONE_ARG) && !defined(HAVE_PTHREAD_YIELD_ZERO_ARG)
/* no pthread_yield() available */
#ifdef HAVE_SCHED_YIELD
#define pthread_yield() sched_yield()
#elif defined(HAVE_PTHREAD_YIELD_NP) /* can be Mac OS X */
#define pthread_yield() pthread_yield_np()
#elif defined(HAVE_THR_YIELD)
#define pthread_yield() thr_yield()
#endif
#endif
/*
The defines set_timespec and set_timespec_nsec should be used
for calculating an absolute time at which
......@@ -666,6 +678,7 @@ struct st_my_thread_var
my_bool init;
struct st_my_thread_var *next,**prev;
void *opt_info;
void *stack_ends_here;
#ifndef DBUG_OFF
void *dbug;
char name[THREAD_NAME_SIZE+1];
......
......@@ -33,7 +33,9 @@ SET(MYSYS_SOURCES array.c charset-def.c charset.c checksum.c default.c default_
my_static.c my_symlink.c my_symlink2.c my_sync.c my_thr_init.c
my_write.c ptr_cmp.c queues.c stacktrace.c
rijndael.c safemalloc.c sha1.c string.c thr_alarm.c thr_lock.c thr_mutex.c
thr_rwlock.c tree.c typelib.c my_vle.c base64.c my_memmem.c my_getpagesize.c)
thr_rwlock.c tree.c typelib.c my_vle.c base64.c my_memmem.c my_getpagesize.c
lf_alloc-pin.c lf_dynarray.c lf_hash.c
my_atomic.c my_getncpus.c)
IF (WIN32)
SET (MYSYS_SOURCES ${MYSYS_SOURCES} my_winthread.c my_wincond.c my_winerr.c my_winfile.c my_windac.c my_conio.c)
......
......@@ -30,7 +30,8 @@ libmysys_a_SOURCES = my_init.c my_getwd.c mf_getdate.c my_mmap.c \
mf_tempdir.c my_lock.c mf_brkhant.c my_alarm.c \
my_malloc.c my_realloc.c my_once.c mulalloc.c \
my_alloc.c safemalloc.c my_new.cc \
my_vle.c my_atomic.c \
my_vle.c my_atomic.c lf_hash.c \
lf_dynarray.c lf_alloc-pin.c \
my_fopen.c my_fstream.c my_getsystime.c \
my_error.c errors.c my_div.c my_messnc.c \
mf_format.c mf_same.c mf_dirname.c mf_fn_ext.c \
......
This diff is collapsed.
/* Copyright (C) 2006 MySQL AB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*
Analog of DYNAMIC_ARRAY that never reallocs
(so no pointer into the array may ever become invalid).
Memory is allocated in non-contiguous chunks.
This data structure is not space efficient for sparse arrays.
Every element is aligned to sizeof(element) boundary
(to avoid false sharing if element is big enough).
LF_DYNARRAY is a recursive structure. On the zero level
LF_DYNARRAY::level[0] it's an array of LF_DYNARRAY_LEVEL_LENGTH elements,
on the first level it's an array of LF_DYNARRAY_LEVEL_LENGTH pointers
to arrays of elements, on the second level it's an array of pointers
to arrays of pointers to arrays of elements. And so on.
With four levels the number of elements is limited to 4311810304
(but as in all functions index is uint, the real limit is 2^32-1)
Actually, it's wait-free, not lock-free ;-)
*/
#include <my_global.h>
#include <m_string.h>
#include <my_sys.h>
#include <lf.h>
void lf_dynarray_init(LF_DYNARRAY *array, uint element_size)
{
bzero(array, sizeof(*array));
array->size_of_element= element_size;
my_atomic_rwlock_init(&array->lock);
}
static void recursive_free(void **alloc, int level)
{
if (!alloc)
return;
if (level)
{
int i;
for (i= 0; i < LF_DYNARRAY_LEVEL_LENGTH; i++)
recursive_free(alloc[i], level-1);
my_free((void *)alloc, MYF(0));
}
else
my_free(alloc[-1], MYF(0));
}
void lf_dynarray_destroy(LF_DYNARRAY *array)
{
int i;
for (i= 0; i < LF_DYNARRAY_LEVELS; i++)
recursive_free(array->level[i], i);
my_atomic_rwlock_destroy(&array->lock);
}
static const ulong dynarray_idxes_in_prev_levels[LF_DYNARRAY_LEVELS]=
{
0, /* +1 here to to avoid -1's below */
LF_DYNARRAY_LEVEL_LENGTH,
LF_DYNARRAY_LEVEL_LENGTH * LF_DYNARRAY_LEVEL_LENGTH +
LF_DYNARRAY_LEVEL_LENGTH,
LF_DYNARRAY_LEVEL_LENGTH * LF_DYNARRAY_LEVEL_LENGTH *
LF_DYNARRAY_LEVEL_LENGTH + LF_DYNARRAY_LEVEL_LENGTH *
LF_DYNARRAY_LEVEL_LENGTH + LF_DYNARRAY_LEVEL_LENGTH
};
static const ulong dynarray_idxes_in_prev_level[LF_DYNARRAY_LEVELS]=
{
0, /* +1 here to to avoid -1's below */
LF_DYNARRAY_LEVEL_LENGTH,
LF_DYNARRAY_LEVEL_LENGTH * LF_DYNARRAY_LEVEL_LENGTH,
LF_DYNARRAY_LEVEL_LENGTH * LF_DYNARRAY_LEVEL_LENGTH *
LF_DYNARRAY_LEVEL_LENGTH,
};
/*
Returns a valid lvalue pointer to the element number 'idx'.
Allocates memory if necessary.
*/
void *_lf_dynarray_lvalue(LF_DYNARRAY *array, uint idx)
{
void * ptr, * volatile * ptr_ptr= 0;
int i;
for (i= LF_DYNARRAY_LEVELS-1; idx < dynarray_idxes_in_prev_levels[i]; i--)
/* no-op */;
ptr_ptr= &array->level[i];
idx-= dynarray_idxes_in_prev_levels[i];
for (; i > 0; i--)
{
if (!(ptr= *ptr_ptr))
{
void *alloc= my_malloc(LF_DYNARRAY_LEVEL_LENGTH * sizeof(void *),
MYF(MY_WME|MY_ZEROFILL));
if (unlikely(!alloc))
return(NULL);
if (my_atomic_casptr(ptr_ptr, &ptr, alloc))
ptr= alloc;
else
my_free(alloc, MYF(0));
}
ptr_ptr= ((void **)ptr) + idx / dynarray_idxes_in_prev_level[i];
idx%= dynarray_idxes_in_prev_level[i];
}
if (!(ptr= *ptr_ptr))
{
uchar *alloc, *data;
alloc= my_malloc(LF_DYNARRAY_LEVEL_LENGTH * array->size_of_element +
max(array->size_of_element, sizeof(void *)),
MYF(MY_WME|MY_ZEROFILL));
if (unlikely(!alloc))
return(NULL);
/* reserve the space for free() address */
data= alloc + sizeof(void *);
{ /* alignment */
intptr mod= ((intptr)data) % array->size_of_element;
if (mod)
data+= array->size_of_element - mod;
}
((void **)data)[-1]= alloc; /* free() will need the original pointer */
if (my_atomic_casptr(ptr_ptr, &ptr, data))
ptr= data;
else
my_free(alloc, MYF(0));
}
return ((uchar*)ptr) + array->size_of_element * idx;
}
/*
Returns a pointer to the element number 'idx'
or NULL if an element does not exists
*/
void *_lf_dynarray_value(LF_DYNARRAY *array, uint idx)
{
void * ptr, * volatile * ptr_ptr= 0;
int i;
for (i= LF_DYNARRAY_LEVELS-1; idx < dynarray_idxes_in_prev_levels[i]; i--)
/* no-op */;
ptr_ptr= &array->level[i];
idx-= dynarray_idxes_in_prev_levels[i];
for (; i > 0; i--)
{
if (!(ptr= *ptr_ptr))
return(NULL);
ptr_ptr= ((void **)ptr) + idx / dynarray_idxes_in_prev_level[i];
idx %= dynarray_idxes_in_prev_level[i];
}
if (!(ptr= *ptr_ptr))
return(NULL);
return ((uchar*)ptr) + array->size_of_element * idx;
}
static int recursive_iterate(LF_DYNARRAY *array, void *ptr, int level,
lf_dynarray_func func, void *arg)
{
int res, i;
if (!ptr)
return 0;
if (!level)
return func(ptr, arg);
for (i= 0; i < LF_DYNARRAY_LEVEL_LENGTH; i++)
if ((res= recursive_iterate(array, ((void **)ptr)[i], level-1, func, arg)))
return res;
return 0;
}
/*
Calls func(array, arg) on every array of LF_DYNARRAY_LEVEL_LENGTH elements
in lf_dynarray.
DESCRIPTION
lf_dynarray consists of a set of arrays, LF_DYNARRAY_LEVEL_LENGTH elements
each. _lf_dynarray_iterate() calls user-supplied function on every array
from the set. It is the fastest way to scan the array, faster than
for (i=0; i < N; i++) { func(_lf_dynarray_value(dynarray, i)); }
NOTE
if func() returns non-zero, the scan is aborted
*/
int _lf_dynarray_iterate(LF_DYNARRAY *array, lf_dynarray_func func, void *arg)
{
int i, res;
for (i= 0; i < LF_DYNARRAY_LEVELS; i++)
if ((res= recursive_iterate(array, array->level[i], i, func, arg)))
return res;
return 0;
}
This diff is collapsed.
/* Copyright (C) 2006 MySQL AB
/* Copyright (C) 2006 MySQL AB, 2008-2009 Sun Microsystems, Inc
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -14,7 +14,7 @@
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include <my_global.h>
#include <my_pthread.h>
#include <my_sys.h>
#ifndef HAVE_INLINE
/* the following will cause all inline functions to be instantiated */
......@@ -43,3 +43,32 @@ int my_atomic_initialize()
#endif
}
#ifdef SAFE_MUTEX
#undef pthread_mutex_init
#undef pthread_mutex_destroy
#undef pthread_mutex_lock
#undef pthread_mutex_unlock
void plain_pthread_mutex_init(safe_mutex_t *m)
{
pthread_mutex_init(& m->mutex, NULL);
}
void plain_pthread_mutex_destroy(safe_mutex_t *m)
{
pthread_mutex_destroy(& m->mutex);
}
void plain_pthread_mutex_lock(safe_mutex_t *m)
{
pthread_mutex_lock(& m->mutex);
}
void plain_pthread_mutex_unlock(safe_mutex_t *m)
{
pthread_mutex_unlock(& m->mutex);
}
#endif
/* Copyright (C) 2006 MySQL AB
/* Copyright (C) 2006 MySQL AB, 2008-2009 Sun Microsystems, Inc
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -16,24 +16,34 @@
/* get the number of (online) CPUs */
#include "mysys_priv.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
static int ncpus=0;
#ifdef _SC_NPROCESSORS_ONLN
int my_getncpus()
{
if (!ncpus)
{
#ifdef _SC_NPROCESSORS_ONLN
ncpus= sysconf(_SC_NPROCESSORS_ONLN);
return ncpus;
}
#elif defined(__WIN__)
SYSTEM_INFO sysinfo;
/*
* We are not calling GetNativeSystemInfo here because (1) we
* don't believe that they return different values for number
* of processors and (2) if WOW64 limits processors for Win32
* then we don't want to try to override that.
*/
GetSystemInfo(&sysinfo);
ncpus= sysinfo.dwNumberOfProcessors;
#else
/* unknown */
int my_getncpus()
{
return 2;
}
/* unknown so play safe: assume SMP and forbid uniprocessor build */
ncpus= 2;
#endif
}
return ncpus;
}
......@@ -284,6 +284,9 @@ my_bool my_thread_init(void)
pthread_cond_init(&tmp->suspend, NULL);
tmp->init= 1;
tmp->stack_ends_here= (char*)&tmp +
STACK_DIRECTION * (long)my_thread_stack_size;
pthread_mutex_lock(&THR_LOCK_threads);
tmp->id= ++thread_id;
++THR_thread_count;
......
# Copyright (C) 2007 MySQL AB
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib
${CMAKE_SOURCE_DIR}/sql
${CMAKE_SOURCE_DIR}/regex
${CMAKE_SOURCE_DIR}/extra/yassl/include
${CMAKE_SOURCE_DIR}/unittest/mytap)
ADD_EXECUTABLE(simple-t simple-t.c)
TARGET_LINK_LIBRARIES(simple-t mytap)
ADD_EXECUTABLE(skip-t skip-t.c)
TARGET_LINK_LIBRARIES(skip-t mytap)
ADD_EXECUTABLE(todo-t todo-t.c)
TARGET_LINK_LIBRARIES(todo-t mytap)
ADD_EXECUTABLE(skip_all-t skip_all-t.c)
TARGET_LINK_LIBRARIES(skip_all-t mytap)
ADD_EXECUTABLE(no_plan-t no_plan-t.c)
TARGET_LINK_LIBRARIES(no_plan-t mytap)
ADD_EXECUTABLE(core-t core-t.c)
TARGET_LINK_LIBRARIES(core-t mytap)
......@@ -13,7 +13,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "my_config.h"
#include <my_global.h>
#include <stdlib.h>
#include <tap.h>
......
......@@ -13,7 +13,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "my_config.h"
#include <my_global.h>
#include <stdlib.h>
#include <tap.h>
......
......@@ -13,7 +13,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "my_config.h"
#include <my_global.h>
#include <stdlib.h>
#include <tap.h>
......
......@@ -13,7 +13,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "my_config.h"
#include <my_global.h>
#include <stdlib.h>
#include <tap.h>
......
......@@ -27,10 +27,6 @@ MACRO (MY_ADD_TEST name)
ENDMACRO()
FOREACH(testname bitmap base64 my_vsnprintf)
FOREACH(testname bitmap base64 my_vsnprintf my_atomic)
MY_ADD_TEST(${testname})
ENDFOREACH()
IF(NOT WIN32)
# does not work on Windows in 5.x codebase
MY_ADD_TEST(my_atomic)
ENDIF()
......@@ -16,12 +16,14 @@
AM_CPPFLAGS = @ZLIB_INCLUDES@ -I$(top_builddir)/include
AM_CPPFLAGS += -I$(top_srcdir)/include -I$(top_srcdir)/unittest/mytap
noinst_HEADERS = thr_template.c
LDADD = $(top_builddir)/unittest/mytap/libmytap.a \
$(top_builddir)/mysys/libmysys.a \
$(top_builddir)/dbug/libdbug.a \
$(top_builddir)/strings/libmystrings.a
noinst_PROGRAMS = bitmap-t base64-t my_vsnprintf-t
noinst_PROGRAMS = bitmap-t base64-t lf-t my_vsnprintf-t
EXTRA_DIST = CMakeLists.txt
......
/* Copyright (C) 2008-2008 MySQL AB, 2008-2009 Sun Microsystems, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/**
@file
Unit tests for lock-free algorithms of mysys
*/
#include "thr_template.c"
#include <lf.h>
int32 inserts= 0, N;
LF_ALLOCATOR lf_allocator;
LF_HASH lf_hash;
/*
pin allocator - alloc and release an element in a loop
*/
pthread_handler_t test_lf_pinbox(void *arg)
{
int m= *(int *)arg;
int32 x= 0;
LF_PINS *pins;
my_thread_init();
pins= lf_pinbox_get_pins(&lf_allocator.pinbox);
for (x= ((int)(intptr)(&m)); m ; m--)
{
lf_pinbox_put_pins(pins);
pins= lf_pinbox_get_pins(&lf_allocator.pinbox);
}
lf_pinbox_put_pins(pins);
pthread_mutex_lock(&mutex);
if (!--running_threads) pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
my_thread_end();
return 0;
}
/*
thread local data area, allocated using lf_alloc.
union is required to enforce the minimum required element size (sizeof(ptr))
*/
typedef union {
int32 data;
void *not_used;
} TLA;
pthread_handler_t test_lf_alloc(void *arg)
{
int m= (*(int *)arg)/2;
int32 x,y= 0;
LF_PINS *pins;
my_thread_init();
pins= lf_alloc_get_pins(&lf_allocator);
for (x= ((int)(intptr)(&m)); m ; m--)
{
TLA *node1, *node2;
x= (x*m+0x87654321) & INT_MAX32;
node1= (TLA *)lf_alloc_new(pins);
node1->data= x;
y+= node1->data;
node1->data= 0;
node2= (TLA *)lf_alloc_new(pins);
node2->data= x;
y-= node2->data;
node2->data= 0;
lf_alloc_free(pins, node1);
lf_alloc_free(pins, node2);
}
lf_alloc_put_pins(pins);
pthread_mutex_lock(&mutex);
bad+= y;
if (--N == 0)
{
diag("%d mallocs, %d pins in stack",
lf_allocator.mallocs, lf_allocator.pinbox.pins_in_array);
#ifdef MY_LF_EXTRA_DEBUG
bad|= lf_allocator.mallocs - lf_alloc_pool_count(&lf_allocator);
#endif
}
if (!--running_threads) pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
my_thread_end();
return 0;
}
#define N_TLH 1000
pthread_handler_t test_lf_hash(void *arg)
{
int m= (*(int *)arg)/(2*N_TLH);
int32 x,y,z,sum= 0, ins= 0;
LF_PINS *pins;
my_thread_init();
pins= lf_hash_get_pins(&lf_hash);
for (x= ((int)(intptr)(&m)); m ; m--)
{
int i;
y= x;
for (i= 0; i < N_TLH; i++)
{
x= (x*(m+i)+0x87654321) & INT_MAX32;
z= (x<0) ? -x : x;
if (lf_hash_insert(&lf_hash, pins, &z))
{
sum+= z;
ins++;
}
}
for (i= 0; i < N_TLH; i++)
{
y= (y*(m+i)+0x87654321) & INT_MAX32;
z= (y<0) ? -y : y;
if (lf_hash_delete(&lf_hash, pins, (uchar *)&z, sizeof(z)))
sum-= z;
}
}
lf_hash_put_pins(pins);
pthread_mutex_lock(&mutex);
bad+= sum;
inserts+= ins;
if (--N == 0)
{
diag("%d mallocs, %d pins in stack, %d hash size, %d inserts",
lf_hash.alloc.mallocs, lf_hash.alloc.pinbox.pins_in_array,
lf_hash.size, inserts);
bad|= lf_hash.count;
}
if (!--running_threads) pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
my_thread_end();
return 0;
}
void do_tests()
{
plan(4);
lf_alloc_init(&lf_allocator, sizeof(TLA), offsetof(TLA, not_used));
lf_hash_init(&lf_hash, sizeof(int), LF_HASH_UNIQUE, 0, sizeof(int), 0,
&my_charset_bin);
bad= my_atomic_initialize();
ok(!bad, "my_atomic_initialize() returned %d", bad);
test_concurrently("lf_pinbox", test_lf_pinbox, N= THREADS, CYCLES);
test_concurrently("lf_alloc", test_lf_alloc, N= THREADS, CYCLES);
test_concurrently("lf_hash", test_lf_hash, N= THREADS, CYCLES/10);
lf_hash_destroy(&lf_hash);
lf_alloc_destroy(&lf_allocator);
}
/* Copyright (C) 2006 MySQL AB
/* Copyright (C) 2006-2008 MySQL AB, 2008 Sun Microsystems, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -13,10 +13,7 @@
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include <my_global.h>
#include <my_sys.h>
#include <my_atomic.h>
#include <tap.h>
#include "thr_template.c"
/* at least gcc 3.4.5 and 3.4.6 (but not 3.2.3) on RHEL */
#if __GNUC__ == 3 && __GNUC_MINOR__ == 4
......@@ -25,181 +22,125 @@
#define GCC_BUG_WORKAROUND
#endif
int32 a32,b32,c32;
volatile uint32 b32;
volatile int32 c32;
my_atomic_rwlock_t rwl;
pthread_attr_t thr_attr;
pthread_mutex_t mutex;
pthread_cond_t cond;
int N;
/* add and sub a random number in a loop. Must get 0 at the end */
pthread_handler_t test_atomic_add_handler(void *arg)
pthread_handler_t test_atomic_add(void *arg)
{
int m=*(int *)arg;
int m= (*(int *)arg)/2;
GCC_BUG_WORKAROUND int32 x;
for (x=((int)((long)(&m))); m ; m--)
for (x= ((int)(intptr)(&m)); m ; m--)
{
x=x*m+0x87654321;
x= (x*m+0x87654321) & INT_MAX32;
my_atomic_rwlock_wrlock(&rwl);
my_atomic_add32(&a32, x);
my_atomic_add32(&bad, x);
my_atomic_rwlock_wrunlock(&rwl);
my_atomic_rwlock_wrlock(&rwl);
my_atomic_add32(&a32, -x);
my_atomic_add32(&bad, -x);
my_atomic_rwlock_wrunlock(&rwl);
}
pthread_mutex_lock(&mutex);
N--;
if (!N) pthread_cond_signal(&cond);
if (!--running_threads) pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return 0;
}
/*
1. generate thread number 0..N-1 from b32
2. add it to a32
2. add it to bad
3. swap thread numbers in c32
4. (optionally) one more swap to avoid 0 as a result
5. subtract result from a32
must get 0 in a32 at the end
5. subtract result from bad
must get 0 in bad at the end
*/
pthread_handler_t test_atomic_swap_handler(void *arg)
pthread_handler_t test_atomic_fas(void *arg)
{
int m=*(int *)arg;
int m= *(int *)arg;
int32 x;
my_atomic_rwlock_wrlock(&rwl);
x=my_atomic_add32(&b32, 1);
x= my_atomic_add32(&b32, 1);
my_atomic_rwlock_wrunlock(&rwl);
my_atomic_rwlock_wrlock(&rwl);
my_atomic_add32(&a32, x);
my_atomic_add32(&bad, x);
my_atomic_rwlock_wrunlock(&rwl);
for (; m ; m--)
{
my_atomic_rwlock_wrlock(&rwl);
x=my_atomic_swap32(&c32, x);
x= my_atomic_fas32(&c32, x);
my_atomic_rwlock_wrunlock(&rwl);
}
if (!x)
{
my_atomic_rwlock_wrlock(&rwl);
x=my_atomic_swap32(&c32, x);
x= my_atomic_fas32(&c32, x);
my_atomic_rwlock_wrunlock(&rwl);
}
my_atomic_rwlock_wrlock(&rwl);
my_atomic_add32(&a32, -x);
my_atomic_add32(&bad, -x);
my_atomic_rwlock_wrunlock(&rwl);
pthread_mutex_lock(&mutex);
N--;
if (!N) pthread_cond_signal(&cond);
if (!--running_threads) pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return 0;
}
/*
same as test_atomic_add_handler, but my_atomic_add32 is emulated with
(slower) my_atomic_cas32
same as test_atomic_add, but my_atomic_add32 is emulated with
my_atomic_cas32 - notice that the slowdown is proportional to the
number of CPUs
*/
pthread_handler_t test_atomic_cas_handler(void *arg)
pthread_handler_t test_atomic_cas(void *arg)
{
int m=*(int *)arg, ok;
GCC_BUG_WORKAROUND int32 x,y;
for (x=((int)((long)(&m))); m ; m--)
int m= (*(int *)arg)/2, ok= 0;
GCC_BUG_WORKAROUND int32 x, y;
for (x= ((int)(intptr)(&m)); m ; m--)
{
my_atomic_rwlock_wrlock(&rwl);
y=my_atomic_load32(&a32);
y= my_atomic_load32(&bad);
my_atomic_rwlock_wrunlock(&rwl);
x=x*m+0x87654321;
x= (x*m+0x87654321) & INT_MAX32;
do {
my_atomic_rwlock_wrlock(&rwl);
ok=my_atomic_cas32(&a32, &y, y+x);
ok= my_atomic_cas32(&bad, &y, (uint32)y+x);
my_atomic_rwlock_wrunlock(&rwl);
} while (!ok);
} while (!ok) ;
do {
my_atomic_rwlock_wrlock(&rwl);
ok=my_atomic_cas32(&a32, &y, y-x);
ok= my_atomic_cas32(&bad, &y, y-x);
my_atomic_rwlock_wrunlock(&rwl);
} while (!ok);
} while (!ok) ;
}
pthread_mutex_lock(&mutex);
N--;
if (!N) pthread_cond_signal(&cond);
if (!--running_threads) pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return 0;
}
void test_atomic(const char *test, pthread_handler handler, int n, int m)
{
pthread_t t;
ulonglong now=my_getsystime();
a32= 0;
b32= 0;
c32= 0;
diag("Testing %s with %d threads, %d iterations... ", test, n, m);
for (N=n ; n ; n--)
{
if (pthread_create(&t, &thr_attr, handler, &m) != 0)
{
diag("Could not create thread");
a32= 1;
goto err;
}
}
pthread_mutex_lock(&mutex);
while (N)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
now=my_getsystime()-now;
err:
ok(a32 == 0, "tested %s in %g secs", test, ((double)now)/1e7);
}
int main()
void do_tests()
{
int err;
MY_INIT("my_atomic-t.c");
diag("N CPUs: %d", my_getncpus());
err= my_atomic_initialize();
plan(4);
ok(err == 0, "my_atomic_initialize() returned %d", err);
pthread_attr_init(&thr_attr);
pthread_attr_setdetachstate(&thr_attr,PTHREAD_CREATE_DETACHED);
pthread_mutex_init(&mutex, 0);
pthread_cond_init(&cond, 0);
bad= my_atomic_initialize();
ok(!bad, "my_atomic_initialize() returned %d", bad);
my_atomic_rwlock_init(&rwl);
#ifdef HPUX11
#define CYCLES 1000
#else
#define CYCLES 10000
#endif
#define THREADS 100
test_atomic("my_atomic_add32", test_atomic_add_handler, THREADS, CYCLES);
test_atomic("my_atomic_swap32", test_atomic_swap_handler, THREADS, CYCLES);
test_atomic("my_atomic_cas32", test_atomic_cas_handler, THREADS, CYCLES);
/*
workaround until we know why it crashes randomly on some machine
(BUG#22320).
*/
sleep(2);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
pthread_attr_destroy(&thr_attr);
b32= c32= 0;
test_concurrently("my_atomic_add32", test_atomic_add, THREADS, CYCLES);
b32= c32= 0;
test_concurrently("my_atomic_fas32", test_atomic_fas, THREADS, CYCLES);
b32= c32= 0;
test_concurrently("my_atomic_cas32", test_atomic_cas, THREADS, CYCLES);
my_atomic_rwlock_destroy(&rwl);
return exit_status();
}
/* Copyright (C) 2006-2008 MySQL AB, 2008 Sun Microsystems, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include <my_global.h>
#include <my_sys.h>
#include <my_atomic.h>
#include <tap.h>
volatile uint32 bad;
pthread_attr_t thr_attr;
pthread_mutex_t mutex;
pthread_cond_t cond;
uint running_threads;
void do_tests();
void test_concurrently(const char *test, pthread_handler handler, int n, int m)
{
pthread_t t;
ulonglong now= my_getsystime();
bad= 0;
diag("Testing %s with %d threads, %d iterations... ", test, n, m);
for (running_threads= n ; n ; n--)
{
if (pthread_create(&t, &thr_attr, handler, &m) != 0)
{
diag("Could not create thread");
abort();
}
}
pthread_mutex_lock(&mutex);
while (running_threads)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
now= my_getsystime()-now;
ok(!bad, "tested %s in %g secs (%d)", test, ((double)now)/1e7, bad);
}
int main(int argc __attribute__((unused)), char **argv)
{
MY_INIT("thd_template");
if (argv[1] && *argv[1])
DBUG_SET_INITIAL(argv[1]);
pthread_mutex_init(&mutex, 0);
pthread_cond_init(&cond, 0);
pthread_attr_init(&thr_attr);
pthread_attr_setdetachstate(&thr_attr,PTHREAD_CREATE_DETACHED);
#ifdef MY_ATOMIC_MODE_RWLOCKS
#if defined(HPUX11) || defined(__POWERPC__) /* showed to be very slow (scheduler-related) */
#define CYCLES 300
#else
#define CYCLES 3000
#endif
#else
#define CYCLES 3000
#endif
#define THREADS 30
diag("N CPUs: %d, atomic ops: %s", my_getncpus(), MY_ATOMIC_MODE);
do_tests();
/*
workaround until we know why it crashes randomly on some machine
(BUG#22320).
*/
sleep(2);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
pthread_attr_destroy(&thr_attr);
my_end(0);
return exit_status();
}
......@@ -13,7 +13,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -I$(srcdir)
AM_CPPFLAGS = -I$(top_srcdir)/include
noinst_LIBRARIES = libmytap.a
noinst_HEADERS = tap.h
......@@ -23,6 +23,3 @@ libmytap_a_SOURCES = tap.c
EXTRA_DIST = CMakeLists.txt
SUBDIRS = . t
# Don't update the files from bitkeeper
%::SCCS/s.%
......@@ -19,7 +19,7 @@
#include "tap.h"
#include "my_config.h"
#include "my_global.h"
#include <stdlib.h>
#include <stdarg.h>
......@@ -27,6 +27,16 @@
#include <string.h>
#include <signal.h>
/*
Visual Studio 2003 does not know vsnprintf but knows _vsnprintf.
We don't put this #define in config-win.h because we prefer
my_vsnprintf everywhere instead, except when linking with libmysys
is not desirable - the case here.
*/
#if defined(_MSC_VER) && ( _MSC_VER == 1310 )
#define vsnprintf _vsnprintf
#endif
/**
@defgroup MyTAP_Internal MyTAP Internals
......@@ -150,9 +160,9 @@ static signal_entry install_signal[]= {
{ SIGILL, handle_core_signal },
{ SIGABRT, handle_core_signal },
{ SIGFPE, handle_core_signal },
{ SIGSEGV, handle_core_signal },
{ SIGSEGV, handle_core_signal }
#ifdef SIGBUS
{ SIGBUS, handle_core_signal }
, { SIGBUS, handle_core_signal }
#endif
#ifdef SIGXCPU
, { SIGXCPU, handle_core_signal }
......@@ -168,13 +178,22 @@ static signal_entry install_signal[]= {
#endif
};
int skip_big_tests= 1;
void
plan(int count)
{
char *config= getenv("MYTAP_CONFIG");
size_t i;
if (config)
skip_big_tests= strcmp(config, "big");
setvbuf(tapout, 0, _IONBF, 0); /* provide output at once */
/*
Install signal handler
*/
size_t i;
for (i= 0; i < sizeof(install_signal)/sizeof(*install_signal); ++i)
signal(install_signal[i].signo, install_signal[i].handler);
......
......@@ -61,6 +61,24 @@ typedef struct TEST_DATA {
extern "C" {
#endif
/**
Defines whether "big" tests should be skipped.
This variable is set by plan() function unless MYTAP_CONFIG environment
variable is set to the string "big". It is supposed to be used as
@code
if (skip_big_tests) {
skip(1, "Big test skipped");
} else {
ok(life_universe_and_everything() == 42, "The answer is CORRECT");
}
@endcode
@see SKIP_BIG_TESTS
*/
extern int skip_big_tests;
/**
@defgroup MyTAP_API MyTAP API
......@@ -81,10 +99,15 @@ extern "C" {
that generate a core, so if you want to override these signals, do
it <em>after</em> you have called the plan() function.
It will also set skip_big_tests variable if MYTAP_CONFIG environment
variable is defined.
@see skip_big_tests
@param count The planned number of tests to run.
*/
void plan(int count);
void plan(int const count);
/**
......@@ -103,7 +126,7 @@ void plan(int count);
which case nothing is printed.
*/
void ok(int pass, char const *fmt, ...)
void ok(int const pass, char const *fmt, ...)
__attribute__((format(printf,2,3)));
......@@ -135,7 +158,7 @@ void ok(int pass, char const *fmt, ...)
@param reason A reason for skipping the tests
*/
void skip(int how_many, char const *reason, ...)
void skip(int how_many, char const *const reason, ...)
__attribute__((format(printf,2,3)));
......@@ -160,6 +183,24 @@ void skip(int how_many, char const *reason, ...)
if (SKIP_IF_TRUE) skip((COUNT),(REASON)); else
/**
Helper macro to skip a group of "big" tests. It is used in the following
manner:
@code
SKIP_BIG_TESTS(1)
{
ok(life_universe_and_everything() == 42, "The answer is CORRECT");
}
@endcode
@see skip_big_tests
*/
#define SKIP_BIG_TESTS(COUNT) \
if (skip_big_tests) skip((COUNT), "big test"); else
/**
Print a diagnostics message.
......
......@@ -14,8 +14,9 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
use Test::Harness qw(&runtests $verbose);
use Test::Harness;
use File::Find;
use Getopt::Long;
use strict;
......@@ -31,10 +32,19 @@ unit - Run unit tests in directory
=head1 SYNOPSIS
unit run
unit [--[no]big] [--[no]verbose] run [tests to run]
=cut
my $big= $ENV{'MYTAP_CONFIG'} eq 'big';
my $result = GetOptions (
"big!" => \$big,
"verbose!" => \$Test::Harness::verbose,
);
$ENV{'MYTAP_CONFIG'} = $big ? 'big' : '';
my $cmd = shift;
if (defined $cmd && exists $dispatch{$cmd}) {
......@@ -56,7 +66,7 @@ sub _find_test_files (@) {
my @files;
find sub {
$File::Find::prune = 1 if /^SCCS$/;
push(@files, $File::Find::name) if -x _ && /-t\z/;
push(@files, $File::Find::name) if -x _ && (/-t\z/ || /-t\.exe\z/);
}, @dirs;
return @files;
}
......@@ -92,7 +102,7 @@ sub run_cmd (@) {
if (@files > 0) {
# Removing the first './' from the file names
foreach (@files) { s!^\./!! }
$ENV{'HARNESS_PERL_SWITCHES'} .= q" -e 'exec @ARGV'";
$ENV{'HARNESS_PERL_SWITCHES'} .= ' -e "exec @ARGV"';
runtests @files;
}
}
......
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