Remove dependencies, modularize, add bytebuffer implementation
This commit is contained in:
parent
4a41699551
commit
fbaf2586a2
10
pom.xml
10
pom.xml
@ -40,16 +40,6 @@
|
|||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>com.squareup.tape2</groupId>
|
|
||||||
<artifactId>tape</artifactId>
|
|
||||||
<version>2.0.0-beta1</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.lmdbjava</groupId>
|
|
||||||
<artifactId>lmdbjava</artifactId>
|
|
||||||
<version>0.8.3</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
package it.cavallium.filequeue;
|
|
||||||
|
|
||||||
import com.squareup.tape2.QueueFile;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public final class DiskQueueToConsumer<T> implements IQueueToConsumer<T> {
|
|
||||||
|
|
||||||
private final QueueToConsumer<T> queue;
|
|
||||||
private final QueueFile queueFile;
|
|
||||||
|
|
||||||
public DiskQueueToConsumer(Path file,
|
|
||||||
boolean clear,
|
|
||||||
Serializer<T> serializer,
|
|
||||||
Deserializer<T> deserializer,
|
|
||||||
QueueConsumer<T> consumer) throws IOException {
|
|
||||||
QueueFile queueFile;
|
|
||||||
try {
|
|
||||||
queueFile = new QueueFile.Builder(file.toFile()).zero(false).build();
|
|
||||||
if (clear) {
|
|
||||||
queueFile.clear();
|
|
||||||
}
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(file);
|
|
||||||
} catch (Throwable ex2) {
|
|
||||||
|
|
||||||
}
|
|
||||||
queueFile = new QueueFile.Builder(file.toFile()).zero(false).build();
|
|
||||||
}
|
|
||||||
this.queueFile = queueFile;
|
|
||||||
this.queue = new QueueToConsumer<>(new SimpleQueueFile<>(queueFile, serializer, deserializer), consumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(T value) {
|
|
||||||
queue.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
queue.close();
|
|
||||||
try {
|
|
||||||
queueFile.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startQueue() {
|
|
||||||
queue.startQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void runStep(boolean park) {
|
|
||||||
queue.runStep(park);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package it.cavallium.filequeue;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.lmdbjava.ByteArrayProxy;
|
|
||||||
import org.lmdbjava.Env;
|
|
||||||
import org.lmdbjava.EnvFlags;
|
|
||||||
|
|
||||||
public class LMDBEnvManager {
|
|
||||||
private static final Map<Path, Env<byte[]>> ENVS = new HashMap<>();
|
|
||||||
|
|
||||||
public synchronized static Env<byte[]> ofPath(Path path) {
|
|
||||||
return ENVS.compute(path, (p, env) -> {
|
|
||||||
if (env != null) {
|
|
||||||
if (env.isClosed()) {
|
|
||||||
env = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (env != null) {
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
if (Files.notExists(path)) {
|
|
||||||
try {
|
|
||||||
Files.createDirectories(path);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return open(path.toFile());
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
try {
|
|
||||||
Files.walk(path)
|
|
||||||
.sorted(Comparator.reverseOrder())
|
|
||||||
.map(Path::toFile)
|
|
||||||
.forEach(File::delete);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return open(path.toFile());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Env<byte[]> open(File path) {
|
|
||||||
return Env.create(ByteArrayProxy.PROXY_BA)
|
|
||||||
// 128GiB
|
|
||||||
.setMapSize(Integer.parseInt(System.getProperty("fq.writemap.size.gb", "32")) * 1024L * 1024 * 1024)
|
|
||||||
.setMaxReaders(1024)
|
|
||||||
.setMaxDbs(1024)
|
|
||||||
.open(path, EnvFlags.MDB_NOSYNC, EnvFlags.MDB_WRITEMAP, EnvFlags.MDB_NOMETASYNC);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package it.cavallium.filequeue;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import org.lmdbjava.Env;
|
|
||||||
|
|
||||||
public final class LMDBQueueToConsumer<T> implements IQueueToConsumer<T> {
|
|
||||||
|
|
||||||
private final Env<byte[]> env;
|
|
||||||
private final SimpleQueueLMDB<T> queueLMDB;
|
|
||||||
private final QueueToConsumer<T> queue;
|
|
||||||
|
|
||||||
public LMDBQueueToConsumer(Path file,
|
|
||||||
String dbName,
|
|
||||||
boolean clear,
|
|
||||||
Serializer<T> serializer,
|
|
||||||
Deserializer<T> deserializer,
|
|
||||||
QueueConsumer<T> consumer) {
|
|
||||||
this.env = LMDBEnvManager.ofPath(file);
|
|
||||||
this.queueLMDB = new SimpleQueueLMDB<>(env, dbName, serializer, deserializer);
|
|
||||||
this.queue = new QueueToConsumer<>(queueLMDB, consumer) {
|
|
||||||
@Override
|
|
||||||
protected String getQueueManagerName() {
|
|
||||||
return super.getQueueManagerName() + "-" + dbName;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(T value) {
|
|
||||||
queue.add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
queue.close();
|
|
||||||
queueLMDB.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startQueue() {
|
|
||||||
queue.startQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void runStep(boolean park) {
|
|
||||||
queue.runStep(park);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,63 @@
|
|||||||
|
package it.cavallium.filequeue;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public final class QueueToConsumerFileChannel<T> implements IQueueToConsumer<T> {
|
||||||
|
|
||||||
|
private final SimpleQueueMemorySegment<T> queueMemorySegment;
|
||||||
|
private final QueueToConsumer<T> queue;
|
||||||
|
private final Path dir;
|
||||||
|
|
||||||
|
public QueueToConsumerFileChannel(Path file,
|
||||||
|
String dbName,
|
||||||
|
Serializer<T> serializer,
|
||||||
|
Deserializer<T> deserializer,
|
||||||
|
QueueConsumer<T> consumer,
|
||||||
|
long segmentSize) {
|
||||||
|
try {
|
||||||
|
this.dir = file.resolve(dbName);
|
||||||
|
this.queueMemorySegment = new SimpleQueueMemorySegment<>(dir,
|
||||||
|
dbName,
|
||||||
|
serializer,
|
||||||
|
deserializer,
|
||||||
|
segmentSize
|
||||||
|
);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
this.queue = new QueueToConsumer<>(queueMemorySegment, consumer) {
|
||||||
|
@Override
|
||||||
|
protected String getQueueManagerName() {
|
||||||
|
return super.getQueueManagerName() + "-" + dbName;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(T value) {
|
||||||
|
queue.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
queue.close();
|
||||||
|
queueMemorySegment.close();
|
||||||
|
try {
|
||||||
|
FileUtils.deleteDirectory(dir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startQueue() {
|
||||||
|
queue.startQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void runStep(boolean park) {
|
||||||
|
queue.runStep(park);
|
||||||
|
}
|
||||||
|
}
|
@ -4,18 +4,18 @@ import java.io.IOException;
|
|||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public final class MemorySegmentQueueToConsumer<T> implements IQueueToConsumer<T> {
|
public final class QueueToConsumerMemorySegment<T> implements IQueueToConsumer<T> {
|
||||||
|
|
||||||
private final SimpleQueueMemorySegment<T> queueMemorySegment;
|
private final SimpleQueueMemorySegment<T> queueMemorySegment;
|
||||||
private final QueueToConsumer<T> queue;
|
private final QueueToConsumer<T> queue;
|
||||||
private final Path dir;
|
private final Path dir;
|
||||||
|
|
||||||
public MemorySegmentQueueToConsumer(Path file,
|
public QueueToConsumerMemorySegment(Path file,
|
||||||
String dbName,
|
String dbName,
|
||||||
Serializer<T> serializer,
|
Serializer<T> serializer,
|
||||||
Deserializer<T> deserializer,
|
Deserializer<T> deserializer,
|
||||||
QueueConsumer<T> consumer,
|
QueueConsumer<T> consumer,
|
||||||
long segmentSize) {
|
long segmentSize) {
|
||||||
try {
|
try {
|
||||||
this.dir = file.resolve(dbName);
|
this.dir = file.resolve(dbName);
|
||||||
this.queueMemorySegment = new SimpleQueueMemorySegment<>(dir,
|
this.queueMemorySegment = new SimpleQueueMemorySegment<>(dir,
|
@ -1,47 +0,0 @@
|
|||||||
package it.cavallium.filequeue;
|
|
||||||
|
|
||||||
import com.squareup.tape2.QueueFile;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
class SimpleQueueFile<T> implements SimpleQueue<T> {
|
|
||||||
|
|
||||||
private final QueueFile queueFile;
|
|
||||||
private final Serializer<T> ser;
|
|
||||||
private final Deserializer<T> des;
|
|
||||||
|
|
||||||
public SimpleQueueFile(QueueFile queueFile, Serializer<T> serializer, Deserializer<T> deserializer) {
|
|
||||||
this.queueFile = queueFile;
|
|
||||||
this.ser = serializer;
|
|
||||||
this.des = deserializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void add(T element) {
|
|
||||||
try {
|
|
||||||
queueFile.add(ser.serialize(element));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized T remove() {
|
|
||||||
try {
|
|
||||||
byte[] element = queueFile.peek();
|
|
||||||
if (element == null) {
|
|
||||||
throw new NoSuchElementException("Queue is empty");
|
|
||||||
}
|
|
||||||
var deserialized = des.deserialize(element);
|
|
||||||
queueFile.remove();
|
|
||||||
return deserialized;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized int size() {
|
|
||||||
return queueFile.size();
|
|
||||||
}
|
|
||||||
}
|
|
164
src/main/java/it/cavallium/filequeue/SimpleQueueFileChannel.java
Normal file
164
src/main/java/it/cavallium/filequeue/SimpleQueueFileChannel.java
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package it.cavallium.filequeue;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread safe queue
|
||||||
|
*/
|
||||||
|
public class SimpleQueueFileChannel<T> implements SimpleQueue<T>, Closeable {
|
||||||
|
|
||||||
|
private static final Set<String> REGISTRY = new HashSet<>();
|
||||||
|
private final ArrayDeque<SimpleQueueFileChannelFixedSize> queueSegments = new ArrayDeque<>();
|
||||||
|
private final Path directory;
|
||||||
|
private final String name;
|
||||||
|
private final Serializer<T> serializer;
|
||||||
|
private final Deserializer<T> deserializer;
|
||||||
|
private final long segmentSize;
|
||||||
|
|
||||||
|
public SimpleQueueFileChannel(Path directory,
|
||||||
|
String name,
|
||||||
|
Serializer<T> serializer,
|
||||||
|
Deserializer<T> deserializer,
|
||||||
|
long segmentSize) throws IOException {
|
||||||
|
this.name = name;
|
||||||
|
this.directory = directory;
|
||||||
|
this.serializer = serializer;
|
||||||
|
this.deserializer = deserializer;
|
||||||
|
this.segmentSize = segmentSize;
|
||||||
|
synchronized (REGISTRY) {
|
||||||
|
if (!REGISTRY.add(name)) {
|
||||||
|
throw new IllegalArgumentException("Database already registered: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Files.notExists(directory)) {
|
||||||
|
Files.createDirectories(directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path generateQueuePath() {
|
||||||
|
return directory.resolve("queue_%s_%d.queue.bin".formatted(name, System.nanoTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(T element) {
|
||||||
|
byte[] serialized;
|
||||||
|
try {
|
||||||
|
serialized = serializer.serialize(element);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
var memorySegment = ByteBuffer.wrap(serialized);
|
||||||
|
try {
|
||||||
|
if (!getQueueSegmentForWrite().offer(memorySegment)) {
|
||||||
|
expandQueueSegmentForWrite().add(memorySegment);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new UncheckedIOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SimpleQueueFileChannelFixedSize expandQueueSegmentForWrite() throws IOException {
|
||||||
|
synchronized (queueSegments) {
|
||||||
|
var queueMemorySegment = new SimpleQueueFileChannelFixedSize(segmentSize, generateQueuePath());
|
||||||
|
// Hibernate the middle segments, if there will be 3 or more segments after the expansion
|
||||||
|
if (queueSegments.size() >= 2) {
|
||||||
|
queueSegments.getLast().hibernate();
|
||||||
|
}
|
||||||
|
queueSegments.add(queueMemorySegment);
|
||||||
|
return queueMemorySegment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SimpleQueueFileChannelFixedSize getQueueSegmentForWrite() throws IOException {
|
||||||
|
synchronized (queueSegments) {
|
||||||
|
var first = queueSegments.peekLast();
|
||||||
|
if (first == null) {
|
||||||
|
first = expandQueueSegmentForWrite();
|
||||||
|
}
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SimpleQueueFileChannelFixedSize getQueueSegmentForRead() throws IOException {
|
||||||
|
synchronized (queueSegments) {
|
||||||
|
var last = queueSegments.peekFirst();
|
||||||
|
if (last == null) {
|
||||||
|
last = expandQueueSegmentForWrite();
|
||||||
|
}
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean popQueueSegmentForRead() throws IOException {
|
||||||
|
synchronized (queueSegments) {
|
||||||
|
if (queueSegments.size() > 1) {
|
||||||
|
var polled = queueSegments.poll();
|
||||||
|
if (polled != null) {
|
||||||
|
polled.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T remove() {
|
||||||
|
ByteBuffer removed;
|
||||||
|
try {
|
||||||
|
removed = getQueueSegmentForRead().poll();
|
||||||
|
if (removed == null) {
|
||||||
|
if (popQueueSegmentForRead()) {
|
||||||
|
removed = getQueueSegmentForRead().poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removed == null) throw new NoSuchElementException();
|
||||||
|
byte[] data = new byte[removed.remaining()];
|
||||||
|
removed.get(data);
|
||||||
|
return readObject(data);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new UncheckedIOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private T readObject(byte[] byteBuffer) {
|
||||||
|
try {
|
||||||
|
return deserializer.deserialize(byteBuffer);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return this.queueSegments.stream().mapToInt(SimpleQueueFileChannelFixedSize::size).sum();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
this.queueSegments.forEach(qs -> {
|
||||||
|
try {
|
||||||
|
qs.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
synchronized (REGISTRY) {
|
||||||
|
REGISTRY.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package it.cavallium.filequeue;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.MappedByteBuffer;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.FileChannel.MapMode;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class SimpleQueueFileChannelFixedSize implements Closeable {
|
||||||
|
|
||||||
|
private final Path filePath;
|
||||||
|
private final FileChannel ch;
|
||||||
|
private final MappedByteBuffer writer;
|
||||||
|
private final long fixedSize;
|
||||||
|
private int readPosition = 0;
|
||||||
|
private int writePosition = 0;
|
||||||
|
private final AtomicInteger size = new AtomicInteger();
|
||||||
|
|
||||||
|
public SimpleQueueFileChannelFixedSize(long fixedSize, Path filePath) throws IOException {
|
||||||
|
this.filePath = filePath;
|
||||||
|
this.fixedSize = fixedSize;
|
||||||
|
Files.deleteIfExists(filePath);
|
||||||
|
this.ch = FileChannel.open(filePath, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.READ);
|
||||||
|
ch.truncate(fixedSize);
|
||||||
|
this.writer = ch.map(MapMode.READ_WRITE, 0L, fixedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
ch.close();
|
||||||
|
Files.deleteIfExists(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(ByteBuffer element) {
|
||||||
|
if (!offer(element)) {
|
||||||
|
throw new IllegalStateException("Can't add the element, not enough space");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if there is enough space, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean offer(ByteBuffer element) {
|
||||||
|
int size = Math.toIntExact(element.remaining());
|
||||||
|
if (writePosition + size + Integer.BYTES > fixedSize) {
|
||||||
|
if (writePosition == 0) throw new UnsupportedOperationException("Entity too large! " + size + " bytes");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
writer.slice(writePosition, Integer.BYTES).putInt(size);
|
||||||
|
writer.put(writePosition + Integer.BYTES, element, 0, size);
|
||||||
|
writePosition += size + Integer.BYTES;
|
||||||
|
this.size.incrementAndGet();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer poll() {
|
||||||
|
if (readPosition >= fixedSize) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (size.decrementAndGet() < 0) {
|
||||||
|
size.incrementAndGet();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int size = writer.slice(readPosition, Integer.BYTES).getInt();
|
||||||
|
var segment = ByteBuffer.allocate(size);
|
||||||
|
segment.put(0, writer, readPosition + Integer.BYTES, size);
|
||||||
|
readPosition += size + Integer.BYTES;
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer remove() {
|
||||||
|
var polled = poll();
|
||||||
|
if (polled == null) throw new NoSuchElementException();
|
||||||
|
return polled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return Math.max(0, size.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hibernate() {
|
||||||
|
try {
|
||||||
|
this.ch.force(false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,108 +0,0 @@
|
|||||||
package it.cavallium.filequeue;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.lmdbjava.Dbi;
|
|
||||||
import org.lmdbjava.DbiFlags;
|
|
||||||
import org.lmdbjava.Env;
|
|
||||||
import org.lmdbjava.Txn;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread safe queue
|
|
||||||
*/
|
|
||||||
public class SimpleQueueLMDB<T> implements SimpleQueue<T>, Closeable {
|
|
||||||
|
|
||||||
private static final Set<String> REGISTRY = new HashSet<>();
|
|
||||||
private final Env<byte[]> env;
|
|
||||||
private final String name;
|
|
||||||
private final Dbi<byte[]> dbi;
|
|
||||||
private final Serializer<T> serializer;
|
|
||||||
private final Deserializer<T> deserializer;
|
|
||||||
/**
|
|
||||||
* The First index.
|
|
||||||
*/
|
|
||||||
private int firstIndex;
|
|
||||||
/**
|
|
||||||
* The Last index.
|
|
||||||
*/
|
|
||||||
private int lastIndex;
|
|
||||||
|
|
||||||
public SimpleQueueLMDB(Env<byte[]> env,
|
|
||||||
String name,
|
|
||||||
Serializer<T> serializer,
|
|
||||||
Deserializer<T> deserializer) {
|
|
||||||
this.env = env;
|
|
||||||
this.name = name;
|
|
||||||
this.serializer = serializer;
|
|
||||||
this.deserializer = deserializer;
|
|
||||||
synchronized (REGISTRY) {
|
|
||||||
if (!REGISTRY.add(name)) {
|
|
||||||
throw new IllegalArgumentException("Database already registered: " + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.dbi = env.openDbi(name, DbiFlags.MDB_CREATE, DbiFlags.MDB_INTEGERKEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(T element) {
|
|
||||||
int last = lastIndex++;
|
|
||||||
try {
|
|
||||||
dbi.put(serializeKey(last), serializer.serialize(element));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T remove() {
|
|
||||||
byte[] byteBuffer;
|
|
||||||
try (Txn<byte[]> txnWrite = env.txnWrite()) {
|
|
||||||
var bb = serializeKey(firstIndex);
|
|
||||||
byteBuffer = dbi.get(txnWrite, bb);
|
|
||||||
if (byteBuffer == null) {
|
|
||||||
txnWrite.commit();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
boolean isDel = dbi.delete(txnWrite, bb);
|
|
||||||
firstIndex++;
|
|
||||||
txnWrite.commit();
|
|
||||||
}
|
|
||||||
return readObject(byteBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private T readObject(byte[] byteBuffer) {
|
|
||||||
try {
|
|
||||||
return deserializer.deserialize(byteBuffer);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] serializeKey(int value) {
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
|
|
||||||
buffer.putInt(value);
|
|
||||||
return buffer.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
try {
|
|
||||||
dbi.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} finally {
|
|
||||||
synchronized (REGISTRY) {
|
|
||||||
REGISTRY.remove(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
2
src/main/java/module-info.java
Normal file
2
src/main/java/module-info.java
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
module filequeue {
|
||||||
|
}
|
@ -13,7 +13,7 @@ public abstract class TestQueueToConsumer {
|
|||||||
public abstract SimpleQueue<String> getSimpleQueue();
|
public abstract SimpleQueue<String> getSimpleQueue();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() throws Exception {
|
||||||
var q = getSimpleQueue();
|
var q = getSimpleQueue();
|
||||||
AtomicBoolean ab = new AtomicBoolean();
|
AtomicBoolean ab = new AtomicBoolean();
|
||||||
try (var qtc = new QueueToConsumer<>(q, value -> {
|
try (var qtc = new QueueToConsumer<>(q, value -> {
|
||||||
@ -35,10 +35,13 @@ public abstract class TestQueueToConsumer {
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
if (q instanceof AutoCloseable autoCloseable) {
|
||||||
|
autoCloseable.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testConcurrency() {
|
public void testConcurrency() throws Exception {
|
||||||
var q = getSimpleQueue();
|
var q = getSimpleQueue();
|
||||||
AtomicBoolean ab = new AtomicBoolean();
|
AtomicBoolean ab = new AtomicBoolean();
|
||||||
CountDownLatch cdl = new CountDownLatch(1);
|
CountDownLatch cdl = new CountDownLatch(1);
|
||||||
@ -77,5 +80,8 @@ public abstract class TestQueueToConsumer {
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
if (q instanceof AutoCloseable autoCloseable) {
|
||||||
|
autoCloseable.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package it.cavallium.filequeue;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public class TestQueueToConsumerFileChannel extends TestQueueToConsumer {
|
||||||
|
|
||||||
|
private static final long SEGMENT_SIZE = 24;
|
||||||
|
private Path dir;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() throws IOException {
|
||||||
|
dir = Files.createTempDirectory("queue");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() throws IOException {
|
||||||
|
Path pathToBeDeleted = dir;
|
||||||
|
try (var fs = Files.walk(pathToBeDeleted)) {
|
||||||
|
fs.sorted(Comparator.reverseOrder())
|
||||||
|
.forEach(f -> {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(f);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleQueue<String> getSimpleQueue() {
|
||||||
|
try {
|
||||||
|
return new SimpleQueueMemorySegment<>(dir, "test", new Serializer<>() {
|
||||||
|
@Override
|
||||||
|
public byte[] serialize(String data) {
|
||||||
|
return data.getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}, new Deserializer<>() {
|
||||||
|
@Override
|
||||||
|
public String deserialize(byte[] data) {
|
||||||
|
return new String(data, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}, SEGMENT_SIZE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user