Fulcrum/src/Mixins.cpp
2025-03-20 21:21:56 -05:00

207 lines
6.3 KiB
C++

//
// Fulcrum - A fast & nimble SPV Server for Bitcoin Cash
// Copyright (C) 2019-2025 Calin A. Culianu <calin.culianu@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program (see LICENSE.txt). If not, see
// <https://www.gnu.org/licenses/>.
//
#include "App.h"
#include "Mixins.h"
#include <utility>
QObjectMixin::~QObjectMixin() {}
QObject *QObjectMixin::qobj() const
{
QObject *ret = dynamic_cast<QObject *>(const_cast<QObjectMixin *>(this));
if (!ret) {
Error() << __PRETTY_FUNCTION__ << ": Cannot cast this to QObject! App will likely crash now!";
}
return ret;
}
ThreadObjectMixin::ThreadObjectMixin()
{
}
ThreadObjectMixin::~ThreadObjectMixin()
{
stop(); // paranoia. Ideally child classes call this on d'tor to call their derived stop() methods.
}
bool ThreadObjectMixin::isLifecyclePrint() const
{
if (threadObjectDebugLifecycle)
return true;
App *a = app();
return a && a->isQuitting();
}
void ThreadObjectMixin::start()
{
if (_thread.isRunning()) return;
if (isLifecyclePrint()) DebugM(qobj()->objectName(), " starting thread");
chan.clear();
origThread = qobj()->thread();
conns += origThread->connect(origThread, &QThread::finished, qobj(), [this, qo=qobj(), which=conns.size()] {
Warning() << "ThreadObjectMixin: original thread for " << qo->objectName() << " ended! Setting 'original thread' to main thread! FIXME!";
origThread = qApp->thread();
QObject::disconnect(conns.takeAt(which));
});
qobj()->moveToThread(&_thread);
conns += QObject::connect(&_thread, &QThread::started, qobj(), [this]{
Util::ThreadName::Set(qobj()->objectName()); // Set thread name for logger
on_started();
});
conns += QObject::connect(&_thread, &QThread::finished, qobj(), [this]{on_finished();});
_thread.start();
}
void ThreadObjectMixin::stop()
{
const bool dbgLC = isLifecyclePrint();
if (_thread.isRunning()) {
if (dbgLC) DebugM(_thread.objectName(), " thread is running, joining thread");
_thread.quit();
_thread.wait();
}
int ct = 0;
for (const auto & c : std::as_const(conns)) {
QObject::disconnect(c);
++ct;
}
conns.clear();
if (ct && dbgLC) DebugM(_thread.objectName(), " cleaned up ", ct, " signal/slot connections");
}
void ThreadObjectMixin::on_started()
{
}
void ThreadObjectMixin::on_finished()
{
qobj()->moveToThread(origThread);
origThread = nullptr;
}
/*static*/
auto IdMixin::newId() -> Id { return app()->newId(); }
TimersByNameMixin::TimersByNameMixin() {}
TimersByNameMixin::~TimersByNameMixin() {}
void TimersByNameMixin::callOnTimerSoon(int ms, const QString &name, const std::function<bool()> &func, bool force, Qt::TimerType ttype)
{
if (auto it = _timerMap.find(name); it != _timerMap.end()) {
if (!force)
// timer exists, and force=false; bail
return;
// kill existing timer, create new one below.
it.value()->stop();
it = _timerMap.erase(it); // shared_ptr refs will go away immediately, which ends up deleting this instance
}
auto timer = std::make_shared<QTimer>(qobj());
timer->setSingleShot(false);
timer->setTimerType(ttype);
QObject::connect(timer.get(), &QTimer::timeout, qobj(), [this, func, name]{
const bool keepGoing = func();
if (!keepGoing)
stopTimer(name);
});
_timerMap[name] = timer;
timer->setObjectName(name);
timer->start(ms);
//DebugM("timerByName: ", name, " started, ms = ", ms);
}
/// Identical to above, except takes a pure voidfunc. It's as if the above returned false (so will not keep going).
void TimersByNameMixin::callOnTimerSoonNoRepeat(int ms, const QString &name, const std::function<void()> & fn, bool force, Qt::TimerType ttype)
{
callOnTimerSoon(ms, name, [fn]() -> bool { fn(); return false; }, force, ttype);
}
bool TimersByNameMixin::stopTimer(const QString &name) {
if (auto timer = _timerMap.take(name)) {
// immediately stop for belt-and-suspenders
timer->stop();
return true; // timer will go out of scope here and deleter will be called.
}
return false;
}
int TimersByNameMixin::stopAllTimers() {
int ctr = 0;
for (const auto & name : activeTimers()) {
stopTimer(name);
++ctr;
}
return ctr;
}
QVariantMap TimersByNameMixin::activeTimerMapForStats() const
{
QVariantMap ret;
for (const auto & timer: _timerMap) {
ret.insert(timer->objectName(), timer->interval());
}
return ret;
}
bool TimersByNameMixin::restartTimer(const QString &name)
{
if (auto it = _timerMap.find(name); it != _timerMap.end()) {
it.value()->start(); // restart from "now"
return true;
}
return false;
}
/// --- StatsMixin
StatsMixin::~StatsMixin() {}
// unsafe in subclasses (safe here)
auto StatsMixin::stats() const -> Stats { return Stats(); }
// unsafe in subclasses (safe here)
auto StatsMixin::debug(const StatsParams &) const -> Stats { return Stats(); }
// thread-safe
auto StatsMixin::statsSafe(int timeout_ms) const -> Stats
{
Stats ret;
try {
ret = Util::LambdaOnObject<Stats>(qobj(), [this]{ return stats(); }, timeout_ms);
} catch (const std::exception & e) {
DebugM("Safe stats get failed: ", e.what());
ret = QVariantMap{{"error" , e.what()}};
}
return ret;
}
// thread-safe
auto StatsMixin::debugSafe(const StatsParams &p, int timeout_ms) const -> Stats
{
Stats ret;
try {
ret = Util::LambdaOnObject<Stats>(qobj(), [this, p]{ return debug(p); }, timeout_ms);
} catch (const std::exception & e) {
DebugM("Safe debug get failed: ", e.what());
ret = QVariantMap{{"error" , e.what()}};
}
return ret;
}
ProcessAgainMixin::~ProcessAgainMixin() {}