From e768c4b1ca946ff5aa88d99e9ce72f8d6dc55937 Mon Sep 17 00:00:00 2001 From: Tom Ballard Date: Mon, 21 Sep 2026 18:27:38 +0100 Subject: [PATCH] Backport verified Bridge shutdown fix as 0.0.3-3; bound Qt test crashes --- pkgbuilds/omarchy-task-manager/PKGBUILD | 11 ++- .../worker-shutdown.patch | 90 +++++++++++++++++++ 2 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 pkgbuilds/omarchy-task-manager/worker-shutdown.patch diff --git a/pkgbuilds/omarchy-task-manager/PKGBUILD b/pkgbuilds/omarchy-task-manager/PKGBUILD index 8d43690..94f01cd 100644 --- a/pkgbuilds/omarchy-task-manager/PKGBUILD +++ b/pkgbuilds/omarchy-task-manager/PKGBUILD @@ -2,7 +2,7 @@ # Maintainer: Tom Ballard (tcballard) pkgname=omarchy-task-manager pkgver=0.0.3 -pkgrel=2 +pkgrel=3 url='https://github.com/tcballard/omarchy-task-manager' pkgdesc='Floating native task manager for Omarchy (preview)' arch=('x86_64') @@ -12,14 +12,17 @@ optdepends=('gdb: live process core dumps' 'nvidia-utils: NVIDIA device telemetr makedepends=('cmake' 'ninja' 'rust' 'cargo') checkdepends=('python' 'desktop-file-utils') source=("$url/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz" - "pause-resume-test.patch") + "pause-resume-test.patch" + "worker-shutdown.patch") sha256sums=('cc3e24a0bb8fad2b7ce0d4fb780aa774d32b53b8de924b488b55674bdb936c85' - 'ce191fc8dc7e4f7018193aa4982d58fd463f66b37e3129b3acce46b7a1c9a89e') + 'ce191fc8dc7e4f7018193aa4982d58fd463f66b37e3129b3acce46b7a1c9a89e' + '1142aaedf8739bf2ed1cb823a03ed7e406d7ff59d7dfb72748815cc81cfda96b') -# Backport the test observation fix from upstream PR #11. +# Backport upstream PR #11 (sample observation) and PR #12 (worker shutdown). prepare() { cd "$srcdir/$pkgname-$pkgver" patch -Np1 -i "$srcdir/pause-resume-test.patch" + patch -Np1 -i "$srcdir/worker-shutdown.patch" } build() { diff --git a/pkgbuilds/omarchy-task-manager/worker-shutdown.patch b/pkgbuilds/omarchy-task-manager/worker-shutdown.patch new file mode 100644 index 0000000..d71f21b --- /dev/null +++ b/pkgbuilds/omarchy-task-manager/worker-shutdown.patch @@ -0,0 +1,90 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index c940e5e..d1cd40f 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -29,7 +29,8 @@ if(BUILD_TESTING) + target_include_directories(bridge-test PRIVATE ui) + target_link_libraries(bridge-test PRIVATE Qt6::Core Qt6::Gui Qt6::Test) + add_dependencies(bridge-test core) +- add_test(NAME bridge COMMAND bridge-test) ++ add_test(NAME bridge COMMAND bridge-test -nocrashhandler) ++ set_tests_properties(bridge PROPERTIES TIMEOUT 60) + endif() + if(BUILD_TESTING) + qt_add_executable(ui-test tests/ui_test.cpp ui/bridge.cpp ui/bridge.h) +@@ -37,5 +38,6 @@ if(BUILD_TESTING) + qt_add_resources(ui-test test_qml PREFIX "/" FILES ${TASK_MANAGER_QML}) + target_link_libraries(ui-test PRIVATE Qt6::Core Qt6::Gui Qt6::Quick Qt6::Qml Qt6::QuickControls2 Qt6::Test) + add_dependencies(ui-test core) +- add_test(NAME ui COMMAND ui-test) ++ add_test(NAME ui COMMAND ui-test -nocrashhandler) ++ set_tests_properties(ui PROPERTIES TIMEOUT 60) + endif() +diff --git a/tests/bridge_test.cpp b/tests/bridge_test.cpp +index 9bed7be..d6737a4 100644 +--- a/tests/bridge_test.cpp ++++ b/tests/bridge_test.cpp +@@ -3,9 +3,46 @@ + #include + #include + #include ++#include ++#include + class BridgeTest : public QObject { + Q_OBJECT + private slots: ++ void shutdownDoesNotPublish_data() { ++ QTest::addColumn("inFlight"); ++ QTest::addColumn("stopped"); ++ QTest::newRow("idle") << false << false; ++ QTest::newRow("sample-in-flight") << true << false; ++ QTest::newRow("stopped-worker") << true << true; ++ } ++ void shutdownDoesNotPublish() { ++ QFETCH(bool, inFlight); ++ QFETCH(bool, stopped); ++ auto bridge = std::make_unique(); ++ QTRY_VERIFY_WITH_TIMEOUT(!bridge->snapshot().isEmpty(), 8000); ++ bridge->m_timer.stop(); ++ QTRY_VERIFY_WITH_TIMEOUT(!bridge->busy(), 8000); ++ // Stop only the disposable worker owned by this bridge, forcing shutdown ++ // through terminate/kill rather than the normal stdin-EOF exit. ++ if (stopped) { ++ const auto workerPid = bridge->m_worker.processId(); ++ QVERIFY(workerPid > 0); ++ QCOMPARE(::kill(workerPid, SIGSTOP), 0); ++ } ++ if (inFlight) { ++ bridge->refresh(); ++ QVERIFY(bridge->busy()); ++ } ++ QSignalSpy snapshots(bridge.get(), &Bridge::snapshotChanged); ++ QSignalSpy statuses(bridge.get(), &Bridge::statusChanged); ++ QSignalSpy busy(bridge.get(), &Bridge::busyChanged); ++ // Closing the app must not publish late samples or worker-exit errors while ++ // its bridge and UI are being destroyed. Spies outlive the bridge on purpose. ++ bridge.reset(); ++ QCOMPARE(snapshots.count(), 0); ++ QCOMPARE(statuses.count(), 0); ++ QCOMPARE(busy.count(), 0); ++ } + void inspectionErrorsAndDismissal() { + Bridge bridge; + QTRY_VERIFY_WITH_TIMEOUT(!bridge.snapshot().isEmpty(), 8000); +diff --git a/ui/bridge.cpp b/ui/bridge.cpp +index 86b5b47..b1fb303 100644 +--- a/ui/bridge.cpp ++++ b/ui/bridge.cpp +@@ -82,7 +82,12 @@ Bridge::Bridge(QObject *p) : QObject(p), m_rows(this) { + m_clock.start(); + } + Bridge::~Bridge() { ++ // waitForFinished() can emit readyRead/error/finished synchronously. Quiesce ++ // callbacks before waiting, including if QProcess outlives our final wait and ++ // emits from its destructor after the other Bridge members are gone. + m_timer.stop(); ++ m_timeout.stop(); ++ m_worker.disconnect(this); + m_worker.closeWriteChannel(); + if (!m_worker.waitForFinished(600)) { + m_worker.terminate();