blob: f0cd1ace6c9d991b3798877a15d7f598892234db [file] [log] [blame]
using System;
using System.IO;
using System.Threading;
namespace Lucene.Net.Store
{
/*
* 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.
*/
/// <summary>
/// An interprocess mutex lock.
/// <para/>Typical use might look like:
///
/// <code>
/// var result = Lock.With.NewAnonymous&lt;string&gt;(
/// @lock: directory.MakeLock("my.lock"),
/// lockWaitTimeout: Lock.LOCK_OBTAIN_WAIT_FOREVER,
/// doBody: () =>
/// {
/// //... code to execute while locked ...
/// return "the result";
/// }).Run();
/// </code>
/// </summary>
/// <seealso cref="Directory.MakeLock(string)"/>
public abstract class Lock : IDisposable
{
/// <summary>
/// How long <see cref="Obtain(long)"/> waits, in milliseconds,
/// in between attempts to acquire the lock.
/// </summary>
public static long LOCK_POLL_INTERVAL = 1000;
/// <summary>
/// Pass this value to <see cref="Obtain(long)"/> to try
/// forever to obtain the lock.
/// </summary>
public const long LOCK_OBTAIN_WAIT_FOREVER = -1;
/// <summary>
/// Creates a new instance with the ability to specify the <see cref="With{T}.DoBody()"/> method
/// through the <paramref name="doBody"/> argument
/// <para/>
/// Simple example:
/// <code>
/// var result = Lock.With.NewAnonymous&lt;string&gt;(
/// @lock: directory.MakeLock("my.lock"),
/// lockWaitTimeout: Lock.LOCK_OBTAIN_WAIT_FOREVER,
/// doBody: () =>
/// {
/// //... code to execute while locked ...
/// return "the result";
/// }).Run();
/// </code>
/// <para/>
/// The result of the operation is the value that is returned from <paramref name="doBody"/>
/// (i.e. () => { return "the result"; }). The type of <typeparam name="T"/> determines the
/// return type of the operation.
/// </summary>
/// <param name="lock"> the <see cref="Lock"/> instance to use </param>
/// <param name="lockWaitTimeout"> length of time to wait in
/// milliseconds or
/// <see cref="LOCK_OBTAIN_WAIT_FOREVER"/> to retry forever </param>
/// <param name="doBody"> a delegate method that </param>
/// <returns>The value that is returned from the <paramref name="doBody"/> delegate method (i.e. () => { return theObject; })</returns>
public static With<T> NewAnonymous<T>(Lock @lock, int lockWaitTimeout, Func<T> doBody)
{
return new AnonymousWith<T>(@lock, lockWaitTimeout, doBody);
}
/// <summary>
/// Attempts to obtain exclusive access and immediately return
/// upon success or failure. Use <see cref="Dispose()"/> to
/// release the lock. </summary>
/// <returns> true iff exclusive access is obtained </returns>
public abstract bool Obtain();
/// <summary>
/// If a lock obtain called, this failureReason may be set
/// with the "root cause" <see cref="Exception"/> as to why the lock was
/// not obtained.
/// </summary>
protected internal Exception FailureReason { get; set; }
/// <summary>
/// Attempts to obtain an exclusive lock within amount of
/// time given. Polls once per <see cref="LOCK_POLL_INTERVAL"/>
/// (currently 1000) milliseconds until <paramref name="lockWaitTimeout"/> is
/// passed.
/// </summary>
/// <param name="lockWaitTimeout"> length of time to wait in
/// milliseconds or
/// <see cref="LOCK_OBTAIN_WAIT_FOREVER"/> to retry forever </param>
/// <returns> <c>true</c> if lock was obtained </returns>
/// <exception cref="LockObtainFailedException"> if lock wait times out </exception>
/// <exception cref="ArgumentException"> if <paramref name="lockWaitTimeout"/> is
/// out of bounds </exception>
/// <exception cref="IOException"> if <see cref="Obtain()"/> throws <see cref="IOException"/> </exception>
public bool Obtain(long lockWaitTimeout)
{
FailureReason = null;
bool locked = Obtain();
if (lockWaitTimeout < 0 && lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER)
{
throw new ArgumentException("lockWaitTimeout should be LOCK_OBTAIN_WAIT_FOREVER or a non-negative number (got " + lockWaitTimeout + ")");
}
long maxSleepCount = lockWaitTimeout / LOCK_POLL_INTERVAL;
long sleepCount = 0;
while (!locked)
{
if (lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER && sleepCount++ >= maxSleepCount)
{
string reason = "Lock obtain timed out: " + this.ToString();
if (FailureReason != null)
{
reason += ": " + FailureReason;
}
LockObtainFailedException e = new LockObtainFailedException(reason);
e = FailureReason != null
? new LockObtainFailedException(reason, FailureReason)
: new LockObtainFailedException(reason);
throw e;
}
//#if FEATURE_THREAD_INTERRUPT
// try
// {
//#endif
Thread.Sleep(TimeSpan.FromMilliseconds(LOCK_POLL_INTERVAL));
//#if FEATURE_THREAD_INTERRUPT // LUCENENET NOTE: Senseless to catch and rethrow the same exception type
// }
// catch (ThreadInterruptedException ie)
// {
// throw new ThreadInterruptedException(ie.ToString(), ie);
// }
//#endif
locked = Obtain();
}
return locked;
}
/// <summary>
/// Releases exclusive access. </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases exclusive access. </summary>
protected abstract void Dispose(bool disposing);
/// <summary>
/// Returns <c>true</c> if the resource is currently locked. Note that one must
/// still call <see cref="Obtain()"/> before using the resource.
/// </summary>
public abstract bool IsLocked();
/// <summary>
/// Utility class for executing code with exclusive access. </summary>
public abstract class With<T> // LUCENENET specific - made generic so we don't need to deal with casting
{
private Lock @lock;
private long lockWaitTimeout;
/// <summary>
/// Constructs an executor that will grab the named <paramref name="lock"/>. </summary>
/// <param name="lock"> the <see cref="Lock"/> instance to use </param>
/// <param name="lockWaitTimeout"> length of time to wait in
/// milliseconds or
/// <see cref="LOCK_OBTAIN_WAIT_FOREVER"/> to retry forever </param>
public With(Lock @lock, long lockWaitTimeout)
{
this.@lock = @lock;
this.lockWaitTimeout = lockWaitTimeout;
}
/// <summary>
/// Code to execute with exclusive access. </summary>
protected abstract T DoBody();
/// <summary>
/// Calls <see cref="DoBody"/> while <i>lock</i> is obtained. Blocks if lock
/// cannot be obtained immediately. Retries to obtain lock once per second
/// until it is obtained, or until it has tried ten times. Lock is released when
/// <see cref="DoBody"/> exits. </summary>
/// <exception cref="LockObtainFailedException"> if lock could not
/// be obtained </exception>
/// <exception cref="IOException"> if <see cref="Lock.Obtain()"/> throws <see cref="IOException"/> </exception>
public virtual T Run()
{
bool locked = false;
try
{
locked = @lock.Obtain(lockWaitTimeout);
return DoBody();
}
finally
{
if (locked)
{
@lock.Dispose();
}
}
}
}
/// <summary>
/// LUCENENET specific class to simulate the anonymous creation of a With class in Java
/// by using deletate methods.
/// </summary>
private class AnonymousWith<T> : With<T>
{
private readonly Func<T> doBody;
public AnonymousWith(Lock @lock, int lockWaitTimeout, Func<T> doBody)
: base(@lock, lockWaitTimeout)
{
if (doBody == null)
throw new ArgumentNullException("doBody");
this.doBody = doBody;
}
protected override T DoBody()
{
return doBody();
}
}
}
}