blob: dc8ea9be08aacbd9fe3fa5bd26e9945227ccbbd5 [file] [log] [blame]
using Lucene.Net.Diagnostics;
using System;
using System.Collections.Generic;
namespace Lucene.Net.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.
*/
using Directory = Lucene.Net.Store.Directory;
/// <summary>
/// An <see cref="IndexDeletionPolicy"/> that wraps any other
/// <see cref="IndexDeletionPolicy"/> and adds the ability to hold and later release
/// snapshots of an index. While a snapshot is held, the <see cref="IndexWriter"/> will
/// not remove any files associated with it even if the index is otherwise being
/// actively, arbitrarily changed. Because we wrap another arbitrary
/// <see cref="IndexDeletionPolicy"/>, this gives you the freedom to continue using
/// whatever <see cref="IndexDeletionPolicy"/> you would normally want to use with your
/// index.
///
/// <para/>
/// This class maintains all snapshots in-memory, and so the information is not
/// persisted and not protected against system failures. If persistence is
/// important, you can use <see cref="PersistentSnapshotDeletionPolicy"/>.
/// <para/>
/// @lucene.experimental
/// </summary>
public class SnapshotDeletionPolicy : IndexDeletionPolicy
{
/// <summary>
/// Records how many snapshots are held against each
/// commit generation
/// </summary>
protected IDictionary<long, int> m_refCounts = new Dictionary<long, int>();
/// <summary>
/// Used to map gen to <see cref="IndexCommit"/>. </summary>
protected IDictionary<long?, IndexCommit> m_indexCommits = new Dictionary<long?, IndexCommit>();
/// <summary>
/// Wrapped <see cref="IndexDeletionPolicy"/> </summary>
private IndexDeletionPolicy primary;
/// <summary>
/// Most recently committed <see cref="IndexCommit"/>. </summary>
protected IndexCommit m_lastCommit;
/// <summary>
/// Used to detect misuse </summary>
private bool initCalled;
/// <summary>
/// Sole constructor, taking the incoming
/// <see cref="IndexDeletionPolicy"/> to wrap.
/// </summary>
public SnapshotDeletionPolicy(IndexDeletionPolicy primary)
{
this.primary = primary;
}
public override void OnCommit<T>(IList<T> commits)
{
lock (this)
{
primary.OnCommit(WrapCommits(commits));
m_lastCommit = commits[commits.Count - 1];
}
}
public override void OnInit<T>(IList<T> commits)
{
lock (this)
{
initCalled = true;
primary.OnInit(WrapCommits(commits));
foreach (IndexCommit commit in commits)
{
if (m_refCounts.ContainsKey(commit.Generation))
{
m_indexCommits[commit.Generation] = commit;
}
}
if (commits.Count > 0)
{
m_lastCommit = commits[commits.Count - 1];
}
}
}
/// <summary>
/// Release a snapshotted commit.
/// </summary>
/// <param name="commit">
/// the commit previously returned by <see cref="Snapshot()"/> </param>
public virtual void Release(IndexCommit commit)
{
lock (this)
{
long gen = commit.Generation;
ReleaseGen(gen);
}
}
/// <summary>
/// Release a snapshot by generation. </summary>
protected internal virtual void ReleaseGen(long gen)
{
if (!initCalled)
{
throw new InvalidOperationException("this instance is not being used by IndexWriter; be sure to use the instance returned from writer.getConfig().getIndexDeletionPolicy()");
}
int? refCount = m_refCounts[gen];
if (refCount == null)
{
throw new ArgumentException("commit gen=" + gen + " is not currently snapshotted");
}
int refCountInt = (int)refCount;
if (Debugging.AssertsEnabled) Debugging.Assert(refCountInt > 0);
refCountInt--;
if (refCountInt == 0)
{
m_refCounts.Remove(gen);
m_indexCommits.Remove(gen);
}
else
{
m_refCounts[gen] = refCountInt;
}
}
/// <summary>
/// Increments the refCount for this <see cref="IndexCommit"/>. </summary>
protected internal virtual void IncRef(IndexCommit ic)
{
lock (this)
{
long gen = ic.Generation;
int refCount;
int refCountInt;
if (!m_refCounts.TryGetValue(gen, out refCount))
{
m_indexCommits[gen] = m_lastCommit;
refCountInt = 0;
}
else
{
refCountInt = (int)refCount;
}
m_refCounts[gen] = refCountInt + 1;
}
}
/// <summary>
/// Snapshots the last commit and returns it. Once a commit is 'snapshotted,' it is protected
/// from deletion (as long as this <see cref="IndexDeletionPolicy"/> is used). The
/// snapshot can be removed by calling <see cref="Release(IndexCommit)"/> followed
/// by a call to <see cref="IndexWriter.DeleteUnusedFiles()"/>.
///
/// <para/>
/// <b>NOTE:</b> while the snapshot is held, the files it references will not
/// be deleted, which will consume additional disk space in your index. If you
/// take a snapshot at a particularly bad time (say just before you call
/// <see cref="IndexWriter.ForceMerge(int)"/>) then in the worst case this could consume an extra 1X of your
/// total index size, until you release the snapshot.
/// </summary>
/// <exception cref="InvalidOperationException">
/// if this index does not have any commits yet </exception>
/// <returns> the <see cref="IndexCommit"/> that was snapshotted. </returns>
public virtual IndexCommit Snapshot()
{
lock (this)
{
if (!initCalled)
{
throw new InvalidOperationException("this instance is not being used by IndexWriter; be sure to use the instance returned from writer.getConfig().getIndexDeletionPolicy()");
}
if (m_lastCommit == null)
{
// No commit yet, eg this is a new IndexWriter:
throw new InvalidOperationException("No index commit to snapshot");
}
IncRef(m_lastCommit);
return m_lastCommit;
}
}
/// <summary>
/// Returns all <see cref="IndexCommit"/>s held by at least one snapshot. </summary>
public virtual IList<IndexCommit> GetSnapshots()
{
lock (this)
{
return new List<IndexCommit>(m_indexCommits.Values);
}
}
/// <summary>
/// Returns the total number of snapshots currently held. </summary>
public virtual int SnapshotCount
{
get
{
lock (this)
{
int total = 0;
foreach (var refCount in m_refCounts.Values)
{
total += refCount;
}
return total;
}
}
}
/// <summary>
/// Retrieve an <see cref="IndexCommit"/> from its generation;
/// returns <c>null</c> if this <see cref="IndexCommit"/> is not currently
/// snapshotted
/// </summary>
public virtual IndexCommit GetIndexCommit(long gen)
{
lock (this)
{
return m_indexCommits[gen];
}
}
public override object Clone()
{
lock (this)
{
SnapshotDeletionPolicy other = (SnapshotDeletionPolicy)base.Clone();
other.primary = (IndexDeletionPolicy)this.primary.Clone();
other.m_lastCommit = null;
other.m_refCounts = new Dictionary<long, int>(m_refCounts);
other.m_indexCommits = new Dictionary<long?, IndexCommit>(m_indexCommits);
return other;
}
}
/// <summary>
/// Wraps each <see cref="IndexCommit"/> as a
/// <see cref="SnapshotCommitPoint"/>.
/// </summary>
private IList<IndexCommit> WrapCommits<T>(IList<T> commits)
where T : IndexCommit
{
IList<IndexCommit> wrappedCommits = new List<IndexCommit>(commits.Count);
foreach (IndexCommit ic in commits)
{
wrappedCommits.Add(new SnapshotCommitPoint(this, ic));
}
return wrappedCommits;
}
/// <summary>
/// Wraps a provided <see cref="IndexCommit"/> and prevents it
/// from being deleted.
/// </summary>
private class SnapshotCommitPoint : IndexCommit
{
private readonly SnapshotDeletionPolicy outerInstance;
/// <summary>
/// The <see cref="IndexCommit"/> we are preventing from deletion. </summary>
protected IndexCommit m_cp;
/// <summary>
/// Creates a <see cref="SnapshotCommitPoint"/> wrapping the provided
/// <see cref="IndexCommit"/>.
/// </summary>
protected internal SnapshotCommitPoint(SnapshotDeletionPolicy outerInstance, IndexCommit cp)
{
this.outerInstance = outerInstance;
this.m_cp = cp;
}
public override string ToString()
{
return "SnapshotDeletionPolicy.SnapshotCommitPoint(" + m_cp + ")";
}
public override void Delete()
{
lock (outerInstance)
{
// Suppress the delete request if this commit point is
// currently snapshotted.
if (!outerInstance.m_refCounts.ContainsKey(m_cp.Generation))
{
m_cp.Delete();
}
}
}
public override Directory Directory => m_cp.Directory;
public override ICollection<string> FileNames => m_cp.FileNames;
public override long Generation => m_cp.Generation;
public override string SegmentsFileName => m_cp.SegmentsFileName;
public override IDictionary<string, string> UserData => m_cp.UserData;
public override bool IsDeleted => m_cp.IsDeleted;
public override int SegmentCount => m_cp.SegmentCount;
}
}
}