From 3130021075ba15b35f8a3369040e593ba8255c8d Mon Sep 17 00:00:00 2001 From: alanb Date: Sat, 27 Feb 2010 18:18:33 +0000 Subject: [PATCH] 6929532: (file) WatchService should avoid queuing new modify events when lots of files are changing Reviewed-by: alanb Contributed-by: sebastian.sickelmann@gmx.de --- .../classes/sun/nio/fs/AbstractWatchKey.java | 37 ++- .../nio/file/WatchService/LotsOfEvents.java | 222 ++++++++++++++++++ .../WatchService/OverflowEventIsLoner.java | 122 ---------- 3 files changed, 257 insertions(+), 124 deletions(-) create mode 100644 jdk/test/java/nio/file/WatchService/LotsOfEvents.java delete mode 100644 jdk/test/java/nio/file/WatchService/OverflowEventIsLoner.java diff --git a/jdk/src/share/classes/sun/nio/fs/AbstractWatchKey.java b/jdk/src/share/classes/sun/nio/fs/AbstractWatchKey.java index b00c71d37c..9faf78cec8 100644 --- a/jdk/src/share/classes/sun/nio/fs/AbstractWatchKey.java +++ b/jdk/src/share/classes/sun/nio/fs/AbstractWatchKey.java @@ -59,10 +59,15 @@ abstract class AbstractWatchKey extends WatchKey { // pending events private List> events; + // maps a context to the last event for the context (iff the last queued + // event for the context is an ENTRY_MODIFY event). + private Map> lastModifyEvents; + protected AbstractWatchKey(AbstractWatchService watcher) { this.watcher = watcher; this.state = State.READY; this.events = new ArrayList>(); + this.lastModifyEvents = new HashMap>(); } final AbstractWatchService watcher() { @@ -86,6 +91,7 @@ abstract class AbstractWatchKey extends WatchKey { */ @SuppressWarnings("unchecked") final void signalEvent(WatchEvent.Kind kind, Object context) { + boolean isModify = (kind == StandardWatchEventKind.ENTRY_MODIFY); synchronized (this) { int size = events.size(); if (size > 0) { @@ -100,17 +106,43 @@ abstract class AbstractWatchKey extends WatchKey { return; } + // if this is a modify event and the last entry for the context + // is a modify event then we simply increment the count + if (!lastModifyEvents.isEmpty()) { + if (isModify) { + WatchEvent ev = lastModifyEvents.get(context); + if (ev != null) { + assert ev.kind() == StandardWatchEventKind.ENTRY_MODIFY; + ((Event)ev).increment(); + return; + } + } else { + // not a modify event so remove from the map as the + // last event will no longer be a modify event. + lastModifyEvents.remove(context); + } + } + // if the list has reached the limit then drop pending events // and queue an OVERFLOW event if (size >= MAX_EVENT_LIST_SIZE) { - events.clear(); kind = StandardWatchEventKind.OVERFLOW; + isModify = false; context = null; } } // non-repeated event - events.add(new Event((WatchEvent.Kind)kind, context)); + Event ev = + new Event((WatchEvent.Kind)kind, context); + if (isModify) { + lastModifyEvents.put(context, ev); + } else if (kind == StandardWatchEventKind.OVERFLOW) { + // drop all pending events + events.clear(); + lastModifyEvents.clear(); + } + events.add(ev); signal(); } } @@ -120,6 +152,7 @@ abstract class AbstractWatchKey extends WatchKey { synchronized (this) { List> result = events; events = new ArrayList>(); + lastModifyEvents.clear(); return result; } } diff --git a/jdk/test/java/nio/file/WatchService/LotsOfEvents.java b/jdk/test/java/nio/file/WatchService/LotsOfEvents.java new file mode 100644 index 0000000000..3cae8c104e --- /dev/null +++ b/jdk/test/java/nio/file/WatchService/LotsOfEvents.java @@ -0,0 +1,222 @@ +/* + * Copyright 2010 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* @test + * @bug 6907760 6929532 + * @summary Tests WatchService behavior when lots of events are pending + * @library .. + * @run main/timeout=180 LotsOfEvents + */ + +import java.nio.file.*; +import static java.nio.file.StandardWatchEventKind.*; +import java.io.IOException; +import java.io.OutputStream; +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class LotsOfEvents { + + static final Random rand = new Random(); + + public static void main(String[] args) throws Exception { + Path dir = TestUtil.createTemporaryDirectory(); + try { + testOverflowEvent(dir); + testModifyEventsQueuing(dir); + } finally { + TestUtil.removeAll(dir); + } + } + + /** + * Tests that OVERFLOW events are not retreived with other events. + */ + static void testOverflowEvent(Path dir) + throws IOException, InterruptedException + { + WatchService watcher = dir.getFileSystem().newWatchService(); + try { + dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE); + + // create a lot of files + int n = 1024; + Path[] files = new Path[n]; + for (int i=0; i expectedKind, + int count) + throws IOException, InterruptedException + { + // wait for key to be signalled - the timeout is long to allow for + // polling implementations + WatchKey key = watcher.poll(15, TimeUnit.SECONDS); + if (key != null && count == 0) + throw new RuntimeException("Key was signalled (unexpected)"); + if (key == null && count > 0) + throw new RuntimeException("Key not signalled (unexpected)"); + + int nread = 0; + boolean gotOverflow = false; + do { + List> events = key.pollEvents(); + for (WatchEvent event: events) { + WatchEvent.Kind kind = event.kind(); + if (kind == expectedKind) { + // expected event kind + if (++nread > count) + throw new RuntimeException("More events than expected!!"); + } else if (kind == OVERFLOW) { + // overflow event should not be retrieved with other events + if (events.size() > 1) + throw new RuntimeException("Overflow retrieved with other events"); + gotOverflow = true; + } else { + throw new RuntimeException("Unexpected event '" + kind + "'"); + } + } + if (!key.reset()) + throw new RuntimeException("Key is no longer valid"); + key = watcher.poll(2, TimeUnit.SECONDS); + } while (key != null); + + // check that all expected events were received or there was an overflow + if (nread < count && !gotOverflow) + throw new RuntimeException("Insufficient events"); + } + + /** + * Tests that check that ENTRY_MODIFY events are queued efficiently + */ + static void testModifyEventsQueuing(Path dir) + throws IOException, InterruptedException + { + // this test uses a random number of files + final int nfiles = 5 + rand.nextInt(10); + DirectoryEntry[] entries = new DirectoryEntry[nfiles]; + for (int i=0; i modified = new HashSet(); + for (WatchEvent event: key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + Path file = (kind == OVERFLOW) ? null : (Path)event.context(); + if (kind == ENTRY_MODIFY) { + boolean added = modified.add(file); + if (!added) { + throw new RuntimeException( + "ENTRY_MODIFY events not queued efficiently"); + } + } else { + if (file != null) modified.remove(file); + } + } + if (!key.reset()) + throw new RuntimeException("Key is no longer valid"); + key = watcher.poll(2, TimeUnit.SECONDS); + } while (key != null); + } + + } finally { + watcher.close(); + } + } + + static class DirectoryEntry { + private final Path file; + DirectoryEntry(Path file) { + this.file = file; + } + void create() throws IOException { + if (file.notExists()) + file.createFile(); + + } + void deleteIfExists() throws IOException { + file.deleteIfExists(); + } + void modifyIfExists() throws IOException { + if (file.exists()) { + OutputStream out = file.newOutputStream(StandardOpenOption.APPEND); + try { + out.write("message".getBytes()); + } finally { + out.close(); + } + } + } + } + +} diff --git a/jdk/test/java/nio/file/WatchService/OverflowEventIsLoner.java b/jdk/test/java/nio/file/WatchService/OverflowEventIsLoner.java deleted file mode 100644 index 0b48a942d8..0000000000 --- a/jdk/test/java/nio/file/WatchService/OverflowEventIsLoner.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2010 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, - * CA 95054 USA or visit www.sun.com if you need additional information or - * have any questions. - */ - -/* @test - * @bug 6907760 - * @summary Check that the OVERFLOW event is not retrieved with other events - * @library .. - */ - -import java.nio.file.*; -import static java.nio.file.StandardWatchEventKind.*; -import java.io.IOException; -import java.util.List; -import java.util.concurrent.TimeUnit; - -public class OverflowEventIsLoner { - - static void drainEvents(WatchService watcher, - WatchEvent.Kind expectedKind, - int count) - throws IOException, InterruptedException - { - // wait for key to be signalled - the timeout is long to allow for - // polling implementations - WatchKey key = watcher.poll(15, TimeUnit.SECONDS); - if (key != null && count == 0) - throw new RuntimeException("Key was signalled (unexpected)"); - if (key == null && count > 0) - throw new RuntimeException("Key not signalled (unexpected)"); - - int nread = 0; - boolean gotOverflow = false; - do { - List> events = key.pollEvents(); - for (WatchEvent event: events) { - WatchEvent.Kind kind = event.kind(); - if (kind == expectedKind) { - // expected event kind - if (++nread > count) - throw new RuntimeException("More events than expected!!"); - } else if (kind == OVERFLOW) { - // overflow event should not be retrieved with other events - if (events.size() > 1) - throw new RuntimeException("Overflow retrieved with other events"); - gotOverflow = true; - } else { - throw new RuntimeException("Unexpected event '" + kind + "'"); - } - } - if (!key.reset()) - throw new RuntimeException("Key is no longer valid"); - key = watcher.poll(2, TimeUnit.SECONDS); - } while (key != null); - - // check that all expected events were received or there was an overflow - if (nread < count && !gotOverflow) - throw new RuntimeException("Insufficient events"); - } - - - static void test(Path dir) throws IOException, InterruptedException { - WatchService watcher = dir.getFileSystem().newWatchService(); - try { - WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE); - - // create a lot of files - int n = 1024; - Path[] files = new Path[n]; - for (int i=0; i