Skip to content

Commit 5d5dbfc

Browse files
authored
Better exception handling (#87)
1 parent 6a3eea9 commit 5d5dbfc

29 files changed

+444
-359
lines changed

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,15 +422,41 @@ public class Demo {
422422

423423
* an exception thrown from the scope's body, or from any of the forks, causes the scope to end
424424
* any forks that are still running are then interrupted
425-
* once all forks complete, an `ExecutionException` is thrown by the `supervised` method
426-
* the cause of the `ExecutionException` is the original exception
425+
* once all forks complete, an `JoxScopeExecutionException` is thrown by the `supervised` method
426+
* the cause of the `JoxScopeExecutionException` is the original exception
427427
* any other exceptions (e.g. `InterruptedExceptions`) that have been thrown while ending the scope, are added as
428428
suppressed
429429

430430
Jox implements the "let it crash" model. When an error occurs, the entire scope ends, propagating the exception higher,
431431
so that it can be properly handled. Moreover, no detail is lost: all exceptions are preserved, either as causes, or
432432
suppressed exceptions.
433433

434+
As `JoxScopeExecutionException` is unchecked, we introduced utility method called `JoxScopeExecutionException#unwrapAndThrow`.
435+
If the wrapped exception is instance of any of passed classes, this method unwraps original exception and throws it as checked exception, `throws` signature forces exception handling.
436+
If the wrapped exception is not instance of any of passed classes, **nothing happens**.
437+
All suppressed exceptions are rewritten from `JoxScopeExecutionException`
438+
439+
**Note** `throws` signature points to the closest super class of passed arguments.
440+
Method does **not** rethrow `JoxScopeExecutionException` by default.
441+
So it is advised to manually rethrow it after calling `unwrapAndThrow` method.
442+
443+
e.g.
444+
```java
445+
import com.softwaremill.jox.structured.JoxScopeExecutionException;
446+
import com.softwaremill.jox.structured.Scopes;
447+
448+
...
449+
try {
450+
Scopes.supervised(scope -> {
451+
throw new TestException("x");
452+
});
453+
} catch (JoxScopeExecutionException e) {
454+
e.unwrapAndThrow(OtherException.class, TestException.class, YetAnotherException.class);
455+
throw e;
456+
}
457+
...
458+
```
459+
434460
#### Other types of scopes & forks
435461

436462
There are 4 types of forks:

flows/src/main/java/com/softwaremill/jox/flows/Flow.java

Lines changed: 65 additions & 63 deletions
Large diffs are not rendered by default.

flows/src/main/java/com/softwaremill/jox/flows/GroupByImpl.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
import java.util.Map;
88
import java.util.Optional;
99
import java.util.function.Function;
10+
import java.util.stream.Stream;
1011

1112
import com.softwaremill.jox.Channel;
1213
import com.softwaremill.jox.ChannelDone;
1314
import com.softwaremill.jox.ChannelError;
1415
import com.softwaremill.jox.SelectClause;
1516
import com.softwaremill.jox.Source;
17+
import com.softwaremill.jox.structured.JoxScopeExecutionException;
1618
import com.softwaremill.jox.structured.Scopes;
19+
import com.softwaremill.jox.structured.ThrowingFunction;
1720
import com.softwaremill.jox.structured.UnsupervisedScope;
1821

1922
class GroupByImpl<T, V, U> {
@@ -48,10 +51,10 @@ public ChildDone(V v) {
4851

4952
private final Flow<T> parent;
5053
private final int parallelism;
51-
private final Function<T, V> predicate;
54+
private final ThrowingFunction<T, V> predicate;
5255
private final Flow.ChildFlowTransformer<T, V, U> childFlowTransform;
5356

54-
public GroupByImpl(Flow<T> parent, int parallelism, Function<T, V> predicate, Flow.ChildFlowTransformer<T, V, U> childFlowTransform) {
57+
public GroupByImpl(Flow<T> parent, int parallelism, ThrowingFunction<T, V> predicate, Flow.ChildFlowTransformer<T, V, U> childFlowTransform) {
5558
this.parent = parent;
5659
this.parallelism = parallelism;
5760
this.predicate = predicate;

flows/src/test/java/com/softwaremill/jox/flows/FlowAlsoToTest.java

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,7 @@ void alsoToTap_shouldSendToBothSinksWhenOtherIsFaster() throws Exception {
127127
Flow<Integer> flow = Flows
128128
.fromValues(1, 2, 3)
129129
.alsoToTap(other)
130-
.tap(v -> {
131-
try {
132-
Thread.sleep(50);
133-
} catch (InterruptedException e) {
134-
throw new RuntimeException(e);
135-
}
136-
});
130+
.tap(_ -> Thread.sleep(50));
137131

138132
// when & then
139133
assertEquals(List.of(1, 2, 3), flow.runToList());
@@ -193,13 +187,7 @@ void alsoTapTo_shouldNotFailTheFlowWhenTheOtherSinkFails() throws Exception {
193187
List<Integer> result = Flows
194188
.iterate(1, i -> i + 1)
195189
.take(10)
196-
.tap(v -> {
197-
try {
198-
Thread.sleep(10);
199-
} catch (InterruptedException e) {
200-
throw new RuntimeException(e);
201-
}
202-
})
190+
.tap(_ -> Thread.sleep(10))
203191
.alsoToTap(other)
204192
.runToList();
205193

@@ -225,13 +213,7 @@ void alsoTapTo_shouldNotCloseTheFlowWhenTheOtherSinkCloses() throws Exception {
225213
List<Integer> result = Flows
226214
.iterate(1, i -> i + 1)
227215
.take(10)
228-
.tap(v -> {
229-
try {
230-
Thread.sleep(10);
231-
} catch (InterruptedException e) {
232-
throw new RuntimeException(e);
233-
}
234-
})
216+
.tap(_ -> Thread.sleep(10))
235217
.alsoToTap(other)
236218
.runToList();
237219

flows/src/test/java/com/softwaremill/jox/flows/FlowCompleteCallbacksTest.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.softwaremill.jox.flows;
22

3-
import org.junit.jupiter.api.Test;
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
46

57
import java.util.concurrent.atomic.AtomicBoolean;
68

7-
import static org.junit.jupiter.api.Assertions.*;
9+
import org.junit.jupiter.api.Test;
810

911
public class FlowCompleteCallbacksTest {
1012
@Test
@@ -27,7 +29,7 @@ void ensureOnCompleteRunsInCaseOfError() {
2729
//given
2830
AtomicBoolean didRun = new AtomicBoolean(false);
2931
Flow<Integer> f = Flows.fromValues(1, 2, 3)
30-
.tap(i -> {throw new RuntimeException();})
32+
.tap(_ -> {throw new RuntimeException();})
3133
.onComplete(() -> didRun.set(true));
3234
assertFalse(didRun.get());
3335

@@ -57,7 +59,7 @@ void ensureOnDoneDoesNotRunInCaseOfError() {
5759
// given
5860
AtomicBoolean didRun = new AtomicBoolean(false);
5961
Flow<Integer> f = Flows.fromValues(1, 2, 3)
60-
.tap(i -> {throw new RuntimeException();})
62+
.tap(_ -> {throw new RuntimeException();})
6163
.onDone(() -> didRun.set(true));
6264
assertFalse(didRun.get());
6365

@@ -73,7 +75,7 @@ void ensureOnErrorDoesNotRunInCaseOfSuccess() throws Exception {
7375
// given
7476
AtomicBoolean didRun = new AtomicBoolean(false);
7577
Flow<Integer> f = Flows.fromValues(1, 2, 3)
76-
.onError(e -> didRun.set(true));
78+
.onError(_ -> didRun.set(true));
7779
assertFalse(didRun.get());
7880

7981
// when
@@ -88,8 +90,8 @@ void ensureOnErrorRunsInCaseOfError() {
8890
// given
8991
AtomicBoolean didRun = new AtomicBoolean(false);
9092
Flow<Integer> f = Flows.fromValues(1, 2, 3)
91-
.tap(i -> {throw new RuntimeException();})
92-
.onError(e -> didRun.set(true));
93+
.tap(_ -> {throw new RuntimeException();})
94+
.onError(_ -> didRun.set(true));
9395
assertFalse(didRun.get());
9496

9597
// when

0 commit comments

Comments
 (0)