diff --git a/apps/tui/src/main/kotlin/com/correx/apps/tui/ws/TuiWsClient.kt b/apps/tui/src/main/kotlin/com/correx/apps/tui/ws/TuiWsClient.kt index b317c263..3fdb1ec5 100644 --- a/apps/tui/src/main/kotlin/com/correx/apps/tui/ws/TuiWsClient.kt +++ b/apps/tui/src/main/kotlin/com/correx/apps/tui/ws/TuiWsClient.kt @@ -19,9 +19,6 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString @@ -53,8 +50,8 @@ class TuiWsClient( private val _messages = Channel(Channel.UNLIMITED) val messages: Flow = _messages.receiveAsFlow() - private val _connection = MutableSharedFlow(extraBufferCapacity = 16) - val connection: SharedFlow = _connection.asSharedFlow() + private val _connection = Channel(Channel.UNLIMITED) + val connection: Flow = _connection.receiveAsFlow() suspend fun send(message: ClientMessage) { runCatching { @@ -75,7 +72,7 @@ class TuiWsClient( runCatching { client.webSocket(method = HttpMethod.Get, host = host, port = port, path = "/stream") { session = this - _connection.tryEmit(ConnectionEvent.Connected) + _connection.send(ConnectionEvent.Connected) delayMs = INITIAL_RETRY_DELAY_MS attempt = 0 for (frame in incoming) { @@ -90,10 +87,10 @@ class TuiWsClient( } }.onFailure { } session = null - _connection.tryEmit(ConnectionEvent.Disconnected) + _connection.send(ConnectionEvent.Disconnected) attempt++ val nextRetryAtMs = System.currentTimeMillis() + delayMs - _connection.tryEmit(ConnectionEvent.RetryScheduled(attempt = attempt, nextRetryAtMs = nextRetryAtMs)) + _connection.send(ConnectionEvent.RetryScheduled(attempt = attempt, nextRetryAtMs = nextRetryAtMs)) delay(delayMs) delayMs = minOf(delayMs * 2, MAX_RETRY_DELAY_MS) } diff --git a/apps/tui/src/test/kotlin/com/correx/apps/tui/ws/TuiWsClientConnectionChannelTest.kt b/apps/tui/src/test/kotlin/com/correx/apps/tui/ws/TuiWsClientConnectionChannelTest.kt new file mode 100644 index 00000000..0449192a --- /dev/null +++ b/apps/tui/src/test/kotlin/com/correx/apps/tui/ws/TuiWsClientConnectionChannelTest.kt @@ -0,0 +1,45 @@ +package com.correx.apps.tui.ws + +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class TuiWsClientConnectionChannelTest { + + @Test + fun `Channel UNLIMITED does not drop connection events under high load`(): Unit = runTest { + // Create a test channel to simulate TuiWsClient._connection behavior + val channel = Channel(Channel.UNLIMITED) + + // Collect events from the channel + val collected = mutableListOf() + val collectJob = launch { + for (event in channel) { + collected.add(event) + } + } + + // Send 10k events rapidly without delay (mix of all event types) + val sentCount = 10_000 + repeat(sentCount) { i -> + val event = when (i % 3) { + 0 -> ConnectionEvent.Connected + 1 -> ConnectionEvent.Disconnected + else -> ConnectionEvent.RetryScheduled(attempt = i, nextRetryAtMs = System.currentTimeMillis() + 1000) + } + channel.send(event) + } + + // Close channel to signal completion + channel.close() + + // Wait for collection to complete + collectJob.join() + + // Verify all events were received (no silent drops) + assertEquals(sentCount, collected.size, "All $sentCount connection events should be received without drops") + } + +}