/* $Id$ */ /** * 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.manifoldcf.core.lockmanager; import java.util.*; import java.io.*; import org.apache.manifoldcf.core.interfaces.*; /** This class creates a first-come, first-serve local queue for locks. * The usage model is as follows: * (1) There is one lock gate per key for all threads. * (2) The LockGate replaces LockObjects in the pool, but each LockGate refers to * a LockObject. They are separate objects though because they require separate locking. * (3) When a lock is desired, the appropriate LockGate method is called to obtain * the lock. This method places the thread ID onto the queue, and waits until * the thread ID reaches the head of the queue before executing the lock request. * (4) Only when the lock request is successful, or is aborted, is the thread ID * removed from the queue. Write requests therefore serve to block read requests * until the write request is serviced. * Preferred structure: * <wait_for_permission> * try { * ... obtain the lock ... * } finally { * <release_permission> * } * Seeing lockups. These lockups are characterized by a thread waiting on a lock object * while another thread waits on permission to do something else with the lock object. * It is by no means clear at this point how this situation causes a hang-up; the * lock object is waiting to be awakened, but there is no obvious entity holding the lock elsewhere. * But one thread (A) seems always to be in a multi-lock situation, waiting to obtain a lock, e.g.: at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:503) at org.apache.manifoldcf.core.lockmanager.LockObject.enterWriteLock(LockObject.java:80) - locked <0x00000000fe205720> (a org.apache.manifoldcf.core.lockmanager.LockObject) at org.apache.manifoldcf.core.lockmanager.LockGate.enterWriteLock(LockGate.java:132) at org.apache.manifoldcf.core.lockmanager.BaseLockManager.enter(BaseLockManager.java:1483) at org.apache.manifoldcf.core.lockmanager.BaseLockManager.enterCriticalSections(BaseLockManager.java:920) at org.apache.manifoldcf.core.lockmanager.LockManager.enterCriticalSections(LockManager.java:455) * Here's the second thread (B), which is waitingForPermission: at java.lang.Object.wait(Native Method) - waiting on <0x00000000f8b71c78> (a org.apache.manifoldcf.core.lockmanager.LockGate) at java.lang.Object.wait(Object.java:503) at org.apache.manifoldcf.core.lockmanager.LockGate.waitForPermission(LockGate.java:91) - locked <0x00000000f8b71c78> (a org.apache.manifoldcf.core.lockmanager.LockGate) at org.apache.manifoldcf.core.lockmanager.LockGate.enterWriteLock(LockGate.java:129) at org.apache.manifoldcf.core.lockmanager.BaseLockManager.enterWrite(BaseLockManager.java:1130) at org.apache.manifoldcf.core.lockmanager.BaseLockManager.enterWriteCriticalSection(BaseLockManager.java:896) at org.apache.manifoldcf.core.lockmanager.LockManager.enterWriteCriticalSection(LockManager.java:431) at org.apache.manifoldcf.core.interfaces.IDFactory.make(IDFactory.java:55) * The problem is that (A) has already obtained permission, but cannot obtain the lock. (B) is somehow blocking * (A) from obtaining the lock even though it has not yet taken its own lock! Or, maybe it has, and we don't see it in * the stack trace. * Another example: (C) at java.lang.Object.wait(Native Method) - waiting on <0x00000000ffbdc038> (a org.apache.manifoldcf.core.lockmanager.LockGate) at java.lang.Object.wait(Object.java:503) at org.apache.manifoldcf.core.lockmanager.LockGate.waitForPermission(LockGate.java:91) - locked <0x00000000ffbdc038> (a org.apache.manifoldcf.core.lockmanager.LockGate) at org.apache.manifoldcf.core.lockmanager.LockGate.enterReadLock(LockGate.java:211) at org.apache.manifoldcf.core.lockmanager.BaseLockManager.enter(BaseLockManager.java:1532) at org.apache.manifoldcf.core.lockmanager.BaseLockManager.enterLocks(BaseLockManager.java:813) at org.apache.manifoldcf.core.lockmanager.LockManager.enterLocks(LockManager.java:355) * and (D): at java.lang.Object.wait(Native Method) - waiting on <0x00000000ffbdd2f8> (a org.apache.manifoldcf.core.lockmanager.LockObject) at java.lang.Object.wait(Object.java:503) at org.apache.manifoldcf.core.lockmanager.LockObject.enterWriteLock(LockObject.java:83) - locked <0x00000000ffbdd2f8> (a org.apache.manifoldcf.core.lockmanager.LockObject) at org.apache.manifoldcf.core.lockmanager.LockGate.enterWriteLock(LockGate.java:132) at org.apache.manifoldcf.core.lockmanager.BaseLockManager.enter(BaseLockManager.java:1483) at org.apache.manifoldcf.core.lockmanager.BaseLockManager.enterLocks(BaseLockManager.java:813) at org.apache.manifoldcf.core.lockmanager.LockManager.enterLocks(LockManager.java:355) * Problem here: The LockGate 0x00000000ffbdc038 has no other instance anywhere, which should not be able to happen. * Debugging must entail dumping ALL outstanding locks periodically -- and who holds each. */ public class LockGate { protected final List<Long> threadRequests = new ArrayList<Long>(); protected final LockObject lockObject; protected final Object lockKey; protected LockPool lockPool; public LockGate(Object lockKey, LockObject lockObject, LockPool lockPool) { this.lockKey = lockKey; this.lockObject = lockObject; this.lockPool = lockPool; } public void makeInvalid() { synchronized (this) { this.lockPool = null; lockObject.makeInvalid(); } } protected void waitForPermission(Long threadID) throws InterruptedException, ExpiredObjectException { synchronized (this) { threadRequests.add(threadID); } try { // Now, wait until we are #1 while (true) { synchronized (this) { if (lockPool == null) throw new ExpiredObjectException("Invalid"); if (threadRequests.get(0).equals(threadID)) return; wait(); } } } catch (InterruptedException e) { freePermission(threadID); throw e; } catch (ExpiredObjectException e) { freePermission(threadID); throw e; } catch (Error e) { freePermission(threadID); throw e; } catch (RuntimeException e) { freePermission(threadID); throw e; } } protected void freePermission(Long threadID) { synchronized (this) { threadRequests.remove(threadID); notifyAll(); } } public void enterWriteLock(Long threadID) throws ManifoldCFException, InterruptedException, ExpiredObjectException { waitForPermission(threadID); try { lockObject.enterWriteLock(); } finally { freePermission(threadID); } } public void enterWriteLockNoWait(Long threadID) throws ManifoldCFException, LockException, LocalLockException, InterruptedException, ExpiredObjectException { waitForPermission(threadID); try { lockObject.enterWriteLockNoWait(); } finally { freePermission(threadID); } } public void leaveWriteLock() throws ManifoldCFException, InterruptedException, ExpiredObjectException { synchronized (this) { // Leave, and if we succeed, flush from pool. if (lockObject.leaveWriteLock()) { if (threadRequests.size() == 0 && lockPool != null) lockPool.releaseObject(lockKey, this); } } } public void enterNonExWriteLock(Long threadID) throws ManifoldCFException, InterruptedException, ExpiredObjectException { waitForPermission(threadID); try { lockObject.enterNonExWriteLock(); } finally { freePermission(threadID); } } public void enterNonExWriteLockNoWait(Long threadID) throws ManifoldCFException, LockException, LocalLockException, InterruptedException, ExpiredObjectException { waitForPermission(threadID); try { lockObject.enterNonExWriteLockNoWait(); } finally { freePermission(threadID); } } public void leaveNonExWriteLock() throws ManifoldCFException, InterruptedException, ExpiredObjectException { synchronized (this) { // Leave, and if we succeed, flush from pool. if (lockObject.leaveNonExWriteLock()) { if (threadRequests.size() == 0 && lockPool != null) lockPool.releaseObject(lockKey, this); } } } public void enterReadLock(Long threadID) throws ManifoldCFException, InterruptedException, ExpiredObjectException { waitForPermission(threadID); try { lockObject.enterReadLock(); } finally { freePermission(threadID); } } public void enterReadLockNoWait(Long threadID) throws ManifoldCFException, LockException, LocalLockException, InterruptedException, ExpiredObjectException { waitForPermission(threadID); try { lockObject.enterReadLockNoWait(); } finally { freePermission(threadID); } } public void leaveReadLock() throws ManifoldCFException, InterruptedException, ExpiredObjectException { // Leave, and if we succeed (and the thread queue is empty), flush from pool. synchronized (this) { if (lockObject.leaveReadLock()) { if (threadRequests.size() == 0 && lockPool != null) lockPool.releaseObject(lockKey, this); } } } }