package org.apache.lucene.index; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import org.apache.lucene.store.Directory; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; import java.util.Vector; public final class SegmentInfos extends Vector { /** The file format version, a negative number. */ /* Works since counter, the old 1st entry, is always >= 0 */ public static final int FORMAT = -1; /** This format adds details used for lockless commits. It differs * slightly from the previous format in that file names * are never re-used (write once). Instead, each file is * written to the next generation. For example, * segments_1, segments_2, etc. This allows us to not use * a commit lock. See file * formats for details. */ public static final int FORMAT_LOCKLESS = -2; /** This is the current file format written. It adds a * "hasSingleNormFile" flag into each segment info. * See LUCENE-756 * for details. */ public static final int FORMAT_SINGLE_NORM_FILE = -3; public int counter = 0; // used to name new segments /** * counts how often the index has been changed by adding or deleting docs. * starting with the current time in milliseconds forces to create unique version numbers. */ private long version = System.currentTimeMillis(); private long generation = 0; // generation of the "segments_N" for the next commit private long lastGeneration = 0; // generation of the "segments_N" file we last successfully read // or wrote; this is normally the same as generation except if // there was an IOException that had interrupted a commit /** * If non-null, information about loading segments_N files * will be printed here. @see #setInfoStream. */ private static PrintStream infoStream; public final SegmentInfo info(int i) { return (SegmentInfo) elementAt(i); } /** * Get the generation (N) of the current segments_N file * from a list of files. * * @param files -- array of file names to check */ public static long getCurrentSegmentGeneration(String[] files) { if (files == null) { return -1; } long max = -1; int prefixLen = IndexFileNames.SEGMENTS.length()+1; for (int i = 0; i < files.length; i++) { String file = files[i]; if (file.startsWith(IndexFileNames.SEGMENTS) && !file.equals(IndexFileNames.SEGMENTS_GEN)) { if (file.equals(IndexFileNames.SEGMENTS)) { // Pre lock-less commits: if (max == -1) { max = 0; } } else { long v = Long.parseLong(file.substring(prefixLen), Character.MAX_RADIX); if (v > max) { max = v; } } } } return max; } /** * Get the generation (N) of the current segments_N file * in the directory. * * @param directory -- directory to search for the latest segments_N file */ public static long getCurrentSegmentGeneration(Directory directory) throws IOException { String[] files = directory.list(); if (files == null) throw new IOException("Cannot read directory " + directory); return getCurrentSegmentGeneration(files); } /** * Get the filename of the current segments_N file * from a list of files. * * @param files -- array of file names to check */ public static String getCurrentSegmentFileName(String[] files) throws IOException { return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", getCurrentSegmentGeneration(files)); } /** * Get the filename of the current segments_N file * in the directory. * * @param directory -- directory to search for the latest segments_N file */ public static String getCurrentSegmentFileName(Directory directory) throws IOException { return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", getCurrentSegmentGeneration(directory)); } /** * Get the segments_N filename in use by this segment infos. */ public String getCurrentSegmentFileName() { return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", lastGeneration); } /** * Get the next segments_N filename that will be written. */ public String getNextSegmentFileName() { long nextGeneration; if (generation == -1) { nextGeneration = 1; } else { nextGeneration = generation+1; } return IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", nextGeneration); } /** * Read a particular segmentFileName. Note that this may * throw an IOException if a commit is in process. * * @param directory -- directory containing the segments file * @param segmentFileName -- segment file to load */ public final void read(Directory directory, String segmentFileName) throws IOException { boolean success = false; IndexInput input = directory.openInput(segmentFileName); if (segmentFileName.equals(IndexFileNames.SEGMENTS)) { generation = 0; } else { generation = Long.parseLong(segmentFileName.substring(1+IndexFileNames.SEGMENTS.length()), Character.MAX_RADIX); } lastGeneration = generation; try { int format = input.readInt(); if(format < 0){ // file contains explicit format info // check that it is a format we can understand if (format < FORMAT_SINGLE_NORM_FILE) throw new IOException("Unknown format version: " + format); version = input.readLong(); // read version counter = input.readInt(); // read counter } else{ // file is in old format without explicit format info counter = format; } for (int i = input.readInt(); i > 0; i--) { // read segmentInfos addElement(new SegmentInfo(directory, format, input)); } if(format >= 0){ // in old format the version number may be at the end of the file if (input.getFilePointer() >= input.length()) version = System.currentTimeMillis(); // old file format without version number else version = input.readLong(); // read version } success = true; } finally { input.close(); if (!success) { // Clear any segment infos we had loaded so we // have a clean slate on retry: clear(); } } } /** * This version of read uses the retry logic (for lock-less * commits) to find the right segments file to load. */ public final void read(Directory directory) throws IOException { generation = lastGeneration = -1; new FindSegmentsFile(directory) { public Object doBody(String segmentFileName) throws IOException { read(directory, segmentFileName); return null; } }.run(); } public final void write(Directory directory) throws IOException { String segmentFileName = getNextSegmentFileName(); // Always advance the generation on write: if (generation == -1) { generation = 1; } else { generation++; } IndexOutput output = directory.createOutput(segmentFileName); try { output.writeInt(FORMAT_SINGLE_NORM_FILE); // write FORMAT output.writeLong(++version); // every write changes // the index output.writeInt(counter); // write counter output.writeInt(size()); // write infos for (int i = 0; i < size(); i++) { info(i).write(output); } } finally { output.close(); } try { output = directory.createOutput(IndexFileNames.SEGMENTS_GEN); try { output.writeInt(FORMAT_LOCKLESS); output.writeLong(generation); output.writeLong(generation); } finally { output.close(); } } catch (IOException e) { // It's OK if we fail to write this file since it's // used only as one of the retry fallbacks. } lastGeneration = generation; } /** * Returns a copy of this instance, also copying each * SegmentInfo. */ public Object clone() { SegmentInfos sis = (SegmentInfos) super.clone(); for(int i=0;i gen) { message("fallback to '" + IndexFileNames.SEGMENTS_GEN + "' check: now try generation " + gen0 + " > " + gen); gen = gen0; } break; } } } catch (IOException err2) { // will retry } finally { genInput.close(); } } try { Thread.sleep(defaultGenFileRetryPauseMsec); } catch (InterruptedException e) { // will retry } } } // Method 3 (fallback if Methods 2 & 3 are not // reliable): since both directory cache and file // contents cache seem to be stale, just advance the // generation. if (2 == method || (1 == method && lastGen == gen && retry)) { method = 2; if (genLookaheadCount < defaultGenLookaheadCount) { gen++; genLookaheadCount++; message("look ahead increment gen to " + gen); } } if (lastGen == gen) { // This means we're about to try the same // segments_N last tried. This is allowed, // exactly once, because writer could have been in // the process of writing segments_N last time. if (retry) { // OK, we've tried the same segments_N file // twice in a row, so this must be a real // error. We throw the original exception we // got. throw exc; } else { retry = true; } } else { // Segment file has advanced since our last loop, so // reset retry: retry = false; } lastGen = gen; segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", gen); try { Object v = doBody(segmentFileName); if (exc != null) { message("success on " + segmentFileName); } return v; } catch (IOException err) { // Save the original root cause: if (exc == null) { exc = err; } message("primary Exception on '" + segmentFileName + "': " + err + "'; will retry: retry=" + retry + "; gen = " + gen); if (!retry && gen > 1) { // This is our first time trying this segments // file (because retry is false), and, there is // possibly a segments_(N-1) (because gen > 1). // So, check if the segments_(N-1) exists and // try it if so: String prevSegmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", gen-1); if (directory.fileExists(prevSegmentFileName)) { message("fallback to prior segment file '" + prevSegmentFileName + "'"); try { Object v = doBody(prevSegmentFileName); if (exc != null) { message("success on fallback " + prevSegmentFileName); } return v; } catch (IOException err2) { message("secondary Exception on '" + prevSegmentFileName + "': " + err2 + "'; will retry"); } } } } } } /** * Subclass must implement this. The assumption is an * IOException will be thrown if something goes wrong * during the processing that could have been caused by * a writer committing. */ protected abstract Object doBody(String segmentFileName) throws IOException;} }