- Use LockAcquisitionException instead of boolean for failures
 - Lock update with TTL=0 sets the start and expiry time to 0


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13956 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2009-04-15 13:52:12 +00:00
parent 6a20eda27f
commit 5bda334c22
6 changed files with 180 additions and 57 deletions

View File

@@ -19,3 +19,9 @@ system.openoffice.info.connection_verified=The connection to OpenOffice has been
system.openoffice.err.connection_failed=An initial OpenOffice connection could not be established. system.openoffice.err.connection_failed=An initial OpenOffice connection could not be established.
system.openoffice.err.connection_lost=The OpenOffice connection has been lost. system.openoffice.err.connection_lost=The OpenOffice connection has been lost.
system.openoffice.err.connection_remade=The OpenOffice connection was re-established. system.openoffice.err.connection_remade=The OpenOffice connection was re-established.
# Locks
system.locks.err.failed_to_acquire_lock=Failed to get lock ''{0}'' using token ''{1}''.
system.locks.err.lock_resource_missing=Failed to manipulate lock ''{0}'' using token ''{1}''. The lock resource no longer exists.
system.locks.err.lock_update_count=Failed to update lock ''{0}'' using token ''{1}''. {2} locks were updated when {3} should have been.
system.locks.err.excl_lock_exists=Failed to get lock ''{0}'' using token ''{1}''. An exclusive lock in the hierarchy exists.

View File

@@ -31,6 +31,7 @@ import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.alfresco.repo.domain.QNameDAO; import org.alfresco.repo.domain.QNameDAO;
import org.alfresco.repo.lock.LockAcquisitionException;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper; import org.alfresco.util.EqualsHelper;
import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.ConcurrencyFailureException;
@@ -63,7 +64,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO
this.qnameDAO = qnameDAO; this.qnameDAO = qnameDAO;
} }
public boolean getLock(QName lockQName, String lockToken, long timeToLive) public void getLock(QName lockQName, String lockToken, long timeToLive)
{ {
String qnameNamespaceUri = lockQName.getNamespaceURI(); String qnameNamespaceUri = lockQName.getNamespaceURI();
String qnameLocalName = lockQName.getLocalName(); String qnameLocalName = lockQName.getLocalName();
@@ -114,7 +115,9 @@ public abstract class AbstractLockDAOImpl implements LockDAO
boolean canTakeLock = canTakeLock(existingLock, lockToken, requiredExclusiveLockResourceId); boolean canTakeLock = canTakeLock(existingLock, lockToken, requiredExclusiveLockResourceId);
if (!canTakeLock) if (!canTakeLock)
{ {
return false; throw new LockAcquisitionException(
LockAcquisitionException.ERR_EXCLUSIVE_LOCK_EXISTS,
lockQName, lockToken);
} }
existingLocksMap.put(existingLock, existingLock); existingLocksMap.put(existingLock, existingLock);
} }
@@ -142,24 +145,25 @@ public abstract class AbstractLockDAOImpl implements LockDAO
timeToLive); timeToLive);
} }
} }
return true; // Done
} }
public boolean refreshLock(QName lockQName, String lockToken, long timeToLive) public void refreshLock(QName lockQName, String lockToken, long timeToLive)
{ {
return updateLocks(lockQName, lockToken, lockToken, timeToLive); updateLocks(lockQName, lockToken, lockToken, timeToLive);
} }
public boolean releaseLock(QName lockQName, String lockToken) public void releaseLock(QName lockQName, String lockToken)
{ {
return updateLocks(lockQName, lockToken, LOCK_TOKEN_RELEASED, 0L); updateLocks(lockQName, lockToken, LOCK_TOKEN_RELEASED, 0L);
} }
/** /**
* Put new values against the given exclusive lock. This works against the related locks as * Put new values against the given exclusive lock. This works against the related locks as
* well. * well.
* @throws LockAcquisitionException on failure
*/ */
private boolean updateLocks(QName lockQName, String lockToken, String newLockToken, long timeToLive) private void updateLocks(QName lockQName, String lockToken, String newLockToken, long timeToLive)
{ {
String qnameNamespaceUri = lockQName.getNamespaceURI(); String qnameNamespaceUri = lockQName.getNamespaceURI();
String qnameLocalName = lockQName.getLocalName(); String qnameLocalName = lockQName.getLocalName();
@@ -181,7 +185,9 @@ public abstract class AbstractLockDAOImpl implements LockDAO
if (exclusiveLockResource == null) if (exclusiveLockResource == null)
{ {
// If the exclusive lock doesn't exist, the locks don't exist // If the exclusive lock doesn't exist, the locks don't exist
return false; throw new LockAcquisitionException(
LockAcquisitionException.ERR_LOCK_RESOURCE_MISSING,
lockQName, lockToken);
} }
Long exclusiveLockResourceId = exclusiveLockResource.getId(); Long exclusiveLockResourceId = exclusiveLockResource.getId();
// Split the lock name // Split the lock name
@@ -194,12 +200,11 @@ public abstract class AbstractLockDAOImpl implements LockDAO
// Check // Check
if (updateCount != requiredUpdateCount) if (updateCount != requiredUpdateCount)
{ {
return false; throw new LockAcquisitionException(
} LockAcquisitionException.ERR_LOCK_UPDATE_COUNT,
else lockQName, lockToken, new Integer(updateCount), new Integer(requiredUpdateCount));
{
return true;
} }
// Done
} }
/** /**

View File

@@ -24,6 +24,7 @@
*/ */
package org.alfresco.repo.domain.locks; package org.alfresco.repo.domain.locks;
import org.alfresco.repo.lock.LockAcquisitionException;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
/** /**
@@ -45,8 +46,9 @@ public interface LockDAO
* @param timeToLive the time (in milliseconds) that the lock must remain * @param timeToLive the time (in milliseconds) that the lock must remain
* @return Returns <tt>true</tt> if the lock was taken, * @return Returns <tt>true</tt> if the lock was taken,
* otherwise <tt>false</tt> * otherwise <tt>false</tt>
* @throws LockAcquisitionException on failure
*/ */
boolean getLock(QName lockQName, String lockToken, long timeToLive); void getLock(QName lockQName, String lockToken, long timeToLive);
/** /**
* Refresh a held lock. This is successful if the lock in question still exists * Refresh a held lock. This is successful if the lock in question still exists
@@ -58,8 +60,9 @@ public interface LockDAO
* @param timeToLive the new time to live (in milliseconds) * @param timeToLive the new time to live (in milliseconds)
* @return Returns <tt>true</tt> if the lock was updated, * @return Returns <tt>true</tt> if the lock was updated,
* otherwise <tt>false</tt> * otherwise <tt>false</tt>
* @throws LockAcquisitionException on failure
*/ */
boolean refreshLock(QName lockQName, String lockToken, long timeToLive); void refreshLock(QName lockQName, String lockToken, long timeToLive);
/** /**
* Release a lock. The lock token must still apply and all the shared and exclusive * Release a lock. The lock token must still apply and all the shared and exclusive
@@ -75,5 +78,5 @@ public interface LockDAO
* (still) held under the lock token and were * (still) held under the lock token and were
* valid at the time of release, otherwise <tt>false</tt> * valid at the time of release, otherwise <tt>false</tt>
*/ */
boolean releaseLock(QName lockQName, String lockToken); void releaseLock(QName lockQName, String lockToken);
} }

View File

@@ -29,6 +29,7 @@ import java.util.concurrent.locks.ReentrantLock;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.alfresco.repo.lock.LockAcquisitionException;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
@@ -90,25 +91,33 @@ public class LockDAOTest extends TestCase
} }
private String lock(final QName lockName, final long timeToLive, boolean expectSuccess) private String lock(final QName lockName, final long timeToLive, boolean expectSuccess)
{
try
{ {
String token = lock(lockName, timeToLive); String token = lock(lockName, timeToLive);
if (!expectSuccess)
{
fail("Expected lock " + lockName + " to have been denied");
}
return token;
}
catch (LockAcquisitionException e)
{
if (expectSuccess) if (expectSuccess)
{ {
assertNotNull( // oops
"Expected to get lock " + lockName + " with TTL of " + timeToLive, throw new RuntimeException("Expected to get lock " + lockName + " with TTL of " + timeToLive, e);
token);
} }
else else
{ {
assertNull( return null;
"Expected lock " + lockName + " to have been denied", }
token);
} }
return token;
} }
/** /**
* Do the lock in a new transaction * Do the lock in a new transaction
* @return Returns the lock token or <tt>null</tt> if it didn't work * @return Returns the lock token or <tt>null</tt> if it didn't work
* @throws LockAcquisitionException on failure
*/ */
private String lock(final QName lockName, final long timeToLive) private String lock(final QName lockName, final long timeToLive)
{ {
@@ -117,8 +126,8 @@ public class LockDAOTest extends TestCase
public String execute() throws Throwable public String execute() throws Throwable
{ {
String txnId = AlfrescoTransactionSupport.getTransactionId(); String txnId = AlfrescoTransactionSupport.getTransactionId();
boolean locked = lockDAO.getLock(lockName, txnId, timeToLive); lockDAO.getLock(lockName, txnId, timeToLive);
return locked ? txnId : null; return txnId;
} }
}; };
return txnHelper.doInTransaction(callback); return txnHelper.doInTransaction(callback);
@@ -130,21 +139,24 @@ public class LockDAOTest extends TestCase
{ {
public Boolean execute() throws Throwable public Boolean execute() throws Throwable
{ {
return lockDAO.refreshLock(lockName, lockToken, timeToLive); lockDAO.refreshLock(lockName, lockToken, timeToLive);
return Boolean.TRUE;
} }
}; };
Boolean released = txnHelper.doInTransaction(callback); try
{
txnHelper.doInTransaction(callback);
if (!expectSuccess)
{
fail("Expected to have failed to refresh lock " + lockName);
}
}
catch (LockAcquisitionException e)
{
if (expectSuccess) if (expectSuccess)
{ {
assertTrue( throw new RuntimeException("Expected to have refreshed lock " + lockName, e);
"Expected to have refreshed lock " + lockName,
released.booleanValue());
} }
else
{
assertFalse(
"Expected to have failed to refresh lock " + lockName,
released.booleanValue());
} }
} }
@@ -154,21 +166,24 @@ public class LockDAOTest extends TestCase
{ {
public Boolean execute() throws Throwable public Boolean execute() throws Throwable
{ {
return lockDAO.releaseLock(lockName, lockToken); lockDAO.releaseLock(lockName, lockToken);
return Boolean.TRUE;
} }
}; };
Boolean released = txnHelper.doInTransaction(callback); try
{
txnHelper.doInTransaction(callback);
if (!expectSuccess)
{
fail("Expected to have failed to release lock " + lockName);
}
}
catch (LockAcquisitionException e)
{
if (expectSuccess) if (expectSuccess)
{ {
assertTrue( throw new RuntimeException("Expected to have released lock " + lockName, e);
"Expected to have released lock " + lockName,
released.booleanValue());
} }
else
{
assertFalse(
"Expected to have failed to release lock " + lockName,
released.booleanValue());
} }
} }
@@ -387,10 +402,15 @@ public class LockDAOTest extends TestCase
String tokenAAA = null; String tokenAAA = null;
while (true) while (true)
{ {
tokenAAA = lock(lockAAA, 100000L); // Lock for a long time try
if (tokenAAA != null)
{ {
break; // Got the lock tokenAAA = lock(lockAAA, 100000L); // Lock for a long time
// Success
break;
}
catch (LockAcquisitionException e)
{
// OK. Keep trying.
} }
try { wait(20L); } catch (InterruptedException e) {} try { wait(20L); } catch (InterruptedException e) {}
} }

View File

@@ -144,8 +144,8 @@ public class LockDAOImpl extends AbstractLockDAOImpl
updateLockEntity.setSharedResourceId(lockEntity.getSharedResourceId()); updateLockEntity.setSharedResourceId(lockEntity.getSharedResourceId());
updateLockEntity.setExclusiveResourceId(lockEntity.getExclusiveResourceId()); updateLockEntity.setExclusiveResourceId(lockEntity.getExclusiveResourceId());
updateLockEntity.setLockToken(lockToken); updateLockEntity.setLockToken(lockToken);
long now = System.currentTimeMillis(); long now = (timeToLive > 0) ? System.currentTimeMillis() : 0L;
long exp = now + timeToLive; long exp = (timeToLive > 0) ? (now + timeToLive) : 0L;
updateLockEntity.setStartTime(new Long(now)); updateLockEntity.setStartTime(new Long(now));
updateLockEntity.setExpiryTime(new Long(exp)); updateLockEntity.setExpiryTime(new Long(exp));
template.update(UPDATE_LOCK, updateLockEntity, 1); template.update(UPDATE_LOCK, updateLockEntity, 1);
@@ -164,8 +164,8 @@ public class LockDAOImpl extends AbstractLockDAOImpl
params.put("exclusiveLockResourceId", exclusiveLockResourceId); params.put("exclusiveLockResourceId", exclusiveLockResourceId);
params.put("oldLockToken", oldLockToken); params.put("oldLockToken", oldLockToken);
params.put("newLockToken", newLockToken); params.put("newLockToken", newLockToken);
long now = System.currentTimeMillis(); long now = (timeToLive > 0) ? System.currentTimeMillis() : 0L;
long exp = now + timeToLive; long exp = (timeToLive > 0) ? (now + timeToLive) : 0L;
params.put("newStartTime", new Long(now)); params.put("newStartTime", new Long(now));
params.put("newExpiryTime", new Long(exp)); params.put("newExpiryTime", new Long(exp));
int updateCount = template.update(UPDATE_EXCLUSIVE_LOCK, params); int updateCount = template.update(UPDATE_EXCLUSIVE_LOCK, params);

View File

@@ -0,0 +1,89 @@
/*
* 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.lock;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.namespace.QName;
/**
* Exception generated when a lock cannot be acquired.
*
* @author Derek Hulley
* @since 3.2
*/
public class LockAcquisitionException extends AlfrescoRuntimeException
{
private static final long serialVersionUID = 8215858379509645862L;
/**
* <ul>
* <li>1: the qname</li>
* <li>2: the lock token</li>
* </ul>
*/
public static final String ERR_FAILED_TO_ACQUIRE_LOCK = "system.locks.err.failed_to_acquire_lock";
/**
* <ul>
* <li>1: the qname</li>
* <li>2: the lock token</li>
* </ul>
*/
public static final String ERR_LOCK_RESOURCE_MISSING = "system.locks.err.lock_resource_missing";
/**
* <ul>
* <li>1: the qname</li>
* <li>2: the lock token</li>
* <li>3: the actual update count</li>
* <li>4: the expected update count</li>
* </ul>
*/
public static final String ERR_LOCK_UPDATE_COUNT = "system.locks.err.lock_update_count";
/**
* <ul>
* <li>1: the qname</li>
* <li>2: the lock token</li>
* </ul>
*/
public static final String ERR_EXCLUSIVE_LOCK_EXISTS = "system.locks.err.excl_lock_exists";
/**
* @param lockQName the lock that was sought
* @param lockToken the lock token being used
*/
public LockAcquisitionException(QName lockQName, String lockToken)
{
super(ERR_FAILED_TO_ACQUIRE_LOCK, new Object[] {lockQName, lockToken});
}
/**
*
* @param msgId one of the message IDs defined
* @param args the arguments that apply
*/
public LockAcquisitionException(String msgId, Object ... args)
{
super(msgId, args);
}
}