Skip to content

Instantly share code, notes, and snippets.

@ocadaruma
Last active November 20, 2023 04:04
Show Gist options
  • Save ocadaruma/fc26fc122829c63cb61e14d7fc96896d to your computer and use it in GitHub Desktop.
Save ocadaruma/fc26fc122829c63cb61e14d7fc96896d to your computer and use it in GitHub Desktop.
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Create two mmap against same file.
// 1st mmap: open with MapMode.READ_WRITE and written by the main thread
// 2nd mmap: open with MapMode.READ_ONLY and read by the reader thread, then we check if writes to 1st mmap are reflected
public class MmapTest {
private static final int SIZE = 1024 * 1024;
public static void main(String[] args) throws Exception {
Path filePath = Paths.get(args[0]);
ExecutorService readExecutor = Executors.newSingleThreadExecutor();
// initialize the file
System.err.println("Initializing the file with size: " + SIZE + " bytes");
try (FileChannel channel = FileChannel.open(
filePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
for (int i = 0; i < (SIZE / 4); i++) {
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(0).flip();
channel.write(buf);
}
}
Random rnd = new Random(0); // use same seed for reproducibility
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
MappedByteBuffer mmap = channel.map(MapMode.READ_WRITE, 0, channel.size());
long attempt = 0;
while (true) {
attempt++;
mmap.position(0);
final int start = rnd.nextInt();
for (int i = 0; i < (SIZE / 4); i++) {
mmap.putInt(start + i);
}
try {
readExecutor.submit(reader(start, channel)).get();
} catch (Exception e) {
System.err.println("Failed on attempt " + attempt);
throw e;
}
}
}
}
private static Runnable reader(int start, FileChannel channel) {
MappedByteBuffer mmap;
try {
mmap = channel.map(MapMode.READ_ONLY, 0, channel.size());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return () -> {
for (int i = 0; i < (SIZE / 4); i++) {
int value = mmap.getInt(i * 4);
if (value != (start + i)) {
throw new RuntimeException("Expected " + (start + i) + " but found " + value);
}
}
};
}
}
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Create two mmap against same file.
// 1st mmap: open with MapMode.PRIVATE and written by the main thread
// 2nd mmap: open with MapMode.READ_ONLY and read by the reader thread, then we check if writes to 1st mmap are reflected
public class MmapTest2 {
private static final int SIZE = 1024 * 1024;
public static void main(String[] args) throws Exception {
Path filePath = Paths.get(args[0]);
ExecutorService readExecutor = Executors.newSingleThreadExecutor();
// initialize the file
System.err.println("Initializing the file with size: " + SIZE + " bytes");
try (FileChannel channel = FileChannel.open(
filePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
for (int i = 0; i < (SIZE / 4); i++) {
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(0).flip();
channel.write(buf);
}
}
Random rnd = new Random(0); // use same seed for reproducibility
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
MappedByteBuffer mmap = channel.map(MapMode.PRIVATE, 0, channel.size());
long attempt = 0;
while (true) {
attempt++;
mmap.position(0);
final int start = rnd.nextInt();
for (int i = 0; i < (SIZE / 4); i++) {
mmap.putInt(start + i);
}
try {
readExecutor.submit(reader(start, channel)).get();
} catch (Exception e) {
System.err.println("Failed on attempt " + attempt);
throw e;
}
}
}
}
private static Runnable reader(int start, FileChannel channel) {
MappedByteBuffer mmap;
try {
mmap = channel.map(MapMode.READ_ONLY, 0, channel.size());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return () -> {
for (int i = 0; i < (SIZE / 4); i++) {
int value = mmap.getInt(i * 4);
if (value != (start + i)) {
throw new RuntimeException("Expected " + (start + i) + " but found " + value);
}
}
};
}
}
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Read/write against same file but using mmap for write and ordinary read() for read
// then check the consistency of the content.
public class MmapTest3 {
private static final int SIZE = 1024 * 1024;
public static void main(String[] args) throws Exception {
Path filePath = Paths.get(args[0]);
ExecutorService readExecutor = Executors.newSingleThreadExecutor();
// initialize the file
System.err.println("Initializing the file with size: " + SIZE + " bytes");
try (FileChannel channel = FileChannel.open(
filePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
for (int i = 0; i < (SIZE / 4); i++) {
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(0).flip();
channel.write(buf);
}
}
Random rnd = new Random(0); // use same seed for reproducibility
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
MappedByteBuffer mmap = channel.map(MapMode.READ_WRITE, 0, channel.size());
long attempt = 0;
while (true) {
attempt++;
mmap.position(0);
final int start = rnd.nextInt();
for (int i = 0; i < (SIZE / 4); i++) {
mmap.putInt(start + i);
}
try {
readExecutor.submit(reader(start, filePath)).get();
} catch (Exception e) {
System.err.println("Failed on attempt " + attempt);
throw e;
}
}
}
}
private static Runnable reader(int start, Path filePath) {
FileChannel channel;
try {
channel = FileChannel.open(filePath, StandardOpenOption.READ);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return () -> {
for (int i = 0; i < (SIZE / 4); i++) {
ByteBuffer buf = ByteBuffer.allocate(4);
try {
channel.read(buf, i * 4);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
int value = buf.flip().getInt();
if (value != (start + i)) {
throw new RuntimeException("Expected " + (start + i) + " but found " + value);
}
}
};
}
}
$ javac MmapTest.java
# This lasts forever as expected.
$ java MmapTest ./test.dat
Initializing the file with size: 1048576 bytes
# This fails immediately as 1st mmap change is not visible to 2nd mmap
$ java MmapTest2 ./test2.dat
Initializing the file with size: 1048576 bytes
Failed on attempt 1
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: Expected -1155484576 but found 0
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at MmapTest2.main(MmapTest2.java:45)
Caused by: java.lang.RuntimeException: Expected -1155484576 but found 0
at MmapTest2.lambda$reader$0(MmapTest2.java:65)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
# This lasts forever as expected.
$ java MmapTest3 ./test3.dat
Initializing the file with size: 1048576 bytes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment