diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-08-03 00:53:22 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-08-05 00:42:24 +0900 |
| commit | 85d2c6187f9124d5a7026c9ffea040df3ba953f8 (patch) | |
| tree | af1370dbc75fab6b6bfaf26b84f1a5bd60448a57 | |
| parent | c7e23c73b4c5863242646d61c1d7d3296bdfb4ba (diff) | |
| download | LunaticChat-85d2c6187f9124d5a7026c9ffea040df3ba953f8.tar.gz LunaticChat-85d2c6187f9124d5a7026c9ffea040df3ba953f8.tar.bz2 LunaticChat-85d2c6187f9124d5a7026c9ffea040df3ba953f8.zip | |
perf: take the direct message path off the tick thread
/tell and /reply ran romaji conversion inline. Brigadier executors run on
the main thread, and convertWithRomaji wrapped a Google IME call in
runBlocking with a one-second timeout, so a single direct message could
hold the tick thread for up to a second - twenty ticks - whenever the
words were not already cached. Conversion defaults to on for players once
the feature is enabled, so this was the ordinary path, not an edge case.
The conversion chain is suspending now, and the two commands dispatch
delivery to the plugin scope instead of running it inline. Nothing in that
chain touches world state: it sends chat components and plays client-side
sounds, both of which Paper already accepts off-thread, and which this
plugin already does from AsyncChatEvent.
AsyncChatEvent keeps a blocking bridge, renamed convertWithRomajiBlocking
so the choice is visible at the call site. That handler has to decide
whether to cancel the event and what body to set before it returns, and it
is already off the tick thread.
The commands take their scope as a constructor parameter so tests can
choose one. The new test uses StandardTestDispatcher to pin the property
that matters: execute() returns before delivery has run at all.
Co-Authored-By: Claude <noreply@anthropic.com>
10 files changed, 100 insertions, 39 deletions
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt index 7b2f3a0..d51a254 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt @@ -40,7 +40,10 @@ class LunaticChat : private lateinit var services: ServiceContainer private lateinit var configuration: LunaticChatConfiguration private lateinit var serviceInitializer: ServiceInitializer - private lateinit var pluginScope: PluginCoroutineScope + + // Read by commands that must not block the tick thread. + lateinit var pluginScope: PluginCoroutineScope + private set private var updateChecker: UpdateChecker? = null private val updateAvailable = AtomicBoolean(false) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt index ba0b5f8..b3266fb 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt @@ -107,7 +107,7 @@ class DirectMessageHandler( * * @return true if message was sent successfully */ - fun sendDirectMessage( + suspend fun sendDirectMessage( sender: Player, recipient: Player, message: String, @@ -145,7 +145,7 @@ class DirectMessageHandler( * @return the message body to relay (romaji-converted if applicable), since the * receiving server has no access to the sender's settings. */ - fun handleOutgoingCrossServerMessage( + suspend fun handleOutgoingCrossServerMessage( sender: Player, targetName: String, targetServerName: String, @@ -196,7 +196,7 @@ class DirectMessageHandler( lastMessager[recipient.uniqueId] = ReplyTarget.Remote(senderName, sourceServerName) } - private fun convertIfEnabled( + private suspend fun convertIfEnabled( message: String, enabled: Boolean, ): String = diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt index 86cac38..c530ce9 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/ReplyCommand.kt @@ -16,6 +16,8 @@ import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import dev.m1sk9.lunaticChat.paper.velocity.CrossServerDirectMessageManager import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.bukkit.Bukkit @Command( @@ -30,6 +32,9 @@ class ReplyCommand( private val dmHandler: DirectMessageHandler, override val languageManager: LanguageManager, private val crossServerDirectMessageManager: CrossServerDirectMessageManager? = null, + // Delivery is dispatched here rather than run inline: romaji conversion can reach the Google + // IME API, and a command executor runs on the tick thread. + private val scope: CoroutineScope = plugin.pluginScope.scope, ) : LunaticCommand(plugin) { override val description: String get() = languageManager.getMessage("commandDescription.reply") @@ -65,14 +70,14 @@ class ReplyCommand( val recipient = Bukkit.getPlayer(target.uuid) ?: return fail("directMessage.replyTargetNotFound") - dmHandler.sendDirectMessage(sender, recipient, message) + scope.launch { dmHandler.sendDirectMessage(sender, recipient, message) } CommandResult.Success } is ReplyTarget.Remote -> { val manager = crossServerDirectMessageManager ?: return fail("directMessage.replyTargetNotFound") - manager.sendCrossServerMessage(sender, target.playerName, target.serverName, message) + scope.launch { manager.sendCrossServerMessage(sender, target.playerName, target.serverName, message) } CommandResult.Success } } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt index 990399e..bd98f22 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommand.kt @@ -18,6 +18,8 @@ import dev.m1sk9.lunaticChat.paper.velocity.CrossServerDirectMessageManager import dev.m1sk9.lunaticChat.paper.velocity.RemotePlayerRegistry import io.papermc.paper.command.brigadier.CommandSourceStack import io.papermc.paper.command.brigadier.Commands +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.bukkit.Bukkit import org.bukkit.entity.Player import java.util.concurrent.CompletableFuture @@ -37,6 +39,9 @@ class TellCommand( private val crossServerDirectMessageManager: CrossServerDirectMessageManager? = null, private val remotePlayerRegistry: RemotePlayerRegistry? = null, private val localServerName: String = "", + // Delivery is dispatched here rather than run inline: romaji conversion can reach the Google + // IME API, and a command executor runs on the tick thread. + private val scope: CoroutineScope = plugin.pluginScope.scope, ) : LunaticCommand(plugin) { override val description: String get() = languageManager.getMessage("commandDescription.tell") @@ -99,7 +104,7 @@ class TellCommand( ) { return fail("directMessage.yourself") } - manager.sendCrossServerMessage(sender, name, server, message) + scope.launch { manager.sendCrossServerMessage(sender, name, server, message) } return CommandResult.Success } @@ -111,7 +116,7 @@ class TellCommand( return fail("directMessage.yourself") } - directMessageHandler.sendDirectMessage(sender, recipient, message) + scope.launch { directMessageHandler.sendDirectMessage(sender, recipient, message) } return CommandResult.Success } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/converter/RomajiConversionHelper.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/converter/RomajiConversionHelper.kt index 841061a..8c656ec 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/converter/RomajiConversionHelper.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/converter/RomajiConversionHelper.kt @@ -12,15 +12,26 @@ import kotlinx.coroutines.withTimeoutOrNull * @param timeoutMs Timeout in milliseconds for the conversion (default: 1000ms). * @return The message with conversion appended (e.g., "hello §e(こんにちは)"), or the original message. */ -fun convertWithRomaji( +suspend fun convertWithRomaji( message: String, converter: RomanjiConverter, timeoutMs: Long = 1000, ): String = runCatching { - runBlocking { - withTimeoutOrNull(timeoutMs) { - converter.convert(message) - }?.let { "$message §e($it)" } ?: message - } + withTimeoutOrNull(timeoutMs) { + converter.convert(message) + }?.let { "$message §e($it)" } ?: message }.getOrElse { message } + +/** + * Blocking form of [convertWithRomaji], for callers that cannot suspend. + * + * AsyncChatEvent is the only such caller: it has to decide whether to cancel the event and what + * body to set before the handler returns, and it already runs off the tick thread. Command + * handlers do run on the tick thread and must use the suspending form instead. + */ +fun convertWithRomajiBlocking( + message: String, + converter: RomanjiConverter, + timeoutMs: Long = 1000, +): String = runBlocking { convertWithRomaji(message, converter, timeoutMs) } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt index 3bd2d4a..2652bed 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerChatListener.kt @@ -4,7 +4,7 @@ import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager import dev.m1sk9.lunaticChat.paper.chat.handler.ChannelMessageHandler import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter -import dev.m1sk9.lunaticChat.paper.converter.convertWithRomaji +import dev.m1sk9.lunaticChat.paper.converter.convertWithRomajiBlocking import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager import dev.m1sk9.lunaticChat.paper.velocity.CrossServerChatManager import io.papermc.paper.event.player.AsyncChatEvent @@ -74,7 +74,7 @@ class PlayerChatListener( val displayMessage = if (settings.japaneseConversionEnabled && romajiConverter != null) { - convertWithRomaji(messageWithoutPrefix, romajiConverter) + convertWithRomajiBlocking(messageWithoutPrefix, romajiConverter) } else { messageWithoutPrefix } diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerDirectMessageManager.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerDirectMessageManager.kt index 76cc879..307ca8c 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerDirectMessageManager.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerDirectMessageManager.kt @@ -39,7 +39,7 @@ class CrossServerDirectMessageManager( * notification and reply recording are handled by [DirectMessageHandler]; * the (possibly romaji-converted) body is what gets relayed. */ - fun sendCrossServerMessage( + suspend fun sendCrossServerMessage( sender: Player, targetName: String, targetServerName: String, diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt index e8764b2..27a4b9c 100644 --- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt @@ -11,6 +11,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkStatic +import kotlinx.coroutines.runBlocking import org.bukkit.Bukkit import kotlin.test.Test import kotlin.test.assertEquals @@ -23,6 +24,8 @@ import kotlin.test.assertTrue * Validates message handling with dependency injection and conversion features. */ class DirectMessageHandlerTest { + private fun <T> sync(block: suspend () -> T): T = runBlocking { block() } + private fun createHandler( configuration: dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration? = null, settingsManager: PlayerSettingsManager? = null, @@ -40,7 +43,7 @@ class DirectMessageHandlerTest { val sender = TestUtils.createMockPlayer() val recipient = TestUtils.createMockPlayer() - val result = handler.sendDirectMessage(sender, recipient, "Test message") + val result = sync { handler.sendDirectMessage(sender, recipient, "Test message") } assertTrue(result) } @@ -62,7 +65,7 @@ class DirectMessageHandlerTest { val sender = TestUtils.createMockPlayer(name = "Alice") val recipient = TestUtils.createMockPlayer(name = "Bob") - val result = handler.sendDirectMessage(sender, recipient, "Test") + val result = sync { handler.sendDirectMessage(sender, recipient, "Test") } assertTrue(result) } @@ -81,7 +84,7 @@ class DirectMessageHandlerTest { val sender = TestUtils.createMockPlayer() val recipient = TestUtils.createMockPlayer() - val result = handler.sendDirectMessage(sender, recipient, "konnichiwa") + val result = sync { handler.sendDirectMessage(sender, recipient, "konnichiwa") } assertTrue(result) } @@ -107,7 +110,7 @@ class DirectMessageHandlerTest { val recipient = TestUtils.createMockPlayer() // Should not throw exception and should complete quickly (within timeout) - val result = handler.sendDirectMessage(sender, recipient, "konnichiwa") + val result = sync { handler.sendDirectMessage(sender, recipient, "konnichiwa") } assertTrue(result) } @@ -122,7 +125,7 @@ class DirectMessageHandlerTest { // This validates Issue #1 refactoring - ConfigManager DI val sender = TestUtils.createMockPlayer() val recipient = TestUtils.createMockPlayer() - val result = handler.sendDirectMessage(sender, recipient, "Test") + val result = sync { handler.sendDirectMessage(sender, recipient, "Test") } assertTrue(result) } @@ -143,7 +146,7 @@ class DirectMessageHandlerTest { handler.remotePlayerRegistry = registry val sender = TestUtils.createMockPlayer(name = "Alice") - val relayed = handler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") + val relayed = sync { handler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") } assertEquals("hi", relayed) val target = handler.getReplyTarget(sender) @@ -174,7 +177,7 @@ class DirectMessageHandlerTest { handler.remotePlayerRegistry = RemotePlayerRegistry(localServerName = "lobby") // empty roster val sender = TestUtils.createMockPlayer(name = "Alice") - handler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") + sync { handler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") } assertNull(handler.getReplyTarget(sender)) } @@ -188,7 +191,7 @@ class DirectMessageHandlerTest { val recipient = TestUtils.createMockPlayer(name = "Bob") every { Bukkit.getPlayer(recipient.uniqueId) } returns recipient - handler.sendDirectMessage(sender, recipient, "hi") + sync { handler.sendDirectMessage(sender, recipient, "hi") } val target = handler.getReplyTarget(sender) assertIs<ReplyTarget.Local>(target) @@ -206,7 +209,7 @@ class DirectMessageHandlerTest { handler.remotePlayerRegistry = registry val sender = TestUtils.createMockPlayer(name = "Alice") - handler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") + sync { handler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") } handler.clearPlayer(sender) assertNull(handler.getReplyTarget(sender)) @@ -225,7 +228,7 @@ class DirectMessageHandlerTest { val sender = TestUtils.createMockPlayer() val recipient = TestUtils.createMockPlayer() - val result = handler.sendDirectMessage(sender, recipient, "Test") + val result = sync { handler.sendDirectMessage(sender, recipient, "Test") } assertTrue(result) } diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommandTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommandTest.kt index 208d4bf..cea9d6f 100644 --- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommandTest.kt +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/command/impl/TellCommandTest.kt @@ -7,9 +7,12 @@ import dev.m1sk9.lunaticChat.paper.chat.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.command.core.CommandContext import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import dev.m1sk9.lunaticChat.paper.velocity.CrossServerDirectMessageManager +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk -import io.mockk.verify +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.StandardTestDispatcher import kotlin.test.Test import kotlin.test.assertIs @@ -17,6 +20,9 @@ class TellCommandTest { private fun createCommand( crossServerManager: CrossServerDirectMessageManager? = null, localServerName: String = "lobby", + // Unconfined runs the dispatched delivery inline, so a test can assert on it right after + // execute() returns. + scope: CoroutineScope = CoroutineScope(Dispatchers.Unconfined), ): TellDeps { val plugin = mockk<LunaticChat>(relaxed = true) val dmHandler = mockk<DirectMessageHandler>(relaxed = true) @@ -28,7 +34,16 @@ class TellCommandTest { val ctx = mockk<CommandContext>(relaxed = true) every { ctx.requirePlayer() } returns sender - val command = TellCommand(plugin, dmHandler, languageManager, crossServerManager, null, localServerName) + val command = + TellCommand( + plugin, + dmHandler, + languageManager, + crossServerManager, + null, + localServerName, + scope, + ) return TellDeps(command, ctx, dmHandler, crossServerManager, sender) } @@ -57,7 +72,7 @@ class TellCommandTest { val result = deps.command.execute(deps.ctx, "Bob@survival", "hello") assertIs<CommandResult.Success>(result) - verify { manager.sendCrossServerMessage(deps.sender, "Bob", "survival", "hello") } + coVerify { manager.sendCrossServerMessage(deps.sender, "Bob", "survival", "hello") } } @Test @@ -69,7 +84,7 @@ class TellCommandTest { val result = deps.command.execute(deps.ctx, "Alice@lobby", "hello") assertIs<CommandResult.Failure>(result) - verify(exactly = 0) { manager.sendCrossServerMessage(any(), any(), any(), any()) } + coVerify(exactly = 0) { manager.sendCrossServerMessage(any(), any(), any(), any()) } } @Test @@ -80,7 +95,7 @@ class TellCommandTest { val result = deps.command.execute(deps.ctx, "Bob@", "hello") assertIs<CommandResult.Failure>(result) - verify(exactly = 0) { manager.sendCrossServerMessage(any(), any(), any(), any()) } + coVerify(exactly = 0) { manager.sendCrossServerMessage(any(), any(), any(), any()) } } @Test @@ -91,7 +106,7 @@ class TellCommandTest { val result = deps.command.parseAndExecute(deps.ctx, "Bob@survival hello there") assertIs<CommandResult.Success>(result) - verify { manager.sendCrossServerMessage(deps.sender, "Bob", "survival", "hello there") } + coVerify { manager.sendCrossServerMessage(deps.sender, "Bob", "survival", "hello there") } } @Test @@ -102,6 +117,22 @@ class TellCommandTest { val result = deps.command.parseAndExecute(deps.ctx, "Bob@survival") assertIs<CommandResult.Failure>(result) - verify(exactly = 0) { manager.sendCrossServerMessage(any(), any(), any(), any()) } + coVerify(exactly = 0) { manager.sendCrossServerMessage(any(), any(), any(), any()) } + } + + @Test + fun `execute returns without waiting for delivery`() { + val dispatcher = StandardTestDispatcher() + val manager = mockk<CrossServerDirectMessageManager>(relaxed = true) + val deps = createCommand(crossServerManager = manager, scope = CoroutineScope(dispatcher)) + + val result = deps.command.execute(deps.ctx, "Bob@survival", "hello") + + // Delivery can reach the Google IME API; the command must not hold the tick thread for it. + assertIs<CommandResult.Success>(result) + coVerify(exactly = 0) { manager.sendCrossServerMessage(any(), any(), any(), any()) } + + dispatcher.scheduler.advanceUntilIdle() + coVerify { manager.sendCrossServerMessage(deps.sender, "Bob", "survival", "hello") } } } diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerDirectMessageManagerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerDirectMessageManagerTest.kt index cf9f529..03938fc 100644 --- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerDirectMessageManagerTest.kt +++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/velocity/CrossServerDirectMessageManagerTest.kt @@ -7,6 +7,7 @@ import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager import io.mockk.every import io.mockk.mockk import io.mockk.verify +import kotlinx.coroutines.runBlocking import net.kyori.adventure.text.Component import org.bukkit.plugin.Plugin import java.util.UUID @@ -14,6 +15,8 @@ import java.util.logging.Logger import kotlin.test.Test class CrossServerDirectMessageManagerTest { + private fun <T> sync(block: suspend () -> T): T = runBlocking { block() } + private class Fixture( cacheSize: Int = 100, ) { @@ -53,11 +56,11 @@ class CrossServerDirectMessageManagerTest { fun `sendCrossServerMessage relays via plugin channel and delegates display`() { val f = Fixture() val sender = TestUtils.createMockPlayer(name = "Alice") - every { f.dmHandler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") } returns "hi" + every { sync { f.dmHandler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") } } returns "hi" - f.manager.sendCrossServerMessage(sender, "Bob", "survival", "hi") + sync { f.manager.sendCrossServerMessage(sender, "Bob", "survival", "hi") } - verify { f.dmHandler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") } + verify { sync { f.dmHandler.handleOutgoingCrossServerMessage(sender, "Bob", "survival", "hi") } } verify { sender.sendPluginMessage(f.plugin, "lunaticchat:main", any<ByteArray>()) } } @@ -140,9 +143,9 @@ class CrossServerDirectMessageManagerTest { fun `sendCrossServerMessage prunes the dedup cache when over capacity`() { val f = Fixture(cacheSize = 1) val sender = TestUtils.createMockPlayer(name = "Alice") - every { f.dmHandler.handleOutgoingCrossServerMessage(any(), any(), any(), any()) } returns "hi" + every { sync { f.dmHandler.handleOutgoingCrossServerMessage(any(), any(), any(), any()) } } returns "hi" - repeat(3) { f.manager.sendCrossServerMessage(sender, "Bob$it", "survival", "hi") } + repeat(3) { sync { f.manager.sendCrossServerMessage(sender, "Bob$it", "survival", "hi") } } verify(atLeast = 1) { sender.sendPluginMessage(f.plugin, "lunaticchat:main", any<ByteArray>()) } } |
