# HG changeset patch # User alanb # Date 1335867440 -3600 # Node ID 2e06461600d99b24f7996f978c837c4a3e4b79a2 # Parent 44798b7530d1139cd5e5266b0fe6a5494e69ace5 7164570: (fs) WatchService queues CREATE event but not DELETE event for very short lived files [sol11] Reviewed-by: chegar diff -r 44798b7530d1 -r 2e06461600d9 jdk/src/solaris/classes/sun/nio/fs/SolarisWatchService.java --- 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 stream = null; - try { - stream = Files.newDirectoryStream(dir); - } catch (IOException x) { - // nothing we can do - return; - } - try { + try (DirectoryStream 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 } } diff -r 44798b7530d1 -r 2e06461600d9 jdk/test/java/nio/file/WatchService/MayFlies.java --- /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 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"); + } +}