1 /*
   2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /* @test
  25  * @bug 7164570 7191467
  26  * @summary Test that CREATE and DELETE events are paired for very
  27  *     short lived files
  28  * @library ..
  29  * @run main MayFlies
  30  */
  31 
  32 import java.nio.file.*;
  33 import static java.nio.file.StandardWatchEventKinds.*;
  34 import java.util.*;
  35 import java.util.concurrent.*;
  36 
  37 public class MayFlies {
  38 
  39     static volatile boolean stopped;
  40 
  41     static volatile boolean failure;
  42 
  43     /**
  44      * Continuously creates short-lived files in a directory until {@code
  45      * stopped} is set to {@code true}.
  46      */
  47     static class MayFlyHatcher implements Runnable {
  48         static final Random rand = new Random();
  49 
  50         private final Path dir;
  51         private final String prefix;
  52 
  53         private MayFlyHatcher(Path dir, String prefix) {
  54             this.dir = dir;
  55             this.prefix = prefix;
  56         }
  57 
  58         static void start(Path dir, String prefix) {
  59             MayFlyHatcher hatcher = new MayFlyHatcher(dir, prefix);
  60             new Thread(hatcher).start();
  61         }
  62 
  63         public void run() {
  64             try {
  65                 int n = 0;
  66                 while (!stopped) {
  67                     Path name = dir.resolve(prefix + (++n));
  68                     Files.createFile(name);
  69                     if (rand.nextBoolean())
  70                         Thread.sleep(rand.nextInt(500));
  71                     Files.delete(name);
  72                     Thread.sleep(rand.nextInt(100));
  73                 }
  74                 System.out.format("%d %ss hatched%n", n, prefix);
  75             } catch (Exception x) {
  76                 failure = true;
  77                 x.printStackTrace();
  78             }
  79         }
  80     }
  81 
  82     /**
  83      * Test phases.
  84      */
  85     static enum Phase {
  86         /**
  87          * Short-lived files are being created
  88          */
  89         RUNNING,
  90         /**
  91          * Draining the final events
  92          */
  93         FINISHING,
  94         /**
  95          * No more events or overflow detected
  96          */
  97         FINISHED
  98     };
  99 
 100 
 101     public static void main(String[] args) throws Exception {
 102 
 103         // schedules file creation to stop after 10 seconds
 104         ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
 105         pool.schedule(
 106             new Runnable() { public void run() { stopped = true; }},
 107             10, TimeUnit.SECONDS);
 108 
 109         Path dir = TestUtil.createTemporaryDirectory();
 110 
 111         Set<Path> entries = new HashSet<>();
 112         int nCreateEvents = 0;
 113         boolean overflow = false;
 114 
 115         try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
 116             WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE);
 117 
 118             // start hatching Mayflies
 119             MayFlyHatcher.start(dir, "clinger");
 120             MayFlyHatcher.start(dir, "crawler");
 121             MayFlyHatcher.start(dir, "burrower");
 122             MayFlyHatcher.start(dir, "swimmer");
 123 
 124             Phase phase = Phase.RUNNING;
 125             while (phase != Phase.FINISHED) {
 126                 // during the running phase then poll for 1 second.
 127                 // once the file creation has stopped then move to the finishing
 128                 // phase where we do a long poll to ensure that all events have
 129                 // been read.
 130                 int time = (phase == Phase.RUNNING) ? 1 : 15;
 131                 key = watcher.poll(time, TimeUnit.SECONDS);
 132                 if (key == null) {
 133                     if (phase == Phase.RUNNING && stopped)
 134                         phase = Phase.FINISHING;
 135                     else if (phase == Phase.FINISHING)
 136                         phase = Phase.FINISHED;
 137                 } else {
 138                     // process events
 139                     for (WatchEvent<?> event: key.pollEvents()) {
 140                         if (event.kind() == ENTRY_CREATE) {
 141                             Path name = (Path)event.context();
 142                             boolean added = entries.add(name);
 143                             if (!added)
 144                                 throw new RuntimeException("Duplicate ENTRY_CREATE event");
 145                             nCreateEvents++;
 146                         } else if (event.kind() == ENTRY_DELETE) {
 147                             Path name = (Path)event.context();
 148                             boolean removed = entries.remove(name);
 149                             if (!removed)
 150                                 throw new RuntimeException("ENTRY_DELETE event without ENTRY_CREATE event");
 151                         } else if (event.kind() == OVERFLOW) {
 152                             overflow = true;
 153                             phase = Phase.FINISHED;
 154                         } else {
 155                             throw new RuntimeException("Unexpected event: " + event.kind());
 156                         }
 157                     }
 158                     key.reset();
 159                 }
 160             }
 161 
 162             System.out.format("%d ENTRY_CREATE events read%n", nCreateEvents);
 163 
 164             // there should be a DELETE event for each CREATE event and so the
 165             // entries set should be empty.
 166             if (!overflow && !entries.isEmpty())
 167                 throw new RuntimeException("Missed " + entries.size() + " DELETE event(s)");
 168 
 169 
 170         } finally {
 171             try {
 172                 TestUtil.removeAll(dir);
 173             } finally {
 174                 pool.shutdown();
 175             }
 176         }
 177 
 178         if (failure)
 179             throw new RuntimeException("Test failed - see log file for details");
 180     }
 181 }