add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / src / java / org / apache / lucene / store / NativeFSLockFactory.java
1 package org.apache.lucene.store;
2
3 /**
4  * Licensed to the Apache Software Foundation (ASF) under one or more
5  * contributor license agreements.  See the NOTICE file distributed with
6  * this work for additional information regarding copyright ownership.
7  * The ASF licenses this file to You under the Apache License, Version 2.0
8  * (the "License"); you may not use this file except in compliance with
9  * the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 import java.nio.channels.FileChannel;
21 import java.nio.channels.FileLock;
22 import java.io.File;
23 import java.io.RandomAccessFile;
24 import java.io.IOException;
25 import java.util.HashSet;
26
27 /**
28  * <p>Implements {@link LockFactory} using native OS file
29  * locks.  Note that because this LockFactory relies on
30  * java.nio.* APIs for locking, any problems with those APIs
31  * will cause locking to fail.  Specifically, on certain NFS
32  * environments the java.nio.* locks will fail (the lock can
33  * incorrectly be double acquired) whereas {@link
34  * SimpleFSLockFactory} worked perfectly in those same
35  * environments.  For NFS based access to an index, it's
36  * recommended that you try {@link SimpleFSLockFactory}
37  * first and work around the one limitation that a lock file
38  * could be left when the JVM exits abnormally.</p>
39  *
40  * <p>The primary benefit of {@link NativeFSLockFactory} is
41  * that lock files will be properly removed (by the OS) if
42  * the JVM has an abnormal exit.</p>
43  * 
44  * <p>Note that, unlike {@link SimpleFSLockFactory}, the existence of
45  * leftover lock files in the filesystem on exiting the JVM
46  * is fine because the OS will free the locks held against
47  * these files even though the files still remain.</p>
48  *
49  * <p>If you suspect that this or any other LockFactory is
50  * not working properly in your environment, you can easily
51  * test it by using {@link VerifyingLockFactory}, {@link
52  * LockVerifyServer} and {@link LockStressTest}.</p>
53  *
54  * @see LockFactory
55  */
56
57 public class NativeFSLockFactory extends FSLockFactory {
58
59   /**
60    * Create a NativeFSLockFactory instance, with null (unset)
61    * lock directory. When you pass this factory to a {@link FSDirectory}
62    * subclass, the lock directory is automatically set to the
63    * directory itself. Be sure to create one instance for each directory
64    * your create!
65    */
66   public NativeFSLockFactory() throws IOException {
67     this((File) null);
68   }
69
70   /**
71    * Create a NativeFSLockFactory instance, storing lock
72    * files into the specified lockDirName:
73    *
74    * @param lockDirName where lock files are created.
75    */
76   public NativeFSLockFactory(String lockDirName) throws IOException {
77     this(new File(lockDirName));
78   }
79
80   /**
81    * Create a NativeFSLockFactory instance, storing lock
82    * files into the specified lockDir:
83    * 
84    * @param lockDir where lock files are created.
85    */
86   public NativeFSLockFactory(File lockDir) throws IOException {
87     setLockDir(lockDir);
88   }
89
90   @Override
91   public synchronized Lock makeLock(String lockName) {
92     if (lockPrefix != null)
93       lockName = lockPrefix + "-" + lockName;
94     return new NativeFSLock(lockDir, lockName);
95   }
96
97   @Override
98   public void clearLock(String lockName) throws IOException {
99     // Note that this isn't strictly required anymore
100     // because the existence of these files does not mean
101     // they are locked, but, still do this in case people
102     // really want to see the files go away:
103     if (lockDir.exists()) {
104       
105       // Try to release the lock first - if it's held by another process, this
106       // method should not silently fail.
107       // NOTE: makeLock fixes the lock name by prefixing it w/ lockPrefix.
108       // Therefore it should be called before the code block next which prefixes
109       // the given name.
110       makeLock(lockName).release();
111
112       if (lockPrefix != null) {
113         lockName = lockPrefix + "-" + lockName;
114       }
115       
116       // As mentioned above, we don't care if the deletion of the file failed.
117       new File(lockDir, lockName).delete();
118     }
119   }
120 }
121
122 class NativeFSLock extends Lock {
123
124   private RandomAccessFile f;
125   private FileChannel channel;
126   private FileLock lock;
127   private File path;
128   private File lockDir;
129
130   /*
131    * The javadocs for FileChannel state that you should have
132    * a single instance of a FileChannel (per JVM) for all
133    * locking against a given file (locks are tracked per 
134    * FileChannel instance in Java 1.4/1.5). Even using the same 
135    * FileChannel instance is not completely thread-safe with Java 
136    * 1.4/1.5 though. To work around this, we have a single (static) 
137    * HashSet that contains the file paths of all currently 
138    * locked locks.  This protects against possible cases 
139    * where different Directory instances in one JVM (each 
140    * with their own NativeFSLockFactory instance) have set 
141    * the same lock dir and lock prefix. However, this will not 
142    * work when LockFactorys are created by different 
143    * classloaders (eg multiple webapps). 
144    * 
145    * TODO: Java 1.6 tracks system wide locks in a thread safe manner 
146    * (same FileChannel instance or not), so we may want to 
147    * change this when Lucene moves to Java 1.6.
148    */
149   private static HashSet<String> LOCK_HELD = new HashSet<String>();
150
151   public NativeFSLock(File lockDir, String lockFileName) {
152     this.lockDir = lockDir;
153     path = new File(lockDir, lockFileName);
154   }
155
156   private synchronized boolean lockExists() {
157     return lock != null;
158   }
159
160   @Override
161   public synchronized boolean obtain() throws IOException {
162
163     if (lockExists()) {
164       // Our instance is already locked:
165       return false;
166     }
167
168     // Ensure that lockDir exists and is a directory.
169     if (!lockDir.exists()) {
170       if (!lockDir.mkdirs())
171         throw new IOException("Cannot create directory: " +
172                               lockDir.getAbsolutePath());
173     } else if (!lockDir.isDirectory()) {
174       throw new IOException("Found regular file where directory expected: " + 
175                             lockDir.getAbsolutePath());
176     }
177
178     String canonicalPath = path.getCanonicalPath();
179
180     boolean markedHeld = false;
181
182     try {
183
184       // Make sure nobody else in-process has this lock held
185       // already, and, mark it held if not:
186
187       synchronized(LOCK_HELD) {
188         if (LOCK_HELD.contains(canonicalPath)) {
189           // Someone else in this JVM already has the lock:
190           return false;
191         } else {
192           // This "reserves" the fact that we are the one
193           // thread trying to obtain this lock, so we own
194           // the only instance of a channel against this
195           // file:
196           LOCK_HELD.add(canonicalPath);
197           markedHeld = true;
198         }
199       }
200
201       try {
202         f = new RandomAccessFile(path, "rw");
203       } catch (IOException e) {
204         // On Windows, we can get intermittent "Access
205         // Denied" here.  So, we treat this as failure to
206         // acquire the lock, but, store the reason in case
207         // there is in fact a real error case.
208         failureReason = e;
209         f = null;
210       }
211
212       if (f != null) {
213         try {
214           channel = f.getChannel();
215           try {
216             lock = channel.tryLock();
217           } catch (IOException e) {
218             // At least on OS X, we will sometimes get an
219             // intermittent "Permission Denied" IOException,
220             // which seems to simply mean "you failed to get
221             // the lock".  But other IOExceptions could be
222             // "permanent" (eg, locking is not supported via
223             // the filesystem).  So, we record the failure
224             // reason here; the timeout obtain (usually the
225             // one calling us) will use this as "root cause"
226             // if it fails to get the lock.
227             failureReason = e;
228           } finally {
229             if (lock == null) {
230               try {
231                 channel.close();
232               } finally {
233                 channel = null;
234               }
235             }
236           }
237         } finally {
238           if (channel == null) {
239             try {
240               f.close();
241             } finally {
242               f = null;
243             }
244           }
245         }
246       }
247
248     } finally {
249       if (markedHeld && !lockExists()) {
250         synchronized(LOCK_HELD) {
251           if (LOCK_HELD.contains(canonicalPath)) {
252             LOCK_HELD.remove(canonicalPath);
253           }
254         }
255       }
256     }
257     return lockExists();
258   }
259
260   @Override
261   public synchronized void release() throws IOException {
262     if (lockExists()) {
263       try {
264         lock.release();
265       } finally {
266         lock = null;
267         try {
268           channel.close();
269         } finally {
270           channel = null;
271           try {
272             f.close();
273           } finally {
274             f = null;
275             synchronized(LOCK_HELD) {
276               LOCK_HELD.remove(path.getCanonicalPath());
277             }
278           }
279         }
280       }
281       // LUCENE-2421: we don't care anymore if the file cannot be deleted
282       // because it's held up by another process (e.g. AntiVirus). NativeFSLock
283       // does not depend on the existence/absence of the lock file
284       path.delete();
285     } else {
286       // if we don't hold the lock, and somebody still called release(), for
287       // example as a result of calling IndexWriter.unlock(), we should attempt
288       // to obtain the lock and release it. If the obtain fails, it means the
289       // lock cannot be released, and we should throw a proper exception rather
290       // than silently failing/not doing anything.
291       boolean obtained = false;
292       try {
293         if (!(obtained = obtain())) {
294           throw new LockReleaseFailedException(
295               "Cannot forcefully unlock a NativeFSLock which is held by another indexer component: "
296                   + path);
297         }
298       } finally {
299         if (obtained) {
300           release();
301         }
302       }
303     }
304   }
305
306   @Override
307   public synchronized boolean isLocked() {
308     // The test for is isLocked is not directly possible with native file locks:
309     
310     // First a shortcut, if a lock reference in this instance is available
311     if (lockExists()) return true;
312     
313     // Look if lock file is present; if not, there can definitely be no lock!
314     if (!path.exists()) return false;
315     
316     // Try to obtain and release (if was locked) the lock
317     try {
318       boolean obtained = obtain();
319       if (obtained) release();
320       return !obtained;
321     } catch (IOException ioe) {
322       return false;
323     }    
324   }
325
326   @Override
327   public String toString() {
328     return "NativeFSLock@" + path;
329   }
330 }