Detach GUI SQLite readers before worker teardown
This commit is contained in:
1 parent
ce133a6acf
commit
adefef675c
3 files changed
+53
-1
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user