From 2c9b4c83298ea67e2e7b576fd7209b5bc782f23c Mon Sep 17 00:00:00 2001 From: Sho Sakuma Date: Wed, 5 Aug 2026 00:42:19 +0900 Subject: fix: report queued work dropped at shutdown Cancelling the scope kills the worker coroutines but does not close their channels, so trySend kept reporting success for work nothing would ever read. The warning that exists precisely to avoid dropping a message in silence was therefore unreachable in the case it was written for. Co-Authored-By: Claude --- .../dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueue.kt | 6 +++++- .../dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueueTest.kt | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueue.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueue.kt index 0769823..f5e4a28 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueue.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueue.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import java.util.UUID import java.util.concurrent.ConcurrentHashMap @@ -34,7 +35,10 @@ class PerPlayerWorkQueue( playerId: UUID, work: suspend () -> Unit, ) { - val accepted = queues.computeIfAbsent(playerId) { startWorker() }.trySend(work).isSuccess + // Checked rather than left to trySend: cancelling the scope kills the worker coroutines but + // does not close their channels, so after shutdown trySend would keep reporting success for + // work nothing will ever read. + val accepted = scope.isActive && queues.computeIfAbsent(playerId) { startWorker() }.trySend(work).isSuccess if (!accepted) { // Reachable once the scope is cancelled at shutdown, or if the player's queue is // released in the same tick as their command. Dropping a message in silence is worse diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueueTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueueTest.kt index 95b7700..2d96cba 100644 --- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueueTest.kt +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueueTest.kt @@ -3,6 +3,7 @@ package dev.m1sk9.lunaticChat.paper import dev.m1sk9.lunaticChat.paper.TestUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeout @@ -134,4 +135,18 @@ class PerPlayerWorkQueueTest { assertTrue(logger.severeMessages.any { it.contains("Queued work failed") }) } + + @Test + fun `work submitted after shutdown is reported rather than silently dropped`() { + val logger = TestUtils.TestLogger() + val scope = CoroutineScope(Dispatchers.Default) + val queue = PerPlayerWorkQueue(scope, logger) + val completed = ConcurrentLinkedQueue() + + scope.cancel() + queue.submit(alice) { completed.add("never") } + + assertTrue(completed.isEmpty()) + assertTrue(logger.warningMessages.any { it.contains("their queue is closed") }) + } } -- cgit v1.2.1