Last active
November 20, 2023 04:04
-
-
Save ocadaruma/fc26fc122829c63cb61e14d7fc96896d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ 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