Skip to content

Commit 6ad270f

Browse files
committed
feat(appserver): serialize work per session key
1 parent 0901063 commit 6ad270f

1 file changed

Lines changed: 76 additions & 0 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright (c) 2018-present, easy-4-java (https://github.com/easy-4-java).
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
*/
6+
package io.github.easy4j.codex.appserver;
7+
8+
import java.util.Objects;
9+
import java.util.concurrent.CompletableFuture;
10+
import java.util.concurrent.ConcurrentHashMap;
11+
import java.util.function.Supplier;
12+
13+
/**
14+
* Serializes asynchronous work per logical session key while allowing
15+
* independent sessions to run concurrently.
16+
*/
17+
final class SessionExecutionCoordinator {
18+
19+
private final ConcurrentHashMap<String, CompletableFuture<Void>> tails =
20+
new ConcurrentHashMap<>();
21+
22+
<T> CompletableFuture<T> submit(
23+
String sessionKey,
24+
Supplier<CompletableFuture<T>> task) {
25+
Objects.requireNonNull(task, "task");
26+
27+
String key = normalize(sessionKey);
28+
if (key == null) {
29+
return invoke(task);
30+
}
31+
32+
CompletableFuture<Void> gate = new CompletableFuture<>();
33+
CompletableFuture<Void> previous = tails.put(key, gate);
34+
35+
CompletableFuture<Void> ready = previous == null
36+
? CompletableFuture.completedFuture(null)
37+
: previous.handle((ignored, error) -> null);
38+
39+
CompletableFuture<T> result = ready.thenCompose(ignored -> invoke(task));
40+
result.whenComplete((value, error) -> {
41+
gate.complete(null);
42+
tails.remove(key, gate);
43+
});
44+
return result;
45+
}
46+
47+
int activeSessionCount() {
48+
return tails.size();
49+
}
50+
51+
private static <T> CompletableFuture<T> invoke(
52+
Supplier<CompletableFuture<T>> task) {
53+
try {
54+
CompletableFuture<T> future = task.get();
55+
if (future == null) {
56+
CompletableFuture<T> failed = new CompletableFuture<>();
57+
failed.completeExceptionally(
58+
new NullPointerException("session task returned null future"));
59+
return failed;
60+
}
61+
return future;
62+
} catch (Throwable error) {
63+
CompletableFuture<T> failed = new CompletableFuture<>();
64+
failed.completeExceptionally(error);
65+
return failed;
66+
}
67+
}
68+
69+
private static String normalize(String sessionKey) {
70+
if (sessionKey == null) {
71+
return null;
72+
}
73+
String trimmed = sessionKey.trim();
74+
return trimmed.isEmpty() ? null : trimmed;
75+
}
76+
}

0 commit comments

Comments
 (0)