/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see
* Locks are identified by a fully qualified name ({@link QName}) and follow a hierarchical * naming convention i.e. locks higher up a hierarchy can be shared but will prevent explicit * (exclusive) locks from being taken. For example: If exclusive lock a.a.a has been * taken, then a.a and a are all implicitly taken as shared locks. Exclusive lock * a.a.b can be taken by another process and will share locks a.a and a * with the first process. It will not be possible for a third process to take a lock on * a.a, however. *
* LOCK ORDERING:
* The transactional locks will be applied in strict alphabetical order. A very basic deadlock
* prevention system (at least) must be in place when applying or reapplying locks and be biased
* against locks applied non-alphabetically.
*
* @author Derek Hulley
* @since 3.2
*/
public interface JobLockService
{
/**
* Take a transactionally-managed lock. This method can be called repeatedly to both
* initially acquire the lock as well as to maintain the lock. This method should
* be called repeatedly during the transaction to ensure that the lock remains refreshed.
* DO NOT use a long-lived lock to avoid calling this method at intervals; long-lived
* locks get left behind during server crashes, amongst other things.
*
* The following rules apply to taking and releasing locks:
* - Expired locks can be taken by any process
* - Lock expiration does not prevent a lock from being refreshed or released
* - Only locks that were manipulated using another token will cause failure
*
* The locks are automatically released when the transaction is terminated. *
* Any failure to acquire the lock (after retries), refresh the lock or subsequently * release the owned locks will invalidate the transaction and cause rollback. * * @param lockQName the name of the lock to acquire * @param timeToLive the time (in milliseconds) for the lock to remain valid. * This value must not be larger than either the anticipated * operation time or a server startup time. Typically, it should be * a few seconds. * @throws LockAcquisitionException if the lock could not be acquired * @throws IllegalStateException if a transaction is not active */ void getTransactionalLock(QName lockQName, long timeToLive); /** * Take a transactionally-managed lock. This method can be called repeatedly to both * initially acquire the lock as well as to maintain the lock. This method should * be called repeatedly during the transaction to ensure that the lock remains refreshed. * DO NOT use a long-lived lock to avoid calling this method at intervals; long-lived * locks get left behind during server crashes, amongst other things. *
* The following rules apply to taking and releasing locks:
* - Expired locks can be taken by any process
* - Lock expiration does not prevent a lock from being refreshed or released
* - Only locks that were manipulated using another token will cause failure
*
* The locks are automatically released when the transaction is terminated. *
* Any failure to acquire the lock (after retries), refresh the lock or subsequently * release the owned locks will invalidate the transaction and cause rollback. *
* If the lock cannot be immediately acquired, the process will wait and retry. Note * that second and subsequent attempts to get the lock during a transaction cannot * make use of retrying; the lock is actually being refreshed and will therefore never * become valid if it doesn't refresh directly. * * @param lockQName the name of the lock to acquire * @param timeToLive the time (in milliseconds) for the lock to remain valid. * This value must not be larger than either the anticipated * operation time or a server startup time. Typically, it should be * a few seconds. * @param retryWait the time (in milliseconds) to wait before trying again * @param retryCount the maximum number of times to attempt the lock acquisition * @throws LockAcquisitionException if the lock could not be acquired * @throws IllegalStateException if a transaction is not active */ void getTransactionalLock(QName lockQName, long timeToLive, long retryWait, int retryCount); /** * Take a manually-managed lock. The lock current thread or transaction will not be tagged - * the returned lock token must be used for further management of the lock. *
* No lock management is provided: the lock must be released manually or will only become * available by expiry. No deadlock management is provided, either. * * @param lockQName the name of the lock to acquire * @param timeToLive the time (in milliseconds) for the lock to remain valid. * This value must not be larger than either the anticipated * operation time or a server startup time. Typically, it should be * a few seconds. * @return Returns the newly-created lock token * @throws LockAcquisitionException if the lock could not be acquired */ String getLock(QName lockQName, long timeToLive); /** * Take a manually-managed lock. The lock current thread or transaction will not be tagged - * the returned lock token must be used for further management of the lock. *
* No lock management is provided: the lock must be released manually or will only become * available by expiry. No deadlock management is provided, either. *
* If the lock cannot be immediately acquired, the process will wait and retry. * * @param lockQName the name of the lock to acquire * @param timeToLive the time (in milliseconds) for the lock to remain valid. * This value must not be larger than either the anticipated * operation time or a server startup time. Typically, it should be * a few seconds. * @param retryWait the time (in milliseconds) to wait before trying again * @param retryCount the maximum number of times to attempt the lock acquisition * @throws LockAcquisitionException if the lock could not be acquired */ String getLock(QName lockQName, long timeToLive, long retryWait, int retryCount); /** * Refresh the lock using a valid lock token. * * @param lockToken the lock token returned when the lock was acquired * @param lockQName the name of the previously-acquired lock * @param timeToLive the time (in milliseconds) for the lock to remain valid * @throws LockAcquisitionException if the lock could not be refreshed or acquired */ void refreshLock(String lockToken, QName lockQName, long timeToLive); /** * Provide a callback to refresh a lock using a valid lock token, pushing responsibility * for regular lock refreshing onto the service implementation code. This method should only * be called once for a given lock token to prevent unnecessary refreshing. *
* Since the lock is not actually refreshed by this method, there will be no LockAcquisitionException. * * The TTL (time to live) will be divided by two and the result used to trigger a timer thread * to initiate the callback. * * @param lockToken the lock token returned when the lock was acquired * @param lockQName the name of the previously-acquired lock * @param timeToLive the time (in milliseconds) for the lock to remain valid * @param callback the object that will be called at intervals of timeToLive/2 (about) * * @since 3.4.0a */ void refreshLock(String lockToken, QName lockQName, long timeToLive, JobLockRefreshCallback callback); /** * Release the lock using a valid lock token. * * @param lockToken the lock token returned when the lock was acquired * @param lockQName the name of the previously-acquired lock */ void releaseLock(String lockToken, QName lockQName); /** * Interface for implementations that need a timed callback in order to refresh the lock. * * This callback is designed for processes that need to lock and wait for external processes * to complete; keeping a local thread to refresh the lock is possible but it is more * efficient for the thread pool and timer mechanisms to be shared. * * The callback implementations must be thread-safe and should be independent * of other callbacks i.e. the simplest and safest is to use an anonymous inner class for * the implementation. * * IMPORTANT: Do not block the calls to this interface - other callbacks might be held * up producing inconsistent behaviour. Failure to observe this will lead * to warnings and lock termination i.e. the service implementation will * force early termination of the lock and will discard the callback. * * @author Derek Hulley * @since 3.4.0b */ public interface JobLockRefreshCallback { /** * Timed callback from the service to determine if the lock is still required. * * IMPORTANT: Do not block calls to this method for any reason and do perform any * non-trivial determination of state i.e. have the answer to this * method immediately available at all times. Failure to observe this * will lead to warnings and lock termination. * * The original lock token is not provided in the callback; this is to prevent * implementations from attempting to link the lock token back to the specific callback * instances. * * @return Return true if the task associated with the callback * is still active i.e. it still needs the lock associated with the * callback or false if the lock is no longer required. * * @since 3.4.0b */ boolean isActive(); /** * Callback received when the lock refresh has failed. Implementations should immediately and * gracefully terminate their associated processes as the associated lock is no longer valid, * which is a direct indication that a competing process has taken and is using the required * lock or that the process has already completed and released the lock. * * As a convenenience, this method is called when a VM shutdown is detected as well; the * associated lock is not refreshed and this method is called to instruct the locking process * to terminate. * * This method is also called if the initiating process is self-terminated i.e. if the originating * process releases the lock itself. This method is not called if the process is not * {@link #isActive() active}. * * @since 3.4.0b */ void lockReleased(); } }