2023-07-06 15:11:19 +02:00
|
|
|
#include "orbis/utils/SharedCV.hpp"
|
2023-07-12 13:05:23 +02:00
|
|
|
#include "orbis/utils/Logs.hpp"
|
2023-07-06 15:11:19 +02:00
|
|
|
#include <linux/futex.h>
|
|
|
|
|
#include <syscall.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
namespace orbis::utils {
|
2024-01-13 18:57:02 +01:00
|
|
|
int shared_cv::impl_wait(shared_mutex &mutex, unsigned _val,
|
2023-07-06 15:11:19 +02:00
|
|
|
std::uint64_t usec_timeout) noexcept {
|
|
|
|
|
// Not supposed to fail
|
2023-11-13 19:38:21 +01:00
|
|
|
if (!_val) {
|
2023-07-06 15:11:19 +02:00
|
|
|
std::abort();
|
2023-11-13 19:38:21 +01:00
|
|
|
}
|
2023-07-06 15:11:19 +02:00
|
|
|
|
|
|
|
|
// Wait with timeout
|
|
|
|
|
struct timespec timeout {};
|
|
|
|
|
timeout.tv_nsec = (usec_timeout % 1000'000) * 1000;
|
|
|
|
|
timeout.tv_sec = (usec_timeout / 1000'000);
|
|
|
|
|
|
2024-01-13 18:57:02 +01:00
|
|
|
int result = 0;
|
|
|
|
|
|
2023-11-13 19:38:21 +01:00
|
|
|
while (true) {
|
2024-01-13 18:57:02 +01:00
|
|
|
result = syscall(SYS_futex, &m_value, FUTEX_WAIT, _val,
|
2023-11-13 19:38:21 +01:00
|
|
|
usec_timeout + 1 ? &timeout : nullptr, 0, 0);
|
|
|
|
|
if (result < 0) {
|
|
|
|
|
result = errno;
|
|
|
|
|
}
|
2023-07-06 15:11:19 +02:00
|
|
|
|
2023-11-13 19:38:21 +01:00
|
|
|
// Cleanup
|
|
|
|
|
const auto old = atomic_fetch_op(m_value, [&](unsigned &value) {
|
|
|
|
|
// Remove waiter if no signals
|
2024-01-13 18:57:02 +01:00
|
|
|
if (!(value & ~c_waiter_mask) && result != EAGAIN) {
|
2023-11-13 19:38:21 +01:00
|
|
|
value -= 1;
|
|
|
|
|
}
|
2023-07-06 15:11:19 +02:00
|
|
|
|
2023-11-13 19:38:21 +01:00
|
|
|
// Try to remove signal
|
|
|
|
|
if (value & c_signal_mask) {
|
|
|
|
|
value -= c_signal_one;
|
|
|
|
|
}
|
2023-07-07 12:49:59 +02:00
|
|
|
|
2023-11-13 19:38:21 +01:00
|
|
|
if (value & c_locked_mask) {
|
|
|
|
|
value -= c_locked_mask;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Lock is already acquired
|
|
|
|
|
if (old & c_locked_mask) {
|
2024-01-13 18:57:02 +01:00
|
|
|
return 0;
|
2023-11-13 19:38:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wait directly (waiter has been added)
|
|
|
|
|
if (old & c_signal_mask) {
|
2024-01-13 18:57:02 +01:00
|
|
|
return mutex.impl_wait();
|
2023-11-13 19:38:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Possibly spurious wakeup
|
2024-01-13 18:57:02 +01:00
|
|
|
if (result != EAGAIN) {
|
2023-11-13 19:38:21 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2024-01-13 18:57:02 +01:00
|
|
|
|
|
|
|
|
_val = old;
|
2023-07-06 15:11:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mutex.lock();
|
2024-01-13 18:57:02 +01:00
|
|
|
return result;
|
2023-07-06 15:11:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void shared_cv::impl_wake(shared_mutex &mutex, int _count) noexcept {
|
|
|
|
|
unsigned _old = m_value.load();
|
|
|
|
|
const bool is_one = _count == 1;
|
|
|
|
|
|
|
|
|
|
// Enqueue _count waiters
|
|
|
|
|
_count = std::min<int>(_count, _old & c_waiter_mask);
|
|
|
|
|
if (_count <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Try to lock the mutex
|
2023-07-07 12:49:59 +02:00
|
|
|
const bool locked = mutex.lock_forced(_count);
|
2023-07-06 15:11:19 +02:00
|
|
|
|
|
|
|
|
const int max_sig = atomic_op(m_value, [&](unsigned &value) {
|
|
|
|
|
// Verify the number of waiters
|
|
|
|
|
int max_sig = std::min<int>(_count, value & c_waiter_mask);
|
|
|
|
|
|
|
|
|
|
// Add lock signal (mutex was immediately locked)
|
2023-07-07 12:49:59 +02:00
|
|
|
if (locked && max_sig)
|
|
|
|
|
value |= c_locked_mask;
|
2023-07-12 13:05:23 +02:00
|
|
|
else if (locked)
|
|
|
|
|
std::abort();
|
2023-07-07 12:49:59 +02:00
|
|
|
|
|
|
|
|
// Add normal signals
|
|
|
|
|
value += c_signal_one * max_sig;
|
|
|
|
|
|
|
|
|
|
// Remove waiters
|
|
|
|
|
value -= max_sig;
|
2023-07-06 15:11:19 +02:00
|
|
|
_old = value;
|
|
|
|
|
return max_sig;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (max_sig < _count) {
|
2023-07-07 12:49:59 +02:00
|
|
|
// Fixup mutex
|
|
|
|
|
mutex.lock_forced(max_sig - _count);
|
2023-07-06 15:11:19 +02:00
|
|
|
_count = max_sig;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_count) {
|
|
|
|
|
// Wake up one thread + requeue remaining waiters
|
2023-07-07 20:37:24 +02:00
|
|
|
unsigned awake_count = locked ? 1 : 0;
|
|
|
|
|
if (auto r = syscall(SYS_futex, &m_value, FUTEX_REQUEUE, awake_count,
|
2023-07-12 13:05:23 +02:00
|
|
|
_count - awake_count, &mutex, 0);
|
2023-07-07 03:58:59 +02:00
|
|
|
r < _count) {
|
|
|
|
|
// Keep awaking waiters
|
2023-07-06 15:11:19 +02:00
|
|
|
return impl_wake(mutex, is_one ? 1 : INT_MAX);
|
2023-07-07 03:58:59 +02:00
|
|
|
}
|
2023-07-06 15:11:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // namespace orbis::utils
|