1 /*
2  * Copyright (c) 2008, 2014, 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.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.nio.fs;
27 
28 import java.nio.file.*;
29 import java.security.AccessController;
30 import java.security.PrivilegedAction;
31 import java.io.IOException;
32 import java.util.*;
33 
34 /**
35  * Base implementation of background poller thread used in watch service
36  * implementations. A poller thread waits on events from the file system and
37  * also services "requests" from clients to register for new events or cancel
38  * existing registrations.
39  */
40 
41 abstract class AbstractPoller implements Runnable {
42 
43     // list of requests pending to the poller thread
44     private final LinkedList<Request> requestList;
45 
46     // set to true when shutdown
47     private boolean shutdown;
48 
AbstractPoller()49     protected AbstractPoller() {
50         this.requestList = new LinkedList<Request>();
51         this.shutdown = false;
52     }
53 
54     /**
55      * Starts the poller thread
56      */
start()57     public void start() {
58         final Runnable thisRunnable = this;
59         AccessController.doPrivileged(new PrivilegedAction<Object>() {
60             @Override
61             public Object run() {
62                 Thread thr = new Thread(thisRunnable);
63                 thr.setDaemon(true);
64                 thr.start();
65                 return null;
66             }
67          });
68     }
69 
70     /**
71      * Wakeup poller thread so that it can service pending requests
72      */
wakeup()73     abstract void wakeup() throws IOException;
74 
75     /**
76      * Executed by poller thread to register directory for changes
77      */
implRegister(Path path, Set<? extends WatchEvent.Kind<?>> events, WatchEvent.Modifier... modifiers)78     abstract Object implRegister(Path path,
79                                  Set<? extends WatchEvent.Kind<?>> events,
80                                  WatchEvent.Modifier... modifiers);
81 
82     /**
83      * Executed by poller thread to cancel key
84      */
implCancelKey(WatchKey key)85     abstract void implCancelKey(WatchKey key);
86 
87     /**
88      * Executed by poller thread to shutdown and cancel all keys
89      */
implCloseAll()90     abstract void implCloseAll();
91 
92     /**
93      * Requests, and waits on, poller thread to register given file.
94      */
register(Path dir, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)95     final WatchKey register(Path dir,
96                             WatchEvent.Kind<?>[] events,
97                             WatchEvent.Modifier... modifiers)
98         throws IOException
99     {
100         // validate arguments before request to poller
101         if (dir == null)
102             throw new NullPointerException();
103         Set<WatchEvent.Kind<?>> eventSet = new HashSet<>(events.length);
104         for (WatchEvent.Kind<?> event: events) {
105             // standard events
106             if (event == StandardWatchEventKinds.ENTRY_CREATE ||
107                 event == StandardWatchEventKinds.ENTRY_MODIFY ||
108                 event == StandardWatchEventKinds.ENTRY_DELETE)
109             {
110                 eventSet.add(event);
111                 continue;
112             }
113 
114             // OVERFLOW is ignored
115             if (event == StandardWatchEventKinds.OVERFLOW)
116                 continue;
117 
118             // null/unsupported
119             if (event == null)
120                 throw new NullPointerException("An element in event set is 'null'");
121             throw new UnsupportedOperationException(event.name());
122         }
123         if (eventSet.isEmpty())
124             throw new IllegalArgumentException("No events to register");
125         return (WatchKey)invoke(RequestType.REGISTER, dir, eventSet, modifiers);
126     }
127 
128     /**
129      * Cancels, and waits on, poller thread to cancel given key.
130      */
cancel(WatchKey key)131     final void cancel(WatchKey key) {
132         try {
133             invoke(RequestType.CANCEL, key);
134         } catch (IOException x) {
135             // should not happen
136             throw new AssertionError(x.getMessage());
137         }
138     }
139 
140     /**
141      * Shutdown poller thread
142      */
close()143     final void close() throws IOException {
144         invoke(RequestType.CLOSE);
145     }
146 
147     /**
148      * Types of request that the poller thread must handle
149      */
150     private static enum RequestType {
151         REGISTER,
152         CANCEL,
153         CLOSE;
154     }
155 
156     /**
157      * Encapsulates a request (command) to the poller thread.
158      */
159     private static class Request {
160         private final RequestType type;
161         private final Object[] params;
162 
163         private boolean completed = false;
164         private Object result = null;
165 
Request(RequestType type, Object... params)166         Request(RequestType type, Object... params) {
167             this.type = type;
168             this.params = params;
169         }
170 
type()171         RequestType type() {
172             return type;
173         }
174 
parameters()175         Object[] parameters() {
176             return params;
177         }
178 
release(Object result)179         void release(Object result) {
180             synchronized (this) {
181                 this.completed = true;
182                 this.result = result;
183                 notifyAll();
184             }
185         }
186 
187         /**
188          * Await completion of the request. The return value is the result of
189          * the request.
190          */
awaitResult()191         Object awaitResult() {
192             boolean interrupted = false;
193             synchronized (this) {
194                 while (!completed) {
195                     try {
196                         wait();
197                     } catch (InterruptedException x) {
198                         interrupted = true;
199                     }
200                 }
201                 if (interrupted)
202                     Thread.currentThread().interrupt();
203                 return result;
204             }
205         }
206     }
207 
208     /**
209      * Enqueues request to poller thread and waits for result
210      */
invoke(RequestType type, Object... params)211     private Object invoke(RequestType type, Object... params) throws IOException {
212         // submit request
213         Request req = new Request(type, params);
214         synchronized (requestList) {
215             if (shutdown) {
216                 throw new ClosedWatchServiceException();
217             }
218             requestList.add(req);
219         }
220 
221         // wakeup thread
222         wakeup();
223 
224         // wait for result
225         Object result = req.awaitResult();
226 
227         if (result instanceof RuntimeException)
228             throw (RuntimeException)result;
229         if (result instanceof IOException )
230             throw (IOException)result;
231         return result;
232     }
233 
234     /**
235      * Invoked by poller thread to process all pending requests
236      *
237      * @return  true if poller thread should shutdown
238      */
239     @SuppressWarnings("unchecked")
processRequests()240     boolean processRequests() {
241         synchronized (requestList) {
242             Request req;
243             while ((req = requestList.poll()) != null) {
244                 // if in process of shutdown then reject request
245                 if (shutdown) {
246                     req.release(new ClosedWatchServiceException());
247                 }
248 
249                 switch (req.type()) {
250                     /**
251                      * Register directory
252                      */
253                     case REGISTER: {
254                         Object[] params = req.parameters();
255                         Path path = (Path)params[0];
256                         Set<? extends WatchEvent.Kind<?>> events =
257                             (Set<? extends WatchEvent.Kind<?>>)params[1];
258                         WatchEvent.Modifier[] modifiers =
259                             (WatchEvent.Modifier[])params[2];
260                         req.release(implRegister(path, events, modifiers));
261                         break;
262                     }
263                     /**
264                      * Cancel existing key
265                      */
266                     case CANCEL : {
267                         Object[] params = req.parameters();
268                         WatchKey key = (WatchKey)params[0];
269                         implCancelKey(key);
270                         req.release(null);
271                         break;
272                     }
273                     /**
274                      * Close watch service
275                      */
276                     case CLOSE: {
277                         implCloseAll();
278                         req.release(null);
279                         shutdown = true;
280                         break;
281                     }
282 
283                     default:
284                         req.release(new IOException("request not recognized"));
285                 }
286             }
287         }
288         return shutdown;
289     }
290 }
291