Use synchronous=NORMAL in sqlite since we are using WAL
This commit is contained in:
parent
782fb29dbf
commit
b5c9b1f35a
@ -71,6 +71,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
init {
|
init {
|
||||||
database.createStatement().use {
|
database.createStatement().use {
|
||||||
it.execute("PRAGMA journal_mode=WAL")
|
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 `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 `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)")
|
it.execute("CREATE TABLE IF NOT EXISTS `client_context` (`uuid` VARCHAR NOT NULL PRIMARY KEY, `data` BLOB NOT NULL)")
|
||||||
|
@ -39,6 +39,7 @@ import java.sql.DriverManager
|
|||||||
import java.sql.PreparedStatement
|
import java.sql.PreparedStatement
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
import java.util.concurrent.Executor
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
@ -51,6 +52,8 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
protected abstract fun load(at: ByteKey): CompletableFuture<KOptional<ByteArray>>
|
protected abstract fun load(at: ByteKey): CompletableFuture<KOptional<ByteArray>>
|
||||||
protected abstract fun write(at: ByteKey, value: ByteArray)
|
protected abstract fun write(at: ByteKey, value: ByteArray)
|
||||||
|
|
||||||
|
protected abstract val executor: Executor
|
||||||
|
|
||||||
override fun loadCells(pos: ChunkPos): CompletableFuture<KOptional<Pair<Object2DArray<out AbstractCell>, ChunkState>>> {
|
override fun loadCells(pos: ChunkPos): CompletableFuture<KOptional<Pair<Object2DArray<out AbstractCell>, ChunkState>>> {
|
||||||
val chunkX = pos.x
|
val chunkX = pos.x
|
||||||
val chunkY = pos.y
|
val chunkY = pos.y
|
||||||
@ -112,62 +115,48 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun saveEntities(pos: ChunkPos, data: Collection<AbstractEntity>, now: Boolean): Boolean {
|
override fun saveEntities(pos: ChunkPos, data: Collection<AbstractEntity>, now: Boolean): Boolean {
|
||||||
if (now) {
|
executor.execute {
|
||||||
val chunkX = pos.x
|
val chunkX = pos.x
|
||||||
val chunkY = pos.y
|
val chunkY = pos.y
|
||||||
val key = ByteKey(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())
|
val key = ByteKey(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())
|
||||||
|
|
||||||
write(key, writeEntities(data))
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveCells0(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState) {
|
override fun saveCells(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState): Boolean {
|
||||||
val buff = FastByteArrayOutputStream()
|
executor.execute {
|
||||||
val stream = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buff)))
|
val buff = FastByteArrayOutputStream()
|
||||||
|
val stream = DataOutputStream(BufferedOutputStream(DeflaterOutputStream(buff)))
|
||||||
|
|
||||||
stream.writeVarInt(when (state) {
|
stream.writeVarInt(
|
||||||
ChunkState.FRESH -> 0
|
when (state) {
|
||||||
ChunkState.EMPTY -> 0
|
ChunkState.FRESH -> 0
|
||||||
ChunkState.TERRAIN -> 1
|
ChunkState.EMPTY -> 0
|
||||||
ChunkState.MICRO_DUNGEONS -> 2
|
ChunkState.TERRAIN -> 1
|
||||||
ChunkState.CAVE_LIQUID -> 3
|
ChunkState.MICRO_DUNGEONS -> 2
|
||||||
ChunkState.FULL -> 4
|
ChunkState.CAVE_LIQUID -> 3
|
||||||
})
|
ChunkState.FULL -> 4
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
stream.writeVarInt(418)
|
stream.writeVarInt(418)
|
||||||
|
|
||||||
for (y in 0 until CHUNK_SIZE) {
|
for (y in 0 until CHUNK_SIZE) {
|
||||||
for (x in 0 until CHUNK_SIZE) {
|
for (x in 0 until CHUNK_SIZE) {
|
||||||
val cell = data.getOrNull(x, y) ?: AbstractCell.NULL
|
val cell = data.getOrNull(x, y) ?: AbstractCell.NULL
|
||||||
cell.writeLegacy(stream)
|
cell.writeLegacy(stream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val chunkX = pos.x
|
val chunkX = pos.x
|
||||||
val chunkY = pos.y
|
val chunkY = pos.y
|
||||||
val key = ByteKey(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())
|
val key = ByteKey(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())
|
||||||
|
|
||||||
stream.close()
|
stream.close()
|
||||||
write(key, buff.array.copyOf(buff.length))
|
write(key, buff.array.copyOf(buff.length))
|
||||||
}
|
|
||||||
|
|
||||||
override fun saveCells(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState, now: Boolean): Boolean {
|
|
||||||
if (now) {
|
|
||||||
saveCells0(pos, data, state)
|
|
||||||
} else {
|
|
||||||
Starbound.EXECUTOR.execute {
|
|
||||||
saveCells0(pos, data, state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -188,6 +177,8 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Memory(private val get: (ByteKey) -> ByteArray?, private val set: (ByteKey, ByteArray) -> Unit) : LegacyWorldStorage() {
|
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<KOptional<ByteArray>> {
|
override fun load(at: ByteKey): CompletableFuture<KOptional<ByteArray>> {
|
||||||
return CompletableFuture.completedFuture(KOptional.ofNullable(get(at)))
|
return CompletableFuture.completedFuture(KOptional.ofNullable(get(at)))
|
||||||
}
|
}
|
||||||
@ -200,10 +191,10 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DB5(private val database: BTreeDB5) : LegacyWorldStorage() {
|
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<KOptional<ByteArray>> {
|
override fun load(at: ByteKey): CompletableFuture<KOptional<ByteArray>> {
|
||||||
return CompletableFuture.supplyAsync(Supplier { database.read(at) }, carrier)
|
return CompletableFuture.supplyAsync(Supplier { database.read(at) }, executor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(at: ByteKey, value: ByteArray) {
|
override fun write(at: ByteKey, value: ByteArray) {
|
||||||
@ -211,13 +202,13 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
carrier.execute { database.close() }
|
executor.execute { database.close() }
|
||||||
carrier.wait(300L, TimeUnit.SECONDS)
|
executor.wait(300L, TimeUnit.SECONDS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SQL(path: File) : LegacyWorldStorage() {
|
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 connection = DriverManager.getConnection("jdbc:sqlite:${path.canonicalPath.replace('\\', '/')}")
|
||||||
private val cleaner: Cleaner.Cleanable
|
private val cleaner: Cleaner.Cleanable
|
||||||
|
|
||||||
@ -231,10 +222,11 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
|
|
||||||
connection.createStatement().use {
|
connection.createStatement().use {
|
||||||
it.execute("PRAGMA journal_mode=WAL")
|
it.execute("PRAGMA journal_mode=WAL")
|
||||||
|
it.execute("PRAGMA synchronous=NORMAL")
|
||||||
|
|
||||||
it.execute("""CREATE TABLE IF NOT EXISTS `data` (
|
it.execute("""CREATE TABLE IF NOT EXISTS "data" (
|
||||||
|`key` BLOB NOT NULL PRIMARY KEY,
|
|"key" BLOB NOT NULL PRIMARY KEY,
|
||||||
|`value` BLOB NOT NULL
|
|"value" BLOB NOT NULL
|
||||||
|)""".trimMargin())
|
|)""".trimMargin())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,11 +248,11 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
KOptional()
|
KOptional()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, carrier)
|
}, executor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(at: ByteKey, value: ByteArray) {
|
override fun write(at: ByteKey, value: ByteArray) {
|
||||||
carrier.execute {
|
executor.execute {
|
||||||
writer.setBytes(1, at.toByteArray())
|
writer.setBytes(1, at.toByteArray())
|
||||||
writer.setBytes(2, value)
|
writer.setBytes(2, value)
|
||||||
writer.execute()
|
writer.execute()
|
||||||
@ -269,8 +261,8 @@ sealed class LegacyWorldStorage() : WorldStorage() {
|
|||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
// carrier.execute { connection.commit() }
|
// carrier.execute { connection.commit() }
|
||||||
carrier.execute { cleaner.clean() }
|
executor.execute { cleaner.clean() }
|
||||||
carrier.wait(300L, TimeUnit.SECONDS)
|
executor.wait(300L, TimeUnit.SECONDS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ class NativeWorldStorage() : WorldStorage() {
|
|||||||
return super.saveEntities(pos, data, now)
|
return super.saveEntities(pos, data, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveCells(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState, now: Boolean): Boolean {
|
override fun saveCells(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState): Boolean {
|
||||||
return super.saveCells(pos, data, state, now)
|
return super.saveCells(pos, data, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveMetadata(data: Metadata): Boolean {
|
override fun saveMetadata(data: Metadata): Boolean {
|
||||||
|
@ -533,7 +533,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeToStorage(now: Boolean = false): Collection<AbstractEntity> {
|
private fun writeToStorage(): Collection<AbstractEntity> {
|
||||||
if (!cells.isInitialized() || state <= ChunkState.EMPTY)
|
if (!cells.isInitialized() || state <= ChunkState.EMPTY)
|
||||||
return emptyList()
|
return emptyList()
|
||||||
|
|
||||||
@ -542,8 +542,8 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
aabbd,
|
aabbd,
|
||||||
filter = Predicate { !it.isRemote && aabbd.isInside(it.position) })
|
filter = Predicate { !it.isRemote && aabbd.isInside(it.position) })
|
||||||
|
|
||||||
world.storage.saveCells(pos, copyCells(), state, now)
|
world.storage.saveCells(pos, copyCells(), state)
|
||||||
world.storage.saveEntities(pos, unloadable.filter { it.isPersistent }, now)
|
world.storage.saveEntities(pos, unloadable.filter { it.isPersistent })
|
||||||
|
|
||||||
return unloadable
|
return unloadable
|
||||||
}
|
}
|
||||||
@ -567,7 +567,7 @@ class ServerChunk(world: ServerWorld, pos: ChunkPos) : Chunk<ServerWorld, Server
|
|||||||
world.chunkMap.remove(pos)
|
world.chunkMap.remove(pos)
|
||||||
flushTask.cancel(false)
|
flushTask.cancel(false)
|
||||||
|
|
||||||
writeToStorage(true).forEach {
|
writeToStorage().forEach {
|
||||||
it.remove(AbstractEntity.RemovalReason.UNLOADED)
|
it.remove(AbstractEntity.RemovalReason.UNLOADED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,6 +97,7 @@ class ServerUniverse(folder: File? = null) : Universe(), Closeable {
|
|||||||
|
|
||||||
database.createStatement().use {
|
database.createStatement().use {
|
||||||
it.execute("PRAGMA journal_mode=WAL")
|
it.execute("PRAGMA journal_mode=WAL")
|
||||||
|
it.execute("PRAGMA synchronous=NORMAL")
|
||||||
|
|
||||||
it.execute("""
|
it.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS `chunk` (
|
CREATE TABLE IF NOT EXISTS `chunk` (
|
||||||
|
@ -83,7 +83,7 @@ abstract class WorldStorage : Closeable {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun saveCells(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState, now: Boolean = false): Boolean {
|
open fun saveCells(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,8 +152,8 @@ abstract class WorldStorage : Closeable {
|
|||||||
return children.any { it.saveEntities(pos, data, now) }
|
return children.any { it.saveEntities(pos, data, now) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveCells(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState, now: Boolean): Boolean {
|
override fun saveCells(pos: ChunkPos, data: Object2DArray<out AbstractCell>, state: ChunkState): Boolean {
|
||||||
return children.any { it.saveCells(pos, data, state, now) }
|
return children.any { it.saveCells(pos, data, state) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun saveMetadata(data: Metadata): Boolean {
|
override fun saveMetadata(data: Metadata): Boolean {
|
||||||
|
Loading…
Reference in New Issue
Block a user