MOB-434: Cluster-wide Locking Service

- DAO and unit tests (MOB-436)
 - MySQL and Derby scripts (MOB-438)
 - TODO: Oracle, PostgreSQL and SQLServer for Enterprise
 - TOD0: Wrap lock wait and lock retry behaviour


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13947 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2009-04-15 03:37:22 +00:00
parent 82dd391580
commit b2836c3aca
14 changed files with 865 additions and 54 deletions

View File

@@ -169,7 +169,9 @@ public class HibernateQNameDAOImpl extends HibernateDaoSupport implements QNameD
NamespaceEntity namespace = new NamespaceEntityImpl();
namespace.setUri(namespaceUri);
// Persist
Long id = (Long) getSession().save(namespace);
Session session = getSession();
Long id = (Long) session.save(namespace);
DirtySessionMethodInterceptor.flushSession(session, true);
// Cache it
namespaceEntityCache.put(id, namespaceUri);
namespaceEntityCache.put(namespaceUri, id);

View File

@@ -43,6 +43,8 @@ import org.springframework.dao.ConcurrencyFailureException;
*/
public abstract class AbstractLockDAOImpl implements LockDAO
{
private static final String LOCK_TOKEN_RELEASED = "not-locked";
private QNameDAO qnameDAO;
/**
@@ -61,7 +63,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO
this.qnameDAO = qnameDAO;
}
public boolean getLock(QName lockQName, String lockApplicant, long timeToLive)
public boolean getLock(QName lockQName, String lockToken, long timeToLive)
{
String qnameNamespaceUri = lockQName.getNamespaceURI();
String qnameLocalName = lockQName.getLocalName();
@@ -71,8 +73,8 @@ public abstract class AbstractLockDAOImpl implements LockDAO
lockQName = QName.createQName(qnameNamespaceUri, qnameLocalName.toLowerCase());
qnameLocalName = lockQName.getLocalName();
}
// Force the lock applicant to lowercase
lockApplicant = lockApplicant.toLowerCase();
// Force the lock token to lowercase
lockToken = lockToken.toLowerCase();
// Resolve the namespace
Long qnameNamespaceId = qnameDAO.getOrCreateNamespace(qnameNamespaceUri).getFirst();
@@ -94,7 +96,6 @@ public abstract class AbstractLockDAOImpl implements LockDAO
{
String localname = lockQNameIter.getLocalName();
// Get the basic lock resource, forcing a create
// TODO: Pull back all lock resources in a single query
LockResourceEntity lockResource = getLockResource(qnameNamespaceId, localname);
if (lockResource == null)
{
@@ -105,12 +106,12 @@ public abstract class AbstractLockDAOImpl implements LockDAO
}
// Now, get all locks for the resources we will need
List<LockEntity> existingLocks = getLocks(requiredLockResourceIds);
List<LockEntity> existingLocks = getLocksBySharedResourceIds(requiredLockResourceIds);
Map<LockEntity, LockEntity> existingLocksMap = new HashMap<LockEntity, LockEntity>();
// Check them and make sure they don't prevent locks
for (LockEntity existingLock : existingLocks)
{
boolean canTakeLock = canTakeLock(existingLock, lockApplicant, requiredExclusiveLockResourceId);
boolean canTakeLock = canTakeLock(existingLock, lockToken, requiredExclusiveLockResourceId);
if (!canTakeLock)
{
return false;
@@ -129,7 +130,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO
{
requiredLock = existingLocksMap.get(requiredLock);
// Do an update
throw new UnsupportedOperationException();
updateLock(requiredLock, lockToken, timeToLive);
}
else
{
@@ -137,18 +138,78 @@ public abstract class AbstractLockDAOImpl implements LockDAO
requiredLock = createLock(
requiredLockResourceId,
requiredExclusiveLockResourceId,
lockApplicant,
lockToken,
timeToLive);
}
}
return true;
}
private boolean canTakeLock(LockEntity existingLock, String lockApplicant, Long desiredExclusiveLock)
public boolean refreshLock(QName lockQName, String lockToken, long timeToLive)
{
if (EqualsHelper.nullSafeEquals(existingLock.getLockHolder(), lockApplicant))
return updateLocks(lockQName, lockToken, lockToken, timeToLive);
}
public boolean releaseLock(QName lockQName, String lockToken)
{
return updateLocks(lockQName, lockToken, LOCK_TOKEN_RELEASED, 0L);
}
/**
* Put new values against the given exclusive lock. This works against the related locks as
* well.
*/
private boolean updateLocks(QName lockQName, String lockToken, String newLockToken, long timeToLive)
{
String qnameNamespaceUri = lockQName.getNamespaceURI();
String qnameLocalName = lockQName.getLocalName();
// Force lower case for case insensitivity
if (!qnameLocalName.toLowerCase().equals(qnameLocalName))
{
// The lock applicant to be is also the current lock holder.
lockQName = QName.createQName(qnameNamespaceUri, qnameLocalName.toLowerCase());
qnameLocalName = lockQName.getLocalName();
}
// Force the lock token to lowercase
lockToken = lockToken.toLowerCase();
// Resolve the namespace
Long qnameNamespaceId = qnameDAO.getOrCreateNamespace(qnameNamespaceUri).getFirst();
// Get the lock resource for the exclusive lock.
// All the locks that are created will need the exclusive case.
LockResourceEntity exclusiveLockResource = getLockResource(qnameNamespaceId, qnameLocalName);
if (exclusiveLockResource == null)
{
// If the exclusive lock doesn't exist, the locks don't exist
return false;
}
Long exclusiveLockResourceId = exclusiveLockResource.getId();
// Split the lock name
List<QName> lockQNames = splitLockQName(lockQName);
// We just need to know how many resources needed updating.
// They will all share the same exclusive lock resource
int requiredUpdateCount = lockQNames.size();
// Update
int updateCount = updateLocks(exclusiveLockResourceId, lockToken, newLockToken, timeToLive);
// Check
if (updateCount != requiredUpdateCount)
{
return false;
}
else
{
return true;
}
}
/**
* Validate if a lock can be taken or not.
*/
private boolean canTakeLock(LockEntity existingLock, String lockToken, Long desiredExclusiveLock)
{
if (EqualsHelper.nullSafeEquals(existingLock.getLockToken(), lockToken))
{
// The lock token is the same.
// Regardless of lock expiry, the lock can be taken
return true;
}
@@ -159,7 +220,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO
}
else if (existingLock.isExclusive())
{
// It's a valid, exclusive lock held by someone else ...
// It's a valid, exclusive lock held using a different token ...
return false;
}
else if (desiredExclusiveLock.equals(existingLock.getSharedResourceId()))
@@ -173,48 +234,86 @@ public abstract class AbstractLockDAOImpl implements LockDAO
return true;
}
}
/**
* Override to get the unique, lock resource entity if one exists.
*
* @param qnameNamespaceId the namespace entity ID
* @param qnameLocalName the lock localname
* @return Returns the lock resource entity,
* or <tt>null</tt> if it doesn't exist
* @param qnameNamespaceId the namespace entity ID
* @param qnameLocalName the lock localname
* @return Returns the lock resource entity,
* or <tt>null</tt> if it doesn't exist
*/
protected abstract LockResourceEntity getLockResource(Long qnameNamespaceId, String qnameLocalName);
/**
* Create a unique lock resource
*
* @param qnameNamespaceId the namespace entity ID
* @param qnameLocalName the lock localname
* @return Returns the newly created lock resource entity
* @param qnameNamespaceId the namespace entity ID
* @param qnameLocalName the lock localname
* @return Returns the newly created lock resource entity
*/
protected abstract LockResourceEntity createLockResource(Long qnameNamespaceId, String qnameLocalName);
/**
* Get any existing lock data for the resources required. The locks returned are not filtered and
* @param id the lock instance ID
* @return Returns the lock, if it exists, otherwise <tt>null</tt>
*/
protected abstract LockEntity getLock(Long id);
/**
* @param sharedResourceId the shared lock resource ID
* @param exclusiveResourceId the exclusive lock resource ID
* @return Returns the lock, if it exists, otherwise <tt>null</tt>
*/
protected abstract LockEntity getLock(Long sharedResourceId, Long exclusiveResourceId);
/**
* Get any existing lock data for the shared resources. The locks returned are not filtered and
* may be expired.
*
* @param lockResourceIds a list of resource IDs for which to retrieve the current locks
* @param lockResourceIds a list of shared resource IDs for which to retrieve the current locks
* @return Returns a list of locks (expired or not) for the given lock resources
*/
protected abstract List<LockEntity> getLocks(List<Long> lockResourceIds);
protected abstract List<LockEntity> getLocksBySharedResourceIds(List<Long> sharedLockResourceIds);
/**
* Create a new lock.
* @param sharedResourceId the specific resource to lock
* @param exclusiveResourceId the exclusive lock that is being sought
* @param lockApplicant the ID of the lock applicant
* @param timeToLive the time, in milliseconds, for the lock to remain valid
* @return Returns the new lock
* @param sharedResourceId the specific resource to lock
* @param exclusiveResourceId the exclusive lock that is being sought
* @param lockToken the lock token to assign
* @param timeToLive the time, in milliseconds, for the lock to remain valid
* @return Returns the new lock
* @throws ConcurrencyFailureException if the lock was already taken at the time of creation
*/
protected abstract LockEntity createLock(
Long sharedResourceId,
Long exclusiveResourceId,
String lockApplicant,
String lockToken,
long timeToLive);
/**
* Update an existing lock
* @param lockEntity the specific lock to update
* @param lockApplicant the new lock token
* @param timeToLive the new lock time, in milliseconds, for the lock to remain valid
* @return Returns the updated lock
*/
protected abstract LockEntity updateLock(
LockEntity lockEntity,
String lockToken,
long timeToLive);
/**
* @param exclusiveLockResourceId the exclusive resource ID being locks
* @param oldLockToken the lock token to change from
* @param newLockToken the new lock token
* @param timeToLive the new time to live (in milliseconds)
* @return the number of rows updated
*/
protected abstract int updateLocks(
Long exclusiveLockResourceId,
String oldLockToken,
String newLockToken,
long timeToLive);
/**
@@ -222,8 +321,8 @@ public abstract class AbstractLockDAOImpl implements LockDAO
* separator on the localname. The namespace is preserved. The provided qualified
* name will always be the last component in the returned list.
*
* @param lockQName the lock name to split into it's higher-level paths
* @return Returns the namespace ID along with the ordered localnames
* @param lockQName the lock name to split into it's higher-level paths
* @return Returns the namespace ID along with the ordered localnames
*/
protected List<QName> splitLockQName(QName lockQName)
{

View File

@@ -37,12 +37,43 @@ public interface LockDAO
/**
* Aquire a given exclusive lock, assigning it (and any implicitly shared locks) a
* timeout. All shared locks are implicitly taken as well.
* <p>
* A lock can be re-taken if it has expired and if the lock token has not changed
*
* @param lockQName the unique name of the lock to acquire
* @param lockApplicant the potential lock holder's identifier (max 36 chars)
* @param lockToken the potential lock token (max 36 chars)
* @param timeToLive the time (in milliseconds) that the lock must remain
* @return Returns <tt>true</tt> if the lock was taken,
* otherwise <tt>false</tt>
*/
boolean getLock(QName lockQName, String lockApplicant, long timeToLive);
boolean getLock(QName lockQName, String lockToken, long timeToLive);
/**
* Refresh a held lock. This is successful if the lock in question still exists
* and if the lock token has not changed. Lock expiry does not prevent the lock
* from being refreshed.
*
* @param lockQName the unique name of the lock to update
* @param lockToken the lock token for the lock held
* @param timeToLive the new time to live (in milliseconds)
* @return Returns <tt>true</tt> if the lock was updated,
* otherwise <tt>false</tt>
*/
boolean refreshLock(QName lockQName, String lockToken, long timeToLive);
/**
* Release a lock. The lock token must still apply and all the shared and exclusive
* locks need to still be present. Lock expiration does not prevent this operation
* from succeeding.
* <p>
* Note: Failure to release a lock due to a exception condition is dealt with by
* passing the exception out.
*
* @param lockQName the unique name of the lock to release
* @param lockToken the current lock token
* @return Returns <tt>true</tt> if all the required locks were
* (still) held under the lock token and were
* valid at the time of release, otherwise <tt>false</tt>
*/
boolean releaseLock(QName lockQName, String lockToken);
}

View File

@@ -0,0 +1,420 @@
/*
* Copyright (C) 2005-2009 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.domain.locks;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import junit.framework.TestCase;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
/**
* @see LockDAO
*
* @author Derek Hulley
* @since 3.2
*/
public class LockDAOTest extends TestCase
{
public static final String NAMESPACE = "http://www.alfresco.org/test/LockDAOTest";
private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private TransactionService transactionService;
private RetryingTransactionHelper txnHelper;
private LockDAO lockDAO;
// Lock names for the tests
private QName lockA;
private QName lockAA;
private QName lockAAA;
private QName lockAAB;
private QName lockAAC;
private QName lockAB;
private QName lockABA;
private QName lockABB;
private QName lockABC;
@Override
public void setUp() throws Exception
{
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
transactionService = serviceRegistry.getTransactionService();
txnHelper = transactionService.getRetryingTransactionHelper();
txnHelper.setMinRetryWaitMs(10);
txnHelper.setRetryWaitIncrementMs(10);
txnHelper.setMaxRetryWaitMs(50);
lockDAO = (LockDAO) ctx.getBean("lockDAO");
// Get the test name
String testName = getName();
// Build lock names for the test
lockA = QName.createQName(NAMESPACE, "a-" + testName);
lockAA = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName);
lockAAA = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".a-" + testName);
lockAAB = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".b-" + testName);
lockAAC = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".c-" + testName);
lockAB = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName);
lockABA = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".a-" + testName);
lockABB = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".b-" + testName);
lockABC = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".c-" + testName);
}
private String lock(final QName lockName, final long timeToLive, boolean expectSuccess)
{
String token = lock(lockName, timeToLive);
if (expectSuccess)
{
assertNotNull(
"Expected to get lock " + lockName + " with TTL of " + timeToLive,
token);
}
else
{
assertNull(
"Expected lock " + lockName + " to have been denied",
token);
}
return token;
}
/**
* Do the lock in a new transaction
* @return Returns the lock token or <tt>null</tt> if it didn't work
*/
private String lock(final QName lockName, final long timeToLive)
{
RetryingTransactionCallback<String> callback = new RetryingTransactionCallback<String>()
{
public String execute() throws Throwable
{
String txnId = AlfrescoTransactionSupport.getTransactionId();
boolean locked = lockDAO.getLock(lockName, txnId, timeToLive);
return locked ? txnId : null;
}
};
return txnHelper.doInTransaction(callback);
}
private void refresh(final QName lockName, final String lockToken, final long timeToLive, boolean expectSuccess)
{
RetryingTransactionCallback<Boolean> callback = new RetryingTransactionCallback<Boolean>()
{
public Boolean execute() throws Throwable
{
return lockDAO.refreshLock(lockName, lockToken, timeToLive);
}
};
Boolean released = txnHelper.doInTransaction(callback);
if (expectSuccess)
{
assertTrue(
"Expected to have refreshed lock " + lockName,
released.booleanValue());
}
else
{
assertFalse(
"Expected to have failed to refresh lock " + lockName,
released.booleanValue());
}
}
private void release(final QName lockName, final String lockToken, boolean expectSuccess)
{
RetryingTransactionCallback<Boolean> callback = new RetryingTransactionCallback<Boolean>()
{
public Boolean execute() throws Throwable
{
return lockDAO.releaseLock(lockName, lockToken);
}
};
Boolean released = txnHelper.doInTransaction(callback);
if (expectSuccess)
{
assertTrue(
"Expected to have released lock " + lockName,
released.booleanValue());
}
else
{
assertFalse(
"Expected to have failed to release lock " + lockName,
released.booleanValue());
}
}
public void testGetLockBasic() throws Exception
{
lock(lockAAA, 500L, true);
}
/**
* Ensure that the lock tables and queries scale
*/
public void testLockTableScaling() throws Exception
{
int count = 500;
long before = System.currentTimeMillis();
for (int i = 1; i <= count; i++)
{
QName lockName = QName.createQName(lockAAA.getNamespaceURI(), lockAAA.getLocalName() + "-" + i);
lock(lockName, 500L, true);
if (i % 100 == 0)
{
long after = System.currentTimeMillis();
System.out.println("Creation of " + i + " locks took " + (after-before)/1000 + "s");
}
}
}
public void testGetLockFailureBasic() throws Exception
{
lock(lockAAA, 500L, true);
lock(lockAAA, 0L, false);
}
public void testSharedLocks() throws Exception
{
lock(lockAAA, 500L, true);
lock(lockAAB, 500L, true);
lock(lockAAC, 500L, true);
lock(lockABA, 500L, true);
lock(lockABB, 500L, true);
lock(lockABC, 500L, true);
}
public void testExclusiveLockBlockedByShared() throws Exception
{
lock(lockAAA, 100L, true);
lock(lockAA, 100L, false);
lock(lockAB, 100L, true);
lock(lockA, 100L, false);
lock(lockABA, 100L, false);
}
public void testReleaseLockBasic() throws Exception
{
String token = lock(lockAAA, 500000L, true);
release(lockAAA, token, true);
token = lock(lockAAA, 0L, true);
}
public void testSharedLockAndRelease() throws Exception
{
String tokenAAA = lock(lockAAA, 5000L, true);
String tokenAAB = lock(lockAAB, 5000L, true);
String tokenAAC = lock(lockAAC, 5000L, true);
String tokenABA = lock(lockABA, 5000L, true);
String tokenABB = lock(lockABB, 5000L, true);
String tokenABC = lock(lockABC, 5000L, true);
// Can't lock shared resources
lock(lockAA, 0L, false);
lock(lockAB, 0L, false);
lock(lockA, 0L, false);
// Release a lock and check again
release(lockAAA, tokenAAA, true);
lock(lockAA, 0L, false);
lock(lockAB, 0L, false);
lock(lockA, 0L, false);
// Release a lock and check again
release(lockAAB, tokenAAB, true);
lock(lockAA, 0L, false);
lock(lockAB, 0L, false);
lock(lockA, 0L, false);
// Release a lock and check again
release(lockAAC, tokenAAC, true);
String tokenAA = lock(lockAA, 5000L, true); // This should be open now
lock(lockAB, 0L, false);
lock(lockA, 0L, false);
// Release a lock and check again
release(lockABA, tokenABA, true);
lock(lockAB, 0L, false);
lock(lockA, 0L, false);
// Release a lock and check again
release(lockABB, tokenABB, true);
lock(lockAB, 0L, false);
lock(lockA, 0L, false);
// Release a lock and check again
release(lockABC, tokenABC, true);
String tokenAB = lock(lockAB, 5000L, true);
lock(lockA, 0L, false);
// Release AA and AB
release(lockAA, tokenAA, true);
release(lockAB, tokenAB, true);
String tokenA = lock(lockA, 5000L, true);
// ... and release
release(lockA, tokenA, true);
}
public synchronized void testLockExpiry() throws Exception
{
lock(lockAAA, 50L, true);
this.wait(50L);
lock(lockAA, 50L, true);
this.wait(50L);
lock(lockA, 100L, true);
}
/**
* Check that locks grabbed away due to expiry cannot be released
* @throws Exception
*/
public synchronized void testLockExpiryAndRelease() throws Exception
{
String tokenAAA = lock(lockAAA, 500L, true);
release(lockAAA, tokenAAA, true);
tokenAAA = lock(lockAAA, 50L, true); // Make sure we can re-acquire the lock
this.wait(50L); // Wait for expiry
String grabbedTokenAAAA = lock(lockAAA, 50L, true); // Grabbed lock over the expiry
release(lockAAA, tokenAAA, false); // Can't release any more
this.wait(50L); // Wait for expiry
release(lockAAA, grabbedTokenAAAA, true); // Proof that expiry, on it's own, doesn't prevent release
}
public synchronized void testLockRefresh() throws Exception
{
String tokenAAA = lock(lockAAA, 1000L, true);
// Loop, refreshing and testing
for (int i = 0; i < 40; i++)
{
wait(50L);
// It will have expired, but refresh it anyway
refresh(lockAAA, tokenAAA, 1000L, true);
// Check that it is still holding
lock(lockAAA, 0L, false);
}
}
/**
* Uses a thread lock to ensure that the lock DAO only allows locks through one at a time.
*/
public synchronized void testConcurrentLockAquisition() throws Exception
{
ReentrantLock threadLock = new ReentrantLock();
GetLockThread[] threads = new GetLockThread[50];
for (int i = 0; i < threads.length; i++)
{
threads[i] = new GetLockThread(threadLock);
threads[i].start();
}
// Wait a bit and see if any encountered errors
boolean allDone = false;
waitLoop:
for (int waitLoop = 0; waitLoop < 50; waitLoop++)
{
wait(2000L);
for (int i = 0; i < threads.length; i++)
{
if (!threads[i].done)
{
continue waitLoop;
}
}
// All the threads are done
allDone = true;
break;
}
// Check that all the threads got a turn
if (!allDone)
{
fail("Not all threads managed to acquire the lock");
}
// Get errors
StringBuilder errors = new StringBuilder(512);
for (int i = 0; i < threads.length; i++)
{
if (threads[i].error != null)
{
errors.append("\nThread ").append(i).append(" error: ").append(threads[i].error);
}
}
if (errors.toString().length() > 0)
{
fail(errors.toString());
}
}
/**
* Checks that the lock via the DAO forces a serialization
*/
private class GetLockThread extends Thread
{
private final ReentrantLock threadLock;
private boolean done;
private String error;
private GetLockThread(ReentrantLock threadLock)
{
this.threadLock = threadLock;
this.done = false;
this.error = null;
setDaemon(true);
}
@Override
public synchronized void run()
{
boolean gotLock = false;
try
{
String tokenAAA = null;
while (true)
{
tokenAAA = lock(lockAAA, 100000L); // Lock for a long time
if (tokenAAA != null)
{
break; // Got the lock
}
try { wait(20L); } catch (InterruptedException e) {}
}
gotLock = threadLock.tryLock(0, TimeUnit.MILLISECONDS);
if (!gotLock)
{
error = "Got lock via DAO but not via thread lock";
return;
}
release(lockAAA, tokenAAA, true);
}
catch (Throwable e)
{
error = e.getMessage();
}
finally
{
done = true;
if (gotLock)
{
threadLock.unlock();
}
}
}
}
}

View File

@@ -41,9 +41,9 @@ public class LockEntity
private Long version;
private Long sharedResourceId;
private Long exclusiveResourceId;
private String lockHolder;
private String lockToken;
private Long startTime;
private Long expiryTime = Long.MAX_VALUE; // TODO:
private Long expiryTime = Long.MIN_VALUE; // 'expired' unless set
@Override
public int hashCode()
@@ -144,19 +144,19 @@ public class LockEntity
}
/**
* @return Returns the ID of the lock holder
* @return Returns the token assigned when the lock was created
*/
public String getLockHolder()
public String getLockToken()
{
return lockHolder;
return lockToken;
}
/**
* @param lockHolder the ID of the lock holder
* @param lockToken the token assigned when the lock was created
*/
public void setLockHolder(String lockHolder)
public void setLockToken(String lockToken)
{
this.lockHolder = lockHolder;
this.lockToken = lockToken;
}
/**

View File

@@ -24,7 +24,9 @@
*/
package org.alfresco.repo.domain.locks.ibatis;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.domain.locks.AbstractLockDAOImpl;
import org.alfresco.repo.domain.locks.LockEntity;
@@ -41,9 +43,13 @@ public class LockDAOImpl extends AbstractLockDAOImpl
{
private static final Long CONST_LONG_ZERO = new Long(0L);
private static final String SELECT_LOCKRESOURCE_BY_QNAME = "select.LockResourceByQName";
private static final String SELECT_LOCK_BY_ID = "select.LockByID";
private static final String SELECT_LOCK_BY_KEY = "select.LockByKey";
private static final String SELECT_LOCK_BY_SHARED_IDS = "select.LockBySharedIds";
private static final String INSERT_LOCKRESOURCE = "insert.LockResource";
private static final String INSERT_LOCK = "insert.Lock";
private static final String UPDATE_LOCK = "update.Lock";
private static final String UPDATE_EXCLUSIVE_LOCK = "update.ExclusiveLock";
private SqlMapClientTemplate template;
@@ -78,25 +84,46 @@ public class LockDAOImpl extends AbstractLockDAOImpl
@SuppressWarnings("unchecked")
@Override
protected List<LockEntity> getLocks(List<Long> lockResourceIds)
protected List<LockEntity> getLocksBySharedResourceIds(List<Long> sharedLockResourceIds)
{
List<LockEntity> locks = template.queryForList(SELECT_LOCK_BY_SHARED_IDS, lockResourceIds);
List<LockEntity> locks = template.queryForList(SELECT_LOCK_BY_SHARED_IDS, sharedLockResourceIds);
// Done
return locks;
}
@Override
protected LockEntity getLock(Long id)
{
LockEntity lock = new LockEntity();
lock.setId(id);
lock = (LockEntity) template.queryForObject(SELECT_LOCK_BY_ID, lock);
// Done
return lock;
}
@Override
protected LockEntity getLock(Long sharedResourceId, Long exclusiveResourceId)
{
LockEntity lock = new LockEntity();
lock.setSharedResourceId(sharedResourceId);
lock.setExclusiveResourceId(exclusiveResourceId);
lock = (LockEntity) template.queryForObject(SELECT_LOCK_BY_KEY, lock);
// Done
return lock;
}
@Override
protected LockEntity createLock(
Long sharedResourceId,
Long exclusiveResourceId,
String lockApplicant,
String lockToken,
long timeToLive)
{
LockEntity lock = new LockEntity();
lock.setVersion(CONST_LONG_ZERO);
lock.setSharedResourceId(sharedResourceId);
lock.setExclusiveResourceId(exclusiveResourceId);
lock.setLockHolder(lockApplicant);
lock.setLockToken(lockToken);
long now = System.currentTimeMillis();
long exp = now + timeToLive;
lock.setStartTime(now);
@@ -106,4 +133,42 @@ public class LockDAOImpl extends AbstractLockDAOImpl
// Done
return lock;
}
@Override
protected LockEntity updateLock(LockEntity lockEntity, String lockToken, long timeToLive)
{
LockEntity updateLockEntity = new LockEntity();
updateLockEntity.setId(lockEntity.getId());
updateLockEntity.setVersion(lockEntity.getVersion());
updateLockEntity.incrementVersion(); // Increment the version number
updateLockEntity.setSharedResourceId(lockEntity.getSharedResourceId());
updateLockEntity.setExclusiveResourceId(lockEntity.getExclusiveResourceId());
updateLockEntity.setLockToken(lockToken);
long now = System.currentTimeMillis();
Long exp = now + timeToLive;
updateLockEntity.setStartTime(lockEntity.getStartTime()); // Keep original start time
updateLockEntity.setExpiryTime(exp); // Don't update the start time
template.update(UPDATE_LOCK, updateLockEntity, 1);
// Done
return updateLockEntity;
}
@Override
protected int updateLocks(
Long exclusiveLockResourceId,
String oldLockToken,
String newLockToken,
long timeToLive)
{
Map<String, Object> params = new HashMap<String, Object>(11);
params.put("exclusiveLockResourceId", exclusiveLockResourceId);
params.put("oldLockToken", oldLockToken);
params.put("newLockToken", oldLockToken);
long now = System.currentTimeMillis();
Long exp = new Long(now + timeToLive);
params.put("newExpiryTime", exp);
int updateCount = template.update(UPDATE_EXCLUSIVE_LOCK, params);
// Done
return updateCount;
}
}

View File

@@ -54,6 +54,7 @@ import org.springframework.aop.framework.ProxyFactory;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DeadlockLoserDataAccessException;
import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException;
import org.springframework.jdbc.UncategorizedSQLException;
import com.ibatis.common.jdbc.exception.NestedSQLException;
@@ -95,6 +96,7 @@ public class RetryingTransactionHelper
ConcurrencyFailureException.class,
DeadlockLoserDataAccessException.class,
StaleObjectStateException.class,
JdbcUpdateAffectedIncorrectNumberOfRowsException.class, // Similar to StaleObjectState
LockAcquisitionException.class,
ConstraintViolationException.class,
UncategorizedSQLException.class,
@@ -137,7 +139,7 @@ public class RetryingTransactionHelper
/**
* Callback interface
* @author britt
* @author Derek Hulley
*/
public interface RetryingTransactionCallback<Result>
{