Skip to content

Commit d9a6a83

Browse files
committed
Fix ArchUnit violations from the new server code (layering + no-sleep)
Two LlamaArchitectureTest rules failed on PR #293 because of this branch's server additions: - layeredArchitecture (12 violations): the branch adds two legitimate new edges out of the Server layer -- Server -> Args (OpenAiServerCli maps -ctk/-ctv to the args.CacheType enum) and Server -> Loader (NativeServer.start() calls LlamaLoader.initialize() before launching the embedded native server). The rule documents itself as the EXACT set of accessors today, to be updated when a new dependency is intended, so Server is added to the Loader and Args mayOnlyBeAccessedByLayers lists (+ a doc note). Server remains the only layer allowed to reach the Api root and stays mayNotBeAccessedByAnyLayer. - noThreadSleep (1 violation): NativeServer.main() kept the JVM alive with a while(isRunning()) Thread.sleep(200) poll loop. The rule bans Thread.sleep and has no suppression seam (it prefers Condition.await/poll), so main() now blocks on a bounded CountDownLatch.await(200ms) signalled by the shutdown hook. This is also a behavioural improvement: Ctrl-C/SIGTERM wakes the wait immediately instead of after up to a 200 ms tick, while the timeout still re-checks isRunning() to catch a self-terminated native worker. Verified: LlamaArchitectureTest 12/12 pass; server-package tests 44/44 pass (ServerLauncher, OpenAiServerCli, NativeServerSmoke); javadoc + spotless clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HL7d4uQ3cKR5HwYFPvZvv7
1 parent 5664957 commit d9a6a83

2 files changed

Lines changed: 18 additions & 7 deletions

File tree

llama/src/main/java/net/ladenthin/llama/server/NativeServer.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
package net.ladenthin.llama.server;
66

77
import java.util.Objects;
8+
import java.util.concurrent.CountDownLatch;
9+
import java.util.concurrent.TimeUnit;
810
import java.util.concurrent.atomic.AtomicBoolean;
911
import lombok.ToString;
1012
import net.ladenthin.llama.loader.LlamaLoader;
@@ -198,20 +200,27 @@ public void close() {
198200
public static void main(String[] args) throws InterruptedException {
199201
final NativeServer server = new NativeServer(args);
200202
final AtomicBoolean stoppedByHook = new AtomicBoolean(false);
203+
// Signalled by the shutdown hook so the main thread wakes immediately on Ctrl-C / SIGTERM
204+
// rather than waiting out a poll tick — and so the wait uses a bounded latch await instead of
205+
// Thread.sleep (banned by LlamaArchitectureTest.noThreadSleep).
206+
final CountDownLatch stopSignal = new CountDownLatch(1);
201207
// Graceful Ctrl-C / SIGTERM: the embedded server installs no signal handlers of its own
202208
// (see patches/0006), so the JVM-level shutdown hook is what stops it before exit.
203209
Runtime.getRuntime()
204210
.addShutdownHook(new Thread(
205211
() -> {
206212
stoppedByHook.set(true);
207213
server.close();
214+
stopSignal.countDown();
208215
},
209216
"jllama-native-server-shutdown"));
210217
server.start();
211218
// Keep the JVM alive until the native worker exits — on its own (e.g. a fatal startup/model
212-
// error that llama_server has already logged) or because the shutdown hook stopped it.
213-
while (server.isRunning()) {
214-
Thread.sleep(200L);
219+
// error that llama_server has already logged) or because the shutdown hook stopped it. The
220+
// bounded await returns early when the hook fires; on timeout we re-check isRunning() to catch
221+
// a self-terminated worker.
222+
while (server.isRunning() && !stopSignal.await(200L, TimeUnit.MILLISECONDS)) {
223+
// wait for the native worker to exit or the shutdown hook to fire
215224
}
216225
if (!stoppedByHook.get()) {
217226
server.close();

llama/src/test/java/net/ladenthin/llama/LlamaArchitectureTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,10 @@ public class LlamaArchitectureTest {
9494
* intend it. Conceptual tiers (informational): {@code Server} &gt; {@code Api} (root) &gt;
9595
* {@code Loader} &gt; {@code Json}/{@code Parameters} &gt;
9696
* {@code Value}/{@code Callback}/{@code Exception}/{@code Args}. The {@code Server} layer is the
97-
* optional OpenAI-compatible HTTP entry point; it is the only layer permitted to access the
98-
* {@code Api} root.
97+
* optional OpenAI-compatible HTTP / native-server entry point; it is the only layer permitted to
98+
* access the {@code Api} root, and it also reaches the {@code Loader} ({@code NativeServer}
99+
* triggers {@code LlamaLoader.initialize()} before starting the embedded native server) and the
100+
* {@code Args} enums ({@code OpenAiServerCli} maps {@code -ctk}/{@code -ctv} to {@code CacheType}).
99101
*/
100102
@ArchTest
101103
static final ArchRule layeredArchitecture = layeredArchitecture()
@@ -121,7 +123,7 @@ public class LlamaArchitectureTest {
121123
.whereLayer("Api")
122124
.mayOnlyBeAccessedByLayers("Server")
123125
.whereLayer("Loader")
124-
.mayOnlyBeAccessedByLayers("Api")
126+
.mayOnlyBeAccessedByLayers("Api", "Server")
125127
.whereLayer("Json")
126128
.mayOnlyBeAccessedByLayers("Api")
127129
.whereLayer("Parameters")
@@ -133,7 +135,7 @@ public class LlamaArchitectureTest {
133135
.whereLayer("Exception")
134136
.mayOnlyBeAccessedByLayers("Api", "Loader")
135137
.whereLayer("Args")
136-
.mayOnlyBeAccessedByLayers("Api", "Loader", "Parameters")
138+
.mayOnlyBeAccessedByLayers("Api", "Loader", "Parameters", "Server")
137139
.whereLayer("Server")
138140
.mayNotBeAccessedByAnyLayer();
139141

0 commit comments

Comments
 (0)