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  * @key randomness
  31  */
  32 
  33 import java.nio.file.*;
  34 import static java.nio.file.StandardWatchEventKinds.*;
  35 import java.util.*;
  36 import java.util.concurrent.*;
  37 
  38 public class MayFlies {
  39 
  40     static volatile boolean stopped;
  41 
  42     static volatile boolean failure;
  43 
  44     /**
  45      * Continuously creates short-lived files in a directory until {@code
  46      * stopped} is set to {@code true}.
  47      */
  48     static class MayFlyHatcher implements Runnable {
  49         static final Random rand = new Random();
  50 
  51         private final Path dir;
  52         private final String prefix;
  53 
  54         private MayFlyHatcher(Path dir, String prefix) {
  55             this.dir = dir;
  56             this.prefix = prefix;
  57         }
  58 
  59         static void start(Path dir, String prefix) {
  60             MayFlyHatcher hatcher = new MayFlyHatcher(dir, prefix);
  61             new Thread(hatcher).start();
  62         }
  63 
  64         public void run() {
  65             try {
  66                 int n = 0;
  67                 while (!stopped) {
  68                     Path name = dir.resolve(prefix + (++n));
  69                     Files.createFile(name);
  70                     if (rand.nextBoolean())
  71                         Thread.sleep(rand.nextInt(500));
  72                     Files.delete(name);
  73                     Thread.sleep(rand.nextInt(100));
  74                 }
  75                 System.out.format("%d %ss hatched%n", n, prefix);
  76             } catch (Exception x) {
  77                 failure = true;
  78                 x.printStackTrace();
  79             }
  80         }
  81     }
  82 
  83     /**
  84      * Test phases.
  85      */
  86     static enum Phase {
  87         /**
  88          * Short-lived files are being created
  89          */
  90         RUNNING,
  91         /**
  92          * Draining the final events
  93          */
  94         FINISHING,
  95         /**
  96          * No more events or overflow detected
  97          */
  98         FINISHED
  99     };
 100 
 101 
 102     public static void main(String[] args) throws Exception {
 103 
 104         // schedules file creation to stop after 10 seconds
 105         ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
 106         pool.schedule(
 107             new Runnable() { public void run() { stopped = true; }},
 108             10, TimeUnit.SECONDS);
 109 
 110         Path dir = TestUtil.createTemporaryDirectory();
 111 
 112         Set<Path> entries = new HashSet<>();
 113         int nCreateEvents = 0;
 114         boolean overflow = false;
 115 
 116         try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
 117             WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE);
 118 
 119             // start hatching Mayflies
 120             MayFlyHatcher.start(dir, "clinger");
 121             MayFlyHatcher.start(dir, "crawler");
 122             MayFlyHatcher.start(dir, "burrower");
 123             MayFlyHatcher.start(dir, "swimmer");
 124 
 125             Phase phase = Phase.RUNNING;
 126             while (phase != Phase.FINISHED) {
 127                 // during the running phase then poll for 1 second.
 128                 // once the file creation has stopped then move to the finishing
 129                 // phase where we do a long poll to ensure that all events have
 130                 // been read.
 131                 int time = (phase == Phase.RUNNING) ? 1 : 15;
 132                 key = watcher.poll(time, TimeUnit.SECONDS);
 133                 if (key == null) {
 134                     if (phase == Phase.RUNNING && stopped)
 135                         phase = Phase.FINISHING;
 136                     else if (phase == Phase.FINISHING)
 137                         phase = Phase.FINISHED;
 138                 } else {
 139                     // process events
 140                     for (WatchEvent<?> event: key.pollEvents()) {
 141                         if (event.kind() == ENTRY_CREATE) {
 142                             Path name = (Path)event.context();
 143                             boolean added = entries.add(name);
 144                             if (!added)
 145                                 throw new RuntimeException("Duplicate ENTRY_CREATE event");
 146                             nCreateEvents++;
 147                         } else if (event.kind() == ENTRY_DELETE) {
 148                             Path name = (Path)event.context();
 149                             boolean removed = entries.remove(name);
 150                             if (!removed)
 151                                 throw new RuntimeException("ENTRY_DELETE event without ENTRY_CREATE event");
 152                         } else if (event.kind() == OVERFLOW) {
 153                             overflow = true;
 154                             phase = Phase.FINISHED;
 155                         } else {
 156                             throw new RuntimeException("Unexpected event: " + event.kind());
 157                         }
 158                     }
 159                     key.reset();
 160                 }
 161             }
 162 
 163             System.out.format("%d ENTRY_CREATE events read%n", nCreateEvents);
 164 
 165             // there should be a DELETE event for each CREATE event and so the
 166             // entries set should be empty.
 167             if (!overflow && !entries.isEmpty())
 168                 throw new RuntimeException("Missed " + entries.size() + " DELETE event(s)");
 169 
 170 
 171         } finally {
 172             try {
 173                 TestUtil.removeAll(dir);
 174             } finally {
 175                 pool.shutdown();
 176             }
 177         }
 178 
 179         if (failure)
 180             throw new RuntimeException("Test failed - see log file for details");
 181     }
 182 }