2020-12-07 22:15:18 +01:00
|
|
|
package it.cavallium.dbengine.database.disk;
|
|
|
|
|
2022-04-01 01:30:56 +02:00
|
|
|
import static it.cavallium.dbengine.database.disk.UpdateAtomicResultMode.DELTA;
|
|
|
|
|
2022-03-16 13:47:56 +01:00
|
|
|
import io.netty5.buffer.api.Buffer;
|
|
|
|
import io.netty5.buffer.api.BufferAllocator;
|
|
|
|
import io.netty5.buffer.api.Send;
|
2022-03-20 14:33:27 +01:00
|
|
|
import it.cavallium.dbengine.database.LLDelta;
|
2021-02-11 22:27:43 +01:00
|
|
|
import it.cavallium.dbengine.database.LLSingleton;
|
|
|
|
import it.cavallium.dbengine.database.LLSnapshot;
|
2022-04-15 16:49:01 +02:00
|
|
|
import it.cavallium.dbengine.database.LLUtils;
|
2021-11-12 02:05:44 +01:00
|
|
|
import it.cavallium.dbengine.database.UpdateReturnMode;
|
2020-12-07 22:15:18 +01:00
|
|
|
import java.io.IOException;
|
2021-03-04 22:01:50 +01:00
|
|
|
import java.util.Arrays;
|
2021-11-12 02:05:44 +01:00
|
|
|
import java.util.concurrent.Callable;
|
2020-12-07 22:15:18 +01:00
|
|
|
import java.util.function.Function;
|
2021-11-12 02:05:44 +01:00
|
|
|
import org.jetbrains.annotations.NotNull;
|
2020-12-07 22:15:18 +01:00
|
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import org.rocksdb.ReadOptions;
|
|
|
|
import org.rocksdb.RocksDBException;
|
|
|
|
import org.rocksdb.Snapshot;
|
2021-11-12 02:05:44 +01:00
|
|
|
import org.rocksdb.WriteOptions;
|
2021-01-30 10:52:14 +01:00
|
|
|
import reactor.core.publisher.Mono;
|
2021-02-11 22:27:43 +01:00
|
|
|
import reactor.core.scheduler.Scheduler;
|
2021-09-05 14:23:46 +02:00
|
|
|
import reactor.core.scheduler.Schedulers;
|
2020-12-07 22:15:18 +01:00
|
|
|
|
|
|
|
public class LLLocalSingleton implements LLSingleton {
|
2021-11-12 02:05:44 +01:00
|
|
|
private final RocksDBColumn db;
|
2022-05-20 10:20:00 +02:00
|
|
|
private final Function<LLSnapshot, Snapshot> snapshotResolver;
|
2020-12-07 22:15:18 +01:00
|
|
|
private final byte[] name;
|
2022-03-20 14:33:27 +01:00
|
|
|
private final String columnName;
|
2022-05-20 10:20:00 +02:00
|
|
|
private final Mono<Buffer> nameMono;
|
2020-12-07 22:15:18 +01:00
|
|
|
private final String databaseName;
|
2022-04-05 13:58:12 +02:00
|
|
|
private final Scheduler dbWScheduler;
|
|
|
|
private final Scheduler dbRScheduler;
|
2020-12-07 22:15:18 +01:00
|
|
|
|
2021-11-12 02:05:44 +01:00
|
|
|
public LLLocalSingleton(RocksDBColumn db,
|
2022-05-20 10:20:00 +02:00
|
|
|
Function<LLSnapshot, Snapshot> snapshotResolver,
|
2020-12-07 22:15:18 +01:00
|
|
|
String databaseName,
|
|
|
|
byte[] name,
|
2022-03-20 14:33:27 +01:00
|
|
|
String columnName,
|
2022-04-05 13:58:12 +02:00
|
|
|
Scheduler dbWScheduler,
|
|
|
|
Scheduler dbRScheduler,
|
2022-03-20 14:33:27 +01:00
|
|
|
byte @Nullable [] defaultValue) throws RocksDBException {
|
2020-12-07 22:15:18 +01:00
|
|
|
this.db = db;
|
|
|
|
this.databaseName = databaseName;
|
|
|
|
this.snapshotResolver = snapshotResolver;
|
|
|
|
this.name = name;
|
2022-03-20 14:33:27 +01:00
|
|
|
this.columnName = columnName;
|
2021-11-12 02:05:44 +01:00
|
|
|
this.nameMono = Mono.fromCallable(() -> {
|
|
|
|
var alloc = db.getAllocator();
|
2022-05-20 10:20:00 +02:00
|
|
|
var nameBuf = alloc.allocate(this.name.length);
|
|
|
|
nameBuf.writeBytes(this.name);
|
|
|
|
return nameBuf;
|
2021-11-12 02:05:44 +01:00
|
|
|
});
|
2022-04-05 13:58:12 +02:00
|
|
|
this.dbWScheduler = dbWScheduler;
|
|
|
|
this.dbRScheduler = dbRScheduler;
|
2021-09-05 14:23:46 +02:00
|
|
|
if (Schedulers.isInNonBlockingThread()) {
|
|
|
|
throw new UnsupportedOperationException("Initialized in a nonblocking thread");
|
|
|
|
}
|
2022-05-20 10:20:00 +02:00
|
|
|
try (var readOptions = new ReadOptions();
|
|
|
|
var writeOptions = new WriteOptions()) {
|
2022-05-12 19:14:27 +02:00
|
|
|
if (defaultValue != null && db.get(readOptions, this.name, true) == null) {
|
|
|
|
db.put(writeOptions, this.name, defaultValue);
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-05 13:58:12 +02:00
|
|
|
private <T> @NotNull Mono<T> runOnDb(boolean write, Callable<@Nullable T> callable) {
|
|
|
|
return Mono.fromCallable(callable).subscribeOn(write ? dbWScheduler : dbRScheduler);
|
2021-11-12 02:05:44 +01:00
|
|
|
}
|
|
|
|
|
2022-05-20 10:20:00 +02:00
|
|
|
private ReadOptions generateReadOptions(LLSnapshot snapshot) {
|
2020-12-07 22:15:18 +01:00
|
|
|
if (snapshot != null) {
|
2022-05-20 10:20:00 +02:00
|
|
|
return new ReadOptions().setSnapshot(snapshotResolver.apply(snapshot));
|
2022-05-10 16:57:41 +02:00
|
|
|
} else {
|
2022-05-20 10:20:00 +02:00
|
|
|
return new ReadOptions();
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 02:05:44 +01:00
|
|
|
@Override
|
|
|
|
public BufferAllocator getAllocator() {
|
|
|
|
return db.getAllocator();
|
|
|
|
}
|
|
|
|
|
2020-12-07 22:15:18 +01:00
|
|
|
@Override
|
2022-05-20 10:20:00 +02:00
|
|
|
public Mono<Buffer> get(@Nullable LLSnapshot snapshot) {
|
|
|
|
return nameMono.publishOn(dbRScheduler).handle((name, sink) -> {
|
|
|
|
try (name) {
|
2022-05-11 00:29:42 +02:00
|
|
|
Buffer result;
|
2022-05-12 19:14:27 +02:00
|
|
|
try (var readOptions = generateReadOptions(snapshot)) {
|
2022-05-11 00:29:42 +02:00
|
|
|
result = db.get(readOptions, name);
|
|
|
|
}
|
2022-03-20 14:33:27 +01:00
|
|
|
if (result != null) {
|
2022-05-20 10:20:00 +02:00
|
|
|
sink.next(result);
|
2022-03-20 14:33:27 +01:00
|
|
|
} else {
|
|
|
|
sink.complete();
|
|
|
|
}
|
|
|
|
} catch (RocksDBException ex) {
|
2022-05-20 10:20:00 +02:00
|
|
|
sink.error(new IOException("Failed to read " + LLUtils.toString(name), ex));
|
2022-03-20 14:33:27 +01:00
|
|
|
}
|
|
|
|
});
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-05-20 10:20:00 +02:00
|
|
|
public Mono<Void> set(Mono<Buffer> valueMono) {
|
2022-04-05 13:58:12 +02:00
|
|
|
return Mono.zip(nameMono, valueMono).publishOn(dbWScheduler).handle((tuple, sink) -> {
|
2022-05-20 10:20:00 +02:00
|
|
|
var name = tuple.getT1();
|
|
|
|
var value = tuple.getT2();
|
|
|
|
try (name; value; var writeOptions = new WriteOptions()) {
|
2022-05-12 19:14:27 +02:00
|
|
|
db.put(writeOptions, name, value);
|
|
|
|
sink.next(true);
|
2022-03-20 14:33:27 +01:00
|
|
|
} catch (RocksDBException ex) {
|
2022-05-20 10:20:00 +02:00
|
|
|
sink.error(new IOException("Failed to write " + LLUtils.toString(name), ex));
|
2022-03-20 14:33:27 +01:00
|
|
|
}
|
|
|
|
}).switchIfEmpty(unset().thenReturn(true)).then();
|
|
|
|
}
|
|
|
|
|
|
|
|
private Mono<Void> unset() {
|
2022-05-20 10:20:00 +02:00
|
|
|
return nameMono.publishOn(dbWScheduler).handle((name, sink) -> {
|
|
|
|
try (name; var writeOptions = new WriteOptions()) {
|
2022-05-12 19:14:27 +02:00
|
|
|
db.delete(writeOptions, name);
|
2022-03-20 14:33:27 +01:00
|
|
|
} catch (RocksDBException ex) {
|
2022-05-20 10:20:00 +02:00
|
|
|
sink.error(new IOException("Failed to read " + LLUtils.toString(name), ex));
|
2022-03-20 14:33:27 +01:00
|
|
|
}
|
|
|
|
});
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|
|
|
|
|
2021-11-12 02:05:44 +01:00
|
|
|
@Override
|
2022-05-20 10:20:00 +02:00
|
|
|
public Mono<Buffer> update(BinarySerializationFunction updater,
|
2021-11-12 02:05:44 +01:00
|
|
|
UpdateReturnMode updateReturnMode) {
|
2022-05-20 10:20:00 +02:00
|
|
|
return Mono.usingWhen(nameMono, key -> runOnDb(true, () -> {
|
2022-04-01 01:30:56 +02:00
|
|
|
if (Schedulers.isInNonBlockingThread()) {
|
|
|
|
throw new UnsupportedOperationException("Called update in a nonblocking thread");
|
|
|
|
}
|
|
|
|
UpdateAtomicResultMode returnMode = switch (updateReturnMode) {
|
|
|
|
case NOTHING -> UpdateAtomicResultMode.NOTHING;
|
|
|
|
case GET_NEW_VALUE -> UpdateAtomicResultMode.CURRENT;
|
|
|
|
case GET_OLD_VALUE -> UpdateAtomicResultMode.PREVIOUS;
|
|
|
|
};
|
|
|
|
UpdateAtomicResult result;
|
2022-05-20 18:31:05 +02:00
|
|
|
try (var readOptions = new ReadOptions(); var writeOptions = new WriteOptions()) {
|
2022-05-12 19:14:27 +02:00
|
|
|
result = db.updateAtomic(readOptions, writeOptions, key, updater, returnMode);
|
2022-04-01 01:30:56 +02:00
|
|
|
}
|
|
|
|
return switch (updateReturnMode) {
|
2022-05-20 18:31:05 +02:00
|
|
|
case NOTHING -> {
|
|
|
|
result.close();
|
|
|
|
yield null;
|
|
|
|
}
|
2022-04-01 01:30:56 +02:00
|
|
|
case GET_NEW_VALUE -> ((UpdateAtomicResultCurrent) result).current();
|
|
|
|
case GET_OLD_VALUE -> ((UpdateAtomicResultPrevious) result).previous();
|
|
|
|
};
|
|
|
|
}).onErrorMap(cause -> new IOException("Failed to read or write", cause)),
|
|
|
|
keySend -> Mono.fromRunnable(keySend::close));
|
2021-11-12 02:05:44 +01:00
|
|
|
}
|
|
|
|
|
2022-03-20 14:33:27 +01:00
|
|
|
@Override
|
2022-05-20 10:20:00 +02:00
|
|
|
public Mono<LLDelta> updateAndGetDelta(BinarySerializationFunction updater) {
|
|
|
|
return Mono.usingWhen(nameMono, key -> runOnDb(true, () -> {
|
2022-04-01 01:30:56 +02:00
|
|
|
if (Schedulers.isInNonBlockingThread()) {
|
|
|
|
throw new UnsupportedOperationException("Called update in a nonblocking thread");
|
|
|
|
}
|
|
|
|
UpdateAtomicResult result;
|
2022-05-20 18:31:05 +02:00
|
|
|
try (var readOptions = new ReadOptions(); var writeOptions = new WriteOptions()) {
|
2022-05-12 19:14:27 +02:00
|
|
|
result = db.updateAtomic(readOptions, writeOptions, key, updater, DELTA);
|
2022-04-01 01:30:56 +02:00
|
|
|
}
|
|
|
|
return ((UpdateAtomicResultDelta) result).delta();
|
|
|
|
}).onErrorMap(cause -> new IOException("Failed to read or write", cause)),
|
|
|
|
keySend -> Mono.fromRunnable(keySend::close));
|
2022-03-20 14:33:27 +01:00
|
|
|
}
|
|
|
|
|
2020-12-07 22:15:18 +01:00
|
|
|
@Override
|
|
|
|
public String getDatabaseName() {
|
|
|
|
return databaseName;
|
|
|
|
}
|
2022-03-20 14:33:27 +01:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getColumnName() {
|
|
|
|
return columnName;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getName() {
|
|
|
|
return new String(name);
|
|
|
|
}
|
2020-12-07 22:15:18 +01:00
|
|
|
}
|