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"); } } } }