# HG changeset patch # User alanb # Date 1345379341 -3600 # Node ID e5a8ca9244e1489740fe296a03b7e88b83f690fa # Parent cb435d74ab6b5a037e821cee4e6b263a5e5d8fdc 7191467: (fs) WatchService periodically fails to queue ENTRY_DELETE event for short lived file [sol11] Reviewed-by: chegar diff -r cb435d74ab6b -r e5a8ca9244e1 jdk/src/solaris/classes/sun/nio/fs/SolarisWatchService.java --- a/jdk/src/solaris/classes/sun/nio/fs/SolarisWatchService.java Sun Aug 19 13:03:00 2012 +0100 +++ b/jdk/src/solaris/classes/sun/nio/fs/SolarisWatchService.java Sun Aug 19 13:29:01 2012 +0100 @@ -138,7 +138,7 @@ // map of entries in directory; created lazily; accessed only by // poller thread. - private Map children; + private Map children = new HashMap<>(); SolarisWatchKey(SolarisWatchService watcher, UnixPath dir, @@ -177,6 +177,10 @@ this.events = events; } + Map children() { + return children; + } + @Override public boolean isValid() { return events != null; @@ -192,8 +196,6 @@ @Override public void addChild(Path name, EntryNode node) { - if (children == null) - children = new HashMap(); children.put(name, node); } @@ -204,9 +206,7 @@ @Override public EntryNode getChild(Path name) { - if (children != null) - return children.get(name); - return null; + return children.get(name); } } @@ -291,12 +291,15 @@ return new NotDirectoryException(dir.getPathForExceptionMessage()); } - // return existing watch key after updating events if already - // registered + // if already registered then update the events and return existing key UnixFileKey fileKey = attrs.fileKey(); SolarisWatchKey watchKey = fileKey2WatchKey.get(fileKey); if (watchKey != null) { - updateEvents(watchKey, events); + try { + updateEvents(watchKey, events); + } catch (UnixException x) { + return x.asIOException(dir); + } return watchKey; } @@ -319,6 +322,23 @@ return watchKey; } + // release resources for single entry + void releaseChild(EntryNode node) { + long object = node.object(); + if (object != 0L) { + object2Node.remove(object); + releaseObject(object, true); + node.setObject(0L); + } + } + + // release resources for entries in directory + void releaseChildren(SolarisWatchKey key) { + for (EntryNode node: key.children().values()) { + releaseChild(node); + } + } + // cancel single key @Override void implCancelKey(WatchKey obj) { @@ -326,15 +346,8 @@ if (key.isValid()) { fileKey2WatchKey.remove(key.getFileKey()); - // release resources for entries in directory - if (key.children != null) { - for (Map.Entry entry: key.children.entrySet()) { - EntryNode node = entry.getValue(); - long object = node.object(); - object2Node.remove(object); - releaseObject(object, true); - } - } + // release resources for entries + releaseChildren(key); // release resources for directory long object = key.object(); @@ -510,26 +523,7 @@ key.signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, node.name()); } - // entry removed - if (((mask & (FILE_REMOVED)) != 0) && - events.contains(StandardWatchEventKinds.ENTRY_DELETE)) - { - // Due to 6636438/6636412 we may get a remove event for cases - // where a rmdir/unlink/rename is attempted but fails. Until - // this issue is resolved we re-lstat the file to check if it - // exists. If it exists then we ignore the event. To keep the - // workaround simple we don't check the st_ino so it isn't - // effective when the file is replaced. - boolean removed = true; - try { - UnixFileAttributes - .get(key.getDirectory().resolve(node.name()), false); - removed = false; - } catch (UnixException x) { } - if (removed) - key.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, node.name()); - } return false; } @@ -547,85 +541,137 @@ boolean sendCreateEvents, boolean sendDeleteEvents) { - // if the ENTRY_MODIFY event is not enabled then we don't need - // modification events for entries in the directory - int events = FILE_NOFOLLOW; - if (parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY)) - events |= (FILE_MODIFIED | FILE_ATTRIB); + boolean isModifyEnabled = + parent.events().contains(StandardWatchEventKinds.ENTRY_MODIFY) ; + + // reset visited flag on entries so that we can detect file deletes + for (EntryNode node: parent.children().values()) { + node.setVisited(false); + } try (DirectoryStream stream = Files.newDirectoryStream(dir)) { for (Path entry: stream) { Path name = entry.getFileName(); // skip entry if already registered - if (parent.getChild(name) != null) + EntryNode node = parent.getChild(name); + if (node != null) { + node.setVisited(true); continue; - - // attempt to register entry - long object = 0L; - int errno = 0; - try { - object = registerImpl((UnixPath)entry, events); - } catch (UnixException x) { - errno = x.errno(); } - boolean registered = (object != 0L); - boolean deleted = (errno == ENOENT); + // new entry found + + long object = 0L; + int errno = 0; + boolean addNode = false; - if (registered) { + // if ENTRY_MODIFY enabled then we register the entry for events + if (isModifyEnabled) { + try { + UnixPath path = (UnixPath)entry; + int events = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB); + object = registerImpl(path, events); + addNode = true; + } catch (UnixException x) { + errno = x.errno(); + } + } else { + addNode = true; + } + + if (addNode) { // create node - EntryNode node = new EntryNode(object, entry.getFileName(), parent); + node = new EntryNode(object, (UnixPath)entry.getFileName(), parent); + node.setVisited(true); // tell the parent about it parent.addChild(entry.getFileName(), node); - object2Node.put(object, node); + if (object != 0L) + object2Node.put(object, node); } - if (sendCreateEvents && (registered || deleted)) + // send ENTRY_CREATE event for the new file + // send ENTRY_DELETE event for files that were deleted immediately + boolean deleted = (errno == ENOENT); + if (sendCreateEvents && (addNode || deleted)) parent.signalEvent(StandardWatchEventKinds.ENTRY_CREATE, name); if (sendDeleteEvents && deleted) parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name); } } catch (DirectoryIteratorException | IOException x) { - // nothing we can do + // queue OVERFLOW event so that user knows to re-scan directory + parent.signalEvent(StandardWatchEventKinds.OVERFLOW, null); + return; + } + + // clean-up and send ENTRY_DELETE events for any entries that were + // not found + Iterator> iterator = + parent.children().entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + EntryNode node = entry.getValue(); + if (!node.isVisited()) { + long object = node.object(); + if (object != 0L) { + object2Node.remove(object); + releaseObject(object, true); + } + if (sendDeleteEvents) + parent.signalEvent(StandardWatchEventKinds.ENTRY_DELETE, node.name()); + iterator.remove(); + } } } /** - * Update watch key's events. Where the ENTRY_MODIFY changes then we - * need to update the events of registered children. + * Update watch key's events. If ENTRY_MODIFY changes to be enabled + * then register each file in the direcory; If ENTRY_MODIFY changed to + * be disabled then unregister each file. */ - void updateEvents(SolarisWatchKey key, Set> events) { + void updateEvents(SolarisWatchKey key, Set> events) + throws UnixException + { + // update events, rembering if ENTRY_MODIFY was previously // enabled or disabled. - boolean wasModifyEnabled = key.events() + boolean oldModifyEnabled = key.events() .contains(StandardWatchEventKinds.ENTRY_MODIFY); key.setEvents(events); // check if ENTRY_MODIFY has changed - boolean isModifyEnabled = events + boolean newModifyEnabled = events .contains(StandardWatchEventKinds.ENTRY_MODIFY); - if (wasModifyEnabled == isModifyEnabled) { - return; - } + if (newModifyEnabled != oldModifyEnabled) { + UnixException ex = null; + for (EntryNode node: key.children().values()) { + if (newModifyEnabled) { + // register + UnixPath path = key.getDirectory().resolve(node.name()); + int ev = (FILE_NOFOLLOW | FILE_MODIFIED | FILE_ATTRIB); + try { + long object = registerImpl(path, ev); + object2Node.put(object, node); + node.setObject(object); + } catch (UnixException x) { + // if file has been deleted then it will be detected + // as a FILE_MODIFIED event on the directory + if (x.errno() != ENOENT) { + ex = x; + break; + } + } + } else { + // unregister + releaseChild(node); + } + } - // if changed then update events of children - if (key.children != null) { - int ev = FILE_NOFOLLOW; - if (isModifyEnabled) - ev |= (FILE_MODIFIED | FILE_ATTRIB); - - for (Map.Entry entry: key.children.entrySet()) { - long object = entry.getValue().object(); - try { - portAssociate(port, - PORT_SOURCE_FILE, - object, - ev); - } catch (UnixException x) { - // nothing we can do. - } + // an error occured + if (ex != null) { + releaseChildren(key); + throw ex; } } } @@ -713,11 +759,12 @@ * An implementation of a node that is an entry in a directory. */ private static class EntryNode implements Node { - private final long object; - private final Path name; + private long object; + private final UnixPath name; private final DirectoryNode parent; + private boolean visited; - EntryNode(long object, Path name, DirectoryNode parent) { + EntryNode(long object, UnixPath name, DirectoryNode parent) { this.object = object; this.name = name; this.parent = parent; @@ -728,13 +775,25 @@ return object; } - Path name() { + void setObject(long ptr) { + this.object = ptr; + } + + UnixPath name() { return name; } DirectoryNode parent() { return parent; } + + boolean isVisited() { + return visited; + } + + void setVisited(boolean v) { + this.visited = v; + } } // -- native methods -- diff -r cb435d74ab6b -r e5a8ca9244e1 jdk/test/java/nio/file/WatchService/MayFlies.java --- a/jdk/test/java/nio/file/WatchService/MayFlies.java Sun Aug 19 13:03:00 2012 +0100 +++ b/jdk/test/java/nio/file/WatchService/MayFlies.java Sun Aug 19 13:29:01 2012 +0100 @@ -22,7 +22,7 @@ */ /* @test - * @bug 7164570 + * @bug 7164570 7191467 * @summary Test that CREATE and DELETE events are paired for very * short lived files * @library ..