Skip to content

Commit 0bda609

Browse files
committed
feat: wip - migrate obfuscation/cache/proximity to core
1 parent 11ab36a commit 0bda609

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2743
-28
lines changed

orebfuscator-core/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@
5353
<version>${dependency.joml.version}</version>
5454
<scope>provided</scope>
5555
</dependency>
56-
<!--<dependency>
56+
<dependency>
5757
<groupId>org.lz4</groupId>
5858
<artifactId>lz4-java</artifactId>
5959
<version>${dependency.lz4.version}</version>
60-
<scope>compile</scope>
61-
</dependency>-->
60+
<scope>provided</scope>
61+
</dependency>
6262
</dependencies>
6363

6464
<build>
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package dev.imprex.orebfuscator.cache;
2+
3+
import java.io.IOException;
4+
import java.util.HashMap;
5+
import java.util.LinkedList;
6+
import java.util.Map;
7+
import java.util.Queue;
8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.locks.Condition;
10+
import java.util.concurrent.locks.Lock;
11+
import java.util.concurrent.locks.ReentrantLock;
12+
import dev.imprex.orebfuscator.interop.OrebfuscatorCore;
13+
import dev.imprex.orebfuscator.logging.OfcLogger;
14+
import dev.imprex.orebfuscator.util.ChunkCacheKey;
15+
import org.jspecify.annotations.NullMarked;
16+
import org.jspecify.annotations.Nullable;
17+
18+
/**
19+
* This class works similar to a bounded buffer for cache read and write requests but also functions as the only
20+
* consumer of said buffer. All requests can get reorder similar to modern memory access reordering in CPUs. If for
21+
* example a write request is already in the buffer and a new read request for the same position is created then the
22+
* read request doesn't get put in the buffer and gets completed with the content of the write request.
23+
*
24+
* @see <a href="https://en.wikipedia.org/wiki/Producer–consumer_problem">Bound buffer</a>
25+
* @see <a href="https://en.wikipedia.org/wiki/Memory_ordering">Memory ordering</a>
26+
*/
27+
// TODO: add statistice for read/write times and ratio of read/write as well as average throughput for read/write
28+
@NullMarked
29+
public class AsyncChunkSerializer implements Runnable {
30+
31+
private final Lock lock = new ReentrantLock(true);
32+
private final Condition notFull = lock.newCondition();
33+
private final Condition notEmpty = lock.newCondition();
34+
35+
private final Map<ChunkCacheKey, Runnable> tasks = new HashMap<>();
36+
private final Queue<ChunkCacheKey> positions = new LinkedList<>();
37+
38+
private final int maxTaskQueueSize;
39+
private final ChunkSerializer serializer;
40+
41+
private final Thread thread;
42+
private volatile boolean running = true;
43+
44+
public AsyncChunkSerializer(OrebfuscatorCore orebfuscator, AbstractRegionFileCache<?> regionFileCache) {
45+
this.maxTaskQueueSize = orebfuscator.config().cache().maximumTaskQueueSize();
46+
this.serializer = new ChunkSerializer(regionFileCache);
47+
48+
this.thread = new Thread(OrebfuscatorCore.THREAD_GROUP, this, "ofc-chunk-serializer");
49+
this.thread.setDaemon(true);
50+
this.thread.start();
51+
52+
orebfuscator.statistics().cache.setDiskCacheQueueLength(this.tasks::size);
53+
}
54+
55+
public CompletableFuture<@Nullable ChunkCacheEntry> read(ChunkCacheKey key) {
56+
this.lock.lock();
57+
try {
58+
Runnable task = this.tasks.get(key);
59+
if (task instanceof WriteTask) {
60+
return CompletableFuture.completedFuture(((WriteTask) task).chunk);
61+
} else if (task instanceof ReadTask) {
62+
return ((ReadTask) task).future;
63+
} else {
64+
CompletableFuture<ChunkCacheEntry> future = new CompletableFuture<>();
65+
this.queueTask(key, new ReadTask(key, future));
66+
return future;
67+
}
68+
} finally {
69+
this.lock.unlock();
70+
}
71+
}
72+
73+
public void write(ChunkCacheKey key, ChunkCacheEntry chunk) {
74+
this.lock.lock();
75+
try {
76+
Runnable prevTask = this.queueTask(key, new WriteTask(key, chunk));
77+
if (prevTask instanceof ReadTask) {
78+
((ReadTask) prevTask).future.complete(chunk);
79+
}
80+
} finally {
81+
this.lock.unlock();
82+
}
83+
}
84+
85+
@Nullable
86+
private Runnable queueTask(ChunkCacheKey key, Runnable nextTask) {
87+
while (this.positions.size() >= this.maxTaskQueueSize) {
88+
this.notFull.awaitUninterruptibly();
89+
}
90+
91+
if (!this.running) {
92+
throw new IllegalStateException("AsyncChunkSerializer already closed");
93+
}
94+
95+
Runnable prevTask = this.tasks.put(key, nextTask);
96+
if (prevTask == null) {
97+
this.positions.offer(key);
98+
}
99+
100+
this.notEmpty.signal();
101+
return prevTask;
102+
}
103+
104+
@Override
105+
public void run() {
106+
while (this.running) {
107+
this.lock.lock();
108+
try {
109+
while (this.positions.isEmpty()) {
110+
this.notEmpty.await();
111+
}
112+
113+
this.tasks.remove(this.positions.poll()).run();
114+
115+
this.notFull.signal();
116+
} catch (InterruptedException e) {
117+
break;
118+
} finally {
119+
this.lock.unlock();
120+
}
121+
}
122+
}
123+
124+
public void close() {
125+
this.lock.lock();
126+
try {
127+
this.running = false;
128+
this.thread.interrupt();
129+
130+
while (!this.positions.isEmpty()) {
131+
Runnable task = this.tasks.remove(this.positions.poll());
132+
if (task instanceof WriteTask) {
133+
task.run();
134+
}
135+
}
136+
} finally {
137+
this.lock.unlock();
138+
}
139+
}
140+
141+
private class WriteTask implements Runnable {
142+
143+
private final ChunkCacheKey key;
144+
private final ChunkCacheEntry chunk;
145+
146+
public WriteTask(ChunkCacheKey key, ChunkCacheEntry chunk) {
147+
this.key = key;
148+
this.chunk = chunk;
149+
}
150+
151+
@Override
152+
public void run() {
153+
try {
154+
serializer.write(key, chunk);
155+
} catch (IOException e) {
156+
OfcLogger.error(e);
157+
}
158+
}
159+
}
160+
161+
private class ReadTask implements Runnable {
162+
163+
private final ChunkCacheKey key;
164+
private final CompletableFuture<@Nullable ChunkCacheEntry> future;
165+
166+
public ReadTask(ChunkCacheKey key, CompletableFuture<@Nullable ChunkCacheEntry> future) {
167+
this.key = key;
168+
this.future = future;
169+
}
170+
171+
@Override
172+
public void run() {
173+
try {
174+
future.complete(serializer.read(key));
175+
} catch (Exception e) {
176+
future.completeExceptionally(e);
177+
}
178+
}
179+
}
180+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package dev.imprex.orebfuscator.cache;
2+
3+
import dev.imprex.orebfuscator.config.api.CacheConfig;
4+
import dev.imprex.orebfuscator.config.api.Config;
5+
import dev.imprex.orebfuscator.logging.OfcLogger;
6+
import java.io.IOException;
7+
import java.nio.file.FileVisitResult;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import java.nio.file.SimpleFileVisitor;
11+
import java.nio.file.attribute.BasicFileAttributes;
12+
import org.jspecify.annotations.NullMarked;
13+
14+
@NullMarked
15+
public class CacheFileCleanupTask implements Runnable {
16+
17+
private final CacheConfig cacheConfig;
18+
private final AbstractRegionFileCache<?> regionFileCache;
19+
20+
private int deleteCount = 0;
21+
22+
public CacheFileCleanupTask(Config config, AbstractRegionFileCache<?> regionFileCache) {
23+
this.cacheConfig = config.cache();
24+
this.regionFileCache = regionFileCache;
25+
}
26+
27+
@Override
28+
public void run() {
29+
if (Files.notExists(this.cacheConfig.baseDirectory())) {
30+
OfcLogger.debug("Skipping CacheFileCleanupTask as the cache directory doesn't exist.");
31+
return;
32+
}
33+
34+
long deleteAfterMillis = this.cacheConfig.deleteRegionFilesAfterAccess();
35+
36+
this.deleteCount = 0;
37+
38+
try {
39+
Files.walkFileTree(this.cacheConfig.baseDirectory(), new SimpleFileVisitor<>() {
40+
41+
@Override
42+
public FileVisitResult visitFile(Path path, BasicFileAttributes attributes) throws IOException {
43+
if (System.currentTimeMillis() - attributes.lastAccessTime().toMillis() > deleteAfterMillis) {
44+
regionFileCache.close(path);
45+
Files.delete(path);
46+
47+
CacheFileCleanupTask.this.deleteCount++;
48+
OfcLogger.debug("deleted cache file: " + path);
49+
}
50+
return FileVisitResult.CONTINUE;
51+
}
52+
});
53+
} catch (IOException e) {
54+
OfcLogger.error(e);
55+
}
56+
57+
if (this.deleteCount > 0) {
58+
OfcLogger.info(String.format("CacheFileCleanupTask successfully deleted %d cache file(s)", this.deleteCount));
59+
}
60+
}
61+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.imprex.orebfuscator.cache;
2+
3+
import java.util.Objects;
4+
import org.jspecify.annotations.NullMarked;
5+
import com.google.common.hash.HashFunction;
6+
import com.google.common.hash.Hashing;
7+
import dev.imprex.orebfuscator.util.ChunkCacheKey;
8+
9+
@NullMarked
10+
public record CacheRequest(ChunkCacheKey cacheKey, byte[] hash) {
11+
12+
public static final HashFunction HASH_FUNCTION = Hashing.murmur3_128();
13+
public static final int HASH_LENGTH = HASH_FUNCTION.bits() / Byte.SIZE;
14+
15+
public CacheRequest {
16+
Objects.requireNonNull(cacheKey);
17+
Objects.requireNonNull(hash);
18+
}
19+
20+
@Override
21+
public String toString() {
22+
return "CacheRequest [cacheKey=" + cacheKey + "]";
23+
}
24+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dev.imprex.orebfuscator.cache;
2+
3+
import java.util.Objects;
4+
import org.jspecify.annotations.NullMarked;
5+
6+
@NullMarked
7+
public sealed interface CacheResponse permits CacheResponse.Success, CacheResponse.Failure {
8+
9+
public static CacheResponse success(ChunkCacheEntry entry) {
10+
return new Success(entry);
11+
}
12+
13+
record Success(ChunkCacheEntry entry) implements CacheResponse {
14+
public Success {
15+
Objects.requireNonNull(entry, "entry");
16+
}
17+
}
18+
19+
enum Failure implements CacheResponse {
20+
NOT_FOUND, MEMORY_INVALID, DISK_INVALID;
21+
}
22+
}

0 commit comments

Comments
 (0)