From 5efadabd9774342f9f39e835e2e8feeade3259df Mon Sep 17 00:00:00 2001 From: Sean Proctor Date: Mon, 2 Nov 2026 19:36:01 -0500 Subject: [PATCH 1/2] Fix the arm64 release build and a racy routing test v3.2.0-beta.2 failed on Linux arm64: Nucleus's native-image resource task creates the iOS tasks of the app's modules, and the Kotlin plugin refuses to create those there. Pass iosSkip, as CI and the Android job already do. NucleusFramework/Nucleus#904 fixes the cause. WraythStreamRoutingTests cancelled its collectors as soon as the client disconnected, which could drop lines they hadn't reached yet and failed CI on main. Wait for them. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yaml | 4 +++- .../jvmTest/kotlin/WraythStreamRoutingTests.kt | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 61ee428b..d056ad00 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -121,7 +121,9 @@ jobs: fi log="$(mktemp)" status=0 - ./gradlew $TASKS --stacktrace --no-daemon 2>&1 | tee "$log" \ + # iosSkip: Nucleus's native-image resource task pulls in the iOS tasks of the app's modules, + # and the Kotlin plugin refuses to create those on a Linux arm64 host. + ./gradlew $TASKS -PiosSkip=true --stacktrace --no-daemon 2>&1 | tee "$log" \ || status="${PIPESTATUS[0]}" if [ "$status" -eq 0 ]; then exit 0 diff --git a/wrayth/src/jvmTest/kotlin/WraythStreamRoutingTests.kt b/wrayth/src/jvmTest/kotlin/WraythStreamRoutingTests.kt index d050d4b9..f2198abc 100644 --- a/wrayth/src/jvmTest/kotlin/WraythStreamRoutingTests.kt +++ b/wrayth/src/jvmTest/kotlin/WraythStreamRoutingTests.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -177,7 +178,19 @@ class WraythStreamRoutingTests { mainSubscribed.await() beforeConnect(client) client.connect("key") - withTimeout(10.seconds) { client.disconnected.first { it } } + withTimeout(10.seconds) { + client.disconnected.first { it } + // Disconnecting says the client has sent everything, not that the collectors have + // taken it: they run on their own threads, and cancelling them here drops what they + // haven't reached. Each line sent has one collector, so the stream side can be + // counted. eventFlow keeps no count, so wait for main to go quiet. + while (synchronized(streams) { streams.size } < feed.linesSent) delay(1) + var seen = -1 + while (seen != synchronized(main) { main.size }) { + seen = synchronized(main) { main.size } + delay(50) + } + } scope.cancel() registry to TextEvents(synchronized(main) { main.toList() }, synchronized(streams) { streams.toList() }) } From f22415b3bbf01e153a907a56186baf2a520c4041 Mon Sep 17 00:00:00 2001 From: Sean Proctor Date: Mon, 2 Nov 2026 19:42:40 -0500 Subject: [PATCH 2/2] Collect routing test events on the client's own thread Waiting for main to go quiet for 50 ms still loses events if the collector stalls for longer. Unconfined collectors take each line inside the client's send, so nothing is buffered at disconnect and the test needs no wait at all. Co-Authored-By: Claude Opus 5.5 --- .../kotlin/WraythStreamRoutingTests.kt | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/wrayth/src/jvmTest/kotlin/WraythStreamRoutingTests.kt b/wrayth/src/jvmTest/kotlin/WraythStreamRoutingTests.kt index f2198abc..8a7328a5 100644 --- a/wrayth/src/jvmTest/kotlin/WraythStreamRoutingTests.kt +++ b/wrayth/src/jvmTest/kotlin/WraythStreamRoutingTests.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -160,7 +159,9 @@ class WraythStreamRoutingTests { val main = mutableListOf() val streams = mutableListOf() val mainSubscribed = CompletableDeferred() - scope.launch { + // Unconfined, so each collector takes a line inside the client's own send: nothing can + // still be buffered when the client disconnects, and scope.cancel below loses nothing. + scope.launch(Dispatchers.Unconfined) { client.eventFlow.onSubscription { mainSubscribed.complete(Unit) }.collect { event -> if (event is ClientTextEvent) { synchronized(main) { main += event } @@ -169,7 +170,7 @@ class WraythStreamRoutingTests { } // Undispatched, so each is subscribed before the client connects. for (stream in watched) { - scope.launch(start = CoroutineStart.UNDISPATCHED) { + scope.launch(Dispatchers.Unconfined, start = CoroutineStart.UNDISPATCHED) { client.streamLines(stream).collect { event -> synchronized(streams) { streams += event } } @@ -178,19 +179,7 @@ class WraythStreamRoutingTests { mainSubscribed.await() beforeConnect(client) client.connect("key") - withTimeout(10.seconds) { - client.disconnected.first { it } - // Disconnecting says the client has sent everything, not that the collectors have - // taken it: they run on their own threads, and cancelling them here drops what they - // haven't reached. Each line sent has one collector, so the stream side can be - // counted. eventFlow keeps no count, so wait for main to go quiet. - while (synchronized(streams) { streams.size } < feed.linesSent) delay(1) - var seen = -1 - while (seen != synchronized(main) { main.size }) { - seen = synchronized(main) { main.size } - delay(50) - } - } + withTimeout(10.seconds) { client.disconnected.first { it } } scope.cancel() registry to TextEvents(synchronized(main) { main.toList() }, synchronized(streams) { streams.toList() }) }