Browse Source

chatgui: fix rlottie thread hang on Windows shutdown

Root cause: librlottie RleTaskScheduler creates 32 worker threads on first
lottie_animation_from_data call. These pthreads wait in WaitForMultipleObjects(INFINITE)
and never exit. On MinGW, exit()/pthread_join blocks forever.

Changes:
- messagelist: s_icons moved to file-static; cleanupAnimations() clears them,
  calling lottie_animation_destroy BEFORE static destruction
- mainwindow: ~MainWindow calls MessageList::cleanupAnimations() after node stop
- main: ShutdownSentinel counts threads at exit; if >4 remaining, calls
  ExitProcess(0) to bypass CRT pthread_join hang
- main: if rlottie properly shuts down (<=4 threads), normal return (no ExitProcess)
topo_upd
Evgeny 2 months ago
parent
commit
73be79d204
  1. 22
      tools/chatgui/src/main.cpp
  2. 2
      tools/chatgui/src/mainwindow.cpp
  3. 11
      tools/chatgui/src/messagelist.cpp
  4. 1
      tools/chatgui/src/messagelist.h

22
tools/chatgui/src/main.cpp

@ -5,6 +5,7 @@
#include <cstdio>
#ifdef _WIN32
#include <windows.h>
#include <tlhelp32.h>
#endif
#include "mainwindow.h"
#include "../db/db_manager.h"
@ -15,6 +16,19 @@
#ifdef _WIN32
static UtunNode* g_ctrl_node = nullptr;
static DWORD count_threads(void) {
DWORD pid = GetCurrentProcessId(), count = 0;
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (snap == INVALID_HANDLE_VALUE) return 0;
THREADENTRY32 te = { sizeof(te) };
if (Thread32First(snap, &te)) {
do { if (te.th32OwnerProcessID == pid) count++; }
while (Thread32Next(snap, &te));
}
CloseHandle(snap);
return count;
}
static BOOL WINAPI console_ctrl_handler(DWORD dwCtrlType) {
if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) {
if (g_ctrl_node) g_ctrl_node->requestStop();
@ -27,6 +41,14 @@ static BOOL WINAPI console_ctrl_handler(DWORD dwCtrlType) {
static struct ShutdownSentinel {
~ShutdownSentinel() {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "main: static destructors done");
#ifdef _WIN32
DWORD threads = count_threads();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "main: threads at exit=%lu", (unsigned long)threads);
if (threads > 4) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "main: %lu hanging threads, ExitProcess", (unsigned long)threads);
ExitProcess(0);
}
#endif
}
} g_shutdown_sentinel;

2
tools/chatgui/src/mainwindow.cpp

@ -90,6 +90,8 @@ MainWindow::~MainWindow() {
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "MainWindow: ~MainWindow — stopping utun node");
if (m_node) { m_node->stop(); delete m_node; m_node = nullptr; }
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "MainWindow: ~MainWindow — node stop+delete done");
MessageList::cleanupAnimations();
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "MainWindow: ~MainWindow — animations cleaned");
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "MainWindow: ~MainWindow done");
}

11
tools/chatgui/src/messagelist.cpp

@ -7,6 +7,7 @@
#include "animtimer.h"
#include "lottieicon.h"
#include "../db/db_manager.h"
#include "../../lib/debug_config.h"
#include <QVBoxLayout>
#include <QPainter>
#include <QPixmap>
@ -18,6 +19,8 @@
#include <vector>
#include <memory>
static std::vector<std::unique_ptr<LottieIcon>> s_icons;
extern "C" {
#include "../transport/gui_bridge.h"
#include "../transport/chat_core.h"
@ -95,7 +98,6 @@ MessageList::MessageList(DbManager* db, QWidget *parent)
m_view->setBackgroundImage("../resources/bg.jpg");
layout->addWidget(m_view, 1);
static std::vector<std::unique_ptr<LottieIcon>> s_icons;
if (s_icons.empty()) {
for (const auto &cat : builtinEmojiSet()) {
for (const auto &e : cat.items) {
@ -412,3 +414,10 @@ void MessageList::updateReadPosition() {
chat_core_save_ui_state(posKey.constData(), posVal.constData());
}
}
void MessageList::cleanupAnimations() {
size_t n = s_icons.size();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "MessageList: cleanupAnimations clearing %zu icons", n);
s_icons.clear();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "MessageList: cleanupAnimations done");
}

1
tools/chatgui/src/messagelist.h

@ -19,6 +19,7 @@ public:
void loadChannel(const QString& channelId);
void refresh();
void saveChannelState();
static void cleanupAnimations();
void addMessage(const QString& channelId, quint64 authorNodeId,
const QByteArray& content, qint64 timestamp);

Loading…
Cancel
Save