mirror of
https://github.com/usatiuk/dhfs.git
synced 2025-10-29 04:57:48 +01:00
Compare commits
16 Commits
289a2b880e
...
javadoc-pu
| Author | SHA1 | Date | |
|---|---|---|---|
| 7902d9e486 | |||
| 7c056b9674 | |||
| 8cc040b234 | |||
| f8375c9cd8 | |||
| 0c3524851e | |||
| 3eb7164c0f | |||
| f544a67fb5 | |||
| 964b3da951 | |||
| cb33472dc5 | |||
| de211bb2d2 | |||
| 56ab3bad4c | |||
| 9403556220 | |||
| 469a6b9011 | |||
| 52ccbb99bc | |||
| d972cd1562 | |||
| 80151bcca5 |
45
.github/workflows/server.yml
vendored
45
.github/workflows/server.yml
vendored
@@ -43,11 +43,11 @@ jobs:
|
|||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
cache: maven
|
cache: maven
|
||||||
|
|
||||||
- name: Build LazyFS
|
# - name: Build LazyFS
|
||||||
run: cd thirdparty/lazyfs/ && ./build.sh
|
# run: cd thirdparty/lazyfs/ && ./build.sh
|
||||||
|
|
||||||
- name: Test with Maven
|
- name: Test with Maven
|
||||||
run: cd dhfs-parent && mvn -T $(nproc) --batch-mode --update-snapshots package verify
|
run: cd dhfs-parent && mvn -T $(nproc) --batch-mode --update-snapshots javadoc:aggregate
|
||||||
|
|
||||||
# - name: Build with Maven
|
# - name: Build with Maven
|
||||||
# run: cd dhfs-parent && mvn --batch-mode --update-snapshots package # -Dquarkus.log.category.\"com.usatiuk.dhfs\".min-level=DEBUG
|
# run: cd dhfs-parent && mvn --batch-mode --update-snapshots package # -Dquarkus.log.category.\"com.usatiuk.dhfs\".min-level=DEBUG
|
||||||
@@ -57,6 +57,11 @@ jobs:
|
|||||||
name: DHFS Server Package
|
name: DHFS Server Package
|
||||||
path: dhfs-parent/dhfs-fuse/target/quarkus-app
|
path: dhfs-parent/dhfs-fuse/target/quarkus-app
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: DHFS Javadocs
|
||||||
|
path: dhfs-parent/target/reports/apidocs/
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
@@ -231,3 +236,37 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: Run wrapper
|
name: Run wrapper
|
||||||
path: ~/run-wrapper.tar.gz
|
path: ~/run-wrapper.tar.gz
|
||||||
|
|
||||||
|
publish-javadoc:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
needs: [build-webui, build-dhfs]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: DHFS Javadocs
|
||||||
|
path: dhfs-javadocs-downloaded
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v5
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
# Upload entire repository
|
||||||
|
path: 'dhfs-javadocs-downloaded'
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
dhfs-parent/.gitignore
vendored
2
dhfs-parent/.gitignore
vendored
@@ -41,3 +41,5 @@ nb-configuration.xml
|
|||||||
|
|
||||||
# Plugin directory
|
# Plugin directory
|
||||||
/.quarkus/cli/plugins/
|
/.quarkus/cli/plugins/
|
||||||
|
|
||||||
|
.jqwik-database
|
||||||
@@ -5,6 +5,11 @@ import com.usatiuk.dhfs.remoteobj.JDataRemote;
|
|||||||
import com.usatiuk.dhfs.remoteobj.JDataRemoteDto;
|
import com.usatiuk.dhfs.remoteobj.JDataRemoteDto;
|
||||||
import com.usatiuk.objects.JObjectKey;
|
import com.usatiuk.objects.JObjectKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ChunkData is a data structure that represents an immutable binary blob
|
||||||
|
* @param key unique key
|
||||||
|
* @param data binary data
|
||||||
|
*/
|
||||||
public record ChunkData(JObjectKey key, ByteString data) implements JDataRemote, JDataRemoteDto {
|
public record ChunkData(JObjectKey key, ByteString data) implements JDataRemote, JDataRemoteDto {
|
||||||
@Override
|
@Override
|
||||||
public int estimateSize() {
|
public int estimateSize() {
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.usatiuk.dhfsfs.objects;
|
|
||||||
|
|
||||||
import com.usatiuk.dhfs.ProtoSerializer;
|
|
||||||
import com.usatiuk.dhfs.persistence.ChunkDataP;
|
|
||||||
import com.usatiuk.dhfs.persistence.JObjectKeyP;
|
|
||||||
import com.usatiuk.objects.JObjectKey;
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class ChunkDataProtoSerializer implements ProtoSerializer<ChunkDataP, ChunkData> {
|
|
||||||
@Override
|
|
||||||
public ChunkData deserialize(ChunkDataP message) {
|
|
||||||
return new ChunkData(
|
|
||||||
JObjectKey.of(message.getKey().getName()),
|
|
||||||
message.getData()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkDataP serialize(ChunkData object) {
|
|
||||||
return ChunkDataP.newBuilder()
|
|
||||||
.setKey(JObjectKeyP.newBuilder().setName(object.key().value()).build())
|
|
||||||
.setData(object.data())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,6 +9,14 @@ import com.usatiuk.objects.JObjectKey;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File is a data structure that represents a file in the file system
|
||||||
|
* @param key unique key
|
||||||
|
* @param mode file mode
|
||||||
|
* @param cTime creation time
|
||||||
|
* @param mTime modification time
|
||||||
|
* @param symlink true if the file is a symlink, false otherwise
|
||||||
|
*/
|
||||||
public record File(JObjectKey key, long mode, long cTime, long mTime,
|
public record File(JObjectKey key, long mode, long cTime, long mTime,
|
||||||
boolean symlink
|
boolean symlink
|
||||||
) implements JDataRemote, JMapHolder<JMapLongKey> {
|
) implements JDataRemote, JMapHolder<JMapLongKey> {
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileDto is a data transfer object that contains a file and its chunks.
|
||||||
|
* @param file the file
|
||||||
|
* @param chunks the list of chunks, each represented as a pair of a long and a JObjectKey
|
||||||
|
*/
|
||||||
public record FileDto(File file, List<Pair<Long, JObjectKey>> chunks) implements JDataRemoteDto {
|
public record FileDto(File file, List<Pair<Long, JObjectKey>> chunks) implements JDataRemoteDto {
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends JDataRemote> objClass() {
|
public Class<? extends JDataRemote> objClass() {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import com.usatiuk.dhfs.syncmap.DtoMapper;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a {@link File} object to a {@link FileDto} object and vice versa.
|
||||||
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class FileDtoMapper implements DtoMapper<File, FileDto> {
|
public class FileDtoMapper implements DtoMapper<File, FileDto> {
|
||||||
@Inject
|
@Inject
|
||||||
|
|||||||
@@ -10,11 +10,20 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for working with files.
|
||||||
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class FileHelper {
|
public class FileHelper {
|
||||||
@Inject
|
@Inject
|
||||||
JMapHelper jMapHelper;
|
JMapHelper jMapHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chunks of a file.
|
||||||
|
* Transaction is expected to be already started.
|
||||||
|
* @param file the file to get chunks from
|
||||||
|
* @return a list of pairs of chunk offset and chunk key
|
||||||
|
*/
|
||||||
public List<Pair<Long, JObjectKey>> getChunks(File file) {
|
public List<Pair<Long, JObjectKey>> getChunks(File file) {
|
||||||
ArrayList<Pair<Long, JObjectKey>> chunks = new ArrayList<>();
|
ArrayList<Pair<Long, JObjectKey>> chunks = new ArrayList<>();
|
||||||
try (var it = jMapHelper.getIterator(file)) {
|
try (var it = jMapHelper.getIterator(file)) {
|
||||||
@@ -26,6 +35,13 @@ public class FileHelper {
|
|||||||
return List.copyOf(chunks);
|
return List.copyOf(chunks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the chunks of a file.
|
||||||
|
* All previous chunks will be deleted.
|
||||||
|
* Transaction is expected to be already started.
|
||||||
|
* @param file the file to replace chunks in
|
||||||
|
* @param chunks the list of pairs of chunk offset and chunk key
|
||||||
|
*/
|
||||||
public void replaceChunks(File file, List<Pair<Long, JObjectKey>> chunks) {
|
public void replaceChunks(File file, List<Pair<Long, JObjectKey>> chunks) {
|
||||||
jMapHelper.deleteAll(file);
|
jMapHelper.deleteAll(file);
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
package com.usatiuk.dhfsfs.objects;
|
|
||||||
|
|
||||||
import com.usatiuk.dhfs.ProtoSerializer;
|
|
||||||
import com.usatiuk.dhfs.persistence.FileDtoP;
|
|
||||||
import com.usatiuk.utils.SerializationHelper;
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class FileProtoSerializer implements ProtoSerializer<FileDtoP, FileDto> {
|
|
||||||
@Override
|
|
||||||
public FileDto deserialize(FileDtoP message) {
|
|
||||||
try (var is = message.getSerializedData().newInput()) {
|
|
||||||
return SerializationHelper.deserialize(is);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileDtoP serialize(FileDto object) {
|
|
||||||
return FileDtoP.newBuilder().setSerializedData(SerializationHelper.serialize(object)).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import com.usatiuk.dhfs.remoteobj.*;
|
|||||||
import com.usatiuk.dhfsfs.service.DhfsFileService;
|
import com.usatiuk.dhfsfs.service.DhfsFileService;
|
||||||
import com.usatiuk.kleppmanntree.AlreadyExistsException;
|
import com.usatiuk.kleppmanntree.AlreadyExistsException;
|
||||||
import com.usatiuk.objects.JObjectKey;
|
import com.usatiuk.objects.JObjectKey;
|
||||||
import com.usatiuk.objects.transaction.LockingStrategy;
|
|
||||||
import com.usatiuk.objects.transaction.Transaction;
|
import com.usatiuk.objects.transaction.Transaction;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
@@ -24,6 +23,9 @@ import javax.annotation.Nullable;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles synchronization of file objects.
|
||||||
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class FileSyncHandler implements ObjSyncHandler<File, FileDto> {
|
public class FileSyncHandler implements ObjSyncHandler<File, FileDto> {
|
||||||
@Inject
|
@Inject
|
||||||
@@ -42,14 +44,18 @@ public class FileSyncHandler implements ObjSyncHandler<File, FileDto> {
|
|||||||
@Inject
|
@Inject
|
||||||
DhfsFileService fileService;
|
DhfsFileService fileService;
|
||||||
|
|
||||||
private JKleppmannTreeManager.JKleppmannTree getTreeW() {
|
private JKleppmannTreeManager.JKleppmannTree getTree() {
|
||||||
return jKleppmannTreeManager.getTree(JObjectKey.of("fs")).orElseThrow();
|
return jKleppmannTreeManager.getTree(JObjectKey.of("fs")).orElseThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private JKleppmannTreeManager.JKleppmannTree getTreeR() {
|
/**
|
||||||
return jKleppmannTreeManager.getTree(JObjectKey.of("fs"), LockingStrategy.OPTIMISTIC).orElseThrow();
|
* Resolve conflict between two file versions, update the file in storage and create a conflict file.
|
||||||
}
|
*
|
||||||
|
* @param from the peer that sent the update
|
||||||
|
* @param key the key of the file
|
||||||
|
* @param receivedChangelog the changelog of the received file
|
||||||
|
* @param receivedData the received file data
|
||||||
|
*/
|
||||||
private void resolveConflict(PeerId from, JObjectKey key, PMap<PeerId, Long> receivedChangelog,
|
private void resolveConflict(PeerId from, JObjectKey key, PMap<PeerId, Long> receivedChangelog,
|
||||||
@Nullable FileDto receivedData) {
|
@Nullable FileDto receivedData) {
|
||||||
var oursCurMeta = curTx.get(RemoteObjectMeta.class, key).orElse(null);
|
var oursCurMeta = curTx.get(RemoteObjectMeta.class, key).orElse(null);
|
||||||
@@ -131,12 +137,12 @@ public class FileSyncHandler implements ObjSyncHandler<File, FileDto> {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
getTreeW().move(parent.getRight(),
|
getTree().move(parent.getRight(),
|
||||||
new JKleppmannTreeNodeMetaFile(
|
new JKleppmannTreeNodeMetaFile(
|
||||||
parent.getLeft() + ".fconflict." + persistentPeerDataService.getSelfUuid() + "." + otherHostname.toString() + "." + i,
|
parent.getLeft() + ".fconflict." + persistentPeerDataService.getSelfUuid() + "." + otherHostname.toString() + "." + i,
|
||||||
newFile.key()
|
newFile.key()
|
||||||
),
|
),
|
||||||
getTreeW().getNewNodeId()
|
getTree().getNewNodeId()
|
||||||
);
|
);
|
||||||
} catch (AlreadyExistsException aex) {
|
} catch (AlreadyExistsException aex) {
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import com.usatiuk.objects.JObjectKey;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JKleppmannTreeNodeMetaDirectory is a record that represents a directory in the JKleppmann tree.
|
||||||
|
* @param name the name of the directory
|
||||||
|
*/
|
||||||
public record JKleppmannTreeNodeMetaDirectory(String name) implements JKleppmannTreeNodeMeta {
|
public record JKleppmannTreeNodeMetaDirectory(String name) implements JKleppmannTreeNodeMeta {
|
||||||
public JKleppmannTreeNodeMeta withName(String name) {
|
public JKleppmannTreeNodeMeta withName(String name) {
|
||||||
return new JKleppmannTreeNodeMetaDirectory(name);
|
return new JKleppmannTreeNodeMetaDirectory(name);
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ import com.usatiuk.objects.JObjectKey;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JKleppmannTreeNodeMetaFile is a record that represents a file in the JKleppmann tree.
|
||||||
|
* @param name the name of the file
|
||||||
|
* @param fileIno a reference to the `File` object
|
||||||
|
*/
|
||||||
public record JKleppmannTreeNodeMetaFile(String name, JObjectKey fileIno) implements JKleppmannTreeNodeMeta {
|
public record JKleppmannTreeNodeMetaFile(String name, JObjectKey fileIno) implements JKleppmannTreeNodeMeta {
|
||||||
@Override
|
@Override
|
||||||
public JKleppmannTreeNodeMeta withName(String name) {
|
public JKleppmannTreeNodeMeta withName(String name) {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import com.usatiuk.dhfsfs.objects.JKleppmannTreeNodeMetaFile;
|
|||||||
import com.usatiuk.objects.JData;
|
import com.usatiuk.objects.JData;
|
||||||
import com.usatiuk.objects.JObjectKey;
|
import com.usatiuk.objects.JObjectKey;
|
||||||
import com.usatiuk.objects.iterators.IteratorStart;
|
import com.usatiuk.objects.iterators.IteratorStart;
|
||||||
import com.usatiuk.objects.transaction.LockingStrategy;
|
|
||||||
import com.usatiuk.objects.transaction.Transaction;
|
import com.usatiuk.objects.transaction.Transaction;
|
||||||
import com.usatiuk.objects.transaction.TransactionManager;
|
import com.usatiuk.objects.transaction.TransactionManager;
|
||||||
import com.usatiuk.utils.StatusRuntimeExceptionNoStacktrace;
|
import com.usatiuk.utils.StatusRuntimeExceptionNoStacktrace;
|
||||||
@@ -40,6 +39,9 @@ import java.nio.file.Path;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actual filesystem implementation.
|
||||||
|
*/
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class DhfsFileService {
|
public class DhfsFileService {
|
||||||
@ConfigProperty(name = "dhfs.files.target_chunk_alignment")
|
@ConfigProperty(name = "dhfs.files.target_chunk_alignment")
|
||||||
@@ -68,14 +70,16 @@ public class DhfsFileService {
|
|||||||
@Inject
|
@Inject
|
||||||
JMapHelper jMapHelper;
|
JMapHelper jMapHelper;
|
||||||
|
|
||||||
private JKleppmannTreeManager.JKleppmannTree getTreeW() {
|
private JKleppmannTreeManager.JKleppmannTree getTree() {
|
||||||
return jKleppmannTreeManager.getTree(JObjectKey.of("fs"), () -> new JKleppmannTreeNodeMetaDirectory(""));
|
return jKleppmannTreeManager.getTree(JObjectKey.of("fs"), () -> new JKleppmannTreeNodeMetaDirectory(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
private JKleppmannTreeManager.JKleppmannTree getTreeR() {
|
/**
|
||||||
return jKleppmannTreeManager.getTree(JObjectKey.of("fs"), LockingStrategy.OPTIMISTIC, () -> new JKleppmannTreeNodeMetaDirectory(""));
|
* Create a new chunk with the given data and a new unique ID.
|
||||||
}
|
*
|
||||||
|
* @param bytes the data to store in the chunk
|
||||||
|
* @return the created chunk
|
||||||
|
*/
|
||||||
private ChunkData createChunk(ByteString bytes) {
|
private ChunkData createChunk(ByteString bytes) {
|
||||||
var newChunk = new ChunkData(JObjectKey.of(UUID.randomUUID().toString()), bytes);
|
var newChunk = new ChunkData(JObjectKey.of(UUID.randomUUID().toString()), bytes);
|
||||||
remoteTx.putDataNew(newChunk);
|
remoteTx.putDataNew(newChunk);
|
||||||
@@ -84,30 +88,28 @@ public class DhfsFileService {
|
|||||||
|
|
||||||
void init(@Observes @Priority(500) StartupEvent event) {
|
void init(@Observes @Priority(500) StartupEvent event) {
|
||||||
Log.info("Initializing file service");
|
Log.info("Initializing file service");
|
||||||
getTreeW();
|
getTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
private JKleppmannTreeNode getDirEntryW(String name) {
|
private JKleppmannTreeNode getDirEntry(String name) {
|
||||||
var res = getTreeW().traverse(StreamSupport.stream(Path.of(name).spliterator(), false).map(p -> p.toString()).toList());
|
var res = getTree().traverse(StreamSupport.stream(Path.of(name).spliterator(), false).map(p -> p.toString()).toList());
|
||||||
if (res == null) throw new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND);
|
|
||||||
var ret = curTx.get(JKleppmannTreeNodeHolder.class, res).map(JKleppmannTreeNodeHolder::node).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("Tree node exists but not found as jObject: " + name)));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JKleppmannTreeNode getDirEntryR(String name) {
|
|
||||||
var res = getTreeR().traverse(StreamSupport.stream(Path.of(name).spliterator(), false).map(p -> p.toString()).toList());
|
|
||||||
if (res == null) throw new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND);
|
if (res == null) throw new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND);
|
||||||
var ret = curTx.get(JKleppmannTreeNodeHolder.class, res).map(JKleppmannTreeNodeHolder::node).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("Tree node exists but not found as jObject: " + name)));
|
var ret = curTx.get(JKleppmannTreeNodeHolder.class, res).map(JKleppmannTreeNodeHolder::node).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("Tree node exists but not found as jObject: " + name)));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<JKleppmannTreeNode> getDirEntryOpt(String name) {
|
private Optional<JKleppmannTreeNode> getDirEntryOpt(String name) {
|
||||||
var res = getTreeW().traverse(StreamSupport.stream(Path.of(name).spliterator(), false).map(p -> p.toString()).toList());
|
var res = getTree().traverse(StreamSupport.stream(Path.of(name).spliterator(), false).map(p -> p.toString()).toList());
|
||||||
if (res == null) return Optional.empty();
|
if (res == null) return Optional.empty();
|
||||||
var ret = curTx.get(JKleppmannTreeNodeHolder.class, res).map(JKleppmannTreeNodeHolder::node);
|
var ret = curTx.get(JKleppmannTreeNodeHolder.class, res).map(JKleppmannTreeNodeHolder::node);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the attributes of a file or directory.
|
||||||
|
* @param uuid the UUID of the file or directory
|
||||||
|
* @return the attributes of the file or directory
|
||||||
|
*/
|
||||||
public Optional<GetattrRes> getattr(JObjectKey uuid) {
|
public Optional<GetattrRes> getattr(JObjectKey uuid) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var ref = curTx.get(JData.class, uuid).orElse(null);
|
var ref = curTx.get(JData.class, uuid).orElse(null);
|
||||||
@@ -129,10 +131,15 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to resolve a path to a file or directory.
|
||||||
|
* @param name the path to resolve
|
||||||
|
* @return the key of the file or directory, or an empty optional if it does not exist
|
||||||
|
*/
|
||||||
public Optional<JObjectKey> open(String name) {
|
public Optional<JObjectKey> open(String name) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
try {
|
try {
|
||||||
var ret = getDirEntryR(name);
|
var ret = getDirEntry(name);
|
||||||
return switch (ret.meta()) {
|
return switch (ret.meta()) {
|
||||||
case JKleppmannTreeNodeMetaFile f -> Optional.of(f.fileIno());
|
case JKleppmannTreeNodeMetaFile f -> Optional.of(f.fileIno());
|
||||||
case JKleppmannTreeNodeMetaDirectory f -> Optional.of(ret.key());
|
case JKleppmannTreeNodeMetaDirectory f -> Optional.of(ret.key());
|
||||||
@@ -152,10 +159,16 @@ public class DhfsFileService {
|
|||||||
throw new StatusRuntimeExceptionNoStacktrace(Status.INVALID_ARGUMENT.withDescription("Not a directory: " + entry.key()));
|
throw new StatusRuntimeExceptionNoStacktrace(Status.INVALID_ARGUMENT.withDescription("Not a directory: " + entry.key()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new file with the given name and mode.
|
||||||
|
* @param name the name of the file
|
||||||
|
* @param mode the mode of the file
|
||||||
|
* @return the key of the created file
|
||||||
|
*/
|
||||||
public Optional<JObjectKey> create(String name, long mode) {
|
public Optional<JObjectKey> create(String name, long mode) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
Path path = Path.of(name);
|
Path path = Path.of(name);
|
||||||
var parent = getDirEntryW(path.getParent().toString());
|
var parent = getDirEntry(path.getParent().toString());
|
||||||
|
|
||||||
ensureDir(parent);
|
ensureDir(parent);
|
||||||
|
|
||||||
@@ -167,7 +180,7 @@ public class DhfsFileService {
|
|||||||
remoteTx.putData(f);
|
remoteTx.putData(f);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
getTreeW().move(parent.key(), new JKleppmannTreeNodeMetaFile(fname, f.key()), getTreeW().getNewNodeId());
|
getTree().move(parent.key(), new JKleppmannTreeNodeMetaFile(fname, f.key()), getTree().getNewNodeId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// fobj.getMeta().removeRef(newNodeId);
|
// fobj.getMeta().removeRef(newNodeId);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -176,10 +189,15 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: Slow..
|
/**
|
||||||
|
* Get the parent directory of a file or directory.
|
||||||
|
* @param ino the key of the file or directory
|
||||||
|
* @return the parent directory
|
||||||
|
*/
|
||||||
public Pair<String, JObjectKey> inoToParent(JObjectKey ino) {
|
public Pair<String, JObjectKey> inoToParent(JObjectKey ino) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
return getTreeW().findParent(w -> {
|
// FIXME: Slow
|
||||||
|
return getTree().findParent(w -> {
|
||||||
if (w.meta() instanceof JKleppmannTreeNodeMetaFile f)
|
if (w.meta() instanceof JKleppmannTreeNodeMetaFile f)
|
||||||
return f.fileIno().equals(ino);
|
return f.fileIno().equals(ino);
|
||||||
return false;
|
return false;
|
||||||
@@ -187,20 +205,31 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new directory with the given name and mode.
|
||||||
|
* @param name the name of the directory
|
||||||
|
* @param mode the mode of the directory
|
||||||
|
*/
|
||||||
public void mkdir(String name, long mode) {
|
public void mkdir(String name, long mode) {
|
||||||
jObjectTxManager.executeTx(() -> {
|
jObjectTxManager.executeTx(() -> {
|
||||||
Path path = Path.of(name);
|
Path path = Path.of(name);
|
||||||
var parent = getDirEntryW(path.getParent().toString());
|
var parent = getDirEntry(path.getParent().toString());
|
||||||
ensureDir(parent);
|
ensureDir(parent);
|
||||||
|
|
||||||
String dname = path.getFileName().toString();
|
String dname = path.getFileName().toString();
|
||||||
|
|
||||||
Log.debug("Creating directory " + name);
|
Log.debug("Creating directory " + name);
|
||||||
|
|
||||||
getTreeW().move(parent.key(), new JKleppmannTreeNodeMetaDirectory(dname), getTreeW().getNewNodeId());
|
// TODO: No modes for directories yet
|
||||||
|
getTree().move(parent.key(), new JKleppmannTreeNodeMetaDirectory(dname), getTree().getNewNodeId());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlink a file or directory.
|
||||||
|
* @param name the name of the file or directory
|
||||||
|
* @throws DirectoryNotEmptyException if the directory is not empty and recursive delete is not allowed
|
||||||
|
*/
|
||||||
public void unlink(String name) {
|
public void unlink(String name) {
|
||||||
jObjectTxManager.executeTx(() -> {
|
jObjectTxManager.executeTx(() -> {
|
||||||
var node = getDirEntryOpt(name).orElse(null);
|
var node = getDirEntryOpt(name).orElse(null);
|
||||||
@@ -210,24 +239,36 @@ public class DhfsFileService {
|
|||||||
if (!allowRecursiveDelete && !node.children().isEmpty())
|
if (!allowRecursiveDelete && !node.children().isEmpty())
|
||||||
throw new DirectoryNotEmptyException();
|
throw new DirectoryNotEmptyException();
|
||||||
}
|
}
|
||||||
getTreeW().trash(node.meta(), node.key());
|
getTree().trash(node.meta(), node.key());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a file or directory.
|
||||||
|
* @param from the old name
|
||||||
|
* @param to the new name
|
||||||
|
* @return true if the rename was successful, false otherwise
|
||||||
|
*/
|
||||||
public Boolean rename(String from, String to) {
|
public Boolean rename(String from, String to) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var node = getDirEntryW(from);
|
var node = getDirEntry(from);
|
||||||
JKleppmannTreeNodeMeta meta = node.meta();
|
JKleppmannTreeNodeMeta meta = node.meta();
|
||||||
|
|
||||||
var toPath = Path.of(to);
|
var toPath = Path.of(to);
|
||||||
var toDentry = getDirEntryW(toPath.getParent().toString());
|
var toDentry = getDirEntry(toPath.getParent().toString());
|
||||||
ensureDir(toDentry);
|
ensureDir(toDentry);
|
||||||
|
|
||||||
getTreeW().move(toDentry.key(), meta.withName(toPath.getFileName().toString()), node.key());
|
getTree().move(toDentry.key(), meta.withName(toPath.getFileName().toString()), node.key());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the mode of a file or directory.
|
||||||
|
* @param uuid the ID of the file or directory
|
||||||
|
* @param mode the new mode
|
||||||
|
* @return true if the mode was changed successfully, false otherwise
|
||||||
|
*/
|
||||||
public Boolean chmod(JObjectKey uuid, long mode) {
|
public Boolean chmod(JObjectKey uuid, long mode) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var dent = curTx.get(JData.class, uuid).orElseThrow(() -> new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND));
|
var dent = curTx.get(JData.class, uuid).orElseThrow(() -> new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND));
|
||||||
@@ -248,9 +289,14 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a directory.
|
||||||
|
* @param name the path of the directory
|
||||||
|
* @return an iterable of the names of the files in the directory
|
||||||
|
*/
|
||||||
public Iterable<String> readDir(String name) {
|
public Iterable<String> readDir(String name) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var found = getDirEntryW(name);
|
var found = getDirEntry(name);
|
||||||
|
|
||||||
if (!(found.meta() instanceof JKleppmannTreeNodeMetaDirectory md))
|
if (!(found.meta() instanceof JKleppmannTreeNodeMetaDirectory md))
|
||||||
throw new StatusRuntimeException(Status.INVALID_ARGUMENT);
|
throw new StatusRuntimeException(Status.INVALID_ARGUMENT);
|
||||||
@@ -259,6 +305,13 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param offset the offset to start reading from
|
||||||
|
* @param length the number of bytes to read
|
||||||
|
* @return the contents of the file as a ByteString
|
||||||
|
*/
|
||||||
public ByteString read(JObjectKey fileUuid, long offset, int length) {
|
public ByteString read(JObjectKey fileUuid, long offset, int length) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
if (length < 0)
|
if (length < 0)
|
||||||
@@ -320,6 +373,11 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a file.
|
||||||
|
* @param uuid the ID of the file
|
||||||
|
* @return the size of the file
|
||||||
|
*/
|
||||||
private ByteString readChunk(JObjectKey uuid) {
|
private ByteString readChunk(JObjectKey uuid) {
|
||||||
var chunkRead = remoteTx.getData(ChunkData.class, uuid).orElse(null);
|
var chunkRead = remoteTx.getData(ChunkData.class, uuid).orElse(null);
|
||||||
|
|
||||||
@@ -331,6 +389,11 @@ public class DhfsFileService {
|
|||||||
return chunkRead.data();
|
return chunkRead.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a chunk.
|
||||||
|
* @param uuid the ID of the chunk
|
||||||
|
* @return the size of the chunk
|
||||||
|
*/
|
||||||
private int getChunkSize(JObjectKey uuid) {
|
private int getChunkSize(JObjectKey uuid) {
|
||||||
return readChunk(uuid).size();
|
return readChunk(uuid).size();
|
||||||
}
|
}
|
||||||
@@ -339,12 +402,19 @@ public class DhfsFileService {
|
|||||||
return num & -(1L << n);
|
return num & -(1L << n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param offset the offset to write to
|
||||||
|
* @param data the data to write
|
||||||
|
* @return the number of bytes written
|
||||||
|
*/
|
||||||
public Long write(JObjectKey fileUuid, long offset, ByteString data) {
|
public Long write(JObjectKey fileUuid, long offset, ByteString data) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
if (offset < 0)
|
if (offset < 0)
|
||||||
throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Offset should be more than zero: " + offset));
|
throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Offset should be more than zero: " + offset));
|
||||||
|
|
||||||
var file = remoteTx.getData(File.class, fileUuid, LockingStrategy.WRITE).orElse(null);
|
var file = remoteTx.getData(File.class, fileUuid).orElse(null);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw new StatusRuntimeException(Status.NOT_FOUND.withDescription("File not found when trying to write: " + fileUuid));
|
throw new StatusRuntimeException(Status.NOT_FOUND.withDescription("File not found when trying to write: " + fileUuid));
|
||||||
}
|
}
|
||||||
@@ -441,6 +511,12 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate a file to the given length.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param length the new length of the file
|
||||||
|
* @return true if the truncate was successful, false otherwise
|
||||||
|
*/
|
||||||
public Boolean truncate(JObjectKey fileUuid, long length) {
|
public Boolean truncate(JObjectKey fileUuid, long length) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
if (length < 0)
|
if (length < 0)
|
||||||
@@ -530,6 +606,12 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the given range with zeroes.
|
||||||
|
* @param fillStart the start of the range
|
||||||
|
* @param length the end of the range
|
||||||
|
* @param newChunks the map to store the new chunks in
|
||||||
|
*/
|
||||||
private void fillZeros(long fillStart, long length, Map<Long, JObjectKey> newChunks) {
|
private void fillZeros(long fillStart, long length, Map<Long, JObjectKey> newChunks) {
|
||||||
long combinedSize = (length - fillStart);
|
long combinedSize = (length - fillStart);
|
||||||
|
|
||||||
@@ -565,12 +647,22 @@ public class DhfsFileService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a symlink.
|
||||||
|
* @param uuid the ID of the symlink
|
||||||
|
* @return the contents of the symlink as a string
|
||||||
|
*/
|
||||||
public String readlink(JObjectKey uuid) {
|
public String readlink(JObjectKey uuid) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
return readlinkBS(uuid).toStringUtf8();
|
return readlinkBS(uuid).toStringUtf8();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the contents of a symlink as a ByteString.
|
||||||
|
* @param uuid the ID of the symlink
|
||||||
|
* @return the contents of the symlink as a ByteString
|
||||||
|
*/
|
||||||
public ByteString readlinkBS(JObjectKey uuid) {
|
public ByteString readlinkBS(JObjectKey uuid) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var fileOpt = remoteTx.getData(File.class, uuid).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("File not found when trying to readlink: " + uuid)));
|
var fileOpt = remoteTx.getData(File.class, uuid).orElseThrow(() -> new StatusRuntimeException(Status.NOT_FOUND.withDescription("File not found when trying to readlink: " + uuid)));
|
||||||
@@ -578,10 +670,16 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a symlink.
|
||||||
|
* @param oldpath the target of the symlink
|
||||||
|
* @param newpath the path of the symlink
|
||||||
|
* @return the key of the created symlink
|
||||||
|
*/
|
||||||
public JObjectKey symlink(String oldpath, String newpath) {
|
public JObjectKey symlink(String oldpath, String newpath) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
Path path = Path.of(newpath);
|
Path path = Path.of(newpath);
|
||||||
var parent = getDirEntryW(path.getParent().toString());
|
var parent = getDirEntry(path.getParent().toString());
|
||||||
|
|
||||||
ensureDir(parent);
|
ensureDir(parent);
|
||||||
|
|
||||||
@@ -595,11 +693,18 @@ public class DhfsFileService {
|
|||||||
jMapHelper.put(f, JMapLongKey.of(0), newChunkData.key());
|
jMapHelper.put(f, JMapLongKey.of(0), newChunkData.key());
|
||||||
|
|
||||||
remoteTx.putData(f);
|
remoteTx.putData(f);
|
||||||
getTreeW().move(parent.key(), new JKleppmannTreeNodeMetaFile(fname, f.key()), getTreeW().getNewNodeId());
|
getTree().move(parent.key(), new JKleppmannTreeNodeMetaFile(fname, f.key()), getTree().getNewNodeId());
|
||||||
return f.key();
|
return f.key();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the access and modification times of a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param atimeMs the access time in milliseconds
|
||||||
|
* @param mtimeMs the modification time in milliseconds
|
||||||
|
* @return true if the times were set successfully, false otherwise
|
||||||
|
*/
|
||||||
public Boolean setTimes(JObjectKey fileUuid, long atimeMs, long mtimeMs) {
|
public Boolean setTimes(JObjectKey fileUuid, long atimeMs, long mtimeMs) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
var dent = curTx.get(JData.class, fileUuid).orElseThrow(() -> new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND));
|
var dent = curTx.get(JData.class, fileUuid).orElseThrow(() -> new StatusRuntimeExceptionNoStacktrace(Status.NOT_FOUND));
|
||||||
@@ -621,6 +726,11 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the size of a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @return the size of the file
|
||||||
|
*/
|
||||||
public long size(JObjectKey fileUuid) {
|
public long size(JObjectKey fileUuid) {
|
||||||
return jObjectTxManager.executeTx(() -> {
|
return jObjectTxManager.executeTx(() -> {
|
||||||
long realSize = 0;
|
long realSize = 0;
|
||||||
@@ -640,6 +750,13 @@ public class DhfsFileService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to a file.
|
||||||
|
* @param fileUuid the ID of the file
|
||||||
|
* @param offset the offset to write to
|
||||||
|
* @param data the data to write
|
||||||
|
* @return the number of bytes written
|
||||||
|
*/
|
||||||
public Long write(JObjectKey fileUuid, long offset, byte[] data) {
|
public Long write(JObjectKey fileUuid, long offset, byte[] data) {
|
||||||
return write(fileUuid, offset, UnsafeByteOperations.unsafeWrap(data));
|
return write(fileUuid, offset, UnsafeByteOperations.unsafeWrap(data));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package com.usatiuk.dhfsfs.service;
|
package com.usatiuk.dhfsfs.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DirectoryNotEmptyException is thrown when a directory is not empty.
|
||||||
|
* This exception is used to indicate that a directory cannot be deleted
|
||||||
|
* because it contains files or subdirectories.
|
||||||
|
*/
|
||||||
public class DirectoryNotEmptyException extends RuntimeException {
|
public class DirectoryNotEmptyException extends RuntimeException {
|
||||||
@Override
|
@Override
|
||||||
public synchronized Throwable fillInStackTrace() {
|
public synchronized Throwable fillInStackTrace() {
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
package com.usatiuk.dhfsfs.service;
|
package com.usatiuk.dhfsfs.service;
|
||||||
|
|
||||||
public record GetattrRes(long mtime, long ctime, long mode, GetattrType type) {
|
/**
|
||||||
|
* GetattrRes is a record that represents the result of a getattr operation.
|
||||||
|
* @param mtime File modification time
|
||||||
|
* @param ctime File creation time
|
||||||
|
* @param mode File mode
|
||||||
|
* @param type File type
|
||||||
|
*/
|
||||||
|
public record GetattrRes(long mtime, long ctime, long mode, GetattrType type) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-failsafe-plugin</artifactId>
|
<artifactId>maven-failsafe-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<forkCount>1C</forkCount>
|
<forkCount>0.5C</forkCount>
|
||||||
<reuseForks>false</reuseForks>
|
<reuseForks>false</reuseForks>
|
||||||
<parallel>classes</parallel>
|
<parallel>classes</parallel>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public class LazyFsIT {
|
|||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
Log.info("Killing");
|
Log.info("Killing");
|
||||||
lazyFs1.crash();
|
lazyFs1.crash();
|
||||||
waitingConsumer1.waitUntil(frame -> frame.getUtf8String().contains("Caused by: org.lmdbjava"), 60, TimeUnit.SECONDS);
|
waitingConsumer1.waitUntil(frame -> frame.getUtf8String().contains("org.lmdbjava.LmdbNativeException"), 60, TimeUnit.SECONDS);
|
||||||
var client = DockerClientFactory.instance().client();
|
var client = DockerClientFactory.instance().client();
|
||||||
client.killContainerCmd(container1.getContainerId()).exec();
|
client.killContainerCmd(container1.getContainerId()).exec();
|
||||||
container1.stop();
|
container1.stop();
|
||||||
@@ -195,7 +195,7 @@ public class LazyFsIT {
|
|||||||
lazyFs1.crash();
|
lazyFs1.crash();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
waitingConsumer1.waitUntil(frame -> frame.getUtf8String().contains("Caused by: org.lmdbjava"), 60, TimeUnit.SECONDS);
|
waitingConsumer1.waitUntil(frame -> frame.getUtf8String().contains("org.lmdbjava.LmdbNativeException"), 60, TimeUnit.SECONDS);
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
// Sometimes crash doesn't work
|
// Sometimes crash doesn't work
|
||||||
Log.info("Failed to crash: " + testInfo.getDisplayName());
|
Log.info("Failed to crash: " + testInfo.getDisplayName());
|
||||||
@@ -237,7 +237,7 @@ public class LazyFsIT {
|
|||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
Log.info("Killing");
|
Log.info("Killing");
|
||||||
lazyFs1.crash();
|
lazyFs1.crash();
|
||||||
waitingConsumer1.waitUntil(frame -> frame.getUtf8String().contains("Caused by: org.lmdbjava"), 60, TimeUnit.SECONDS);
|
waitingConsumer1.waitUntil(frame -> frame.getUtf8String().contains("org.lmdbjava.LmdbNativeException"), 60, TimeUnit.SECONDS);
|
||||||
var client = DockerClientFactory.instance().client();
|
var client = DockerClientFactory.instance().client();
|
||||||
client.killContainerCmd(container1.getContainerId()).exec();
|
client.killContainerCmd(container1.getContainerId()).exec();
|
||||||
container1.stop();
|
container1.stop();
|
||||||
@@ -279,7 +279,7 @@ public class LazyFsIT {
|
|||||||
lazyFs1.crash();
|
lazyFs1.crash();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
waitingConsumer1.waitUntil(frame -> frame.getUtf8String().contains("Caused by: org.lmdbjava"), 60, TimeUnit.SECONDS);
|
waitingConsumer1.waitUntil(frame -> frame.getUtf8String().contains("org.lmdbjava.LmdbNativeException"), 60, TimeUnit.SECONDS);
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
// Sometimes crash doesn't work
|
// Sometimes crash doesn't work
|
||||||
Log.info("Failed to crash: " + testInfo.getDisplayName());
|
Log.info("Failed to crash: " + testInfo.getDisplayName());
|
||||||
@@ -322,7 +322,7 @@ public class LazyFsIT {
|
|||||||
Log.info("Killing");
|
Log.info("Killing");
|
||||||
lazyFs2.crash();
|
lazyFs2.crash();
|
||||||
container1.execInContainer("/bin/sh", "-c", "touch /tmp/stopprinting1");
|
container1.execInContainer("/bin/sh", "-c", "touch /tmp/stopprinting1");
|
||||||
waitingConsumer2.waitUntil(frame -> frame.getUtf8String().contains("Caused by: org.lmdbjava"), 60, TimeUnit.SECONDS);
|
waitingConsumer2.waitUntil(frame -> frame.getUtf8String().contains("org.lmdbjava.LmdbNativeException"), 60, TimeUnit.SECONDS);
|
||||||
var client = DockerClientFactory.instance().client();
|
var client = DockerClientFactory.instance().client();
|
||||||
client.killContainerCmd(container2.getContainerId()).exec();
|
client.killContainerCmd(container2.getContainerId()).exec();
|
||||||
container2.stop();
|
container2.stop();
|
||||||
@@ -366,7 +366,7 @@ public class LazyFsIT {
|
|||||||
}
|
}
|
||||||
container1.execInContainer("/bin/sh", "-c", "touch /tmp/stopprinting2");
|
container1.execInContainer("/bin/sh", "-c", "touch /tmp/stopprinting2");
|
||||||
try {
|
try {
|
||||||
waitingConsumer2.waitUntil(frame -> frame.getUtf8String().contains("Caused by: org.lmdbjava"), 60, TimeUnit.SECONDS);
|
waitingConsumer2.waitUntil(frame -> frame.getUtf8String().contains("org.lmdbjava.LmdbNativeException"), 60, TimeUnit.SECONDS);
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
// Sometimes crash doesn't work
|
// Sometimes crash doesn't work
|
||||||
Log.info("Failed to crash: " + testInfo.getDisplayName());
|
Log.info("Failed to crash: " + testInfo.getDisplayName());
|
||||||
@@ -409,7 +409,7 @@ public class LazyFsIT {
|
|||||||
Log.info("Killing");
|
Log.info("Killing");
|
||||||
lazyFs2.crash();
|
lazyFs2.crash();
|
||||||
container1.execInContainer("/bin/sh", "-c", "touch /tmp/stopprinting1");
|
container1.execInContainer("/bin/sh", "-c", "touch /tmp/stopprinting1");
|
||||||
waitingConsumer2.waitUntil(frame -> frame.getUtf8String().contains("Caused by: org.lmdbjava"), 60, TimeUnit.SECONDS);
|
waitingConsumer2.waitUntil(frame -> frame.getUtf8String().contains("org.lmdbjava.LmdbNativeException"), 60, TimeUnit.SECONDS);
|
||||||
var client = DockerClientFactory.instance().client();
|
var client = DockerClientFactory.instance().client();
|
||||||
client.killContainerCmd(container2.getContainerId()).exec();
|
client.killContainerCmd(container2.getContainerId()).exec();
|
||||||
container2.stop();
|
container2.stop();
|
||||||
@@ -453,7 +453,7 @@ public class LazyFsIT {
|
|||||||
}
|
}
|
||||||
container1.execInContainer("/bin/sh", "-c", "touch /tmp/stopprinting2");
|
container1.execInContainer("/bin/sh", "-c", "touch /tmp/stopprinting2");
|
||||||
try {
|
try {
|
||||||
waitingConsumer2.waitUntil(frame -> frame.getUtf8String().contains("Caused by: org.lmdbjava"), 60, TimeUnit.SECONDS);
|
waitingConsumer2.waitUntil(frame -> frame.getUtf8String().contains("org.lmdbjava.LmdbNativeException"), 60, TimeUnit.SECONDS);
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
// Sometimes crash doesn't work
|
// Sometimes crash doesn't work
|
||||||
Log.info("Failed to crash: " + testInfo.getDisplayName());
|
Log.info("Failed to crash: " + testInfo.getDisplayName());
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package com.usatiuk.kleppmanntree;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
public class AtomicClock implements Clock<Long>, Serializable {
|
|
||||||
private long _max = 0;
|
|
||||||
|
|
||||||
public AtomicClock(long counter) {
|
|
||||||
_max = counter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long getTimestamp() {
|
|
||||||
return ++_max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimestamp(Long timestamp) {
|
|
||||||
_max = timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long peekTimestamp() {
|
|
||||||
return _max;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Long updateTimestamp(Long receivedTimestamp) {
|
|
||||||
var old = _max;
|
|
||||||
_max = Math.max(_max, receivedTimestamp) + 1;
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package com.usatiuk.objects.iterators;
|
package com.usatiuk.objects.iterators;
|
||||||
|
|
||||||
import com.usatiuk.utils.AutoCloseableNoThrow;
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
public interface CloseableKvIterator<K extends Comparable<? super K>, V> extends Iterator<Pair<K, V>>, AutoCloseableNoThrow {
|
public interface CloseableKvIterator<K extends Comparable<? super K>, V> extends Iterator<Pair<K, V>>, AutoCloseable {
|
||||||
K peekNextKey();
|
K peekNextKey();
|
||||||
|
|
||||||
void skip();
|
void skip();
|
||||||
@@ -21,4 +20,7 @@ public interface CloseableKvIterator<K extends Comparable<? super K>, V> extends
|
|||||||
default CloseableKvIterator<K, V> reversed() {
|
default CloseableKvIterator<K, V> reversed() {
|
||||||
return new ReversedKvIterator<K, V>(this);
|
return new ReversedKvIterator<K, V>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,20 @@ package com.usatiuk.objects.snapshot;
|
|||||||
import com.usatiuk.objects.iterators.CloseableKvIterator;
|
import com.usatiuk.objects.iterators.CloseableKvIterator;
|
||||||
import com.usatiuk.objects.iterators.IteratorStart;
|
import com.usatiuk.objects.iterators.IteratorStart;
|
||||||
import com.usatiuk.objects.iterators.MaybeTombstone;
|
import com.usatiuk.objects.iterators.MaybeTombstone;
|
||||||
import com.usatiuk.objects.iterators.Tombstone;
|
|
||||||
import com.usatiuk.utils.AutoCloseableNoThrow;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public interface Snapshot<K extends Comparable<K>, V> extends AutoCloseableNoThrow {
|
public interface Snapshot<K extends Comparable<K>, V> extends AutoCloseable {
|
||||||
List<CloseableKvIterator<K, MaybeTombstone<V>>> getIterator(IteratorStart start, K key);
|
List<CloseableKvIterator<K, MaybeTombstone<V>>> getIterator(IteratorStart start, K key);
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Optional<V> readObject(K name);
|
Optional<V> readObject(K name);
|
||||||
|
|
||||||
long id();
|
long id();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ public class CachingObjectPersistentStore {
|
|||||||
SerializingObjectPersistentStore delegate;
|
SerializingObjectPersistentStore delegate;
|
||||||
@ConfigProperty(name = "dhfs.objects.lru.print-stats")
|
@ConfigProperty(name = "dhfs.objects.lru.print-stats")
|
||||||
boolean printStats;
|
boolean printStats;
|
||||||
private ExecutorService _commitExecutor;
|
|
||||||
private ExecutorService _statusExecutor;
|
private ExecutorService _statusExecutor;
|
||||||
private AtomicLong _cached = new AtomicLong();
|
private AtomicLong _cached = new AtomicLong();
|
||||||
private AtomicLong _cacheTries = new AtomicLong();
|
private AtomicLong _cacheTries = new AtomicLong();
|
||||||
@@ -47,7 +46,6 @@ public class CachingObjectPersistentStore {
|
|||||||
_cache.set(_cache.get().withVersion(s.id()));
|
_cache.set(_cache.get().withVersion(s.id()));
|
||||||
}
|
}
|
||||||
|
|
||||||
_commitExecutor = Executors.newSingleThreadExecutor();
|
|
||||||
if (printStats) {
|
if (printStats) {
|
||||||
_statusExecutor = Executors.newSingleThreadExecutor();
|
_statusExecutor = Executors.newSingleThreadExecutor();
|
||||||
_statusExecutor.submit(() -> {
|
_statusExecutor.submit(() -> {
|
||||||
@@ -68,7 +66,6 @@ public class CachingObjectPersistentStore {
|
|||||||
Log.tracev("Committing: {0} writes, {1} deletes", objs.written().size(), objs.deleted().size());
|
Log.tracev("Committing: {0} writes, {1} deletes", objs.written().size(), objs.deleted().size());
|
||||||
|
|
||||||
var cache = _cache.get();
|
var cache = _cache.get();
|
||||||
var commitFuture = _commitExecutor.submit(() -> delegate.prepareTx(objs, txId).run());
|
|
||||||
for (var write : objs.written()) {
|
for (var write : objs.written()) {
|
||||||
cache = cache.withPut(write.getLeft(), Optional.of(write.getRight()));
|
cache = cache.withPut(write.getLeft(), Optional.of(write.getRight()));
|
||||||
}
|
}
|
||||||
@@ -76,11 +73,7 @@ public class CachingObjectPersistentStore {
|
|||||||
cache = cache.withPut(del, Optional.empty());
|
cache = cache.withPut(del, Optional.empty());
|
||||||
}
|
}
|
||||||
cache = cache.withVersion(txId);
|
cache = cache.withVersion(txId);
|
||||||
try {
|
delegate.commitTx(objs, txId);
|
||||||
commitFuture.get();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
_cache.set(cache);
|
_cache.set(cache);
|
||||||
|
|
||||||
Log.tracev("Committed: {0} writes, {1} deletes", objs.written().size(), objs.deleted().size());
|
Log.tracev("Committed: {0} writes, {1} deletes", objs.written().size(), objs.deleted().size());
|
||||||
|
|||||||
@@ -145,10 +145,9 @@ public class LmdbObjectPersistentStore implements ObjectPersistentStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Runnable prepareTx(TxManifestRaw names, long txId) {
|
public void commitTx(TxManifestRaw names, long txId) {
|
||||||
verifyReady();
|
verifyReady();
|
||||||
var txn = _env.txnWrite();
|
try (var txn = _env.txnWrite()) {
|
||||||
try {
|
|
||||||
for (var written : names.written()) {
|
for (var written : names.written()) {
|
||||||
var putBb = _db.reserve(txn, written.getKey().toByteBuffer(), written.getValue().size());
|
var putBb = _db.reserve(txn, written.getKey().toByteBuffer(), written.getValue().size());
|
||||||
written.getValue().copyTo(putBb);
|
written.getValue().copyTo(putBb);
|
||||||
@@ -163,17 +162,8 @@ public class LmdbObjectPersistentStore implements ObjectPersistentStore {
|
|||||||
bbData.putLong(txId);
|
bbData.putLong(txId);
|
||||||
bbData.flip();
|
bbData.flip();
|
||||||
_db.put(txn, DB_VER_OBJ_NAME.asReadOnlyBuffer(), bbData);
|
_db.put(txn, DB_VER_OBJ_NAME.asReadOnlyBuffer(), bbData);
|
||||||
} catch (Throwable t) {
|
txn.commit();
|
||||||
txn.close();
|
|
||||||
throw t;
|
|
||||||
}
|
}
|
||||||
return () -> {
|
|
||||||
try {
|
|
||||||
txn.commit();
|
|
||||||
} finally {
|
|
||||||
txn.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -188,12 +178,6 @@ public class LmdbObjectPersistentStore implements ObjectPersistentStore {
|
|||||||
return _root.toFile().getFreeSpace();
|
return _root.toFile().getFreeSpace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getUsableSpace() {
|
|
||||||
verifyReady();
|
|
||||||
return _root.toFile().getUsableSpace();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LmdbKvIterator extends ReversibleKvIterator<JObjectKey, MaybeTombstone<ByteBuffer>> {
|
private class LmdbKvIterator extends ReversibleKvIterator<JObjectKey, MaybeTombstone<ByteBuffer>> {
|
||||||
private static final Cleaner CLEANER = Cleaner.create();
|
private static final Cleaner CLEANER = Cleaner.create();
|
||||||
private final Txn<ByteBuffer> _txn; // Managed by the snapshot
|
private final Txn<ByteBuffer> _txn; // Managed by the snapshot
|
||||||
|
|||||||
@@ -53,19 +53,18 @@ public class MemoryObjectPersistentStore implements ObjectPersistentStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Runnable prepareTx(TxManifestRaw names, long txId) {
|
@Override
|
||||||
return () -> {
|
public void commitTx(TxManifestRaw names, long txId) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
for (var written : names.written()) {
|
for (var written : names.written()) {
|
||||||
_objects = _objects.plus(written.getKey(), written.getValue());
|
_objects = _objects.plus(written.getKey(), written.getValue());
|
||||||
}
|
|
||||||
for (JObjectKey key : names.deleted()) {
|
|
||||||
_objects = _objects.minus(key);
|
|
||||||
}
|
|
||||||
assert txId > _lastCommitId;
|
|
||||||
_lastCommitId = txId;
|
|
||||||
}
|
}
|
||||||
};
|
for (JObjectKey key : names.deleted()) {
|
||||||
|
_objects = _objects.minus(key);
|
||||||
|
}
|
||||||
|
assert txId > _lastCommitId;
|
||||||
|
_lastCommitId = txId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -77,9 +76,4 @@ public class MemoryObjectPersistentStore implements ObjectPersistentStore {
|
|||||||
public long getFreeSpace() {
|
public long getFreeSpace() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getUsableSpace() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ import java.util.Optional;
|
|||||||
public interface ObjectPersistentStore {
|
public interface ObjectPersistentStore {
|
||||||
Snapshot<JObjectKey, ByteBuffer> getSnapshot();
|
Snapshot<JObjectKey, ByteBuffer> getSnapshot();
|
||||||
|
|
||||||
Runnable prepareTx(TxManifestRaw names, long txId);
|
void commitTx(TxManifestRaw names, long txId);
|
||||||
|
|
||||||
long getTotalSpace();
|
long getTotalSpace();
|
||||||
|
|
||||||
long getFreeSpace();
|
long getFreeSpace();
|
||||||
|
|
||||||
long getUsableSpace();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class SerializingObjectPersistentStore {
|
|||||||
, objs.deleted());
|
, objs.deleted());
|
||||||
}
|
}
|
||||||
|
|
||||||
Runnable prepareTx(TxManifestObj<? extends JDataVersionedWrapper> objects, long txId) {
|
void commitTx(TxManifestObj<? extends JDataVersionedWrapper> objects, long txId) {
|
||||||
return delegateStore.prepareTx(prepareManifest(objects), txId);
|
delegateStore.commitTx(prepareManifest(objects), txId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ public class WritebackObjectPersistentStore {
|
|||||||
Log.info("Writeback thread exiting");
|
Log.info("Writeback thread exiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
public long commitBundle(Collection<TxRecord.TxObjectRecord<?>> writes) {
|
private long commitBundle(Collection<TxRecord.TxObjectRecord<?>> writes) {
|
||||||
verifyReady();
|
verifyReady();
|
||||||
_pendingBundleLock.lock();
|
_pendingBundleLock.lock();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ public class CurrentTransaction implements Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends JData> Optional<T> get(Class<T> type, JObjectKey key, LockingStrategy strategy) {
|
public <T extends JData> Optional<T> get(Class<T> type, JObjectKey key) {
|
||||||
return transactionManager.current().get(type, key, strategy);
|
return transactionManager.current().get(type, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package com.usatiuk.objects.transaction;
|
|
||||||
|
|
||||||
import com.usatiuk.objects.JObjectKey;
|
|
||||||
import com.usatiuk.utils.AutoCloseableNoThrow;
|
|
||||||
import com.usatiuk.utils.DataLocker;
|
|
||||||
import jakarta.annotation.Nonnull;
|
|
||||||
import jakarta.annotation.Nullable;
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class LockManager {
|
|
||||||
private final DataLocker _objLocker = new DataLocker();
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public AutoCloseableNoThrow lockObject(JObjectKey key) {
|
|
||||||
return _objLocker.lock(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public AutoCloseableNoThrow tryLockObject(JObjectKey key) {
|
|
||||||
return _objLocker.tryLock(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.usatiuk.objects.transaction;
|
|
||||||
|
|
||||||
public enum LockingStrategy {
|
|
||||||
OPTIMISTIC, // Optimistic write, no blocking other possible writers/readers
|
|
||||||
WRITE, // Write lock, blocks all other writers
|
|
||||||
}
|
|
||||||
@@ -11,17 +11,13 @@ import java.util.Optional;
|
|||||||
public interface Transaction extends TransactionHandle {
|
public interface Transaction extends TransactionHandle {
|
||||||
void onCommit(Runnable runnable);
|
void onCommit(Runnable runnable);
|
||||||
|
|
||||||
<T extends JData> Optional<T> get(Class<T> type, JObjectKey key, LockingStrategy strategy);
|
<T extends JData> Optional<T> get(Class<T> type, JObjectKey key);
|
||||||
|
|
||||||
<T extends JData> void put(JData obj);
|
<T extends JData> void put(JData obj);
|
||||||
<T extends JData> void putNew(JData obj);
|
<T extends JData> void putNew(JData obj);
|
||||||
|
|
||||||
void delete(JObjectKey key);
|
void delete(JObjectKey key);
|
||||||
|
|
||||||
default <T extends JData> Optional<T> get(Class<T> type, JObjectKey key) {
|
|
||||||
return get(type, key, LockingStrategy.OPTIMISTIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
CloseableKvIterator<JObjectKey, JData> getIterator(IteratorStart start, JObjectKey key);
|
CloseableKvIterator<JObjectKey, JData> getIterator(IteratorStart start, JObjectKey key);
|
||||||
|
|
||||||
default CloseableKvIterator<JObjectKey, JData> getIterator(JObjectKey key) {
|
default CloseableKvIterator<JObjectKey, JData> getIterator(JObjectKey key) {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.usatiuk.objects.transaction;
|
|
||||||
|
|
||||||
public interface TransactionFactory {
|
|
||||||
TransactionPrivate createTransaction();
|
|
||||||
}
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
package com.usatiuk.objects.transaction;
|
|
||||||
|
|
||||||
import com.usatiuk.objects.JData;
|
|
||||||
import com.usatiuk.objects.JDataVersionedWrapper;
|
|
||||||
import com.usatiuk.objects.JObjectKey;
|
|
||||||
import com.usatiuk.objects.iterators.*;
|
|
||||||
import com.usatiuk.objects.snapshot.Snapshot;
|
|
||||||
import com.usatiuk.objects.stores.WritebackObjectPersistentStore;
|
|
||||||
import com.usatiuk.utils.ListUtils;
|
|
||||||
import io.quarkus.logging.Log;
|
|
||||||
import jakarta.inject.Inject;
|
|
||||||
import jakarta.inject.Singleton;
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
|
||||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class TransactionFactoryImpl implements TransactionFactory {
|
|
||||||
@Inject
|
|
||||||
WritebackObjectPersistentStore writebackObjectPersistentStore;
|
|
||||||
@Inject
|
|
||||||
LockManager lockManager;
|
|
||||||
@ConfigProperty(name = "dhfs.objects.transaction.never-lock")
|
|
||||||
boolean neverLock;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TransactionPrivate createTransaction() {
|
|
||||||
return new TransactionImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface ReadTrackingInternalCrap {
|
|
||||||
boolean fromSource();
|
|
||||||
|
|
||||||
JData obj();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME:
|
|
||||||
private record ReadTrackingInternalCrapSource(JDataVersionedWrapper wrapped) implements ReadTrackingInternalCrap {
|
|
||||||
@Override
|
|
||||||
public boolean fromSource() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JData obj() {
|
|
||||||
return wrapped.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private record ReadTrackingInternalCrapTx(JData obj) implements ReadTrackingInternalCrap {
|
|
||||||
@Override
|
|
||||||
public boolean fromSource() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TransactionImpl implements TransactionPrivate {
|
|
||||||
private final Map<JObjectKey, Optional<JDataVersionedWrapper>> _readSet = new HashMap<>();
|
|
||||||
private final NavigableMap<JObjectKey, TxRecord.TxObjectRecord<?>> _writes = new TreeMap<>();
|
|
||||||
private final List<Runnable> _onCommit = new LinkedList<>();
|
|
||||||
private final List<Runnable> _onFlush = new LinkedList<>();
|
|
||||||
private final HashSet<JObjectKey> _knownNew = new HashSet<>();
|
|
||||||
private final Snapshot<JObjectKey, JDataVersionedWrapper> _snapshot;
|
|
||||||
private boolean _closed = false;
|
|
||||||
|
|
||||||
private boolean _writeTrack = false;
|
|
||||||
private Map<JObjectKey, TxRecord.TxObjectRecord<?>> _newWrites = new HashMap<>();
|
|
||||||
|
|
||||||
private TransactionImpl() {
|
|
||||||
_snapshot = writebackObjectPersistentStore.getSnapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCommit(Runnable runnable) {
|
|
||||||
_onCommit.add(runnable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFlush(Runnable runnable) {
|
|
||||||
_onFlush.add(runnable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Runnable> getOnCommit() {
|
|
||||||
return Collections.unmodifiableCollection(_onCommit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Snapshot<JObjectKey, JDataVersionedWrapper> snapshot() {
|
|
||||||
return _snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Runnable> getOnFlush() {
|
|
||||||
return Collections.unmodifiableCollection(_onFlush);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends JData> Optional<T> getFromSource(Class<T> type, JObjectKey key) {
|
|
||||||
if (_knownNew.contains(key)) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _readSet.computeIfAbsent(key, _snapshot::readObject)
|
|
||||||
.map(JDataVersionedWrapper::data)
|
|
||||||
.map(type::cast);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends JData> Optional<T> get(Class<T> type, JObjectKey key, LockingStrategy strategy) {
|
|
||||||
return switch (_writes.get(key)) {
|
|
||||||
case TxRecord.TxObjectRecordWrite<?> write -> Optional.of(type.cast(write.data()));
|
|
||||||
case TxRecord.TxObjectRecordDeleted deleted -> Optional.empty();
|
|
||||||
case null -> getFromSource(type, key);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void delete(JObjectKey key) {
|
|
||||||
var record = new TxRecord.TxObjectRecordDeleted(key);
|
|
||||||
if (_writes.put(key, record) instanceof TxRecord.TxObjectRecordDeleted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_writeTrack)
|
|
||||||
_newWrites.put(key, record);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CloseableKvIterator<JObjectKey, JData> getIterator(IteratorStart start, JObjectKey key) {
|
|
||||||
Log.tracev("Getting tx iterator with start={0}, key={1}", start, key);
|
|
||||||
return new ReadTrackingIterator(new TombstoneSkippingIterator<JObjectKey, ReadTrackingInternalCrap>(start, key,
|
|
||||||
ListUtils.prependAndMap(
|
|
||||||
new MappingKvIterator<>(new NavigableMapKvIterator<>(_writes, start, key),
|
|
||||||
t -> switch (t) {
|
|
||||||
case TxRecord.TxObjectRecordWrite<?> write ->
|
|
||||||
new DataWrapper<ReadTrackingInternalCrap>(new ReadTrackingInternalCrapTx(write.data()));
|
|
||||||
case TxRecord.TxObjectRecordDeleted deleted ->
|
|
||||||
new TombstoneImpl<ReadTrackingInternalCrap>();
|
|
||||||
case null, default -> null;
|
|
||||||
}),
|
|
||||||
_snapshot.getIterator(start, key),
|
|
||||||
itin -> new MappingKvIterator<JObjectKey, MaybeTombstone<JDataVersionedWrapper>, MaybeTombstone<ReadTrackingInternalCrap>>(itin,
|
|
||||||
d -> switch (d) {
|
|
||||||
case Data<JDataVersionedWrapper> w ->
|
|
||||||
new DataWrapper<>(new ReadTrackingInternalCrapSource(w.value()));
|
|
||||||
case Tombstone<JDataVersionedWrapper> t -> new TombstoneImpl<>();
|
|
||||||
case null, default -> null;
|
|
||||||
}))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void put(JData obj) {
|
|
||||||
var key = obj.key();
|
|
||||||
var read = _readSet.get(key);
|
|
||||||
if (read != null && (read.map(JDataVersionedWrapper::data).orElse(null) == obj)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var record = new TxRecord.TxObjectRecordWrite<>(obj);
|
|
||||||
_writes.put(key, record);
|
|
||||||
if (_writeTrack)
|
|
||||||
_newWrites.put(key, record);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putNew(JData obj) {
|
|
||||||
var key = obj.key();
|
|
||||||
_knownNew.add(key);
|
|
||||||
|
|
||||||
var record = new TxRecord.TxObjectRecordWrite<>(obj);
|
|
||||||
_writes.put(key, record);
|
|
||||||
if (_writeTrack)
|
|
||||||
_newWrites.put(key, record);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<TxRecord.TxObjectRecord<?>> drainNewWrites() {
|
|
||||||
if (!_writeTrack) {
|
|
||||||
_writeTrack = true;
|
|
||||||
return Collections.unmodifiableCollection(_writes.values());
|
|
||||||
}
|
|
||||||
var ret = _newWrites;
|
|
||||||
_newWrites = new HashMap<>();
|
|
||||||
return ret.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<JObjectKey, Optional<JDataVersionedWrapper>> reads() {
|
|
||||||
return _readSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<JObjectKey> knownNew() {
|
|
||||||
return _knownNew;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
if (_closed) return;
|
|
||||||
_closed = true;
|
|
||||||
_snapshot.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ReadTrackingIterator implements CloseableKvIterator<JObjectKey, JData> {
|
|
||||||
private final CloseableKvIterator<JObjectKey, ReadTrackingInternalCrap> _backing;
|
|
||||||
|
|
||||||
public ReadTrackingIterator(CloseableKvIterator<JObjectKey, ReadTrackingInternalCrap> backing) {
|
|
||||||
_backing = backing;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JObjectKey peekNextKey() {
|
|
||||||
return _backing.peekNextKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void skip() {
|
|
||||||
_backing.skip();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JObjectKey peekPrevKey() {
|
|
||||||
return _backing.peekPrevKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<JObjectKey, JData> prev() {
|
|
||||||
var got = _backing.prev();
|
|
||||||
if (got.getValue() instanceof ReadTrackingInternalCrapSource(JDataVersionedWrapper wrapped)) {
|
|
||||||
_readSet.putIfAbsent(got.getKey(), Optional.of(wrapped));
|
|
||||||
}
|
|
||||||
return Pair.of(got.getKey(), got.getValue().obj());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasPrev() {
|
|
||||||
return _backing.hasPrev();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void skipPrev() {
|
|
||||||
_backing.skipPrev();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
_backing.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
return _backing.hasNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<JObjectKey, JData> next() {
|
|
||||||
var got = _backing.next();
|
|
||||||
if (got.getValue() instanceof ReadTrackingInternalCrapSource(JDataVersionedWrapper wrapped)) {
|
|
||||||
_readSet.putIfAbsent(got.getKey(), Optional.of(wrapped));
|
|
||||||
}
|
|
||||||
return Pair.of(got.getKey(), got.getValue().obj());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.usatiuk.objects.transaction;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public interface TransactionHandlePrivate extends TransactionHandle {
|
|
||||||
Collection<Runnable> getOnFlush();
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
package com.usatiuk.objects.transaction;
|
||||||
|
|
||||||
|
import com.usatiuk.objects.JData;
|
||||||
|
import com.usatiuk.objects.JDataVersionedWrapper;
|
||||||
|
import com.usatiuk.objects.JObjectKey;
|
||||||
|
import com.usatiuk.objects.iterators.*;
|
||||||
|
import com.usatiuk.objects.snapshot.Snapshot;
|
||||||
|
import com.usatiuk.utils.ListUtils;
|
||||||
|
import io.quarkus.logging.Log;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
class TransactionImpl implements Transaction, AutoCloseable {
|
||||||
|
private final Map<JObjectKey, Optional<JDataVersionedWrapper>> _readSet = new HashMap<>();
|
||||||
|
private final NavigableMap<JObjectKey, TxRecord.TxObjectRecord<?>> _writes = new TreeMap<>();
|
||||||
|
private final List<Runnable> _onCommit = new LinkedList<>();
|
||||||
|
private final List<Runnable> _onFlush = new LinkedList<>();
|
||||||
|
private final HashSet<JObjectKey> _knownNew = new HashSet<>();
|
||||||
|
private final Snapshot<JObjectKey, JDataVersionedWrapper> _snapshot;
|
||||||
|
private boolean _closed = false;
|
||||||
|
|
||||||
|
private boolean _writeTrack = false;
|
||||||
|
private Map<JObjectKey, TxRecord.TxObjectRecord<?>> _newWrites = new HashMap<>();
|
||||||
|
|
||||||
|
private interface ReadTrackingInternalCrap {
|
||||||
|
boolean fromSource();
|
||||||
|
|
||||||
|
JData obj();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME:
|
||||||
|
private record ReadTrackingInternalCrapSource(JDataVersionedWrapper wrapped) implements ReadTrackingInternalCrap {
|
||||||
|
@Override
|
||||||
|
public boolean fromSource() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JData obj() {
|
||||||
|
return wrapped.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ReadTrackingInternalCrapTx(JData obj) implements ReadTrackingInternalCrap {
|
||||||
|
@Override
|
||||||
|
public boolean fromSource() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionImpl(Snapshot<JObjectKey, JDataVersionedWrapper> snapshot) {
|
||||||
|
_snapshot = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCommit(Runnable runnable) {
|
||||||
|
_onCommit.add(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFlush(Runnable runnable) {
|
||||||
|
_onFlush.add(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<Runnable> getOnCommit() {
|
||||||
|
return Collections.unmodifiableCollection(_onCommit);
|
||||||
|
}
|
||||||
|
|
||||||
|
Snapshot<JObjectKey, JDataVersionedWrapper> snapshot() {
|
||||||
|
return _snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<Runnable> getOnFlush() {
|
||||||
|
return Collections.unmodifiableCollection(_onFlush);
|
||||||
|
}
|
||||||
|
|
||||||
|
<T extends JData> Optional<T> getFromSource(Class<T> type, JObjectKey key) {
|
||||||
|
if (_knownNew.contains(key)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _readSet.computeIfAbsent(key, _snapshot::readObject)
|
||||||
|
.map(JDataVersionedWrapper::data)
|
||||||
|
.map(type::cast);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends JData> Optional<T> get(Class<T> type, JObjectKey key) {
|
||||||
|
return switch (_writes.get(key)) {
|
||||||
|
case TxRecord.TxObjectRecordWrite<?> write -> Optional.of(type.cast(write.data()));
|
||||||
|
case TxRecord.TxObjectRecordDeleted deleted -> Optional.empty();
|
||||||
|
case null -> getFromSource(type, key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(JObjectKey key) {
|
||||||
|
var record = new TxRecord.TxObjectRecordDeleted(key);
|
||||||
|
if (_writes.put(key, record) instanceof TxRecord.TxObjectRecordDeleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_writeTrack)
|
||||||
|
_newWrites.put(key, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CloseableKvIterator<JObjectKey, JData> getIterator(IteratorStart start, JObjectKey key) {
|
||||||
|
Log.tracev("Getting tx iterator with start={0}, key={1}", start, key);
|
||||||
|
return new ReadTrackingIterator(new TombstoneSkippingIterator<JObjectKey, ReadTrackingInternalCrap>(start, key,
|
||||||
|
ListUtils.prependAndMap(
|
||||||
|
new MappingKvIterator<>(new NavigableMapKvIterator<>(_writes, start, key),
|
||||||
|
t -> switch (t) {
|
||||||
|
case TxRecord.TxObjectRecordWrite<?> write ->
|
||||||
|
new DataWrapper<ReadTrackingInternalCrap>(new ReadTrackingInternalCrapTx(write.data()));
|
||||||
|
case TxRecord.TxObjectRecordDeleted deleted ->
|
||||||
|
new TombstoneImpl<ReadTrackingInternalCrap>();
|
||||||
|
case null, default -> null;
|
||||||
|
}),
|
||||||
|
_snapshot.getIterator(start, key),
|
||||||
|
itin -> new MappingKvIterator<JObjectKey, MaybeTombstone<JDataVersionedWrapper>, MaybeTombstone<ReadTrackingInternalCrap>>(itin,
|
||||||
|
d -> switch (d) {
|
||||||
|
case Data<JDataVersionedWrapper> w ->
|
||||||
|
new DataWrapper<>(new ReadTrackingInternalCrapSource(w.value()));
|
||||||
|
case Tombstone<JDataVersionedWrapper> t -> new TombstoneImpl<>();
|
||||||
|
case null, default -> null;
|
||||||
|
}))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(JData obj) {
|
||||||
|
var key = obj.key();
|
||||||
|
var read = _readSet.get(key);
|
||||||
|
if (read != null && (read.map(JDataVersionedWrapper::data).orElse(null) == obj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var record = new TxRecord.TxObjectRecordWrite<>(obj);
|
||||||
|
_writes.put(key, record);
|
||||||
|
if (_writeTrack)
|
||||||
|
_newWrites.put(key, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putNew(JData obj) {
|
||||||
|
var key = obj.key();
|
||||||
|
_knownNew.add(key);
|
||||||
|
|
||||||
|
var record = new TxRecord.TxObjectRecordWrite<>(obj);
|
||||||
|
_writes.put(key, record);
|
||||||
|
if (_writeTrack)
|
||||||
|
_newWrites.put(key, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<TxRecord.TxObjectRecord<?>> drainNewWrites() {
|
||||||
|
if (!_writeTrack) {
|
||||||
|
_writeTrack = true;
|
||||||
|
return Collections.unmodifiableCollection(_writes.values());
|
||||||
|
}
|
||||||
|
var ret = _newWrites;
|
||||||
|
_newWrites = new HashMap<>();
|
||||||
|
return ret.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<JObjectKey, Optional<JDataVersionedWrapper>> reads() {
|
||||||
|
return _readSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<JObjectKey> knownNew() {
|
||||||
|
return _knownNew;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (_closed) return;
|
||||||
|
_closed = true;
|
||||||
|
_snapshot.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReadTrackingIterator implements CloseableKvIterator<JObjectKey, JData> {
|
||||||
|
private final CloseableKvIterator<JObjectKey, ReadTrackingInternalCrap> _backing;
|
||||||
|
|
||||||
|
public ReadTrackingIterator(CloseableKvIterator<JObjectKey, ReadTrackingInternalCrap> backing) {
|
||||||
|
_backing = backing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JObjectKey peekNextKey() {
|
||||||
|
return _backing.peekNextKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void skip() {
|
||||||
|
_backing.skip();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JObjectKey peekPrevKey() {
|
||||||
|
return _backing.peekPrevKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<JObjectKey, JData> prev() {
|
||||||
|
var got = _backing.prev();
|
||||||
|
if (got.getValue() instanceof ReadTrackingInternalCrapSource(JDataVersionedWrapper wrapped)) {
|
||||||
|
_readSet.putIfAbsent(got.getKey(), Optional.of(wrapped));
|
||||||
|
}
|
||||||
|
return Pair.of(got.getKey(), got.getValue().obj());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPrev() {
|
||||||
|
return _backing.hasPrev();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void skipPrev() {
|
||||||
|
_backing.skipPrev();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
_backing.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return _backing.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pair<JObjectKey, JData> next() {
|
||||||
|
var got = _backing.next();
|
||||||
|
if (got.getValue() instanceof ReadTrackingInternalCrapSource(JDataVersionedWrapper wrapped)) {
|
||||||
|
_readSet.putIfAbsent(got.getKey(), Optional.of(wrapped));
|
||||||
|
}
|
||||||
|
return Pair.of(got.getKey(), got.getValue().obj());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.usatiuk.objects.transaction;
|
package com.usatiuk.objects.transaction;
|
||||||
|
|
||||||
import com.usatiuk.utils.VoidFn;
|
|
||||||
import io.quarkus.logging.Log;
|
import io.quarkus.logging.Log;
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -41,9 +40,9 @@ public interface TransactionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default TransactionHandle runTries(VoidFn fn, int tries, boolean nest) {
|
default TransactionHandle runTries(Runnable fn, int tries, boolean nest) {
|
||||||
if (!nest && current() != null) {
|
if (!nest && current() != null) {
|
||||||
fn.apply();
|
fn.run();
|
||||||
return new TransactionHandle() {
|
return new TransactionHandle() {
|
||||||
@Override
|
@Override
|
||||||
public void onFlush(Runnable runnable) {
|
public void onFlush(Runnable runnable) {
|
||||||
@@ -56,7 +55,7 @@ public interface TransactionManager {
|
|||||||
begin();
|
begin();
|
||||||
boolean commit = false;
|
boolean commit = false;
|
||||||
try {
|
try {
|
||||||
fn.apply();
|
fn.run();
|
||||||
commit = true;
|
commit = true;
|
||||||
var ret = commit();
|
var ret = commit();
|
||||||
return ret;
|
return ret;
|
||||||
@@ -80,11 +79,11 @@ public interface TransactionManager {
|
|||||||
return runTries(supplier, tries, false);
|
return runTries(supplier, tries, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
default TransactionHandle runTries(VoidFn fn, int tries) {
|
default TransactionHandle runTries(Runnable fn, int tries) {
|
||||||
return runTries(fn, tries, false);
|
return runTries(fn, tries, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
default TransactionHandle run(VoidFn fn, boolean nest) {
|
default TransactionHandle run(Runnable fn, boolean nest) {
|
||||||
return runTries(fn, 10, nest);
|
return runTries(fn, 10, nest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +91,7 @@ public interface TransactionManager {
|
|||||||
return runTries(supplier, 10, nest);
|
return runTries(supplier, 10, nest);
|
||||||
}
|
}
|
||||||
|
|
||||||
default TransactionHandle run(VoidFn fn) {
|
default TransactionHandle run(Runnable fn) {
|
||||||
return run(fn, false);
|
return run(fn, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +99,7 @@ public interface TransactionManager {
|
|||||||
return run(supplier, false);
|
return run(supplier, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
default void executeTx(VoidFn fn) {
|
default void executeTx(Runnable fn) {
|
||||||
run(fn, false);
|
run(fn, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ import java.util.Stack;
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class TransactionManagerImpl implements TransactionManager {
|
public class TransactionManagerImpl implements TransactionManager {
|
||||||
private static final ThreadLocal<Stack<TransactionPrivate>> _currentTransaction = ThreadLocal.withInitial(Stack::new);
|
private static final ThreadLocal<Stack<TransactionImpl>> _currentTransaction = ThreadLocal.withInitial(Stack::new);
|
||||||
@Inject
|
@Inject
|
||||||
JObjectManager jObjectManager;
|
TransactionService transactionService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void begin() {
|
public void begin() {
|
||||||
Log.trace("Starting transaction");
|
Log.trace("Starting transaction");
|
||||||
var tx = jObjectManager.createTransaction();
|
var tx = transactionService.createTransaction();
|
||||||
_currentTransaction.get().push(tx);
|
_currentTransaction.get().push(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ public class TransactionManagerImpl implements TransactionManager {
|
|||||||
|
|
||||||
Pair<Collection<Runnable>, TransactionHandle> ret;
|
Pair<Collection<Runnable>, TransactionHandle> ret;
|
||||||
try {
|
try {
|
||||||
ret = jObjectManager.commit(peeked);
|
ret = transactionService.commit(peeked);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.trace("Transaction commit failed", e);
|
Log.trace("Transaction commit failed", e);
|
||||||
throw e;
|
throw e;
|
||||||
@@ -64,7 +64,7 @@ public class TransactionManagerImpl implements TransactionManager {
|
|||||||
var peeked = stack.peek();
|
var peeked = stack.peek();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
jObjectManager.rollback(peeked);
|
transactionService.rollback(peeked);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.error("Transaction rollback failed", e);
|
Log.error("Transaction rollback failed", e);
|
||||||
throw e;
|
throw e;
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.usatiuk.objects.transaction;
|
|
||||||
|
|
||||||
import com.usatiuk.objects.JData;
|
|
||||||
import com.usatiuk.objects.JDataVersionedWrapper;
|
|
||||||
import com.usatiuk.objects.JObjectKey;
|
|
||||||
import com.usatiuk.objects.snapshot.Snapshot;
|
|
||||||
import com.usatiuk.utils.AutoCloseableNoThrow;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
// The transaction interface actually used by user code to retrieve objects
|
|
||||||
public interface TransactionPrivate extends Transaction, TransactionHandlePrivate, AutoCloseableNoThrow {
|
|
||||||
Collection<TxRecord.TxObjectRecord<?>> drainNewWrites();
|
|
||||||
|
|
||||||
Map<JObjectKey, Optional<JDataVersionedWrapper>> reads();
|
|
||||||
|
|
||||||
Set<JObjectKey> knownNew();
|
|
||||||
|
|
||||||
<T extends JData> Optional<T> getFromSource(Class<T> type, JObjectKey key);
|
|
||||||
|
|
||||||
Collection<Runnable> getOnCommit();
|
|
||||||
|
|
||||||
Snapshot<JObjectKey, JDataVersionedWrapper> snapshot();
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import com.usatiuk.objects.JObjectKey;
|
|||||||
import com.usatiuk.objects.snapshot.Snapshot;
|
import com.usatiuk.objects.snapshot.Snapshot;
|
||||||
import com.usatiuk.objects.stores.WritebackObjectPersistentStore;
|
import com.usatiuk.objects.stores.WritebackObjectPersistentStore;
|
||||||
import com.usatiuk.utils.AutoCloseableNoThrow;
|
import com.usatiuk.utils.AutoCloseableNoThrow;
|
||||||
|
import com.usatiuk.utils.DataLocker;
|
||||||
import io.quarkus.logging.Log;
|
import io.quarkus.logging.Log;
|
||||||
import io.quarkus.runtime.StartupEvent;
|
import io.quarkus.runtime.StartupEvent;
|
||||||
import jakarta.annotation.Priority;
|
import jakarta.annotation.Priority;
|
||||||
@@ -21,21 +22,19 @@ import java.util.function.Consumer;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class JObjectManager {
|
public class TransactionService {
|
||||||
private static final List<PreCommitTxHook> _preCommitTxHooks;
|
private static final List<PreCommitTxHook> _preCommitTxHooks;
|
||||||
@Inject
|
@Inject
|
||||||
WritebackObjectPersistentStore writebackObjectPersistentStore;
|
WritebackObjectPersistentStore writebackObjectPersistentStore;
|
||||||
@Inject
|
|
||||||
TransactionFactory transactionFactory;
|
|
||||||
@Inject
|
|
||||||
LockManager lockManager;
|
|
||||||
private boolean _ready = false;
|
private boolean _ready = false;
|
||||||
|
private final DataLocker _objLocker = new DataLocker();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
_preCommitTxHooks = List.copyOf(CDI.current().select(PreCommitTxHook.class).stream().sorted(Comparator.comparingInt(PreCommitTxHook::getPriority)).toList());
|
_preCommitTxHooks = List.copyOf(CDI.current().select(PreCommitTxHook.class).stream().sorted(Comparator.comparingInt(PreCommitTxHook::getPriority)).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
JObjectManager(Instance<PreCommitTxHook> preCommitTxHooks) {
|
TransactionService(Instance<PreCommitTxHook> preCommitTxHooks) {
|
||||||
Log.debugv("Pre-commit hooks: {0}", String.join("->", _preCommitTxHooks.stream().map(Objects::toString).toList()));
|
Log.debugv("Pre-commit hooks: {0}", String.join("->", _preCommitTxHooks.stream().map(Objects::toString).toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,14 +46,14 @@ public class JObjectManager {
|
|||||||
_ready = true;
|
_ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransactionPrivate createTransaction() {
|
public TransactionImpl createTransaction() {
|
||||||
verifyReady();
|
verifyReady();
|
||||||
var tx = transactionFactory.createTransaction();
|
var tx = new TransactionImpl(writebackObjectPersistentStore.getSnapshot());
|
||||||
Log.tracev("Created transaction with snapshotId={0}", tx.snapshot().id());
|
Log.tracev("Created transaction with snapshotId={0}", tx.snapshot().id());
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<Collection<Runnable>, TransactionHandle> commit(TransactionPrivate tx) {
|
public Pair<Collection<Runnable>, TransactionHandle> commit(TransactionImpl tx) {
|
||||||
verifyReady();
|
verifyReady();
|
||||||
var writes = new HashMap<JObjectKey, TxRecord.TxObjectRecord<?>>();
|
var writes = new HashMap<JObjectKey, TxRecord.TxObjectRecord<?>>();
|
||||||
Snapshot<JObjectKey, JDataVersionedWrapper> commitSnapshot = null;
|
Snapshot<JObjectKey, JDataVersionedWrapper> commitSnapshot = null;
|
||||||
@@ -162,7 +161,7 @@ public class JObjectManager {
|
|||||||
for (var key : toLock) {
|
for (var key : toLock) {
|
||||||
if (tx.knownNew().contains(key))
|
if (tx.knownNew().contains(key))
|
||||||
continue;
|
continue;
|
||||||
var lock = lockManager.lockObject(key);
|
var lock = _objLocker.lock(key);
|
||||||
toUnlock.add(lock);
|
toUnlock.add(lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +257,7 @@ public class JObjectManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rollback(TransactionPrivate tx) {
|
public void rollback(TransactionImpl tx) {
|
||||||
verifyReady();
|
verifyReady();
|
||||||
tx.close();
|
tx.close();
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@ dhfs.objects.lru.print-stats=false
|
|||||||
dhfs.objects.lock_timeout_secs=15
|
dhfs.objects.lock_timeout_secs=15
|
||||||
dhfs.objects.persistence.files.root=${HOME}/dhfs_default/data/objs
|
dhfs.objects.persistence.files.root=${HOME}/dhfs_default/data/objs
|
||||||
dhfs.objects.persistence.snapshot-extra-checks=false
|
dhfs.objects.persistence.snapshot-extra-checks=false
|
||||||
dhfs.objects.transaction.never-lock=true
|
|
||||||
dhfs.objects.last-seen.update=60
|
dhfs.objects.last-seen.update=60
|
||||||
dhfs.objects.last-seen.timeout=43200
|
dhfs.objects.last-seen.timeout=43200
|
||||||
quarkus.log.category."com.usatiuk.objects.iterators".level=INFO
|
quarkus.log.category."com.usatiuk.objects.iterators".level=INFO
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ package com.usatiuk.objects;
|
|||||||
|
|
||||||
import com.usatiuk.objects.data.Parent;
|
import com.usatiuk.objects.data.Parent;
|
||||||
import com.usatiuk.objects.iterators.IteratorStart;
|
import com.usatiuk.objects.iterators.IteratorStart;
|
||||||
import com.usatiuk.objects.transaction.LockingStrategy;
|
|
||||||
import com.usatiuk.objects.transaction.Transaction;
|
import com.usatiuk.objects.transaction.Transaction;
|
||||||
import com.usatiuk.objects.transaction.TransactionManager;
|
import com.usatiuk.objects.transaction.TransactionManager;
|
||||||
import io.quarkus.logging.Log;
|
import io.quarkus.logging.Log;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.EnumSource;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -151,12 +148,12 @@ public abstract class ObjectsTestImpl {
|
|||||||
});
|
});
|
||||||
|
|
||||||
txm.run(() -> {
|
txm.run(() -> {
|
||||||
var parent = curTx.get(Parent.class, JObjectKey.of(testInfo.getDisplayName() + "Parent3"), LockingStrategy.OPTIMISTIC).orElse(null);
|
var parent = curTx.get(Parent.class, JObjectKey.of(testInfo.getDisplayName() + "Parent3")).orElse(null);
|
||||||
Assertions.assertEquals("John", parent.name());
|
Assertions.assertEquals("John", parent.name());
|
||||||
curTx.put(parent.withName("John2"));
|
curTx.put(parent.withName("John2"));
|
||||||
});
|
});
|
||||||
txm.run(() -> {
|
txm.run(() -> {
|
||||||
var parent = curTx.get(Parent.class, JObjectKey.of(testInfo.getDisplayName() + "Parent3"), LockingStrategy.WRITE).orElse(null);
|
var parent = curTx.get(Parent.class, JObjectKey.of(testInfo.getDisplayName() + "Parent3")).orElse(null);
|
||||||
Assertions.assertEquals("John2", parent.name());
|
Assertions.assertEquals("John2", parent.name());
|
||||||
curTx.put(parent.withName("John3"));
|
curTx.put(parent.withName("John3"));
|
||||||
});
|
});
|
||||||
@@ -236,10 +233,9 @@ public abstract class ObjectsTestImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@Test
|
||||||
@EnumSource(LockingStrategy.class)
|
void editConflict(TestInfo testInfo) {
|
||||||
void editConflict(LockingStrategy strategy, TestInfo testInfo) {
|
String key = testInfo.getDisplayName() + "Parent4";
|
||||||
String key = testInfo.getDisplayName() + "Parent4" + strategy.name();
|
|
||||||
txm.run(() -> {
|
txm.run(() -> {
|
||||||
var newParent = new Parent(JObjectKey.of(key), "John3");
|
var newParent = new Parent(JObjectKey.of(key), "John3");
|
||||||
curTx.put(newParent);
|
curTx.put(newParent);
|
||||||
@@ -260,7 +256,7 @@ public abstract class ObjectsTestImpl {
|
|||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
var parent = curTx.get(Parent.class, JObjectKey.of(key), strategy).orElse(null);
|
var parent = curTx.get(Parent.class, JObjectKey.of(key)).orElse(null);
|
||||||
curTx.put(parent.withName("John"));
|
curTx.put(parent.withName("John"));
|
||||||
Log.warn("Thread 1 commit");
|
Log.warn("Thread 1 commit");
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -276,7 +272,7 @@ public abstract class ObjectsTestImpl {
|
|||||||
Log.warn("Thread 2");
|
Log.warn("Thread 2");
|
||||||
barrier.await(); // Ensure thread 2 tx id is larger than thread 1
|
barrier.await(); // Ensure thread 2 tx id is larger than thread 1
|
||||||
txm.runTries(() -> {
|
txm.runTries(() -> {
|
||||||
var parent = curTx.get(Parent.class, JObjectKey.of(key), strategy).orElse(null);
|
var parent = curTx.get(Parent.class, JObjectKey.of(key)).orElse(null);
|
||||||
curTx.put(parent.withName("John2"));
|
curTx.put(parent.withName("John2"));
|
||||||
Log.warn("Thread 2 commit");
|
Log.warn("Thread 2 commit");
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -317,10 +313,9 @@ public abstract class ObjectsTestImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@Test
|
||||||
@EnumSource(LockingStrategy.class)
|
void editConflict2(TestInfo testInfo) {
|
||||||
void editConflict2(LockingStrategy strategy, TestInfo testInfo) {
|
String key = testInfo.getDisplayName() + "EditConflict2";
|
||||||
String key = testInfo.getDisplayName() + "EditConflict2" + strategy.name();
|
|
||||||
txm.run(() -> {
|
txm.run(() -> {
|
||||||
var newParent = new Parent(JObjectKey.of(key), "John3");
|
var newParent = new Parent(JObjectKey.of(key), "John3");
|
||||||
curTx.put(newParent);
|
curTx.put(newParent);
|
||||||
@@ -341,7 +336,7 @@ public abstract class ObjectsTestImpl {
|
|||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
var parent = curTx.get(Parent.class, JObjectKey.of(key), strategy).orElse(null);
|
var parent = curTx.get(Parent.class, JObjectKey.of(key)).orElse(null);
|
||||||
curTx.put(parent.withName("John"));
|
curTx.put(parent.withName("John"));
|
||||||
Log.warn("Thread 1 commit");
|
Log.warn("Thread 1 commit");
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -362,7 +357,7 @@ public abstract class ObjectsTestImpl {
|
|||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
var parent = curTx.get(Parent.class, JObjectKey.of(key), strategy).orElse(null);
|
var parent = curTx.get(Parent.class, JObjectKey.of(key)).orElse(null);
|
||||||
curTx.put(parent.withName("John2"));
|
curTx.put(parent.withName("John2"));
|
||||||
Log.warn("Thread 2 commit");
|
Log.warn("Thread 2 commit");
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -922,10 +917,8 @@ public abstract class ObjectsTestImpl {
|
|||||||
() -> createGetObject(testInfo),
|
() -> createGetObject(testInfo),
|
||||||
() -> createDeleteObject(testInfo),
|
() -> createDeleteObject(testInfo),
|
||||||
() -> createCreateObject(testInfo),
|
() -> createCreateObject(testInfo),
|
||||||
() -> editConflict(LockingStrategy.WRITE, testInfo),
|
() -> editConflict(testInfo),
|
||||||
() -> editConflict(LockingStrategy.OPTIMISTIC, testInfo),
|
() -> editConflict2(testInfo),
|
||||||
() -> editConflict2(LockingStrategy.WRITE, testInfo),
|
|
||||||
() -> editConflict2(LockingStrategy.OPTIMISTIC, testInfo),
|
|
||||||
() -> snapshotTest1(testInfo),
|
() -> snapshotTest1(testInfo),
|
||||||
() -> snapshotTest2(testInfo),
|
() -> snapshotTest2(testInfo),
|
||||||
() -> snapshotTest3(testInfo),
|
() -> snapshotTest3(testInfo),
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<compiler-plugin.version>3.12.1</compiler-plugin.version>
|
<compiler-plugin.version>3.12.1</compiler-plugin.version>
|
||||||
<!--FIXME-->
|
<!--FIXME-->
|
||||||
<maven.compiler.release></maven.compiler.release>
|
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
@@ -87,6 +86,19 @@
|
|||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.11.2</version>
|
||||||
|
<configuration>
|
||||||
|
<additionalOptions>
|
||||||
|
--add-exports java.base/sun.nio.ch=ALL-UNNAMED
|
||||||
|
--add-exports java.base/jdk.internal.access=ALL-UNNAMED
|
||||||
|
--add-opens=java.base/java.nio=ALL-UNNAMED
|
||||||
|
--enable-preview
|
||||||
|
</additionalOptions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>${quarkus.platform.group-id}</groupId>
|
<groupId>${quarkus.platform.group-id}</groupId>
|
||||||
<artifactId>quarkus-maven-plugin</artifactId>
|
<artifactId>quarkus-maven-plugin</artifactId>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.usatiuk.dhfs.invalidation;
|
|||||||
|
|
||||||
import com.usatiuk.dhfs.peersync.PeerConnectedEventListener;
|
import com.usatiuk.dhfs.peersync.PeerConnectedEventListener;
|
||||||
import com.usatiuk.dhfs.peersync.PeerId;
|
import com.usatiuk.dhfs.peersync.PeerId;
|
||||||
import com.usatiuk.dhfs.peersync.ConnectedPeerManager;
|
import com.usatiuk.dhfs.peersync.ReachablePeerManager;
|
||||||
import com.usatiuk.utils.SerializationHelper;
|
import com.usatiuk.utils.SerializationHelper;
|
||||||
import io.quarkus.logging.Log;
|
import io.quarkus.logging.Log;
|
||||||
import io.quarkus.runtime.ShutdownEvent;
|
import io.quarkus.runtime.ShutdownEvent;
|
||||||
@@ -24,7 +24,7 @@ import java.nio.file.Paths;
|
|||||||
public class DeferredInvalidationQueueService implements PeerConnectedEventListener {
|
public class DeferredInvalidationQueueService implements PeerConnectedEventListener {
|
||||||
private static final String dataFileName = "invqueue";
|
private static final String dataFileName = "invqueue";
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager remoteHostManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
@Inject
|
@Inject
|
||||||
InvalidationQueueService invalidationQueueService;
|
InvalidationQueueService invalidationQueueService;
|
||||||
@ConfigProperty(name = "dhfs.objects.persistence.files.root")
|
@ConfigProperty(name = "dhfs.objects.persistence.files.root")
|
||||||
@@ -63,7 +63,7 @@ public class DeferredInvalidationQueueService implements PeerConnectedEventListe
|
|||||||
@Scheduled(every = "15s", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
|
@Scheduled(every = "15s", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
|
||||||
@Blocking
|
@Blocking
|
||||||
void periodicReturn() {
|
void periodicReturn() {
|
||||||
for (var reachable : remoteHostManager.getAvailableHosts())
|
for (var reachable : reachablePeerManager.getAvailableHosts())
|
||||||
returnForHost(reachable);
|
returnForHost(reachable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.usatiuk.dhfs.invalidation;
|
|||||||
|
|
||||||
import com.usatiuk.dhfs.peersync.PeerId;
|
import com.usatiuk.dhfs.peersync.PeerId;
|
||||||
import com.usatiuk.dhfs.peersync.PeerInfoService;
|
import com.usatiuk.dhfs.peersync.PeerInfoService;
|
||||||
import com.usatiuk.dhfs.peersync.ConnectedPeerManager;
|
import com.usatiuk.dhfs.peersync.ReachablePeerManager;
|
||||||
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
||||||
import com.usatiuk.dhfs.rpc.RemoteObjectServiceClient;
|
import com.usatiuk.dhfs.rpc.RemoteObjectServiceClient;
|
||||||
import com.usatiuk.objects.JData;
|
import com.usatiuk.objects.JData;
|
||||||
@@ -37,7 +37,7 @@ public class InvalidationQueueService {
|
|||||||
private final AtomicReference<ConcurrentHashSet<JObjectKey>> _toAllQueue = new AtomicReference<>(new ConcurrentHashSet<>());
|
private final AtomicReference<ConcurrentHashSet<JObjectKey>> _toAllQueue = new AtomicReference<>(new ConcurrentHashSet<>());
|
||||||
private final DataLocker _locker = new DataLocker();
|
private final DataLocker _locker = new DataLocker();
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager remoteHostManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
@Inject
|
@Inject
|
||||||
DeferredInvalidationQueueService deferredInvalidationQueueService;
|
DeferredInvalidationQueueService deferredInvalidationQueueService;
|
||||||
@Inject
|
@Inject
|
||||||
@@ -103,7 +103,7 @@ public class InvalidationQueueService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toAllQueue != null) {
|
if (toAllQueue != null) {
|
||||||
var hostInfo = remoteHostManager.getHostStateSnapshot();
|
var hostInfo = reachablePeerManager.getHostStateSnapshot();
|
||||||
for (var o : toAllQueue) {
|
for (var o : toAllQueue) {
|
||||||
for (var h : hostInfo.available())
|
for (var h : hostInfo.available())
|
||||||
_queue.add(new InvalidationQueueEntry(h, o));
|
_queue.add(new InvalidationQueueEntry(h, o));
|
||||||
@@ -129,7 +129,7 @@ public class InvalidationQueueService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!remoteHostManager.isReachable(e.peer())) {
|
if (!reachablePeerManager.isReachable(e.peer())) {
|
||||||
deferredInvalidationQueueService.defer(e);
|
deferredInvalidationQueueService.defer(e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -210,14 +210,14 @@ public class InvalidationQueueService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void pushInvalidationToOne(InvalidationQueueEntry entry) {
|
void pushInvalidationToOne(InvalidationQueueEntry entry) {
|
||||||
if (remoteHostManager.isReachable(entry.peer()))
|
if (reachablePeerManager.isReachable(entry.peer()))
|
||||||
_queue.add(entry);
|
_queue.add(entry);
|
||||||
else
|
else
|
||||||
deferredInvalidationQueueService.defer(entry);
|
deferredInvalidationQueueService.defer(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushInvalidationToOneNoDelay(InvalidationQueueEntry entry) {
|
void pushInvalidationToOneNoDelay(InvalidationQueueEntry entry) {
|
||||||
if (remoteHostManager.isReachable(entry.peer()))
|
if (reachablePeerManager.isReachable(entry.peer()))
|
||||||
_queue.addNoDelay(entry);
|
_queue.addNoDelay(entry);
|
||||||
else
|
else
|
||||||
deferredInvalidationQueueService.defer(entry);
|
deferredInvalidationQueueService.defer(entry);
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.usatiuk.dhfs.peersync.PeerInfoService;
|
|||||||
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
||||||
import com.usatiuk.kleppmanntree.*;
|
import com.usatiuk.kleppmanntree.*;
|
||||||
import com.usatiuk.objects.JObjectKey;
|
import com.usatiuk.objects.JObjectKey;
|
||||||
import com.usatiuk.objects.transaction.LockingStrategy;
|
|
||||||
import com.usatiuk.objects.transaction.Transaction;
|
import com.usatiuk.objects.transaction.Transaction;
|
||||||
import com.usatiuk.objects.transaction.TransactionManager;
|
import com.usatiuk.objects.transaction.TransactionManager;
|
||||||
import io.quarkus.logging.Log;
|
import io.quarkus.logging.Log;
|
||||||
@@ -39,9 +38,9 @@ public class JKleppmannTreeManager {
|
|||||||
@Inject
|
@Inject
|
||||||
PersistentPeerDataService persistentPeerDataService;
|
PersistentPeerDataService persistentPeerDataService;
|
||||||
|
|
||||||
public JKleppmannTree getTree(JObjectKey name, LockingStrategy lockingStrategy, Supplier<JKleppmannTreeNodeMeta> rootNodeSupplier) {
|
public JKleppmannTree getTree(JObjectKey name, Supplier<JKleppmannTreeNodeMeta> rootNodeSupplier) {
|
||||||
return txManager.executeTx(() -> {
|
return txManager.executeTx(() -> {
|
||||||
var data = curTx.get(JKleppmannTreePersistentData.class, name, lockingStrategy).orElse(null);
|
var data = curTx.get(JKleppmannTreePersistentData.class, name).orElse(null);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
data = new JKleppmannTreePersistentData(
|
data = new JKleppmannTreePersistentData(
|
||||||
name,
|
name,
|
||||||
@@ -66,18 +65,11 @@ public class JKleppmannTreeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Optional<JKleppmannTree> getTree(JObjectKey name) {
|
public Optional<JKleppmannTree> getTree(JObjectKey name) {
|
||||||
return getTree(name, LockingStrategy.WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<JKleppmannTree> getTree(JObjectKey name, LockingStrategy lockingStrategy) {
|
|
||||||
return txManager.executeTx(() -> {
|
return txManager.executeTx(() -> {
|
||||||
return curTx.get(JKleppmannTreePersistentData.class, name, lockingStrategy).map(JKleppmannTree::new);
|
return curTx.get(JKleppmannTreePersistentData.class, name).map(JKleppmannTree::new);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public JKleppmannTree getTree(JObjectKey name, Supplier<JKleppmannTreeNodeMeta> rootNodeSupplier) {
|
|
||||||
return getTree(name, LockingStrategy.WRITE, rootNodeSupplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class JKleppmannTree {
|
public class JKleppmannTree {
|
||||||
private final KleppmannTree<Long, PeerId, JKleppmannTreeNodeMeta, JObjectKey> _tree;
|
private final KleppmannTree<Long, PeerId, JKleppmannTreeNodeMeta, JObjectKey> _tree;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.usatiuk.dhfs.jkleppmanntree.structs.JKleppmannTreeNodeHolder;
|
|||||||
import com.usatiuk.dhfs.peersync.structs.JKleppmannTreeNodeMetaPeer;
|
import com.usatiuk.dhfs.peersync.structs.JKleppmannTreeNodeMetaPeer;
|
||||||
import com.usatiuk.dhfs.remoteobj.RemoteTransaction;
|
import com.usatiuk.dhfs.remoteobj.RemoteTransaction;
|
||||||
import com.usatiuk.objects.JObjectKey;
|
import com.usatiuk.objects.JObjectKey;
|
||||||
import com.usatiuk.objects.transaction.LockingStrategy;
|
|
||||||
import com.usatiuk.objects.transaction.Transaction;
|
import com.usatiuk.objects.transaction.Transaction;
|
||||||
import com.usatiuk.objects.transaction.TransactionManager;
|
import com.usatiuk.objects.transaction.TransactionManager;
|
||||||
import io.quarkus.logging.Log;
|
import io.quarkus.logging.Log;
|
||||||
@@ -29,14 +28,10 @@ public class PeerInfoService {
|
|||||||
@Inject
|
@Inject
|
||||||
RemoteTransaction remoteTx;
|
RemoteTransaction remoteTx;
|
||||||
|
|
||||||
private JKleppmannTreeManager.JKleppmannTree getTreeW() {
|
private JKleppmannTreeManager.JKleppmannTree getTree() {
|
||||||
return jKleppmannTreeManager.getTree(TREE_KEY, () -> null);
|
return jKleppmannTreeManager.getTree(TREE_KEY, () -> null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JKleppmannTreeManager.JKleppmannTree getTreeR() {
|
|
||||||
return jKleppmannTreeManager.getTree(TREE_KEY, LockingStrategy.OPTIMISTIC, () -> null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<PeerInfo> getPeerInfoImpl(JObjectKey key) {
|
public Optional<PeerInfo> getPeerInfoImpl(JObjectKey key) {
|
||||||
return jObjectTxManager.run(() -> {
|
return jObjectTxManager.run(() -> {
|
||||||
return curTx.get(JKleppmannTreeNodeHolder.class, key).map(JKleppmannTreeNodeHolder::node).flatMap(node -> {
|
return curTx.get(JKleppmannTreeNodeHolder.class, key).map(JKleppmannTreeNodeHolder::node).flatMap(node -> {
|
||||||
@@ -49,7 +44,7 @@ public class PeerInfoService {
|
|||||||
|
|
||||||
public boolean existsPeer(PeerId peer) {
|
public boolean existsPeer(PeerId peer) {
|
||||||
return jObjectTxManager.run(() -> {
|
return jObjectTxManager.run(() -> {
|
||||||
var gotKey = getTreeR().traverse(List.of(JKleppmannTreeNodeMetaPeer.peerIdToNodeId(peer).value()));
|
var gotKey = getTree().traverse(List.of(JKleppmannTreeNodeMetaPeer.peerIdToNodeId(peer).value()));
|
||||||
if (gotKey == null) {
|
if (gotKey == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -59,7 +54,7 @@ public class PeerInfoService {
|
|||||||
|
|
||||||
public Optional<PeerInfo> getPeerInfo(PeerId peer) {
|
public Optional<PeerInfo> getPeerInfo(PeerId peer) {
|
||||||
return jObjectTxManager.run(() -> {
|
return jObjectTxManager.run(() -> {
|
||||||
var gotKey = getTreeR().traverse(List.of(JKleppmannTreeNodeMetaPeer.peerIdToNodeId(peer).value()));
|
var gotKey = getTree().traverse(List.of(JKleppmannTreeNodeMetaPeer.peerIdToNodeId(peer).value()));
|
||||||
if (gotKey == null) {
|
if (gotKey == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
@@ -72,7 +67,7 @@ public class PeerInfoService {
|
|||||||
|
|
||||||
public List<PeerInfo> getPeers() {
|
public List<PeerInfo> getPeers() {
|
||||||
return jObjectTxManager.run(() -> {
|
return jObjectTxManager.run(() -> {
|
||||||
var gotKey = getTreeR().traverse(List.of());
|
var gotKey = getTree().traverse(List.of());
|
||||||
return curTx.get(JKleppmannTreeNodeHolder.class, gotKey).map(JKleppmannTreeNodeHolder::node).map(
|
return curTx.get(JKleppmannTreeNodeHolder.class, gotKey).map(JKleppmannTreeNodeHolder::node).map(
|
||||||
node -> node.children().keySet().stream()
|
node -> node.children().keySet().stream()
|
||||||
.map(JObjectKey::of).map(this::getPeerInfoImpl)
|
.map(JObjectKey::of).map(this::getPeerInfoImpl)
|
||||||
@@ -113,16 +108,16 @@ public class PeerInfoService {
|
|||||||
|
|
||||||
public void putPeer(PeerId id, byte[] cert) {
|
public void putPeer(PeerId id, byte[] cert) {
|
||||||
jObjectTxManager.run(() -> {
|
jObjectTxManager.run(() -> {
|
||||||
var parent = getTreeW().traverse(List.of());
|
var parent = getTree().traverse(List.of());
|
||||||
var newPeerInfo = new PeerInfo(id, cert);
|
var newPeerInfo = new PeerInfo(id, cert);
|
||||||
remoteTx.putData(newPeerInfo);
|
remoteTx.putData(newPeerInfo);
|
||||||
getTreeW().move(parent, new JKleppmannTreeNodeMetaPeer(newPeerInfo.id()), JKleppmannTreeNodeMetaPeer.peerIdToNodeId(newPeerInfo.id()));
|
getTree().move(parent, new JKleppmannTreeNodeMetaPeer(newPeerInfo.id()), JKleppmannTreeNodeMetaPeer.peerIdToNodeId(newPeerInfo.id()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePeer(PeerId id) {
|
public void removePeer(PeerId id) {
|
||||||
jObjectTxManager.run(() -> {
|
jObjectTxManager.run(() -> {
|
||||||
var gotKey = getTreeR().traverse(List.of(JKleppmannTreeNodeMetaPeer.peerIdToNodeId(id).value()));
|
var gotKey = getTree().traverse(List.of(JKleppmannTreeNodeMetaPeer.peerIdToNodeId(id).value()));
|
||||||
if (gotKey == null) {
|
if (gotKey == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -131,7 +126,7 @@ public class PeerInfoService {
|
|||||||
Log.warn("Peer " + id + " not found in the tree");
|
Log.warn("Peer " + id + " not found in the tree");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getTreeW().trash(node.meta(), node.key());
|
getTree().trash(node.meta(), node.key());
|
||||||
curTx.onCommit(persistentPeerDataService::updateCerts);
|
curTx.onCommit(persistentPeerDataService::updateCerts);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
|
|||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class PeerLastSeenUpdater {
|
public class PeerLastSeenUpdater {
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager connectedPeerManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
@Inject
|
@Inject
|
||||||
PeerInfoService peerInfoService;
|
PeerInfoService peerInfoService;
|
||||||
@Inject
|
@Inject
|
||||||
@@ -30,7 +30,7 @@ public class PeerLastSeenUpdater {
|
|||||||
@Scheduled(every = "${dhfs.objects.last-seen.update}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
|
@Scheduled(every = "${dhfs.objects.last-seen.update}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
|
||||||
@Blocking
|
@Blocking
|
||||||
void update() {
|
void update() {
|
||||||
var snapshot = connectedPeerManager.getHostStateSnapshot();
|
var snapshot = reachablePeerManager.getHostStateSnapshot();
|
||||||
for (var a : snapshot.available()) {
|
for (var a : snapshot.available()) {
|
||||||
txm.run(() -> {
|
txm.run(() -> {
|
||||||
var curInfo = remoteTransaction.getData(PeerInfo.class, a.id()).orElse(null);
|
var curInfo = remoteTransaction.getData(PeerInfo.class, a.id()).orElse(null);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class PersistentPeerDataService {
|
|||||||
@Inject
|
@Inject
|
||||||
TransactionManager txm;
|
TransactionManager txm;
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager connectedPeerManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
|
|
||||||
@ConfigProperty(name = "dhfs.peerdiscovery.preset-uuid")
|
@ConfigProperty(name = "dhfs.peerdiscovery.preset-uuid")
|
||||||
Optional<String> presetUuid;
|
Optional<String> presetUuid;
|
||||||
@@ -135,7 +135,7 @@ public class PersistentPeerDataService {
|
|||||||
}
|
}
|
||||||
curTx.put(data.withInitialSyncDone(data.initialSyncDone().minus(peerId)));
|
curTx.put(data.withInitialSyncDone(data.initialSyncDone().minus(peerId)));
|
||||||
Log.infov("Did reset sync state for {0}", peerId);
|
Log.infov("Did reset sync state for {0}", peerId);
|
||||||
curTx.onCommit(() -> connectedPeerManager.handleConnectionError(peerId));
|
curTx.onCommit(() -> reachablePeerManager.handleConnectionError(peerId));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class ConnectedPeerManager {
|
public class ReachablePeerManager {
|
||||||
private final ConcurrentMap<PeerId, PeerAddress> _states = new ConcurrentHashMap<>();
|
private final ConcurrentMap<PeerId, PeerAddress> _states = new ConcurrentHashMap<>();
|
||||||
private final Collection<PeerConnectedEventListener> _connectedListeners;
|
private final Collection<PeerConnectedEventListener> _connectedListeners;
|
||||||
private final Collection<PeerDisconnectedEventListener> _disconnectedListeners;
|
private final Collection<PeerDisconnectedEventListener> _disconnectedListeners;
|
||||||
@@ -58,7 +58,7 @@ public class ConnectedPeerManager {
|
|||||||
SyncHandler syncHandler;
|
SyncHandler syncHandler;
|
||||||
private ExecutorService _heartbeatExecutor;
|
private ExecutorService _heartbeatExecutor;
|
||||||
|
|
||||||
public ConnectedPeerManager(Instance<PeerConnectedEventListener> connectedListeners, Instance<PeerDisconnectedEventListener> disconnectedListeners) {
|
public ReachablePeerManager(Instance<PeerConnectedEventListener> connectedListeners, Instance<PeerDisconnectedEventListener> disconnectedListeners) {
|
||||||
_connectedListeners = List.copyOf(connectedListeners.stream().toList());
|
_connectedListeners = List.copyOf(connectedListeners.stream().toList());
|
||||||
_disconnectedListeners = List.copyOf(disconnectedListeners.stream().toList());
|
_disconnectedListeners = List.copyOf(disconnectedListeners.stream().toList());
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ package com.usatiuk.dhfs.remoteobj;
|
|||||||
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
||||||
import com.usatiuk.dhfs.rpc.RemoteObjectServiceClient;
|
import com.usatiuk.dhfs.rpc.RemoteObjectServiceClient;
|
||||||
import com.usatiuk.objects.JObjectKey;
|
import com.usatiuk.objects.JObjectKey;
|
||||||
import com.usatiuk.objects.transaction.LockingStrategy;
|
|
||||||
import com.usatiuk.objects.transaction.Transaction;
|
import com.usatiuk.objects.transaction.Transaction;
|
||||||
import io.quarkus.logging.Log;
|
import io.quarkus.logging.Log;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
@@ -55,11 +54,11 @@ public class RemoteTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T extends JDataRemote> Optional<T> getData(Class<T> type, JObjectKey key, LockingStrategy strategy, boolean tryRequest) {
|
private <T extends JDataRemote> Optional<T> getData(Class<T> type, JObjectKey key, boolean tryRequest) {
|
||||||
return curTx.get(RemoteObjectMeta.class, RemoteObjectMeta.ofMetaKey(key), strategy)
|
return curTx.get(RemoteObjectMeta.class, RemoteObjectMeta.ofMetaKey(key))
|
||||||
.flatMap(obj -> {
|
.flatMap(obj -> {
|
||||||
if (obj.hasLocalData()) {
|
if (obj.hasLocalData()) {
|
||||||
var realData = curTx.get(RemoteObjectDataWrapper.class, RemoteObjectMeta.ofDataKey(key), strategy).orElse(null);
|
var realData = curTx.get(RemoteObjectDataWrapper.class, RemoteObjectMeta.ofDataKey(key)).orElse(null);
|
||||||
if (realData == null)
|
if (realData == null)
|
||||||
throw new IllegalStateException("Local data not found for " + key); // TODO: Race
|
throw new IllegalStateException("Local data not found for " + key); // TODO: Race
|
||||||
if (!type.isInstance(realData.data()))
|
if (!type.isInstance(realData.data()))
|
||||||
@@ -72,8 +71,8 @@ public class RemoteTransaction {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<RemoteObjectMeta> getMeta(JObjectKey key, LockingStrategy strategy) {
|
public Optional<RemoteObjectMeta> getMeta(JObjectKey key) {
|
||||||
return curTx.get(RemoteObjectMeta.class, RemoteObjectMeta.ofMetaKey(key), strategy);
|
return curTx.get(RemoteObjectMeta.class, RemoteObjectMeta.ofMetaKey(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends JDataRemote> void putDataRaw(T obj) {
|
public <T extends JDataRemote> void putDataRaw(T obj) {
|
||||||
@@ -127,23 +126,12 @@ public class RemoteTransaction {
|
|||||||
curTx.put(newData);
|
curTx.put(newData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<RemoteObjectMeta> getMeta(JObjectKey key) {
|
|
||||||
return getMeta(key, LockingStrategy.OPTIMISTIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends JDataRemote> Optional<T> getData(Class<T> type, JObjectKey key) {
|
public <T extends JDataRemote> Optional<T> getData(Class<T> type, JObjectKey key) {
|
||||||
return getData(type, key, LockingStrategy.OPTIMISTIC, true);
|
return getData(type, key, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends JDataRemote> Optional<T> getDataLocal(Class<T> type, JObjectKey key) {
|
public <T extends JDataRemote> Optional<T> getDataLocal(Class<T> type, JObjectKey key) {
|
||||||
return getData(type, key, LockingStrategy.OPTIMISTIC, false);
|
return getData(type, key, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends JDataRemote> Optional<T> getData(Class<T> type, JObjectKey key, LockingStrategy strategy) {
|
|
||||||
return getData(type, key, strategy, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends JDataRemote> Optional<T> getDataLocal(Class<T> type, JObjectKey key, LockingStrategy strategy) {
|
|
||||||
return getData(type, key, strategy, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import com.usatiuk.dhfs.ProtoSerializer;
|
|||||||
import com.usatiuk.dhfs.invalidation.InvalidationQueueService;
|
import com.usatiuk.dhfs.invalidation.InvalidationQueueService;
|
||||||
import com.usatiuk.dhfs.invalidation.Op;
|
import com.usatiuk.dhfs.invalidation.Op;
|
||||||
import com.usatiuk.dhfs.peersync.PeerId;
|
import com.usatiuk.dhfs.peersync.PeerId;
|
||||||
import com.usatiuk.dhfs.peersync.ConnectedPeerManager;
|
import com.usatiuk.dhfs.peersync.ReachablePeerManager;
|
||||||
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
||||||
import com.usatiuk.dhfs.persistence.JObjectKeyP;
|
import com.usatiuk.dhfs.persistence.JObjectKeyP;
|
||||||
import com.usatiuk.dhfs.remoteobj.ReceivedObject;
|
import com.usatiuk.dhfs.remoteobj.ReceivedObject;
|
||||||
@@ -51,7 +51,7 @@ public class RemoteObjectServiceClient {
|
|||||||
@Inject
|
@Inject
|
||||||
ProtoSerializer<GetObjectReply, ReceivedObject> receivedObjectProtoSerializer;
|
ProtoSerializer<GetObjectReply, ReceivedObject> receivedObjectProtoSerializer;
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager connectedPeerManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
|
|
||||||
public Pair<PeerId, ReceivedObject> getSpecificObject(JObjectKey key, PeerId peerId) {
|
public Pair<PeerId, ReceivedObject> getSpecificObject(JObjectKey key, PeerId peerId) {
|
||||||
return rpcClientFactory.withObjSyncClient(peerId, (peer, client) -> {
|
return rpcClientFactory.withObjSyncClient(peerId, (peer, client) -> {
|
||||||
@@ -70,7 +70,7 @@ public class RemoteObjectServiceClient {
|
|||||||
|
|
||||||
var targetVersion = objMeta.versionSum();
|
var targetVersion = objMeta.versionSum();
|
||||||
var targets = objMeta.knownRemoteVersions().isEmpty()
|
var targets = objMeta.knownRemoteVersions().isEmpty()
|
||||||
? connectedPeerManager.getAvailableHosts()
|
? reachablePeerManager.getAvailableHosts()
|
||||||
: objMeta.knownRemoteVersions().entrySet().stream()
|
: objMeta.knownRemoteVersions().entrySet().stream()
|
||||||
.filter(entry -> entry.getValue().equals(targetVersion))
|
.filter(entry -> entry.getValue().equals(targetVersion))
|
||||||
.map(Map.Entry::getKey).toList();
|
.map(Map.Entry::getKey).toList();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.usatiuk.dhfs.autosync.AutosyncProcessor;
|
|||||||
import com.usatiuk.dhfs.invalidation.Op;
|
import com.usatiuk.dhfs.invalidation.Op;
|
||||||
import com.usatiuk.dhfs.invalidation.OpHandlerService;
|
import com.usatiuk.dhfs.invalidation.OpHandlerService;
|
||||||
import com.usatiuk.dhfs.peersync.PeerId;
|
import com.usatiuk.dhfs.peersync.PeerId;
|
||||||
import com.usatiuk.dhfs.peersync.ConnectedPeerManager;
|
import com.usatiuk.dhfs.peersync.ReachablePeerManager;
|
||||||
import com.usatiuk.dhfs.persistence.JObjectKeyP;
|
import com.usatiuk.dhfs.persistence.JObjectKeyP;
|
||||||
import com.usatiuk.dhfs.remoteobj.*;
|
import com.usatiuk.dhfs.remoteobj.*;
|
||||||
import com.usatiuk.dhfs.repository.*;
|
import com.usatiuk.dhfs.repository.*;
|
||||||
@@ -31,7 +31,7 @@ public class RemoteObjectServiceServerImpl {
|
|||||||
@Inject
|
@Inject
|
||||||
TransactionManager txm;
|
TransactionManager txm;
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager connectedPeerManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
@Inject
|
@Inject
|
||||||
Transaction curTx;
|
Transaction curTx;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import com.usatiuk.dhfs.peerdiscovery.IpPeerAddress;
|
|||||||
import com.usatiuk.dhfs.peerdiscovery.PeerAddress;
|
import com.usatiuk.dhfs.peerdiscovery.PeerAddress;
|
||||||
import com.usatiuk.dhfs.peersync.PeerDisconnectedEventListener;
|
import com.usatiuk.dhfs.peersync.PeerDisconnectedEventListener;
|
||||||
import com.usatiuk.dhfs.peersync.PeerId;
|
import com.usatiuk.dhfs.peersync.PeerId;
|
||||||
import com.usatiuk.dhfs.peersync.ConnectedPeerManager;
|
import com.usatiuk.dhfs.peersync.ReachablePeerManager;
|
||||||
import com.usatiuk.dhfs.repository.DhfsObjectSyncGrpcGrpc;
|
import com.usatiuk.dhfs.repository.DhfsObjectSyncGrpcGrpc;
|
||||||
import io.grpc.ManagedChannel;
|
import io.grpc.ManagedChannel;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
@@ -29,7 +29,7 @@ public class RpcClientFactory implements PeerDisconnectedEventListener {
|
|||||||
long syncTimeout;
|
long syncTimeout;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager connectedPeerManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
RpcChannelFactory rpcChannelFactory;
|
RpcChannelFactory rpcChannelFactory;
|
||||||
@@ -56,7 +56,7 @@ public class RpcClientFactory implements PeerDisconnectedEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <R> R withObjSyncClient(PeerId target, ObjectSyncClientFunction<R> fn) {
|
public <R> R withObjSyncClient(PeerId target, ObjectSyncClientFunction<R> fn) {
|
||||||
var hostinfo = connectedPeerManager.getAddress(target);
|
var hostinfo = reachablePeerManager.getAddress(target);
|
||||||
|
|
||||||
if (hostinfo == null)
|
if (hostinfo == null)
|
||||||
throw new StatusRuntimeException(Status.UNAVAILABLE.withDescription("Not known to be reachable: " + target));
|
throw new StatusRuntimeException(Status.UNAVAILABLE.withDescription("Not known to be reachable: " + target));
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.usatiuk.dhfs.webapi;
|
|||||||
|
|
||||||
import com.usatiuk.dhfs.peersync.PeerId;
|
import com.usatiuk.dhfs.peersync.PeerId;
|
||||||
import com.usatiuk.dhfs.peersync.PeerInfoService;
|
import com.usatiuk.dhfs.peersync.PeerInfoService;
|
||||||
import com.usatiuk.dhfs.peersync.ConnectedPeerManager;
|
import com.usatiuk.dhfs.peersync.ReachablePeerManager;
|
||||||
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.*;
|
import jakarta.ws.rs.*;
|
||||||
@@ -14,7 +14,7 @@ public class PeerManagementApi {
|
|||||||
@Inject
|
@Inject
|
||||||
PeerInfoService peerInfoService;
|
PeerInfoService peerInfoService;
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager connectedPeerManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
@Inject
|
@Inject
|
||||||
PersistentPeerDataService persistentPeerDataService;
|
PersistentPeerDataService persistentPeerDataService;
|
||||||
|
|
||||||
@@ -23,27 +23,27 @@ public class PeerManagementApi {
|
|||||||
public List<PeerInfo> knownPeers() {
|
public List<PeerInfo> knownPeers() {
|
||||||
return peerInfoService.getPeers().stream().map(
|
return peerInfoService.getPeers().stream().map(
|
||||||
peerInfo -> new PeerInfo(peerInfo.id().toString(), Base64.getEncoder().encodeToString(peerInfo.cert().toByteArray()),
|
peerInfo -> new PeerInfo(peerInfo.id().toString(), Base64.getEncoder().encodeToString(peerInfo.cert().toByteArray()),
|
||||||
Optional.ofNullable(connectedPeerManager.getAddress(peerInfo.id())).map(Objects::toString).orElse(null))).toList();
|
Optional.ofNullable(reachablePeerManager.getAddress(peerInfo.id())).map(Objects::toString).orElse(null))).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("known-peers/{peerId}")
|
@Path("known-peers/{peerId}")
|
||||||
@PUT
|
@PUT
|
||||||
public void addPeer(@PathParam("peerId") String peerId, KnownPeerPut knownPeerPut) {
|
public void addPeer(@PathParam("peerId") String peerId, KnownPeerPut knownPeerPut) {
|
||||||
connectedPeerManager.addRemoteHost(PeerId.of(peerId), knownPeerPut.cert());
|
reachablePeerManager.addRemoteHost(PeerId.of(peerId), knownPeerPut.cert());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("known-peers/{peerId}")
|
@Path("known-peers/{peerId}")
|
||||||
@DELETE
|
@DELETE
|
||||||
public void deletePeer(@PathParam("peerId") String peerId) {
|
public void deletePeer(@PathParam("peerId") String peerId) {
|
||||||
connectedPeerManager.removeRemoteHost(PeerId.of(peerId));
|
reachablePeerManager.removeRemoteHost(PeerId.of(peerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("available-peers")
|
@Path("available-peers")
|
||||||
@GET
|
@GET
|
||||||
public Collection<PeerInfo> availablePeers() {
|
public Collection<PeerInfo> availablePeers() {
|
||||||
return connectedPeerManager.getSeenButNotAddedHosts().stream()
|
return reachablePeerManager.getSeenButNotAddedHosts().stream()
|
||||||
.map(p -> new PeerInfo(p.getLeft().toString(), p.getRight().cert(),
|
.map(p -> new PeerInfo(p.getLeft().toString(), p.getRight().cert(),
|
||||||
connectedPeerManager.selectBestAddress(p.getLeft()).map(Objects::toString).orElse(null)))
|
reachablePeerManager.selectBestAddress(p.getLeft()).map(Objects::toString).orElse(null)))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.usatiuk.dhfs.webapi;
|
|||||||
import com.usatiuk.dhfs.peerdiscovery.PeerAddrStringHelper;
|
import com.usatiuk.dhfs.peerdiscovery.PeerAddrStringHelper;
|
||||||
import com.usatiuk.dhfs.peersync.PeerId;
|
import com.usatiuk.dhfs.peersync.PeerId;
|
||||||
import com.usatiuk.dhfs.peersync.PeerInfoService;
|
import com.usatiuk.dhfs.peersync.PeerInfoService;
|
||||||
import com.usatiuk.dhfs.peersync.ConnectedPeerManager;
|
import com.usatiuk.dhfs.peersync.ReachablePeerManager;
|
||||||
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
import com.usatiuk.dhfs.peersync.PersistentPeerDataService;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.*;
|
import jakarta.ws.rs.*;
|
||||||
@@ -15,7 +15,7 @@ public class PersistentPeerAddressApi {
|
|||||||
@Inject
|
@Inject
|
||||||
PeerInfoService peerInfoService;
|
PeerInfoService peerInfoService;
|
||||||
@Inject
|
@Inject
|
||||||
ConnectedPeerManager connectedPeerManager;
|
ReachablePeerManager reachablePeerManager;
|
||||||
@Inject
|
@Inject
|
||||||
PersistentPeerDataService persistentPeerDataService;
|
PersistentPeerDataService persistentPeerDataService;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.usatiuk.utils;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface VoidFn {
|
|
||||||
void apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.usatiuk.utils;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface VoidFnThrows {
|
|
||||||
void apply() throws Throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user