Skip to content

Commit 6f436a8

Browse files
authored
Merge pull request #30 from SWAT-engineering/improved-overflow-support/update-readme
Improved overflow support: Update README
2 parents 61ebbb8 + 58472c5 commit 6f436a8

File tree

8 files changed

+37
-34
lines changed

8 files changed

+37
-34
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ a java file watcher that works across platforms and supports recursion, single f
77
Features:
88

99
- monitor a single file (or directory) for changes
10-
- monitor a directory for changes to it's direct descendants
11-
- monitor a directory for changes for all it's descendants (aka recursive directory watch)
10+
- monitor a directory for changes to its direct descendants
11+
- monitor a directory for changes for all its descendants (aka recursive directory watch)
1212
- edge cases dealt with:
13-
- in case of overflow we will still generate events for new descendants
1413
- recursive watches will also continue in new directories
1514
- multiple watches for the same directory are merged to avoid overloading the kernel
1615
- events are processed in a configurable worker pool
16+
- when an overflow happens, automatically approximate the events that were
17+
missed using a configurable approximation policy
1718

1819
Planned features:
1920

@@ -39,6 +40,7 @@ Start using java-watch:
3940
var directory = Path.of("tmp", "test-dir");
4041
var watcherSetup = Watcher.watch(directory, WatchScope.PATH_AND_CHILDREN)
4142
.withExecutor(Executors.newCachedThreadPool()) // optionally configure a custom thread pool
43+
.onOverflow(Approximation.DIRTY) // optionally configure a handler for overflows
4244
.on(watchEvent -> {
4345
System.err.println(watchEvent);
4446
});

src/main/java/engineering/swat/watch/OnOverflow.java renamed to src/main/java/engineering/swat/watch/Approximation.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
* overflow issue), but it doesn't have to (e.g., it may carry out additional
3939
* overflow bookkeeping).
4040
*/
41-
public enum OnOverflow {
41+
public enum Approximation {
4242

4343
/**
4444
* Synthetic events are issued for <b>no regular files/directories</b> in
@@ -66,8 +66,8 @@ public enum OnOverflow {
6666
*
6767
* <p>
6868
* This approach is relatively cheap in terms of memory usage (cf.
69-
* {@link #DIRTY}), but it results in a large over/underapproximation of the
70-
* actual events (cf. DIRTY).
69+
* {@link #DIFF}), but it results in a large over/underapproximation of the
70+
* actual events (cf. DIFF).
7171
*/
7272
ALL,
7373

@@ -76,7 +76,8 @@ public enum OnOverflow {
7676
* <p>
7777
* Synthetic events of kinds {@link WatchEvent.Kind#CREATED},
7878
* {@link WatchEvent.Kind#MODIFIED}, and {@link WatchEvent.Kind#DELETED} are
79-
* issued for dirty regular files/directories in the scope of the watch, as
79+
* issued for regular files/directories in the scope of the watch, when
80+
* their current versions are different from their previous versions, as
8081
* determined using <i>last-modified-times</i>. Specifically, when an
8182
* overflow event happens:
8283
*
@@ -103,5 +104,5 @@ public enum OnOverflow {
103104
* but it is relatively expensive in terms of memory usage (cf. ALL), as the
104105
* watch needs to keep track of last-modified-times.
105106
*/
106-
DIRTY
107+
DIFF
107108
}

src/main/java/engineering/swat/watch/Watcher.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public class Watcher {
5757
private final Logger logger = LogManager.getLogger();
5858
private final Path path;
5959
private final WatchScope scope;
60-
private volatile OnOverflow approximateOnOverflow = OnOverflow.ALL;
60+
private volatile Approximation approximateOnOverflow = Approximation.ALL;
6161
private volatile Executor executor = CompletableFuture::runAsync;
6262

6363
private static final BiConsumer<EventHandlingWatch, WatchEvent> EMPTY_HANDLER = (w, e) -> {};
@@ -174,12 +174,12 @@ public Watcher withExecutor(Executor callbackHandler) {
174174
* {@link WatchEvent.Kind#CREATED}, {@link WatchEvent.Kind#MODIFIED}, and/or
175175
* {@link WatchEvent.Kind#DELETED}) should be issued when an overflow event
176176
* happens. If not defined before this watcher is started, the
177-
* {@link engineering.swat.watch.OnOverflow#ALL} approach will be used.
177+
* {@link Approximation#ALL} approach will be used.
178178
* @param whichFiles Constant to indicate for which regular
179179
* files/directories to approximate
180180
* @return This watcher for optional method chaining
181181
*/
182-
public Watcher approximate(OnOverflow whichFiles) {
182+
public Watcher onOverflow(Approximation whichFiles) {
183183
this.approximateOnOverflow = whichFiles;
184184
return this;
185185
}
@@ -233,7 +233,7 @@ private BiConsumer<EventHandlingWatch, WatchEvent> applyApproximateOnOverflow()
233233
return eventHandler;
234234
case ALL:
235235
return eventHandler.andThen(new MemorylessRescanner(executor));
236-
case DIRTY:
236+
case DIFF:
237237
return eventHandler.andThen(new IndexingRescanner(executor, path, scope));
238238
default:
239239
throw new UnsupportedOperationException("No event handler has been defined yet for this overflow policy");

src/test/java/engineering/swat/watch/RecursiveWatchTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ void deleteOfFileInDirectoryShouldBeVisible() throws IOException {
149149
}
150150

151151
@ParameterizedTest
152-
@EnumSource // Repeat test for each `OnOverflow` value
153-
void overflowsAreRecoveredFrom(OnOverflow whichFiles) throws IOException, InterruptedException {
152+
@EnumSource // Repeat test for each `Approximation` value
153+
void overflowsAreRecoveredFrom(Approximation whichFiles) throws IOException, InterruptedException {
154154
var parent = testDir.getTestDirectory();
155155
var descendants = new Path[] {
156156
Path.of("foo"),
@@ -180,7 +180,7 @@ void overflowsAreRecoveredFrom(OnOverflow whichFiles) throws IOException, Interr
180180
var dropEvents = new AtomicBoolean(false); // Toggles overflow simulation
181181
var watchConfig = Watcher.watch(parent, WatchScope.PATH_AND_ALL_DESCENDANTS)
182182
.withExecutor(ForkJoinPool.commonPool())
183-
.approximate(whichFiles)
183+
.onOverflow(whichFiles)
184184
.filter(e -> !dropEvents.get())
185185
.on(events::add);
186186

@@ -207,7 +207,7 @@ void overflowsAreRecoveredFrom(OnOverflow whichFiles) throws IOException, Interr
207207
var overflow = new WatchEvent(WatchEvent.Kind.OVERFLOW, parent);
208208
watch.handleEvent(overflow);
209209

210-
if (whichFiles != OnOverflow.NONE) { // Auto-handler is configured
210+
if (whichFiles != Approximation.NONE) { // Auto-handler is configured
211211
for (var descendant : descendants) {
212212
awaitCreation.accept(descendant);
213213
awaitCreation.accept(descendant.resolve(file1));

src/test/java/engineering/swat/watch/SingleDirectoryTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ void memorylessRescanOnOverflow() throws IOException, InterruptedException {
134134
var nModified = new AtomicInteger();
135135
var nOverflow = new AtomicInteger();
136136
var watchConfig = Watcher.watch(directory, WatchScope.PATH_AND_CHILDREN)
137-
.approximate(OnOverflow.ALL)
137+
.onOverflow(Approximation.ALL)
138138
.on(e -> {
139139
switch (e.getKind()) {
140140
case CREATED:
@@ -179,7 +179,7 @@ void indexingRescanOnOverflow() throws IOException, InterruptedException {
179179
var nDeleted = new AtomicInteger();
180180

181181
var watchConfig = Watcher.watch(directory, WatchScope.PATH_AND_CHILDREN)
182-
.approximate(OnOverflow.DIRTY)
182+
.onOverflow(Approximation.DIFF)
183183
.on(e -> {
184184
var kind = e.getKind();
185185
if (kind != OVERFLOW) {

src/test/java/engineering/swat/watch/SingleFileTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ void singleFileThatMonitorsOnlyADirectory() throws IOException, InterruptedExcep
135135
@Test
136136
void noRescanOnOverflow() throws IOException, InterruptedException {
137137
var bookkeeper = new Bookkeeper();
138-
try (var watch = startWatchAndTriggerOverflow(OnOverflow.NONE, bookkeeper)) {
138+
try (var watch = startWatchAndTriggerOverflow(Approximation.NONE, bookkeeper)) {
139139
Thread.sleep(TestHelper.SHORT_WAIT.toMillis());
140140

141141
await("Overflow shouldn't trigger created, modified, or deleted events")
@@ -148,7 +148,7 @@ void noRescanOnOverflow() throws IOException, InterruptedException {
148148
@Test
149149
void memorylessRescanOnOverflow() throws IOException, InterruptedException {
150150
var bookkeeper = new Bookkeeper();
151-
try (var watch = startWatchAndTriggerOverflow(OnOverflow.ALL, bookkeeper)) {
151+
try (var watch = startWatchAndTriggerOverflow(Approximation.ALL, bookkeeper)) {
152152
Thread.sleep(TestHelper.SHORT_WAIT.toMillis());
153153

154154
var isFile = Predicate.isEqual(watch.getPath());
@@ -167,13 +167,13 @@ void memorylessRescanOnOverflow() throws IOException, InterruptedException {
167167
}
168168
}
169169

170-
private ActiveWatch startWatchAndTriggerOverflow(OnOverflow whichFiles, Bookkeeper bookkeeper) throws IOException {
170+
private ActiveWatch startWatchAndTriggerOverflow(Approximation whichFiles, Bookkeeper bookkeeper) throws IOException {
171171
var parent = testDir.getTestDirectory();
172172
var file = parent.resolve("a.txt");
173173

174174
var watch = Watcher
175175
.watch(file, WatchScope.PATH_ONLY)
176-
.approximate(whichFiles)
176+
.onOverflow(whichFiles)
177177
.on(bookkeeper)
178178
.start();
179179

src/test/java/engineering/swat/watch/TortureTests.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ Set<Path> stop() throws InterruptedException {
145145
private static final int THREADS = 4;
146146

147147
@ParameterizedTest
148-
@EnumSource(names = { "ALL", "DIRTY" })
149-
void pressureOnFSShouldNotMissNewFilesAnything(OnOverflow whichFiles) throws InterruptedException, IOException {
148+
@EnumSource(names = { "ALL", "DIFF" })
149+
void pressureOnFSShouldNotMissNewFilesAnything(Approximation whichFiles) throws InterruptedException, IOException {
150150
final var root = testDir.getTestDirectory();
151151
var pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4);
152152

@@ -155,7 +155,7 @@ void pressureOnFSShouldNotMissNewFilesAnything(OnOverflow whichFiles) throws Int
155155
var seenCreates = ConcurrentHashMap.<Path>newKeySet();
156156
var watchConfig = Watcher.watch(testDir.getTestDirectory(), WatchScope.PATH_AND_ALL_DESCENDANTS)
157157
.withExecutor(pool)
158-
.approximate(whichFiles)
158+
.onOverflow(whichFiles)
159159
.on(ev -> {
160160
var fullPath = ev.calculateFullPath();
161161
switch (ev.getKind()) {
@@ -267,14 +267,14 @@ void manyRegistrationsForSamePath() throws InterruptedException, IOException {
267267
}
268268
}
269269

270-
static Stream<OnOverflow> manyRegisterAndUnregisterSameTimeSource() {
271-
OnOverflow[] values = { OnOverflow.ALL, OnOverflow.DIRTY };
270+
static Stream<Approximation> manyRegisterAndUnregisterSameTimeSource() {
271+
Approximation[] values = { Approximation.ALL, Approximation.DIFF };
272272
return TestHelper.streamOf(values, 5);
273273
}
274274

275275
@ParameterizedTest
276276
@MethodSource("manyRegisterAndUnregisterSameTimeSource")
277-
void manyRegisterAndUnregisterSameTime(OnOverflow whichFiles) throws InterruptedException, IOException {
277+
void manyRegisterAndUnregisterSameTime(Approximation whichFiles) throws InterruptedException, IOException {
278278
var startRegistering = new Semaphore(0);
279279
var startedWatching = new Semaphore(0);
280280
var stopAll = new Semaphore(0);
@@ -296,7 +296,7 @@ void manyRegisterAndUnregisterSameTime(OnOverflow whichFiles) throws Interrupted
296296
for (int k = 0; k < 1000; k++) {
297297
var watcher = Watcher
298298
.watch(testDir.getTestDirectory(), WatchScope.PATH_AND_CHILDREN)
299-
.approximate(whichFiles)
299+
.onOverflow(whichFiles)
300300
.on(e -> {
301301
if (e.calculateFullPath().equals(target)) {
302302
seen.add(id);
@@ -342,10 +342,10 @@ void manyRegisterAndUnregisterSameTime(OnOverflow whichFiles) throws Interrupted
342342
}
343343

344344
@ParameterizedTest
345-
@EnumSource(names = { "ALL", "DIRTY" })
345+
@EnumSource(names = { "ALL", "DIFF" })
346346
//Deletes can race the filesystem, so you might miss a few files in a dir, if that dir is already deleted
347347
@EnabledIfEnvironmentVariable(named="TORTURE_DELETE", matches="true")
348-
void pressureOnFSShouldNotMissDeletes(OnOverflow whichFiles) throws InterruptedException, IOException {
348+
void pressureOnFSShouldNotMissDeletes(Approximation whichFiles) throws InterruptedException, IOException {
349349
final var root = testDir.getTestDirectory();
350350
var pool = Executors.newCachedThreadPool();
351351

@@ -361,7 +361,7 @@ void pressureOnFSShouldNotMissDeletes(OnOverflow whichFiles) throws InterruptedE
361361
final var happened = new Semaphore(0);
362362
var watchConfig = Watcher.watch(testDir.getTestDirectory(), WatchScope.PATH_AND_ALL_DESCENDANTS)
363363
.withExecutor(pool)
364-
.approximate(whichFiles)
364+
.onOverflow(whichFiles)
365365
.on(ev -> {
366366
events.getAndIncrement();
367367
happened.release();

src/test/java/engineering/swat/watch/impl/overflows/IndexingRescannerTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import org.junit.jupiter.api.BeforeEach;
3939
import org.junit.jupiter.api.Test;
4040

41-
import engineering.swat.watch.OnOverflow;
41+
import engineering.swat.watch.Approximation;
4242
import engineering.swat.watch.TestDirectory;
4343
import engineering.swat.watch.TestHelper;
4444
import engineering.swat.watch.WatchEvent;
@@ -75,7 +75,7 @@ void onlyEventsForFilesInScopeAreIssued() throws IOException, InterruptedExcepti
7575
// children (not all descendants) of `path`
7676
var eventsOnlyForChildren = new AtomicBoolean(true);
7777
var watchConfig = Watcher.watch(path, WatchScope.PATH_AND_CHILDREN)
78-
.approximate(OnOverflow.NONE) // Disable the auto-handler here; we'll have an explicit one below
78+
.onOverflow(Approximation.NONE) // Disable the auto-handler here; we'll have an explicit one below
7979
.on(e -> {
8080
if (e.getRelativePath().getNameCount() > 1) {
8181
eventsOnlyForChildren.set(false);

0 commit comments

Comments
 (0)