Lucene.Net  3.0.3
Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
NativeFSLockFactory.cs
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 using System;
19 using System.Collections.Generic;
20 
21 namespace Lucene.Net.Store
22 {
23 
24  /// <summary> <p/>Implements <see cref="LockFactory" /> using native OS file
25  /// locks. Note that because this LockFactory relies on
26  /// java.nio.* APIs for locking, any problems with those APIs
27  /// will cause locking to fail. Specifically, on certain NFS
28  /// environments the java.nio.* locks will fail (the lock can
29  /// incorrectly be double acquired) whereas <see cref="SimpleFSLockFactory" />
30  /// worked perfectly in those same
31  /// environments. For NFS based access to an index, it's
32  /// recommended that you try <see cref="SimpleFSLockFactory" />
33  /// first and work around the one limitation that a lock file
34  /// could be left when the JVM exits abnormally.<p/>
35  ///
36  /// <p/>The primary benefit of <see cref="NativeFSLockFactory" /> is
37  /// that lock files will be properly removed (by the OS) if
38  /// the JVM has an abnormal exit.<p/>
39  ///
40  /// <p/>Note that, unlike <see cref="SimpleFSLockFactory" />, the existence of
41  /// leftover lock files in the filesystem on exiting the JVM
42  /// is fine because the OS will free the locks held against
43  /// these files even though the files still remain.<p/>
44  ///
45  /// <p/>If you suspect that this or any other LockFactory is
46  /// not working properly in your environment, you can easily
47  /// test it by using <see cref="VerifyingLockFactory" />, <see cref="LockVerifyServer" />
48  /// and <see cref="LockStressTest" />.<p/>
49  ///
50  /// </summary>
51  /// <seealso cref="LockFactory">
52  /// </seealso>
53 
55  {
56  /// <summary> Create a NativeFSLockFactory instance, with null (unset)
57  /// lock directory. When you pass this factory to a <see cref="FSDirectory" />
58  /// subclass, the lock directory is automatically set to the
59  /// directory itsself. Be sure to create one instance for each directory
60  /// your create!
61  /// </summary>
62  public NativeFSLockFactory():this((System.IO.DirectoryInfo) null)
63  {
64  }
65 
66  /// <summary> Create a NativeFSLockFactory instance, storing lock
67  /// files into the specified lockDirName:
68  ///
69  /// </summary>
70  /// <param name="lockDirName">where lock files are created.
71  /// </param>
72  public NativeFSLockFactory(System.String lockDirName):this(new System.IO.DirectoryInfo(lockDirName))
73  {
74  }
75 
76  /// <summary> Create a NativeFSLockFactory instance, storing lock
77  /// files into the specified lockDir:
78  ///
79  /// </summary>
80  /// <param name="lockDir">where lock files are created.
81  /// </param>
82  public NativeFSLockFactory(System.IO.DirectoryInfo lockDir)
83  {
84  LockDir = lockDir;
85  }
86 
87  public override Lock MakeLock(System.String lockName)
88  {
89  lock (this)
90  {
91  if (internalLockPrefix != null)
92  lockName = internalLockPrefix + "-" + lockName;
93  return new NativeFSLock(internalLockDir, lockName);
94  }
95  }
96 
97  public override void ClearLock(System.String lockName)
98  {
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  bool tmpBool;
104  if (System.IO.File.Exists(internalLockDir.FullName))
105  tmpBool = true;
106  else
107  tmpBool = System.IO.Directory.Exists(internalLockDir.FullName);
108  if (tmpBool)
109  {
110  if (internalLockPrefix != null)
111  {
112  lockName = internalLockPrefix + "-" + lockName;
113  }
114  System.IO.FileInfo lockFile = new System.IO.FileInfo(System.IO.Path.Combine(internalLockDir.FullName, lockName));
115  bool tmpBool2;
116  if (System.IO.File.Exists(lockFile.FullName))
117  tmpBool2 = true;
118  else
119  tmpBool2 = System.IO.Directory.Exists(lockFile.FullName);
120  bool tmpBool3;
121  if (System.IO.File.Exists(lockFile.FullName))
122  {
123  System.IO.File.Delete(lockFile.FullName);
124  tmpBool3 = true;
125  }
126  else if (System.IO.Directory.Exists(lockFile.FullName))
127  {
128  System.IO.Directory.Delete(lockFile.FullName);
129  tmpBool3 = true;
130  }
131  else
132  tmpBool3 = false;
133  if (tmpBool2 && !tmpBool3)
134  {
135  throw new System.IO.IOException("Cannot delete " + lockFile);
136  }
137  }
138  }
139  }
140 
141 
143  {
144 
145  private System.IO.FileStream f;
146  private System.IO.FileStream channel;
147  private bool lock_Renamed;
148  private System.IO.FileInfo path;
149  private System.IO.DirectoryInfo lockDir;
150 
151  /*
152  * The javadocs for FileChannel state that you should have
153  * a single instance of a FileChannel (per JVM) for all
154  * locking against a given file. To ensure this, we have
155  * a single (static) HashSet that contains the file paths
156  * of all currently locked locks. This protects against
157  * possible cases where different Directory instances in
158  * one JVM (each with their own NativeFSLockFactory
159  * instance) have set the same lock dir and lock prefix.
160  */
161  private static HashSet<string> LOCK_HELD = new HashSet<string>();
162 
163  public NativeFSLock(System.IO.DirectoryInfo lockDir, System.String lockFileName)
164  {
165  this.lockDir = lockDir;
166  path = new System.IO.FileInfo(System.IO.Path.Combine(lockDir.FullName, lockFileName));
167  }
168 
169  private bool LockExists()
170  {
171  lock (this)
172  {
173  return lock_Renamed != false;
174  }
175  }
176 
177  public override bool Obtain()
178  {
179  lock (this)
180  {
181 
182  if (LockExists())
183  {
184  // Our instance is already locked:
185  return false;
186  }
187 
188  // Ensure that lockDir exists and is a directory.
189  bool tmpBool;
190  if (System.IO.File.Exists(lockDir.FullName))
191  tmpBool = true;
192  else
193  tmpBool = System.IO.Directory.Exists(lockDir.FullName);
194  if (!tmpBool)
195  {
196  try
197  {
198  System.IO.Directory.CreateDirectory(lockDir.FullName);
199  }
200  catch
201  {
202  throw new System.IO.IOException("Cannot create directory: " + lockDir.FullName);
203  }
204  }
205  else if (!System.IO.Directory.Exists(lockDir.FullName))
206  {
207  throw new System.IO.IOException("Found regular file where directory expected: " + lockDir.FullName);
208  }
209 
210  System.String canonicalPath = path.FullName;
211 
212  bool markedHeld = false;
213 
214  try
215  {
216 
217  // Make sure nobody else in-process has this lock held
218  // already, and, mark it held if not:
219 
220  lock (LOCK_HELD)
221  {
222  if (LOCK_HELD.Contains(canonicalPath))
223  {
224  // Someone else in this JVM already has the lock:
225  return false;
226  }
227  else
228  {
229  // This "reserves" the fact that we are the one
230  // thread trying to obtain this lock, so we own
231  // the only instance of a channel against this
232  // file:
233  LOCK_HELD.Add(canonicalPath);
234  markedHeld = true;
235  }
236  }
237 
238  try
239  {
240  f = new System.IO.FileStream(path.FullName, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite);
241  }
242  catch (System.IO.IOException e)
243  {
244  // On Windows, we can get intermittent "Access
245  // Denied" here. So, we treat this as failure to
246  // acquire the lock, but, store the reason in case
247  // there is in fact a real error case.
248  failureReason = e;
249  f = null;
250  }
251  // lucene.net: UnauthorizedAccessException does not derive from IOException like in java
252  catch (System.UnauthorizedAccessException e)
253  {
254  // On Windows, we can get intermittent "Access
255  // Denied" here. So, we treat this as failure to
256  // acquire the lock, but, store the reason in case
257  // there is in fact a real error case.
258  failureReason = e;
259  f = null;
260  }
261 
262  if (f != null)
263  {
264  try
265  {
266  channel = f;
267  lock_Renamed = false;
268  try
269  {
270  channel.Lock(0, channel.Length);
271  lock_Renamed = true;
272  }
273  catch (System.IO.IOException e)
274  {
275  // At least on OS X, we will sometimes get an
276  // intermittent "Permission Denied" IOException,
277  // which seems to simply mean "you failed to get
278  // the lock". But other IOExceptions could be
279  // "permanent" (eg, locking is not supported via
280  // the filesystem). So, we record the failure
281  // reason here; the timeout obtain (usually the
282  // one calling us) will use this as "root cause"
283  // if it fails to get the lock.
284  failureReason = e;
285  }
286  // lucene.net: UnauthorizedAccessException does not derive from IOException like in java
287  catch (System.UnauthorizedAccessException e)
288  {
289  // At least on OS X, we will sometimes get an
290  // intermittent "Permission Denied" IOException,
291  // which seems to simply mean "you failed to get
292  // the lock". But other IOExceptions could be
293  // "permanent" (eg, locking is not supported via
294  // the filesystem). So, we record the failure
295  // reason here; the timeout obtain (usually the
296  // one calling us) will use this as "root cause"
297  // if it fails to get the lock.
298  failureReason = e;
299  }
300  finally
301  {
302  if (lock_Renamed == false)
303  {
304  try
305  {
306  channel.Close();
307  }
308  finally
309  {
310  channel = null;
311  }
312  }
313  }
314  }
315  finally
316  {
317  if (channel == null)
318  {
319  try
320  {
321  f.Close();
322  }
323  finally
324  {
325  f = null;
326  }
327  }
328  }
329  }
330  }
331  finally
332  {
333  if (markedHeld && !LockExists())
334  {
335  lock (LOCK_HELD)
336  {
337  if (LOCK_HELD.Contains(canonicalPath))
338  {
339  LOCK_HELD.Remove(canonicalPath);
340  }
341  }
342  }
343  }
344  return LockExists();
345  }
346  }
347 
348  public override void Release()
349  {
350  lock (this)
351  {
352  if (LockExists())
353  {
354  try
355  {
356  channel.Unlock(0, channel.Length);
357  }
358  finally
359  {
360  lock_Renamed = false;
361  try
362  {
363  channel.Close();
364  }
365  finally
366  {
367  channel = null;
368  try
369  {
370  f.Close();
371  }
372  finally
373  {
374  f = null;
375  lock (LOCK_HELD)
376  {
377  LOCK_HELD.Remove(path.FullName);
378  }
379  }
380  }
381  }
382  bool tmpBool;
383  if (System.IO.File.Exists(path.FullName))
384  {
385  System.IO.File.Delete(path.FullName);
386  tmpBool = true;
387  }
388  else if (System.IO.Directory.Exists(path.FullName))
389  {
390  System.IO.Directory.Delete(path.FullName);
391  tmpBool = true;
392  }
393  else
394  tmpBool = false;
395  if (!tmpBool)
396  throw new LockReleaseFailedException("failed to delete " + path);
397  }
398  }
399  }
400 
401  public override bool IsLocked()
402  {
403  lock (this)
404  {
405  // The test for is isLocked is not directly possible with native file locks:
406 
407  // First a shortcut, if a lock reference in this instance is available
408  if (LockExists())
409  return true;
410 
411  // Look if lock file is present; if not, there can definitely be no lock!
412  bool tmpBool;
413  if (System.IO.File.Exists(path.FullName))
414  tmpBool = true;
415  else
416  tmpBool = System.IO.Directory.Exists(path.FullName);
417  if (!tmpBool)
418  return false;
419 
420  // Try to obtain and release (if was locked) the lock
421  try
422  {
423  bool obtained = Obtain();
424  if (obtained)
425  Release();
426  return !obtained;
427  }
428  catch (System.IO.IOException)
429  {
430  return false;
431  }
432  }
433  }
434 
435  public override System.String ToString()
436  {
437  return "NativeFSLock@" + path;
438  }
439  }
440 }