[Base] Remove unneeded delay scheduler
This commit is contained in:
parent
15950eec37
commit
4a36a7962c
|
@ -1,111 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/base/delay_scheduler.h"
|
|
||||||
#include "xenia/base/assert.h"
|
|
||||||
#include "xenia/base/logging.h"
|
|
||||||
|
|
||||||
namespace xe::internal {
|
|
||||||
|
|
||||||
DelaySchedulerBase::~DelaySchedulerBase() {
|
|
||||||
if (call_on_destruct_) {
|
|
||||||
for (auto& slot : deletion_queue_) {
|
|
||||||
// No thread safety in destructors anyway
|
|
||||||
if (slot.state == State::kWaiting) {
|
|
||||||
callback_(slot.info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DelaySchedulerBase::Collect() {
|
|
||||||
static_assert(atomic_state_t::is_always_lock_free,
|
|
||||||
"Locks are unsafe to use with thread suspension");
|
|
||||||
|
|
||||||
for (auto& slot : deletion_queue_) {
|
|
||||||
TryCollectOne_impl(slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DelaySchedulerBase::TryCollectOne_impl(deletion_record_t& slot) {
|
|
||||||
auto now = clock::now();
|
|
||||||
|
|
||||||
auto state = State::kWaiting;
|
|
||||||
// Try to lock the waiting slot for reading the other fields
|
|
||||||
if (slot.state.compare_exchange_strong(state, State::kDeleting,
|
|
||||||
std::memory_order_acq_rel)) {
|
|
||||||
if (now > slot.due) {
|
|
||||||
// Time has passed, call back now
|
|
||||||
callback_(slot.info);
|
|
||||||
slot.info = nullptr;
|
|
||||||
slot.state.store(State::kFree, std::memory_order_release);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// Oops it's not yet due
|
|
||||||
slot.state.store(State::kWaiting, std::memory_order_release);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DelaySchedulerBase::ScheduleAt_impl(
|
|
||||||
void* info, const clock::time_point& timeout_time) {
|
|
||||||
bool first_pass = true;
|
|
||||||
|
|
||||||
if (info == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
if (TryScheduleAt_impl(info, timeout_time)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (first_pass) {
|
|
||||||
first_pass = false;
|
|
||||||
XELOGE(
|
|
||||||
"`DelayScheduler::ScheduleAt(...)` stalled: list is full! Find out "
|
|
||||||
"why or increase `MAX_QUEUE`.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DelaySchedulerBase::TryScheduleAt_impl(
|
|
||||||
void* info, const clock::time_point& timeout_time) {
|
|
||||||
if (info == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& slot : deletion_queue_) {
|
|
||||||
// Clean up due item
|
|
||||||
TryCollectOne_impl(slot);
|
|
||||||
|
|
||||||
if (TryScheduleAtOne_impl(slot, info, timeout_time)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DelaySchedulerBase::TryScheduleAtOne_impl(deletion_record_t& slot,
|
|
||||||
void* info,
|
|
||||||
clock::time_point due) {
|
|
||||||
auto state = State::kFree;
|
|
||||||
if (slot.state.compare_exchange_strong(state, State::kInitializing,
|
|
||||||
std::memory_order_acq_rel)) {
|
|
||||||
slot.info = info;
|
|
||||||
slot.due = due;
|
|
||||||
slot.state.store(State::kWaiting, std::memory_order_release);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace xe::internal
|
|
|
@ -1,142 +0,0 @@
|
||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_BASE_DELAY_SCHEDULER_H_
|
|
||||||
#define XENIA_BASE_DELAY_SCHEDULER_H_
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <functional>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
// Put the implementation in a non templated base class to reduce compile time
|
|
||||||
// and code duplication
|
|
||||||
class DelaySchedulerBase {
|
|
||||||
protected: // Types
|
|
||||||
enum class State : uint_least8_t {
|
|
||||||
kFree = 0, // Slot is unsued
|
|
||||||
kInitializing, // Slot is reserved and currently written to by a thread
|
|
||||||
kWaiting, // The slot contains a pointer scheduled for deletion
|
|
||||||
kDeleting // A thread is currently deleting the pointer
|
|
||||||
};
|
|
||||||
using atomic_state_t = std::atomic<State>;
|
|
||||||
using clock = std::chrono::steady_clock;
|
|
||||||
struct deletion_record_t {
|
|
||||||
atomic_state_t state;
|
|
||||||
void* info;
|
|
||||||
clock::time_point due;
|
|
||||||
};
|
|
||||||
using deletion_queue_t = std::vector<deletion_record_t>;
|
|
||||||
using callback_t = std::function<void(void*)>;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Check all scheduled items in the queue and free any that are due using
|
|
||||||
/// `callback(info);`. Call this to reliably collect all due wait items in the
|
|
||||||
/// queue.
|
|
||||||
void Collect();
|
|
||||||
|
|
||||||
size_t size() { return deletion_queue_.size(); }
|
|
||||||
|
|
||||||
protected: // Implementation
|
|
||||||
DelaySchedulerBase(size_t queue_size, callback_t callback,
|
|
||||||
bool call_on_destruct)
|
|
||||||
: deletion_queue_(queue_size),
|
|
||||||
callback_(callback),
|
|
||||||
call_on_destruct_(call_on_destruct) {}
|
|
||||||
virtual ~DelaySchedulerBase();
|
|
||||||
|
|
||||||
void ScheduleAt_impl(void* info, const clock::time_point& timeout_time);
|
|
||||||
[[nodiscard]] bool TryScheduleAt_impl(void* info,
|
|
||||||
const clock::time_point& timeout_time);
|
|
||||||
|
|
||||||
/// Checks if the slot is due and if so, call back for it.
|
|
||||||
bool TryCollectOne_impl(deletion_record_t& slot);
|
|
||||||
|
|
||||||
[[nodiscard]] bool TryScheduleAtOne_impl(deletion_record_t& slot, void* info,
|
|
||||||
clock::time_point due);
|
|
||||||
|
|
||||||
private:
|
|
||||||
deletion_queue_t deletion_queue_;
|
|
||||||
callback_t callback_;
|
|
||||||
bool call_on_destruct_;
|
|
||||||
};
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
/// A lazy scheduler/timer.
|
|
||||||
/// Will wait at least the specified duration before invoking the callbacks but
|
|
||||||
/// might wait until it is destructed. Lockless thread-safe, will spinlock
|
|
||||||
/// though if the wait queue is full (except for `Try`... methods). Might use
|
|
||||||
/// any thread that calls any member to invoke callbacks of due wait items.
|
|
||||||
template <typename INFO>
|
|
||||||
class DelayScheduler : internal::DelaySchedulerBase {
|
|
||||||
public:
|
|
||||||
DelayScheduler(size_t queue_size, std::function<void(INFO*)> callback,
|
|
||||||
bool call_on_destruct)
|
|
||||||
: DelaySchedulerBase(
|
|
||||||
queue_size,
|
|
||||||
[callback](void* info) { callback(reinterpret_cast<INFO*>(info)); },
|
|
||||||
call_on_destruct){};
|
|
||||||
DelayScheduler(const DelayScheduler&) = delete;
|
|
||||||
DelayScheduler& operator=(const DelayScheduler&) = delete;
|
|
||||||
virtual ~DelayScheduler() {}
|
|
||||||
|
|
||||||
// From base class:
|
|
||||||
// void Collect();
|
|
||||||
|
|
||||||
/// Schedule an object for deletion at some point after `timeout_time` using
|
|
||||||
/// `callback(info);`. Will collect any wait items it encounters which can be
|
|
||||||
/// 0 or all, use `Collect()` to collect all due wait items. Blocks until a
|
|
||||||
/// free wait slot is found.
|
|
||||||
template <class Clock, class Duration>
|
|
||||||
void ScheduleAt(
|
|
||||||
INFO* info,
|
|
||||||
const std::chrono::time_point<Clock, Duration>& timeout_time) {
|
|
||||||
ScheduleAt(info,
|
|
||||||
std::chrono::time_point_cast<clock::time_point>(timeout_time));
|
|
||||||
}
|
|
||||||
/// Like `ScheduleAt` but does not block on full list.
|
|
||||||
template <class Clock, class Duration>
|
|
||||||
[[nodiscard]] bool TryScheduleAt(
|
|
||||||
INFO* info,
|
|
||||||
const std::chrono::time_point<Clock, Duration>& timeout_time) {
|
|
||||||
return TryScheduleAt(
|
|
||||||
info, std::chrono::time_point_cast<clock::time_point>(timeout_time));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScheduleAt(INFO* info, const clock::time_point& timeout_time) {
|
|
||||||
ScheduleAt_impl(info, timeout_time);
|
|
||||||
}
|
|
||||||
[[nodiscard]] bool TryScheduleAt(INFO* info,
|
|
||||||
const clock::time_point& timeout_time) {
|
|
||||||
return TryScheduleAt_impl(info, timeout_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Schedule a callback at some point after `rel_time` has passed.
|
|
||||||
template <class Rep, class Period>
|
|
||||||
void ScheduleAfter(INFO* info,
|
|
||||||
const std::chrono::duration<Rep, Period>& rel_time) {
|
|
||||||
ScheduleAt(info,
|
|
||||||
clock::now() + std::chrono::ceil<clock::duration>(rel_time));
|
|
||||||
}
|
|
||||||
/// Like `ScheduleAfter` but does not block.
|
|
||||||
template <class Rep, class Period>
|
|
||||||
[[nodiscard]] bool TryScheduleAfter(
|
|
||||||
INFO* info, const std::chrono::duration<Rep, Period>& rel_time) {
|
|
||||||
return TryScheduleAt(
|
|
||||||
info, clock::now() + std::chrono::ceil<clock::duration>(rel_time));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -11,7 +11,6 @@
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/chrono_steady_cast.h"
|
#include "xenia/base/chrono_steady_cast.h"
|
||||||
#include "xenia/base/delay_scheduler.h"
|
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/threading_timer_queue.h"
|
#include "xenia/base/threading_timer_queue.h"
|
||||||
|
|
||||||
|
@ -80,47 +79,6 @@ void AndroidShutdown() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// This is separately allocated for each (`HighResolution`)`Timer` object. It
|
|
||||||
// will be cleaned up some time (`timers_garbage_collector_delay`) after the
|
|
||||||
// posix timer was canceled because posix `timer_delete(...)` does not remove
|
|
||||||
// pending timer signals.
|
|
||||||
// https://stackoverflow.com/questions/49756114/linux-timer-pending-signal
|
|
||||||
struct timer_callback_info_t {
|
|
||||||
std::atomic_bool disarmed;
|
|
||||||
#if !XE_HAS_SIGEV_THREAD_ID
|
|
||||||
pthread_t target_thread;
|
|
||||||
#endif
|
|
||||||
std::function<void()> callback;
|
|
||||||
void* userdata;
|
|
||||||
|
|
||||||
timer_callback_info_t(std::function<void()> callback)
|
|
||||||
: disarmed(false),
|
|
||||||
#if !XE_HAS_SIGEV_THREAD_ID
|
|
||||||
target_thread(),
|
|
||||||
#endif
|
|
||||||
callback(callback),
|
|
||||||
userdata(nullptr) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// GC for timer signal info structs:
|
|
||||||
constexpr uint_fast8_t timers_garbage_collector_scale_ =
|
|
||||||
#if XE_HAS_SIGEV_THREAD_ID
|
|
||||||
1;
|
|
||||||
#else
|
|
||||||
2;
|
|
||||||
#endif
|
|
||||||
DelayScheduler<timer_callback_info_t> timers_garbage_collector_(
|
|
||||||
512 * timers_garbage_collector_scale_,
|
|
||||||
[](timer_callback_info_t* info) {
|
|
||||||
assert_not_null(info);
|
|
||||||
delete info;
|
|
||||||
},
|
|
||||||
true);
|
|
||||||
// Delay we have to assume it takes to clear all pending signals (maximum):
|
|
||||||
constexpr auto timers_garbage_collector_delay =
|
|
||||||
std::chrono::milliseconds(100 * timers_garbage_collector_scale_);
|
|
||||||
|
|
||||||
template <typename _Rep, typename _Period>
|
template <typename _Rep, typename _Period>
|
||||||
inline timespec DurationToTimeSpec(
|
inline timespec DurationToTimeSpec(
|
||||||
std::chrono::duration<_Rep, _Period> duration) {
|
std::chrono::duration<_Rep, _Period> duration) {
|
||||||
|
|
Loading…
Reference in New Issue