blob: f41c862ba7106b7f6efb874e9b61b9c0c1fe1a11 [file]
/** @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.
*/
#pragma once
#include "tscore/ink_platform.h"
#include "tscore/Diags.h"
#include "iocore/eventsystem/Thread.h"
#define MAX_LOCK_TIME HRTIME_MSECONDS(200)
#define THREAD_MUTEX_THREAD_HOLDING (-1024 * 1024)
/*------------------------------------------------------*\
| 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 '{' and '}'.
@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.
*/
// A weak version of the SCOPED_MUTEX_LOCK macro, allows the mutex to be a nullptr.
#ifdef DEBUG
#define WEAK_SCOPED_MUTEX_LOCK(_l, _m, _t) WeakMutexLock _l(MakeSourceLocation(), (char *)nullptr, _m, _t);
#else // DEBUG
#define WEAK_SCOPED_MUTEX_LOCK(_l, _m, _t) WeakMutexLock _l(_m, _t);
#endif // DEBUG
#ifdef DEBUG
#define SCOPED_MUTEX_LOCK(_l, _m, _t) MutexLock _l(MakeSourceLocation(), (char *)nullptr, _m, _t)
#else // DEBUG
#define SCOPED_MUTEX_LOCK(_l, _m, _t) MutexLock _l(_m, _t)
#endif // 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.
*/
#ifdef DEBUG
#define WEAK_MUTEX_TRY_LOCK(_l, _m, _t) WeakMutexTryLock _l(MakeSourceLocation(), (char *)nullptr, _m, _t);
#else // DEBUG
#define WEAK_MUTEX_TRY_LOCK(_l, _m, _t) WeakMutexTryLock _l(_m, _t);
#endif // DEBUG
#ifdef DEBUG
#define MUTEX_TRY_LOCK(_l, _m, _t) MutexTryLock _l(MakeSourceLocation(), (char *)nullptr, _m, _t)
#else // DEBUG
#define MUTEX_TRY_LOCK(_l, _m, _t) 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(MakeSourceLocation(), (char *)nullptr, _m, _t)
#else
#define MUTEX_TAKE_TRY_LOCK(_m, _t) Mutex_trylock(_m, _t)
#endif
#ifdef DEBUG
#define MUTEX_TAKE_LOCK(_m, _t) Mutex_lock(MakeSourceLocation(), (char *)nullptr, _m, _t)
#else
#define MUTEX_TAKE_LOCK(_m, _t) Mutex_lock(_m, _t)
#endif // DEBUG
#define MUTEX_UNTAKE_LOCK(_m, _t) Mutex_unlock(_m, _t)
// DEPRECATED DEPRECATED DEPRECATED
/////////////////////////////////////
class EThread;
using EThreadPtr = EThread *;
#if DEBUG
extern void lock_waiting(const SourceLocation &, const char *handler);
extern void lock_holding(const SourceLocation &, const char *handler);
extern void lock_taken(const SourceLocation &, const char *handler);
#endif
/**
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 functionality 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.
*/
EThreadPtr thread_holding;
int nthread_holding;
#ifdef DEBUG
ink_hrtime hold_time;
SourceLocation srcloc;
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() override;
/**
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()
#ifdef DEBUG
: srcloc(nullptr, nullptr, 0)
#endif
{
thread_holding = nullptr;
nthread_holding = 0;
#ifdef DEBUG
hold_time = 0;
handler = nullptr;
#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()
{
ink_mutex_init(&the_mutex);
}
};
// The ClassAllocator for ProxyMutexes
extern ClassAllocator<ProxyMutex, false> mutexAllocator;
inline bool
Mutex_trylock(
#ifdef DEBUG
const SourceLocation &location, const char *ahandler,
#endif
ProxyMutex *m, EThread *t)
{
ink_assert(t != nullptr);
ink_assert(t == reinterpret_cast<EThread *>(this_thread()));
if (m->thread_holding != t) {
if (!ink_mutex_try_acquire(&m->the_mutex)) {
#ifdef DEBUG
lock_waiting(m->srcloc, 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;
#ifdef DEBUG
m->srcloc = location;
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(
#ifdef DEBUG
const SourceLocation &location, const char *ahandler,
#endif
Ptr<ProxyMutex> &m, EThread *t)
{
return Mutex_trylock(
#ifdef DEBUG
location, ahandler,
#endif
m.get(), t);
}
inline int
Mutex_lock(
#ifdef DEBUG
const SourceLocation &location, const char *ahandler,
#endif
ProxyMutex *m, EThread *t)
{
ink_assert(t != nullptr);
if (m->thread_holding != t) {
ink_mutex_acquire(&m->the_mutex);
m->thread_holding = t;
ink_assert(m->thread_holding);
#ifdef DEBUG
m->srcloc = location;
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 int
Mutex_lock(
#ifdef DEBUG
const SourceLocation &location, const char *ahandler,
#endif
Ptr<ProxyMutex> &m, EThread *t)
{
return Mutex_lock(
#ifdef DEBUG
location, ahandler,
#endif
m.get(), t);
}
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->srcloc, m->handler);
#ifdef MAX_LOCK_TAKEN
if (m->taken > MAX_LOCK_TAKEN)
lock_taken(m->srcloc, m->handler);
#endif // MAX_LOCK_TAKEN
m->srcloc = SourceLocation(nullptr, nullptr, 0);
m->handler = nullptr;
#endif // DEBUG
ink_assert(m->thread_holding);
m->thread_holding = nullptr;
ink_mutex_release(&m->the_mutex);
}
}
}
inline void
Mutex_unlock(Ptr<ProxyMutex> &m, EThread *t)
{
Mutex_unlock(m.get(), t);
}
class WeakMutexLock
{
private:
Ptr<ProxyMutex> m;
bool locked_p{false};
public:
WeakMutexLock() = default;
WeakMutexLock(
#ifdef DEBUG
const SourceLocation &location, const char *ahandler,
#endif // DEBUG
Ptr<ProxyMutex> &am, EThread *t)
: m(am), locked_p(true)
{
if (m.get()) {
Mutex_lock(
#ifdef DEBUG
location, ahandler,
#endif // DEBUG
m, t);
}
}
WeakMutexLock(WeakMutexLock &) = delete;
WeakMutexLock &operator=(const WeakMutexLock &) = delete;
WeakMutexLock(WeakMutexLock &&) = delete;
WeakMutexLock &
operator=(WeakMutexLock &&orig)
{
if (&orig != this) {
std::swap(m, orig.m);
std::swap(locked_p, orig.locked_p);
}
return *this;
}
void
release()
{
if (locked_p && m.get()) {
Mutex_unlock(m, m->thread_holding);
}
locked_p = false;
}
~WeakMutexLock() { this->release(); }
};
/** Scoped lock class for ProxyMutex
*/
class MutexLock
{
private:
Ptr<ProxyMutex> m{};
bool locked_p{false};
public:
MutexLock() = default;
MutexLock(
#ifdef DEBUG
const SourceLocation &location, const char *ahandler,
#endif // DEBUG
Ptr<ProxyMutex> &am, EThread *t)
: m(am), locked_p(true)
{
Mutex_lock(
#ifdef DEBUG
location, ahandler,
#endif // DEBUG
m, t);
}
MutexLock(MutexLock &) = delete;
MutexLock &operator=(const MutexLock &) = delete;
MutexLock(MutexLock &&) = delete;
MutexLock &
operator=(MutexLock &&orig)
{
if (&orig != this) {
std::swap(m, orig.m);
std::swap(locked_p, orig.locked_p);
}
return *this;
}
void
release()
{
if (locked_p) {
Mutex_unlock(m, m->thread_holding);
}
locked_p = false;
}
~MutexLock() { this->release(); }
};
/** Scoped try lock class for ProxyMutex
*/
class WeakMutexTryLock
{
private:
Ptr<ProxyMutex> m;
bool lock_acquired{false};
public:
WeakMutexTryLock() = default;
WeakMutexTryLock(
#ifdef DEBUG
const SourceLocation &location, const char *ahandler,
#endif // DEBUG
Ptr<ProxyMutex> &am, EThread *t)
: m(am)
{
if (m.get()) {
lock_acquired = Mutex_trylock(
#ifdef DEBUG
location, ahandler,
#endif // DEBUG
m, t);
} else {
lock_acquired = true;
}
}
WeakMutexTryLock(WeakMutexTryLock &) = delete;
WeakMutexTryLock &operator=(const WeakMutexTryLock &) = delete;
WeakMutexTryLock(WeakMutexTryLock &&) = delete;
WeakMutexTryLock &
operator=(WeakMutexTryLock &&orig)
{
if (&orig != this) {
std::swap(m, orig.m);
std::swap(lock_acquired, orig.lock_acquired);
}
return *this;
}
~WeakMutexTryLock()
{
if (lock_acquired && m.get()) {
Mutex_unlock(m, m->thread_holding);
}
lock_acquired = false;
}
/** Spin till lock is acquired
*/
void
acquire(EThread *t)
{
lock_acquired = true;
if (m.get()) {
MUTEX_TAKE_LOCK(m, t);
}
}
void
release()
{
if (lock_acquired && m.get()) {
Mutex_unlock(m, m->thread_holding);
}
lock_acquired = false;
}
bool
is_locked() const
{
return lock_acquired;
}
const ProxyMutex *
get_mutex() const
{
return m.get();
}
};
/** Scoped try lock class for ProxyMutex
*/
class MutexTryLock
{
private:
Ptr<ProxyMutex> m;
bool lock_acquired{false};
public:
MutexTryLock() = default;
MutexTryLock(
#ifdef DEBUG
const SourceLocation &location, const char *ahandler,
#endif // DEBUG
Ptr<ProxyMutex> &am, EThread *t)
: m(am)
{
lock_acquired = Mutex_trylock(
#ifdef DEBUG
location, ahandler,
#endif // DEBUG
m, t);
}
MutexTryLock(MutexTryLock &) = delete;
MutexTryLock &operator=(const MutexTryLock &) = delete;
MutexTryLock(MutexTryLock &&) = delete;
MutexTryLock &
operator=(MutexTryLock &&orig)
{
if (&orig != this) {
std::swap(m, orig.m);
std::swap(lock_acquired, orig.lock_acquired);
}
return *this;
}
~MutexTryLock()
{
if (lock_acquired) {
Mutex_unlock(m, m->thread_holding);
}
}
/** Spin till lock is acquired
*/
void
acquire(EThread *t)
{
MUTEX_TAKE_LOCK(m, t);
lock_acquired = true;
}
void
release()
{
if (lock_acquired) {
Mutex_unlock(m, m->thread_holding);
}
lock_acquired = false;
}
bool
is_locked() const
{
return lock_acquired;
}
const ProxyMutex *
get_mutex() const
{
return m.get();
}
};
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;
}