test: fix the CI hang in the Compose rendering tests (#157)

* test: print all threads when a test times out

The rare CI hang in SelectedStationMessageUiVisualRenderingTest blocks on a
monitor in SnapshotStateObserver.observeReads (line 643 is the monitorenter
of observedScopeMapsLock in Compose 1.8.2). The timed-out thread's stack
shows where it waits but not which thread holds the lock. With the thread
dump enabled, JUnit prints every thread to the test's standard output, which
ends up in the XML report of the test-results artifact.

Claude-Session: https://claude.ai/code/session_01FJBfPYm6rCH4zD6T7bcC7Y

* test: render every ImageComposeScene on the AWT event thread

The rare CI hang is a lock-order deadlock in Compose Multiplatform 1.8.2,
shown by the thread dump of run 40299943329:

- the test thread renders the scene and holds the FlushCoroutineDispatcher
  runLock while LazyListState.scrollToItem measures and wants the
  SnapshotStateObserver's observedScopeMapsLock;
- the AWT event thread sends the global snapshot notifications and holds
  observedScopeMapsLock in drainChanges while a re-read derived state wakes
  the scene's snapshotFlow collector, whose dispatch wants runLock.

The application renders on the event thread, where both monitors are simply
re-entered. The tests now do the same: onEventThread builds, renders and
closes each scene there and rethrows what the block throws.

Claude-Session: https://claude.ai/code/session_01FJBfPYm6rCH4zD6T7bcC7Y
This commit is contained in:
Rsclub2_2 authored and GitHub committed 2026-10-26 11:21:43 +01:00
1 parent 834f4b131a
commit c23982c339
12 files changed
+293 -221

No files matched your search

+7
View File
@@ -151,6 +151,13 @@ subprojects {
*/
systemProperty("junit.jupiter.execution.timeout.default", "2m")
systemProperty("junit.jupiter.execution.timeout.thread.mode.default", "SEPARATE_THREAD")
/*
* The timed-out thread's stack shows where a test waits, not who it waits for: the
* rare CI hang in SelectedStationMessageUiVisualRenderingTest blocks on a monitor
* that another thread holds. On a timeout JUnit prints all threads to the test's
* standard output, which lands in its XML report (the test-results artifact).
*/
systemProperty("junit.jupiter.execution.timeout.threaddump.enabled", "true")
// The default short format prints only the first frame of each cause, which for a
// timed-out test is the line it hung in but not the call that blocked there.
testLogging {