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
SegmentInfos.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 using System.IO;
21 using Lucene.Net.Support;
22 using ChecksumIndexInput = Lucene.Net.Store.ChecksumIndexInput;
23 using ChecksumIndexOutput = Lucene.Net.Store.ChecksumIndexOutput;
24 using Directory = Lucene.Net.Store.Directory;
25 using IndexInput = Lucene.Net.Store.IndexInput;
26 using IndexOutput = Lucene.Net.Store.IndexOutput;
27 using NoSuchDirectoryException = Lucene.Net.Store.NoSuchDirectoryException;
28 
29 namespace Lucene.Net.Index
30 {
31 
32  /// <summary> A collection of segmentInfo objects with methods for operating on
33  /// those segments in relation to the file system.
34  ///
35  /// <p/><b>NOTE:</b> This API is new and still experimental
36  /// (subject to change suddenly in the next release)<p/>
37  /// </summary>
38  [Serializable]
39  public sealed class SegmentInfos : List<SegmentInfo>, ICloneable
40  {
41  private class AnonymousClassFindSegmentsFile:FindSegmentsFile
42  {
43  private void InitBlock(SegmentInfos enclosingInstance)
44  {
45  this.enclosingInstance = enclosingInstance;
46  }
47  private SegmentInfos enclosingInstance;
48  public SegmentInfos Enclosing_Instance
49  {
50  get
51  {
52  return enclosingInstance;
53  }
54 
55  }
56  internal AnonymousClassFindSegmentsFile(SegmentInfos enclosingInstance, Lucene.Net.Store.Directory Param1):base(Param1)
57  {
58  InitBlock(enclosingInstance);
59  }
60 
61  public /*protected internal*/ override System.Object DoBody(System.String segmentFileName)
62  {
63  Enclosing_Instance.Read(directory, segmentFileName);
64  return null;
65  }
66  }
67  /// <summary>The file format version, a negative number. </summary>
68  /* Works since counter, the old 1st entry, is always >= 0 */
69  public const int FORMAT = - 1;
70 
71  /// <summary>This format adds details used for lockless commits. It differs
72  /// slightly from the previous format in that file names
73  /// are never re-used (write once). Instead, each file is
74  /// written to the next generation. For example,
75  /// segments_1, segments_2, etc. This allows us to not use
76  /// a commit lock. See <a
77  /// href="http://lucene.apache.org/java/docs/fileformats.html">file
78  /// formats</a> for details.
79  /// </summary>
80  public const int FORMAT_LOCKLESS = - 2;
81 
82  /// <summary>This format adds a "hasSingleNormFile" flag into each segment info.
83  /// See <a href="http://issues.apache.org/jira/browse/LUCENE-756">LUCENE-756</a>
84  /// for details.
85  /// </summary>
86  public const int FORMAT_SINGLE_NORM_FILE = - 3;
87 
88  /// <summary>This format allows multiple segments to share a single
89  /// vectors and stored fields file.
90  /// </summary>
91  public const int FORMAT_SHARED_DOC_STORE = - 4;
92 
93  /// <summary>This format adds a checksum at the end of the file to
94  /// ensure all bytes were successfully written.
95  /// </summary>
96  public const int FORMAT_CHECKSUM = - 5;
97 
98  /// <summary>This format adds the deletion count for each segment.
99  /// This way IndexWriter can efficiently report numDocs().
100  /// </summary>
101  public const int FORMAT_DEL_COUNT = - 6;
102 
103  /// <summary>This format adds the boolean hasProx to record if any
104  /// fields in the segment store prox information (ie, have
105  /// omitTermFreqAndPositions==false)
106  /// </summary>
107  public const int FORMAT_HAS_PROX = - 7;
108 
109  /// <summary>This format adds optional commit userData (String) storage. </summary>
110  public const int FORMAT_USER_DATA = - 8;
111 
112  /// <summary>This format adds optional per-segment String
113  /// dianostics storage, and switches userData to Map
114  /// </summary>
115  public const int FORMAT_DIAGNOSTICS = - 9;
116 
117  /* This must always point to the most recent file format. */
118  internal static readonly int CURRENT_FORMAT = FORMAT_DIAGNOSTICS;
119 
120  public int counter = 0; // used to name new segments
121  /// <summary> counts how often the index has been changed by adding or deleting docs.
122  /// starting with the current time in milliseconds forces to create unique version numbers.
123  /// </summary>
124  private long version = (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond);
125 
126  private long generation = 0; // generation of the "segments_N" for the next commit
127  private long lastGeneration = 0; // generation of the "segments_N" file we last successfully read
128  // or wrote; this is normally the same as generation except if
129  // there was an IOException that had interrupted a commit
130 
131  private IDictionary<string, string> userData = new HashMap<string, string>(); // Opaque Map<String, String> that user can specify during IndexWriter.commit
132 
133  /// <summary> If non-null, information about loading segments_N files</summary>
134  /// <seealso cref="SetInfoStream">
135  /// </seealso>
136  private static System.IO.StreamWriter infoStream;
137 
138  public SegmentInfo Info(int i)
139  {
140  return (SegmentInfo) this[i];
141  }
142 
143  /// <summary> Get the generation (N) of the current segments_N file
144  /// from a list of files.
145  ///
146  /// </summary>
147  /// <param name="files">-- array of file names to check
148  /// </param>
149  public static long GetCurrentSegmentGeneration(System.String[] files)
150  {
151  if (files == null)
152  {
153  return - 1;
154  }
155  long max = - 1;
156  for (int i = 0; i < files.Length; i++)
157  {
158  System.String file = files[i];
159  if (file.StartsWith(IndexFileNames.SEGMENTS) && !file.Equals(IndexFileNames.SEGMENTS_GEN))
160  {
161  long gen = GenerationFromSegmentsFileName(file);
162  if (gen > max)
163  {
164  max = gen;
165  }
166  }
167  }
168  return max;
169  }
170 
171  /// <summary> Get the generation (N) of the current segments_N file
172  /// in the directory.
173  ///
174  /// </summary>
175  /// <param name="directory">-- directory to search for the latest segments_N file
176  /// </param>
177  public static long GetCurrentSegmentGeneration(Directory directory)
178  {
179  try
180  {
181  return GetCurrentSegmentGeneration(directory.ListAll());
182  }
184  {
185  return - 1;
186  }
187  }
188 
189  /// <summary> Get the filename of the current segments_N file
190  /// from a list of files.
191  ///
192  /// </summary>
193  /// <param name="files">-- array of file names to check
194  /// </param>
195 
196  public static System.String GetCurrentSegmentFileName(System.String[] files)
197  {
198  return IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", GetCurrentSegmentGeneration(files));
199  }
200 
201  /// <summary> Get the filename of the current segments_N file
202  /// in the directory.
203  ///
204  /// </summary>
205  /// <param name="directory">-- directory to search for the latest segments_N file
206  /// </param>
207  public static System.String GetCurrentSegmentFileName(Directory directory)
208  {
209  return IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", GetCurrentSegmentGeneration(directory));
210  }
211 
212  /// <summary> Get the segments_N filename in use by this segment infos.</summary>
213  public System.String GetCurrentSegmentFileName()
214  {
215  return IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", lastGeneration);
216  }
217 
218  /// <summary> Parse the generation off the segments file name and
219  /// return it.
220  /// </summary>
221  public static long GenerationFromSegmentsFileName(System.String fileName)
222  {
223  if (fileName.Equals(IndexFileNames.SEGMENTS))
224  {
225  return 0;
226  }
227  else if (fileName.StartsWith(IndexFileNames.SEGMENTS))
228  {
229  return Number.ToInt64(fileName.Substring(1 + IndexFileNames.SEGMENTS.Length));
230  }
231  else
232  {
233  throw new System.ArgumentException("fileName \"" + fileName + "\" is not a segments file");
234  }
235  }
236 
237 
238  /// <summary> Get the next segments_N filename that will be written.</summary>
239  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
240  public System.String GetNextSegmentFileName()
241  {
242  long nextGeneration;
243 
244  if (generation == - 1)
245  {
246  nextGeneration = 1;
247  }
248  else
249  {
250  nextGeneration = generation + 1;
251  }
252  return IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", nextGeneration);
253  }
254 
255  /// <summary> Read a particular segmentFileName. Note that this may
256  /// throw an IOException if a commit is in process.
257  ///
258  /// </summary>
259  /// <param name="directory">-- directory containing the segments file
260  /// </param>
261  /// <param name="segmentFileName">-- segment file to load
262  /// </param>
263  /// <throws> CorruptIndexException if the index is corrupt </throws>
264  /// <throws> IOException if there is a low-level IO error </throws>
265  public void Read(Directory directory, System.String segmentFileName)
266  {
267  bool success = false;
268 
269  // Clear any previous segments:
270  Clear();
271 
272  var input = new ChecksumIndexInput(directory.OpenInput(segmentFileName));
273 
274  generation = GenerationFromSegmentsFileName(segmentFileName);
275 
276  lastGeneration = generation;
277 
278  try
279  {
280  int format = input.ReadInt();
281  if (format < 0)
282  {
283  // file contains explicit format info
284  // check that it is a format we can understand
285  if (format < CURRENT_FORMAT)
286  throw new CorruptIndexException("Unknown format version: " + format);
287  version = input.ReadLong(); // read version
288  counter = input.ReadInt(); // read counter
289  }
290  else
291  {
292  // file is in old format without explicit format info
293  counter = format;
294  }
295 
296  for (int i = input.ReadInt(); i > 0; i--)
297  {
298  // read segmentInfos
299  Add(new SegmentInfo(directory, format, input));
300  }
301 
302  if (format >= 0)
303  {
304  // in old format the version number may be at the end of the file
305  if (input.FilePointer >= input.Length())
306  version = (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond);
307  // old file format without version number
308  else
309  version = input.ReadLong(); // read version
310  }
311 
312  if (format <= FORMAT_USER_DATA)
313  {
314  if (format <= FORMAT_DIAGNOSTICS)
315  {
316  userData = input.ReadStringStringMap();
317  }
318  else if (0 != input.ReadByte())
319  {
320  // TODO: Should be read-only map
321  userData = new HashMap<string,string> {{"userData", input.ReadString()}};
322  }
323  else
324  {
325  // TODO: Should be empty read-only map
326  userData = new HashMap<string, string>();
327  }
328  }
329  else
330  {
331  // TODO: Should be empty read-only map
332  userData = new HashMap<string, string>();
333  }
334 
335  if (format <= FORMAT_CHECKSUM)
336  {
337  long checksumNow = input.Checksum;
338  long checksumThen = input.ReadLong();
339  if (checksumNow != checksumThen)
340  throw new CorruptIndexException("checksum mismatch in segments file");
341  }
342  success = true;
343  }
344  finally
345  {
346  input.Close();
347  if (!success)
348  {
349  // Clear any segment infos we had loaded so we
350  // have a clean slate on retry:
351  Clear();
352  }
353  }
354  }
355 
356  /// <summary> This version of read uses the retry logic (for lock-less
357  /// commits) to find the right segments file to load.
358  /// </summary>
359  /// <throws> CorruptIndexException if the index is corrupt </throws>
360  /// <throws> IOException if there is a low-level IO error </throws>
361  public void Read(Directory directory)
362  {
363 
364  generation = lastGeneration = - 1;
365 
366  new AnonymousClassFindSegmentsFile(this, directory).Run();
367  }
368 
369  // Only non-null after prepareCommit has been called and
370  // before finishCommit is called
371  internal ChecksumIndexOutput pendingSegnOutput;
372 
373  private void Write(Directory directory)
374  {
375 
376  System.String segmentFileName = GetNextSegmentFileName();
377 
378  // Always advance the generation on write:
379  if (generation == - 1)
380  {
381  generation = 1;
382  }
383  else
384  {
385  generation++;
386  }
387 
388  var segnOutput = new ChecksumIndexOutput(directory.CreateOutput(segmentFileName));
389 
390  bool success = false;
391 
392  try
393  {
394  segnOutput.WriteInt(CURRENT_FORMAT); // write FORMAT
395  segnOutput.WriteLong(++version); // every write changes
396  // the index
397  segnOutput.WriteInt(counter); // write counter
398  segnOutput.WriteInt(Count); // write infos
399  for (int i = 0; i < Count; i++)
400  {
401  Info(i).Write(segnOutput);
402  }
403  segnOutput.WriteStringStringMap(userData);
404  segnOutput.PrepareCommit();
405  success = true;
406  pendingSegnOutput = segnOutput;
407  }
408  finally
409  {
410  if (!success)
411  {
412  // We hit an exception above; try to close the file
413  // but suppress any exception:
414  try
415  {
416  segnOutput.Close();
417  }
418  catch (System.Exception)
419  {
420  // Suppress so we keep throwing the original exception
421  }
422  try
423  {
424  // Try not to leave a truncated segments_N file in
425  // the index:
426  directory.DeleteFile(segmentFileName);
427  }
428  catch (System.Exception)
429  {
430  // Suppress so we keep throwing the original exception
431  }
432  }
433  }
434  }
435 
436  /// <summary> Returns a copy of this instance, also copying each
437  /// SegmentInfo.
438  /// </summary>
439 
440  public System.Object Clone()
441  {
442  SegmentInfos sis = new SegmentInfos();
443  for (int i = 0; i < this.Count; i++)
444  {
445  sis.Add((SegmentInfo)this[i].Clone());
446  }
447  sis.counter = this.counter;
448  sis.generation = this.generation;
449  sis.lastGeneration = this.lastGeneration;
450  // sis.pendingSegnOutput = this.pendingSegnOutput; // {{Aroush-2.9}} needed?
451  sis.userData = new HashMap<string, string>(userData);
452  sis.version = this.version;
453  return sis;
454  }
455 
456  /// <summary> version number when this SegmentInfos was generated.</summary>
457  public long Version
458  {
459  get { return version; }
460  }
461 
462  public long Generation
463  {
464  get { return generation; }
465  }
466 
467  public long LastGeneration
468  {
469  get { return lastGeneration; }
470  }
471 
472  /// <summary> Current version number from segments file.</summary>
473  /// <throws> CorruptIndexException if the index is corrupt </throws>
474  /// <throws> IOException if there is a low-level IO error </throws>
475  public static long ReadCurrentVersion(Directory directory)
476  {
477  // Fully read the segments file: this ensures that it's
478  // completely written so that if
479  // IndexWriter.prepareCommit has been called (but not
480  // yet commit), then the reader will still see itself as
481  // current:
482  var sis = new SegmentInfos();
483  sis.Read(directory);
484  return sis.version;
485  //return (long) ((System.Int64) new AnonymousClassFindSegmentsFile1(directory).Run());
486  //DIGY: AnonymousClassFindSegmentsFile1 can safely be deleted
487  }
488 
489  /// <summary> Returns userData from latest segments file</summary>
490  /// <throws> CorruptIndexException if the index is corrupt </throws>
491  /// <throws> IOException if there is a low-level IO error </throws>
492  public static System.Collections.Generic.IDictionary<string, string> ReadCurrentUserData(Directory directory)
493  {
494  var sis = new SegmentInfos();
495  sis.Read(directory);
496  return sis.UserData;
497  }
498 
499  /// <summary>If non-null, information about retries when loading
500  /// the segments file will be printed to this.
501  /// </summary>
502  public static void SetInfoStream(System.IO.StreamWriter infoStream)
503  {
504  SegmentInfos.infoStream = infoStream;
505  }
506 
507  /* Advanced configuration of retry logic in loading
508  segments_N file */
509  private static int defaultGenFileRetryCount = 10;
510  private static int defaultGenFileRetryPauseMsec = 50;
511  private static int defaultGenLookaheadCount = 10;
512 
513  /// <summary> Advanced: Gets or sets how many times to try loading the
514  /// segments.gen file contents to determine current segment
515  /// generation. This file is only referenced when the
516  /// primary method (listing the directory) fails.
517  /// </summary>
518  public static int DefaultGenFileRetryCount
519  {
520  get { return defaultGenFileRetryCount; }
521  set { defaultGenFileRetryCount = value; }
522  }
523 
524  public static int DefaultGenFileRetryPauseMsec
525  {
526  set { defaultGenFileRetryPauseMsec = value; }
527  get { return defaultGenFileRetryPauseMsec; }
528  }
529 
530  /// <summary> Advanced: set how many times to try incrementing the
531  /// gen when loading the segments file. This only runs if
532  /// the primary (listing directory) and secondary (opening
533  /// segments.gen file) methods fail to find the segments
534  /// file.
535  /// </summary>
536  public static int DefaultGenLookaheadCount
537  {
538  set { defaultGenLookaheadCount = value; }
539  get { return defaultGenLookaheadCount; }
540  }
541 
542  /// <seealso cref="SetInfoStream">
543  /// </seealso>
544  public static StreamWriter InfoStream
545  {
546  get { return infoStream; }
547  }
548 
549  private static void Message(System.String message)
550  {
551  if (infoStream != null)
552  {
553  infoStream.WriteLine("SIS [" + ThreadClass.Current().Name + "]: " + message);
554  }
555  }
556 
557  /// <summary> Utility class for executing code that needs to do
558  /// something with the current segments file. This is
559  /// necessary with lock-less commits because from the time
560  /// you locate the current segments file name, until you
561  /// actually open it, read its contents, or check modified
562  /// time, etc., it could have been deleted due to a writer
563  /// commit finishing.
564  /// </summary>
565  public abstract class FindSegmentsFile
566  {
567 
568  internal Directory directory;
569 
570  protected FindSegmentsFile(Directory directory)
571  {
572  this.directory = directory;
573  }
574 
575  public System.Object Run()
576  {
577  return Run(null);
578  }
579 
580  public System.Object Run(IndexCommit commit)
581  {
582  if (commit != null)
583  {
584  if (directory != commit.Directory)
585  throw new System.IO.IOException("the specified commit does not match the specified Directory");
586  return DoBody(commit.SegmentsFileName);
587  }
588 
589  System.String segmentFileName = null;
590  long lastGen = - 1;
591  long gen = 0;
592  int genLookaheadCount = 0;
593  System.IO.IOException exc = null;
594  bool retry = false;
595 
596  int method = 0;
597 
598  // Loop until we succeed in calling doBody() without
599  // hitting an IOException. An IOException most likely
600  // means a commit was in process and has finished, in
601  // the time it took us to load the now-old infos files
602  // (and segments files). It's also possible it's a
603  // true error (corrupt index). To distinguish these,
604  // on each retry we must see "forward progress" on
605  // which generation we are trying to load. If we
606  // don't, then the original error is real and we throw
607  // it.
608 
609  // We have three methods for determining the current
610  // generation. We try the first two in parallel, and
611  // fall back to the third when necessary.
612 
613  while (true)
614  {
615 
616  if (0 == method)
617  {
618 
619  // Method 1: list the directory and use the highest
620  // segments_N file. This method works well as long
621  // as there is no stale caching on the directory
622  // contents (NOTE: NFS clients often have such stale
623  // caching):
624  System.String[] files = null;
625 
626  long genA = - 1;
627 
628  files = directory.ListAll();
629 
630  if (files != null)
631  genA = Lucene.Net.Index.SegmentInfos.GetCurrentSegmentGeneration(files);
632 
633  Lucene.Net.Index.SegmentInfos.Message("directory listing genA=" + genA);
634 
635  // Method 2: open segments.gen and read its
636  // contents. Then we take the larger of the two
637  // gens. This way, if either approach is hitting
638  // a stale cache (NFS) we have a better chance of
639  // getting the right generation.
640  long genB = - 1;
641  for (int i = 0; i < Lucene.Net.Index.SegmentInfos.defaultGenFileRetryCount; i++)
642  {
643  IndexInput genInput = null;
644  try
645  {
646  genInput = directory.OpenInput(IndexFileNames.SEGMENTS_GEN);
647  }
648  catch (System.IO.FileNotFoundException e)
649  {
650  Lucene.Net.Index.SegmentInfos.Message("segments.gen open: FileNotFoundException " + e);
651  break;
652  }
653  catch (System.IO.IOException e)
654  {
655  Lucene.Net.Index.SegmentInfos.Message("segments.gen open: IOException " + e);
656  }
657 
658  if (genInput != null)
659  {
660  try
661  {
662  int version = genInput.ReadInt();
663  if (version == Lucene.Net.Index.SegmentInfos.FORMAT_LOCKLESS)
664  {
665  long gen0 = genInput.ReadLong();
666  long gen1 = genInput.ReadLong();
667  Lucene.Net.Index.SegmentInfos.Message("fallback check: " + gen0 + "; " + gen1);
668  if (gen0 == gen1)
669  {
670  // The file is consistent.
671  genB = gen0;
672  break;
673  }
674  }
675  }
676  catch (System.IO.IOException)
677  {
678  // will retry
679  }
680  finally
681  {
682  genInput.Close();
683  }
684  }
685 
686  System.Threading.Thread.Sleep(new TimeSpan((System.Int64) 10000 * Lucene.Net.Index.SegmentInfos.defaultGenFileRetryPauseMsec));
687 
688 
689  }
690 
691  Lucene.Net.Index.SegmentInfos.Message(IndexFileNames.SEGMENTS_GEN + " check: genB=" + genB);
692 
693  // Pick the larger of the two gen's:
694  if (genA > genB)
695  gen = genA;
696  else
697  gen = genB;
698 
699  if (gen == - 1)
700  {
701  throw new System.IO.FileNotFoundException("no segments* file found in " + directory + ": files:" + string.Join(" ", files));
702  }
703  }
704 
705  // Third method (fallback if first & second methods
706  // are not reliable): since both directory cache and
707  // file contents cache seem to be stale, just
708  // advance the generation.
709  if (1 == method || (0 == method && lastGen == gen && retry))
710  {
711 
712  method = 1;
713 
714  if (genLookaheadCount < Lucene.Net.Index.SegmentInfos.defaultGenLookaheadCount)
715  {
716  gen++;
717  genLookaheadCount++;
718  Lucene.Net.Index.SegmentInfos.Message("look ahead increment gen to " + gen);
719  }
720  }
721 
722  if (lastGen == gen)
723  {
724 
725  // This means we're about to try the same
726  // segments_N last tried. This is allowed,
727  // exactly once, because writer could have been in
728  // the process of writing segments_N last time.
729 
730  if (retry)
731  {
732  // OK, we've tried the same segments_N file
733  // twice in a row, so this must be a real
734  // error. We throw the original exception we
735  // got.
736  throw exc;
737  }
738 
739  retry = true;
740  }
741  else if (0 == method)
742  {
743  // Segment file has advanced since our last loop, so
744  // reset retry:
745  retry = false;
746  }
747 
748  lastGen = gen;
749 
750  segmentFileName = IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", gen);
751 
752  try
753  {
754  System.Object v = DoBody(segmentFileName);
755  Lucene.Net.Index.SegmentInfos.Message("success on " + segmentFileName);
756 
757  return v;
758  }
759  catch (System.IO.IOException err)
760  {
761 
762  // Save the original root cause:
763  if (exc == null)
764  {
765  exc = err;
766  }
767 
768  Lucene.Net.Index.SegmentInfos.Message("primary Exception on '" + segmentFileName + "': " + err + "'; will retry: retry=" + retry + "; gen = " + gen);
769 
770  if (!retry && gen > 1)
771  {
772 
773  // This is our first time trying this segments
774  // file (because retry is false), and, there is
775  // possibly a segments_(N-1) (because gen > 1).
776  // So, check if the segments_(N-1) exists and
777  // try it if so:
778  System.String prevSegmentFileName = IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", gen - 1);
779 
780  bool prevExists;
781  prevExists = directory.FileExists(prevSegmentFileName);
782 
783  if (prevExists)
784  {
785  Lucene.Net.Index.SegmentInfos.Message("fallback to prior segment file '" + prevSegmentFileName + "'");
786  try
787  {
788  System.Object v = DoBody(prevSegmentFileName);
789  if (exc != null)
790  {
791  Lucene.Net.Index.SegmentInfos.Message("success on fallback " + prevSegmentFileName);
792  }
793  return v;
794  }
795  catch (System.IO.IOException err2)
796  {
797  Lucene.Net.Index.SegmentInfos.Message("secondary Exception on '" + prevSegmentFileName + "': " + err2 + "'; will retry");
798  }
799  }
800  }
801  }
802  }
803  }
804 
805  /// <summary> Subclass must implement this. The assumption is an
806  /// IOException will be thrown if something goes wrong
807  /// during the processing that could have been caused by
808  /// a writer committing.
809  /// </summary>
810  public /*internal*/ abstract System.Object DoBody(System.String segmentFileName);
811  }
812 
813  /// <summary> Returns a new SegmentInfos containg the SegmentInfo
814  /// instances in the specified range first (inclusive) to
815  /// last (exclusive), so total number of segments returned
816  /// is last-first.
817  /// </summary>
818  public SegmentInfos Range(int first, int last)
819  {
820  SegmentInfos infos = new SegmentInfos();
821  infos.AddRange(this.GetRange(first, last - first));
822  return infos;
823  }
824 
825  // Carry over generation numbers from another SegmentInfos
826  internal void UpdateGeneration(SegmentInfos other)
827  {
828  lastGeneration = other.lastGeneration;
829  generation = other.generation;
830  version = other.version;
831  }
832 
833  internal void RollbackCommit(Directory dir)
834  {
835  if (pendingSegnOutput != null)
836  {
837  try
838  {
839  pendingSegnOutput.Close();
840  }
841  catch (System.Exception)
842  {
843  // Suppress so we keep throwing the original exception
844  // in our caller
845  }
846 
847  // Must carefully compute fileName from "generation"
848  // since lastGeneration isn't incremented:
849  try
850  {
851  System.String segmentFileName = IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", generation);
852  dir.DeleteFile(segmentFileName);
853  }
854  catch (System.Exception)
855  {
856  // Suppress so we keep throwing the original exception
857  // in our caller
858  }
859  pendingSegnOutput = null;
860  }
861  }
862 
863  /// <summary>Call this to start a commit. This writes the new
864  /// segments file, but writes an invalid checksum at the
865  /// end, so that it is not visible to readers. Once this
866  /// is called you must call <see cref="FinishCommit" /> to complete
867  /// the commit or <see cref="RollbackCommit" /> to abort it.
868  /// </summary>
869  internal void PrepareCommit(Directory dir)
870  {
871  if (pendingSegnOutput != null)
872  throw new System.SystemException("prepareCommit was already called");
873  Write(dir);
874  }
875 
876  /// <summary>Returns all file names referenced by SegmentInfo
877  /// instances matching the provided Directory (ie files
878  /// associated with any "external" segments are skipped).
879  /// The returned collection is recomputed on each
880  /// invocation.
881  /// </summary>
882  public System.Collections.Generic.ICollection<string> Files(Directory dir, bool includeSegmentsFile)
883  {
884  System.Collections.Generic.HashSet<string> files = new System.Collections.Generic.HashSet<string>();
885  if (includeSegmentsFile)
886  {
887  files.Add(GetCurrentSegmentFileName());
888  }
889  int size = Count;
890  for (int i = 0; i < size; i++)
891  {
892  SegmentInfo info = Info(i);
893  if (info.dir == dir)
894  {
895  files.UnionWith(Info(i).Files());
896  }
897  }
898  return files;
899  }
900 
901  internal void FinishCommit(Directory dir)
902  {
903  if (pendingSegnOutput == null)
904  throw new System.SystemException("prepareCommit was not called");
905  bool success = false;
906  try
907  {
908  pendingSegnOutput.FinishCommit();
909  pendingSegnOutput.Close();
910  pendingSegnOutput = null;
911  success = true;
912  }
913  finally
914  {
915  if (!success)
916  RollbackCommit(dir);
917  }
918 
919  // NOTE: if we crash here, we have left a segments_N
920  // file in the directory in a possibly corrupt state (if
921  // some bytes made it to stable storage and others
922  // didn't). But, the segments_N file includes checksum
923  // at the end, which should catch this case. So when a
924  // reader tries to read it, it will throw a
925  // CorruptIndexException, which should cause the retry
926  // logic in SegmentInfos to kick in and load the last
927  // good (previous) segments_N-1 file.
928 
929  System.String fileName = IndexFileNames.FileNameFromGeneration(IndexFileNames.SEGMENTS, "", generation);
930  success = false;
931  try
932  {
933  dir.Sync(fileName);
934  success = true;
935  }
936  finally
937  {
938  if (!success)
939  {
940  try
941  {
942  dir.DeleteFile(fileName);
943  }
944  catch (System.Exception)
945  {
946  // Suppress so we keep throwing the original exception
947  }
948  }
949  }
950 
951  lastGeneration = generation;
952 
953  try
954  {
955  IndexOutput genOutput = dir.CreateOutput(IndexFileNames.SEGMENTS_GEN);
956  try
957  {
958  genOutput.WriteInt(FORMAT_LOCKLESS);
959  genOutput.WriteLong(generation);
960  genOutput.WriteLong(generation);
961  }
962  finally
963  {
964  genOutput.Close();
965  }
966  }
967  catch (System.Exception)
968  {
969  // It's OK if we fail to write this file since it's
970  // used only as one of the retry fallbacks.
971  }
972  }
973 
974  /// <summary>Writes &amp; syncs to the Directory dir, taking care to
975  /// remove the segments file on exception
976  /// </summary>
977  public /*internal*/ void Commit(Directory dir)
978  {
979  PrepareCommit(dir);
980  FinishCommit(dir);
981  }
982 
983  public System.String SegString(Directory directory)
984  {
985  lock (this)
986  {
987  var buffer = new System.Text.StringBuilder();
988  int count = Count;
989  for (int i = 0; i < count; i++)
990  {
991  if (i > 0)
992  {
993  buffer.Append(' ');
994  }
995  SegmentInfo info = Info(i);
996  buffer.Append(info.SegString(directory));
997  if (info.dir != directory)
998  buffer.Append("**");
999  }
1000  return buffer.ToString();
1001  }
1002  }
1003 
1004  public IDictionary<string, string> UserData
1005  {
1006  get { return userData; }
1007  internal set {
1008  userData = value ?? new HashMap<string, string>();
1009  }
1010  }
1011 
1012  /// <summary>Replaces all segments in this instance, but keeps
1013  /// generation, version, counter so that future commits
1014  /// remain write once.
1015  /// </summary>
1016  internal void Replace(SegmentInfos other)
1017  {
1018  Clear();
1019  AddRange(other);
1020  lastGeneration = other.lastGeneration;
1021  }
1022 
1023  // Used only for testing
1024  public bool HasExternalSegments(Directory dir)
1025  {
1026  int numSegments = Count;
1027  for (int i = 0; i < numSegments; i++)
1028  if (Info(i).dir != dir)
1029  return true;
1030  return false;
1031  }
1032 
1033  #region Lucene.NET (Equals & GetHashCode )
1034  /// <summary>
1035  /// Simple brute force implementation.
1036  /// If size is equal, compare items one by one.
1037  /// </summary>
1038  /// <param name="obj">SegmentInfos object to check equality for</param>
1039  /// <returns>true if lists are equal, false otherwise</returns>
1040  public override bool Equals(object obj)
1041  {
1042  if (obj == null) return false;
1043 
1044  var objToCompare = obj as SegmentInfos;
1045  if (objToCompare == null) return false;
1046 
1047  if (this.Count != objToCompare.Count) return false;
1048 
1049  for (int idx = 0; idx < this.Count; idx++)
1050  {
1051  if (!this[idx].Equals(objToCompare[idx])) return false;
1052  }
1053 
1054  return true;
1055  }
1056 
1057  /// <summary>
1058  /// Calculate hash code of SegmentInfos
1059  /// </summary>
1060  /// <returns>hash code as in java version of ArrayList</returns>
1061  public override int GetHashCode()
1062  {
1063  int h = 1;
1064  for (int i = 0; i < this.Count; i++)
1065  {
1066  SegmentInfo si = (this[i] as SegmentInfo);
1067  h = 31 * h + (si == null ? 0 : si.GetHashCode());
1068  }
1069 
1070  return h;
1071  }
1072  #endregion
1073  }
1074 }