Skip to content

Commit b7798d0

Browse files
authored
Merge pull request #744 from jooby-project/743
file monitor should stop background thread on application shutdown fi…
2 parents 9de3308 + 00a055f commit b7798d0

File tree

2 files changed

+79
-29
lines changed

2 files changed

+79
-29
lines changed

jooby-filewatcher/src/main/java/org/jooby/filewatcher/FileMonitor.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@
3636
import java.util.Map;
3737
import java.util.Set;
3838
import java.util.concurrent.ConcurrentHashMap;
39+
import java.util.concurrent.ExecutorService;
40+
import java.util.concurrent.Executors;
3941

4042
import javax.inject.Inject;
4143

44+
import org.jooby.Env;
4245
import org.slf4j.Logger;
4346
import org.slf4j.LoggerFactory;
4447

@@ -60,16 +63,20 @@ class FileMonitor implements Runnable {
6063
private final Set<FileEventOptions> optionList;
6164

6265
@Inject
63-
public FileMonitor(final Injector injector,
66+
public FileMonitor(final Injector injector, final Env env,
6467
final WatchService watcher, final Set<FileEventOptions> optionList) throws IOException {
6568
this.injector = injector;
6669
this.watcher = watcher;
6770
this.optionList = optionList;
6871

6972
// start monitor:
70-
Thread thread = new Thread(this, "file-watcher");
71-
thread.setDaemon(true);
72-
thread.start();
73+
ExecutorService monitor = Executors.newSingleThreadExecutor(task -> {
74+
Thread thread = new Thread(task, "file-watcher");
75+
thread.setDaemon(true);
76+
return thread;
77+
});
78+
env.onStop(monitor::shutdown);
79+
monitor.execute(this);
7380
}
7481

7582
@Override

jooby-filewatcher/src/test/java/org/jooby/filewatcher/FileMonitorTest.java

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@
1717
import java.nio.file.WatchEvent.Modifier;
1818
import java.nio.file.WatchKey;
1919
import java.nio.file.WatchService;
20+
import java.util.concurrent.ExecutorService;
21+
import java.util.concurrent.Executors;
22+
import java.util.concurrent.ThreadFactory;
2023
import java.util.function.Function;
2124

2225
import org.easymock.IExpectationSetters;
26+
import org.jooby.Env;
2327
import org.jooby.test.MockUnit;
2428
import org.jooby.test.MockUnit.Block;
2529
import org.junit.Test;
@@ -31,17 +35,28 @@
3135
import com.google.common.collect.ImmutableSet;
3236
import com.google.inject.Injector;
3337

38+
import javaslang.control.Try.CheckedRunnable;
39+
3440
@RunWith(PowerMockRunner.class)
35-
@PrepareForTest({FileMonitor.class, Thread.class, Files.class })
41+
@PrepareForTest({FileMonitor.class, Thread.class, Files.class, Executors.class })
3642
public class FileMonitorTest {
3743

3844
private Block newThread = unit -> {
45+
unit.mockStatic(Executors.class);
46+
ExecutorService executor = unit.mock(ExecutorService.class);
47+
expect(Executors.newSingleThreadExecutor(unit.capture(ThreadFactory.class)))
48+
.andReturn(executor);
49+
executor.execute(unit.capture(Runnable.class));
50+
unit.registerMock(ExecutorService.class, executor);
51+
52+
Env env = unit.get(Env.class);
53+
expect(env.onStop(unit.capture(CheckedRunnable.class))).andReturn(env);
54+
3955
Thread thread = unit.constructor(Thread.class)
4056
.args(Runnable.class, String.class)
41-
.build(unit.capture(Runnable.class), eq("file-watcher"));
57+
.build(isA(Runnable.class), eq("file-watcher"));
4258

4359
thread.setDaemon(true);
44-
thread.start();
4560
};
4661
private Block takeInterrupt = unit -> {
4762
WatchService watcher = unit.get(WatchService.class);
@@ -60,25 +75,35 @@ public class FileMonitorTest {
6075

6176
@Test
6277
public void newFileMonitor() throws Exception {
63-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class)
78+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class)
6479
.expect(newThread)
80+
.expect(unit -> {
81+
ExecutorService executor = unit.get(ExecutorService.class);
82+
executor.shutdown();
83+
})
6584
.run(unit -> {
66-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
85+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
86+
unit.get(WatchService.class),
6787
ImmutableSet.of(unit.get(FileEventOptions.class)));
88+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
89+
unit.captured(CheckedRunnable.class).get(0).run();
90+
;
6891
});
6992
}
7093

7194
@SuppressWarnings("unchecked")
7295
@Test
7396
public void registerTreeRecursive() throws Exception {
74-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
97+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
7598
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class)
7699
.expect(newThread)
77100
.expect(registerTree(true, false))
78101
.expect(takeInterrupt)
79102
.run(unit -> {
80-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
103+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
104+
unit.get(WatchService.class),
81105
ImmutableSet.of(unit.get(FileEventOptions.class)));
106+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
82107
}, unit -> {
83108
unit.captured(Runnable.class).get(0).run();
84109
unit.captured(FileVisitor.class).get(0).preVisitDirectory(unit.get(Path.class), null);
@@ -87,45 +112,51 @@ public void registerTreeRecursive() throws Exception {
87112

88113
@Test
89114
public void registerTree() throws Exception {
90-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
115+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
91116
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class)
92117
.expect(newThread)
93118
.expect(registerTree(false, false))
94119
.expect(takeInterrupt)
95120
.run(unit -> {
96-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
121+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
122+
unit.get(WatchService.class),
97123
ImmutableSet.of(unit.get(FileEventOptions.class)));
124+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
98125
}, unit -> {
99126
unit.captured(Runnable.class).get(0).run();
100127
});
101128
}
102129

103130
@Test
104131
public void registerTreeErr() throws Exception {
105-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
132+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
106133
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class)
107134
.expect(newThread)
108135
.expect(registerTree(false, true))
109136
.expect(takeInterrupt)
110137
.run(unit -> {
111-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
138+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
139+
unit.get(WatchService.class),
112140
ImmutableSet.of(unit.get(FileEventOptions.class)));
141+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
113142
}, unit -> {
114143
unit.captured(Runnable.class).get(0).run();
115144
});
116145
}
117146

118147
@Test
119148
public void pollIgnoreMissing() throws Exception {
120-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
149+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
121150
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class)
122151
.expect(newThread)
123152
.expect(registerTree(false, false))
124153
.expect(takeIgnore)
125154
.expect(takeInterrupt)
126155
.run(unit -> {
127-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
156+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
157+
unit.get(WatchService.class),
128158
ImmutableSet.of(unit.get(FileEventOptions.class)));
159+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
129160
}, unit -> {
130161
unit.captured(Runnable.class).get(0).run();
131162
});
@@ -134,7 +165,7 @@ public void pollIgnoreMissing() throws Exception {
134165
@Test
135166
public void pollEvents() throws Exception {
136167
Path path = Paths.get("target/foo.txt");
137-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
168+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
138169
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class,
139170
PathMatcher.class, FileEventHandler.class)
140171
.expect(newThread)
@@ -147,8 +178,10 @@ public void pollEvents() throws Exception {
147178
.expect(reset(true))
148179
.expect(takeInterrupt)
149180
.run(unit -> {
150-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
181+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
182+
unit.get(WatchService.class),
151183
ImmutableSet.of(unit.get(FileEventOptions.class)));
184+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
152185
}, unit -> {
153186
unit.captured(Runnable.class).get(0).run();
154187
});
@@ -157,7 +190,7 @@ public void pollEvents() throws Exception {
157190
@Test
158191
public void pollEventsInvalid() throws Exception {
159192
Path path = Paths.get("target/foo.txt");
160-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
193+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
161194
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class,
162195
PathMatcher.class, FileEventHandler.class)
163196
.expect(newThread)
@@ -169,8 +202,10 @@ public void pollEventsInvalid() throws Exception {
169202
.expect(recursive(false, false))
170203
.expect(reset(false))
171204
.run(unit -> {
172-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
205+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
206+
unit.get(WatchService.class),
173207
ImmutableSet.of(unit.get(FileEventOptions.class)));
208+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
174209
}, unit -> {
175210
unit.captured(Runnable.class).get(0).run();
176211
});
@@ -179,7 +214,7 @@ public void pollEventsInvalid() throws Exception {
179214
@Test
180215
public void pollEventsRecursive() throws Exception {
181216
Path path = Paths.get("target/foo.txt");
182-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
217+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
183218
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class,
184219
PathMatcher.class, FileEventHandler.class)
185220
.expect(newThread)
@@ -192,8 +227,10 @@ public void pollEventsRecursive() throws Exception {
192227
.expect(reset(true))
193228
.expect(takeInterrupt)
194229
.run(unit -> {
195-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
230+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
231+
unit.get(WatchService.class),
196232
ImmutableSet.of(unit.first(FileEventOptions.class)));
233+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
197234
}, unit -> {
198235
unit.captured(Runnable.class).get(0).run();
199236
});
@@ -202,7 +239,7 @@ public void pollEventsRecursive() throws Exception {
202239
@Test
203240
public void pollEventsRecursiveErr() throws Exception {
204241
Path path = Paths.get("target/foo.txt");
205-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
242+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
206243
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class,
207244
PathMatcher.class, FileEventHandler.class)
208245
.expect(newThread)
@@ -215,8 +252,10 @@ public void pollEventsRecursiveErr() throws Exception {
215252
.expect(reset(true))
216253
.expect(takeInterrupt)
217254
.run(unit -> {
218-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
255+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
256+
unit.get(WatchService.class),
219257
ImmutableSet.of(unit.first(FileEventOptions.class)));
258+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
220259
}, unit -> {
221260
unit.captured(Runnable.class).get(0).run();
222261
});
@@ -225,7 +264,7 @@ public void pollEventsRecursiveErr() throws Exception {
225264
@Test
226265
public void pollEventWithHandleErr() throws Exception {
227266
Path path = Paths.get("target/foo.txt");
228-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
267+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
229268
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class,
230269
PathMatcher.class, FileEventHandler.class)
231270
.expect(newThread)
@@ -238,8 +277,10 @@ public void pollEventWithHandleErr() throws Exception {
238277
.expect(reset(true))
239278
.expect(takeInterrupt)
240279
.run(unit -> {
241-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
280+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
281+
unit.get(WatchService.class),
242282
ImmutableSet.of(unit.get(FileEventOptions.class)));
283+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
243284
}, unit -> {
244285
unit.captured(Runnable.class).get(0).run();
245286
});
@@ -248,7 +289,7 @@ public void pollEventWithHandleErr() throws Exception {
248289
@Test
249290
public void pollEventsNoMatches() throws Exception {
250291
Path path = Paths.get("target/foo.txt");
251-
new MockUnit(Injector.class, WatchService.class, FileEventOptions.class, Path.class,
292+
new MockUnit(Injector.class, Env.class, WatchService.class, FileEventOptions.class, Path.class,
252293
WatchEvent.Kind.class, WatchEvent.Modifier.class, WatchKey.class, WatchEvent.class,
253294
PathMatcher.class, FileEventHandler.class)
254295
.expect(newThread)
@@ -260,8 +301,10 @@ public void pollEventsNoMatches() throws Exception {
260301
.expect(reset(true))
261302
.expect(takeInterrupt)
262303
.run(unit -> {
263-
new FileMonitor(unit.get(Injector.class), unit.get(WatchService.class),
304+
FileMonitor monitor = new FileMonitor(unit.get(Injector.class), unit.get(Env.class),
305+
unit.get(WatchService.class),
264306
ImmutableSet.of(unit.get(FileEventOptions.class)));
307+
unit.captured(ThreadFactory.class).get(0).newThread(monitor);
265308
}, unit -> {
266309
unit.captured(Runnable.class).get(0).run();
267310
});

0 commit comments

Comments
 (0)