mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 21:35:42 +00:00
Defer schema pool refill after game creation (#8533)
This commit is contained in:
@@ -2111,6 +2111,21 @@ class GamesManager(
|
||||
gameId: GameId,
|
||||
humanPlayers: Vector[WaitingGamePlayerInfo],
|
||||
aiPlayerCount: Int
|
||||
): GameControllerWithPostResults =
|
||||
PostgresGameSchemaPool.withRefillDeferred {
|
||||
synchronizedCreateGameWithRefillDeferred(
|
||||
expandedGameParameters = expandedGameParameters,
|
||||
gameId = gameId,
|
||||
humanPlayers = humanPlayers,
|
||||
aiPlayerCount = aiPlayerCount
|
||||
)
|
||||
}
|
||||
|
||||
private def synchronizedCreateGameWithRefillDeferred(
|
||||
expandedGameParameters: ExpandedGameParameters,
|
||||
gameId: GameId,
|
||||
humanPlayers: Vector[WaitingGamePlayerInfo],
|
||||
aiPlayerCount: Int
|
||||
): GameControllerWithPostResults = JfrEvents.createGamePhase(gameId.toHexString, "synchronizedCreateGame.total") { _ =>
|
||||
this.synchronized {
|
||||
val logger = SimpleTimedLogger.printLogger
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package net.eagle0.eagle.service
|
||||
|
||||
import java.sql.Connection
|
||||
import java.util.concurrent.{Executors, TimeUnit}
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
@@ -11,18 +11,47 @@ import net.eagle0.eagle.client_text.PostgresClientTextStore
|
||||
import net.eagle0.eagle.postgres.PostgresConnectionProvider
|
||||
import net.eagle0.eagle.GameId
|
||||
|
||||
private[service] final class DeferredRefillCoordinator(triggerRefill: () => Unit) {
|
||||
private val deferralDepth = ThreadLocal.withInitial[Integer](() => 0)
|
||||
private val refillDeferred = ThreadLocal.withInitial[java.lang.Boolean](() => false)
|
||||
|
||||
def requestRefill(): Unit =
|
||||
if deferralDepth.get() > 0 then refillDeferred.set(true)
|
||||
else triggerRefill()
|
||||
|
||||
def withRefillDeferred[A](work: => A): A = {
|
||||
val isOutermost = deferralDepth.get() == 0
|
||||
deferralDepth.set(deferralDepth.get() + 1)
|
||||
try work
|
||||
finally {
|
||||
deferralDepth.set(deferralDepth.get() - 1)
|
||||
if isOutermost then {
|
||||
val shouldRefill = refillDeferred.get()
|
||||
deferralDepth.remove()
|
||||
refillDeferred.remove()
|
||||
if shouldRefill then triggerRefill()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private[service] object PostgresGameSchemaPool {
|
||||
private val PoolSize = PostgresConnectionProvider.envInt("EAGLE_POSTGRES_GAME_SCHEMA_POOL_SIZE", 3).max(0)
|
||||
private val PoolLockName = "eagle0_game_schema_pool"
|
||||
private val PoolPrefix =
|
||||
private val PoolSize =
|
||||
PostgresConnectionProvider.envInt("EAGLE_POSTGRES_GAME_SCHEMA_POOL_SIZE", 3).max(0)
|
||||
private val PoolLockName = "eagle0_game_schema_pool"
|
||||
private val PostCreationRefillDelayMillis = 1000L
|
||||
private val PoolPrefix =
|
||||
s"eagle_pool_h${PostgresHistory.SchemaVersion}_t${PostgresClientTextStore.SchemaVersion}_"
|
||||
|
||||
private val started = new AtomicBoolean(false)
|
||||
private val refillMonitor = new Object
|
||||
private var refillPending = false
|
||||
private var refillRunning = false
|
||||
private val started = new AtomicBoolean(false)
|
||||
private val delayedRefillScheduled = new AtomicBoolean(false)
|
||||
private val refillMonitor = new Object
|
||||
private var refillPending = false
|
||||
private var refillRunning = false
|
||||
|
||||
private val refillExecutor = Executors.newSingleThreadExecutor { runnable =>
|
||||
private val deferredRefillCoordinator = new DeferredRefillCoordinator(() => scheduleRefillAfterCreation())
|
||||
|
||||
private val refillExecutor = Executors.newSingleThreadScheduledExecutor { runnable =>
|
||||
val thread = new Thread(runnable, "postgres-game-schema-pool")
|
||||
thread.setDaemon(true)
|
||||
thread
|
||||
@@ -35,7 +64,7 @@ private[service] object PostgresGameSchemaPool {
|
||||
case exception: Exception =>
|
||||
log(s"Could not synchronously prepare the first schema: ${exception.getMessage}")
|
||||
}
|
||||
requestRefill()
|
||||
requestRefillNow()
|
||||
}
|
||||
|
||||
def claimForGame(connection: Connection, gameId: GameId): Boolean =
|
||||
@@ -74,7 +103,26 @@ private[service] object PostgresGameSchemaPool {
|
||||
case None => false
|
||||
}
|
||||
|
||||
private def requestRefill(): Unit = refillMonitor.synchronized {
|
||||
private def requestRefill(): Unit =
|
||||
deferredRefillCoordinator.requestRefill()
|
||||
|
||||
private[service] def withRefillDeferred[A](work: => A): A =
|
||||
deferredRefillCoordinator.withRefillDeferred(work)
|
||||
|
||||
private def scheduleRefillAfterCreation(): Unit =
|
||||
if delayedRefillScheduled.compareAndSet(false, true) then
|
||||
refillExecutor.schedule(
|
||||
new Runnable {
|
||||
override def run(): Unit = {
|
||||
delayedRefillScheduled.set(false)
|
||||
requestRefillNow()
|
||||
}
|
||||
},
|
||||
PostCreationRefillDelayMillis,
|
||||
TimeUnit.MILLISECONDS
|
||||
): Unit
|
||||
|
||||
private def requestRefillNow(): Unit = refillMonitor.synchronized {
|
||||
refillPending = true
|
||||
if !refillRunning then {
|
||||
refillRunning = true
|
||||
|
||||
@@ -7,6 +7,41 @@ import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class PostgresGameSchemaPoolTest extends AnyFlatSpec with Matchers with MockFactory {
|
||||
"DeferredRefillCoordinator" should "trigger refill requests made outside creation" in {
|
||||
var refillCount = 0
|
||||
val coordinator = new DeferredRefillCoordinator(() => refillCount += 1)
|
||||
|
||||
coordinator.requestRefill()
|
||||
|
||||
refillCount shouldBe 1
|
||||
}
|
||||
|
||||
it should "coalesce refill requests until creation finishes" in {
|
||||
val events = scala.collection.mutable.ArrayBuffer.empty[String]
|
||||
val coordinator = new DeferredRefillCoordinator(() => events += "refill")
|
||||
|
||||
coordinator.withRefillDeferred {
|
||||
events += "creation"
|
||||
coordinator.requestRefill()
|
||||
coordinator.requestRefill()
|
||||
events shouldBe Vector("creation")
|
||||
}
|
||||
|
||||
events shouldBe Vector("creation", "refill")
|
||||
}
|
||||
|
||||
it should "release a deferred refill when creation fails" in {
|
||||
val events = scala.collection.mutable.ArrayBuffer.empty[String]
|
||||
val coordinator = new DeferredRefillCoordinator(() => events += "refill")
|
||||
|
||||
an[IllegalStateException] should be thrownBy coordinator.withRefillDeferred {
|
||||
coordinator.requestRefill()
|
||||
throw new IllegalStateException("creation failed")
|
||||
}
|
||||
|
||||
events shouldBe Vector("refill")
|
||||
}
|
||||
|
||||
"poolSchemaNames" should "include both schema versions" in {
|
||||
PostgresGameSchemaPool.poolSchemaNames(2) shouldBe Vector(
|
||||
"eagle_pool_h1_t2_0",
|
||||
|
||||
Reference in New Issue
Block a user