Skip to content

Commit d43a5e9

Browse files
feat: Added docs to async stuff
1 parent 5b8203c commit d43a5e9

File tree

4 files changed

+177
-7
lines changed

4 files changed

+177
-7
lines changed

src/main/java/gentle/async/AsyncError.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,22 @@
44
import lombok.NonNull;
55

66
public record AsyncError(@NonNull Throwable throwable) implements Error {
7+
8+
/**
9+
* Returns a numeric code for this error.
10+
*
11+
* @return error code
12+
*/
713
@Override
814
public int code() {
915
return 1;
1016
}
1117

18+
/**
19+
* Returns a human-readable message describing the error.
20+
*
21+
* @return error message
22+
*/
1223
@Override
1324
public @NonNull String message() {
1425
return throwable.getMessage();

src/main/java/gentle/async/Scope.java

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,143 @@
11
package gentle.async;
22

3+
import gentle.Result;
34
import lombok.AccessLevel;
45
import lombok.NonNull;
56
import lombok.RequiredArgsConstructor;
67

8+
import java.time.Duration;
79
import java.util.ArrayList;
810
import java.util.List;
9-
import java.util.concurrent.Callable;
10-
import java.util.concurrent.ExecutorService;
11-
import java.util.concurrent.Executors;
11+
import java.util.concurrent.*;
1212

13+
/**
14+
* A structured concurrency scope for managing asynchronous tasks.
15+
* <p>
16+
* The {@code Scope} allows creating and managing multiple asynchronous {@link Task tasks} in a single
17+
* logical scope. All tasks are automatically cancelled when the scope is closed.
18+
*
19+
* <p>Typical usage:
20+
* <pre>{@code
21+
* try (Scope scope = Scope.open()) {
22+
* Task<String> t1 = scope.async(() -> "Hello");
23+
* Task<Integer> t2 = scope.async(() -> 42);
24+
*
25+
* System.out.println(t1.await());
26+
* System.out.println(t2.await());
27+
* }
28+
* }</pre>
29+
*/
1330
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
1431
public final class Scope implements AutoCloseable {
32+
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(Utils.cores);
33+
34+
/** ExecutorService used for running tasks in this scope. */
1535
private final ExecutorService executor;
36+
37+
/** List of tasks created in this scope. */
1638
private final List<Task<?>> tasks = new ArrayList<>();
39+
40+
/** Indicates whether this scope has been closed. */
1741
private volatile boolean closed = false;
1842

43+
/**
44+
* Opens a new scope with a cached thread pool.
45+
*
46+
* @return a new {@code Scope}
47+
*/
1948
public static Scope open() {
2049
return new Scope(Executors.newCachedThreadPool());
2150
}
2251

52+
/**
53+
* Opens a new scope using a custom {@link ExecutorService}.
54+
*
55+
* @param executor the executor to run tasks
56+
* @return a new {@code Scope}
57+
*/
2358
public static Scope open(@NonNull ExecutorService executor) {
2459
return new Scope(executor);
2560
}
2661

62+
/**
63+
* Submits a new asynchronous {@link Task} to this scope.
64+
* <p>
65+
* The task is automatically cancelled when the scope is closed.
66+
*
67+
* @param supplier the task to execute
68+
* @param <T> the type of the task result
69+
* @return a {@link Task} representing the asynchronous computation
70+
* @throws IllegalStateException if the scope is already closed
71+
*/
2772
public synchronized <T> Task<T> async(@NonNull Callable<T> supplier) {
2873
if (closed) throw new IllegalStateException("Scope already closed");
2974
Task<T> task = new Task<>(executor.submit(supplier));
3075
tasks.add(task);
3176
return task;
3277
}
3378

79+
/**
80+
* Submits a new asynchronous {@link Task} to this scope that will start after a specified delay.
81+
* <p>
82+
* The task is scheduled using a shared {@link ScheduledExecutorService}. When the delay elapses,
83+
* the {@code supplier} is executed and the result is completed in the returned {@link Task}.
84+
* The task is automatically cancelled if the scope is closed before it executes.
85+
*
86+
* <p>Example usage:
87+
* <pre>{@code
88+
* try (Scope scope = Scope.open()) {
89+
* Task<String> delayedTask = scope.delayed(Duration.ofSeconds(2), () -> "Hello after 2s");
90+
* System.out.println(delayedTask.await());
91+
* }
92+
* }</pre>
93+
*
94+
* @param delay the delay after which the task should execute
95+
* @param supplier the task to execute after the delay
96+
* @param <T> the type of the task result
97+
* @return a {@link Task} representing the delayed asynchronous computation
98+
* @throws IllegalStateException if the scope has already been closed
99+
*/
100+
public synchronized <T> Task<T> delayed(@NonNull Duration delay, @NonNull Callable<T> supplier) {
101+
if (closed) throw new IllegalStateException("Scope already closed");
102+
103+
CompletableFuture<T> future = new CompletableFuture<>();
104+
ScheduledFuture<?> scheduled = scheduler.schedule(() -> {
105+
try {
106+
future.complete(supplier.call());
107+
} catch (Throwable t) {
108+
future.completeExceptionally(t);
109+
}
110+
}, delay.toMillis(), TimeUnit.MILLISECONDS);
111+
112+
Task<T> task = new Task<>(future) {
113+
@Override
114+
public void cancel() {
115+
scheduled.cancel(true);
116+
super.cancel();
117+
}
118+
};
119+
120+
tasks.add(task);
121+
return task;
122+
}
123+
124+
/**
125+
* Returns the number of tasks currently tracked by this scope.
126+
*
127+
* @return number of tasks
128+
*/
34129
public synchronized int size() {
35130
return tasks.size();
36131
}
37132

133+
/**
134+
* Closes the scope.
135+
* <p>
136+
* Cancels all tasks and shuts down the executor. If multiple tasks throw exceptions during
137+
* cancellation, the first exception is rethrown and subsequent exceptions are suppressed.
138+
*
139+
* @throws Exception if any task cancellation or shutdown fails
140+
*/
38141
@Override
39142
public void close() throws Exception {
40143
List<Throwable> errors = new ArrayList<>();

src/main/java/gentle/async/Task.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,52 @@
1313
import static gentle.Result.err;
1414
import static gentle.Result.ok;
1515

16+
/**
17+
* Represents a single asynchronous task running inside a {@link Scope}.
18+
*
19+
* <p>Provides methods to cancel the task, check status, and retrieve results
20+
* in a type-safe {@link Result} wrapper.
21+
*
22+
* @param <T> type of the task's result
23+
*/
1624
@RequiredArgsConstructor (access = AccessLevel.PACKAGE)
17-
public final class Task<T> {
25+
public class Task<T> {
26+
27+
/** The underlying future representing the task computation. */
1828
private final Future<T> future;
1929

30+
/**
31+
* Cancels the task.
32+
*/
2033
public void cancel() {
2134
future.cancel(true);
2235
}
2336

37+
/**
38+
* Returns {@code true} if the task was cancelled.
39+
*
40+
* @return whether the task is cancelled
41+
*/
2442
public boolean isCancelled() {
2543
return future.isCancelled();
2644
}
2745

46+
/**
47+
* Returns {@code true} if the task is done (completed or cancelled).
48+
*
49+
* @return whether the task is done
50+
*/
2851
public boolean isDone() {
2952
return future.isDone();
3053
}
3154

32-
public Result<T, AsyncError> await() {
55+
/**
56+
* Waits for the task to complete and returns a {@link Result} containing either the value
57+
* or an {@link AsyncError}.
58+
*
59+
* @return the result of the task
60+
*/
61+
public final Result<T, AsyncError> await() {
3362
try {
3463
return ok(future.get());
3564
} catch (CancellationException | ExecutionException exception) {
@@ -40,11 +69,25 @@ public Result<T, AsyncError> await() {
4069
}
4170
}
4271

43-
public <U> Result<U, AsyncError> map(@NonNull Function<? super T, ? extends U> f) {
72+
/**
73+
* Transforms the result of this task using the provided function if successful.
74+
*
75+
* @param f mapping function
76+
* @param <U> result type of the mapping
77+
* @return a {@link Result} with the transformed value or the original error
78+
*/
79+
public final <U> Result<U, AsyncError> map(@NonNull Function<? super T, ? extends U> f) {
4480
return await().map(f);
4581
}
4682

47-
public <U> Result<U, AsyncError> flatMap(@NonNull Function<? super T, ? extends Result<U, AsyncError>> f) {
83+
/**
84+
* Transforms the result of this task using a function returning a {@link Result}.
85+
*
86+
* @param f mapping function
87+
* @param <U> result type of the mapping
88+
* @return the {@link Result} returned by the mapping function or the original error
89+
*/
90+
public final <U> Result<U, AsyncError> flatMap(@NonNull Function<? super T, ? extends Result<U, AsyncError>> f) {
4891
return await().flatMap(f);
4992
}
5093
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package gentle.async;
2+
3+
import lombok.experimental.UtilityClass;
4+
5+
@UtilityClass
6+
class Utils {
7+
8+
final int cores;
9+
10+
static {
11+
cores = Runtime.getRuntime().availableProcessors();
12+
}
13+
}

0 commit comments

Comments
 (0)