From b5c9b1f35af917f3536f1d63216fc587c20aca06 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Thu, 2 May 2024 17:54:48 +0700 Subject: [PATCH] Use synchronous=NORMAL in sqlite since we are using WAL --- .../kstarbound/server/StarboundServer.kt | 1 + .../server/world/LegacyWorldStorage.kt | 96 +++++++++---------- .../server/world/NativeWorldStorage.kt | 4 +- .../kstarbound/server/world/ServerChunk.kt | 8 +- .../kstarbound/server/world/ServerUniverse.kt | 1 + .../kstarbound/server/world/WorldStorage.kt | 6 +- 6 files changed, 55 insertions(+), 61 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt index 70f1255c..88b20422 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt @@ -71,6 +71,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread init { database.createStatement().use { it.execute("PRAGMA journal_mode=WAL") + it.execute("PRAGMA synchronous=NORMAL") it.execute("CREATE TABLE IF NOT EXISTS `metadata` (`key` VARCHAR NOT NULL PRIMARY KEY, `value` BLOB NOT NULL)") it.execute("CREATE TABLE IF NOT EXISTS `universe_flags` (`flag` VARCHAR NOT NULL PRIMARY KEY)") it.execute("CREATE TABLE IF NOT EXISTS `client_context` (`uuid` VARCHAR NOT NULL PRIMARY KEY, `data` BLOB NOT NULL)") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt index 326c1403..94dbe27d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyWorldStorage.kt @@ -39,6 +39,7 @@ import java.sql.DriverManager import java.sql.PreparedStatement import java.util.concurrent.CompletableFuture import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.Executor import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock import java.util.function.Function @@ -51,6 +52,8 @@ sealed class LegacyWorldStorage() : WorldStorage() { protected abstract fun load(at: ByteKey): CompletableFuture> protected abstract fun write(at: ByteKey, value: ByteArray) + protected abstract val executor: Executor + override fun loadCells(pos: ChunkPos): CompletableFuture, ChunkState>>> { val chunkX = pos.x val chunkY = pos.y @@ -112,62 +115,48 @@ sealed class LegacyWorldStorage() : WorldStorage() { } override fun saveEntities(pos: ChunkPos, data: Collection, now: Boolean): Boolean { - if (now) { + executor.execute { val chunkX = pos.x val chunkY = pos.y val key = ByteKey(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte()) write(key, writeEntities(data)) - } else { - Starbound.EXECUTOR.execute { - val chunkX = pos.x - val chunkY = pos.y - val key = ByteKey(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte()) - - write(key, writeEntities(data)) - } } return true } - private fun saveCells0(pos: ChunkPos, data: Object2DArray, state: ChunkState) { - val buff = FastByteArrayOutputStream() - val stream = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buff))) + override fun saveCells(pos: ChunkPos, data: Object2DArray, state: ChunkState): Boolean { + executor.execute { + val buff = FastByteArrayOutputStream() + val stream = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buff))) - stream.writeVarInt(when (state) { - ChunkState.FRESH -> 0 - ChunkState.EMPTY -> 0 - ChunkState.TERRAIN -> 1 - ChunkState.MICRO_DUNGEONS -> 2 - ChunkState.CAVE_LIQUID -> 3 - ChunkState.FULL -> 4 - }) + stream.writeVarInt( + when (state) { + ChunkState.FRESH -> 0 + ChunkState.EMPTY -> 0 + ChunkState.TERRAIN -> 1 + ChunkState.MICRO_DUNGEONS -> 2 + ChunkState.CAVE_LIQUID -> 3 + ChunkState.FULL -> 4 + } + ) - stream.writeVarInt(418) + stream.writeVarInt(418) - for (y in 0 until CHUNK_SIZE) { - for (x in 0 until CHUNK_SIZE) { - val cell = data.getOrNull(x, y) ?: AbstractCell.NULL - cell.writeLegacy(stream) + for (y in 0 until CHUNK_SIZE) { + for (x in 0 until CHUNK_SIZE) { + val cell = data.getOrNull(x, y) ?: AbstractCell.NULL + cell.writeLegacy(stream) + } } - } - val chunkX = pos.x - val chunkY = pos.y - val key = ByteKey(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte()) + val chunkX = pos.x + val chunkY = pos.y + val key = ByteKey(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte()) - stream.close() - write(key, buff.array.copyOf(buff.length)) - } - - override fun saveCells(pos: ChunkPos, data: Object2DArray, state: ChunkState, now: Boolean): Boolean { - if (now) { - saveCells0(pos, data, state) - } else { - Starbound.EXECUTOR.execute { - saveCells0(pos, data, state) - } + stream.close() + write(key, buff.array.copyOf(buff.length)) } return true @@ -188,6 +177,8 @@ sealed class LegacyWorldStorage() : WorldStorage() { } class Memory(private val get: (ByteKey) -> ByteArray?, private val set: (ByteKey, ByteArray) -> Unit) : LegacyWorldStorage() { + override val executor: Executor = Executor { it.run() } + override fun load(at: ByteKey): CompletableFuture> { return CompletableFuture.completedFuture(KOptional.ofNullable(get(at))) } @@ -200,10 +191,10 @@ sealed class LegacyWorldStorage() : WorldStorage() { } class DB5(private val database: BTreeDB5) : LegacyWorldStorage() { - private val carrier = CarriedExecutor(Starbound.IO_EXECUTOR) + override val executor = CarriedExecutor(Starbound.IO_EXECUTOR) override fun load(at: ByteKey): CompletableFuture> { - return CompletableFuture.supplyAsync(Supplier { database.read(at) }, carrier) + return CompletableFuture.supplyAsync(Supplier { database.read(at) }, executor) } override fun write(at: ByteKey, value: ByteArray) { @@ -211,13 +202,13 @@ sealed class LegacyWorldStorage() : WorldStorage() { } override fun close() { - carrier.execute { database.close() } - carrier.wait(300L, TimeUnit.SECONDS) + executor.execute { database.close() } + executor.wait(300L, TimeUnit.SECONDS) } } class SQL(path: File) : LegacyWorldStorage() { - private val carrier = CarriedExecutor(Starbound.IO_EXECUTOR) + override val executor = CarriedExecutor(Starbound.IO_EXECUTOR) private val connection = DriverManager.getConnection("jdbc:sqlite:${path.canonicalPath.replace('\\', '/')}") private val cleaner: Cleaner.Cleanable @@ -231,10 +222,11 @@ sealed class LegacyWorldStorage() : WorldStorage() { connection.createStatement().use { it.execute("PRAGMA journal_mode=WAL") + it.execute("PRAGMA synchronous=NORMAL") - it.execute("""CREATE TABLE IF NOT EXISTS `data` ( - |`key` BLOB NOT NULL PRIMARY KEY, - |`value` BLOB NOT NULL + it.execute("""CREATE TABLE IF NOT EXISTS "data" ( + |"key" BLOB NOT NULL PRIMARY KEY, + |"value" BLOB NOT NULL |)""".trimMargin()) } } @@ -256,11 +248,11 @@ sealed class LegacyWorldStorage() : WorldStorage() { KOptional() } } - }, carrier) + }, executor) } override fun write(at: ByteKey, value: ByteArray) { - carrier.execute { + executor.execute { writer.setBytes(1, at.toByteArray()) writer.setBytes(2, value) writer.execute() @@ -269,8 +261,8 @@ sealed class LegacyWorldStorage() : WorldStorage() { override fun close() { // carrier.execute { connection.commit() } - carrier.execute { cleaner.clean() } - carrier.wait(300L, TimeUnit.SECONDS) + executor.execute { cleaner.clean() } + executor.wait(300L, TimeUnit.SECONDS) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/NativeWorldStorage.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/NativeWorldStorage.kt index 243463a2..b11c4454 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/NativeWorldStorage.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/NativeWorldStorage.kt @@ -27,8 +27,8 @@ class NativeWorldStorage() : WorldStorage() { return super.saveEntities(pos, data, now) } - override fun saveCells(pos: ChunkPos, data: Object2DArray, state: ChunkState, now: Boolean): Boolean { - return super.saveCells(pos, data, state, now) + override fun saveCells(pos: ChunkPos, data: Object2DArray, state: ChunkState): Boolean { + return super.saveCells(pos, data, state) } override fun saveMetadata(data: Metadata): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt index 9e8ba9ca..0f470867 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerChunk.kt @@ -533,7 +533,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk { + private fun writeToStorage(): Collection { if (!cells.isInitialized() || state <= ChunkState.EMPTY) return emptyList() @@ -542,8 +542,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk, state: ChunkState, now: Boolean = false): Boolean { + open fun saveCells(pos: ChunkPos, data: Object2DArray, state: ChunkState): Boolean { return false } @@ -152,8 +152,8 @@ abstract class WorldStorage : Closeable { return children.any { it.saveEntities(pos, data, now) } } - override fun saveCells(pos: ChunkPos, data: Object2DArray, state: ChunkState, now: Boolean): Boolean { - return children.any { it.saveCells(pos, data, state, now) } + override fun saveCells(pos: ChunkPos, data: Object2DArray, state: ChunkState): Boolean { + return children.any { it.saveCells(pos, data, state) } } override fun saveMetadata(data: Metadata): Boolean {