7164570: (fs) WatchService queues CREATE event but not DELETE event for very short lived files [sol11]
Reviewed-by: chegar
--- a/jdk/src/solaris/classes/sun/nio/fs/SolarisWatchService.java Fri Apr 27 04:25:50 2012 -0700
+++ b/jdk/src/solaris/classes/sun/nio/fs/SolarisWatchService.java Tue May 01 11:17:20 2012 +0100
@@ -314,7 +314,7 @@
fileKey2WatchKey.put(fileKey, watchKey);
// register all entries in directory
- registerChildren(dir, watchKey, false);
+ registerChildren(dir, watchKey, false, false);
return watchKey;
}
@@ -486,7 +486,8 @@
void processDirectoryEvents(SolarisWatchKey key, int mask) {
if ((mask & (FILE_MODIFIED | FILE_ATTRIB)) != 0) {
registerChildren(key.getDirectory(), key,
- key.events().contains(StandardWatchEventKinds.ENTRY_CREATE));
+ key.events().contains(StandardWatchEventKinds.ENTRY_CREATE),
+ key.events().contains(StandardWatchEventKinds.ENTRY_DELETE));
}
}
@@ -535,14 +536,16 @@
/**
* Registers all entries in the given directory
*
- * The {@code sendEvents} parameter indicates if ENTRY_CREATE events
- * should be queued when new entries are found. When initially
- * registering a directory then will always be false. When re-scanning
- * a directory then it depends on if the event is enabled or not.
+ * The {@code sendCreateEvents} and {@code sendDeleteEvents} parameters
+ * indicates if ENTRY_CREATE and ENTRY_DELETE events should be queued
+ * when new entries are found. When initially registering a directory
+ * they will always be false. When re-scanning a directory then it
+ * depends on if the events are enabled or not.
*/
void registerChildren(UnixPath dir,
SolarisWatchKey parent,
- boolean sendEvents)
+ boolean sendCreateEvents,
+ boolean sendDeleteEvents)
{
// if the ENTRY_MODIFY event is not enabled then we don't need
// modification events for entries in the directory
@@ -550,14 +553,7 @@
if (parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY))
events |= (FILE_MODIFIED | FILE_ATTRIB);
- DirectoryStream<Path> stream = null;
- try {
- stream = Files.newDirectoryStream(dir);
- } catch (IOException x) {
- // nothing we can do
- return;
- }
- try {
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry: stream) {
Path name = entry.getFileName();
@@ -565,32 +561,34 @@
if (parent.getChild(name) != null)
continue;
- // send ENTRY_CREATE if enabled
- if (sendEvents) {
- parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name);
- }
-
- // register it
+ // attempt to register entry
long object = 0L;
+ int errno = 0;
try {
object = registerImpl((UnixPath)entry, events);
} catch (UnixException x) {
- // can't register so ignore for now.
- continue;
+ errno = x.errno();
}
- // create node
- EntryNode node = new EntryNode(object, entry.getFileName(), parent);
- // tell the parent about it
- parent.addChild(entry.getFileName(), node);
- object2Node.put(object, node);
+ boolean registered = (object != 0L);
+ boolean deleted = (errno == ENOENT);
+
+ if (registered) {
+ // create node
+ EntryNode node = new EntryNode(object, entry.getFileName(), parent);
+ // tell the parent about it
+ parent.addChild(entry.getFileName(), node);
+ object2Node.put(object, node);
+ }
+
+ if (sendCreateEvents && (registered || deleted))
+ parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name);
+ if (sendDeleteEvents && deleted)
+ parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);
+
}
- } catch (ConcurrentModificationException x) {
- // error during iteration which we ignore for now
- } finally {
- try {
- stream.close();
- } catch (IOException x) { }
+ } catch (DirectoryIteratorException | IOException x) {
+ // nothing we can do
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/nio/file/WatchService/MayFlies.java Tue May 01 11:17:20 2012 +0100
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+ * @bug 7164570
+ * @summary Test that CREATE and DELETE events are paired for very
+ * short lived files
+ * @library ..
+ * @run main MayFlies
+ */
+
+import java.nio.file.*;
+import static java.nio.file.StandardWatchEventKinds.*;
+import java.util.*;
+import java.util.concurrent.*;
+
+public class MayFlies {
+
+ static volatile boolean stopped;
+
+ static volatile boolean failure;
+
+ /**
+ * Continuously creates short-lived files in a directory until {@code
+ * stopped} is set to {@code true}.
+ */
+ static class MayFlyHatcher implements Runnable {
+ static final Random rand = new Random();
+
+ private final Path dir;
+ private final String prefix;
+
+ private MayFlyHatcher(Path dir, String prefix) {
+ this.dir = dir;
+ this.prefix = prefix;
+ }
+
+ static void start(Path dir, String prefix) {
+ MayFlyHatcher hatcher = new MayFlyHatcher(dir, prefix);
+ new Thread(hatcher).start();
+ }
+
+ public void run() {
+ try {
+ int n = 0;
+ while (!stopped) {
+ Path name = dir.resolve(prefix + (++n));
+ Files.createFile(name);
+ if (rand.nextBoolean())
+ Thread.sleep(rand.nextInt(500));
+ Files.delete(name);
+ Thread.sleep(rand.nextInt(100));
+ }
+ System.out.format("%d %ss hatched%n", n, prefix);
+ } catch (Exception x) {
+ failure = true;
+ x.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Test phases.
+ */
+ static enum Phase {
+ /**
+ * Short-lived files are being created
+ */
+ RUNNING,
+ /**
+ * Draining the final events
+ */
+ FINISHING,
+ /**
+ * No more events or overflow detected
+ */
+ FINISHED
+ };
+
+
+ public static void main(String[] args) throws Exception {
+
+ // schedules file creation to stop after 10 seconds
+ ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
+ pool.schedule(
+ new Runnable() { public void run() { stopped = true; }},
+ 10, TimeUnit.SECONDS);
+
+ Path dir = TestUtil.createTemporaryDirectory();
+
+ Set<Path> entries = new HashSet<>();
+ int nCreateEvents = 0;
+ boolean overflow = false;
+
+ try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
+ WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE);
+
+ // start hatching Mayflies
+ MayFlyHatcher.start(dir, "clinger");
+ MayFlyHatcher.start(dir, "crawler");
+ MayFlyHatcher.start(dir, "burrower");
+ MayFlyHatcher.start(dir, "swimmer");
+
+ Phase phase = Phase.RUNNING;
+ while (phase != Phase.FINISHED) {
+ // during the running phase then poll for 1 second.
+ // once the file creation has stopped then move to the finishing
+ // phase where we do a long poll to ensure that all events have
+ // been read.
+ int time = (phase == Phase.RUNNING) ? 1 : 15;
+ key = watcher.poll(time, TimeUnit.SECONDS);
+ if (key == null) {
+ if (phase == Phase.RUNNING && stopped)
+ phase = Phase.FINISHING;
+ else if (phase == Phase.FINISHING)
+ phase = Phase.FINISHED;
+ } else {
+ // process events
+ for (WatchEvent<?> event: key.pollEvents()) {
+ if (event.kind() == ENTRY_CREATE) {
+ Path name = (Path)event.context();
+ boolean added = entries.add(name);
+ if (!added)
+ throw new RuntimeException("Duplicate ENTRY_CREATE event");
+ nCreateEvents++;
+ } else if (event.kind() == ENTRY_DELETE) {
+ Path name = (Path)event.context();
+ boolean removed = entries.remove(name);
+ if (!removed)
+ throw new RuntimeException("ENTRY_DELETE event without ENTRY_CREATE event");
+ } else if (event.kind() == OVERFLOW) {
+ overflow = true;
+ phase = Phase.FINISHED;
+ } else {
+ throw new RuntimeException("Unexpected event: " + event.kind());
+ }
+ }
+ key.reset();
+ }
+ }
+
+ System.out.format("%d ENTRY_CREATE events read%n", nCreateEvents);
+
+ // there should be a DELETE event for each CREATE event and so the
+ // entries set should be empty.
+ if (!overflow && !entries.isEmpty())
+ throw new RuntimeException("Missed " + entries.size() + " DELETE event(s)");
+
+
+ } finally {
+ try {
+ TestUtil.removeAll(dir);
+ } finally {
+ pool.shutdown();
+ }
+ }
+
+ if (failure)
+ throw new RuntimeException("Test failed - see log file for details");
+ }
+}