blob: 8bd986c6a6e2b1f54b1f4998e9713c7b4c95e387 [file] [log] [blame]
#ifndef AOS_MUTEX_H_
#define AOS_MUTEX_H_
#include "aos/macros.h"
#include "aos/type_traits/type_traits.h"
#include "aos/die.h"
#include "aos/linux_code/ipc_lib/aos_sync.h"
namespace aos {
class Condition;
// An abstraction of a mutex that is easy to implement for environments other
// than Linux too.
// If there are multiple threads or processes contending for the mutex,
// higher priority ones will succeed in locking first,
// and tasks of equal priorities have the same chance of getting the lock.
// To deal with priority inversion, the linux implementation does priority
// inheritance.
// Before destroying a mutex, it is important to make sure it isn't locked.
// Otherwise, the destructor will LOG(FATAL).
class Mutex {
public:
// States that signify the result of TryLock.
enum class State {
// The mutex was acquired successfully.
kLocked,
// TryLock tried to grab the mutex and failed.
kLockFailed,
// The previous owner of the mutex died.
kOwnerDied,
};
// Creates an unlocked mutex.
Mutex() : impl_() {
static_assert(shm_ok<Mutex>::value,
"Mutex is not safe for use in shared memory.");
}
// Verifies that it isn't locked.
//
// This is important because freeing a locked mutex means there is freed
// memory in the middle of the robust list, which breaks things horribly.
~Mutex() = default;
// Locks the mutex. If it fails, it calls LOG(FATAL).
// Returns true if the previous owner died instead of unlocking nicely.
bool Lock() __attribute__((warn_unused_result));
// Unlocks the mutex. Fails like Lock.
// Multiple unlocking is undefined.
void Unlock();
// Locks the mutex unless it is already locked.
// Returns the new state of the mutex.
// Doesn't wait for the mutex to be unlocked if it is locked.
State TryLock() __attribute__((warn_unused_result));
// Returns true iff the current task has this mutex locked.
// This is mainly for IPCRecursiveMutexLocker to use.
bool OwnedBySelf() const;
private:
aos_mutex impl_;
friend class Condition; // for access to impl_
};
// A class that locks a Mutex when constructed and unlocks it when destructed.
// Designed to be used as a local variable so that
// the mutex will be unlocked when the scope is exited.
// This one immediately Dies if the previous owner died. This makes it a good
// choice for mutexes that are only used within a single process, but NOT for
// mutexes shared by multiple processes. For those, use IPCMutexLocker.
class MutexLocker {
public:
explicit MutexLocker(Mutex *mutex) : mutex_(mutex) {
if (__builtin_expect(mutex_->Lock(), false)) {
::aos::Die("previous owner of mutex %p died but it shouldn't be able to",
this);
}
}
~MutexLocker() {
mutex_->Unlock();
}
private:
Mutex *const mutex_;
DISALLOW_COPY_AND_ASSIGN(MutexLocker);
};
// A version of MutexLocker which reports the previous owner dying instead of
// immediately LOG(FATAL)ing.
class IPCMutexLocker {
public:
explicit IPCMutexLocker(Mutex *mutex)
: mutex_(mutex), owner_died_(mutex_->Lock()) {}
~IPCMutexLocker() {
if (__builtin_expect(!owner_died_checked_, false)) {
::aos::Die("nobody checked if the previous owner of mutex %p died", this);
}
mutex_->Unlock();
}
// Whether or not the previous owner died. If this is not called at least
// once, the destructor will ::aos::Die.
__attribute__((warn_unused_result)) bool owner_died() {
owner_died_checked_ = true;
return __builtin_expect(owner_died_, false);
}
private:
Mutex *const mutex_;
const bool owner_died_;
bool owner_died_checked_ = false;
DISALLOW_COPY_AND_ASSIGN(IPCMutexLocker);
};
// A version of IPCMutexLocker which only locks (and unlocks) the mutex if the
// current task does not already hold it.
class IPCRecursiveMutexLocker {
public:
explicit IPCRecursiveMutexLocker(Mutex *mutex)
: mutex_(mutex),
locked_(!mutex_->OwnedBySelf()),
owner_died_(locked_ ? mutex_->Lock() : false) {}
~IPCRecursiveMutexLocker() {
if (__builtin_expect(!owner_died_checked_, false)) {
::aos::Die("nobody checked if the previous owner of mutex %p died", this);
}
if (locked_) mutex_->Unlock();
}
// Whether or not the previous owner died. If this is not called at least
// once, the destructor will ::aos::Die.
__attribute__((warn_unused_result)) bool owner_died() {
owner_died_checked_ = true;
return __builtin_expect(owner_died_, false);
}
private:
Mutex *const mutex_;
const bool locked_, owner_died_;
bool owner_died_checked_ = false;
DISALLOW_COPY_AND_ASSIGN(IPCRecursiveMutexLocker);
};
} // namespace aos
#endif // AOS_MUTEX_H_