-
Notifications
You must be signed in to change notification settings - Fork 0
/
SabotageTest.java
187 lines (161 loc) · 6.69 KB
/
SabotageTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package de.pollmann.watchdog;
import de.pollmann.watchdog.tasks.ExceptionConsumer;
import de.pollmann.watchdog.tasks.ExceptionRunnable;
import de.pollmann.watchdog.tasks.Watchable;
import de.pollmann.watchdog.tasks.WatchableWithInput;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
public class SabotageTest {
/**
* This runnable will block the ExecutorService, not even a timeout occur (thread cannot be interrupted!)
*/
private static class Sabotage implements ExceptionRunnable {
@Override
public void run() throws Exception {
int i = 1;
while (i > 0) {
i++;
if (i >= 1000) {
i = 1;
}
}
}
}
/**
* The runnable {@link Sabotage} will block the ExecutorService!
*/
@Test
@Timeout(2)
void runnable_userTriesEverythingToSabotageTheTimeout_TIMEOUT_threadIsNeverFinished() throws InterruptedException {
WatchdogFactory watchdogFactory = new WatchdogFactory(2);
Watchable<Object> sabotage = Watchable.builder(new Sabotage()).build();
AtomicBoolean sabotageStarted = new AtomicBoolean(false);
AtomicBoolean sabotageStopped = new AtomicBoolean(false);
AtomicBoolean wasInterrupted = new AtomicBoolean(false);
// wrapp the call 'sabotage' in a timeout
// => interrupt
// => cancel the wrapped call but leaves the Thread of the Executor Service in an infinite loop
// => at some point the watchdogFactory does not have any thread remaining
TaskResult<?> wrappedCall = watchdogFactory.waitForCompletion(WatchableOptions.builder(1300).build(), Watchable.builder(() -> {
sabotageStarted.set(true);
try {
// timeout can NOT kill the Thread because the submitted task is not interruptable
TaskResult<?> neverCreated = watchdogFactory.waitForCompletion(WatchableOptions.builder(1000).build(), sabotage);
// the runnable does not respond to an interrupt => not stopped => infinite loop
// unreachable ...
sabotageStopped.set(true);
} catch (InterruptedException interruptedException) {
// But "waitForCompletion" itself is interruptable
wasInterrupted.set(true);
throw interruptedException;
}
}).build());
Assertions.assertTrue(wasInterrupted.get());
Assertions.assertFalse(sabotageStopped.get());
Assertions.assertTrue(sabotageStarted.get());
Assertions.assertFalse(sabotage.stopped());
assertTimeout(wrappedCall);
}
/**
* This runnable will not the ExecutorService because of the {@link InterruptedException}
*/
private static class NiceSabotageRunnable implements ExceptionRunnable {
@Override
public void run() throws Exception {
int i = 1;
while (i > 0) {
// add this if to your code to stop the runnable if a timeout occurred
if (Thread.interrupted()) {
throw new InterruptedException();
}
// e.g. thread sleep does that as well
Thread.sleep(10);
i++;
if (i >= 1000) {
i = 1;
}
}
}
}
/**
* The runnable {@link NiceSabotageRunnable} will NOT block the ExecutorService!
*/
@Test
@Timeout(10)
void runnable_endlessLoopRespondingToInterrupts_TIMEOUT_threadGetsInterrupted() throws InterruptedException {
WatchdogFactory watchdogFactory = new WatchdogFactory();
for (int i = 0; i < 100; i++) {
Watchable<Object> niceSabotage = Watchable.builder(new NiceSabotageRunnable()).build();
assertTimeout(watchdogFactory.waitForCompletion(WatchableOptions.builder(50).build(), niceSabotage));
// the runnable does respond to an interrupt => stopped => no endless loop
Assertions.assertTrue(niceSabotage.stopped());
}
}
@Test
@Timeout(10)
void consumer_endlessLoopRespondingToInterrupts_TIMEOUT_threadGetsInterrupted() throws InterruptedException {
WatchdogFactory watchdogFactory = new WatchdogFactory();
for (int i = 0; i < 100; i++) {
Watchable<Object> niceSabotage = Watchable.builder(new NiceSabotageConsumer()).build();
assertTimeout(watchdogFactory.waitForCompletion(WatchableOptions.builder(50).build(), niceSabotage));
// the runnable does respond to interrupts => stopped => no endless loop
Assertions.assertTrue(niceSabotage.stopped());
}
}
@Test
@Timeout(10)
void repeatableRunnable_endlessLoopRespondingToInterrupts_TIMEOUT_threadGetsInterrupted() throws InterruptedException {
WatchdogFactory watchdogFactory = new WatchdogFactory();
Watchable<Object> niceSabotage = Watchable.builder(new NiceSabotageRunnable())
.withResultConsumer(result -> Assertions.assertTrue(result.getWatchable().stopped()))
.build();
RepeatableTaskWithoutInput<Object> repeatable = watchdogFactory.createRepeated(WatchableOptions.builder(50).build(), niceSabotage);
for (int i = 0; i < 100; i++) {
assertTimeout(repeatable.waitForCompletion());
}
}
@Test
@Timeout(10)
void repeatableConsumer_endlessLoopRespondingToInterrupts_TIMEOUT_threadGetsInterrupted() throws InterruptedException {
WatchdogFactory watchdogFactory = new WatchdogFactory();
WatchableWithInput<Integer, Object> niceSabotage = Watchable.builder(new NiceSabotageConsumer())
.withResultConsumer(result -> Assertions.assertTrue(result.getWatchable().stopped()))
.build();
RepeatableTaskWithInput<Integer, Object> repeatable = watchdogFactory.createRepeated(WatchableOptions.builder(50).build(), niceSabotage);
for (int i = 0; i < 100; i++) {
assertTimeout(repeatable.waitForCompletion(i));
}
}
private void assertTimeout(TaskResult<?> result) {
Assertions.assertNotNull(result);
Assertions.assertTrue(result.hasError());
Assertions.assertEquals(ResultCode.TIMEOUT, result.getCode(), String.format("Error: %s", result.getErrorReason()));
Assertions.assertNotNull(result.getErrorReason());
Assertions.assertNull(result.getResult());
Assertions.assertTrue(result.getErrorReason() instanceof TimeoutException);
}
/**
* This runnable will not the ExecutorService because of the {@link InterruptedException}
*/
private static class NiceSabotageConsumer implements ExceptionConsumer<Integer> {
@Override
public void accept(Integer t) throws Exception {
int i = 1;
while (i > 0) {
// add this if to your code to stop the runnable if a timeout occurred
if (Thread.interrupted()) {
throw new InterruptedException();
}
// e.g. thread sleep does that as well
Thread.sleep(10);
i++;
if (i >= 1000) {
i = 1;
}
}
}
}
}