Skip to content

Commit 288f118

Browse files
committed
er
1 parent 7932ad8 commit 288f118

File tree

68 files changed

+738
-544
lines changed

Some content is hidden

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

68 files changed

+738
-544
lines changed

lifecycle-todos.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Lifecycle Runtime API TODOs
2+
3+
## ManagedLifecycle
4+
5+
**Core issue: it mixes two concerns.** It's both a state query interface (`currentState()`, `await()`, `isFailed()`) and a command interface (`start()`, `stop()`). This is fine if the only consumer is an external controller, but the comments suggest it could also be injected into guest beans — where `stop()` on yourself is odd and `start()` is meaningless.
6+
7+
Consider splitting into a read-only `LifecycleState` (await, query) and a `LifecycleController` that extends it and adds start/stop. Beans get the read-only view; external hosts get the controller.
8+
9+
**`state()` returns `ManagedLifetimeState`** but is suppressing an exports warning and throws UOE. If this isn't ready, leave it out of the interface entirely — stub methods on public interfaces become API debt.
10+
11+
**`stopInfo()`** returns `Optional.empty()` as default. Same concern — it signals the feature exists but doesn't work.
12+
13+
---
14+
15+
## ManagedLifetimeState
16+
17+
**The desired/actual model is premature.** Without restart support, `desiredState()` is always equal to `currentState()` except during transitions — which `isTransitioning()` already captures. And during a normal transition (e.g. STARTING), the "desired" state is ambiguous (RUNNING? or just "past STARTING"?).
18+
19+
Defer this to when restart exists. For now, `currentState()` + `isFailed()` + `failure()` is sufficient.
20+
21+
**Naming**: Consider `LifecycleSnapshot` or `RunStateSnapshot`. "ManagedLifetimeState" is long and overlaps with both `ManagedLifecycle` and `RunState`.
22+
23+
The `zaLS` stub interface in the same file should be removed or moved.
24+
25+
---
26+
27+
## StopInfo
28+
29+
**Overlap with RunStateTransition**: Both have `from()` state, `failure()`, and represent "what happened when we stopped." They serve similar purposes at different abstraction levels. Pick one or make one a subset of the other.
30+
31+
**`Trigger` as a class with string constants** is an unusual pattern. It's essentially an open enum. Problems:
32+
- Users can't `switch` on it
33+
- Equality is identity-based (not string-based)
34+
- `ENTRY_POINT_COMPLETED` and `NORMAL` are different objects but both have the string `"Normal"` — this is a bug or at least very confusing
35+
36+
If the set is meant to be extensible, consider making `Trigger` a sealed interface with record implementations, or at minimum fix the duplicate `"Normal"` string. If extensibility isn't actually needed, make it a proper enum.
37+
38+
**`hasResult()` / `isCancelled()` / `isCompletedExceptionally()` / `isCompletedNormally()`** — this mirrors `CompletableFuture`'s completion model. But a lifetime stop isn't really a future completion. A stop is either normal, forced, or failed. Simplify to: `isNormal()`, `isFailed()`, `failure()`, `trigger()`.
39+
40+
---
41+
42+
## StopOption
43+
44+
**Marker interface with only static factories** — this works but makes the type unimplementable by users (no methods to implement). If user extension isn't intended, `sealed interface` would make that explicit.
45+
46+
**Too many overlapping concepts**: `forced()`, `now()`, `now(Throwable)`, `forcedGraceTime()` — three or four ways to express "stop hard." Pick one model: `forced()` means interrupt threads immediately, `forced(Duration graceTime)` means try orderly then interrupt. That's two methods, not four.
47+
48+
**`parentStopping()` as a StopOption** feels wrong. That's an internal lifecycle event, not a user-facing option. It should be an internal signal, not part of the public API.
49+
50+
**`restart()` / `tryRestart()`** — the distinction isn't clear from the API. If `restart()` throws when restart isn't supported and `tryRestart()` is best-effort, name them `restart()` and `restartIfSupported()` or similar.
51+
52+
---
53+
54+
## RunStateTransition
55+
56+
**Clean and focused** — this is the best-designed type in the package. `from()`, `to()`, `failure()` is exactly the right surface for an `@OnStop` callback parameter.
57+
58+
---
59+
60+
## Package-level suggestions
61+
62+
1. **Reduce the type count.** `StopInfo`, `RunStateTransition`, and `ManagedLifetimeState` all describe "what state are we in and why." Consider whether `RunStateTransition` alone (with an added `trigger()`) could replace `StopInfo`.
63+
64+
2. **Package name**: `lifecycle.runtime` reads as "runtime stuff for lifecycle." If this is specifically about managed/running lifetimes, `lifecycle.managed` or just collapsing into `lifecycle` would be clearer.
65+
66+
3. **Defer restart.** Multiple types have restart concepts baked in (`StopOption.restart()`, `Trigger.RESTARTING`, `zaLS.isRestarting()`). Since none of it works yet, removing it from the API surface makes the current API smaller and avoids locking in a restart design prematurely.

modules/packed-incubator/packed-incubator-concurrent/src/main/java/app/packed/concurrent/DaemonJobContext.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public sealed interface DaemonJobContext extends Context<BaseExtension> permits
3939
boolean isShutdown();
4040

4141
/**
42-
* Blocks until the bean has been shutdown, or the timeout occurs, or the current thread is interrupted, whichever
43-
* happens first.
42+
* Blocks until the job has been stopped (for example, the application has been shutdown) , or the timeout occurs, or the current thread is interrupted, whichever happens
43+
* first.
4444
*
4545
* @param timeout
4646
* the maximum time to wait
@@ -50,6 +50,7 @@ public sealed interface DaemonJobContext extends Context<BaseExtension> permits
5050
* @throws InterruptedException
5151
* if interrupted while waiting
5252
*/
53+
// Maybe rename to sleep instead???
5354
boolean awaitShutdown(long timeout, TimeUnit unit) throws InterruptedException;
5455
}
5556

modules/packed-incubator/packed-incubator-concurrent/src/main/java/app/packed/concurrent/job/app/PackedJobApp.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
import java.util.concurrent.TimeUnit;
2222

2323
import app.packed.application.BootstrapApp;
24+
import app.packed.application.ManagedApplicationRuntime;
2425
import app.packed.bean.Bean;
2526
import app.packed.component.SidehandleBinding;
2627
import app.packed.component.SidehandleContext;
2728
import app.packed.lifecycle.LifecycleKind;
2829
import app.packed.lifecycle.RunState;
29-
import app.packed.lifecycle.runtime.ManagedLifecycle;
30-
import app.packed.lifecycle.runtime.StopOption;
30+
import app.packed.lifecycle.sandbox.StopOption;
3131
import internal.app.packed.ValueBased;
3232

3333
/** The default implementation of {@link App}. */
@@ -41,9 +41,9 @@ final class PackedJobApp implements JobApp {
4141
public static final BootstrapApp<PackedJobApp> BOOTSTRAP_APP = BootstrapApp.of(LifecycleKind.MANAGED, Bean.of(PackedJobApp.class));
4242

4343
/** Manages the lifecycle of the app. */
44-
private final ManagedLifecycle lifecycle;
44+
private final ManagedApplicationRuntime lifecycle;
4545

46-
PackedJobApp(@SidehandleBinding(FROM_CONTEXT) ManagedLifecycle lc, @SidehandleBinding(FROM_CONTEXT) Future<?> result, SidehandleContext context) {
46+
PackedJobApp(@SidehandleBinding(FROM_CONTEXT) ManagedApplicationRuntime lc, @SidehandleBinding(FROM_CONTEXT) Future<?> result, SidehandleContext context) {
4747
this.lifecycle = lc;
4848
}
4949

@@ -111,6 +111,12 @@ public void close() {}
111111
/** {@inheritDoc} */
112112
@Override
113113
public void stop(StopOption... options) {}
114+
115+
/** {@inheritDoc} */
116+
@Override
117+
public String name() {
118+
return null;
119+
}
114120
}
115121

116122
/// retur typer

modules/packed-incubator/packed-incubator-concurrent/src/main/java/internal/app/packed/concurrent/daemon/DaemonJobSidehandle.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ protected void onStart() {
8282
protected void onStop() {
8383
// Atomic transition to shutdown state
8484
if (isShutdown.compareAndSet(false, true)) {
85-
// 1. Trigger the latch so awaitShutdown() returns false immediately
85+
// Trigger the latch so awaitShutdown() returns false immediately
8686
shutdownLatch.countDown();
8787

88-
// 2. Interrupt the thread if the user wants to break out of blocking I/O
88+
// Interrupt the thread if the user wants to break out of blocking I/O
8989
Thread t = thread;
9090
if (t != null && interruptOnShutdown) {
9191
t.interrupt();

modules/packed/src/main/java/app/packed/application/App.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import app.packed.binding.Key;
88
import app.packed.container.Wirelet;
99
import app.packed.lifecycle.RunState;
10-
import app.packed.lifecycle.runtime.StopOption;
10+
import app.packed.lifecycle.sandbox.StopOption;
1111

1212
/**
1313
* Represents the main entry point for executing and managing Packed applications. This interface provides methods for
@@ -74,6 +74,9 @@ public interface App extends AutoCloseable, ApplicationInterface {
7474
@Override
7575
void close();
7676

77+
/** {@return the name of the application} */
78+
String name();
79+
7780
/**
7881
* Returns the current state of the application.
7982
*
@@ -232,22 +235,21 @@ interface Launcher extends ApplicationLauncher {
232235

233236
// Config also
234237

235-
236-
@Override
237-
default <T> Launcher provide(Key<? super T> key, T value) {
238-
return null;
238+
default Launcher alwaysRestart() {
239+
return this;
239240
}
240241

241-
default Launcher alwaysRestart() {
242+
default Launcher ignoreAllExceptions() {
242243
return this;
243244
}
244245

245246
// rename to arg
246247

247248
// @LaunchArgument/@LaunchArg <--- Injectable
248249

249-
default Launcher ignoreAllExceptions() {
250-
return this;
250+
@Override
251+
default <T> Launcher provide(Key<? super T> key, T value) {
252+
return null;
251253
}
252254

253255
/**

modules/packed/src/main/java/app/packed/application/ApplicationConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import app.packed.build.BuildException;
2727
import app.packed.build.BuildProcess;
2828
import app.packed.component.ComponentConfiguration;
29-
import app.packed.lifecycle.runtime.StopOption;
29+
import app.packed.lifecycle.sandbox.StopOption;
3030

3131
/**
3232
* The configuration of an application.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright (c) 2026 Kasper Nielsen.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package app.packed.application;
17+
18+
import java.util.concurrent.TimeUnit;
19+
20+
import app.packed.context.Context;
21+
import app.packed.extension.BaseExtension;
22+
import app.packed.lifecycle.RunState;
23+
import app.packed.lifecycle.sandbox.StopOption;
24+
25+
/**
26+
* Every bean is automatically in ApplicationContext.
27+
*/
28+
public interface ApplicationContext extends Context<BaseExtension> {
29+
30+
/** {@return the name of the application} */
31+
String name();
32+
33+
/**
34+
* Blocks until the lifetime reaches the specified run state, or the current thread is interrupted, whichever happens
35+
* first.
36+
* <p>
37+
* If the lifetime has already reached (or passed) the specified state this method returns immediately. For example, if
38+
* attempting to wait on the {@link RunState#RUNNING} state and the lifetime has already terminated. This method will
39+
* return immediately.
40+
*
41+
* @param state
42+
* the state to wait on
43+
* @throws InterruptedException
44+
* if interrupted while waiting
45+
* @see #await(RunState, long, TimeUnit)
46+
* @see #state()
47+
*/
48+
// Er det ikke de eneste relevante states: Running, Stopping???
49+
// Hvad er usecase for Running???
50+
51+
// Terminated <--- er ikke lovligt at vente paa internt
52+
// Den her er ikke saerlig brugbart
53+
void sleep(RunState state) throws InterruptedException;
54+
55+
/**
56+
* Blocks until the component has reached the requested state, or the timeout occurs, or the current thread is
57+
* interrupted, whichever happens first.
58+
* <p>
59+
* If the component has already reached or passed the specified state this method returns immediately with. For example,
60+
* if attempting to wait on the {@link RunState#RUNNING} state and the object has already been stopped. This method will
61+
* return immediately with true.
62+
*
63+
* @param state
64+
* the state to wait on
65+
* @param timeout
66+
* the maximum time to wait
67+
* @param unit
68+
* the time unit of the timeout argument
69+
* @return {@code true} if this component is in (or has already passed) the specified state and {@code false} if the
70+
* timeout elapsed before reaching the state
71+
* @throws InterruptedException
72+
* if interrupted while waiting
73+
* @see #await(RunState)
74+
* @see #state()
75+
*/
76+
boolean sleep(RunState state, long timeout, TimeUnit unit) throws InterruptedException;
77+
78+
/** {@return the current state of the application} */
79+
RunState currentState();
80+
81+
/** {@return the desired state of the application.} */
82+
RunState desiredState();
83+
84+
/**
85+
* @return
86+
*/
87+
boolean isManaged();
88+
89+
/**
90+
* Attempts to stop the application.
91+
*
92+
* @param options
93+
* stop options
94+
* @return false if already shutdown otherwise true
95+
* @throws IllegalStateException
96+
* if attempting to shutdown the container while initializing
97+
* @throws UnsupportedOperationException
98+
* if the application is not {@link #isManaged() managed}
99+
*/
100+
boolean stopAsync(StopOption... options);
101+
}

modules/packed/src/main/java/app/packed/application/BootstrapApp.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ default <E> BootstrapApp<E> map(Function<? super I, ? extends E> mapper) {
164164
// withExpectingResult
165165
BootstrapApp<I> withExpectsResult(Class<?> resultType);
166166

167+
// Can have injected ApplicationName
168+
// ManagedApplicationRuntime
169+
167170
static <A> BootstrapApp<A> of(LifecycleKind lifecycleKind, Bean<A> bean) {
168171
return PackedBootstrapApp.of(lifecycleKind, bean);
169172
}

0 commit comments

Comments
 (0)