@@ -20,10 +20,8 @@ package com.itsaky.androidide.levelhash.internal
20
20
import com.itsaky.androidide.levelhash.DataExternalizer
21
21
import com.itsaky.androidide.levelhash.LevelHash.ResizeFailure
22
22
import com.itsaky.androidide.levelhash.seekInt
23
- import com.itsaky.androidide.levelhash.seekShort
24
23
import com.itsaky.androidide.levelhash.util.DataExternalizers.SIZE_INT
25
24
import com.itsaky.androidide.levelhash.util.DataExternalizers.SIZE_LONG
26
- import com.itsaky.androidide.levelhash.util.DataExternalizers.SIZE_SHORT
27
25
import com.itsaky.androidide.levelhash.util.FileUtils
28
26
import com.itsaky.androidide.levelhash.util.MappedRandomAccessIO
29
27
import org.slf4j.LoggerFactory
@@ -98,7 +96,7 @@ private val logger = LoggerFactory.getLogger(PersistentLevelHashIO::class.java)
98
96
* value values[];
99
97
*
100
98
* value {
101
- * u16 entry_size;
99
+ * u32 entry_size;
102
100
* u32 key_size;
103
101
* u8 key[key_size];
104
102
* u32 value_size;
@@ -165,10 +163,11 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
165
163
// even multiple threads, the current position cannot be determined
166
164
167
165
internal val metaIo =
168
- PersistentMetaIO (File (indexFile.parentFile, " ${indexFile.name} ._meta" ), levelSize, bucketSize)
166
+ PersistentMetaIO (File (indexFile.parentFile, " ${indexFile.name} ._meta" ),
167
+ levelSize, bucketSize)
169
168
private val keymapFile = File (indexFile.parentFile, " ${indexFile.name} ._i" )
170
169
private val raKeymapFile by lazy { RandomAccessFile (keymapFile, " rw" ) }
171
- private val raIndexFile by lazy { RandomAccessFile (indexFile, " rw" ) }
170
+ private val raValuesFile by lazy { RandomAccessFile (indexFile, " rw" ) }
172
171
173
172
private val keymapIo = MappedRandomAccessIO ()
174
173
private var valIo = MappedRandomAccessIO ()
@@ -192,26 +191,54 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
192
191
magicNumber = VALUES_MAGIC_NUMBER )
193
192
194
193
// the header region is not memory mapped
195
- var buffer = raIndexFile.channel.map(FileChannel .MapMode .READ_WRITE ,
196
- VALUES_HEADER_SIZE_BYTES , metaIo.valuesFileSize)
197
- valIo.reset(buffer, 0L , metaIo.valuesFileSize)
198
-
199
- val keymapSize = metaIo.keymapSize
200
- buffer = raKeymapFile.channel.map(FileChannel .MapMode .READ_WRITE ,
201
- KEYMAP_HEADER_SIZE_BYTES , keymapSize)
202
- keymapIo.reset(buffer, 0L , keymapSize)
194
+ valuesRemap(size = metaIo.valuesFileSize)
195
+ keymapResize(newSize = metaIo.keymapSize)
203
196
}
204
197
205
198
private fun realValueOffset (offset : Long ) = VALUES_HEADER_SIZE_BYTES + offset
206
199
207
200
private fun realKeymapOffset (offset : Long ) = KEYMAP_HEADER_SIZE_BYTES + offset
208
201
209
202
private fun valuesDeallocate (offset : Long , len : Long ) =
210
- FileUtils .deallocate(raIndexFile , realValueOffset(offset), len)
203
+ FileUtils .deallocate(raValuesFile , realValueOffset(offset), len)
211
204
212
205
private fun keymapDeallocate (offset : Long , len : Long ) =
213
206
FileUtils .deallocate(raKeymapFile, realKeymapOffset(offset), len)
214
207
208
+ /* *
209
+ * Resize the values file so that its size becomes [newSize] in bytes. This is
210
+ * risky and should be used carefully. Resizing the file incorrectly may lead
211
+ * to data loss. The [valIo] is reset to position 0 after this operation.
212
+ *
213
+ * @param newSize The new size of the values file, in bytes.
214
+ */
215
+ private fun valuesResize (newSize : Long ) {
216
+ metaIo.valuesFileSize = newSize
217
+ raValuesFile.setLength(newSize)
218
+ valuesRemap(newSize)
219
+ }
220
+
221
+ private fun keymapResize (newSize : Long ) {
222
+ if (raKeymapFile.length() != newSize) {
223
+ raKeymapFile.setLength(newSize)
224
+ }
225
+ keymapRemap(newSize)
226
+ }
227
+
228
+ private fun valuesRemap (size : Long , offset : Long = VALUES_HEADER_SIZE_BYTES ) =
229
+ fileRemap(raValuesFile, valIo, size, offset)
230
+
231
+ private fun keymapRemap (size : Long , offset : Long = KEYMAP_HEADER_SIZE_BYTES ) =
232
+ fileRemap(raKeymapFile, keymapIo, size, offset)
233
+
234
+ private fun fileRemap (file : RandomAccessFile , io : MappedRandomAccessIO ,
235
+ size : Long , offset : Long = 0L
236
+ ) {
237
+ val buffer = file.channel.map(FileChannel .MapMode .READ_WRITE , offset, size)
238
+ io.close()
239
+ io.reset(buffer, 0L , size)
240
+ }
241
+
215
242
private fun slotAddress (
216
243
levelIdx : Int ,
217
244
bucketIdx : Int ,
@@ -223,8 +250,9 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
223
250
else -> throw IllegalArgumentException (" Invalid level index: $levelIdx " )
224
251
}
225
252
226
- return levelPos + (KEYMAP_ENTRY_SIZE_BYTES * metaIo.bucketSize * bucketIdx) +
227
- (KEYMAP_ENTRY_SIZE_BYTES * slotIdx)
253
+ return levelPos + // start position of level
254
+ (KEYMAP_ENTRY_SIZE_BYTES * metaIo.bucketSize * bucketIdx) + // bucket position
255
+ (KEYMAP_ENTRY_SIZE_BYTES * slotIdx) // slot position in bucket
228
256
}
229
257
230
258
/* *
@@ -269,7 +297,9 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
269
297
slotIdx : Int ,
270
298
): Boolean {
271
299
// 0 == no value entry
272
- return valueAddressForPos(levelNum, bucketIdx, slotIdx) > 0L
300
+ return valueAddressForPos(levelNum, bucketIdx, slotIdx).let { valAddr ->
301
+ valAddr > 0L && valIo.tryPosition(valAddr - 1 ) && valIo.readInt() > 0
302
+ }
273
303
}
274
304
275
305
/* *
@@ -286,7 +316,7 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
286
316
return null
287
317
}
288
318
289
- if (valIo.readUnsignedShort () <= 0L ) {
319
+ if (valIo.readInt () <= 0L ) {
290
320
// entry size is 0, so the slot is empty
291
321
return null
292
322
}
@@ -361,14 +391,15 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
361
391
return value
362
392
}
363
393
394
+
395
+
364
396
fun writeEntry (
365
397
levelNum : Int ,
366
398
bucketIdx : Int ,
367
399
slotIdx : Int ,
368
400
key : K ? ,
369
401
value : V ? ,
370
402
) {
371
- // TODO: expand values file in case of buffer overflow
372
403
val slotAddr = slotAddress(levelNum, bucketIdx, slotIdx)
373
404
374
405
if (key == null ) {
@@ -382,17 +413,23 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
382
413
val (isUpdate, existingEntrySize) = run {
383
414
if (existingValueAddr > 0L ) {
384
415
valIo.position(existingValueAddr - 1 )
385
- true to valIo.readUnsignedShort ().toLong()
416
+ true to valIo.readInt ().toLong()
386
417
} else {
387
418
false to 0L
388
419
}
389
420
}
390
421
391
- val tokenAddr = metaIo.valuesNextEntry
392
- valIo.position(tokenAddr)
422
+ val thisValueAddr = metaIo.valuesNextEntry
423
+ if (thisValueAddr + 4 * KB_1 > metaIo.valuesFileSize) {
424
+ valuesResize(thisValueAddr + VALUES_SEGMENT_SIZE_BYTES )
425
+ }
393
426
394
- // leave space for entry size
395
- valIo.seekShort()
427
+ // valuesResize will reset the position to 0
428
+ valIo.position(thisValueAddr)
429
+
430
+ if (valIo.readInt() > 0 ) {
431
+ return
432
+ }
396
433
397
434
val keyValPos = valIo.position()
398
435
@@ -402,25 +439,25 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
402
439
403
440
val finalAddr = valIo.position()
404
441
val entrySize = finalAddr - keyValPos
405
- check(entrySize <= Short .MAX_VALUE ) {
442
+ check(entrySize <= Int .MAX_VALUE ) {
406
443
" Entry size is too large: $entrySize "
407
444
}
408
445
409
446
// then set the token
410
- valIo.position(tokenAddr )
411
- valIo.writeShort(( entrySize and 0xFFFF ) .toInt())
447
+ valIo.position(thisValueAddr )
448
+ valIo.writeInt( entrySize.toInt())
412
449
413
450
// reset to the final position
414
451
valIo.position(finalAddr)
415
452
416
453
// then update the address in the keymap
417
454
keymapIo.position(slotAddr)
418
- keymapIo.writeLong(tokenAddr + 1 )
455
+ keymapIo.writeLong(thisValueAddr + 1 )
419
456
420
457
metaIo.valuesNextEntry = finalAddr
421
458
422
459
if (isUpdate) {
423
- var size: Long = SIZE_SHORT
460
+ var size = SIZE_INT
424
461
if (existingEntrySize > 0L ) {
425
462
size + = existingEntrySize
426
463
}
@@ -437,18 +474,18 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
437
474
keymapIo.position(slotAddr)
438
475
val valueAddr = keymapIo.readLong()
439
476
440
- if (valueAddr == 0L ) {
441
- return null
442
- }
443
-
444
477
// reading this region again will return 0, which is considered
445
478
// a null pointer
446
479
keymapDeallocate(slotAddr, SIZE_LONG )
447
480
481
+ if (valueAddr == 0L ) {
482
+ return null
483
+ }
484
+
448
485
valIo.position(valueAddr - 1 )
449
- valIo.seekShort () // seek over entrySize
486
+ valIo.seekInt () // seek over entrySize
450
487
451
- var entrySize = SIZE_SHORT
488
+ var entrySize = SIZE_INT
452
489
453
490
val keySize = valIo.readInt()
454
491
entrySize + = SIZE_INT
@@ -514,7 +551,41 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
514
551
}
515
552
516
553
override fun close () {
517
- closeAndLogErrs(metaIo, keymapIo, valIo, raKeymapFile, raIndexFile)
554
+ closeAndLogErrs(metaIo, keymapIo, valIo, raKeymapFile, raValuesFile)
555
+ }
556
+
557
+ internal fun dmpSlotAddrs (
558
+ levelNum : Int ,
559
+ bucketIdx : Int ,
560
+ slotIdx : Int ,
561
+ ) {
562
+ val sb = StringBuilder ()
563
+ sb.append(" level: " )
564
+ sb.appendLine(levelNum)
565
+ sb.append(" bucket: " )
566
+ sb.appendLine(bucketIdx)
567
+ sb.append(" slot: " )
568
+ sb.appendLine(slotIdx)
569
+
570
+ val slotAddr = slotAddress(levelNum, bucketIdx, slotIdx)
571
+ sb.append(" keymapAddr: " )
572
+ appendAddress(sb, slotAddr)
573
+
574
+ keymapIo.position(slotAddr)
575
+
576
+ val valueAddr = keymapIo.readLong()
577
+ sb.append(" valueAddr: " )
578
+ appendAddress(sb, valueAddr)
579
+
580
+ println (sb.toString())
581
+ }
582
+
583
+ @OptIn(ExperimentalStdlibApi ::class )
584
+ private fun appendAddress (sb : StringBuilder , value : Long ) {
585
+ sb.append(value)
586
+ sb.append(" [" )
587
+ sb.append(value.toHexString(HexFormat .UpperCase ))
588
+ sb.appendLine(" ]" )
518
589
}
519
590
520
591
companion object {
@@ -531,6 +602,11 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
531
602
532
603
private val log = LoggerFactory .getLogger(PersistentLevelHashIO ::class .java)
533
604
605
+ /* *
606
+ * 1 Kilobyte.
607
+ */
608
+ internal const val KB_1 = 1024
609
+
534
610
/* *
535
611
* The number of bytes it takes to store the magic number of the keymap/values
536
612
* file.
@@ -548,10 +624,9 @@ internal class PersistentLevelHashIO<K : Any, V : Any?>(
548
624
internal const val VALUES_HEADER_SIZE_BYTES : Long = MAGIC_NUMBER_SIZE_BYTES
549
625
550
626
/* *
551
- * The maximum size of the region of the values file that can be mapped into
552
- * memory at a given time.
627
+ * The size of one segment region in the values file.
553
628
*/
554
- internal const val VALUES_INITIAL_SIZE_BYTES = 512L * 1024L
629
+ internal const val VALUES_SEGMENT_SIZE_BYTES = 512L * 1024L
555
630
556
631
/* *
557
632
* The number of bytes used to store the header of the keymap file.
0 commit comments