using System;
using System.Linq;
using Db4objects.Db4o;
using Db4objects.Db4o.Ext;
namespace Db4OSeries
{
///
/// Extension for the to have more
/// consortable locks than with and
///
///
///
///
public static class LockExtensions
{
internal const int TimeOut = 100;
internal const string LockPrefix = "LockForObject-No-";
///
/// Lock all the objects given to this method. Avoids
/// dead-locks by ordering the objects by id. The each object
/// has to be already stored in the object-container.
///
///
///
///
public static bool LockObjects(this IObjectContainer db, params object[] objectsToLock)
{
var ids = VerifyAndGetIDs(db, objectsToLock, order => order);
return LockRecursive(db.Ext(), ids);
}
///
/// Unlock the given objects. All rules of apply here aswell
///
///
///
public static void UnlockObjects(this IObjectContainer db, params object[] objectsToLock)
{
var ids = VerifyAndGetIDs(db, objectsToLock, inverseOrder => -1*inverseOrder);
UnLockRecursive(db.Ext(), ids);
}
///
/// Locks the given objects and runs afterward the given action or function.
///
/// Example:
///
/// db.WithLock(person1,person2).Execute(()=>{
/// person1.RoomMate = person2;
///
);
///
///
/// This function returns a context in which you can run the
/// code which need to be protected. The context can be exactly once. After you've passed
/// a closure to the context the context disposed. You shouldn't pass another closure to it.
/// So just call each time like in the example above.
///
///
///
///
public static WithLockContext WithLock(this IObjectContainer db, params object[] objectsToLock)
{
return new WithLockContext(objectsToLock, db);
}
private static void UnLockRecursive(IExtObjectContainer db, long[] ids)
{
RecursiveOperationToRun(ids, 0, name =>
{
db.ReleaseSemaphore(name);
return true;
},
notExpected => { });
}
private static bool LockRecursive(IExtObjectContainer db, long[] ids)
{
return RecursiveOperationToRun(ids, 0, name => db.SetSemaphore(name, TimeOut),
db.ReleaseSemaphore);
}
private static bool RecursiveOperationToRun(long[] ids, int pos,
Func functionToRun,
Action cleanUpFunction)
{
if(0==ids.Length)
{
return true;
}
var id = ids[pos];
var nextPos = pos + 1;
var hasLock = functionToRun(LockName(id));
if (hasLock && HasMoreElements(ids, nextPos))
{
hasLock = RecursiveOperationToRun(ids, nextPos, functionToRun, cleanUpFunction);
}
if (!hasLock)
{
cleanUpFunction(LockName(id));
}
return hasLock;
}
private static bool HasMoreElements(long[] ids, int nextPos)
{
return nextPos < ids.Length;
}
private static string LockName(long id)
{
return LockPrefix + id;
}
private static long[] VerifyAndGetIDs(IObjectContainer db, object[] objectsToLock, Func order)
{
VerifyArguments(db, objectsToLock);
var extContainer = db.Ext();
var ids = objectsToLock.Select(o => extContainer.GetID(o)).OrderBy(order).ToArray();
VerifyAllObjectsPersisted(ids);
return ids;
}
private static void VerifyAllObjectsPersisted(long[] longs)
{
if (longs.Any(id => id <= 0))
{
throw new ArgumentException("All object require to be persisted in order to lock them");
}
}
private static void VerifyArguments(IObjectContainer db, object[] objects)
{
if (db == null)
{
throw new ArgumentNullException("db");
}
if (null == objects)
{
throw new ArgumentNullException("objects");
}
if (objects.Any(o => null == o))
{
throw new ArgumentException("the objects cannot contain a null-reference");
}
}
}
public class WithLockContext
{
private readonly object[] toLock;
private readonly IObjectContainer db;
private bool disposed = false;
public WithLockContext(object[] toLock, IObjectContainer db)
{
this.toLock = toLock;
this.db = db;
}
public TResult Execute(Func action)
{
if(disposed)
{
throw new ObjectDisposedException("This context was already used. Create a new one with the LockExtensions.WithLock");
}
try
{
return LockAndRun(action);
}
finally
{
disposed = true;
db.UnlockObjects(toLock);
}
}
public void Execute(Action action)
{
Execute(() =>
{
action();
return true;
});
}
private TResult LockAndRun(Func action)
{
if (db.LockObjects(toLock))
{
return action();
}
else
{
throw new InvalidOperationException("Couldn't acquire the required locks to perform the operation");
}
}
}
}