diff --git a/src/java.base/share/classes/java/nio/file/WatchService.java b/src/java.base/share/classes/java/nio/file/WatchService.java index 1975dd2693a..2490d14823f 100644 --- a/src/java.base/share/classes/java/nio/file/WatchService.java +++ b/src/java.base/share/classes/java/nio/file/WatchService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2024, 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 @@ -98,6 +98,20 @@ import java.util.concurrent.TimeUnit; * it is not required that changes to files carried out on remote systems be * detected. * + * @implNote + * The JDK's {@code WatchService} implementations buffer up to 512 pending + * events for each registered watchable object. If this limit is exceeded, + * pending events are discarded and the special + * {@link StandardWatchEventKinds#OVERFLOW OVERFLOW} event is queued. This + * special event is the trigger to re-examine the state of the object, e.g. + * scan a watched directory to get an updated list of the files in the + * directory. The limit for the pending events can be changed from its default + * with the system property + * {@systemProperty jdk.nio.file.WatchService.maxEventsPerPoll} + * set to a value that parses as a positive integer. This may be useful in + * environments where there is a high volume of changes and where the impact + * of discarded events is high. + * * @since 1.7 * * @see FileSystem#newWatchService diff --git a/src/java.base/share/classes/sun/nio/fs/AbstractWatchKey.java b/src/java.base/share/classes/sun/nio/fs/AbstractWatchKey.java index 710e38fc607..bdb7920b20a 100644 --- a/src/java.base/share/classes/sun/nio/fs/AbstractWatchKey.java +++ b/src/java.base/share/classes/sun/nio/fs/AbstractWatchKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2024, 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 @@ -28,16 +28,38 @@ package sun.nio.fs; import java.nio.file.*; import java.util.*; +import jdk.internal.util.ArraysSupport; +import sun.security.action.GetPropertyAction; + /** * Base implementation class for watch keys. */ abstract class AbstractWatchKey implements WatchKey { + private static final int DEFAULT_MAX_EVENT_LIST_SIZE = 512; + /** - * Maximum size of event list (in the future this may be tunable) + * Maximum size of event list before dropping events and signalling OVERFLOW */ - static final int MAX_EVENT_LIST_SIZE = 512; + static final int MAX_EVENT_LIST_SIZE; + static { + String rawValue = GetPropertyAction.privilegedGetProperty( + "jdk.nio.file.WatchService.maxEventsPerPoll", + String.valueOf(DEFAULT_MAX_EVENT_LIST_SIZE)); + int intValue; + try { + // Clamp to max array length to signal OVERFLOW and drop events + // before OOMing. + intValue = Math.clamp( + Long.decode(rawValue), + 1, + ArraysSupport.SOFT_MAX_ARRAY_LENGTH); + } catch (NumberFormatException e) { + intValue = DEFAULT_MAX_EVENT_LIST_SIZE; + } + MAX_EVENT_LIST_SIZE = intValue; + } /** * Special event to signal overflow diff --git a/test/jdk/java/nio/file/WatchService/LotsOfEntries.java b/test/jdk/java/nio/file/WatchService/LotsOfEntries.java new file mode 100644 index 00000000000..b12daac9f27 --- /dev/null +++ b/test/jdk/java/nio/file/WatchService/LotsOfEntries.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024, 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 8330077 + * @summary Tests WatchService behavior with more entries in a watched directory + * than the default event limit + * @library .. + * @run main/othervm LotsOfEntries 600 fail + * @run main/othervm -Djdk.nio.file.WatchService.maxEventsPerPoll=invalid LotsOfEntries 600 fail + * @run main/othervm -Djdk.nio.file.WatchService.maxEventsPerPoll=-5 LotsOfEntries 5 fail + * @run main/othervm -Djdk.nio.file.WatchService.maxEventsPerPoll=5 LotsOfEntries 5 pass + * @run main/othervm -Djdk.nio.file.WatchService.maxEventsPerPoll=5 LotsOfEntries 6 fail + * @run main/othervm -Djdk.nio.file.WatchService.maxEventsPerPoll=700 LotsOfEntries 600 pass + * @run main/othervm -Djdk.nio.file.WatchService.maxEventsPerPoll=3000000000 LotsOfEntries 600 pass + */ + +import java.nio.file.*; +import static java.nio.file.StandardWatchEventKinds.*; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class LotsOfEntries { + + static void testCreateLotsOfEntries(Path dir, int numEvents, boolean fail) throws Exception { + try (WatchService watcher = FileSystems.getDefault().newWatchService()) { + System.out.format("register %s for events\n", dir); + WatchKey key = dir.register(watcher, ENTRY_CREATE); + + System.out.format("create %d entries\n", numEvents); + Set entries = new HashSet<>(); + for (int i = 0; i < numEvents; i++) { + Path entry = dir.resolve("entry" + i); + entries.add(entry); + Files.createFile(entry); + } + + // Wait for all events to be signalled - the timeout is long to + // allow for polling implementations. Since we specifically want to + // test the maximum number of events buffered for a single + // WatchKey#pollEvents call, we need to poll on the WatchService + // repeatedly until all (not just some) events have been signalled. + System.out.println("poll watcher..."); + WatchKey signalledKey; + do { + signalledKey = watcher.poll(10, TimeUnit.SECONDS); + if (signalledKey != null && signalledKey != key) { + throw new RuntimeException("Unexpected key returned from poll"); + } + } while (signalledKey != null); + + if (fail) { + System.out.println("poll expecting overflow..."); + var events = key.pollEvents(); + if (events.size() != 1) { + throw new RuntimeException( + "Expected overflow event, got: " + toString(events)); + } + if (!events.getFirst().kind().equals(OVERFLOW)) { + throw new RuntimeException( + "Expected overflow event, got: " + toString(events)); + } + } else { + System.out.println("poll not expecting overflow..."); + List> events = key.pollEvents(); + Set contexts = events.stream() + .map(WatchEvent::context) + .map(Path.class::cast) + .map(entry -> dir.resolve(entry)) + .collect(Collectors.toSet()); + if (!entries.equals(contexts)) { + throw new RuntimeException( + "Expected events on: " + entries + ", got: " + toString(events)); + } + } + } + } + + static String toString(List> events) { + return events.stream() + .map(LotsOfEntries::toString) + .collect(Collectors.joining(", ")); + } + + static String toString(WatchEvent event) { + return String.format("%s(%d): %s", event.kind(), event.count(), event.context()); + } + + public static void main(String[] args) throws Exception { + Path dir = TestUtil.createTemporaryDirectory(); + int numEvents = Integer.parseInt(args[0]); + boolean fail = args[1].equals("fail"); + try { + testCreateLotsOfEntries(dir, numEvents, fail); + } finally { + TestUtil.removeAll(dir); + } + } +}