summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-08-05 00:42:19 +0900
committerSho Sakuma <me@m1sk9.dev>2026-08-05 00:42:25 +0900
commit2c9b4c83298ea67e2e7b576fd7209b5bc782f23c (patch)
tree436eb0a22fd01613bc65d3389adf567b78617522
parent62022f1e192e76b44076a3da0279ccd6bb10c4ef (diff)
downloadLunaticChat-2c9b4c83298ea67e2e7b576fd7209b5bc782f23c.tar.gz
LunaticChat-2c9b4c83298ea67e2e7b576fd7209b5bc782f23c.tar.bz2
LunaticChat-2c9b4c83298ea67e2e7b576fd7209b5bc782f23c.zip
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 <noreply@anthropic.com>
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueue.kt6
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/PerPlayerWorkQueueTest.kt15
2 files changed, 20 insertions, 1 deletions
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<String>()
+
+ scope.cancel()
+ queue.submit(alice) { completed.add("never") }
+
+ assertTrue(completed.isEmpty())
+ assertTrue(logger.warningMessages.any { it.contains("their queue is closed") })
+ }
}