[Base] Remove unneeded delay scheduler

This commit is contained in:
Joel Linn 2022-03-12 18:45:42 +01:00 committed by Rick Gibbed
parent 15950eec37
commit 4a36a7962c
3 changed files with 0 additions and 295 deletions

View File

@ -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

View File

@ -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

View File

@ -11,7 +11,6 @@
#include "xenia/base/assert.h"
#include "xenia/base/chrono_steady_cast.h"
#include "xenia/base/delay_scheduler.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading_timer_queue.h"
@ -80,47 +79,6 @@ void AndroidShutdown() {
}
#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>
inline timespec DurationToTimeSpec(
std::chrono::duration<_Rep, _Period> duration) {