mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
refactor(level-hash): move keymap IO logic to separate classes
This commit is contained in:
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.levelhash.internal
|
||||
|
||||
import com.itsaky.androidide.levelhash.RandomAccessIO
|
||||
import com.itsaky.androidide.levelhash.internal.PersistentKeymapIO.Companion.KEYMAP_SEGMENT_SIZE_BYTES
|
||||
import com.itsaky.androidide.levelhash.util.MappedRandomAccessIO
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.DataInput
|
||||
import java.io.DataOutput
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.MappedByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
|
||||
/**
|
||||
* A segment of the key map file.
|
||||
*/
|
||||
internal class KeymapSegment(
|
||||
val file: RandomAccessFile,
|
||||
val path: String,
|
||||
val segmentOffset: Long,
|
||||
val segmentLength: Long,
|
||||
val io: MappedRandomAccessIO = MappedRandomAccessIO(),
|
||||
) : DataInput by io, DataOutput by io, RandomAccessIO by io, AutoCloseable {
|
||||
|
||||
lateinit var mappedBuffer: MappedByteBuffer
|
||||
|
||||
init {
|
||||
remap(segmentOffset, segmentLength, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a region of the backing file into memory, unmapping previous mappings,
|
||||
* if any.
|
||||
*/
|
||||
@Suppress("SameParameterValue")
|
||||
private fun remap(position: Long, size: Long, forceRemap: Boolean = false
|
||||
) {
|
||||
if (!forceRemap && (segmentOffset == position && segmentLength == size)) {
|
||||
// the given region is already mapped into memory
|
||||
return
|
||||
}
|
||||
|
||||
unmap()
|
||||
|
||||
this.mappedBuffer =
|
||||
file.channel.map(FileChannel.MapMode.READ_WRITE, segmentOffset,
|
||||
segmentLength)
|
||||
this.io.reset(mappedBuffer, segmentOffset, segmentLength)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmap the memory mapped region, if mapped.
|
||||
*/
|
||||
|
||||
override fun close() {
|
||||
unmap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmap the memory mapped buffer. This [KeymapSegment] becomes invalid after
|
||||
* this operation and must not be used further unless remapped.
|
||||
*
|
||||
* @return `true` if the unmapping request was successful, `false` otherwise.
|
||||
*/
|
||||
fun unmap(): Boolean {
|
||||
if (!::mappedBuffer.isInitialized) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
val cleanerF = mappedBuffer.javaClass.getDeclaredField("cleaner")
|
||||
cleanerF.isAccessible = true
|
||||
val cleaner = cleanerF.get(mappedBuffer)
|
||||
|
||||
val cleanM = cleaner.javaClass.getDeclaredMethod("clean")
|
||||
cleanM.isAccessible = true
|
||||
|
||||
cleanM.invoke(cleaner)
|
||||
return true
|
||||
} catch (err: Throwable) {
|
||||
log.error("Failed to unmap keymap segment. file={}, offset={}, size={}",
|
||||
path, segmentOffset, segmentLength, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(KeymapSegment::class.java)
|
||||
|
||||
/**
|
||||
* Create a new segment.
|
||||
*
|
||||
* @param offset The offset of the segment in the keymap file.
|
||||
* @param file The file to create the segment in.
|
||||
* @param path The path of the file (used for debugging).
|
||||
*/
|
||||
fun create(offset: Long, file: RandomAccessFile, path: String
|
||||
): KeymapSegment {
|
||||
return KeymapSegment(file = file, path = path, segmentOffset = offset,
|
||||
segmentLength = KEYMAP_SEGMENT_SIZE_BYTES)
|
||||
}
|
||||
}
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.levelhash.internal
|
||||
|
||||
import com.itsaky.androidide.levelhash.HashT
|
||||
import com.itsaky.androidide.levelhash.internal.PersistentLevelHashIO.Companion.MAGIC_NUMBER_SIZE_BYTES
|
||||
import com.itsaky.androidide.levelhash.util.DataExternalizers.SIZE_INT
|
||||
import com.itsaky.androidide.levelhash.util.DataExternalizers.SIZE_LONG
|
||||
import com.itsaky.androidide.levelhash.util.FileUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
internal class PersistentKeymapIO(indexFile: File
|
||||
) : AutoCloseable {
|
||||
// TODO(itsaky): use LFU cache to limit the number of segments mapped in memory at once.
|
||||
/**
|
||||
* The segments for the keymap file.
|
||||
*/
|
||||
private val keymapSegments = arrayOfNulls<KeymapSegment>(KEYMAP_MAX_SEGMENTS)
|
||||
private val keymapFile = File(indexFile.parentFile, "${indexFile.name}._i")
|
||||
private val raKeymapFile = RandomAccessFile(keymapFile, "rw")
|
||||
|
||||
private val segmentCount by lazy {
|
||||
if (!keymapFile.exists() || !keymapFile.isFile || keymapFile.length() <= KEYMAP_HEADER_SIZE_BYTES) {
|
||||
// there should be 1 segment initially
|
||||
return@lazy 1
|
||||
}
|
||||
|
||||
raKeymapFile.seek(KEYMAP_HEADER_SIZE_BYTES.toLong())
|
||||
val count = raKeymapFile.readLong()
|
||||
raKeymapFile.seek(0)
|
||||
return@lazy count
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
keymapSegments.forEach { segment ->
|
||||
try {
|
||||
segment?.unmap()
|
||||
} catch (err: Throwable) {
|
||||
log.error("Failed to unmap segment", err)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
raKeymapFile.close()
|
||||
} catch (e: Throwable) {
|
||||
log.error("Failed to close keymap file", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSegmentOffset(index: Int): Long {
|
||||
return KEYMAP_HEADER_SIZE_BYTES + index * KEYMAP_SEGMENT_SIZE_BYTES
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a segment for the keymap file.
|
||||
*/
|
||||
fun getOrCreateSegment(index: Int): KeymapSegment {
|
||||
var segment = keymapSegments[index]
|
||||
if (segment == null) {
|
||||
segment = KeymapSegment.create(getSegmentOffset(index), raKeymapFile,
|
||||
keymapFile.canonicalPath)
|
||||
keymapSegments[index] = segment
|
||||
}
|
||||
return segment
|
||||
}
|
||||
|
||||
fun segmentAndPosForPos(
|
||||
levelIdx: Int,
|
||||
bucketIdx: Int,
|
||||
slotIdx: Int,
|
||||
): Pair<KeymapSegment, Long> {
|
||||
val hash = hash(levelIdx, bucketIdx, slotIdx)
|
||||
val segmentIdx = segmentIndex(hash)
|
||||
val positionInSegment = positionInSegment(hash)
|
||||
val segment = getOrCreateSegment(segmentIdx)
|
||||
return segment to positionInSegment
|
||||
}
|
||||
|
||||
fun deallocate(
|
||||
offset: Long,
|
||||
len: Long
|
||||
) {
|
||||
FileUtils.deallocate(raKeymapFile, offset, len)
|
||||
}
|
||||
|
||||
fun deallocate(
|
||||
segment: KeymapSegment,
|
||||
positionInSegment: Long,
|
||||
len: Long
|
||||
) {
|
||||
FileUtils.deallocate(raKeymapFile, segment.segmentOffset + positionInSegment, len)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(PersistentKeymapIO::class.java)
|
||||
|
||||
/**
|
||||
* The number of bytes in the header of the keymap file that is used to store
|
||||
* the segment count.
|
||||
*/
|
||||
internal const val KEYMAP_HEADER_SEGCOUNT_BYTES = SIZE_INT
|
||||
|
||||
/**
|
||||
* The number of bytes in the header of the keymap file that is used to store
|
||||
* the size of the segment structures.
|
||||
*/
|
||||
internal const val KEYMAP_HEADER_SEGSIZE_BYTES = SIZE_LONG
|
||||
|
||||
/**
|
||||
* The number of bytes used to store the header of the keymap file.
|
||||
*/
|
||||
internal const val KEYMAP_HEADER_SIZE_BYTES: Long =
|
||||
MAGIC_NUMBER_SIZE_BYTES + KEYMAP_HEADER_SEGCOUNT_BYTES + KEYMAP_HEADER_SEGSIZE_BYTES
|
||||
|
||||
/**
|
||||
* The size of one segment in the key map file.
|
||||
*/
|
||||
internal const val KEYMAP_SEGMENT_SIZE_BYTES: Long = 1024 * 1024 // 1MB
|
||||
|
||||
/**
|
||||
* The number of bytes that are used to store an entry in a key map file.
|
||||
*/
|
||||
internal const val KEYMAP_ENTRY_BYTES: Long = SIZE_LONG
|
||||
|
||||
/**
|
||||
* The number of entries that can be stored in a segment in the key map file.
|
||||
*/
|
||||
internal const val KEYMAP_ENTRIES_PER_SEGMENT =
|
||||
KEYMAP_SEGMENT_SIZE_BYTES / KEYMAP_ENTRY_BYTES
|
||||
|
||||
/**
|
||||
* The maximum number of segments that can be stored for an index.
|
||||
*/
|
||||
internal const val KEYMAP_MAX_SEGMENTS = 100
|
||||
|
||||
/**
|
||||
* The maximum number of entries that can be stored in all of the keymap segments combined.
|
||||
*/
|
||||
const val KEYMAP_MAX_ENTRIES =
|
||||
KEYMAP_MAX_SEGMENTS * KEYMAP_ENTRIES_PER_SEGMENT
|
||||
|
||||
/**
|
||||
* Magic number that is used as the file signature to identify the keymap file.
|
||||
*/
|
||||
internal const val KEYMAP_MAGIC_NUMBER = 0x414944584B
|
||||
|
||||
/**
|
||||
* Get the ID of the segment that should be used to store the store the
|
||||
* given position hash.
|
||||
*/
|
||||
private fun segmentIndex(positionHash: HashT): Int {
|
||||
return (positionHash % KEYMAP_MAX_SEGMENTS.toULong()).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position of the entry in the segment where the entry should be
|
||||
* written. The returned position is in bytes from the beginning of the
|
||||
* segment.
|
||||
*/
|
||||
private fun positionInSegment(positionHash: HashT): Long {
|
||||
return ((positionHash % KEYMAP_ENTRIES_PER_SEGMENT.toULong()).toLong() * KEYMAP_ENTRY_BYTES)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash of the position coordinates.
|
||||
*/
|
||||
private fun hash(levelNum: Int, bucketIdx: Int, slotIdx: Int
|
||||
): HashT {
|
||||
var hash = 17L
|
||||
hash = 31L * hash + levelNum
|
||||
hash = 31L * hash + bucketIdx
|
||||
hash = 31L * hash + slotIdx
|
||||
return hash.toULong()
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-262
@@ -17,11 +17,10 @@
|
||||
|
||||
package com.itsaky.androidide.levelhash.internal
|
||||
|
||||
import com.itsaky.androidide.jna.Falloc
|
||||
import com.itsaky.androidide.jna.LibC
|
||||
import com.itsaky.androidide.levelhash.DataExternalizer
|
||||
import com.itsaky.androidide.levelhash.HashT
|
||||
import com.itsaky.androidide.levelhash.RandomAccessIO
|
||||
import com.itsaky.androidide.levelhash.internal.PersistentKeymapIO.Companion.KEYMAP_HEADER_SIZE_BYTES
|
||||
import com.itsaky.androidide.levelhash.internal.PersistentKeymapIO.Companion.KEYMAP_MAX_SEGMENTS
|
||||
import com.itsaky.androidide.levelhash.internal.PersistentKeymapIO.Companion.KEYMAP_SEGMENT_SIZE_BYTES
|
||||
import com.itsaky.androidide.levelhash.seekBoolean
|
||||
import com.itsaky.androidide.levelhash.seekInt
|
||||
import com.itsaky.androidide.levelhash.util.DataExternalizers.SIZE_BOOLEAN
|
||||
@@ -29,14 +28,9 @@ import com.itsaky.androidide.levelhash.util.DataExternalizers.SIZE_INT
|
||||
import com.itsaky.androidide.levelhash.util.DataExternalizers.SIZE_LONG
|
||||
import com.itsaky.androidide.levelhash.util.FileUtils
|
||||
import com.itsaky.androidide.levelhash.util.MappedRandomAccessIO
|
||||
import com.itsaky.androidide.utils.FDUtils
|
||||
import com.sun.jna.Native
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.DataInput
|
||||
import java.io.DataOutput
|
||||
import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.MappedByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
|
||||
|
||||
@@ -56,13 +50,13 @@ private val logger = LoggerFactory.getLogger(PersistentLevelHashIO::class.java)
|
||||
*
|
||||
* ## Keymap
|
||||
*
|
||||
* - The keymap file header occupies [PersistentLevelHashIO.KEYMAP_HEADER_SIZE_BYTES]
|
||||
* - The keymap file header occupies [PersistentKeymapIO.KEYMAP_HEADER_SIZE_BYTES]
|
||||
* bytes.
|
||||
* - Each segment is occupies [PersistentLevelHashIO.KEYMAP_SEGMENT_SIZE_BYTES]
|
||||
* - Each segment is occupies [PersistentKeymapIO.KEYMAP_SEGMENT_SIZE_BYTES]
|
||||
* bytes.
|
||||
* - There can be at most [PersistentLevelHashIO.KEYMAP_MAX_SEGMENTS] segments in
|
||||
* - There can be at most [PersistentKeymapIO.KEYMAP_MAX_SEGMENTS] segments in
|
||||
* the keymap file.
|
||||
* - Each segment can store at most [PersistentLevelHashIO.KEYMAP_ENTRIES_PER_SEGMENT]
|
||||
* - Each segment can store at most [PersistentKeymapIO.KEYMAP_ENTRIES_PER_SEGMENT]
|
||||
* key-value pairs.
|
||||
*
|
||||
* Considering the default values, a keymap file can:
|
||||
@@ -127,18 +121,12 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
// as the values file is randomly accessed, and by multiple functions, or
|
||||
// even multiple threads, the current position cannot be determined
|
||||
|
||||
private val keymapIo = PersistentKeymapIO(indexFile)
|
||||
|
||||
private val raIndexFile by lazy {
|
||||
RandomAccessFile(indexFile, "rw")
|
||||
}
|
||||
|
||||
private val keymapFile by lazy {
|
||||
File(indexFile.parentFile, "${indexFile.name}._i")
|
||||
}
|
||||
|
||||
private val raKeymapFile by lazy {
|
||||
RandomAccessFile(keymapFile, "rw")
|
||||
}
|
||||
|
||||
private val metaFile by lazy {
|
||||
File(indexFile.parentFile, "${indexFile.name}._meta")
|
||||
}
|
||||
@@ -147,24 +135,6 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
RandomAccessFile(metaFile, "rw")
|
||||
}
|
||||
|
||||
private val segmentCount by lazy {
|
||||
if (!keymapFile.exists() || !keymapFile.isFile || keymapFile.length() <= KEYMAP_HEADER_SIZE_BYTES) {
|
||||
// there should be 1 segment initially
|
||||
return@lazy 1
|
||||
}
|
||||
|
||||
raKeymapFile.seek(KEYMAP_HEADER_SIZE_BYTES.toLong())
|
||||
val count = raKeymapFile.readLong()
|
||||
raKeymapFile.seek(0)
|
||||
return@lazy count
|
||||
}
|
||||
|
||||
// TODO(itsaky): use LFU cache to limit the number of segments mapped in memory at once.
|
||||
/**
|
||||
* The segments for the keymap file.
|
||||
*/
|
||||
private val keymapSegments = arrayOfNulls<KeymapSegment>(KEYMAP_MAX_SEGMENTS)
|
||||
|
||||
private var valuesFirstEntryAt = 0L
|
||||
private var valuesLastEntryAt = 0L
|
||||
private var valuesWriteAt = 0L
|
||||
@@ -200,35 +170,6 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
private fun getValuesRealOffset(offset: Long) =
|
||||
VALUES_HEADER_SIZE_BYTES + offset
|
||||
|
||||
private fun getSegmentOffset(index: Int): Long {
|
||||
return KEYMAP_HEADER_SIZE_BYTES + index * KEYMAP_SEGMENT_SIZE_BYTES
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a segment for the keymap file.
|
||||
*/
|
||||
private fun getOrCreateSegment(index: Int): KeymapSegment {
|
||||
var segment = keymapSegments[index]
|
||||
if (segment == null) {
|
||||
segment = KeymapSegment.create(getSegmentOffset(index), raKeymapFile,
|
||||
keymapFile.canonicalPath)
|
||||
keymapSegments[index] = segment
|
||||
}
|
||||
return segment
|
||||
}
|
||||
|
||||
private fun segmentAndPosForPos(
|
||||
levelIdx: Int,
|
||||
bucketIdx: Int,
|
||||
slotIdx: Int,
|
||||
): Pair<KeymapSegment, Long> {
|
||||
val hash = hash(levelIdx, bucketIdx, slotIdx)
|
||||
val segmentIdx = segmentIndex(hash)
|
||||
val positionInSegment = positionInSegment(hash)
|
||||
val segment = getOrCreateSegment(segmentIdx)
|
||||
return segment to positionInSegment
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address of the value entry in the values for the given slot
|
||||
* position.
|
||||
@@ -242,8 +183,8 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
bucketIdx: Int,
|
||||
slotIdx: Int,
|
||||
): Long {
|
||||
val (segment, positionInSegment) = segmentAndPosForPos(levelIdx, bucketIdx,
|
||||
slotIdx)
|
||||
val (segment, positionInSegment) = keymapIo.segmentAndPosForPos(levelIdx,
|
||||
bucketIdx, slotIdx)
|
||||
segment.position(positionInSegment)
|
||||
return segment.readLong().also { address ->
|
||||
check(
|
||||
@@ -379,8 +320,8 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
key: K?,
|
||||
value: V?,
|
||||
) {
|
||||
val (segment, positionInSegment) = segmentAndPosForPos(levelNum, bucketIdx,
|
||||
slotIdx)
|
||||
val (segment, positionInSegment) = keymapIo.segmentAndPosForPos(levelNum,
|
||||
bucketIdx, slotIdx)
|
||||
|
||||
if (key == null) {
|
||||
// delete entry
|
||||
@@ -428,7 +369,7 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
return null
|
||||
}
|
||||
|
||||
FileUtils.deallocate(raKeymapFile, positionInSegment, SIZE_LONG)
|
||||
keymapIo.deallocate(segment, positionInSegment, SIZE_LONG)
|
||||
|
||||
valuesIo.position(valueAddr - 1)
|
||||
|
||||
@@ -474,10 +415,10 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
// TODO: This should not clear the header data in the files
|
||||
FileUtils.deallocate(raKeymapFile, 0, KEYMAP_SEGMENT_SIZE_BYTES * KEYMAP_MAX_SEGMENTS)
|
||||
FileUtils.deallocate(raIndexFile, 0, valuesMapSize + VALUES_HEADER_SIZE_BYTES)
|
||||
// FileUtils.deallocate(raMetaFile)
|
||||
keymapIo.deallocate(KEYMAP_HEADER_SIZE_BYTES,
|
||||
KEYMAP_SEGMENT_SIZE_BYTES * KEYMAP_MAX_SEGMENTS)
|
||||
|
||||
FileUtils.deallocate(raIndexFile, VALUES_HEADER_SIZE_BYTES, valuesMapSize)
|
||||
}
|
||||
|
||||
fun tryMoveToInterim(
|
||||
@@ -516,12 +457,11 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
keymapSegments.forEach { segment ->
|
||||
try {
|
||||
segment?.unmap()
|
||||
} catch (err: Throwable) {
|
||||
logger.error("Failed to unmap segment", err)
|
||||
}
|
||||
|
||||
try {
|
||||
keymapIo.close()
|
||||
} catch (e: Throwable) {
|
||||
log.error("Failed to close keymap file", e)
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -530,12 +470,6 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
logger.error("Failed to close index file", e)
|
||||
}
|
||||
|
||||
try {
|
||||
raKeymapFile.close()
|
||||
} catch (e: Throwable) {
|
||||
logger.error("Failed to close keymap file", e)
|
||||
}
|
||||
|
||||
try {
|
||||
raMetaFile.close()
|
||||
} catch (e: Throwable) {
|
||||
@@ -564,67 +498,17 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
* The number of bytes it takes to store the magic number of the keymap/values
|
||||
* file.
|
||||
*/
|
||||
private const val MAGIC_NUMBER_SIZE_BYTES = SIZE_LONG
|
||||
|
||||
/**
|
||||
* The number of bytes in the header of the keymap file that is used to store
|
||||
* the segment count.
|
||||
*/
|
||||
private const val KEYMAP_HEADER_SEGCOUNT_BYTES = SIZE_INT
|
||||
|
||||
/**
|
||||
* The number of bytes in the header of the keymap file that is used to store
|
||||
* the size of the segment structures.
|
||||
*/
|
||||
private const val KEYMAP_HEADER_SEGSIZE_BYTES = SIZE_LONG
|
||||
|
||||
/**
|
||||
* The number of bytes used to store the header of the keymap file.
|
||||
*/
|
||||
private const val KEYMAP_HEADER_SIZE_BYTES: Long =
|
||||
MAGIC_NUMBER_SIZE_BYTES + KEYMAP_HEADER_SEGCOUNT_BYTES + KEYMAP_HEADER_SEGSIZE_BYTES
|
||||
|
||||
/**
|
||||
* The size of one segment in the key map file.
|
||||
*/
|
||||
private const val KEYMAP_SEGMENT_SIZE_BYTES: Long = 1024 * 1024 // 1MB
|
||||
|
||||
/**
|
||||
* The number of bytes that are used to store an entry in a key map file.
|
||||
*/
|
||||
private const val KEYMAP_ENTRY_BYTES: Long = SIZE_LONG
|
||||
|
||||
/**
|
||||
* The number of entries that can be stored in a segment in the key map file.
|
||||
*/
|
||||
private const val KEYMAP_ENTRIES_PER_SEGMENT =
|
||||
KEYMAP_SEGMENT_SIZE_BYTES / KEYMAP_ENTRY_BYTES
|
||||
|
||||
/**
|
||||
* The maximum number of segments that can be stored for an index.
|
||||
*/
|
||||
private const val KEYMAP_MAX_SEGMENTS = 100
|
||||
|
||||
/**
|
||||
* The maximum number of entries that can be stored in all of the keymap segments combined.
|
||||
*/
|
||||
const val KEYMAP_MAX_ENTRIES =
|
||||
KEYMAP_MAX_SEGMENTS * KEYMAP_ENTRIES_PER_SEGMENT
|
||||
|
||||
/**
|
||||
* Magic number that is used as the file signature to identify the keymap file.
|
||||
*/
|
||||
private const val KEYMAP_MAGIC_NUMBER = 0x414944584B
|
||||
internal const val MAGIC_NUMBER_SIZE_BYTES = SIZE_LONG
|
||||
|
||||
/**
|
||||
* Magic number that is used as the file signature to identify the values file.
|
||||
*/
|
||||
private const val VALUES_MAGIC_NUMBER = 0x4149445856
|
||||
internal const val VALUES_MAGIC_NUMBER = 0x4149445856
|
||||
|
||||
/**
|
||||
* The size of the header (bytes) in the values file.
|
||||
*/
|
||||
private const val VALUES_HEADER_SIZE_BYTES: Long =
|
||||
internal const val VALUES_HEADER_SIZE_BYTES: Long =
|
||||
MAGIC_NUMBER_SIZE_BYTES + SIZE_LONG + // first entry address
|
||||
SIZE_LONG // next entry address
|
||||
|
||||
@@ -632,126 +516,7 @@ class PersistentLevelHashIO<K : Any, V : Any?>(
|
||||
* The maximum size of the region of the values file that can be mapped into
|
||||
* memory at a given time.
|
||||
*/
|
||||
private const val VALUES_MAX_MAP_SIZE_BYTES = 512L * 1024L;
|
||||
|
||||
/**
|
||||
* Get the ID of the segment that should be used to store the store the
|
||||
* given position hash.
|
||||
*/
|
||||
private fun segmentIndex(positionHash: HashT): Int {
|
||||
return (positionHash % KEYMAP_MAX_SEGMENTS.toULong()).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position of the entry in the segment where the entry should be
|
||||
* written. The returned position is in bytes from the beginning of the
|
||||
* segment.
|
||||
*/
|
||||
private fun positionInSegment(positionHash: HashT): Long {
|
||||
return ((positionHash % KEYMAP_ENTRIES_PER_SEGMENT.toULong()).toLong() * KEYMAP_ENTRY_BYTES)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash of the position coordinates.
|
||||
*/
|
||||
private fun hash(levelNum: Int, bucketIdx: Int, slotIdx: Int
|
||||
): HashT {
|
||||
var hash = 17L
|
||||
hash = 31L * hash + levelNum
|
||||
hash = 31L * hash + bucketIdx
|
||||
hash = 31L * hash + slotIdx
|
||||
return hash.toULong()
|
||||
}
|
||||
internal const val VALUES_MAX_MAP_SIZE_BYTES = 512L * 1024L;
|
||||
}
|
||||
|
||||
/**
|
||||
* A segment of the key map file.
|
||||
*/
|
||||
private class KeymapSegment(
|
||||
val file: RandomAccessFile,
|
||||
val path: String,
|
||||
private val segmentOffset: Long,
|
||||
private val segmentLength: Long,
|
||||
private val io: MappedRandomAccessIO = MappedRandomAccessIO(),
|
||||
) : DataInput by io, DataOutput by io, RandomAccessIO by io, AutoCloseable {
|
||||
|
||||
lateinit var mappedBuffer: MappedByteBuffer
|
||||
|
||||
init {
|
||||
remap(segmentOffset, segmentLength, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a region of the backing file into memory, unmapping previous mappings,
|
||||
* if any.
|
||||
*/
|
||||
@Suppress("SameParameterValue")
|
||||
private fun remap(position: Long, size: Long, forceRemap: Boolean = false
|
||||
) {
|
||||
if (!forceRemap && (segmentOffset == position && segmentLength == size)) {
|
||||
// the given region is already mapped into memory
|
||||
return
|
||||
}
|
||||
|
||||
unmap()
|
||||
|
||||
this.mappedBuffer =
|
||||
file.channel.map(FileChannel.MapMode.READ_WRITE, segmentOffset,
|
||||
segmentLength)
|
||||
this.io.reset(mappedBuffer, segmentOffset, segmentLength)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmap the memory mapped region, if mapped.
|
||||
*/
|
||||
|
||||
override fun close() {
|
||||
unmap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmap the memory mapped buffer. This [KeymapSegment] becomes invalid after
|
||||
* this operation and must not be used further unless remapped.
|
||||
*
|
||||
* @return `true` if the unmapping request was successful, `false` otherwise.
|
||||
*/
|
||||
fun unmap(): Boolean {
|
||||
if (!::mappedBuffer.isInitialized) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
val cleanerF = mappedBuffer.javaClass.getDeclaredField("cleaner")
|
||||
cleanerF.isAccessible = true
|
||||
val cleaner = cleanerF.get(mappedBuffer)
|
||||
|
||||
val cleanM = cleaner.javaClass.getDeclaredMethod("clean")
|
||||
cleanM.isAccessible = true
|
||||
|
||||
cleanM.invoke(cleaner)
|
||||
return true
|
||||
} catch (err: Throwable) {
|
||||
logger.error(
|
||||
"Failed to unmap keymap segment. file={}, offset={}, size={}", path,
|
||||
segmentOffset, segmentLength, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Create a new segment.
|
||||
*
|
||||
* @param offset The offset of the segment in the keymap file.
|
||||
* @param file The file to create the segment in.
|
||||
* @param path The path of the file (used for debugging).
|
||||
*/
|
||||
fun create(offset: Long, file: RandomAccessFile, path: String
|
||||
): KeymapSegment {
|
||||
return KeymapSegment(file = file, path = path, segmentOffset = offset,
|
||||
segmentLength = KEYMAP_SEGMENT_SIZE_BYTES.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user