MXS-2067: Replace SPINLOCK with pthread_mutex_t

Replaced the SPINLOCK implementation with pthread_mutex_t. The SPINLOCK
interface is still used and will be removed later on.
This commit is contained in:
Markus Mäkelä 2018-09-25 14:16:03 +03:00
parent fe81b399b2
commit fc1e36429c
No known key found for this signature in database
GPG Key ID: 72D48FCE664F7B19
6 changed files with 6 additions and 484 deletions

View File

@ -12,103 +12,16 @@
*/
#pragma once
/**
* @file spinlock.h
*
* Spinlock implementation for MaxScale.
*
* Spinlocks are cheap locks that can be used to protect short code blocks, they are
* generally wasteful as any blocked threads will spin, consuming CPU cycles, waiting
* for the lock to be released. However they are useful in that they do not involve
* system calls and are light weight when the expected wait time for a lock is low.
*/
#include <maxscale/cdefs.h>
#include <stdbool.h>
#include <pthread.h>
MXS_BEGIN_DECLS
#define SPINLOCK_PROFILE 0
/**
* The spinlock structure.
*
* In normal builds the structure merely contains a lock value which
* is 0 if the spinlock is not taken and greater than zero if it is held.
*
* In builds with the SPINLOCK_PROFILE option set this structure also holds
* a number of profile related fields that count the number of spins, number
* of waiting threads and the number of times the lock has been acquired.
*/
typedef struct spinlock
{
int lock; /*< Is the lock held? */
#if SPINLOCK_PROFILE
uint64_t spins; /*< Number of spins on this lock */
uint64_t maxspins; /*< Max no of spins to acquire lock */
uint64_t acquired; /*< No. of times lock was acquired */
uint64_t waiting; /*< No. of threads acquiring this lock */
uint64_t max_waiting; /*< Max no of threads waiting for lock */
uint64_t contended; /*< No. of times acquire was contended */
THREAD owner; /*< Last owner of this lock */
#endif
} SPINLOCK;
#if SPINLOCK_PROFILE
#define SPINLOCK_INIT {0, 0, 0, 0, 0, 0, 0, 0}
#else
#define SPINLOCK_INIT {0}
#endif
/**
* Debugging macro for testing the state of a spinlock.
*
* @attention ONLY to be used in debugging context.
*/
#define SPINLOCK_IS_LOCKED(l) ((l)->lock != 0 ? true : false)
/**
* Initialise a spinlock.
*
* @param lock The spinlock to initialise.
*/
extern void spinlock_init(SPINLOCK* lock);
/**
* Acquire a spinlock.
*
* @param lock The spinlock to acquire
*/
extern void spinlock_acquire(const SPINLOCK* lock);
/**
* Acquire a spinlock if it is not already locked.
*
* @param lock The spinlock to acquire
* @return True if the spinlock was acquired, otherwise false
*/
extern bool spinlock_acquire_nowait(const SPINLOCK* lock);
/*
* Release a spinlock.
*
* @param lock The spinlock to release
*/
extern void spinlock_release(const SPINLOCK* lock);
/**
* Report statistics on a spinlock. This only has an effect if the
* spinlock code has been compiled with the SPINLOCK_PROFILE option set.
*
* NB A callback function is used to return the data rather than
* merely printing to a DCB in order to avoid a dependency on the DCB
* form the spinlock code and also to facilitate other uses of the
* statistics reporting.
*
* @param lock The spinlock to report on
* @param reporter The callback function to pass the statistics to
* @param hdl A handle that is passed to the reporter function
*/
extern void spinlock_stats(const SPINLOCK* lock, void (* reporter)(void*, char*, int), void* hdl);
#define SPINLOCK pthread_mutex_t
#define SPINLOCK_INIT PTHREAD_MUTEX_INITIALIZER
#define spinlock_init(a) pthread_mutex_init(a, NULL)
#define spinlock_acquire(a) pthread_mutex_lock((pthread_mutex_t*)a)
#define spinlock_release(a) pthread_mutex_unlock((pthread_mutex_t*)a)
MXS_END_DECLS

View File

@ -43,7 +43,6 @@ add_library(maxscale-common SHARED
service.cc
session.cc
session_command.cc
spinlock.cc
ssl.cc
users.cc
utils.cc

View File

@ -945,7 +945,6 @@ static void server_parameter_free(SERVER_PARAM* tofree)
*/
static size_t server_get_parameter_nolock(const SERVER* server, const char* name, char* out, size_t size)
{
mxb_assert(SPINLOCK_IS_LOCKED(&server->lock));
size_t len = 0;
SERVER_PARAM* param = server->parameters;

View File

@ -1,120 +0,0 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2022-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
#include <maxscale/spinlock.h>
#include <maxbase/assert.h>
#include <maxbase/atomic.h>
#include <time.h>
void spinlock_init(SPINLOCK* lock)
{
lock->lock = 0;
#if SPINLOCK_PROFILE
lock->spins = 0;
lock->maxspins = 0;
lock->acquired = 0;
lock->waiting = 0;
lock->max_waiting = 0;
lock->contended = 0;
lock->owner = 0;
#endif
}
void spinlock_acquire(const SPINLOCK* const_lock)
{
SPINLOCK* lock = (SPINLOCK*)const_lock;
#if SPINLOCK_PROFILE
int spins = 0;
atomic_add(&(lock->waiting), 1);
#endif
while (__sync_lock_test_and_set(&(lock->lock), 1))
{
#if SPINLOCK_PROFILE
atomic_add(&(lock->spins), 1);
spins++;
#endif
}
#if SPINLOCK_PROFILE
if (spins)
{
lock->contended++;
if (lock->maxspins < spins)
{
lock->maxspins = spins;
}
}
lock->acquired++;
lock->owner = thread_self();
atomic_add(&(lock->waiting), -1);
#endif
}
bool spinlock_acquire_nowait(const SPINLOCK* const_lock)
{
SPINLOCK* lock = (SPINLOCK*)const_lock;
if (__sync_lock_test_and_set(&(lock->lock), 1))
{
return false;
}
#if SPINLOCK_PROFILE
lock->acquired++;
lock->owner = thread_self();
#endif
return true;
}
void spinlock_release(const SPINLOCK* const_lock)
{
SPINLOCK* lock = (SPINLOCK*)const_lock;
mxb_assert(lock->lock != 0);
#if SPINLOCK_PROFILE
if (lock->waiting > lock->max_waiting)
{
lock->max_waiting = lock->waiting;
}
#endif
__sync_lock_release(&lock->lock);
}
void spinlock_stats(const SPINLOCK* lock, void (* reporter)(void*, char*, int), void* hdl)
{
#if SPINLOCK_PROFILE
reporter(hdl, "Spinlock acquired", lock->acquired);
if (lock->acquired)
{
reporter(hdl, "Total no. of spins", lock->spins);
if (lock->acquired)
{
reporter(hdl, "Average no. of spins (overall)", lock->spins / lock->acquired);
}
if (lock->contended)
{
reporter(hdl, "Average no. of spins (when contended)", lock->spins / lock->contended);
}
reporter(hdl, "Maximum no. of spins", lock->maxspins);
reporter(hdl, "Maximim no. of blocked threads", lock->max_waiting);
reporter(hdl, "Contended locks", lock->contended);
if (lock->acquired)
{
reporter(hdl, "Contention percentage", (lock->contended * 100) / lock->acquired);
}
}
#endif
}

View File

@ -19,7 +19,6 @@ add_executable(test_modutil test_modutil.cc)
add_executable(test_poll test_poll.cc)
add_executable(test_server test_server.cc)
add_executable(test_service test_service.cc)
add_executable(test_spinlock test_spinlock.cc)
add_executable(test_trxcompare test_trxcompare.cc ../../../query_classifier/test/testreader.cc)
add_executable(test_trxtracking test_trxtracking.cc)
add_executable(test_users test_users.cc)
@ -47,7 +46,6 @@ target_link_libraries(test_modutil maxscale-common)
target_link_libraries(test_poll maxscale-common)
target_link_libraries(test_server maxscale-common)
target_link_libraries(test_service maxscale-common)
target_link_libraries(test_spinlock maxscale-common)
target_link_libraries(test_trxcompare maxscale-common)
target_link_libraries(test_trxtracking maxscale-common)
target_link_libraries(test_users maxscale-common)
@ -74,7 +72,6 @@ add_test(test_modutil test_modutil)
add_test(test_poll test_poll)
add_test(test_server test_server)
add_test(test_service test_service)
add_test(test_spinlock test_spinlock)
add_test(test_trxcompare_create test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/create.test)
add_test(test_trxcompare_delete test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/delete.test)
add_test(test_trxcompare_insert test_trxcompare ${CMAKE_CURRENT_SOURCE_DIR}/../../../query_classifier/test/insert.test)

View File

@ -1,266 +0,0 @@
/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2022-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
/**
*
* @verbatim
* Revision History
*
* Date Who Description
* 18/08-2014 Mark Riddoch Initial implementation
*
* @endverbatim
*/
// To ensure that ss_info_assert asserts also when builing in non-debug mode.
#if !defined (SS_DEBUG)
#define SS_DEBUG
#endif
#if defined (NDEBUG)
#undef NDEBUG
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#include <maxscale/spinlock.h>
/**
* test1 spinlock_acquire_nowait tests
*
* Test that spinlock_acquire_nowait returns false if the spinlock
* is already taken.
*
* Test that spinlock_acquire_nowait returns true if the spinlock
* is not taken.
*
* Test that spinlock_acquire_nowait does hold the spinlock.
*/
static int test1()
{
SPINLOCK lck;
spinlock_init(&lck);
spinlock_acquire(&lck);
if (spinlock_acquire_nowait(&lck))
{
fprintf(stderr, "spinlock_acquire_nowait: test 1.1 failed.\n");
return 1;
}
spinlock_release(&lck);
if (!spinlock_acquire_nowait(&lck))
{
fprintf(stderr, "spinlock_acquire_nowait: test 1.2 failed.\n");
return 1;
}
if (spinlock_acquire_nowait(&lck))
{
fprintf(stderr, "spinlock_acquire_nowait: test 1.3 failed.\n");
return 1;
}
spinlock_release(&lck);
return 0;
}
static int acquire_time;
static void test2_helper(void* data)
{
SPINLOCK* lck = (SPINLOCK*)data;
unsigned long t1 = time(0);
spinlock_acquire(lck);
acquire_time = time(0) - t1;
spinlock_release(lck);
return;
}
/**
* test2 spinlock_acquire tests
*
* Check that spinlock correctly blocks another thread whilst the spinlock
* is held.
*
* Take out a lock.
* Start a second thread to take the same lock
* sleep for 10 seconds
* release lock
* verify that second thread took at least 8 seconds to obtain the lock
*/
static int test2()
{
SPINLOCK lck;
std::thread handle;
struct timespec sleeptime;
sleeptime.tv_sec = 10;
sleeptime.tv_nsec = 0;
acquire_time = 0;
spinlock_init(&lck);
spinlock_acquire(&lck);
handle = std::thread(test2_helper, (void*)&lck);
nanosleep(&sleeptime, NULL);
spinlock_release(&lck);
handle.join();
if (acquire_time < 8)
{
fprintf(stderr, "spinlock: test 2 failed.\n");
return 1;
}
return 0;
}
/**
* test3 spinlock_acquire tests process bound threads
*
* Check that spinlock correctly blocks all other threads whilst the spinlock
* is held.
*
* Start multiple threads that obtain spinlock and run process bound
*/
#define THREADS 5
#define ITERATIONS 50000
#define PROCESS_LOOP 10000
#define SECONDS 15
#define NANOTIME 100000
static int times_run, failures;
static volatile int active;
static int threadrun[THREADS];
static int nowait[THREADS];
static SPINLOCK lck;
static void test3_helper(void* data)
{
// SPINLOCK *lck = (SPINLOCK *)data;
int i;
int n = *(int*)data;
time_t rawtime;
#if defined (ADD_SOME_NANOSLEEP)
struct timespec sleeptime;
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = 1;
#endif
while (1)
{
if (spinlock_acquire_nowait(&lck))
{
nowait[n]++;
}
else
{
spinlock_acquire(&lck);
}
if (times_run++ > ITERATIONS)
{
break;
}
threadrun[n]++;
/*
* if (99 == (times_run % 100)) {
* time ( &rawtime );
* fprintf(stderr, "%s Done %d iterations of test, in thread %d.\n", asctime (localtime ( &rawtime
* )), times_run, n);
* }
*/
if (0 != active)
{
fprintf(stderr, "spinlock: test 3 failed with active non-zero after lock obtained.\n");
failures++;
}
else
{
active = 1;
for (i = 0; i < PROCESS_LOOP; i++)
{
}
}
active = 0;
spinlock_release(&lck);
for (i = 0; i < (4 * PROCESS_LOOP); i++)
{
}
#if defined (ADD_SOME_NANOSLEEP)
nanosleep(&sleeptime, NULL);
#endif
}
spinlock_release(&lck);
}
static int test3()
{
// SPINLOCK lck;
std::thread handle[THREADS];
int i;
int tnum[THREADS];
time_t rawtime;
times_run = 0;
active = 0;
failures = 0;
spinlock_init(&lck);
time (&rawtime);
fprintf(stderr, "%s Starting %d threads.\n", asctime (localtime (&rawtime)), THREADS);
for (i = 0; i < THREADS; i++)
{
threadrun[i] = 0;
tnum[i] = i;
handle[i] = std::thread(test3_helper, &tnum[i]);
}
for (i = 0; i < THREADS; i++)
{
fprintf(stderr,
"spinlock_test 3 thread %d ran %d times, no wait %d times before waits.\n",
i,
threadrun[i],
nowait[i]);
}
for (i = 0; i < THREADS; i++)
{
time (&rawtime);
fprintf(stderr,
"%s spinlock_test 3 finished sleeps, about to wait for thread %d.\n",
asctime (localtime (&rawtime)),
i);
handle[i].join();
}
for (i = 0; i < THREADS; i++)
{
fprintf(stderr,
"spinlock_test 3 thread %d ran %d times, no wait %d times.\n",
i,
threadrun[i],
nowait[i]);
}
time (&rawtime);
fprintf(stderr, "%s spinlock_test 3 completed, %d failures.\n", asctime (localtime (&rawtime)), failures);
return 0 == failures ? 0 : 1;
}
int main(int argc, char** argv)
{
int result = 0;
result += test1();
result += test2();
result += test3();
exit(result);
}