Skip no-op client text transactions (#8737)

This commit is contained in:
2026-07-20 11:33:43 -07:00
committed by GitHub
parent 18bcc871e5
commit b3614f70b8
2 changed files with 75 additions and 2 deletions
@@ -192,7 +192,11 @@ class UnrequestedTextHandler(llmResolver: LlmResolver, heroNameCache: HeroNameCa
val accumulateStart = System.currentTimeMillis()
val requestedIds = llmResults.collect { case (llmRequest, LlmResolverRequested(_)) => llmRequest.requestId }
val Accumulator(clientTextAfter, stuckEntries) = withTransaction(clientTextStore) {
val changesClientTextStore = llmResults.exists {
case (_, LlmResolverRequested(_) | LlmResolverBypassed) => true
case _ => false
}
def accumulateResults: Accumulator = {
val accumulated = llmResults.foldLeft(
Accumulator(
ctsWithCachedStates,
@@ -231,6 +235,9 @@ class UnrequestedTextHandler(llmResolver: LlmResolver, heroNameCache: HeroNameCa
if requestedIds.isEmpty then accumulated
else accumulated.copy(clientTextStore = accumulated.clientTextStore.withMarkedRequestedBatch(requestedIds))
}
val Accumulator(clientTextAfter, stuckEntries) =
if changesClientTextStore then withTransaction(clientTextStore)(accumulateResults)
else accumulateResults
val accumulateDuration = System.currentTimeMillis() - accumulateStart
// If every unrequested text is waiting on another unrequested text, we have a circular dependency
@@ -101,7 +101,10 @@ class UnrequestedTextHandlerTest extends AnyFlatSpec with BeforeAndAfterEach wit
behavior of "UnrequestedTextHandlerTest"
"controllerWithHandledUnrequestedTexts" should "make LLM requests for each unrequested text" in {
expectTransactionCalls()
(() => mockCts.beginTransaction()).expects().once(): Unit
(() => mockCts.commitTransaction()).expects().once(): Unit
(() => mockCts.rollbackTransaction()).expects().never(): Unit
(() => mockHistory.count).expects().returning(100000).anyNumberOfTimes(): Unit
val unrequested1 = UnrequestedClientText(
id = "firstRequest",
requestedAfterHistoryCount = 37,
@@ -248,6 +251,69 @@ class UnrequestedTextHandlerTest extends AnyFlatSpec with BeforeAndAfterEach wit
result.newlyCompletedTexts shouldBe Vector.empty
}
it should "skip the client-text transaction when there are no requests to process" in {
(() => mockHistory.count).expects().returning(100000): Unit
(() => mockCts.unrequestedTexts).expects().returning(Map.empty): Unit
(() => mockCts.beginTransaction()).expects().never(): Unit
(() => mockCts.commitTransaction()).expects().never(): Unit
(() => mockCts.rollbackTransaction()).expects().never(): Unit
(mockLlmResolver
.resolveLlmRequests(
_: Vector[LlmRequestWithGameState],
_: ClientTextStore,
_: FunctionalRandom
))
.expects(Vector.empty, mockCts, SeededRandom(1))
.returning(RandomState(Vector.empty, mockFr)): Unit
val result = handler.clientTextStoreWithHandledUnrequestedTexts(
clientTextStore = mockCts,
gameHistory = mockHistory,
randomLong = 1
)
result.clientTextStore shouldBe mockCts
result.newlyCompletedTexts shouldBe Vector.empty
}
it should "skip the client-text transaction when resolver results do not change the store" in {
val unrequested = UnrequestedClientText(
id = "firstRequest",
requestedAfterHistoryCount = 37,
llmRequest = llmRequest1,
cachedGameState = Some(gameState)
)
(() => mockHistory.count).expects().returning(100000): Unit
(() => mockCts.unrequestedTexts).expects().returning(Map("firstRequest" -> unrequested)): Unit
(() => mockCts.beginTransaction()).expects().never(): Unit
(() => mockCts.commitTransaction()).expects().never(): Unit
(() => mockCts.rollbackTransaction()).expects().never(): Unit
(mockLlmResolver
.resolveLlmRequests(
_: Vector[LlmRequestWithGameState],
_: ClientTextStore,
_: FunctionalRandom
))
.expects(
Vector(LlmRequestWithGameState(llmRequest1, gameState)),
mockCts,
SeededRandom(1)
)
.returning(RandomState(Vector((llmRequest1, LlmResolverTooManyRequestsInFlight)), mockFr)): Unit
val result = handler.clientTextStoreWithHandledUnrequestedTexts(
clientTextStore = mockCts,
gameHistory = mockHistory,
randomLong = 1
)
result.clientTextStore shouldBe mockCts
result.newlyCompletedTexts shouldBe Vector.empty
}
it should "throw on stale unrequested text requests left behind by a rewind" in {
(() => mockCts.beginTransaction()).expects().anyNumberOfTimes(): Unit
(() => mockCts.commitTransaction()).expects().anyNumberOfTimes(): Unit