blob: 968c2f764340a5caea3d381f553592993d828c48 [file] [log] [blame]
#ifndef AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_
#define AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_
#include <linux/futex.h>
#include <string>
#include "aos/ipc_lib/aos_sync.h"
namespace aos::ipc_lib {
// Results of atomically loading the ownership state via RobustOwnershipTracker
// below. This allows the state to be compared and queried later.
class ThreadOwnerStatusSnapshot {
public:
ThreadOwnerStatusSnapshot() : futex_(0) {}
ThreadOwnerStatusSnapshot(aos_futex futex) : futex_(futex) {}
ThreadOwnerStatusSnapshot(const ThreadOwnerStatusSnapshot &) = default;
ThreadOwnerStatusSnapshot &operator=(const ThreadOwnerStatusSnapshot &) =
default;
ThreadOwnerStatusSnapshot(ThreadOwnerStatusSnapshot &&) = default;
ThreadOwnerStatusSnapshot &operator=(ThreadOwnerStatusSnapshot &&) = default;
// Returns if the owner died as noticed by the robust futex using Acquire
// memory ordering.
bool OwnerIsDead() const { return (futex_ & FUTEX_OWNER_DIED) != 0; }
// Returns true if no one has claimed ownership.
bool IsUnclaimed() const { return futex_ == 0; }
// Returns true if either ownership hasn't been acquired or the owner died.
bool IsUnclaimedOrOwnerIsDead() const {
return IsUnclaimed() || OwnerIsDead();
}
// Returns the thread ID (a.k.a. "tid") of the owning thread. Use this when
// trying to access the /proc entry that corresponds to the owning thread for
// example. Do not use the futex value directly.
pid_t tid() const { return futex_ & FUTEX_TID_MASK; }
bool operator==(const ThreadOwnerStatusSnapshot &other) const {
return other.futex_ == futex_;
}
private:
aos_futex futex_;
};
// This object reliably tracks a thread owning a resource. A single thread may
// possess multiple resources like senders and receivers. Each resource can have
// its own instance of this class. These instances are responsible for
// monitoring the thread that owns them. Each resource can use its instance of
// this class to reliably check whether the owning thread is no longer alive.
//
// All methods other than Load* must be accessed under a mutex.
class RobustOwnershipTracker {
public:
// Loads all the contents of the ownership tracker with Acquire memory
// ordering.
ThreadOwnerStatusSnapshot LoadAcquire() const {
return ThreadOwnerStatusSnapshot(
__atomic_load_n(&(mutex_.futex), __ATOMIC_ACQUIRE));
}
// Loads all the contents of the ownership tracker with Relaxed memory order.
ThreadOwnerStatusSnapshot LoadRelaxed() const {
return ThreadOwnerStatusSnapshot(
__atomic_load_n(&(mutex_.futex), __ATOMIC_RELAXED));
}
// Clears all ownership state.
//
// This should only really be called if you are 100% certain that the owner is
// dead. Use `LoadAquire().OwnerIsDead()` to determine this.
void ForceClear() {
// Must be opposite order of Acquire.
// We only deal with the futex here because we don't want to change anything
// about the linked list. We just want to release ownership here. We still
// want the kernel to know about this element via the linked list the next
// time someone takes ownership.
__atomic_store_n(&(mutex_.futex), 0, __ATOMIC_RELEASE);
}
// Returns true if this thread holds ownership.
bool IsHeldBySelf() { return death_notification_is_held(&mutex_); }
// Acquires ownership. Other threads will know that this thread holds the
// ownership or be notified if this thread dies.
void Acquire() { death_notification_init(&mutex_); }
// Releases ownership.
//
// This should only be called from the owning thread.
void Release() {
// Must be opposite order of Acquire.
death_notification_release(&mutex_);
}
// Marks the owner as dead if the specified tid is the current owner. In other
// words, after this call, a call to `LoadAcquire().OwnerIsDead()` may start
// returning true.
//
// The motivation here is for use in testing. DO NOT USE in production code.
// The logic here is only good enough for testing.
bool PretendThatOwnerIsDeadForTesting(pid_t tid);
// Returns a string representing this object.
std::string DebugString() const;
private:
// Robust futex to track ownership the normal way. The futex is inside the
// mutex here. We use the wrapper mutex because the death_notification_*
// functions operate on that instead of the futex directly.
//
// We use a futex here because:
// - futexes are fast.
// - The kernel can atomically clean up a dead thread and mark the futex
// appropriately.
// - Owners can clean up after dead threads.
aos_mutex mutex_;
};
} // namespace aos::ipc_lib
#endif // AOS_IPC_LIB_ROBUST_OWNERSHIP_TRACKER_H_