Detach GUI SQLite readers before worker teardown

This commit is contained in:
evgeny committed 2026-10-28 00:59:00 +01:00
1 parent ce133a6acf
commit adefef675c
3 files changed
+53 -1

No files matched your search

+10 -1
View File
@@ -331,6 +331,11 @@ void MainWindow::setupNode() {
connect(m_node, &UtunNode::started, this, [this]() {
qDebug("UtunNode started");
});
connect(m_node, &UtunNode::stopping, this, [this] {
m_autoPlayTimer->stop(); m_autoPlayQueue.clear(); m_autoPlayDownloading.clear();
if (m_db) m_db->setDb(nullptr);
DEBUG_INFO(DEBUG_CATEGORY_SYS, "desktop GUI database detached before core teardown");
});
connect(m_node, &UtunNode::stopped, this, [this]() {
qDebug("UtunNode stopped");
qApp->quit();
@@ -514,7 +519,11 @@ void MainWindow::setupBridgeCallbacks() {
gui_bridge_set_channel_peers_online_cb(onChannelPeersOnlineCallback);
gui_bridge_set_db_ready_cb([]() {
if (!s_mainWindow || !s_mainWindow->m_db) return;
s_mainWindow->m_db->setDb(chat_core_get_db(gui_bridge_get_inst()));
auto* node = s_mainWindow->m_node;
auto* database = node ? node->databaseHandle() : nullptr;
if (!database) { DEBUG_WARN(DEBUG_CATEGORY_SYS, "desktop DB_READY ignored while core unavailable/stopping"); return; }
s_mainWindow->m_db->setDb(database);
s_mainWindow->m_db->setMyNodeId(node->nodeIdHex().toULongLong(nullptr, 16));
if (AvatarManager::instance()) AvatarManager::instance()->initialize();
RadioAudioEngine::instance()->loadSettings(s_mainWindow->m_cfgPath);
s_mainWindow->m_channelList->loadChannels();
+31
View File
@@ -54,6 +54,8 @@ bool UtunNode::start(const QString& configPath) {
}
m_configPath = configPath;
m_stop = false;
m_stopping = false;
{ std::lock_guard<std::mutex> guard(m_guiStopMutex); m_guiDetached = false; }
m_running = true;
++m_generation;
std::promise<QString> startup;
@@ -102,12 +104,28 @@ void UtunNode::requestStop() {
void UtunNode::stop() {
if (!m_running) return;
m_stopping = true;
detachGuiReaders();
requestStop();
if (m_thread.joinable()) m_thread.join();
finalize();
m_running = false;
}
/* GUI уже закончил текущие чтения; worker может закрыть общий handle после всех direct-слотов. */
void UtunNode::detachGuiReaders() {
{ std::lock_guard<std::mutex> guard(m_guiStopMutex); if (m_guiDetached) return; }
emit stopping();
{ std::lock_guard<std::mutex> guard(m_guiStopMutex); m_guiDetached = true; }
m_guiStopCondition.notify_all();
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "desktop GUI readers detached: generation=%llu", (unsigned long long)m_generation);
}
struct sqlite3* UtunNode::databaseHandle() const {
std::lock_guard<std::mutex> guard(m_snapshotMutex);
return m_stopping ? nullptr : m_snapshot.database;
}
void UtunNode::finalize() {
gui_bridge_set_inst(nullptr);
gui_bridge_set_uasync(nullptr);
@@ -150,6 +168,7 @@ bool UtunNode::ntpEnabled() const {
void UtunNode::updateSnapshot(const UTUN_INSTANCE* inst) {
std::lock_guard<std::mutex> guard(m_snapshotMutex);
if (!inst) { m_snapshot = {}; return; }
m_snapshot.database = inst->topo_sqlite_db;
if (m_snapshot.nodeId.isEmpty()) {
char id[17]; snprintf(id, sizeof(id), "%016" PRIx64, inst->node_id);
m_snapshot.nodeId = QString::fromLatin1(id);
@@ -308,12 +327,24 @@ void UtunNode::runLoop(std::promise<QString> startup) {
}
if (!m_stop) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "desktop core requested stop; releasing services outside callbacks");
m_stopping = true;
gui_bridge_set_inst(nullptr);
m_instance = nullptr;
uasync_mark_stopped(ua);
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "utun_node: poll stopped ua=%p running=%d requested=%d",
(void*)ua, uasync_is_running(ua), (int)m_stop);
{
std::unique_lock<std::mutex> guard(m_guiStopMutex);
if (!m_guiDetached) {
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "desktop teardown waiting for GUI readers: generation=%llu", (unsigned long long)generation);
if (!QMetaObject::invokeMethod(this, [this, generation] {
if (generation == m_generation) detachGuiReaders();
}, Qt::QueuedConnection)) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "desktop cannot queue GUI reader detach");
m_guiStopCondition.wait(guard, [this] { return m_guiDetached; });
}
}
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "runLoop: destroying instance in worker thread");
gui_stream_detach(inst); call_video_port_detach(inst);
call_audio_destroy(inst); radio_audio_destroy(inst);
+12
View File
@@ -16,10 +16,12 @@
#include <atomic>
#include <cstdint>
#include <future>
#include <condition_variable>
#include <mutex>
#include <thread>
struct UTUN_INSTANCE;
struct sqlite3;
class UtunNode : public QObject {
Q_OBJECT
@@ -43,6 +45,9 @@ public:
void setDebugCategories(const QString& categories); // Overrides cat=level,... поверх sys=info,bgp=info.
struct UTUN_INSTANCE* instance() const { return m_instance.load(); }
/* GUI: заимствованный handle, живой до stopping; после сигнала новые чтения запрещены.
* Слот stopping снимает handle без вызова stop/join. Worker ждёт завершения слотов перед destroy. */
struct sqlite3* databaseHandle() const;
// Копии данных ядра, безопасны вне uasync; до старта/после stop — пустые строки, 0/false.
QString nodeIdHex() const;
@@ -53,18 +58,21 @@ public:
signals:
void started(); // Синхронно в GUI после успешного start.
void stopping(); // GUI: освободить заимствованные данные; SQLite/ядро ещё живы до возврата слотов.
void stopped(); // Через Qt-очередь после освобождения ядра; событие прежнего запуска отсекается.
void error(const QString& msg); // Ошибка start; stopped при отказе запуска не посылается.
private:
void runLoop(std::promise<QString> startup);
void finalize(); // Только GUI после join: сброс моста, уничтожение uasync, очистка копии состояния.
void detachGuiReaders(); // GUI: stopping и подтверждение worker; идемпотентно в пределах запуска.
void updateSnapshot(const UTUN_INSTANCE* inst); // uasync читает ядро; NULL сбрасывает копию при finalize.
struct Snapshot {
QString nodeId, publicKey;
int64_t ntpOffsetUs = 0;
bool ntpSynced = false, ntpEnabled = false;
struct sqlite3* database = nullptr; // Заимствован до подтверждения stopping в GUI.
} m_snapshot; // Копия для GUI; доступ под m_snapshotMutex, указателей ядра не содержит.
mutable std::mutex m_snapshotMutex;
std::atomic<UTUN_INSTANCE*> m_instance{nullptr}; // Публикуется после готовности; не продлевает жизнь ядра.
@@ -73,6 +81,10 @@ private:
std::thread m_thread; // Присоединяется GUI до повторного старта и уничтожения объекта.
std::atomic<bool> m_running{false}; // Узел владеет запущенным/останавливаемым worker.
std::atomic<bool> m_stop{false}; // Потокобезопасный запрос остановки.
std::atomic<bool> m_stopping{false}; // Закрывает публикацию handle перед ожиданием GUI.
std::mutex m_guiStopMutex; // Подтверждение отсоединения GUI до уничтожения SQLite.
std::condition_variable m_guiStopCondition;
bool m_guiDetached = true; // Под m_guiStopMutex; сбрасывается перед start.
uint64_t m_generation = 0; // Поколение запуска; GUI отбрасывает поздний stopped прежнего worker.
QString m_configPath;
QString m_startupError;