Skip to content

Commit ee2fb37

Browse files
committed
Use MemorySegmentReaderSupplier in ReaderSupplierFactory
1 parent 441dff7 commit ee2fb37

File tree

5 files changed

+77
-6
lines changed

5 files changed

+77
-6
lines changed

jvector-examples/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@
214214
</build>
215215
</profile>
216216
<profile>
217-
<id>jdk21</id>
217+
<id>jdk22</id>
218218
<activation>
219219
<activeByDefault>true</activeByDefault>
220220
</activation>

jvector-examples/src/main/java/io/github/jbellis/jvector/example/util/ReaderSupplierFactory.java

+11
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,26 @@
1919
import io.github.jbellis.jvector.disk.SimpleMappedReaderSupplier;
2020

2121
import java.io.IOException;
22+
import java.lang.reflect.Constructor;
2223
import java.nio.file.Files;
2324
import java.nio.file.Path;
2425
import java.util.logging.Level;
2526
import java.util.logging.Logger;
2627

2728
public class ReaderSupplierFactory {
2829
private static final Logger LOG = Logger.getLogger(ReaderSupplierFactory.class.getName());
30+
private static final String MEMORY_SEGMENT_READER_CLASSNAME = "io.github.jbellis.jvector.disk.MemorySegmentReaderSupplier";
2931

3032
public static ReaderSupplier open(Path path) throws IOException {
33+
try {
34+
var supplierClass = Class.forName(MEMORY_SEGMENT_READER_CLASSNAME);
35+
Constructor<?> ctor = supplierClass.getConstructor(Path.class);
36+
return (ReaderSupplier) ctor.newInstance(path);
37+
} catch (Exception e) {
38+
LOG.log(Level.WARNING, "MemorySegmentReaderSupplier not available, falling back to MMapReaderSupplier. Reason: {0}: {1}",
39+
new Object[]{e.getClass().getName(), e.getMessage()});
40+
}
41+
3142
try {
3243
return new MMapReaderSupplier(path);
3344
} catch (UnsatisfiedLinkError|NoClassDefFoundError e) {

jvector-native/src/main/java/io/github/jbellis/jvector/disk/MemorySegmentReader.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public class MemorySegmentReader implements RandomAccessReader {
4040
private static final OfFloat floatLayout = ValueLayout.JAVA_FLOAT_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN);
4141
private static final OfLong longLayout = ValueLayout.JAVA_LONG_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN);
4242

43-
private final Arena arena;
44-
private final MemorySegment memory;
43+
final Arena arena;
44+
final MemorySegment memory;
4545
private long position = 0;
4646

4747
public MemorySegmentReader(Path path) throws IOException {
@@ -54,7 +54,7 @@ public MemorySegmentReader(Path path) throws IOException {
5454
}
5555
}
5656

57-
private MemorySegmentReader(Arena arena, MemorySegment memory) {
57+
MemorySegmentReader(Arena arena, MemorySegment memory) {
5858
this.arena = arena;
5959
this.memory = memory;
6060
}
@@ -134,6 +134,11 @@ public void close() {
134134
arena.close();
135135
}
136136

137+
/**
138+
* Creates a shallow copy of the MemorySegmentReader.
139+
* Underlying memory-mapped region will be shared between all copies.
140+
* When MemorySegmentReader is closed, all copies will become invalid.
141+
*/
137142
public MemorySegmentReader duplicate() {
138143
return new MemorySegmentReader(arena, memory);
139144
}

jvector-native/src/main/java/io/github/jbellis/jvector/disk/MemorySegmentReaderSupplier.java

+31-2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
package io.github.jbellis.jvector.disk;
1717

1818
import java.io.IOException;
19+
import java.lang.foreign.Arena;
20+
import java.lang.foreign.MemorySegment;
1921
import java.nio.file.Path;
2022

2123
public class MemorySegmentReaderSupplier implements ReaderSupplier {
22-
private final MemorySegmentReader reader;
24+
private final InternalMemorySegmentReader reader;
2325

2426
public MemorySegmentReaderSupplier(Path path) throws IOException {
25-
reader = new MemorySegmentReader(path);
27+
reader = new InternalMemorySegmentReader(path);
2628
}
2729

2830
@Override
@@ -34,4 +36,31 @@ public RandomAccessReader get() {
3436
public void close() {
3537
reader.close();
3638
}
39+
40+
private static class InternalMemorySegmentReader extends MemorySegmentReader {
41+
42+
private final boolean shouldClose;
43+
44+
private InternalMemorySegmentReader(Path path) throws IOException {
45+
super(path);
46+
shouldClose = true;
47+
}
48+
49+
private InternalMemorySegmentReader(Arena arena, MemorySegment memory) {
50+
super(arena, memory);
51+
shouldClose = false;
52+
}
53+
54+
@Override
55+
public void close() {
56+
if (shouldClose) {
57+
super.close();
58+
}
59+
}
60+
61+
@Override
62+
public InternalMemorySegmentReader duplicate() {
63+
return new InternalMemorySegmentReader(arena, memory);
64+
}
65+
}
3766
}

jvector-native/src/test/java/io/github/jbellis/jvector/disk/MemorySegmentReaderTest.java

+26
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,32 @@ public void testReaderClose() throws Exception {
100100
}
101101
}
102102

103+
@Test
104+
public void testSupplierClose() throws Exception {
105+
var s = new MemorySegmentReaderSupplier(tempFile);
106+
var r1 = s.get();
107+
var r2 = s.get();
108+
109+
// Close on supplied readers are nop.
110+
r1.close();
111+
r1.readInt();
112+
r2.close();
113+
r2.readInt();
114+
115+
// Backing memory-map will be closed when supplier is closed.
116+
s.close();
117+
try {
118+
r1.readInt();
119+
fail("Should have thrown an exception");
120+
} catch (IllegalStateException _) {
121+
}
122+
try {
123+
r2.readInt();
124+
fail("Should have thrown an exception");
125+
} catch (IllegalStateException _) {
126+
}
127+
}
128+
103129
private void verifyReader(MemorySegmentReader r) {
104130
r.seek(0);
105131
var bytes = new byte[7];

0 commit comments

Comments
 (0)