Related refactoring to updated of ThrottleList to use new Scheduler.

This commit is contained in:
Jari Sundell
2025-05-12 10:38:07 +02:00
committed by GitHub
parent 6d888c008d
commit 9ff373fe58
33 changed files with 1005 additions and 230 deletions
+96
View File
@@ -0,0 +1,96 @@
#ifndef LIBTORRENT_HELPERS_MOCK_COMPARE_H
#define LIBTORRENT_HELPERS_MOCK_COMPARE_H
#include <algorithm>
#include <type_traits>
#include <torrent/event.h>
#include <torrent/net/socket_address.h>
// Compare arguments to mock functions with what is expected. The lhs
// are the expected arguments, rhs are the ones called with.
template <typename Arg>
inline bool mock_compare_arg(Arg lhs, Arg rhs) { return lhs == rhs; }
template <int I, typename A, typename... Args>
typename std::enable_if<I == 1, int>::type
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
return mock_compare_arg(std::get<I>(lhs), std::get<I - 1>(rhs)) ? 0 : 1;
}
template <int I, typename A, typename... Args>
typename std::enable_if<1 < I, int>::type
mock_compare_tuple(const std::tuple<A, Args...>& lhs, const std::tuple<Args...>& rhs) {
auto res = mock_compare_tuple<I - 1>(lhs, rhs);
if (res != 0)
return res;
return mock_compare_arg(std::get<I>(lhs), std::get<I - 1>(rhs)) ? 0 : I;
}
//template <typename T, typename std::enable_if<!std::is_const<T>::value, int>::type = 0>
template <typename T>
struct mock_compare_map {
typedef std::map<const T*, const T*> values_type;
static T* begin_pointer() { return reinterpret_cast<T*>(0x1000); }
static T* end_pointer() { return reinterpret_cast<T*>(0x2000); }
static bool is_key(const T* k) {
return k >= begin_pointer() && k < end_pointer();
}
static bool has_key(const T* k) {
return values.find(k) != values.end();
}
static bool has_value(const T* v) {
return std::find_if(values.begin(), values.end(), [v](typename values_type::value_type& kv) { return v == kv.second; }) != values.end();
}
static const T* get(const T* k) {
auto itr = values.find(k);
CPPUNIT_ASSERT_MESSAGE("mock_compare_map get failed, not inserted", itr != values.end());
return itr->second;
}
static values_type values;
};
template<typename T>
typename mock_compare_map<T>::values_type mock_compare_map<T>::values;
template<typename T>
void mock_compare_add(T* v) {
mock_compare_map<T>::add_value(v);
}
//
// Specialize:
//
template <>
inline bool mock_compare_arg<sockaddr*>(sockaddr* lhs, sockaddr* rhs) {
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
}
template <>
inline bool mock_compare_arg<const sockaddr*>(const sockaddr* lhs, const sockaddr* rhs) {
return lhs != nullptr && rhs != nullptr && torrent::sa_equal(lhs, rhs);
}
template <>
inline bool mock_compare_arg<torrent::Event*>(torrent::Event* lhs, torrent::Event* rhs) {
if (mock_compare_map<torrent::Event>::is_key(lhs)) {
if (!mock_compare_map<torrent::Event>::has_value(rhs)) {
mock_compare_map<torrent::Event>::values[lhs] = rhs;
return true;
}
return mock_compare_map<torrent::Event>::has_key(lhs) && mock_compare_map<torrent::Event>::get(lhs) == rhs;
}
return lhs == rhs;
}
#endif
+182
View File
@@ -0,0 +1,182 @@
#include "config.h"
#include "test/helpers/mock_function.h"
#include <fcntl.h>
#include <iostream>
#include <unistd.h>
#include <torrent/event.h>
#include <torrent/net/socket_address.h>
#include <torrent/net/fd.h>
#include <torrent/utils/log.h>
#include <torrent/utils/random.h>
#define MOCK_CLEANUP_MAP(MOCK_FUNC) \
CPPUNIT_ASSERT_MESSAGE("expected mock function calls not completed for '" #MOCK_FUNC "'", mock_cleanup_map(&MOCK_FUNC) || ignore_assert);
#define MOCK_LOG(log_fmt, ...) \
lt_log_print(torrent::LOG_MOCK_CALLS, "%s: " log_fmt, __func__, __VA_ARGS__);
void
mock_clear(bool ignore_assert) {
MOCK_CLEANUP_MAP(torrent::fd__accept);
MOCK_CLEANUP_MAP(torrent::fd__bind);
MOCK_CLEANUP_MAP(torrent::fd__close);
MOCK_CLEANUP_MAP(torrent::fd__connect);
MOCK_CLEANUP_MAP(torrent::fd__fcntl_int);
MOCK_CLEANUP_MAP(torrent::fd__listen);
MOCK_CLEANUP_MAP(torrent::fd__setsockopt_int);
MOCK_CLEANUP_MAP(torrent::fd__socket);
MOCK_CLEANUP_MAP(torrent::poll_event_open);
MOCK_CLEANUP_MAP(torrent::poll_event_close);
MOCK_CLEANUP_MAP(torrent::poll_event_closed);
MOCK_CLEANUP_MAP(torrent::poll_event_insert_read);
MOCK_CLEANUP_MAP(torrent::poll_event_insert_write);
MOCK_CLEANUP_MAP(torrent::poll_event_insert_error);
MOCK_CLEANUP_MAP(torrent::poll_event_remove_read);
MOCK_CLEANUP_MAP(torrent::poll_event_remove_write);
MOCK_CLEANUP_MAP(torrent::poll_event_remove_error);
MOCK_CLEANUP_MAP(torrent::random_uniform_uint16);
MOCK_CLEANUP_MAP(torrent::random_uniform_uint32);
mock_compare_map<torrent::Event>::values.clear();
};
void
mock_init() {
log_add_group_output(torrent::LOG_MOCK_CALLS, "test_output");
mock_clear(true);
}
void
mock_cleanup() {
mock_clear(false);
}
void
mock_redirect_defaults([[maybe_unused]] mock_redirect_flags flags) {
mock_redirect(torrent::fd__close, std::function<int(int fildes)>([](int fildes) { return ::close(fildes); }));
mock_redirect(torrent::fd__fcntl_int, std::function<int(int fildes, int cmd, int arg)>([](int fildes, int cmd, int arg) { return ::fcntl(fildes, cmd, arg); }));
mock_redirect(torrent::fd__setsockopt_int, std::function<int(int socket, int level, int option_name, int option_value)>([](int socket, int level, int option_name, int option_value) { return ::setsockopt(socket, level, option_name, &option_value, sizeof(int)); }));
mock_redirect(torrent::fd__socket, std::function<int(int domain, int type, int protocol)>([](int domain, int type, int protocol) { return ::socket(domain, type, protocol); }));
}
namespace torrent {
//
// Mock functions for 'torrent/net/fd.h':
//
int fd__accept(int socket, sockaddr *address, socklen_t *address_len) {
MOCK_LOG("entry socket:%i address:%s address_len:%u",
socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)(*address_len));
auto ret = mock_call<int>(__func__, &torrent::fd__accept, socket, address, address_len);
MOCK_LOG("exit socket:%i address:%s address_len:%u",
socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)(*address_len));
return ret;
}
int fd__bind(int socket, const sockaddr *address, socklen_t address_len) {
MOCK_LOG("socket:%i address:%s address_len:%u",
socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)address_len);
return mock_call<int>(__func__, &torrent::fd__bind, socket, address, address_len);
}
int fd__close(int fildes) {
MOCK_LOG("filedes:%i", fildes);
return mock_call<int>(__func__, &torrent::fd__close, fildes);
}
int fd__connect(int socket, const sockaddr *address, socklen_t address_len) {
MOCK_LOG("socket:%i address:%s address_len:%u",
socket, torrent::sa_pretty_str(address).c_str(), (unsigned int)address_len);
return mock_call<int>(__func__, &torrent::fd__connect, socket, address, address_len);
}
int fd__fcntl_int(int fildes, int cmd, int arg) {
MOCK_LOG("filedes:%i cmd:%i arg:%i", fildes, cmd, arg);
return mock_call<int>(__func__, &torrent::fd__fcntl_int, fildes, cmd, arg);
}
int fd__listen(int socket, int backlog) {
MOCK_LOG("socket:%i backlog:%i", socket, backlog);
return mock_call<int>(__func__, &torrent::fd__listen, socket, backlog);
}
int fd__setsockopt_int(int socket, int level, int option_name, int option_value) {
MOCK_LOG("socket:%i level:%i option_name:%i option_value:%i",
socket, level, option_name, option_value);
return mock_call<int>(__func__, &torrent::fd__setsockopt_int, socket, level, option_name, option_value);
}
int fd__socket(int domain, int type, int protocol) {
MOCK_LOG("domain:%i type:%i protocol:%i", domain, type, protocol);
return mock_call<int>(__func__, &torrent::fd__socket, domain, type, protocol);
}
//
// Mock functions for 'torrent/event.h':
//
void poll_event_open(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_open, event);
}
void poll_event_close(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_close, event);
}
void poll_event_closed(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_closed, event);
}
void poll_event_insert_read(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_insert_read, event);
}
void poll_event_insert_write(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_insert_write, event);
}
void poll_event_insert_error(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_insert_error, event);
}
void poll_event_remove_read(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_remove_read, event);
}
void poll_event_remove_write(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_remove_write, event);
}
void poll_event_remove_error(Event* event) {
MOCK_LOG("fd:%i type_name:%s", event->file_descriptor(), event->type_name());
return mock_call<void>(__func__, &torrent::poll_event_remove_error, event);
}
//
// Mock functions for 'torrent/utils/random.h':
//
uint16_t random_uniform_uint16(uint16_t min, uint16_t max) {
MOCK_LOG("min:%" PRIu16 " max:%" PRIu16, min, max);
return mock_call<uint16_t>(__func__, &torrent::random_uniform_uint16, min, max);
}
uint32_t random_uniform_uint32(uint32_t min, uint32_t max) {
MOCK_LOG("min:%" PRIu32 " max:%" PRIu32, min, max);
return mock_call<uint32_t>(__func__, &torrent::random_uniform_uint32, min, max);
}
}
+176
View File
@@ -0,0 +1,176 @@
#ifndef LIBTORRENT_HELPERS_MOCK_FUNCTION_H
#define LIBTORRENT_HELPERS_MOCK_FUNCTION_H
#include <functional>
#include <map>
#include <mutex>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <cppunit/extensions/HelperMacros.h>
#include "test/helpers/mock_compare.h"
namespace torrent {
extern int fd__accept(int socket, sockaddr *address, socklen_t *address_len);
extern int fd__bind(int socket, const sockaddr *address, socklen_t address_len);
extern int fd__close(int fildes);
extern int fd__connect(int socket, const sockaddr *address, socklen_t address_len);
extern int fd__fcntl_int(int fildes, int cmd, int arg);
extern int fd__listen(int socket, int backlog);
extern int fd__setsockopt_int(int socket, int level, int option_name, int option_value);
extern int fd__socket(int domain, int type, int protocol);
}
enum mock_redirect_flags {
mock_redirect_all = ~0,
};
void mock_init();
void mock_cleanup();
void mock_redirect_defaults(mock_redirect_flags flags = mock_redirect_all);
template<typename R, typename... Args>
struct mock_function_map {
typedef std::tuple<R, Args...> call_type;
typedef std::vector<call_type> call_list_type;
typedef std::map<void*, call_list_type> func_map_type;
typedef std::function<R (Args...)> function_type;
typedef std::map<void*, function_type> redirect_map_type;
static std::mutex mutex;
static func_map_type functions;
static redirect_map_type redirects;
static bool cleanup(void* fn) {
std::lock_guard<std::mutex> lock(mutex);
redirects.erase(fn);
return functions.erase(fn) == 0;
}
static R ret_erase(void* fn) {
auto itr = functions.find(fn);
auto ret = std::get<0>(itr->second.front());
itr->second.erase(itr->second.begin());
if (itr->second.empty())
functions.erase(itr);
return ret;
}
};
template<typename R, typename... Args>
std::mutex mock_function_map<R, Args...>::mutex;
template<typename R, typename... Args>
typename mock_function_map<R, Args...>::func_map_type mock_function_map<R, Args...>::functions;
template<typename R, typename... Args>
typename mock_function_map<R, Args...>::redirect_map_type mock_function_map<R, Args...>::redirects;
struct mock_void {};
template<typename R, typename... Args>
struct mock_function_type {
typedef mock_function_map<R, Args...> type;
static int compare_expected(typename type::call_type lhs, Args... rhs) {
return mock_compare_tuple<sizeof...(Args)>(lhs, std::make_tuple(rhs...));
}
static R ret_erase(void* fn) { return type::ret_erase(fn); }
static bool has_redirect(void* fn) {
std::lock_guard<std::mutex> lock(type::mutex);
return type::redirects.find(fn) != type::redirects.end();
}
static R call_redirect(void* fn, Args... args) {
std::lock_guard<std::mutex> lock(type::mutex);
return type::redirects.find(fn)->second(args...);
}
};
template<typename... Args>
struct mock_function_type<void, Args...> {
typedef mock_function_map<mock_void, Args...> type;
static int compare_expected(typename type::call_type lhs, Args... rhs) {
return mock_compare_tuple<sizeof...(Args)>(lhs, std::make_tuple(rhs...));
}
static void ret_erase(void* fn) { type::ret_erase(fn); }
static bool has_redirect(void* fn) {
std::lock_guard<std::mutex> lock(type::mutex);
return type::redirects.find(fn) != type::redirects.end();
}
static void call_redirect(void* fn, Args... args) {
std::lock_guard<std::mutex> lock(type::mutex);
type::redirects.find(fn)->second(args...);
}
};
template<typename R, typename... Args>
bool
mock_cleanup_map(R fn[[gnu::unused]](Args...)) {
return mock_function_type<R, Args...>::type::cleanup(reinterpret_cast<void*>(fn));
}
template<typename R, typename... Args>
void
mock_expect(R fn(Args...), R ret, Args... args) {
typedef mock_function_map<R, Args...> mock_map;
std::lock_guard<std::mutex> lock(mock_map::mutex);
mock_map::functions[reinterpret_cast<void*>(fn)].push_back(std::tuple<R, Args...>(ret, args...));
}
template<typename... Args>
void
mock_expect(void fn(Args...), Args... args) {
typedef mock_function_map<mock_void, Args...> mock_map;
std::lock_guard<std::mutex> lock(mock_map::mutex);
mock_map::functions[reinterpret_cast<void*>(fn)].push_back(std::tuple<mock_void, Args...>(mock_void(), args...));
}
template<typename R, typename... Args>
void
mock_redirect(R fn(Args...), std::function<R (Args...)> func) {
typedef mock_function_map<R, Args...> mock_map;
std::lock_guard<std::mutex> lock(mock_map::mutex);
mock_map::redirects[reinterpret_cast<void*>(fn)] = func;
}
template<typename R, typename... Args>
auto
mock_call_direct(std::string name, R fn(Args...), Args... args) -> decltype(fn(args...)) {
typedef mock_function_type<R, Args...> mock_type;
std::lock_guard<std::mutex> lock(mock_type::type::mutex);
auto itr = mock_type::type::functions.find(reinterpret_cast<void*>(fn));
CPPUNIT_ASSERT_MESSAGE(("mock_call expected function calls exhausted by '" + name + "'").c_str(),
itr != mock_type::type::functions.end());
auto mismatch_arg = mock_type::compare_expected(itr->second.front(), args...);
CPPUNIT_ASSERT_MESSAGE(("mock_call expected function call argument " + std::to_string(mismatch_arg) + " mismatch for '" + name + "'").c_str(),
mismatch_arg == 0);
return mock_type::ret_erase(reinterpret_cast<void*>(fn));
}
template<typename R, typename... Args>
auto
mock_call(std::string name, R fn(Args...), Args... args) -> decltype(fn(args...)) {
typedef mock_function_type<R, Args...> mock_type;
if (mock_type::has_redirect(reinterpret_cast<void*>(fn)))
return mock_type::call_redirect(reinterpret_cast<void*>(fn), args...);
return mock_call_direct(name, fn, args...);
}
#endif
+20
View File
@@ -0,0 +1,20 @@
#include "config.h"
#include "test_fixture.h"
#include "torrent/utils/log.h"
#include <iostream>
void
test_fixture::setUp() {
mock_init();
log_add_group_output(torrent::LOG_CONNECTION_BIND, "test_output");
log_add_group_output(torrent::LOG_CONNECTION_FD, "test_output");
}
void
test_fixture::tearDown() {
mock_cleanup();
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef LIBTORRENT_HELPER_TEST_FIXTURE_H
#define LIBTORRENT_HELPER_TEST_FIXTURE_H
#include <cppunit/TestFixture.h>
#include "test/helpers/mock_function.h"
class test_fixture : public CppUnit::TestFixture {
public:
void setUp();
void tearDown();
};
#endif
+52
View File
@@ -0,0 +1,52 @@
#include "config.h"
#include "test_main_thread.h"
#include "globals.h"
#include "test/helpers/mock_function.h"
#include "torrent/exceptions.h"
#include "torrent/poll.h"
#include "torrent/net/resolver.h"
#include "torrent/utils/scheduler.h"
std::unique_ptr<TestMainThread>
TestMainThread::create() {
if (torrent::Poll::slot_create_poll() == nullptr)
set_create_poll();
// Needs to be called before Thread is created.
mock_redirect_defaults();
auto thread = new TestMainThread();
return std::unique_ptr<TestMainThread>(thread);
}
TestMainThread::TestMainThread() {}
TestMainThread::~TestMainThread() {
m_self = nullptr;
}
void
TestMainThread::init_thread() {
if (!torrent::Poll::slot_create_poll())
throw torrent::internal_error("ThreadMain::init_thread(): Poll::slot_create_poll() not valid.");
m_poll = std::unique_ptr<torrent::Poll>(torrent::Poll::slot_create_poll()());
m_resolver = std::make_unique<torrent::net::Resolver>();
m_state = STATE_INITIALIZED;
//m_instrumentation_index = INSTRUMENTATION_POLLING_DO_POLL_MAIN - INSTRUMENTATION_POLLING_DO_POLL;
init_thread_local();
}
void
TestMainThread::call_events() {
process_callbacks();
}
std::chrono::microseconds
TestMainThread::next_timeout() {
return std::chrono::microseconds(10min);
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef TEST_HELPERS_TEST_MAIN_THREAD_H
#define TEST_HELPERS_TEST_MAIN_THREAD_H
#include <memory>
#include <torrent/common.h>
#include <torrent/utils/thread.h>
#include "test/helpers/test_thread.h"
class TestMainThread : public torrent::utils::Thread {
public:
static std::unique_ptr<TestMainThread> create();
~TestMainThread() override;
const char* name() const override { return "rtorrent test main"; }
void init_thread() override;
void test_set_cached_time(std::chrono::microseconds t) { set_cached_time(365 * 24h + t); }
void test_process_events_without_cached_time() { process_events_without_cached_time(); }
private:
TestMainThread();
void call_events() override;
std::chrono::microseconds next_timeout() override;
};
#endif // TEST_HELPERS_TEST_MAIN_THREAD_H
+102
View File
@@ -0,0 +1,102 @@
#include "config.h"
#include "test_thread.h"
#include <unistd.h>
#include <cppunit/extensions/HelperMacros.h>
#include <torrent/exceptions.h>
#include <torrent/poll.h>
#include "test/helpers/mock_function.h"
const int test_thread::test_flag_pre_stop;
const int test_thread::test_flag_long_timeout;
const int test_thread::test_flag_do_work;
const int test_thread::test_flag_pre_poke;
const int test_thread::test_flag_post_poke;
// TODO: Remove PollSelect.
torrent::Poll*
create_poll() {
torrent::Poll* poll = torrent::Poll::create(256);
if (poll == nullptr)
throw torrent::internal_error("Unable to create poll object");
return poll;
}
void
set_create_poll() {
torrent::Poll::slot_create_poll() = []() {
return create_poll();
};
}
std::unique_ptr<test_thread>
test_thread::create() {
// Needs to be called before Thread is created.
mock_redirect_defaults();
auto thread = new test_thread();
return std::unique_ptr<test_thread>(thread);
}
test_thread::test_thread() :
m_test_state(TEST_NONE),
m_test_flags(0) {
}
test_thread::~test_thread() {
if (is_active())
stop_thread_wait();
m_self = nullptr;
}
void
test_thread::init_thread() {
m_state = STATE_INITIALIZED;
m_test_state = TEST_PRE_START;
m_poll = std::unique_ptr<torrent::Poll>(create_poll());
}
void
test_thread::call_events() {
m_loop_count++;
if ((m_test_flags & test_flag_pre_stop) && m_test_state == TEST_PRE_START && m_state == STATE_ACTIVE)
m_test_state = TEST_PRE_STOP;
if ((m_flags & flag_do_shutdown)) {
if ((m_flags & flag_did_shutdown))
throw torrent::internal_error("Already trigged shutdown.");
m_flags |= flag_did_shutdown;
throw torrent::shutdown_exception();
}
if ((m_test_flags & test_flag_pre_poke)) {
}
if ((m_test_flags & test_flag_do_work)) {
usleep(10 * 1000); // TODO: Don't just sleep, as that give up core.
m_test_flags &= ~test_flag_do_work;
}
if ((m_test_flags & test_flag_post_poke)) {
}
process_callbacks();
}
std::chrono::microseconds
test_thread::next_timeout() {
if ((m_test_flags & test_flag_long_timeout))
return std::chrono::microseconds(10s);
else
return std::chrono::microseconds(100ms);
}
+73
View File
@@ -0,0 +1,73 @@
#ifndef TEST_HELPERS_TEST_THREAD_H
#define TEST_HELPERS_TEST_THREAD_H
#include <atomic>
#include <memory>
#include "torrent/common.h"
#include "torrent/utils/thread.h"
class test_thread : public torrent::utils::Thread {
public:
enum test_state {
TEST_NONE,
TEST_PRE_START,
TEST_PRE_STOP,
TEST_STOP
};
static const int test_flag_pre_stop = 0x1;
static const int test_flag_long_timeout = 0x2;
static const int test_flag_do_work = 0x100;
static const int test_flag_pre_poke = 0x200;
static const int test_flag_post_poke = 0x400;
static std::unique_ptr<test_thread> create();
~test_thread() override;
int test_state() const { return m_test_state; }
bool is_state(int state) const { return m_state == state; }
bool is_test_state(int state) const { return m_test_state == state; }
bool is_test_flags(int flags) const { return (m_test_flags & flags) == flags; }
bool is_not_test_flags(int flags) const { return !(m_test_flags & flags); }
// Loop count increments twice each loop.
int loop_count() const { return m_loop_count; }
const char* name() const override { return "test_thread"; }
void init_thread() override;
void set_pre_stop() { m_test_flags |= test_flag_pre_stop; }
void set_test_flag(int flags) { m_test_flags |= flags; }
private:
test_thread();
void call_events() override;
std::chrono::microseconds next_timeout() override;
std::atomic_int m_test_state;
std::atomic_int m_test_flags;
std::atomic_int m_loop_count{0};
};
void set_create_poll();
// TODO: Need better cleanup here.
// TODO: Replace these with class that holds the threads and cleans them up on destruction.
#define SETUP_THREAD_DISK() \
auto thread_test = test_thread::create(); \
thread_test->init_thread(); \
torrent::ThreadDisk::create_thread(); \
torrent::thread_disk()->init_thread(); \
torrent::thread_disk()->start_thread();
#define CLEANUP_THREAD_DISK() \
torrent::ThreadDisk::destroy_thread();
#endif