/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ejb.plugins.lock; import java.util.LinkedList; import java.util.HashMap; import java.util.HashSet; import java.util.Stack; import java.util.Collections; import java.lang.reflect.Method; import javax.ejb.EJBObject; import javax.ejb.EJBException; import javax.transaction.Status; import javax.transaction.Transaction; import javax.transaction.Synchronization; import org.jboss.invocation.Invocation; /** * * This lock allows multiple read locks concurrently. Once a writer * has requested the lock, future read-lock requests whose transactions * do not already have the read lock will block until all writers are * done -- then all the waiting readers will concurrently go (depending * on the reentrant setting / methodLock). A reader who promotes gets * first crack at the write lock -- ahead of other waiting writers. If * there is already a reader that is promoting, we throw an inconsistent * read exception. Of course, writers have to wait for all read-locks * to release before taking the write lock. * * @author <a href="pete@subx.com">Peter Murray</a> * * @version $Revision: 81030 $ * * <p><b>Revisions:</b><br> * <p><b>2002/6/4: yarrumretep</b> * <ol> * <li>Initial revision * </ol> */ public class SimpleReadWriteEJBLock extends BeanLockSupport { int writersWaiting = 0; Transaction promotingReader = null; Transaction writer = null; HashSet readers = new HashSet(); Object methodLock = new Object(); boolean trace = log.isTraceEnabled(); private void trace(Transaction tx, String message) { trace(tx, message, null); } private void trace(Transaction tx, String message, Method method) { if(method != null) log.trace("LOCK(" + id + "):" + message + " : " + tx + " - " + method.getDeclaringClass().getName() + "." + method.getName()); else log.trace("LOCK(" + id + "):" + message + " : " + tx); } public void schedule(Invocation mi) { boolean reading = mi.getMethod() == null ? false : container.getBeanMetaData().isMethodReadOnly(mi.getMethod().getName()); Transaction miTx = mi.getTransaction(); sync(); try { if(reading) { if(trace) trace(miTx, "READ (RQ)", mi.getMethod()); getReadLock(miTx); if(trace) trace(miTx, "READ (GT)", mi.getMethod()); } else { if(trace) trace(miTx, "WRITE (RQ)", mi.getMethod()); getWriteLock(miTx); if(trace) trace(miTx, "WRITE (GT)", mi.getMethod()); } } finally { releaseSync(); } } private void getReadLock(Transaction tx) { boolean done = false; while(!done) { if(tx == null) { done = writer == null; } else if(readers.contains(tx)) { done = true; } else if(writer == null && promotingReader == null && writersWaiting == 0) { try { ReadLockReliever reliever = getReliever(); reliever.setup(this, tx); tx.registerSynchronization(reliever); } catch (Exception e) { throw new EJBException(e); } readers.add(tx); done = true; } else if (writer != null && writer.equals(tx)) { done = true; } if(!done) { if(trace) trace(tx, "READ (WT) writer:" + writer + " writers waiting: " + writersWaiting + " reader count: " + readers.size()); waitAWhile(tx); } } } private void getWriteLock(Transaction tx) { boolean done = false; boolean isReader; if(tx == null) throw new EJBException("Write lock requested without transaction."); isReader = readers.contains(tx); writersWaiting++; while(!done) { if(writer == null && (readers.isEmpty() || (readers.size() == 1 && isReader))) { writersWaiting--; promotingReader = null; writer = tx; done = true; } else if (writer != null && writer.equals(tx)) { writersWaiting--; done = true; } else { if(isReader) { if(promotingReader != null && !promotingReader.equals(tx)) { writersWaiting--; throw new EJBException("Contention on read lock promotion for bean. Exception in second transaction"); } promotingReader = tx; } if(trace) trace(tx, "WRITE (WT) writer:" + writer + " writers waiting: " + writersWaiting + " reader count: " + readers.size()); waitAWhile(tx); } } } /** * Use readers as a semaphore object to avoid * creating another object */ private void waitAWhile(Transaction tx) { releaseSync(); try { synchronized(readers) { try { readers.wait(txTimeout); } catch(InterruptedException e) {} checkTransaction(tx); } } finally { sync(); } } /** * Use readers as a semaphore object to avoid * creating another object */ private void notifyWaiters() { synchronized(readers) { readers.notifyAll(); } } private void releaseReadLock(Transaction transaction) { if(trace) trace(transaction, "READ (UL)"); if(!readers.remove(transaction)) throw new IllegalStateException("ReadWriteEJBLock: Read lock released when it wasn't taken"); notifyWaiters(); } private void releaseWriteLock(Transaction transaction) { if(trace) trace(transaction, "WRITE (UL)"); if (synched == null) throw new IllegalStateException("ReadWriteEJBLock: Do not call nextTransaction while not synched!"); if(writer != null && !writer.equals(transaction)) throw new IllegalStateException("ReadWriteEJBLock: can't unlock a write lock with a different transaction"); writer = null; notifyWaiters(); } public void endTransaction(Transaction transaction) { releaseWriteLock(transaction); } public void wontSynchronize(Transaction transaction) { releaseWriteLock(transaction); } public void endInvocation(Invocation mi) { } private static Stack kRecycledRelievers = new Stack(); static synchronized ReadLockReliever getReliever() { ReadLockReliever reliever; if(!kRecycledRelievers.empty()) reliever = (ReadLockReliever)kRecycledRelievers.pop(); else reliever = new ReadLockReliever(); return reliever; } private static class ReadLockReliever implements Synchronization { SimpleReadWriteEJBLock lock; Transaction transaction; protected void finalize() { recycle(); } protected void recycle() { lock = null; transaction = null; kRecycledRelievers.push(this); } void setup(SimpleReadWriteEJBLock lock, Transaction transaction) { this.lock = lock; this.transaction = transaction; } public void beforeCompletion() { } public void afterCompletion(int status) { lock.sync(); try { lock.releaseReadLock(transaction); } finally { lock.releaseSync(); } recycle(); } } private void checkTransaction(Transaction tx) { try { if(tx != null && tx.getStatus() == Status.STATUS_MARKED_ROLLBACK) throw new EJBException ("Transaction marked for rollback - probably a timeout."); } catch (Exception e) { throw new EJBException(e); } } }