package org.ovirt.engine.core.utils.lock; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.compat.LogCompat; import org.ovirt.engine.core.compat.LogFactoryCompat; /** * The following class an implementation of internal locking mechanism */ public class InMemoryLockManager implements LockManager { /** A map which is contains all internal representation of locks **/ private final Map<String, InternalLockView> locks = new HashMap<String, InternalLockView>(); /** A lock which is used to synchronized acquireLock(), acquireLockWait() and releaseLock() operations **/ private final Lock globalLock = new ReentrantLock(); /** A condition which is used in order to notify for waiting threads that some lock was released**/ private final Condition releasedLock = globalLock.newCondition(); private static LogCompat log = LogFactoryCompat.getLog(InMemoryLockManager.class); @Override public boolean acquireLock(EngineLock lock) { log.debugFormat("Before acquiring lock {0}", lock); globalLock.lock(); try { return acquireLockInternal(lock); } finally { globalLock.unlock(); } } @Override public void acquireLockWait(EngineLock lock) { log.debugFormat("Before acquiring and wait lock {0}", lock); globalLock.lock(); try { boolean firstRun = true; while (!acquireLockInternal(lock)) { // In case of first try, just wait if (firstRun) { firstRun = false; } else { // This is a second try, we did not successes, but possible that release signal for other waiting thread // so try to signal to other thread releasedLock.signal(); } releasedLock.await(); } } catch (InterruptedException e) { releasedLock.signal(); } finally { globalLock.unlock(); } } @Override public void releaseLock(EngineLock lock) { log.debugFormat("Before releasing a lock {0}", lock); globalLock.lock(); try { if (lock.getSharedLocks() != null) { for (Entry<String, Guid> entry : lock.getSharedLocks().entrySet()) { releaseSharedLock(buildHashMapKey(entry)); } } if (lock.getExclusiveLocks() != null) { for (Entry<String, Guid> entry : lock.getExclusiveLocks().entrySet()) { releaseExclusiveLock(buildHashMapKey(entry)); } } releasedLock.signal(); } finally { globalLock.unlock(); } } @Override public void clear() { log.warn("All in memory locks are going to be cleaned"); globalLock.lock(); try { locks.clear(); releasedLock.signalAll(); } finally { globalLock.unlock(); } } /** * Internal method should build a key for lock * @param entry * @return */ private String buildHashMapKey(Entry<String, Guid> entry) { return new StringBuilder(entry.getKey()).append(entry.getValue()).toString(); } /** * The following method contains a logic for acquiring a lock The method is contains two steps: * 1. The lock can be acquired * 2. If the first step successes acquire a lock * @param lock * @return */ private boolean acquireLockInternal(EngineLock lock) { boolean checkOnly = true; for (int i = 0; i < 2; i++) { if (lock.getSharedLocks() != null) { for (Entry<String, Guid> entry : lock.getSharedLocks().entrySet()) { if (!insertSharedLock(buildHashMapKey(entry), checkOnly)) { log.debugFormat("Failed to acquire a lock because of shared lock - key :{0} and value {1}", entry.getKey(), entry.getValue()); return false; } } } if (lock.getExclusiveLocks() != null) { for (Entry<String, Guid> entry : lock.getExclusiveLocks().entrySet()) { if (!insertExclusiveLock(buildHashMapKey(entry), checkOnly)) { log.debugFormat("Failed to acquire a lock because of exclusive lock - key :{0} and value {1}", entry.getKey(), entry.getValue()); return false; } } } checkOnly = false; } log.debug("Successed to acquire a lock"); return true; } /** * The following method should insert an "shared" internal lock * @param key * @param isCheckOnly * - is insert or check if lock can be inserted * @return */ private boolean insertSharedLock(String key, boolean isCheckOnly) { boolean result = true; InternalLockView lock = locks.get(key); if (lock != null) { if (!isCheckOnly) { lock.increaseCount(); } else if (lock.getExclusive()) { result = false; } } else if (!isCheckOnly) { locks.put(key, new InternalLockView(1, false)); } return result; } /** * The following method will add exclusive lock, the exclusive key can be * added only if there is not exist any shared or exclusive lock for given key */ private boolean insertExclusiveLock(String key, boolean isCheckOnly) { if (locks.containsKey(key)) { return false; } if (!isCheckOnly) { locks.put(key, new InternalLockView(0, true)); } return true; } private void releaseExclusiveLock(String key) { InternalLockView lock = locks.get(key); if (lock != null && lock.getExclusive()) { locks.remove(key); log.debugFormat("The exclusive lock for key {0} is released and lock is removed from map", key); } else if (lock == null) { log.warnFormat("Trying to release exclusive lock which is not exist with for {0}", key); } else { log.warnFormat("Trying to release exclusive lock which is not exclusive lock for key {0}", key); } } private void releaseSharedLock(String key) { InternalLockView lock = locks.get(key); if (key != null) { if (lock.getCount() > 0) { lock.decreaseCount(); log.debugFormat("The shared lock for key {0} is released.", key); if (lock.getCount() == 0) { locks.remove(key); log.debugFormat("The shared lock for key {0} is removed from map", key); } } else { log.warnFormat("Trying decrease shared lock index which is 0 is for key {0}", key); } } else { log.warnFormat("Trying release shared lock which is not exist for key {0}", key); } } /** * The following class is represents different locks which are kept inside InMemoryLockManager */ private class InternalLockView { /** Number for shared locks **/ private int count; /** Indicate if the lock is exclusive and not allowing any other exclusive/shared locks with the same key **/ private boolean exclusive; public InternalLockView(int count, boolean exclusive) { this.count = count; this.exclusive = exclusive; } public boolean getExclusive() { return exclusive; } public void setExclusive(boolean exclusive) { this.exclusive = exclusive; } public int getCount() { return count; } public void increaseCount() { count++; } public void decreaseCount() { count--; } } }