Index: src/main/java/org/apache/jackrabbit/core/state/FineGrainedISMLocking.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/state/FineGrainedISMLocking.java (revision 0) +++ src/main/java/org/apache/jackrabbit/core/state/FineGrainedISMLocking.java (revision 0) @@ -0,0 +1,393 @@ +/* + * 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.jackrabbit.core.state; + +import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.NodeId; +import org.apache.jackrabbit.core.PropertyId; +import org.apache.jackrabbit.uuid.UUID; + +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.LinkedList; +import java.util.Collections; + +import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock; +import EDU.oswego.cs.dl.util.concurrent.Sync; +import EDU.oswego.cs.dl.util.concurrent.Latch; +import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock; + +/** + * FineGrainedISMLocking... + */ +public class FineGrainedISMLocking implements ISMLocking { + + /** + * Avoid creating commonly used Integer instances. + */ + private static final Integer ONE = new Integer(1); + + /** + * An anonymous read lock without an id assigned. + */ + private final ReadLock ANONYMOUS_READ_LOCK = new ReadLockImpl(); + + /** + * The active writer or null if there is none. + */ + private WriteLockImpl activeWriter; + + private volatile Thread activeWriterThread; + + private ReadWriteLock writerStateRWLock = new WriterPreferenceReadWriteLock(); + + /** + * Map that contains the read locks. + */ + private final LockMap readLockMap = new LockMap(); + + /** + * List of waiting readers that are blocked because they conflict with + * the current writer. + */ + private List waitingReaders = Collections.synchronizedList(new LinkedList()); + + /** + * List of waiting writers that are blocked because there is already a + * current writer or one of the current reads conflicts with the change log + * of the blocked writer. + */ + private List waitingWriters = new LinkedList(); + + /** + * {@inheritDoc} + */ + public ReadLock acquireReadLock(ItemId id) + throws InterruptedException { + if (activeWriterThread == Thread.currentThread()) { + // we hold the write lock + readLockMap.addLock(id); + return new ReadLockImpl(id); + } + + // if we get here the following is true: + // - the current thread does not hold a write lock + for (;;) { + Sync signal; + // make sure writer state does not change + Sync shared = writerStateRWLock.readLock(); + shared.acquire(); + try { + if (activeWriter == null || + !hasDependency(activeWriter.changes, id)) { + readLockMap.addLock(id); + return new ReadLockImpl(id); + } else { + signal = new Latch(); + waitingReaders.add(signal); + } + } finally { + shared.release(); + } + + // if we get here there was an active writer with + // a dependency to the current id. + // wait for the writer until it is done, then try again + signal.acquire(); + } + } + + /** + * {@inheritDoc} + */ + public WriteLock acquireWriteLock(ChangeLog changeLog) + throws InterruptedException { + for (;;) { + Sync signal; + // we want to become the current writer + Sync exclusive = writerStateRWLock.writeLock(); + exclusive.acquire(); + try { + if (activeWriter == null && + !readLockMap.hasDependency(changeLog)) { + activeWriter = new WriteLockImpl(changeLog); + activeWriterThread = Thread.currentThread(); + return activeWriter; + } else { + signal = new Latch(); + waitingWriters.add(signal); + } + } finally { + exclusive.release(); + } + // if we get here there is an active writer or there is a read + // lock that conflicts with the change log + signal.acquire(); + } + } + + //----------------------------< internal >---------------------------------- + + private final class WriteLockImpl implements WriteLock { + + private final ChangeLog changes; + + WriteLockImpl(ChangeLog changes) { + this.changes = changes; + } + + public void release() { + Sync exclusive = writerStateRWLock.writeLock(); + for (;;) { + try { + exclusive.acquire(); + break; + } catch (InterruptedException e) { + // try again + Thread.interrupted(); + } + } + try { + activeWriter = null; + activeWriterThread = null; + notifyWaitingReaders(); + notifyWaitingWriters(); + } finally { + exclusive.release(); + } + } + + public ReadLock downgrade() { + readLockMap.addLock(null); + Sync exclusive = writerStateRWLock.writeLock(); + for (;;) { + try { + exclusive.acquire(); + break; + } catch (InterruptedException e) { + // try again + Thread.interrupted(); + } + } + try { + activeWriter = null; + activeWriterThread = null; + // only notify waiting readers since we still hold a down + // graded lock, which is kind of exclusiv with respect to + // other writers + notifyWaitingReaders(); + } finally { + exclusive.release(); + } + return ANONYMOUS_READ_LOCK; + } + + } + + private final class ReadLockImpl implements ReadLock { + + private final ItemId id; + + public ReadLockImpl() { + this(null); + } + + ReadLockImpl(ItemId id) { + this.id = id; + } + + public void release() { + Sync shared = writerStateRWLock.readLock(); + for (;;) { + try { + shared.acquire(); + break; + } catch (InterruptedException e) { + // try again + Thread.interrupted(); + } + } + try { + readLockMap.removeLock(id); + if (activeWriterThread != Thread.currentThread()) { + // only notify waiting writers if we do *not* hold a write + // lock at the same time. that would be a waste of cpu time. + notifyWaitingWriters(); + } + } finally { + shared.release(); + } + } + } + + private static boolean hasDependency(ChangeLog changeLog, ItemId id) { + try { + if (changeLog.get(id) == null) { + if (!id.denotesNode() || changeLog.get(new NodeReferencesId((NodeId) id)) == null) { + // change log does not contain the item + return false; + } + } + } catch (NoSuchItemStateException e) { + // is deleted + } + return true; + } + + /** + * This method is not thread-safe and calling threads must ensure that + * only one thread calls this method at a time. + */ + private void notifyWaitingReaders() { + for (Iterator it = waitingReaders.iterator(); it.hasNext(); ) { + ((Sync) it.next()).release(); + it.remove(); + } + } + + /** + * This method may be called concurrently by multiple threads. + */ + private void notifyWaitingWriters() { + synchronized (waitingWriters) { + if (waitingWriters.isEmpty()) { + return; + } + for (Iterator it = waitingWriters.iterator(); it.hasNext(); ) { + ((Sync) it.next()).release(); + it.remove(); + } + } + } + + private static final class LockMap { + + /** + * 16 slots + */ + private final Map[] slots = new Map[0x10]; + + /** + * Flag that indicates if the entire map is locked. + */ + private volatile boolean global = false; + + public LockMap() { + for (int i = 0; i < slots.length; i++) { + slots[i] = new HashMap(); + } + } + + /** + * This method must be called while holding the reader sync of the + * {@link FineGrainedISMLocking#writerStateRWLock}! + * + * @param id the item id. + */ + public void addLock(ItemId id) { + if (id == null) { + if (global) { + throw new IllegalStateException( + "Map already globally locked"); + } + global = true; + return; + } + Map locks = slots[slotIndex(id)]; + synchronized (locks) { + Integer i = (Integer) locks.get(id); + if (i == null) { + i = ONE; + } else { + i = new Integer(i.intValue() + 1); + } + locks.put(id, i); + } + } + + /** + * This method must be called while holding the reader sync of the + * {@link FineGrainedISMLocking#writerStateRWLock}! + * + * @param id the item id. + */ + public void removeLock(ItemId id) { + if (id == null) { + if (!global) { + throw new IllegalStateException( + "Map not globally locked"); + } + global = false; + return; + } + Map locks = slots[slotIndex(id)]; + synchronized (locks) { + Integer i = (Integer) locks.get(id); + if (i != null) { + if (i.intValue() == 1) { + locks.remove(id); + } else { + locks.put(id, new Integer(i.intValue() - 1)); + } + } else { + throw new IllegalStateException( + "No lock present for id: " + id); + } + } + } + + /** + * This method must be called while holding the write sync of {@link + * FineGrainedISMLocking#writerStateRWLock} to make sure no additional + * read locks are added to or removed from the map! + * + * @param changes the change log. + * @return if the change log has a dependency to the locks currently + * present in this map. + */ + public boolean hasDependency(ChangeLog changes) { + if (global) { + // read lock present, which was downgraded from a write lock + return true; + } + for (int i = 0; i < slots.length; i++) { + Map locks = slots[i]; + if (!locks.isEmpty()) { + for (Iterator it = locks.keySet().iterator(); it.hasNext(); ) { + ItemId id = (ItemId) it.next(); + if (FineGrainedISMLocking.hasDependency(changes, id)) { + return true; + } + } + } + } + return false; + } + + private static int slotIndex(ItemId id) { + UUID uuid; + if (id.denotesNode()) { + uuid = ((NodeId) id).getUUID(); + } else { + uuid = ((PropertyId) id).getParentId().getUUID(); + } + return ((int) uuid.getLeastSignificantBits()) & 0xf; + } + } +} Property changes on: src\main\java\org\apache\jackrabbit\core\state\FineGrainedISMLocking.java ___________________________________________________________________ Name: svn:eol-style + native