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
MMapDirectory.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 
20 using Constants = Lucene.Net.Util.Constants;
21 
22 namespace Lucene.Net.Store
23 {
24 
25  /// <summary>File-based <see cref="Directory" /> implementation that uses
26  /// mmap for reading, and <see cref="SimpleFSDirectory.SimpleFSIndexOutput" />
27  /// for writing.
28  ///
29  /// <p/><b>NOTE</b>: memory mapping uses up a portion of the
30  /// virtual memory address space in your process equal to the
31  /// size of the file being mapped. Before using this class,
32  /// be sure your have plenty of virtual address space, e.g. by
33  /// using a 64 bit JRE, or a 32 bit JRE with indexes that are
34  /// guaranteed to fit within the address space.
35  /// On 32 bit platforms also consult <see cref="MaxChunkSize" />
36  /// if you have problems with mmap failing because of fragmented
37  /// address space. If you get an OutOfMemoryException, it is recommened
38  /// to reduce the chunk size, until it works.
39  ///
40  /// <p/>Due to <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038">
41  /// this bug</a> in Sun's JRE, MMapDirectory's <see cref="IndexInput.Close" />
42  /// is unable to close the underlying OS file handle. Only when GC
43  /// finally collects the underlying objects, which could be quite
44  /// some time later, will the file handle be closed.
45  ///
46  /// <p/>This will consume additional transient disk usage: on Windows,
47  /// attempts to delete or overwrite the files will result in an
48  /// exception; on other platforms, which typically have a &quot;delete on
49  /// last close&quot; semantics, while such operations will succeed, the bytes
50  /// are still consuming space on disk. For many applications this
51  /// limitation is not a problem (e.g. if you have plenty of disk space,
52  /// and you don't rely on overwriting files on Windows) but it's still
53  /// an important limitation to be aware of.
54  ///
55  /// <p/>This class supplies the workaround mentioned in the bug report
56  /// (disabled by default, see <see cref="UseUnmap" />), which may fail on
57  /// non-Sun JVMs. It forcefully unmaps the buffer on close by using
58  /// an undocumented internal cleanup functionality.
59  /// <see cref="UNMAP_SUPPORTED" /> is <c>true</c>, if the workaround
60  /// can be enabled (with no guarantees).
61  /// </summary>
63  {
64  private class AnonymousClassPrivilegedExceptionAction // : SupportClass.IPriviligedAction // {{Aroush-2.9}}
65  {
66  public AnonymousClassPrivilegedExceptionAction(byte[] buffer, MMapDirectory enclosingInstance)
67  {
68  InitBlock(buffer, enclosingInstance);
69  }
70  private void InitBlock(byte[] buffer, MMapDirectory enclosingInstance)
71  {
72  this.buffer = buffer;
73  this.enclosingInstance = enclosingInstance;
74  }
75  private byte[] buffer;
76  private MMapDirectory enclosingInstance;
77  public MMapDirectory Enclosing_Instance
78  {
79  get
80  {
81  return enclosingInstance;
82  }
83 
84  }
85  public virtual System.Object Run()
86  {
87  // {{Aroush-2.9
88  /*
89  System.Reflection.MethodInfo getCleanerMethod = buffer.GetType().GetMethod("cleaner", (Lucene.Net.Store.MMapDirectory.NO_PARAM_TYPES == null)?new System.Type[0]:(System.Type[]) Lucene.Net.Store.MMapDirectory.NO_PARAM_TYPES);
90  getCleanerMethod.SetAccessible(true);
91  System.Object cleaner = getCleanerMethod.Invoke(buffer, (System.Object[]) Lucene.Net.Store.MMapDirectory.NO_PARAMS);
92  if (cleaner != null)
93  {
94  cleaner.GetType().GetMethod("clean", (Lucene.Net.Store.MMapDirectory.NO_PARAM_TYPES == null)?new System.Type[0]:(System.Type[]) Lucene.Net.Store.MMapDirectory.NO_PARAM_TYPES).Invoke(cleaner, (System.Object[]) Lucene.Net.Store.MMapDirectory.NO_PARAMS);
95  }
96  */
97  //System.Diagnostics.Debug.Fail("Port issue:", "sun.misc.Cleaner()"); // {{Aroush-2.9}}
98  throw new NotImplementedException("Port issue: sun.misc.Cleaner()");
99  // Aroush-2.9}}
100  //return null;
101  }
102  }
103  private void InitBlock()
104  {
105  maxBBuf = Constants.JRE_IS_64BIT?System.Int32.MaxValue:(256 * 1024 * 1024);
106  }
107 
108  /// <summary>Create a new MMapDirectory for the named location.
109  ///
110  /// </summary>
111  /// <param name="path">the path of the directory
112  /// </param>
113  /// <param name="lockFactory">the lock factory to use, or null for the default.
114  /// </param>
115  /// <throws> IOException </throws>
116  public MMapDirectory(System.IO.DirectoryInfo path, LockFactory lockFactory)
117  : base(path, lockFactory)
118  {
119  InitBlock();
120  }
121 
122  /// <summary>Create a new MMapDirectory for the named location and the default lock factory.
123  ///
124  /// </summary>
125  /// <param name="path">the path of the directory
126  /// </param>
127  /// <throws> IOException </throws>
128  public MMapDirectory(System.IO.DirectoryInfo path)
129  : base(path, null)
130  {
131  InitBlock();
132  }
133 
134  private bool useUnmapHack = false;
135  private int maxBBuf;
136 
137  /// <summary> <c>true</c>, if this platform supports unmapping mmaped files.</summary>
138  public static bool UNMAP_SUPPORTED;
139 
140  /// <summary> Enables or disables the workaround for unmapping the buffers
141  /// from address space after closing <see cref="IndexInput" />, that is
142  /// mentioned in the bug report. This hack may fail on non-Sun JVMs.
143  /// It forcefully unmaps the buffer on close by using
144  /// an undocumented internal cleanup functionality.
145  /// <p/><b>NOTE:</b> Enabling this is completely unsupported
146  /// by Java and may lead to JVM crashs if <c>IndexInput</c>
147  /// is closed while another thread is still accessing it (SIGSEGV).
148  /// </summary>
149  /// <throws> IllegalArgumentException if <see cref="UNMAP_SUPPORTED" /> </throws>
150  /// <summary> is <c>false</c> and the workaround cannot be enabled.
151  /// </summary>
152  public virtual bool UseUnmap
153  {
154  get { return useUnmapHack; }
155  set
156  {
157  if (value && !UNMAP_SUPPORTED)
158  throw new System.ArgumentException("Unmap hack not supported on this platform!");
159  this.useUnmapHack = value;
160  }
161  }
162 
163  /// <summary> Try to unmap the buffer, this method silently fails if no support
164  /// for that in the JVM. On Windows, this leads to the fact,
165  /// that mmapped files cannot be modified or deleted.
166  /// </summary>
167  internal void CleanMapping(System.IO.MemoryStream buffer)
168  {
169  if (useUnmapHack)
170  {
171  try
172  {
173  // {{Aroush-2.9}} Not converted: java.security.AccessController.doPrivileged()
174  //System.Diagnostics.Debug.Fail("Port issue:", "java.security.AccessController.doPrivileged()"); // {{Aroush-2.9}}
175  throw new NotImplementedException("Port issue: java.security.AccessController.doPrivileged()");
176  // AccessController.DoPrivileged(new AnonymousClassPrivilegedExceptionAction(buffer, this));
177  }
178  catch (System.Exception e)
179  {
180  System.IO.IOException ioe = new System.IO.IOException("unable to unmap the mapped buffer", e.InnerException);
181  throw ioe;
182  }
183  }
184  }
185 
186  /// <summary> Gets or sets the maximum chunk size (default is <see cref="int.MaxValue" /> for
187  /// 64 bit JVMs and 256 MiBytes for 32 bit JVMs) used for memory mapping.
188  /// Especially on 32 bit platform, the address space can be very fragmented,
189  /// so large index files cannot be mapped.
190  /// Using a lower chunk size makes the directory implementation a little
191  /// bit slower (as the correct chunk must be resolved on each seek)
192  /// but the chance is higher that mmap does not fail. On 64 bit
193  /// Java platforms, this parameter should always be <see cref="int.MaxValue" />,
194  /// as the adress space is big enough.
195  /// </summary>
196  public virtual int MaxChunkSize
197  {
198  get { return maxBBuf; }
199  set
200  {
201  if (value <= 0)
202  throw new System.ArgumentException("Maximum chunk size for mmap must be >0");
203  this.maxBBuf = value;
204  }
205  }
206 
207  private class MMapIndexInput : IndexInput
208  {
209  private void InitBlock(MMapDirectory enclosingInstance)
210  {
211  this.enclosingInstance = enclosingInstance;
212  }
213  private MMapDirectory enclosingInstance;
214  public MMapDirectory Enclosing_Instance
215  {
216  get
217  {
218  return enclosingInstance;
219  }
220 
221  }
222 
223  private System.IO.MemoryStream buffer;
224  private long length;
225  private bool isClone;
226  private bool isDisposed;
227 
228  internal MMapIndexInput(MMapDirectory enclosingInstance, System.IO.FileStream raf)
229  {
230  byte[] data = new byte[raf.Length];
231  raf.Read(data, 0, (int) raf.Length);
232 
233  InitBlock(enclosingInstance);
234  this.length = raf.Length;
235  this.buffer = new System.IO.MemoryStream(data);
236  }
237 
238  public override byte ReadByte()
239  {
240  try
241  {
242  return (byte) buffer.ReadByte();
243  }
244  catch (ObjectDisposedException)
245  {
246  throw new System.IO.IOException("read past EOF");
247  }
248  }
249 
250  public override void ReadBytes(byte[] b, int offset, int len)
251  {
252  try
253  {
254  buffer.Read(b, offset, len);
255  }
256  catch (ObjectDisposedException)
257  {
258  throw new System.IO.IOException("read past EOF");
259  }
260  }
261 
262  public override long FilePointer
263  {
264  get
265  {
266  return buffer.Position;
267  }
268  }
269 
270  public override void Seek(long pos)
271  {
272  buffer.Seek(pos, System.IO.SeekOrigin.Begin);
273  }
274 
275  public override long Length()
276  {
277  return length;
278  }
279 
280  public override System.Object Clone()
281  {
282  if (buffer == null)
283  throw new AlreadyClosedException("MMapIndexInput already closed");
284  MMapIndexInput clone = (MMapIndexInput) base.Clone();
285  clone.isClone = true;
286  // clone.buffer = buffer.duplicate(); // {{Aroush-1.9}}
287  return clone;
288  }
289 
290  protected override void Dispose(bool isDisposing)
291  {
292  if (isDisposed) return;
293 
294  if (isDisposing)
295  {
296  if (isClone || buffer == null)
297  return;
298  // unmap the buffer (if enabled) and at least unset it for GC
299  try
300  {
301  Enclosing_Instance.CleanMapping(buffer);
302  }
303  finally
304  {
305  buffer = null;
306  }
307  }
308 
309  isDisposed = true;
310  }
311  }
312 
313  // Because Java's ByteBuffer uses an int to address the
314  // values, it's necessary to access a file >
315  // Integer.MAX_VALUE in size using multiple byte buffers.
316  protected internal class MultiMMapIndexInput:IndexInput, System.ICloneable
317  {
318  private void InitBlock(MMapDirectory enclosingInstance)
319  {
320  this.enclosingInstance = enclosingInstance;
321  }
322  private MMapDirectory enclosingInstance;
323  public MMapDirectory Enclosing_Instance
324  {
325  get
326  {
327  return enclosingInstance;
328  }
329 
330  }
331 
332  private System.IO.MemoryStream[] buffers;
333  private int[] bufSizes; // keep here, ByteBuffer.size() method is optional
334 
335  private long length;
336 
337  private bool isDisposed;
338 
339  private int curBufIndex;
340  private int maxBufSize;
341 
342  private System.IO.MemoryStream curBuf; // redundant for speed: buffers[curBufIndex]
343  private int curAvail; // redundant for speed: (bufSizes[curBufIndex] - curBuf.position())
344 
345  private bool isClone = false;
346 
347  public MultiMMapIndexInput(MMapDirectory enclosingInstance, System.IO.FileStream raf, int maxBufSize)
348  {
349  InitBlock(enclosingInstance);
350  this.length = raf.Length;
351  this.maxBufSize = maxBufSize;
352 
353  if (maxBufSize <= 0)
354  throw new System.ArgumentException("Non positive maxBufSize: " + maxBufSize);
355 
356  if ((length / maxBufSize) > System.Int32.MaxValue)
357  {
358  throw new System.ArgumentException("RandomAccessFile too big for maximum buffer size: " + raf.ToString());
359  }
360 
361  int nrBuffers = (int) (length / maxBufSize);
362  if (((long) nrBuffers * maxBufSize) < length)
363  nrBuffers++;
364 
365  this.buffers = new System.IO.MemoryStream[nrBuffers];
366  this.bufSizes = new int[nrBuffers];
367 
368  long bufferStart = 0;
369  System.IO.FileStream rafc = raf;
370  for (int bufNr = 0; bufNr < nrBuffers; bufNr++)
371  {
372  byte[] data = new byte[rafc.Length];
373  raf.Read(data, 0, (int) rafc.Length);
374 
375  int bufSize = (length > (bufferStart + maxBufSize))?maxBufSize:(int) (length - bufferStart);
376  this.buffers[bufNr] = new System.IO.MemoryStream(data);
377  this.bufSizes[bufNr] = bufSize;
378  bufferStart += bufSize;
379  }
380  Seek(0L);
381  }
382 
383  public override byte ReadByte()
384  {
385  // Performance might be improved by reading ahead into an array of
386  // e.g. 128 bytes and readByte() from there.
387  if (curAvail == 0)
388  {
389  curBufIndex++;
390  if (curBufIndex >= buffers.Length)
391  throw new System.IO.IOException("read past EOF");
392  curBuf = buffers[curBufIndex];
393  curBuf.Seek(0, System.IO.SeekOrigin.Begin);
394  curAvail = bufSizes[curBufIndex];
395  }
396  curAvail--;
397  return (byte) curBuf.ReadByte();
398  }
399 
400  public override void ReadBytes(byte[] b, int offset, int len)
401  {
402  while (len > curAvail)
403  {
404  curBuf.Read(b, offset, curAvail);
405  len -= curAvail;
406  offset += curAvail;
407  curBufIndex++;
408  if (curBufIndex >= buffers.Length)
409  throw new System.IO.IOException("read past EOF");
410  curBuf = buffers[curBufIndex];
411  curBuf.Seek(0, System.IO.SeekOrigin.Begin);
412  curAvail = bufSizes[curBufIndex];
413  }
414  curBuf.Read(b, offset, len);
415  curAvail -= len;
416  }
417 
418  public override long FilePointer
419  {
420  get { return ((long) curBufIndex*maxBufSize) + curBuf.Position; }
421  }
422 
423  public override void Seek(long pos)
424  {
425  curBufIndex = (int) (pos / maxBufSize);
426  curBuf = buffers[curBufIndex];
427  int bufOffset = (int) (pos - ((long) curBufIndex * maxBufSize));
428  curBuf.Seek(bufOffset, System.IO.SeekOrigin.Begin);
429  curAvail = bufSizes[curBufIndex] - bufOffset;
430  }
431 
432  public override long Length()
433  {
434  return length;
435  }
436 
437  public override System.Object Clone()
438  {
439  MultiMMapIndexInput clone = (MultiMMapIndexInput) base.Clone();
440  clone.isClone = true;
441  clone.buffers = new System.IO.MemoryStream[buffers.Length];
442  // No need to clone bufSizes.
443  // Since most clones will use only one buffer, duplicate() could also be
444  // done lazy in clones, e.g. when adapting curBuf.
445  for (int bufNr = 0; bufNr < buffers.Length; bufNr++)
446  {
447  clone.buffers[bufNr] = buffers[bufNr]; // clone.buffers[bufNr] = buffers[bufNr].duplicate(); // {{Aroush-1.9}} how do we clone?!
448  }
449  try
450  {
451  clone.Seek(FilePointer);
452  }
453  catch (System.IO.IOException ioe)
454  {
455  System.SystemException newException = new System.SystemException(ioe.Message, ioe);
456  throw newException;
457  }
458  return clone;
459  }
460 
461  protected override void Dispose(bool disposing)
462  {
463  if (isDisposed) return;
464  if (isClone || buffers == null)
465  return;
466  try
467  {
468  for (int bufNr = 0; bufNr < buffers.Length; bufNr++)
469  {
470  // unmap the buffer (if enabled) and at least unset it for GC
471  try
472  {
473  Enclosing_Instance.CleanMapping(buffers[bufNr]);
474  }
475  finally
476  {
477  buffers[bufNr] = null;
478  }
479  }
480  }
481  finally
482  {
483  buffers = null;
484  }
485  isDisposed = true;
486  }
487  }
488 
489  /// <summary>Creates an IndexInput for the file with the given name. </summary>
490  public override IndexInput OpenInput(System.String name, int bufferSize)
491  {
492  EnsureOpen();
493  System.String path = System.IO.Path.Combine(Directory.FullName, name);
494  System.IO.FileStream raf = new System.IO.FileStream(path, System.IO.FileMode.Open, System.IO.FileAccess.Read);
495  try
496  {
497  return (raf.Length <= (long) maxBBuf)?(IndexInput) new MMapIndexInput(this, raf):(IndexInput) new MultiMMapIndexInput(this, raf, maxBBuf);
498  }
499  finally
500  {
501  raf.Close();
502  }
503  }
504 
505  /// <summary>Creates an IndexOutput for the file with the given name. </summary>
506  public override IndexOutput CreateOutput(System.String name)
507  {
508  InitOutput(name);
509  return new SimpleFSDirectory.SimpleFSIndexOutput(new System.IO.FileInfo(System.IO.Path.Combine(internalDirectory.FullName, name)));
510  }
511  static MMapDirectory()
512  {
513  {
514  bool v;
515  try
516  {
517  // {{Aroush-2.9
518  /*
519  System.Type.GetType("sun.misc.Cleaner"); // {{Aroush-2.9}} port issue?
520  System.Type.GetType("java.nio.DirectByteBuffer").GetMethod("cleaner", (NO_PARAM_TYPES == null)?new System.Type[0]:(System.Type[]) NO_PARAM_TYPES);
521  */
522  //System.Diagnostics.Debug.Fail("Port issue:", "sun.misc.Cleaner.clean()"); // {{Aroush-2.9}}
523  throw new NotImplementedException("Port issue: sun.misc.Cleaner.clean()");
524  // Aroush-2.9}}
525  //v = true;
526  }
527  catch (System.Exception)
528  {
529  v = false;
530  }
531  UNMAP_SUPPORTED = v;
532  }
533  }
534  }
535 }