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 java.io.IOException; import java.io.FileNotFoundException; import java.io.PrintStream; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ArrayList; import java.util.Collections; import java.util.Collection; /* * This class keeps track of each SegmentInfos instance that * is still "live", either because it corresponds to a * segments_N file in the Directory (a "commit", i.e. a * committed SegmentInfos) or because it's an in-memory * SegmentInfos that a writer is actively updating but has * not yet committed. This class uses simple reference * counting to map the live SegmentInfos instances to * individual files in the Directory. * * When autoCommit=true, IndexWriter currently commits only * on completion of a merge (though this may change with * time: it is not a guarantee). When autoCommit=false, * IndexWriter only commits when it is closed. Regardless * of autoCommit, the user may call IndexWriter.commit() to * force a blocking commit. * * The same directory file may be referenced by more than * one IndexCommit, i.e. more than one SegmentInfos. * Therefore we count how many commits reference each file. * When all the commits referencing a certain file have been * deleted, the refcount for that file becomes zero, and the * file is deleted. * * A separate deletion policy interface * (IndexDeletionPolicy) is consulted on creation (onInit) * and once per commit (onCommit), to decide when a commit * should be removed. * * It is the business of the IndexDeletionPolicy to choose * when to delete commit points. The actual mechanics of * file deletion, retrying, etc, derived from the deletion * of commit points is the business of the IndexFileDeleter. * * The current default deletion policy is {@link * KeepOnlyLastCommitDeletionPolicy}, which removes all * prior commits when a new commit has completed. This * matches the behavior before 2.2. * * Note that you must hold the write.lock before * instantiating this class. It opens segments_N file(s) * directly with no retry logic. */ final class IndexFileDeleter { /* Files that we tried to delete but failed (likely * because they are open and we are running on Windows), * so we will retry them again later: */ private List deletable; /* Reference count for all files in the index. * Counts how many existing commits reference a file. * Maps String to RefCount (class below) instances: */ private Map refCounts = new HashMap(); /* Holds all commits (segments_N) currently in the index. * This will have just 1 commit if you are using the * default delete policy (KeepOnlyLastCommitDeletionPolicy). * Other policies may leave commit points live for longer * in which case this list would be longer than 1: */ private List commits = new ArrayList(); /* Holds files we had incref'd from the previous * non-commit checkpoint: */ private List lastFiles = new ArrayList(); /* Commits that the IndexDeletionPolicy have decided to delete: */ private List commitsToDelete = new ArrayList(); private PrintStream infoStream; private Directory directory; private IndexDeletionPolicy policy; private DocumentsWriter docWriter; final boolean startingCommitDeleted; /** Change to true to see details of reference counts when * infoStream != null */ public static boolean VERBOSE_REF_COUNTS = false; void setInfoStream(PrintStream infoStream) { this.infoStream = infoStream; if (infoStream != null) message("setInfoStream deletionPolicy=" + policy); } private void message(String message) { infoStream.println("IFD [" + Thread.currentThread().getName() + "]: " + message); } /** * Initialize the deleter: find all previous commits in * the Directory, incref the files they reference, call * the policy to let it delete commits. This will remove * any files not referenced by any of the commits. * @throws CorruptIndexException if the index is corrupt * @throws IOException if there is a low-level IO error */ public IndexFileDeleter(Directory directory, IndexDeletionPolicy policy, SegmentInfos segmentInfos, PrintStream infoStream, DocumentsWriter docWriter) throws CorruptIndexException, IOException { this.docWriter = docWriter; this.infoStream = infoStream; if (infoStream != null) message("init: current segments file is \"" + segmentInfos.getCurrentSegmentFileName() + "\"; deletionPolicy=" + policy); this.policy = policy; this.directory = directory; // First pass: walk the files and initialize our ref // counts: long currentGen = segmentInfos.getGeneration(); IndexFileNameFilter filter = IndexFileNameFilter.getFilter(); String[] files = directory.listAll(); CommitPoint currentCommitPoint = null; for(int i=0;i 0) { // First decref all files that had been referred to by // the now-deleted commits: for(int i=0;i writeTo) { commits.remove(size-1); size--; } } } /** * Writer calls this when it has hit an error and had to * roll back, to tell us that there may now be * unreferenced files in the filesystem. So we re-list * the filesystem and delete such files. If segmentName * is non-null, we will only delete files corresponding to * that segment. */ public void refresh(String segmentName) throws IOException { String[] files = directory.listAll(); IndexFileNameFilter filter = IndexFileNameFilter.getFilter(); String segmentPrefix1; String segmentPrefix2; if (segmentName != null) { segmentPrefix1 = segmentName + "."; segmentPrefix2 = segmentName + "_"; } else { segmentPrefix1 = null; segmentPrefix2 = null; } for(int i=0;i 0) { for(int i=0;i 0) { for(int i=0;i 0: "RefCount is 0 pre-increment for file \"" + fileName + "\""; } return ++count; } public int DecRef() { assert count > 0: "RefCount is 0 pre-decrement for file \"" + fileName + "\""; return --count; } } /** * Holds details for each commit point. This class is * also passed to the deletion policy. Note: this class * has a natural ordering that is inconsistent with * equals. */ final private static class CommitPoint extends IndexCommit implements Comparable { long gen; Collection files; String segmentsFileName; boolean deleted; Directory directory; Collection commitsToDelete; long version; long generation; final boolean isOptimized; final Map userData; public CommitPoint(Collection commitsToDelete, Directory directory, SegmentInfos segmentInfos) throws IOException { this.directory = directory; this.commitsToDelete = commitsToDelete; userData = segmentInfos.getUserData(); segmentsFileName = segmentInfos.getCurrentSegmentFileName(); version = segmentInfos.getVersion(); generation = segmentInfos.getGeneration(); files = Collections.unmodifiableCollection(segmentInfos.files(directory, true)); gen = segmentInfos.getGeneration(); isOptimized = segmentInfos.size() == 1 && !segmentInfos.info(0).hasDeletions(); assert !segmentInfos.hasExternalSegments(directory); } public boolean isOptimized() { return isOptimized; } public String getSegmentsFileName() { return segmentsFileName; } public Collection getFileNames() throws IOException { return files; } public Directory getDirectory() { return directory; } public long getVersion() { return version; } public long getGeneration() { return generation; } public Map getUserData() { return userData; } /** * Called only be the deletion policy, to remove this * commit point from the index. */ public void delete() { if (!deleted) { deleted = true; commitsToDelete.add(this); } } public boolean isDeleted() { return deleted; } public int compareTo(Object obj) { CommitPoint commit = (CommitPoint) obj; if (gen < commit.gen) { return -1; } else if (gen > commit.gen) { return 1; } else { return 0; } } } }