| /* |
| * 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 7191467 |
| * @summary Test that CREATE and DELETE events are paired for very |
| * short lived files |
| * @library .. |
| * @run main MayFlies |
| * @key randomness |
| */ |
| |
| 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"); |
| } |
| } |