/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.distributed.internal.locks; import org.apache.geode.CancelCriterion; import org.apache.geode.CancelException; import org.apache.geode.cache.CommitConflictException; import org.apache.geode.distributed.DistributedSystemDisconnectedException; import org.apache.geode.distributed.LockServiceDestroyedException; import org.apache.geode.distributed.internal.DM; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.distributed.internal.MembershipListener; import org.apache.geode.distributed.internal.locks.DLockQueryProcessor.DLockQueryMessage; import org.apache.geode.distributed.internal.locks.DLockRequestProcessor.DLockRequestMessage; import org.apache.geode.distributed.internal.membership.InternalDistributedMember; import org.apache.geode.internal.Assert; import org.apache.geode.internal.cache.IdentityArrayList; import org.apache.geode.internal.cache.TXReservationMgr; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.logging.log4j.LocalizedMessage; import org.apache.geode.internal.logging.log4j.LogMarker; import org.apache.geode.internal.util.concurrent.StoppableCountDownLatch; import org.apache.geode.internal.util.concurrent.StoppableReentrantReadWriteLock; import org.apache.logging.log4j.Logger; import java.util.*; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicLong; /** * Provides lock grantor authority to a distributed lock service. This is responsible for granting, * releasing, and timing out locks as well as exposing hooks for recovery or transfer of lock * grantor. * <p> * ReadWriteLocks are not currently handled by grantor recovery or transfer. * */ @SuppressWarnings("unchecked") public class DLockGrantor { private static final Logger logger = LogService.getLogger(); public static final boolean DEBUG_SUSPEND_LOCK = // TODO:LOG:CONVERT: REMOVE THIS Boolean.getBoolean( DistributionConfig.GEMFIRE_PREFIX + "DLockService.DLockGrantor.debugSuspendLock"); /** * Default wait before grantor thread will reawaken to check for expirations and timeouts. */ public static final long GRANTOR_THREAD_MAX_WAIT = DLockGrantorThread.MAX_WAIT; /** * Newly constructed grantor is INITIALIZING. All lock requests and related messages will block. */ private static final int INITIALIZING = 0; // msgs will block until READY /** * Grantor is READY and handling lock requests. */ private static final int READY = 1; // msgs will be processed /** * Grantor is DESTROYED and will respond as NOT_GRANTOR to any requests. */ private static final int DESTROYED = 5; // NOT_GRANTOR /** * DistributedLockService that this grantor is granting locks for. */ protected final DLockService dlock; /** * Map of grant tokens for tracking grantor-side state of distributed locks. Key: Object name, * Value: DLockGrantToken grant * * guarded.By grantTokens */ private final Map grantTokens = new HashMap(); /** * Dedicated thread responsible for handling expirations and timeouts. */ protected final DLockGrantorThread thread; /** * The current state of this grantor. Volatile for read access. Mutation must occur while * synchronized on this grantor. * <p> * State starts out as INITIALIZING and moves to READY at which point the grantor will start * servicing requests. * <p> * If a newcomer requests transfer of grantorship, then the state will change to HALTED and then * finally become DESTROYED when transfer is complete. During HALTED, the elder recognizes the * newcomer as the current grantor. * <p> * If this service is shutting down, it will seek out a successor to become the new grantor. * During this phase, the state will be PENDING_SHUTDOWN which means no requests or releases will * be replied to until a successor is found, registers transfer intentions with the elder and * sends this process a transfer grantorship request. At that point the state will be HALTED as * described above. * * guarded.By this */ private volatile int state = INITIALIZING; /** * ReadWriteLock to protect in-progress operations from destroying service. */ private final StoppableReentrantReadWriteLock destroyLock; /** * Specialized lock information for Transaction lock batches. * <p> * Key: Object batchId, Value: DLockBatch batch * <p> * Handling of batch locks synchronizes on this to assure serial processing. * * guarded.By batchLocks */ private final Map batchLocks = new HashMap(); /** * Handles special lock-reservation type for transactions. */ private final TXReservationMgr resMgr = new TXReservationMgr(false); /** * Enforces waiting until this grantor is initialized. Used to block all lock requests until * INITIALIZED. */ private final StoppableCountDownLatch whileInitializing; /** * Enforces waiting until this grantor is destroyed. Used to block all lock requests while * destroying. Latch opens after state becomes DESTROYED and grantor begins replying with * NOT_GRANTOR. */ private final StoppableCountDownLatch untilDestroyed; /** * If -1 then it has not yet been fetched from elder. Otherwise it is the versionId that the elder * gave us. During explicit becomeGrantor, the value is -1, and then the elder provides a real * versionId. */ private final AtomicLong versionId = new AtomicLong(-1); /** * Used to verify that requestor member is still in view when granting. */ protected final DM dm; // ------------------------------------------------------------------------- // SuspendLocking state (BEGIN) /** * Synchronization for protecting all SuspendLocking state. Guards all suspend locking state. */ protected final Object suspendLock = new Object(); /** * Identifies remote thread that has currently suspended locking or null. * * Concurrency: protected by synchronization of {@link #suspendLock} */ protected RemoteThread lockingSuspendedBy = null; /** * Value indicates nonexistent lock */ protected static final int INVALID_LOCK_ID = -1; /** * Identifies the lockId used by the remote thread to suspend locking. * * Concurrency: protected by synchronization of {@link #suspendLock} */ protected int suspendedLockId = INVALID_LOCK_ID; /** * FIFO queue for suspend read and write lock waiters. * * guarded.By {@link #suspendLock} */ private final LinkedList suspendQueue = new LinkedList(); /** * Map of read lock counts for each RemoteThread currently holding locks. * <p> * Key=RemoteThread, Value=ReadLockCount * * guarded.By {@link #suspendLock} */ private final HashMap readLockCountMap = new HashMap(); /** * Number of suspend waiters waiting for write lock. * * guarded.By {@link #suspendLock} */ private int writeLockWaiters = 0; /** * Total number of read locks held against suspend write lock. * * guarded.By {@link #suspendLock} */ private int totalReadLockCount = 0; /** * List of next requests to process after handling an unlock or resume. * * guarded.By {@link #suspendLock} */ private ArrayList permittedRequests = new ArrayList(); /** * List of active drains of permittedRequests. TODO: does this need to be a synchronizedList? * * guarded.By {@link #suspendLock} */ private final List permittedRequestsDrain = Collections.synchronizedList(new LinkedList()); // SuspendLocking state (END) // ------------------------------------------------------------------------- // ------------------------------------------------------------------------- // Static methods // ------------------------------------------------------------------------- /** * Creates new instance of grantor for the lock service. * * @param dlock the lock service the grantor is authority for * @param versionId the version, from the elder, of this grantor * @return new instance of grantor for the lock service */ static DLockGrantor createGrantor(DLockService dlock, long versionId) { return new DLockGrantor(dlock, versionId); } /** * Gets the local instance of grantor for the lock service if one exists. * * @param dlock the lock service the grantor is authority for * @return instance of grantor for the lock service or null if no local grantor has been created */ static DLockGrantor getGrantorForService(DLockService dlock) { if (dlock == null) return null; return dlock.getGrantor(); } /** * Returns instance of DLockGrantor that will handle distributed lock granting for specified * service. * <p> * Returns null if unable to get ready grantor or if this process is not the grantor for the * service. * * @param svc the lock service to return the grantor instance for */ public static DLockGrantor waitForGrantor(DLockService svc) throws InterruptedException { if (svc == null) return null; // now we need to get the grantor and make sure it's ready... DLockGrantor oldGrantor = null; DLockGrantor grantor = getGrantorForService(svc); do { if (grantor == null || grantor.isDestroyed()) return null; grantor.waitWhileInitializing(); if (svc.isDestroyed()) return null; // make sure we are lock grantor if (!svc.isCurrentlyOrIsMakingLockGrantor()) return null; if (!grantor.isReady()) return null; // Now make sure the service still has this guy as its grantor oldGrantor = grantor; grantor = getGrantorForService(svc); } while (oldGrantor != grantor); return grantor; } // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- /** * Creates instance of grantor for the lock service. * * @param dlock the lock service the grantor is authority for * @param vId unique id that the elder increments for each new grantor */ private DLockGrantor(DLockService dlock, long vId) { this.dm = dlock.getDistributionManager(); CancelCriterion stopper = this.dm.getCancelCriterion(); this.whileInitializing = new StoppableCountDownLatch(stopper, 1); this.untilDestroyed = new StoppableCountDownLatch(stopper, 1); this.dlock = dlock; this.destroyLock = new StoppableReentrantReadWriteLock(stopper); this.versionId.set(vId); this.dm.addMembershipListener(this.membershipListener); this.thread = new DLockGrantorThread(this, stopper); this.dlock.getStats().incGrantors(1); } // ------------------------------------------------------------------------- // Public and package methods // ------------------------------------------------------------------------- /** * Returns the grantor's version id which was assigned by an elder. Required to help uniquely * identify this grantor instance. * * @return the grantor's version id which was assigned by an elder */ public long getVersionId() { return this.versionId.get(); } /** * Sets the version id after the elder tells us what it is. This is called during the explicit * become grantor process. * * @param v the elder assigned version id for this grantor */ public void setVersionId(long v) { this.versionId.set(v); } /** * Waits uninterruptibly while this grantor is initializing. Returns when grantor is ready to * handle lock requests. * * @throws DistributedSystemDisconnectedException if system shuts down before grantor is ready */ public void waitWhileInitializing() throws InterruptedException { boolean interrupted = Thread.interrupted(); try { if (interrupted && this.dlock.isInterruptibleLockRequest()) { throw new InterruptedException(); } while (true) { try { this.whileInitializing.await(); break; } catch (InterruptedException e) { interrupted = true; throwIfInterruptible(e); } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } /** * Waits uninterruptibly until this service is destroyed. Returns when grantor has been completely * destroyed. * * @throws DistributedSystemDisconnectedException if system shuts down before grantor is destroyed */ public void waitUntilDestroyed() throws InterruptedException { while (true) { boolean interrupted = Thread.interrupted(); try { this.untilDestroyed.await(); break; } catch (InterruptedException e) { interrupted = true; throwIfInterruptible(e); } finally { if (interrupted) Thread.currentThread().interrupt(); } } } @Override public String toString() { StringBuffer buffer = new StringBuffer(128); buffer.append('<').append("DLockGrantor").append("@") .append(Integer.toHexString(System.identityHashCode(this))).append(" state=") .append(stateToString(this.state)).append(" name=").append(this.dlock.getName()) .append(" version=").append(this.getVersionId()).append('>'); return buffer.toString(); } /** * Returns true if this grantor is ready to handle lock requests. * * @return true if this grantor is ready to handle lock requests */ boolean isReady() { return this.state == READY; } /** * Returns true if this grantor is still initializing and not yet ready for lock requests. * * @return true if this grantor is still initializing */ boolean isInitializing() { return this.state == INITIALIZING; } /** * Returns true if this grantor has been destroyed. * * @return true if this grantor has been destroyed */ public boolean isDestroyed() { return this.state == DESTROYED; } /** * Throws LockGrantorDestroyedException if this grantor has been destroyed. * * @throws LockGrantorDestroyedException if grantor is destroyed */ void checkDestroyed() { throwIfDestroyed(isDestroyed()); } /** * Throws LockGrantorDestroyedException if destroyed is true. * * @param destroyed if true then throw LockGrantorDestroyedException * @throws LockGrantorDestroyedException if destroyed is true */ private void throwIfDestroyed(boolean destroyed) { if (destroyed) { throw new LockGrantorDestroyedException( LocalizedStrings.DLockGrantor_GRANTOR_IS_DESTROYED.toLocalizedString()); } } /** * Handles request for a batch of locks using optimization for transactions. * <p> * Synchronizes on {@link #batchLocks}. * * @throws LockGrantorDestroyedException if grantor is destroyed */ void handleLockBatch(DLockRequestMessage request) throws InterruptedException { synchronized (this.batchLocks) { // assures serial processing waitWhileInitializing(); // calcWaitMillisFromNow if (request.checkForTimeout()) { cleanupSuspendState(request); return; } final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleLockBatch]"); } if (!acquireDestroyReadLock(0)) { waitUntilDestroyed(); checkDestroyed(); } try { checkDestroyed(); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleLockBatch] request: {}", request); } DLockBatch batch = (DLockBatch) request.getObjectName(); this.resMgr.makeReservation((IdentityArrayList) batch.getReqs()); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleLockBatch] granting {}", batch.getBatchId()); } this.batchLocks.put(batch.getBatchId(), batch); request.respondWithGrant(Long.MAX_VALUE); // // try-lock every lock in batch... // Object name = null; // Set lockNames = batch.getLockNames(); // Set acquiredLocks = new HashSet(); // long leaseExpireTime = -1; // for (Iterator iter = lockNames.iterator(); iter.hasNext();) { // name = iter.next(); // DLockGrantToken grant = getOrCreateGrant( // this.dlock.getOrCreateToken(name)); // // calc lease expire time just once... // if (leaseExpireTime == -1) { // leaseExpireTime = grant.calcLeaseExpireTime(request.getLeaseTime()); // } // // try to grant immediately else fail... // if (grant.grantBatchLock(request.getSender(), leaseExpireTime)) { // acquiredLocks.add(grant); // } else { // // fail out and release all.. // break; // } // } // for-loop // if (acquiredLocks.size() == lockNames.size()) { // // got the locks! // logFine("[DLockGrantor.handleLockBatch] granting " + // batch.getBatchId() + "; leaseExpireTime=" + leaseExpireTime); // // save the batch for later release... // this.batchLocks.put(batch.getBatchId(), batch); // request.respondWithGrant(leaseExpireTime); // } // else { // // failed... release them all... // for (Iterator iter = acquiredLocks.iterator(); iter.hasNext();) { // DLockGrantToken grant = (DLockGrantToken) iter.next(); // grant.release(); // } // request.respondWithTryLockFailed(name); // } } catch (CommitConflictException ex) { request.respondWithTryLockFailed(ex.getMessage()); } finally { releaseDestroyReadLock(); } } } /** * Returns transaction optimized lock batches that were created by the specified owner. * <p> * Synchronizes on batchLocks. * * @param owner member that owned the lock batches to return * @return lock batches that were created by owner */ public DLockBatch[] getLockBatches(InternalDistributedMember owner) { // Key: Object batchId, Value: DLockBatch batch synchronized (this.batchLocks) { List batchList = new ArrayList(); for (Iterator iter = this.batchLocks.values().iterator(); iter.hasNext();) { DLockBatch batch = (DLockBatch) iter.next(); if (batch.getOwner().equals(owner)) { batchList.add(batch); } } return (DLockBatch[]) batchList.toArray(new DLockBatch[batchList.size()]); } } /** * Get the batch for the given batchId (for example use a txLockId from TXLockBatch in order to * update its participants). This operation was added as part of the solution to bug 32999. * <p> * Acquires acquireDestroyReadLock. Synchronizes on batchLocks. * <p> * see org.apache.geode.internal.cache.TXCommitMessage#updateLockMembers() * * @param batchId the identifier for the batch to retrieve * @return the transaction lock batch identified by the given batchId * @see org.apache.geode.internal.cache.locks.TXLockUpdateParticipantsMessage * @see org.apache.geode.internal.cache.locks.TXLockBatch#getBatchId() */ public DLockBatch getLockBatch(Object batchId) throws InterruptedException { DLockBatch ret = null; final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.getLockBatch] enter: {}", batchId); } synchronized (this.batchLocks) { waitWhileInitializing(); if (!acquireDestroyReadLock(0)) { waitUntilDestroyed(); checkDestroyed(); } try { checkDestroyed(); ret = (DLockBatch) this.batchLocks.get(batchId); } finally { releaseDestroyReadLock(); } } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.getLockBatch] exit: {}", batchId); } return ret; } /** * Update the batch for the given batch. This operation was added as part of the solution to bug * 32999. * <p> * Acquires acquireDestroyReadLock. Synchronizes on batchLocks. * <p> * see org.apache.geode.internal.cache.locks.TXCommitMessage#updateLockMembers() * * @param batchId the identify of the transaction lock batch * @param newBatch the new lock batch to be used * @see org.apache.geode.internal.cache.locks.TXLockUpdateParticipantsMessage * @see org.apache.geode.internal.cache.locks.TXLockBatch#getBatchId() */ public void updateLockBatch(Object batchId, DLockBatch newBatch) throws InterruptedException { final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.updateLockBatch] enter: {}", batchId); } synchronized (this.batchLocks) { waitWhileInitializing(); if (!acquireDestroyReadLock(0)) { waitUntilDestroyed(); checkDestroyed(); } try { checkDestroyed(); final DLockBatch oldBatch = (DLockBatch) this.batchLocks.get(batchId); if (oldBatch != null) { this.batchLocks.put(batchId, newBatch); } } finally { releaseDestroyReadLock(); } } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.updateLockBatch] exit: {}", batchId); } } /** * Releases the transaction optimized lock batch. * <p> * Acquires acquireDestroyReadLock. Synchronizes on batchLocks. * * @param batchId the identify of the transaction lock batch to release * @param owner the member that has created and locked the lock batch * @throws LockGrantorDestroyedException if grantor is destroyed or interrupted */ public void releaseLockBatch(Object batchId, InternalDistributedMember owner) throws InterruptedException { if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantor.releaseLockBatch]"); } synchronized (this.batchLocks) { waitWhileInitializing(); if (!acquireDestroyReadLock(0)) { waitUntilDestroyed(); checkDestroyed(); } try { checkDestroyed(); DLockBatch batch = (DLockBatch) this.batchLocks.remove(batchId); if (batch != null) { this.resMgr.releaseReservation((IdentityArrayList) batch.getReqs()); } // Set lockNames = batch.getLockNames(); // for (Iterator iter = lockNames.iterator(); iter.hasNext();) { // Object name = iter.next(); // DLockGrantToken grant = getOrCreateGrant( // this.dlock.getOrCreateToken(name)); // grant.releaseIfLockedBy(owner); // } } finally { releaseDestroyReadLock(); } } } /** * Returns true if the request comes from the local member. * * @param request the lock request to check * @return true if the request comes from the local member */ private boolean isLocalRequest(DLockRequestMessage request) { return request.getSender().equals(this.dlock.getDistributionManager().getId()); } /** * TEST HOOK: Allows testing to determine if there are waiting requests for a lock. * <p> * Synchronizes on grantTokens and the grant token if one exists. * * @param name the lock to check for waiting requests for * @return true if the named lock has requests waiting to acquire it */ boolean hasWaitingRequests(Object name) { DLockGrantToken grant = getGrantToken(name); if (grant == null) return false; synchronized (grant) { return grant.hasWaitingRequests(); } } /** * Handles a DLockQueryMessage. Returns DLockGrantToken for the lock or null. * <p> * Acquires destroyReadLock. Synchronizes on grantTokens. * * @param query the dlock query message to handle * @return DLockGrantToken for the lock or null */ DLockGrantToken handleLockQuery(DLockQueryMessage query) throws InterruptedException { if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleLockQuery] {}", query); } if (acquireDestroyReadLock(0)) { try { checkDestroyed(); return getGrantToken(query.objectName); } finally { releaseDestroyReadLock(); } } return null; } /** * Handles the provided lock request. The lock will either be granted, refused if try-lock, or * scheduled at end of waiting queue to eventually be granted or timed out. * <p> * Acquires destroyReadLock. Synchronizes on grantTokens, suspendLock and the grant token. * * @param request the lock request to be processed by this grantor * @throws LockGrantorDestroyedException if grantor is destroyed */ void handleLockRequest(DLockRequestMessage request) throws InterruptedException { Assert.assertTrue(request.getRemoteThread() != null); if (request.getObjectName() instanceof DLockBatch) { handleLockBatch(request); return; } waitWhileInitializing(); // calcWaitMillisFromNow final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleLockRequest] {}", request); } if (!acquireDestroyReadLock(0)) { if (isLocalRequest(request) && this.dlock.isDestroyed()) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleLockRequest] about to throwIfDestroyed"); } // this special case is one fix for deadlock between waitUntilDestroyed // and dlock waitForGrantorCallsInProgress (when request is local) throwIfDestroyed(true); } else { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleLockRequest] about to waitUntilDestroyed"); } // is there still a deadlock when an explicit become is destroying // this grantor instead of destroying the dlock service? waitUntilDestroyed(); checkDestroyed(); } } try { checkDestroyed(); if (acquireLockPermission(request)) { handlePermittedLockRequest(request); } else { // request has been added to suspendQueue for deferred handling } } finally { releaseDestroyReadLock(); } } /** * Internally handles a lock request which has permission to proceed. * <p> * Calling thread must hold destroyReadLock. Synchronizes on grantTokens, suspendLock and the * grant token. * * @param request the lock request to be processed by this grantor guarded.By * {@link #acquireDestroyReadLock(long)} */ private void handlePermittedLockRequest(final DLockRequestMessage request) { if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantor.handlePermittedLockRequest] {}", request); } Assert.assertTrue(request.getRemoteThread() != null); DLockGrantToken grant = getOrCreateGrant(request.getObjectName()); try { // try to grant immediately if not currently granted... if (grant.grantLockToRequest(request)) { // do nothing } // if request was local and then interrupted/released... else if (request.responded()) { // do nothing } // if request was a failed try-lock... else if (request.isTryLock()) { cleanupSuspendState(request); request.respondWithTryLockFailed(request.getObjectName()); } // if request has timed out... else if (request.checkForTimeout()) { cleanupSuspendState(request); } // schedule into waiting queue for eventual granting... else { grant.schedule(request); this.thread.checkTimeToWait(calcWaitMillisFromNow(request), false); } } finally { grant.decAccess(); } } /** * Initializes this new grantor with previously held locks as provided during grantor recovery. * <p> * Acquires destroyReadLock. Synchronizes on this grantor, grantTokens, suspendLock, the grant * token. * * @param owner the member that owns the tokens to be scheduled * @param tokens set of DLockRemoteTokens to be scheduled for owner */ void initializeHeldLocks(InternalDistributedMember owner, Set tokens) throws InterruptedException { synchronized (this) { if (isDestroyed()) return; if (!acquireDestroyReadLock(0)) { return; } } try { synchronized (this.grantTokens) { Set members = this.dlock.getDistributionManager().getDistributionManagerIds(); final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); for (Iterator iter = tokens.iterator(); iter.hasNext();) { DLockRemoteToken token = (DLockRemoteToken) iter.next(); DLockGrantToken grantToken = getOrCreateGrant(token.getName()); try { // make sure the token's owner is still in the system if (!members.contains(owner)) { // skipping because member is no longer in view if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "Initialization of held locks is skipping {} because owner {} is not in view: ", token, owner, members); } continue; } RemoteThread rThread = null; boolean isSuspendLock = false; int lockId = -1; synchronized (grantToken) { if (grantToken.isLeaseHeld()) { logger.error(LogMarker.DLS, LocalizedMessage.create( LocalizedStrings.DLockGrantor_INITIALIZATION_OF_HELD_LOCKS_IS_SKIPPING_0_BECAUSE_LOCK_IS_ALREADY_HELD_1, new Object[] {token, grantToken})); continue; } grantToken.grantLock(owner, token.getLeaseExpireTime(), token.getLeaseId(), token.getLesseeThread()); // grantToken may have already expired or is about to expire // complete initialization but make sure grantor thread will wake // up and expire it as soon as it's running if (grantToken.getLeaseExpireTime() > -1 && grantToken.getLeaseExpireTime() < Long.MAX_VALUE) { long now = DLockService.getLockTimeStamp(this.dm); this.thread.checkTimeToWait(grantToken.getLeaseExpireTime() - now, true); } rThread = grantToken.getRemoteThread(); isSuspendLock = grantToken.isSuspendLockingToken(); lockId = grantToken.getLockId(); } // update the readLock and suspendLocking states... synchronized (suspendLock) { if (isSuspendLock) { suspendLocking(rThread, lockId); } else { Assert.assertTrue(!isLockingSuspended() || isLockingSuspendedBy(rThread), "Locking is suspended by a different thread: " + token); Integer integer = (Integer) readLockCountMap.get(rThread); int readLockCount = integer == null ? 0 : integer.intValue(); readLockCount++; readLockCountMap.put(rThread, Integer.valueOf(readLockCount)); totalReadLockCount++; checkTotalReadLockCount(); } } // suspendLock sync } finally { grantToken.decAccess(); } } // tokens iter } // grantTokens sync return; } finally { releaseDestroyReadLock(); } } /** * Handles a request for extending the lease time of an already held lock. * <p> * Acquires destroyReadLock. Synchronizes on grantTokens and the grant token. * * @param request the lock request to be reentered for lease extension * @return new extended leaseExpireTime or 0 if requestor no longer holds lock */ long reenterLock(DLockRequestMessage request) throws InterruptedException { waitWhileInitializing(); // calcWaitMillisFromNow if (!acquireDestroyReadLock(0)) { waitUntilDestroyed(); checkDestroyed(); } try { checkDestroyed(); // to fix GEODE-678 no longer call request.checkForTimeout DLockGrantToken grant = getGrantToken(request.getObjectName()); if (grant == null) { if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantor.reenterLock] no grantToken found for {}", request.getObjectName()); } return 0; } synchronized (grant) { // synchronize against grant.expireAndGrantLock if (!this.dm.isCurrentMember(request.getSender()) || grant.isDestroyed()) { return 0; } if (!grant.isLockedBy(request.getSender(), request.getLockId())) { if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantor.reenterLock] grant is not locked by sender={} lockId={} grant={}", request.getSender(), request.getLockId(), grant); } return 0; } long leaseExpireTime = Math.max(grant.getLeaseExpireTime(), grant.calcLeaseExpireTime(request.getLeaseTime())); grant.grantLock(request.getSender(), leaseExpireTime, request.getLockId(), grant.getRemoteThread()); return grant.getLeaseExpireTime(); } } finally { releaseDestroyReadLock(); } } /** * Release named lock if held by owner using lockId. Called from DLockReleaseMessage.basicProcess * for remote unlock. * <p> * Acquires destroyReadLock. Synchronizes on grantTokens and the grant token. * * @param name the name of the lock to release * @param owner the member releasing the lock * @param lockId the identity of the lease used by the owner * @throws LockGrantorDestroyedException if grantor is destroyed */ void releaseIfLocked(Object name, InternalDistributedMember owner, int lockId) throws InterruptedException { waitWhileInitializing(); if (!acquireDestroyReadLock(0)) { waitUntilDestroyed(); checkDestroyed(); } try { checkDestroyed(); getAndReleaseGrantIfLockedBy(name, owner, lockId); } finally { releaseDestroyReadLock(); } } /** * Fetches the actual grant token and releases it if leased by owner using lockId. * DLockReleaseMessage.basicProcess -> releaseIfLocked -> getAndReleaseGrantIfLockedBy * <p> * Caller must hold destroyReadLock. Synchronizes on grantTokens and the grant token. * * @param name the name of the lock to release * @param owner the member attempting to release the granted lock * @param lockId the id of the lease used by the owner guarded.By * {@link #acquireDestroyReadLock(long)} */ private void getAndReleaseGrantIfLockedBy(Object name, InternalDistributedMember owner, int lockId) { synchronized (this.grantTokens) { DLockGrantToken grantToken = basicGetGrantToken(name); if (grantToken != null) { // checking isTokenDestroyed here will deadlock synchronized (grantToken) { // if (!grantToken.isTokenDestroyed()) try { grantToken.releaseIfLockedBy(owner, lockId); removeGrantIfUnused(grantToken); } catch (IllegalStateException e) { this.dlock.checkDestroyed(); checkDestroyed(); // must have hit race... grantor doesn't have the token return; } } } } } /** * Fetches the grant token for named lock and attempts to grant it to the next waiting requestor * if one exists. Called from DLockReleaseProcessor when another process releases a lock and after * the reply has been sent. * <p> * Acquires destroyReadLock. Synchronizes on grantTokens and the grant token. * * @param name the name of the lock to grant * @throws LockGrantorDestroyedException if grantor is destroyed */ void grantLock(Object name) throws InterruptedException { waitWhileInitializing(); if (!acquireDestroyReadLock(0)) { waitUntilDestroyed(); checkDestroyed(); } try { checkDestroyed(); DLockGrantToken grant = getGrantToken(name); if (grant != null) { removeGrantIfUnused(grant); } } finally { releaseDestroyReadLock(); } } /** * Handles the departure of a member by releasing every lock it owned. * <p> * Acquires destroyReadLock. Synchronizes on grantTokens, suspendLock, and the grant token. * * @param owner the member that departed */ void handleDepartureOf(InternalDistributedMember owner) throws InterruptedException { // bug 32657 has another cause in this method... interrupted thread from // connection/channel layer caused acquireDestroyReadLock to fail... // fixed by Darrel in org.apache.geode.internal.tcp.Connection final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (acquireDestroyReadLock(0)) { try { if (isDestroyed()) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleDepartureOf] grantor is destroyed; ignoring {}", owner); } return; } try { DLockLessorDepartureHandler handler = this.dlock.getDLockLessorDepartureHandler(); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.handleDepartureOf] handler = {}", handler); } if (handler != null) { handler.handleDepartureOf(owner, this); } } catch (CancelException e) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DlockGrantor.handleDepartureOf] ignored cancellation (1)"); } } finally { synchronized (this.suspendLock) { HashSet removals = new HashSet(); for (Iterator it = readLockCountMap.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); RemoteThread rThread = (RemoteThread) entry.getKey(); if (rThread.getDistributedMember().equals(owner)) { removals.add(rThread); } } for (Iterator it = removals.iterator(); it.hasNext();) { try { postReleaseLock((RemoteThread) it.next(), null); } catch (CancelException e) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DlockGrantor.handleDepartureOf] ignored cancellation (2)"); } } } } // synchronized synchronized (this.grantTokens) { // do not call handleDepartureOf while iterating grantTokens // changes fix bug 39172 (ConcurrentModificationException) // 1) built up list of grants that reference departed member List grantsReferencingMember = new ArrayList(); Collection grants = this.grantTokens.values(); for (Iterator iter = grants.iterator(); iter.hasNext();) { DLockGrantToken grant = (DLockGrantToken) iter.next(); try { grant.checkDepartureOf(owner, grantsReferencingMember); } catch (CancelException e) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DlockGrantor.handleDepartureOf] ignored cancellation (3)"); } } } // for // 2) call handleDepartureOf on list of grantsReferencingMember ArrayList grantsToRemoveIfUnused = new ArrayList(); for (Iterator iter = grantsReferencingMember.iterator(); iter.hasNext();) { DLockGrantToken grant = (DLockGrantToken) iter.next(); try { grant.handleDepartureOf(owner, grantsToRemoveIfUnused); } catch (CancelException e) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DlockGrantor.handleDepartureOf] ignored cancellation (4)"); } } } // for // 3) remove grants in grantsToRemoveIfUnused list // TODO: if grantsReferencingMember is always empty remove this for (Iterator iter = grantsToRemoveIfUnused.iterator(); iter.hasNext();) { DLockGrantToken grant = (DLockGrantToken) iter.next(); try { removeGrantIfUnused(grant); } catch (CancelException e) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DlockGrantor.handleDepartureOf] ignored cancellation (5)"); } } } // for } // synchronized this.grantTokens } // finally } finally { releaseDestroyReadLock(); } } } /** * Destroys this grantor without attempting to transfer grant tokens to a successor. * <p> * Acquires destroyWriteLock. Synchronizes on this grantor, grantTokens, and each grant token. */ void destroy() { synchronized (this) { if (isDestroyed()) return; final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[simpleDestroy]"); } // wait for the destroy write lock ignoring interrupts... boolean acquired = false; try { boolean locksHeld = false; try { acquireDestroyWriteLock(Long.MAX_VALUE); acquired = true; // check for any held locks... if (isInitializing()) { // assume the worst case and tell the elder that recovery will be required locksHeld = true; } else { synchronized (this.grantTokens) { InternalDistributedMember me = this.dlock.getDistributionManager().getId(); for (Iterator iter = this.grantTokens.values().iterator(); iter.hasNext();) { DLockGrantToken grant = (DLockGrantToken) iter.next(); InternalDistributedMember owner = grant.getOwner(); if (owner != null && !owner.equals(me)) { locksHeld = true; break; } } } } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[simpleDestroy] {} locks held", (locksHeld ? "with" : "without")); } } finally { // make sure the following occurs even if checking locks above failed try { // release latches and change internal state destroyGrantor(); } finally { // tell the elder we are not the grantor anymore this.dlock.clearGrantor(this.getVersionId(), locksHeld); } } } finally { if (acquired) { releaseDestroyWriteLock(); } } } } /** * Send replies to all waiting requestors to notify them that this is no longer the grantor. * <p> * Caller must acquire destroyWriteLock. Synchronizes on suspendLock, grantTokens, and each grant * token. * * guarded.By {@link #acquireDestroyWriteLock(long)} */ private void destroyGrantor() { Assert.assertHoldsLock(this, true); makeDestroyed(); // reply to all pending requests w/ NOT_GRANTOR synchronized (this.grantTokens) { Collection grants = this.grantTokens.values(); for (Iterator iter = grants.iterator(); iter.hasNext();) { DLockGrantToken grant = (DLockGrantToken) iter.next(); try { grant.handleGrantorDestruction(); } // catch (VirtualMachineError err) { // SystemFailure.initiateFailure(err); // // If this ever returns, rethrow the error. We're poisoned // // now, so don't let this thread continue. // throw err; // } // catch (Throwable t) { // // Whenever you catch Error or Throwable, you must also // // catch VirtualMachineError (see above). However, there is // // _still_ a possibility that you are dealing with a cascading // // error condition, so you also need to check to see if the JVM // // is still usable: // SystemFailure.checkFailure(); // } finally { } } } synchronized (suspendLock) { final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.destroyAndRemove] responding to {} permitted requests.", permittedRequests.size()); } respondWithNotGrantor(permittedRequests.iterator()); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.destroyAndRemove] responding to {} requests awaiting permission.", suspendQueue.size()); } respondWithNotGrantor(suspendQueue.iterator()); for (Iterator iter = permittedRequestsDrain.iterator(); iter.hasNext();) { final List drain = (List) iter.next(); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.destroyAndRemove] responding to {} drained permitted requests.", drain.size()); } respondWithNotGrantor(drain.iterator()); } } } /** * Send responses to specified requests informing the senders that this is no longer the grantor. * <p> * Caller must acquire destroyWriteLock. * * @param requests the requests to respond to guarded.By {@link #acquireDestroyWriteLock(long)} */ private void respondWithNotGrantor(Iterator requests) { while (requests.hasNext()) { final DLockRequestMessage request = (DLockRequestMessage) requests.next(); try { request.respondWithNotGrantor(); } // catch (VirtualMachineError err) { // SystemFailure.initiateFailure(err); // // If this ever returns, rethrow the error. We're poisoned // // now, so don't let this thread continue. // throw err; // } // catch (Throwable t) { // // Whenever you catch Error or Throwable, you must also // // catch VirtualMachineError (see above). However, there is // // _still_ a possibility that you are dealing with a cascading // // error condition, so you also need to check to see if the JVM // // is still usable: // SystemFailure.checkFailure(); // } finally { } } } /** * TEST HOOK: Log additional debugging info about this grantor. */ void debug() { logger.info(LogMarker.DLS, LocalizedMessage.create(LocalizedStrings.TESTING, "[DLockGrantor.debug] svc=" + this.dlock.getName() + "; state=" + this.state + "; initLatch.ct=" + this.whileInitializing.getCount())); } /** * Make this grantor ready for handling lock requests. * <p> * Synchronizes on this grantor. * * @param enforceInitializing true if this should assert isInitializing * @return true if grantor was successfully made ready */ synchronized boolean makeReady(boolean enforceInitializing) { if (isDestroyed()) { this.dlock.checkDestroyed(); } if (!enforceInitializing) { if (!isInitializing()) { return false; } } assertInitializing(); if (logger.isTraceEnabled(LogMarker.DLS)) { StringBuffer sb = new StringBuffer("DLockGrantor " + this.dlock.getName() + " initialized with:"); for (Iterator tokens = grantTokens.values().iterator(); tokens.hasNext();) { sb.append("\n\t" + tokens.next()); } logger.trace(LogMarker.DLS, sb.toString()); } this.state = READY; this.whileInitializing.countDown(); this.thread.start(); return true; } // ------------------------------------------------------------------------- // Private methods // ------------------------------------------------------------------------- /** * Drain currently permitted requests and grant lock to next requestor. * <p> * Acquires destroyReadLock. Synchronizes on suspendLock, grantTokens, and the grant token. * * @param objectName the lock to perform post release tasks for */ void postRemoteReleaseLock(Object objectName) throws InterruptedException { if (!acquireDestroyReadLock(0)) { return; } try { checkDestroyed(); drainPermittedRequests(); grantLock(objectName); } catch (LockServiceDestroyedException e) { // ignore... service was destroyed and that's ok } catch (LockGrantorDestroyedException e) { // ignore... grantor was destroyed and that's ok } finally { releaseDestroyReadLock(); } } /** * Acquires a read lock on the destroy ReadWrite lock uninterruptibly using millis for try-lock * attempt. * * @param millis the milliseconds to try to acquire lock within * @return true if destroy read lock was acquired * @throws DistributedSystemDisconnectedException if system has been disconnected */ private boolean acquireDestroyReadLock(long millis) throws InterruptedException { boolean interrupted = Thread.interrupted(); try { if (interrupted && this.dlock.isInterruptibleLockRequest()) { throw new InterruptedException(); } while (true) { try { this.dm.getCancelCriterion().checkCancelInProgress(null); // is this needed? boolean acquired = this.destroyLock.readLock().tryLock(millis); return acquired; } catch (InterruptedException e) { interrupted = true; throwIfInterruptible(e); } } } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } /** * Releases a read lock on the destroy ReadWrite lock. */ private void releaseDestroyReadLock() { this.destroyLock.readLock().unlock(); } /** * Acquires the write lock on the destroy ReadWrite lock within specified millis. * * @param millis the milliseconds to attempt to acquire the lock within * @throws DistributedSystemDisconnectedException if system has been disconnected */ private void acquireDestroyWriteLock(long millis) { for (;;) { boolean interrupted = Thread.interrupted(); try { this.dm.getCancelCriterion().checkCancelInProgress(null); boolean acquired = this.destroyLock.writeLock().tryLock(millis); if (acquired) { return; } } catch (InterruptedException e) { interrupted = true; } finally { if (interrupted) { Thread.currentThread().interrupt(); } } } } /** * Releases the write lock on the destroy ReadWrite lock. */ private void releaseDestroyWriteLock() { this.destroyLock.writeLock().unlock(); } /** * Returns time to wait in millis from now based on start and wait in request. * * @return the current wait time for the request before it times out */ private long calcWaitMillisFromNow(DLockRequestMessage request) { long result = request.getTimeoutTS(); if (result != Long.MAX_VALUE) { long now = DLockService.getLockTimeStamp(this.dlock.getDistributionManager()); result = result - now; } return result; } /** * Shuts down the grantor thread and changes internal state to destroyed. */ private void makeDestroyed() { try { this.thread.shutdown(); this.state = DESTROYED; if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "DLockGrantor {} state is DESTROYED", this.dlock.getName()); } if (this.untilDestroyed.getCount() > 0) { this.untilDestroyed.countDown(); } if (this.whileInitializing.getCount() > 0) { this.whileInitializing.countDown(); } this.dlock.getDistributionManager().removeMembershipListener(this.membershipListener); } finally { this.dlock.getStats().incGrantors(-1); } } /** * Returns a snapshot of the current grant tokens. * <p> * Synchronizes on grantTokens. * * @return a snapshot of the current grant tokens */ protected Collection snapshotGrantTokens() { Collection snapshot = null; synchronized (this.grantTokens) { snapshot = new ArrayList(this.grantTokens.values()); } return snapshot; } /** * Fetches or creates a new grant token for the named lock. * <p> * Synchronizes on grantTokens and the grant token. * * @param name the name of the lock * @return the grant token for the named lock */ private DLockGrantToken getOrCreateGrant(Object name) { DLockGrantToken grantToken = null; synchronized (this.grantTokens) { grantToken = basicGetGrantToken(name); if (grantToken == null) { // checking isTokenDestroyed here will deadlock grantToken = new DLockGrantToken(this.dlock, this, name); grantToken.incAccess(); basicPutGrantToken(grantToken); } else { synchronized (grantToken) { if (grantToken.isDestroyed()) { grantToken = new DLockGrantToken(this.dlock, this, name); grantToken.incAccess(); basicPutGrantToken(grantToken); } else { grantToken.incAccess(); } } } } return grantToken; } /** * TEST HOOK: Returns an unmodifible collection backed by the values of the DLockGrantToken map * for testing purposes only. * <p> * Synchronizes on grantTokens. * * @return unmodifible collection of the grant tokens */ public Collection getGrantTokens() { synchronized (this.grantTokens) { return Collections.unmodifiableCollection(this.grantTokens.values()); } } /** * Remove the grant token if it is unused. * <p> * Synchronizes on grantTokens and the grant token. * * @param grant the grant token to remove */ protected void removeGrantIfUnused(DLockGrantToken grant) { synchronized (this.grantTokens) { synchronized (grant) { if (isDestroyed() || grant.isDestroyed()) { return; } else if (grant.grantLockToNextRequest()) { return; } else if (!grant.isBeingAccessed() && !grant.isGranted(false) && !grant.hasWaitingRequests()) { basicRemoveGrantToken(grant); } } } } /** * Iterates over grants and attempts to remove any that are no longer in use. * <p> * Synchronizes on grantTokens and the grant token. * * @param grants the grants to be checked for removal */ protected void removeUnusedGrants(Iterator grants) { while (grants.hasNext()) { DLockGrantToken grant = (DLockGrantToken) grants.next(); removeGrantIfUnused(grant); } } /** * Returns the DLockGrantToken from grant tokens map stored under the key name. * <p> * Synchronizes on grantTokens. */ public DLockGrantToken getGrantToken(Object name) { synchronized (this.grantTokens) { return basicGetGrantToken(name); } } /** * Fetches the grant token value stored in the map under key name. * <p> * Caller must synchronize on grantTokens * * @param name the key to fetch the grant token value for * @return the grant token stored under key name guarded.By {@link #grantTokens} */ private DLockGrantToken basicGetGrantToken(Object name) { return (DLockGrantToken) this.grantTokens.get(name); } /** * Stores the grant token as a value in the map under the key of its name. * <p> * Caller must synchronize on grantTokens * * @param grantToken the grant token to store in the map guarded.By {@link #grantTokens} */ private void basicPutGrantToken(DLockGrantToken grantToken) { this.grantTokens.put(grantToken.getName(), grantToken); dlock.getStats().incGrantTokens(1); } /** * Removes the grant token from the map. * <p> * Caller must synchronize on grantTokens and then the grantToken. * * @param grantToken the grant token to remove from the map. guarded.By {@link #grantTokens} and * grantToken */ private void basicRemoveGrantToken(DLockGrantToken grantToken) { Object removed = this.grantTokens.remove(grantToken.getName()); // changed to ref token if (removed != null) { Assert.assertTrue(removed == grantToken); grantToken.destroy(); if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantor.basicRemoveGrantToken] removed {}; removed={}", grantToken, removed); } } } /** * Iterates over grants and handles any that have expired. * <p> * Synchronizes on each grant token. * * @param grants the grants to iterate over * @return the next smallest expiration time */ protected long expireAndGrantLocks(Iterator grants) { long smallestExpire = Long.MAX_VALUE; while (grants.hasNext()) { DLockGrantToken grant = (DLockGrantToken) grants.next(); if (grant.isDestroyed()) { continue; } long expire = grant.expireAndGrantLock(); if (expire < smallestExpire) { smallestExpire = expire; } } return smallestExpire; } /** * Iterates over grants and handles any that have timed out. * <p> * Synchronizes on each grant token. * * @param grants the grants to iterate over * @return the next smallest timeout */ protected long handleRequestTimeouts(Iterator grants) { long smallestTimeout = Long.MAX_VALUE; while (grants.hasNext()) { DLockGrantToken grant = (DLockGrantToken) grants.next(); if (grant.isDestroyed()) { continue; } long timeout = grant.handleRequestTimeouts(); if (timeout < smallestTimeout) { smallestTimeout = timeout; } } return smallestTimeout; } /** * TEST HOOK: Specifies time to sleep while handling suspend in order to cause a timeout. * <p> * Synchronizes on suspendLock. * * @param value */ public void setDebugHandleSuspendTimeouts(int value) { synchronized (suspendLock) { debugHandleSuspendTimeouts = value; } } /** * True to enable test hook to sleep while handling suspend to cause timeout. * * guarded.By {@link #suspendLock} */ private int debugHandleSuspendTimeouts = 0; /** * Iterates through a copy of suspendQueue and handles any requests that have timed out. * <p> * Synchronizes on suspendLock. * * @return the next smallest timeout in the suspendQueue */ protected long handleSuspendTimeouts() { long smallestTimeout = Long.MAX_VALUE; synchronized (suspendLock) { if (suspendQueue.isEmpty()) return smallestTimeout; if (isDestroyed()) return smallestTimeout; } List timeouts = new ArrayList(); List copySuspendQueue = null; synchronized (suspendLock) { copySuspendQueue = new ArrayList(suspendQueue); } for (Iterator iter = copySuspendQueue.iterator(); iter.hasNext();) { DLockRequestMessage req = (DLockRequestMessage) iter.next(); if (req.checkForTimeout()) { // sends DLockResponseMessage if timeout cleanupSuspendState(req); timeouts.add(req); } else { long timeout = req.getTimeoutTS(); if (timeout < smallestTimeout) { smallestTimeout = timeout; } } } int localDebugHandleSuspendTimeouts = 0; synchronized (suspendLock) { localDebugHandleSuspendTimeouts = debugHandleSuspendTimeouts; } if (localDebugHandleSuspendTimeouts > 0) { try { logger.info(LogMarker.DLS, LocalizedMessage.create( LocalizedStrings.DLockGrantor_DEBUGHANDLESUSPENDTIMEOUTS_SLEEPING_FOR__0, localDebugHandleSuspendTimeouts)); Thread.sleep(localDebugHandleSuspendTimeouts); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if (!timeouts.isEmpty()) { synchronized (suspendLock) { if (writeLockWaiters > 0) { // suspenders exist... must iterate through for safe removal for (Iterator iter = timeouts.iterator(); iter.hasNext();) { DLockRequestMessage req = (DLockRequestMessage) iter.next(); // attempt to remove timed out req from suspendQueue if (suspendQueue.remove(req)) { // request was still in suspendQueue, so check if suspender if (req.isSuspendLockingRequest()) { writeLockWaiters--; } } } // for } else { // no suspenders so safe to removeAll Assert.assertTrue(writeLockWaiters == 0, "Grantor state writeLockWaiters changed while holding suspendLock"); suspendQueue.removeAll(timeouts); } checkWriteLockWaiters(); } // synchronized } return smallestTimeout; } /** * Returns string representation for the enumerated grantor state. * * @param stateInt the number of the state to return a string for * @return string representation for the enumerated grantor state */ private String stateToString(int stateInt) { String stateDesc = null; switch (stateInt) { case INITIALIZING: stateDesc = "INITIALIZING"; break; case READY: stateDesc = "READY"; break; case DESTROYED: stateDesc = "DESTROYED"; break; default: stateDesc = null; break; } if (stateDesc == null) { throw new IllegalArgumentException(LocalizedStrings.DLockGrantor_UNKNOWN_STATE_FOR_GRANTOR_0 .toLocalizedString(Integer.valueOf(state))); } return stateDesc; } /** * Throws IllegalStateException if this grantor is not still initializing. * * @throws IllegalStateException if this grantor is not still initializing */ private void assertInitializing() { if (this.state != INITIALIZING) { String stateDesc = stateToString(this.state); throw new IllegalStateException( LocalizedStrings.DLockGrantor_DLOCKGRANTOR_OPERATION_ONLY_ALLOWED_WHEN_INITIALIZING_NOT_0 .toLocalizedString(stateDesc)); } } /** * Suspends locking by the remote thread and lease id. * <p> * Caller must synchronize on suspendLock. * * Concurrency: protected by synchronization of {@link #suspendLock} * * @param myRThread the remote thread that is about to suspend locking * @param lockId the id of the lock request used to suspend locking */ protected void suspendLocking(final RemoteThread myRThread, final int lockId) { if (DEBUG_SUSPEND_LOCK) { Assert.assertHoldsLock(this.suspendLock, true); } if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "Suspend locking of {} by {} with lockId of {}", this.dlock, myRThread, lockId); } Assert.assertTrue(myRThread != null, "Attempted to suspend locking for null RemoteThread"); Assert.assertTrue(this.lockingSuspendedBy == null || this.lockingSuspendedBy.equals(myRThread), "Attempted to suspend locking for " + myRThread + " but locking is already suspended by " + this.lockingSuspendedBy); // KIRK: assert fails in bug 37945 this.suspendedLockId = lockId; this.lockingSuspendedBy = myRThread; } /** * Resume locking after it has been suspended. * <p> * Caller must synchronize on suspendLock. */ private void resumeLocking() { if (DEBUG_SUSPEND_LOCK) { Assert.assertHoldsLock(this.suspendLock, true); } if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "Resume locking of {}", this.dlock); } this.lockingSuspendedBy = null; this.suspendedLockId = INVALID_LOCK_ID; } /** * Returns true if locking has been suspended. * <p> * Caller must synchronize on suspendLock. * * Concurrency: protected by synchronization of {@link #suspendLock} * * @return true if locking has been suspended */ protected boolean isLockingSuspended() { if (DEBUG_SUSPEND_LOCK) { Assert.assertHoldsLock(this.suspendLock, true); } return this.lockingSuspendedBy != null; } /** * Returns true if locking has been suspended. * <p> * Synchronizes on suspendLock. * * @return true if locking has been suspended */ protected boolean isLockingSuspendedWithSync() { synchronized (this.suspendLock) { return this.lockingSuspendedBy != null; } } /** * Returns true if locking has been suspended by the remote thread. * <p> * Caller must synchronize on suspendLock. * * Concurrency: protected by synchronization of {@link #suspendLock} * * @return true if locking has been suspended by the remote thread */ protected boolean isLockingSuspendedBy(final RemoteThread rThread) { if (DEBUG_SUSPEND_LOCK) { Assert.assertHoldsLock(this.suspendLock, true); } if (rThread == null) return false; return rThread.equals(this.lockingSuspendedBy); } String displayStatus(RemoteThread rThread, Object name) { StringBuffer sb = new StringBuffer(); synchronized (this.suspendLock) { sb.append(' '); sb.append(this.toString()); sb.append(" id=" + this.hashCode()); sb.append(" rThread=" + rThread); if (name != null) { sb.append(" name=" + name); } sb.append(" permittedRequests (" + permittedRequests.size() + ")=" + permittedRequests.toString() + ""); sb.append(" suspendedLockId = " + suspendedLockId); sb.append(" lockingSuspendedBy = " + lockingSuspendedBy); sb.append(" writeLockWaiters = " + writeLockWaiters); sb.append(" totalReadLockCount = " + totalReadLockCount); sb.append("\nsuspendQueue (" + suspendQueue.size() + ")=" + suspendQueue.toString()); // Kirk said it was ok to not log the list of readLockers to cut // down on how much logging is done at fine level. sb.append("\nreadLockers (" + readLockCountMap.size() + ")" /* + "=" + readLockCountMap.toString() */); } return sb.toString(); } /** * guarded.By {@link #suspendLock} */ private void postReleaseSuspendLock(RemoteThread rThread, Object lock) { if (!isLockingSuspendedBy(rThread)) { // hit bug related to 35749 if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[postReleaseSuspendLock] locking is no longer suspended by {}", rThread); } return; } boolean resume = true; Integer integer = (Integer) readLockCountMap.get(rThread); int readLockCount = integer == null ? 0 : integer.intValue(); if (readLockCount == 0 && !suspendQueue.isEmpty()) { final DLockRequestMessage nextRequest = (DLockRequestMessage) suspendQueue.getFirst(); if (nextRequest.isSuspendLockingRequest()) { resume = false; // final RemoteThread myRemoteThread = nextRequest.getRemoteThread(); // hand-off suspendLocking while under sync... resumeLocking(); suspendLocking(nextRequest.getRemoteThread(), nextRequest.getLockId()); permittedRequests.add(suspendQueue.removeFirst()); writeLockWaiters--; checkWriteLockWaiters(); } } if (resume) { resumeLocking(); // drain readLocks from suspendQueue into permittedRequests queue while (!suspendQueue.isEmpty()) { final DLockRequestMessage nextRequest = (DLockRequestMessage) suspendQueue.getFirst(); if (nextRequest.isSuspendLockingRequest()) { Assert.assertTrue(writeLockWaiters > 0, "SuspendLocking request is waiting but writeLockWaiters is 0"); break; } RemoteThread nextRThread = nextRequest.getRemoteThread(); integer = (Integer) readLockCountMap.get(nextRThread); readLockCount = integer == null ? 0 : integer.intValue(); readLockCount++; readLockCountMap.put(nextRThread, Integer.valueOf(readLockCount)); totalReadLockCount++; checkTotalReadLockCount(); permittedRequests.add(suspendQueue.removeFirst()); } } if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[postReleaseSuspendLock] new status {}", displayStatus(rThread, null)); } } /** * guarded.By {@link #suspendLock} */ private void postReleaseReadLock(RemoteThread rThread, Object lock) { final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); // handle release of regular lock // boolean permitSuspend = false; Integer integer = (Integer) readLockCountMap.get(rThread); int readLockCount = integer == null ? 0 : integer.intValue(); // Assert.assertTrue(readLockCount > 0, rThread + " not found in " + readLockCountMap); // KIRK if (readLockCount < 1) { // hit bug 35749 if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[postReleaseReadLock] no locks are currently held by {}", rThread); } return; } readLockCount--; if (readLockCount == 0) { readLockCountMap.remove(rThread); } else { readLockCountMap.put(rThread, Integer.valueOf(readLockCount)); } totalReadLockCount--; if (totalReadLockCount < 0) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "Total readlock count has dropped to {} for {}", totalReadLockCount, this); } } if (totalReadLockCount == 0 && !suspendQueue.isEmpty()) { final DLockRequestMessage nextRequest = (DLockRequestMessage) suspendQueue.getFirst(); if (nextRequest.isSuspendLockingRequest()) { suspendLocking(nextRequest.getRemoteThread(), nextRequest.getLockId()); writeLockWaiters--; permittedRequests.add(suspendQueue.removeFirst()); checkWriteLockWaiters(); } else { String s = new StringBuilder("\n (readLockCount=").append(readLockCount) .append(", totalReadLockCount=").append(totalReadLockCount) .append(", writeLockWaiters=").append(writeLockWaiters).append(",\nsuspendQueue=") .append(suspendQueue).append(",\npermittedRequests=").append(permittedRequests) .toString(); logger.warn(LocalizedMessage.create( LocalizedStrings.DLockGrantor_RELEASED_REGULAR_LOCK_WITH_WAITING_READ_LOCK_0, s)); Assert.assertTrue(false, LocalizedStrings.DLockGrantor_RELEASED_REGULAR_LOCK_WITH_WAITING_READ_LOCK_0 .toString(s)); } } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[postReleaseReadLock] new status {}", displayStatus(rThread, null)); } checkTotalReadLockCount(); } /** * Handles post release lock tasks including tracking the current suspend locking states. * <p> * Synchronizes on suspendLock. * * @param rThread the remote thread that released the lock * @param lock the named lock that was released */ protected void postReleaseLock(RemoteThread rThread, Object lock) { Assert.assertTrue(rThread != null); synchronized (suspendLock) { checkDestroyed(); if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[postReleaseLock] rThread={} lock={} permittedRequests={} suspendQueue={}", rThread, lock, permittedRequests, suspendQueue); } if (DLockService.SUSPEND_LOCKING_TOKEN.equals(lock)) { postReleaseSuspendLock(rThread, lock); } else { postReleaseReadLock(rThread, lock); } } // suspendLock sync } /** * Departure or other codepath NOT specific to unlock requires that we cleanup suspend state that * was already permitted to request. This needs to be invoked for both regular and suspend locks. * <p> * Synchronizes on suspendLock. * * @param request the request to cleanup after due to departure of sender */ protected void cleanupSuspendState(DLockRequestMessage request) { postReleaseLock(request.getRemoteThread(), request.getObjectName()); } /** * Drains newly permitted requests that have been removed from suspendQueue. All requests in the * permittedRequests queue already have permission to proceed with granting or scheduling. * <p> * Caller must acquire destroyReadLock. Synchronizes on suspendLock, grantTokens and each grant * token. * * Concurrency: protected by {@link #destroyLock} via invoking * {@link #acquireDestroyReadLock(long)} */ protected void drainPermittedRequests() { ArrayList drain = null; synchronized (suspendLock) { checkDestroyed(); if (this.permittedRequests.isEmpty()) { return; } drain = this.permittedRequests; this.permittedRequestsDrain.add(drain); this.permittedRequests = new ArrayList(); } // suspendLock sync final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[drainPermittedRequests] draining {}", drain); } // iterate and attempt to grantOrSchedule each request for (Iterator iter = drain.iterator(); iter.hasNext();) { DLockRequestMessage request = (DLockRequestMessage) iter.next(); checkDestroyed(); // destroyAndRemove should respond to all of these try { handlePermittedLockRequest(request); // synchronizes on grant instance } catch (LockGrantorDestroyedException e) { try { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "LockGrantorDestroyedException respondWithNotGrantor to {}", request); } request.respondWithNotGrantor(); } finally { } } catch (LockServiceDestroyedException e) { try { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "LockServiceDestroyedException respondWithNotGrantor to {}", request); } request.respondWithNotGrantor(); } finally { } } catch (RuntimeException e) { logger.error(LocalizedMessage.create( LocalizedStrings.DLockGrantor_PROCESSING_OF_POSTREMOTERELEASELOCK_THREW_UNEXPECTED_RUNTIMEEXCEPTION, e)); request.respondWithException(e); } finally { } } synchronized (suspendLock) { checkDestroyed(); this.permittedRequestsDrain.remove(drain); } } /** * Synchronizes on suspendLock. */ private boolean acquireSuspendLockPermission(DLockRequestMessage request) { boolean permitLockRequest = false; final RemoteThread rThread = request.getRemoteThread(); Assert.assertTrue(rThread != null); synchronized (suspendLock) { checkDestroyed(); if (!dm.isCurrentMember(request.getSender())) { logger.info(LogMarker.DLS, LocalizedMessage .create(LocalizedStrings.DLockGrantor_IGNORING_LOCK_REQUEST_FROM_NONMEMBER_0, request)); return false; } Integer integer = (Integer) readLockCountMap.get(rThread); int readLockCount = integer == null ? 0 : integer.intValue(); boolean othersHaveReadLocks = totalReadLockCount > readLockCount; final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isLockingSuspended() || writeLockWaiters > 0 || othersHaveReadLocks) { writeLockWaiters++; suspendQueue.addLast(request); this.thread.checkTimeToWait(calcWaitMillisFromNow(request), false); checkWriteLockWaiters(); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.acquireSuspend] added '{}' to end of suspendQueue.", request); } } else { permitLockRequest = true; suspendLocking(rThread, request.getLockId()); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.acquireSuspendLockPermission] permitted and suspended for {}", request); } } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.acquireSuspendLockPermission] new status permitLockRequest = {}{}", permitLockRequest, displayStatus(rThread, null)); } } // suspendLock sync return permitLockRequest; } /** * Synchronizes on suspendLock. */ private boolean acquireReadLockPermission(DLockRequestMessage request) { boolean permitLockRequest = false; final RemoteThread rThread = request.getRemoteThread(); Assert.assertTrue(rThread != null); synchronized (suspendLock) { checkDestroyed(); if (!dm.isCurrentMember(request.getSender())) { logger.info(LogMarker.DLS, LocalizedMessage .create(LocalizedStrings.DLockGrantor_IGNORING_LOCK_REQUEST_FROM_NONMEMBER_0, request)); return false; } Integer integer = (Integer) readLockCountMap.get(rThread); int readLockCount = integer == null ? 0 : integer.intValue(); boolean threadHoldsLock = readLockCount > 0 || isLockingSuspendedBy(rThread); final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (!threadHoldsLock && (isLockingSuspended() || writeLockWaiters > 0)) { suspendQueue.addLast(request); this.thread.checkTimeToWait(calcWaitMillisFromNow(request), false); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.acquireReadLockPermission] added {} to end of suspendQueue.", request); } } else { readLockCount++; readLockCountMap.put(rThread, Integer.valueOf(readLockCount)); totalReadLockCount++; permitLockRequest = true; if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.acquireReadLockPermission] permitted {}", request); } } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.acquireReadLockPermission] new status threadHoldsLock = {} permitLockRequest = {}{}", threadHoldsLock, permitLockRequest, displayStatus(rThread, null)); } checkTotalReadLockCount(); } // suspendLock sync return permitLockRequest; } /** * Returns true if lock request has permission to proceed; else adds the request to the end of * suspendQueue and returns false. * <p> * Synchronizes on suspendLock. * * @param request the lock request to acquire permission for */ private boolean acquireLockPermission(final DLockRequestMessage request) { if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantor.acquireLockPermission] {}", request); } boolean permitLockRequest = false; if (request.getObjectName().equals(DLockService.SUSPEND_LOCKING_TOKEN)) { permitLockRequest = acquireSuspendLockPermission(request); } else { permitLockRequest = acquireReadLockPermission(request); } return permitLockRequest; } /** * Throws InterruptedException if local lock request exists and is interruptible or * CancelException if DistributionManager is forcing us to cancel for shutdown. * * @param e the throwable that caused this check */ private void throwIfInterruptible(InterruptedException e) throws InterruptedException { // This needs to be first, otherwise user gets a complaint that // the TV is off when the problem is the house is burning down... this.dm.getCancelCriterion().checkCancelInProgress(e); if (this.dlock.isInterruptibleLockRequest()) { throw e; } } /** * TEST HOOK: Logs all grant tokens and other lock information for this service at INFO level. * <p> * Synchronizes on grantTokens. */ protected void dumpService() { synchronized (this.grantTokens) { StringBuffer buffer = new StringBuffer(); buffer.append("DLockGrantor.dumpService() for ").append(this); buffer.append("\n").append(this.grantTokens.size()).append(" grantTokens\n"); for (Iterator iter = this.grantTokens.entrySet().iterator(); iter.hasNext();) { Map.Entry entry = (Map.Entry) iter.next(); buffer.append(" ").append(entry.getKey()).append(": "); DLockGrantToken token = (DLockGrantToken) entry.getValue(); buffer.append(token.toString()).append("\n"); } logger.info(LogMarker.DLS, LocalizedMessage.create(LocalizedStrings.TESTING, buffer)); logger.info(LogMarker.DLS, LocalizedMessage.create(LocalizedStrings.TESTING, "\nreadLockCountMap:\n" + readLockCountMap)); } } /** * Verify the waiters (for debugging) * * guarded.By {@link #suspendLock} */ private void checkWriteLockWaiters() { if (!DEBUG_SUSPEND_LOCK) { return; } Assert.assertHoldsLock(this.suspendLock, true); int result = 0; Iterator it = this.suspendQueue.iterator(); while (it.hasNext()) { DLockRequestMessage r = (DLockRequestMessage) it.next(); if (r.isSuspendLockingRequest()) { result++; } } // while Assert.assertTrue(result == this.writeLockWaiters); } /** * Debugging method * * guarded.By {@link #suspendLock} */ private void checkTotalReadLockCount() { if (!DEBUG_SUSPEND_LOCK) { return; } Assert.assertHoldsLock(this.suspendLock, true); int result = 0; Iterator it = readLockCountMap.values().iterator(); while (it.hasNext()) { result += ((Integer) it.next()).intValue(); } Assert.assertTrue(result == totalReadLockCount); } // ------------------------------------------------------------------------- // DLockGrantToken (static inner class) // ------------------------------------------------------------------------- /** * Handles leasing and queued scheduling for an individual distributed lock. */ public static class DLockGrantToken { /** * DLS which contains this lock. Reference is used for stats and lifecycle. */ private final DLockService dlock; /** * Grantor instance that handles leasing of this lock. */ private final DLockGrantor grantor; /** * The uniquely identifying object name for this lock */ private final Object lockName; /** * Pending requests queued up for the lock * * guarded.By this */ private LinkedList pendingRequests; /** * The reply processor id is used to identify the specific lock operation used by the lessee to * lease this lock * * guarded.By this */ private int leaseId = -1; /** * Distributed member that currently has a lease on this lock * * guarded.By this */ private InternalDistributedMember lessee; /** * Absolute time in milliseconds when the current lease will expire. When this lock is not * leased out, the value is -1. When the lock is leased out, the value is > 0. A value of * Long.MAX_VALUE indicates a non-expiring (infinite) lease. * * guarded.By this */ private long leaseExpireTime = -1; /** * Current count of threads attempting to access this grant token. * * guarded.By this */ private int accessCount = 0; /** * True if this token has been destroyed and removed from usage. * * guarded.By this */ private boolean destroyed = false; /** * RemoteThread identity of thread currently holding lease on this lock * * guarded.By this */ private RemoteThread lesseeThread = null; /** * Instatiates a new instance of DLockGrantToken. * * @param dlock the lock service scope for this lock * @param grantor the grantor handling locks for the lock service * @param name the name of this lock */ protected DLockGrantToken(DLockService dlock, DLockGrantor grantor, Object name) { this.lockName = name; this.dlock = dlock; this.grantor = grantor; } /** * Schedules the lock request for immediate or later granting of lock. This will grant the lock * if it is available, otherwise it will add the request at the end of the pending requests * queue. * <p> * Synchronizes on this grant token. * * @param request the request to grant or schedule * @return true if the lock request was immediately granted */ protected synchronized boolean schedule(DLockRequestMessage request) { if (!this.grantor.dm.isCurrentMember(request.getSender())) { this.grantor.cleanupSuspendState(request); return false; } if (!isGranted(false) && !hasWaitingRequests()) { // don't need to schedule... just grant it if (grantLockToRequest(request)) { return true; } } // add the request to the sorted set... if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantToken.schedule] {} scheduling: {}", this, request); } if (this.pendingRequests == null) { this.pendingRequests = new LinkedList(); this.dlock.getStats().incRequestQueues(1); } this.pendingRequests.add(request); this.dlock.getStats().incPendingRequests(1); return true; } /** * Sends NOT_GRANTOR replies to every request waiting for this grant token and then destroys the * grant token. * <p> * Synchronizes on this grant token. */ protected synchronized void handleGrantorDestruction() { try { if (this.pendingRequests != null) { for (Iterator iter = this.pendingRequests.iterator(); iter.hasNext();) { DLockRequestMessage request = (DLockRequestMessage) iter.next(); request.respondWithNotGrantor(); } } } finally { destroy(); } } /** * Checks current lock for expiration and attempts to grant the lock if it is available. * <p> * Synchronizes on this grant token. * <p> * NOTE: expiration is only as accurate as clock synchronization on the hardware that the * members are running on probably should have Requestors handle expirations and send Release * msg - need an Evictor thread in each Requestor * * @return the lease expiration time in millis for the currently held lock or Long.MAX_VALUE if * lock has no owner */ protected synchronized long expireAndGrantLock() { // isGranted calls checkForExpiration... if (this.grantor.isDestroyed()) return Long.MAX_VALUE; if (!isGranted(true) && !this.grantor.isLockingSuspendedWithSync()) { grantLockToNextRequest(); } long result = getLeaseExpireTime(); if (result <= 0) { result = Long.MAX_VALUE; } return result; } /** * Returns true if there are pending requests waiting to lock this grant token. * <p> * Caller must synchronize on this grant token. * * Concurrency: protected by synchronization of *this* DLockGrantToken * * @return true if there are pending requests waiting to lock this */ protected synchronized boolean hasWaitingRequests() { if (this.pendingRequests == null) return false; return !this.pendingRequests.isEmpty(); } /** * Grant this lock to the request if possible. Returns true if lock was granted to the request. * <p> * Synchronizes on this grant token. * * @param request the lock request asking for this lock * @return true if lock was granted to the request */ protected synchronized boolean grantLockToRequest(DLockRequestMessage request) { Assert.assertTrue(request.getRemoteThread() != null); // KIRK search for these assertions and // remove if (isGranted(true) || hasWaitingRequests()) { return false; } if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantToken.grantLockToRequest] granting: {}", request); } long newLeaseExpireTime = grantAndRespondToRequest(request); if (newLeaseExpireTime == -1) return false; if (newLeaseExpireTime < Long.MAX_VALUE) { long now = DLockService.getLockTimeStamp(this.grantor.dm); this.grantor.thread.checkTimeToWait(newLeaseExpireTime - now, true); } return true; } /** * Called to release a remote lock when processing a DLockReleaseMessage. * <p> * Caller must synchronize on this grant token. * <p> * Call stack: DLockReleaseMessage -> releaseIfLocked -> getAndReleaseGrantIfLockedBy -> * grant.releaseIfLockedBy * * Concurrency: protected by synchronization of *this* DLockGrantToken * * @param owner the member to release the lock for * @param lockId the lock id that the member used to acquire the lock */ protected void releaseIfLockedBy(InternalDistributedMember owner, int lockId) { final RemoteThread rThread = getRemoteThread(); boolean released = false; try { released = releaseLock(owner, lockId); } catch (IllegalStateException e) { this.dlock.checkDestroyed(); this.grantor.checkDestroyed(); // must have hit race... grantor doesn't have the token return; } if (released) { // don't bother synchronizing requests for this log statement... if (logger.isTraceEnabled(LogMarker.DLS)) { synchronized (this) { logger.trace(LogMarker.DLS, "[DLockGrantToken.releaseIfLockedBy] pending requests: {}", (this.pendingRequests == null ? "none" : "" + this.pendingRequests.size())); } } Assert.assertTrue(rThread != null); // releaseIfLockedBy (remote unlock) this.grantor.postReleaseLock(rThread, getName()); // note: DLockReleaseMessage calls drainPermittedRequests next... } } /** * Returns true if lock is currently leased by the owner with the specified lock id. * <p> * Caller must synchronize on this grant token. * * Concurrency: protected by synchronization of *this* DLockGrantToken * * @param owner the member to check for lock ownership * @param lockId the lock id that the member used for locking * @return true if lock is currently leased by the owner with the specified lock id */ protected boolean isLockedBy(InternalDistributedMember owner, int lockId) { return isLeaseHeldBy(owner, lockId); } /** * Handle timeouts for requests waiting on this lock. Any requests that have timed out will be * removed. Calculates and returns the next smallest timeout of the requests still waiting on * this lock. * <p> * Synchronizes on this grant token. * * @return next smallest timeout of the requests still waiting on this lock */ protected long handleRequestTimeouts() { long smallestTimeout = Long.MAX_VALUE; synchronized (this) { if (this.pendingRequests == null) return smallestTimeout; if (this.grantor.isDestroyed()) return smallestTimeout; } List timeouts = new ArrayList(); // narrow timeouts to just contain requests that have timed out... DLockRequestMessage req = null; // ... copyRequests is synchronized on this ... synchronized (this) { for (Iterator iter = this.pendingRequests.iterator(); iter.hasNext();) { req = (DLockRequestMessage) iter.next(); if (req.checkForTimeout()) { // sends DLockResponseMessage if timeout this.grantor.cleanupSuspendState(req); timeouts.add(req); } else { long timeout = req.getTimeoutTS(); if (timeout < smallestTimeout) { smallestTimeout = timeout; } } } removeRequests(timeouts); } return smallestTimeout; } /** * Cleans up any state for the departed member. If the lock is held by this member, it will be * released. Any pending lock requests for this member will be removed. * <p> * Synchronizes on this grant token, suspendLock, and grantTokens. * * @param member the departed member */ protected void handleDepartureOf(final InternalDistributedMember member, final ArrayList grantsToRemoveIfUnused) { boolean released = false; RemoteThread rThread = null; final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); try { synchronized (this) { try { if (isDestroyed()) return; if (this.pendingRequests == null) return; // remove member from pendingRequests... DLockRequestMessage req = null; for (Iterator iter = this.pendingRequests.iterator(); iter.hasNext();) { req = (DLockRequestMessage) iter.next(); if (member.equals(req.getSender())) { // found departed member, respondWithNotHolder to end dlock stats try { req.handleDepartureOfSender(); // cleanup suspend state for this request this.grantor.cleanupSuspendState(req); } catch (CancelException e) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantToken.handleDepartureOf] ignored cancellation (1)"); } } // remove the request iter.remove(); this.dlock.getStats().incPendingRequests(-1); } } } finally { synchronized (this) { // bugfix 32657 release lock AFTER removing member from queued requests // because release will grant to first request in queued requests rThread = getRemoteThread(); boolean releasedToken = false; try { releasedToken = releaseLock(member, getLockId()); } catch (IllegalStateException e) { this.dlock.checkDestroyed(); this.grantor.checkDestroyed(); // must have hit race... grantor doesn't have the token return; } if (releasedToken) { released = true; if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantToken.handleDepartureOf] pending requests: {}", (this.pendingRequests == null ? "none" : "" + this.pendingRequests.size())); } Assert.assertTrue(rThread != null); } } } } } finally { if (released) { try { this.grantor.postReleaseLock(rThread, getName()); } catch (CancelException e) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantToken.handleDepartureOf] ignored cancellation (2)"); } } this.grantor.drainPermittedRequests(); // destroyReadLock{grant{}, suspendLock{}} grantsToRemoveIfUnused.add(this); } } } /** * Adds this grant to the list if it references the departed member. * <p> * Synchronizes on this grant token. * * @param member the departed member * @param grantsReferencingMember list to add grant to if it references departed member */ protected synchronized void checkDepartureOf(final InternalDistributedMember member, final List grantsReferencingMember) { if (this.destroyed) { return; } if (member.equals(this.lessee)) { grantsReferencingMember.add(this); return; } if (this.pendingRequests != null) { DLockRequestMessage req = null; for (Iterator iter = this.pendingRequests.iterator(); iter.hasNext();) { req = (DLockRequestMessage) iter.next(); if (member.equals(req.getSender())) { grantsReferencingMember.add(this); return; } } } } /** * Remove all the specified pending requests. * <p> * Caller must synchronize on this grant token. * * @param requestsToRemove the pending requests to remove guarded.By this */ private void removeRequests(Collection requestsToRemove) { if (!requestsToRemove.isEmpty()) { synchronized (this) { this.pendingRequests.removeAll(requestsToRemove); } this.dlock.getStats().incPendingRequests(-requestsToRemove.size()); } } /** * Grants this lock to the next waiting request if one exists. * <p> * Caller must synchronize on this grant token. * * Concurrency: protected by synchronization of *this* DLockGrantToken * * @return true if the lock was granted to next request */ protected boolean grantLockToNextRequest() { final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantToken.grantLock] {} isGranted={} hasWaitingRequests={}", getName(), isLeaseHeld(), hasWaitingRequests()); } while (!isGranted(true) && hasWaitingRequests()) { try { // get request at front of queue... DLockRequestMessage request = null; synchronized (this) { request = (DLockRequestMessage) this.pendingRequests.remove(0); } this.dlock.getStats().incPendingRequests(-1); // grant lock to the request unless it is timed out... if (request.checkForTimeout()) { this.grantor.cleanupSuspendState(request); continue; } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantToken.grantLock] granting {} to {}", getName(), request.getSender()); } long newLeaseExpireTime = grantAndRespondToRequest(request); if (newLeaseExpireTime == -1) continue; if (newLeaseExpireTime < Long.MAX_VALUE) { long now = DLockService.getLockTimeStamp(this.grantor.dm); this.grantor.thread.checkTimeToWait(newLeaseExpireTime - now, true); } } catch (IndexOutOfBoundsException e) { // ignore... entry may have timed out between empty check and remove } } return isGranted(false); } /** * Grants the lock to the specified request and sends a reply to the member that initiated the * request. * <p> * Caller must synchronize on this grant token. * * @param request the request to grant the lock to * @return leaseExpireTime or -1 if failed to grant. guarded.By this */ private long grantAndRespondToRequest(DLockRequestMessage request) { synchronized (request) { if (request.respondedNoSync()) { return -1; } Assert.assertTrue(request.getRemoteThread() != null); if (!this.grantor.dm.isCurrentMember(request.getSender())) { this.grantor.cleanupSuspendState(request); return -1; } if (isSuspendLockingToken()) { synchronized (this.grantor.suspendLock) { Assert.assertTrue( this.grantor.lockingSuspendedBy == null || this.grantor.isLockingSuspendedBy(request.getRemoteThread()), "Locking is suspended by " + this.grantor.lockingSuspendedBy + " with lockId of " + this.grantor.suspendedLockId + " instead of " + request.getRemoteThread() + " with lockId of " + request.getLockId()); } // suspendLock sync } long newLeaseExpireTime = calcLeaseExpireTime(request.getLeaseTime()); grantLock(request.getSender(), newLeaseExpireTime, request.getLockId(), request.getRemoteThread()); if (isSuspendLockingToken()) { synchronized (this.grantor.suspendLock) { // no-op if already suspend by this RemoteThread... this.grantor.suspendLocking(request.getRemoteThread(), request.getLockId()); Assert.assertTrue(this.grantor.isLockingSuspendedBy(request.getRemoteThread()), "Locking should now be suspended by " + request.getRemoteThread() + " with lockId of " + request.getLockId() + " instead of " + this.grantor.lockingSuspendedBy + " with lockId of " + this.grantor.suspendedLockId); } // suspendLock sync } // NOTE: if grantor is local and client interrupts the request, the // following will release the lock because the reply processor is gone request.respondWithGrant(newLeaseExpireTime); if (!isLeaseHeldBy(request.getSender(), request.getLockId())) { // lock request was local and interrupted then released return -1; } return newLeaseExpireTime; } } /** * Returns the absolute time at which the specified lease time will expire from now. This call * does not change or check any state other than current time. * * @param leaseTime the desired length of lease time * @return the absolute time at which the lease will expire */ protected long calcLeaseExpireTime(long leaseTime) { if (leaseTime == Long.MAX_VALUE || leaseTime == -1) { return Long.MAX_VALUE; } long currentTime = getCurrentTime(); long newLeaseExpireTime = currentTime + leaseTime; if (newLeaseExpireTime < leaseTime) { // rolled over MAX_VALUE... newLeaseExpireTime = Long.MAX_VALUE; } if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantToken.calcLeaseExpireTime] currentTime={} newLeaseExpireTime={}", currentTime, newLeaseExpireTime); } return newLeaseExpireTime; } /** * Returns true if this grant token is currently granted. * <p> * Caller must synchronize on this grant token. * * Concurrency: protected by synchronization of *this* DLockGrantToken * * @param checkForExpiration true if expiration should be attempted before checking if this * grant token is currently granted * @return true if this grant token is currently granted */ protected boolean isGranted(boolean checkForExpiration) { if (checkForExpiration) { checkForExpiration(); } return isLeaseHeld(); } /** * Creates a string of the pending requests for logging or debugging. * <p> * Caller must synchronize on this grant token. * * @return a string of the pending requests for logging or debugging guarded.By this */ private String pendingRequestsToString() { if (this.pendingRequests == null) { return "(null)"; } StringBuffer sb = new StringBuffer(); Iterator it = this.pendingRequests.iterator(); while (it.hasNext()) { Object req = it.next(); sb.append("["); sb.append(req.toString()); sb.append("]"); } return sb.toString(); } /** * Returns string representation of this object. * <p> * Synchronizes on this grant token. */ @Override public String toString() { return toString(true); } /** * Returns string representation of this object. * <p> * Synchronizes on this grant token. * <p> * * @param displayPendingRequests true if string should include pendingRequests */ public String toString(boolean displayPendingRequests) { StringBuffer sb = new StringBuffer("DLockGrantToken"); sb.append("@").append(Integer.toHexString(hashCode())); synchronized (this) { sb.append(" {name: ").append(getName()); sb.append(", isGranted: ").append(isLeaseHeld()); sb.append(", isDestroyed: ").append(this.destroyed); sb.append(", accessCount: ").append(this.accessCount); sb.append(", lessee: ").append(this.lessee); sb.append(", leaseExpireTime: ").append(this.leaseExpireTime); sb.append(", leaseId: ").append(this.leaseId); sb.append(", lesseeThread: ").append(this.lesseeThread); if (displayPendingRequests) { sb.append(", pendingRequests: ").append(pendingRequestsToString()); } sb.append("}"); } return sb.toString(); } /** * Returns the name of this lock. * * @return the name of this lock */ Object getName() { return this.lockName; } /** * Returns true if this lock represents suspend locking. * * @return true if this lock represents suspend locking * @see org.apache.geode.distributed.DistributedLockService#suspendLocking(long) */ boolean isSuspendLockingToken() { return DLockService.SUSPEND_LOCKING_TOKEN.equals(this.lockName); } /** * Returns the lock id used to lease this lock. * <p> * Caller must synchronize on this grant token. * * @return the lock id used to lease this lock guarded.By this */ int getLockId() { return this.leaseId; } /** * Returns the identity of the thread that has this lock leased. * <p> * Caller must synchronize on this grant token. * * @return the identity of the thread that has this lock leased guarded.By this */ RemoteThread getRemoteThread() { return this.lesseeThread; } /** * Increments or decrements access count by the specified amount. * <p> * Synchronizes on this grant token. * * @param amount the amount to inc or dec access count by */ private synchronized void incAccess(int amount) { if (amount < 0) { Assert.assertTrue(this.accessCount - amount >= 0, amount + " cannot be subtracted from accessCount " + this.accessCount); } this.accessCount += amount; } /** * Increments the access count by one. * <p> * Synchronizes on this grant token. */ void incAccess() { incAccess(1); } /** * Decrements the access count by one. * <p> * Synchronizes on this grant token. */ void decAccess() { incAccess(-1); } /** * Returns true if the access count is greater than zero. * <p> * Synchronizes on this grant token. * * @return true if the access count is greater than zero */ boolean isBeingAccessed() { synchronized (this) { return this.accessCount > 0; } } /** * Returns the member that currently holds a lease on this lock. * <p> * Synchronizes on this grant token. * * @return the member that currently holds a lease on this lock */ public synchronized InternalDistributedMember getOwner() { return this.lessee; } /** * Returns the lease expiration time. This the absolute time in milliseconds when the current * lease will expire. * <p> * Caller must synchronize on this grant token. * * Concurrency: protected by synchronization of *this* DLockGrantToken * * @return the lease expiration time */ public long getLeaseExpireTime() { return this.leaseExpireTime; } /** * Returns true if this grant token has been destroyed. * <p> * Synchronizes on this grant token. * * @return true if this grant token has been destroyed */ public synchronized boolean isDestroyed() { return this.destroyed; } /** * Returns the current time in milliseconds. * * @return the current time in milliseconds */ long getCurrentTime() { return DLockService.getLockTimeStamp(this.grantor.dm); } /** * Handle expiration if the lease expire time has been reached for the current lease on this * grant token. * <p> * Synchronizes on this grant token. * * @return true if the lease is expired */ synchronized boolean checkForExpiration() { if (this.lessee != null && this.leaseId > -1) { if (this.leaseExpireTime == Long.MAX_VALUE) return false; long currentTime = getCurrentTime(); if (currentTime > this.leaseExpireTime) { // expired! final RemoteThread rThread = this.lesseeThread; this.lessee = null; this.leaseId = -1; this.lesseeThread = null; this.leaseExpireTime = -1; if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[checkForExpiration] Expired token at {}: {}", currentTime, toString(true)); } this.grantor.postReleaseLock(rThread, this.lockName); return true; } /* * else if (this.log.fineEnabled()) { this.log.fine("[checkForExpiration] not expired: " + * this); } */ } return false; } /** * Grants this lock. * <p> * Caller must synchronize on this grant token. * * @param owner the member that is being granted the lock * @param newLeaseExpireTime the absolute expiration time * @param lockId the lock id used to request the lock * @param remoteThread identity of the locking thread guarded.By this */ void grantLock(InternalDistributedMember owner, long newLeaseExpireTime, int lockId, RemoteThread remoteThread) { Assert.assertTrue(remoteThread != null); checkDestroyed(); basicGrantLock(owner, newLeaseExpireTime, lockId, remoteThread); } /** * Modify grant token state to mark the lock as granted. * <p> * Caller must synchronize on this grant token. * * @param owner the member that has been granted the lock * @param newLeaseExpireTime the absolute expiration time * @param lockId the lock id used to request the lock * @param remoteThread identity of the locking thread guarded.By this */ private void basicGrantLock(InternalDistributedMember owner, long newLeaseExpireTime, int lockId, RemoteThread remoteThread) { Assert.assertTrue(remoteThread != null); Assert.assertTrue(lockId > -1, "Invalid attempt to grant lock with lockId " + lockId); this.lessee = owner; this.leaseExpireTime = newLeaseExpireTime; this.leaseId = lockId; this.lesseeThread = remoteThread; if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantToken.grantLock.grantor] Granting {}", toString(false)); } } /** * Returns true if this lock is currently leased out. * <p> * Caller must synchronize on this grant token. * * @return true if this lock is currently leased out guarded.By this */ boolean isLeaseHeld() { return this.lessee != null && this.leaseId > -1; } /** * Mark this grant token as destroyed. This should only happen to a token that is no longer in * use. * <p> * Caller must synchronize on this grant token. guarded.By this */ void destroy() { if (!this.destroyed) { this.destroyed = true; this.dlock.getStats().incGrantTokens(-1); if (this.pendingRequests != null) { this.dlock.getStats().incPendingRequests(-this.pendingRequests.size()); this.dlock.getStats().incRequestQueues(-1); } } } /** * Throws IllegalStateException if this grant token has been destroyed. * <p> * Caller must synchronize on this grant token. * * @throws IllegalStateException if this grant token has been destroyed guarded.By this */ private void checkDestroyed() { if (this.destroyed) { String s = "Attempting to use destroyed grant token: " + this; IllegalStateException e = new IllegalStateException(s); // log.warning(e); -enable for debugging throw e; } } /** * Called by the grantor. Releases lock on this token if it is currently locked by the specified * member and lockId. * <p> * Caller must synchronize on this grant token. * * @param member the member to release the lock from * @param lockId the lock id that the member used when locking * @return true if lock was released guarded.By this */ private boolean releaseLock(InternalDistributedMember member, int lockId) { if (lockId == -1) return false; checkDestroyed(); if (isLeaseHeldBy(member, lockId)) { if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantToken.releaseLock] releasing ownership: {}", this); } this.lessee = null; this.leaseId = -1; this.lesseeThread = null; this.leaseExpireTime = -1; return true; } if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantToken.releaseLock] {} attempted to release: {}", member, this); } return false; } /** * Returns true if the sender holds a lease on this lock using lockId. * <p> * Caller must synchronize on this grant token. * * @param sender the member that potentially holds a lease * @param lockId the lock id provided by the member * @return true if the sender holds a lease on this lock guarded.By this */ private boolean isLeaseHeldBy(InternalDistributedMember sender, int lockId) { Assert.assertTrue(sender != null, "sender is null: " + this); Assert.assertTrue(lockId > -1, "lockId is < 0: " + this); return sender.equals(this.lessee) && lockId == this.leaseId; } } // ------------------------------------------------------------------------- // DLockGrantorThread (static inner class) // ------------------------------------------------------------------------- /** * Thread dedicated to handling background tasks for this grantor. */ private static class DLockGrantorThread extends Thread { private static final long MAX_WAIT = 60 * 1000; // 60 seconds... private volatile boolean shutdown = false; private boolean waiting = false; private boolean requireTimeToWait = false; private boolean goIntoWait = false; private long timeToWait = MAX_WAIT; private long expectedWakeupTimeStamp = 0; private final Object lock = new Object(); private final DLockGrantor grantor; private final CancelCriterion stopper; /** Time in millis that next pending request will timeout */ private long nextTimeout = DLockGrantorThread.MAX_WAIT; /** Time in millis that next lock is due to expire */ private long nextExpire = DLockGrantorThread.MAX_WAIT; DLockGrantorThread(DLockGrantor grantor, CancelCriterion stopper) { super(DLockService.getThreadGroup(), "Lock Grantor for " + grantor.dlock.getName()); setDaemon(true); this.grantor = grantor; this.stopper = stopper; } private long now() { DM dm = this.grantor.dlock.getDistributionManager(); return DLockService.getLockTimeStamp(dm); } protected void shutdown() { this.shutdown = true; this.interrupt(); } protected void checkTimeToWait(long newTimeToWaitArg, boolean expire) { long newTimeToWait = newTimeToWaitArg; if (newTimeToWait == Long.MAX_VALUE) { // never expire return; } else if (newTimeToWait < 0) { // negative means already expired or timed out so we wakeup immediately newTimeToWait = 0; } synchronized (this.lock) { if (expire && newTimeToWait < this.nextExpire) { this.nextExpire = newTimeToWait; } if (!expire && newTimeToWait < this.nextTimeout) { this.nextTimeout = newTimeToWait; } if (newTimeToWait < this.timeToWait) { if (this.waiting) { long newWakeupTimeStamp = now() + newTimeToWait; if (newWakeupTimeStamp > -1 // accounts for overflow && newWakeupTimeStamp < this.expectedWakeupTimeStamp) { this.timeToWait = newTimeToWait; this.requireTimeToWait = true; this.goIntoWait = true; this.lock.notify(); } /* * if (this.log.fineEnabled()) { this.log.fine("[DLockGrantorThread.checkTimeToWait.k2]" * + " newTimeToWait=" + newTimeToWait + " expire=" + expire + " newWakeupTimeStamp=" + * newWakeupTimeStamp + " expectedWakeupTimeStamp=" + expectedWakeupTimeStamp + * " nextExpire=" + this.nextExpire + " nextTimeout=" + this.nextTimeout + * " timeToWait=" + this.timeToWait + " goIntoWait=" + this.goIntoWait ); } */ } else { this.timeToWait = newTimeToWait; this.requireTimeToWait = true; /* * if (this.log.fineEnabled()) { this.log.fine("[DLockGrantorThread.checkTimeToWait.k3]" * + " newTimeToWait=" + newTimeToWait + " expire=" + expire + * " expectedWakeupTimeStamp=" + expectedWakeupTimeStamp + " nextExpire=" + * this.nextExpire + " nextTimeout=" + this.nextTimeout + " timeToWait=" + * this.timeToWait + " goIntoWait=" + this.goIntoWait ); } */ } } // end if newTimeToWait /* * else if (this.log.fineEnabled()) { * this.log.fine("[DLockGrantorThread.checkTimeToWait.k4]" + " newTimeToWait=" + * newTimeToWait + " expire=" + expire + " expectedWakeupTimeStamp=" + * expectedWakeupTimeStamp + " nextExpire=" + this.nextExpire + " nextTimeout=" + * this.nextTimeout + " timeToWait=" + this.timeToWait + " goIntoWait=" + this.goIntoWait ); * } */ } // end sync this.lock } @Override public void run() { final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); DistributedLockStats stats = this.grantor.dlock.getStats(); boolean recalcTimeToWait = false; while (!this.shutdown) { // SystemFailure.checkFailure(); stopper checks this if (stopper.isCancelInProgress()) { break; // done } try { // go into wait if we know we have no timeouts or expires for a while synchronized (this.lock) { // synchronized if (recalcTimeToWait || this.requireTimeToWait) { recalcTimeToWait = false; long nextTS = Math.min(this.nextExpire, this.nextTimeout); this.nextExpire = Long.MAX_VALUE; this.nextTimeout = Long.MAX_VALUE; if (nextTS != Long.MAX_VALUE || this.requireTimeToWait) { this.requireTimeToWait = false; long now = now(); // fix bug 39355 by using current timeToWait if smaller long newTimeToWait = nextTS - now; if (this.requireTimeToWait) { this.timeToWait = Math.min(this.timeToWait, newTimeToWait); } else { this.timeToWait = newTimeToWait; } if (this.timeToWait < 0) this.timeToWait = 0; if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "DLockGrantorThread will wait for {} ms. nextExpire={} nextTimeout={} now={}", this.timeToWait, this.nextExpire, this.nextTimeout, now); } } else { this.timeToWait = Long.MAX_VALUE; if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "DLockGrantorThread will wait until rescheduled."); } } } if (this.timeToWait > 0) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "DLockGrantorThread is about to wait for {} ms.", this.timeToWait); } if (this.timeToWait != Long.MAX_VALUE) { this.expectedWakeupTimeStamp = now() + this.timeToWait; if (this.expectedWakeupTimeStamp < 0) { // overflow this.expectedWakeupTimeStamp = Long.MAX_VALUE; } } else { this.expectedWakeupTimeStamp = Long.MAX_VALUE; } if (this.expectedWakeupTimeStamp == Long.MAX_VALUE) { while (!this.goIntoWait) { this.waiting = true; this.lock.wait(); // spurious wakeup ok this.waiting = false; } } else { long timeToWaitThisTime = this.timeToWait; for (;;) { this.waiting = true; this.lock.wait(timeToWaitThisTime); // spurious wakeup ok this.waiting = false; if (this.goIntoWait) break; // out of for loop timeToWaitThisTime = this.expectedWakeupTimeStamp - now(); if (timeToWaitThisTime <= 0) break; // out of for loop } } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "DLockGrantorThread has woken up..."); } if (this.shutdown) break; // if goIntoWait, continue back around and enter wait again if (this.goIntoWait) { this.goIntoWait = false; continue; } } } // synchronized long statStart = stats.startGrantorThread(); try { Collection grants = this.grantor.snapshotGrantTokens(); // TASK: expire and grant locks if (this.shutdown) { return; } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "DLockGrantorThread about to expireAndGrantLocks..."); } { long smallestExpire = this.grantor.expireAndGrantLocks(grants.iterator()); synchronized (this.lock) { if (smallestExpire < this.nextExpire) { this.nextExpire = smallestExpire; } } } long timing = stats.endGrantorThreadExpireAndGrantLocks(statStart); // TASK: timeout waiting requests if (this.shutdown) { return; } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "DLockGrantorThread about to handleRequestTimeouts..."); } { long smallestRequestTimeout = this.grantor.handleRequestTimeouts(grants.iterator()); long smallestSuspendTimeout = this.grantor.handleSuspendTimeouts(); synchronized (this.lock) { if (smallestRequestTimeout < this.nextTimeout) { this.nextTimeout = smallestRequestTimeout; } if (smallestSuspendTimeout < this.nextTimeout) { this.nextTimeout = smallestSuspendTimeout; } } } timing = stats.endGrantorThreadHandleRequestTimeouts(timing); // TASK: remove unused tokens if (this.shutdown) { return; } if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "DLockGrantorThread about to removeUnusedGrants..."); } this.grantor.removeUnusedGrants(grants.iterator()); stats.endGrantorThreadRemoveUnusedTokens(timing); } catch (CancelException e) { // so, exit then. } finally { recalcTimeToWait = true; stats.endGrantorThread(statStart); } } catch (InterruptedException e) { // shutdown probably interrupted us // Not necessary to reset the interrupt bit, we're going to go // away of our own accord. if (this.shutdown) { // ok to ignore since this thread will now shutdown } else { logger.warn( LocalizedMessage.create( LocalizedStrings.DLockGrantor_DLOCKGRANTORTHREAD_WAS_UNEXPECTEDLY_INTERRUPTED), e); // do not set interrupt flag since this thread needs to resume stopper.checkCancelInProgress(e); } } // catch (VirtualMachineError err) { // SystemFailure.initiateFailure(err); // // If this ever returns, rethrow the error. We're poisoned // // now, so don't let this thread continue. // throw err; // } // catch (Throwable e) { // // Whenever you catch Error or Throwable, you must also // // catch VirtualMachineError (see above). However, there is // // _still_ a possibility that you are dealing with a cascading // // error condition, so you also need to check to see if the JVM // // is still usable: // SystemFailure.checkFailure(); // this.log.warning(LocalizedStrings.DLockGrantor_DLOCKGRANTORTHREAD_CAUGHT_EXCEPTION, e); // } finally { } } } } // ------------------------------------------------------------------------- // MembershipListener inner classes // ------------------------------------------------------------------------- /** Detects loss of the lock grantor and initiates grantor recovery. */ private MembershipListener membershipListener = new MembershipListener() { public void memberJoined(InternalDistributedMember id) {} public void quorumLost(Set<InternalDistributedMember> failures, List<InternalDistributedMember> remaining) {} public void memberSuspect(InternalDistributedMember id, InternalDistributedMember whoSuspected, String reason) {} public void memberDeparted(final InternalDistributedMember id, final boolean crashed) { final DLockGrantor me = DLockGrantor.this; final DM distMgr = me.dlock.getDistributionManager(); // if the VM is being forcibly disconnected, we shouldn't release locks as it // will take longer than the time allowed by the InternalDistributedSystem // shutdown mechanism. if (distMgr.getCancelCriterion().isCancelInProgress()) { return; } final boolean isDebugEnabled_DLS = logger.isTraceEnabled(LogMarker.DLS); try { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.memberDeparted] waiting thread pool will process id={}", id); } distMgr.getWaitingThreadPool().execute(new Runnable() { public void run() { try { processMemberDeparted(id, crashed, me); } catch (InterruptedException e) { // ignore if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "Ignored interrupt processing departed member"); } } } }); } catch (RejectedExecutionException e) { if (isDebugEnabled_DLS) { logger.trace(LogMarker.DLS, "[DLockGrantor.memberDeparted] rejected handling of id={}", id); } } } protected void processMemberDeparted(InternalDistributedMember id, boolean crashed, DLockGrantor me) throws InterruptedException { if (logger.isTraceEnabled(LogMarker.DLS)) { logger.trace(LogMarker.DLS, "[DLockGrantor.processMemberDeparted] id={}", id); } try { me.waitWhileInitializing(); // one cause of bug 32657 is "if (crashed) {" around handleDepartureOf // ... we cannot rely on the value of crashed to determine if grantor // has or will receive a NonGrantorDestroy message me.handleDepartureOf(id); } catch (LockGrantorDestroyedException e) { // ignore... grantor was destroyed } // outer try-catch } }; }