blob: 1bca2c834ebb122b47e8c29b9a8730e4e208afe8 [file] [log] [blame]
/** @file
Basic locks for threads
@section license License
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.
*/
#ifndef _I_Lock_h_
#define _I_Lock_h_
#include "libts.h"
#include "I_Thread.h"
#define MAX_LOCK_TIME HRTIME_MSECONDS(200)
#define THREAD_MUTEX_THREAD_HOLDING (-1024*1024)
class EThread;
typedef EThread *EThreadPtr;
typedef volatile EThreadPtr VolatileEThreadPtr;
inkcoreapi extern void lock_waiting(const char *file, int line, const char *handler);
inkcoreapi extern void lock_holding(const char *file, int line, const char *handler);
extern void lock_taken(const char *file, int line, const char *handler);
/**
Lock object used in continuations and threads.
The ProxyMutex class is the main synchronization object used
throughout the Event System. It is a reference counted object
that provides mutually exclusive access to a resource. Since the
Event System is multithreaded by design, the ProxyMutex is required
to protect data structures and state information that could
otherwise be affected by the action of concurrent threads.
A ProxyMutex object has an ink_mutex member (defined in ink_mutex.h)
which is a wrapper around the platform dependent mutex type. This
member allows the ProxyMutex to provide the functionallity required
by the users of the class without the burden of platform specific
function calls.
The ProxyMutex also has a reference to the current EThread holding
the lock as a back pointer for verifying that it is released
correctly.
Acquiring/Releasing locks:
Included with the ProxyMutex class, there are several macros that
allow you to lock/unlock the underlying mutex object.
*/
class ProxyMutex: public RefCountObj
{
public:
/**
Underlying mutex object.
The platform independent mutex for the ProxyMutex class. You
must not modify or set it directly.
*/
// coverity[uninit_member]
ink_mutex the_mutex;
/**
Backpointer to owning thread.
This is a pointer to the thread currently holding the mutex
lock. You must not modify or set this value directly.
*/
volatile EThreadPtr thread_holding;
int nthread_holding;
#ifdef DEBUG
ink_hrtime hold_time;
const char *file;
int line;
const char *handler;
# ifdef MAX_LOCK_TAKEN
int taken;
# endif //MAX_LOCK_TAKEN
# ifdef LOCK_CONTENTION_PROFILING
int total_acquires, blocking_acquires,
nonblocking_acquires, successful_nonblocking_acquires, unsuccessful_nonblocking_acquires;
void print_lock_stats(int flag);
# endif //LOCK_CONTENTION_PROFILING
#endif //DEBUG
void free();
/**
Constructor - use new_ProxyMutex() instead.
The constructor of a ProxyMutex object. Initializes the state
of the object but leaves the initialization of the mutex member
until it is needed (through init()). Do not use this constructor,
the preferred mechanism for creating a ProxyMutex is via the
new_ProxyMutex function, which provides a faster allocation.
*/
ProxyMutex()
{
thread_holding = NULL;
nthread_holding = 0;
#ifdef DEBUG
hold_time = 0;
file = NULL;
line = 0;
handler = NULL;
# ifdef MAX_LOCK_TAKEN
taken = 0;
# endif //MAX_LOCK_TAKEN
# ifdef LOCK_CONTENTION_PROFILING
total_acquires = 0;
blocking_acquires = 0;
nonblocking_acquires = 0;
successful_nonblocking_acquires = 0;
unsuccessful_nonblocking_acquires = 0;
# endif //LOCK_CONTENTION_PROFILING
#endif //DEBUG
// coverity[uninit_member]
}
/**
Initializes the underlying mutex object.
After constructing your ProxyMutex object, use this function
to initialize the underlying mutex object with an optional name.
@param name Name to identify this ProxyMutex. Its use depends
on the given platform.
*/
void init(const char *name = "UnnamedMutex") {
ink_mutex_init(&the_mutex, name);
}
};
// The ClassAlocator for ProxyMutexes
extern inkcoreapi ClassAllocator<ProxyMutex> mutexAllocator;
inline bool
Mutex_trylock(
#ifdef DEBUG
const char *afile, int aline, const char *ahandler,
#endif
ProxyMutex * m, EThread * t)
{
ink_assert(t != 0);
ink_assert(t == (EThread*)this_thread());
if (m->thread_holding != t) {
if (!ink_mutex_try_acquire(&m->the_mutex)) {
#ifdef DEBUG
lock_waiting(m->file, m->line, m->handler);
#ifdef LOCK_CONTENTION_PROFILING
m->unsuccessful_nonblocking_acquires++;
m->nonblocking_acquires++;
m->total_acquires++;
m->print_lock_stats(0);
#endif //LOCK_CONTENTION_PROFILING
#endif //DEBUG
return false;
}
ink_assert(m->thread_holding = t);
#ifdef DEBUG
m->file = afile;
m->line = aline;
m->handler = ahandler;
m->hold_time = ink_get_hrtime();
#ifdef MAX_LOCK_TAKEN
m->taken++;
#endif //MAX_LOCK_TAKEN
#endif //DEBUG
}
#ifdef DEBUG
#ifdef LOCK_CONTENTION_PROFILING
m->successful_nonblocking_acquires++;
m->nonblocking_acquires++;
m->total_acquires++;
m->print_lock_stats(0);
#endif //LOCK_CONTENTION_PROFILING
#endif //DEBUG
m->nthread_holding++;
return true;
}
inline bool
Mutex_trylock_spin(
#ifdef DEBUG
const char *afile, int aline, const char *ahandler,
#endif
ProxyMutex * m, EThread * t, int spincnt = 1)
{
ink_assert(t != 0);
if (m->thread_holding != t) {
int locked;
do {
if ((locked = ink_mutex_try_acquire(&m->the_mutex)))
break;
} while (--spincnt);
if (!locked) {
#ifdef DEBUG
lock_waiting(m->file, m->line, m->handler);
#ifdef LOCK_CONTENTION_PROFILING
m->unsuccessful_nonblocking_acquires++;
m->nonblocking_acquires++;
m->total_acquires++;
m->print_lock_stats(0);
#endif //LOCK_CONTENTION_PROFILING
#endif //DEBUG
return false;
}
m->thread_holding = t;
ink_assert(m->thread_holding);
#ifdef DEBUG
m->file = afile;
m->line = aline;
m->handler = ahandler;
m->hold_time = ink_get_hrtime();
#ifdef MAX_LOCK_TAKEN
m->taken++;
#endif //MAX_LOCK_TAKEN
#endif //DEBUG
}
#ifdef DEBUG
#ifdef LOCK_CONTENTION_PROFILING
m->successful_nonblocking_acquires++;
m->nonblocking_acquires++;
m->total_acquires++;
m->print_lock_stats(0);
#endif //LOCK_CONTENTION_PROFILING
#endif //DEBUG
m->nthread_holding++;
return true;
}
inline int
Mutex_lock(
#ifdef DEBUG
const char *afile, int aline, const char *ahandler,
#endif
ProxyMutex * m, EThread * t)
{
ink_assert(t != 0);
if (m->thread_holding != t) {
ink_mutex_acquire(&m->the_mutex);
m->thread_holding = t;
ink_assert(m->thread_holding);
#ifdef DEBUG
m->file = afile;
m->line = aline;
m->handler = ahandler;
m->hold_time = ink_get_hrtime();
#ifdef MAX_LOCK_TAKEN
m->taken++;
#endif //MAX_LOCK_TAKEN
#endif //DEBUG
}
#ifdef DEBUG
#ifdef LOCK_CONTENTION_PROFILING
m->blocking_acquires++;
m->total_acquires++;
m->print_lock_stats(0);
#endif // LOCK_CONTENTION_PROFILING
#endif //DEBUG
m->nthread_holding++;
return true;
}
inline void
Mutex_unlock(ProxyMutex * m, EThread * t)
{
if (m->nthread_holding) {
ink_assert(t == m->thread_holding);
m->nthread_holding--;
if (!m->nthread_holding) {
#ifdef DEBUG
if (ink_get_hrtime() - m->hold_time > MAX_LOCK_TIME)
lock_holding(m->file, m->line, m->handler);
#ifdef MAX_LOCK_TAKEN
if (m->taken > MAX_LOCK_TAKEN)
lock_taken(m->file, m->line, m->handler);
#endif //MAX_LOCK_TAKEN
m->file = NULL;
m->line = 0;
m->handler = NULL;
#endif //DEBUG
ink_assert(m->thread_holding);
m->thread_holding = 0;
ink_mutex_release(&m->the_mutex);
}
}
}
struct MutexLock
{
Ptr<ProxyMutex> m;
MutexLock():m(NULL)
{
};
MutexLock(
#ifdef DEBUG
const char *afile, int aline, const char *ahandler,
#endif //DEBUG
ProxyMutex * am, EThread * t):m(am)
{
Mutex_lock(
#ifdef DEBUG
afile, aline, ahandler,
#endif //DEBUG
m, t);
}
void set_and_take(
#ifdef DEBUG
const char *afile, int aline, const char *ahandler,
#endif //DEBUG
ProxyMutex * am, EThread * t)
{
m = am;
Mutex_lock(
#ifdef DEBUG
afile, aline, ahandler,
#endif //DEBUG
m, t);
}
void release()
{
if (m)
Mutex_unlock(m, m->thread_holding);
m.clear();
}
~MutexLock()
{
if (m)
Mutex_unlock(m, m->thread_holding);
}
int operator!() const { return false; }
operator bool() { return true; }
};
struct MutexTryLock
{
Ptr<ProxyMutex> m;
volatile bool lock_acquired;
MutexTryLock(
#ifdef DEBUG
const char *afile, int aline, const char *ahandler,
#endif //DEBUG
ProxyMutex * am, EThread * t)
{
lock_acquired = Mutex_trylock(
#ifdef DEBUG
afile, aline, ahandler,
#endif //DEBUG
am, t);
if (lock_acquired)
m = am;
}
MutexTryLock(
#ifdef DEBUG
const char *afile, int aline, const char *ahandler,
#endif //DEBUG
ProxyMutex * am, EThread * t, int sp)
{
lock_acquired = Mutex_trylock_spin(
#ifdef DEBUG
afile, aline, ahandler,
#endif //DEBUG
am, t, sp);
if (lock_acquired)
m = am;
}
~MutexTryLock()
{
if (m.m_ptr)
Mutex_unlock(m.m_ptr, m.m_ptr->thread_holding);
}
void release()
{
if (m.m_ptr) {
Mutex_unlock(m.m_ptr, m.m_ptr->thread_holding);
m.clear();
}
lock_acquired = false;
}
int operator!() const { return !lock_acquired; }
operator bool() const { return lock_acquired; }
};
inline void
ProxyMutex::free()
{
#ifdef DEBUG
#ifdef LOCK_CONTENTION_PROFILING
print_lock_stats(1);
#endif
#endif
ink_mutex_destroy(&the_mutex);
mutexAllocator.free(this);
}
// TODO should take optional mutex "name" identifier, to pass along to the init() fun
/**
Creates a new ProxyMutex object.
This is the preferred mechanism for constructing objects of the
ProxyMutex class. It provides you with faster allocation than
that of the normal constructor.
@return A pointer to a ProxyMutex object appropriate for the build
environment.
*/
inline ProxyMutex *
new_ProxyMutex()
{
ProxyMutex *m = mutexAllocator.alloc();
m->init();
return m;
}
/*------------------------------------------------------*\
| Macros |
\*------------------------------------------------------*/
/**
Blocks until the lock to the ProxyMutex is acquired.
This macro performs a blocking call until the lock to the ProxyMutex
is acquired. This call allocates a special object that holds the
lock to the ProxyMutex only for the scope of the function or
region. It is a good practice to delimit such scope explicitly
with '&#123;' and '&#125;'.
@param _l Arbitrary name for the lock to use in this call
@param _m A pointer to (or address of) a ProxyMutex object
@param _t The current EThread executing your code.
*/
# ifdef DEBUG
# define MUTEX_LOCK(_l,_m,_t) MutexLock _l(__FILE__,__LINE__,NULL,_m,_t)
# else
# define MUTEX_LOCK(_l,_m,_t) MutexLock _l(_m,_t)
# endif //DEBUG
# ifdef DEBUG
/**
Attempts to acquire the lock to the ProxyMutex.
This macro attempts to acquire the lock to the specified ProxyMutex
object in a non-blocking manner. After using the macro you can
see if it was successful by comparing the lock variable with true
or false (the variable name passed in the _l parameter).
@param _l Arbitrary name for the lock to use in this call (lock variable)
@param _m A pointer to (or address of) a ProxyMutex object
@param _t The current EThread executing your code.
*/
# define MUTEX_TRY_LOCK(_l,_m,_t) \
MutexTryLock _l(__FILE__,__LINE__,(char*)NULL,_m,_t)
/**
Attempts to acquire the lock to the ProxyMutex.
This macro performs up to the specified number of attempts to
acquire the lock on the ProxyMutex object. It does so by running
a busy loop (busy wait) '_sc' times. You should use it with care
since it blocks the thread during that time and wastes CPU time.
@param _l Arbitrary name for the lock to use in this call (lock variable)
@param _m A pointer to (or address of) a ProxyMutex object
@param _t The current EThread executing your code.
@param _sc The number of attempts or spin count. It must be a positive value.
*/
# define MUTEX_TRY_LOCK_SPIN(_l,_m,_t,_sc) \
MutexTryLock _l(__FILE__,__LINE__,(char*)NULL,_m,_t,_sc)
/**
Attempts to acquire the lock to the ProxyMutex.
This macro attempts to acquire the lock to the specified ProxyMutex
object in a non-blocking manner. After using the macro you can
see if it was successful by comparing the lock variable with true
or false (the variable name passed in the _l parameter).
@param _l Arbitrary name for the lock to use in this call (lock variable)
@param _m A pointer to (or address of) a ProxyMutex object
@param _t The current EThread executing your code.
@param _c Continuation whose mutex will be attempted to lock.
*/
# define MUTEX_TRY_LOCK_FOR(_l,_m,_t,_c) \
MutexTryLock _l(__FILE__,__LINE__,NULL,_m,_t)
# else //DEBUG
# define MUTEX_TRY_LOCK(_l,_m,_t) MutexTryLock _l(_m,_t)
# define MUTEX_TRY_LOCK_SPIN(_l,_m,_t,_sc) MutexTryLock _l(_m,_t,_sc)
# define MUTEX_TRY_LOCK_FOR(_l,_m,_t,_c) MutexTryLock _l(_m,_t)
# endif //DEBUG
/**
Releases the lock on a ProxyMutex.
This macro releases the lock on the ProxyMutex, provided it is
currently held. The lock must have been successfully acquired
with one of the MUTEX macros.
@param _l Arbitrary name for the lock to use in this call (lock
variable) It must be the same name as the one used to acquire the
lock.
*/
# define MUTEX_RELEASE(_l) (_l).release()
/////////////////////////////////////
// DEPRECATED DEPRECATED DEPRECATED
#ifdef DEBUG
# define MUTEX_TAKE_TRY_LOCK(_m,_t) \
Mutex_trylock(__FILE__,__LINE__,(char*)NULL,_m,_t)
# define MUTEX_TAKE_TRY_LOCK_FOR(_m,_t,_c) \
Mutex_trylock(__FILE__,__LINE__,(char*)NULL,_m,_t)
# define MUTEX_TAKE_TRY_LOCK_FOR_SPIN(_m,_t,_c,_sc) \
Mutex_trylock_spin(__FILE__,__LINE__,NULL,_m,_t,_sc)
#else
# define MUTEX_TAKE_TRY_LOCK(_m,_t) Mutex_trylock(_m,_t)
# define MUTEX_TAKE_TRY_LOCK_FOR(_m,_t,_c) Mutex_trylock(_m,_t)
# define MUTEX_TAKE_TRY_LOCK_FOR_SPIN(_m,_t,_c,_sc) \
Mutex_trylock_spin(_m,_t,_sc)
#endif
#ifdef DEBUG
# define MUTEX_TAKE_LOCK(_m,_t)\
Mutex_lock(__FILE__,__LINE__,(char*)NULL,_m,_t)
# define MUTEX_SET_AND_TAKE_LOCK(_s,_m,_t)\
_s.set_and_take(__FILE__,__LINE__,(char*)NULL,_m,_t)
# define MUTEX_TAKE_LOCK_FOR(_m,_t,_c) \
Mutex_lock(__FILE__,__LINE__,NULL,_m,_t)
#else
# define MUTEX_TAKE_LOCK(_m,_t) Mutex_lock(_m,_t)
# define MUTEX_SET_AND_TAKE_LOCK(_s,_m,_t)_s.set_and_take(_m,_t)
# define MUTEX_TAKE_LOCK_FOR(_m,_t,_c) Mutex_lock(_m,_t)
#endif //DEBUG
#define MUTEX_UNTAKE_LOCK(_m,_t) Mutex_unlock(_m,_t)
// DEPRECATED DEPRECATED DEPRECATED
/////////////////////////////////////
#endif // _Lock_h_