From 23203c76cf56a648fd15149d76355bbe729afde2 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Sat, 27 Jul 2024 09:44:30 +0530 Subject: [PATCH] refactor(level-hash): move keymap IO logic to separate classes --- .../levelhash/internal/KeymapSegment.kt | 120 ++++++++ .../levelhash/internal/PersistentKeymapIO.kt | 197 ++++++++++++ .../internal/PersistentLevelHashIO.kt | 289 ++---------------- 3 files changed, 344 insertions(+), 262 deletions(-) create mode 100644 utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/KeymapSegment.kt create mode 100644 utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/PersistentKeymapIO.kt diff --git a/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/KeymapSegment.kt b/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/KeymapSegment.kt new file mode 100644 index 000000000..475267296 --- /dev/null +++ b/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/KeymapSegment.kt @@ -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 . + */ + +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) + } + } +} diff --git a/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/PersistentKeymapIO.kt b/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/PersistentKeymapIO.kt new file mode 100644 index 000000000..a70c04389 --- /dev/null +++ b/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/PersistentKeymapIO.kt @@ -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 . + */ + +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(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 { + 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() + } + } +} \ No newline at end of file diff --git a/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/PersistentLevelHashIO.kt b/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/PersistentLevelHashIO.kt index 2d11ed715..4871208b6 100644 --- a/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/PersistentLevelHashIO.kt +++ b/utilities/level-hash/src/main/java/com/itsaky/androidide/levelhash/internal/PersistentLevelHashIO.kt @@ -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( // 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( 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(KEYMAP_MAX_SEGMENTS) - private var valuesFirstEntryAt = 0L private var valuesLastEntryAt = 0L private var valuesWriteAt = 0L @@ -200,35 +170,6 @@ class PersistentLevelHashIO( 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 { - 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( 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( 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( return null } - FileUtils.deallocate(raKeymapFile, positionInSegment, SIZE_LONG) + keymapIo.deallocate(segment, positionInSegment, SIZE_LONG) valuesIo.position(valueAddr - 1) @@ -474,10 +415,10 @@ class PersistentLevelHashIO( } 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( } 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( 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( * 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( * 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()) - } - } - } } \ No newline at end of file