001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins.lock;
023:
024: import java.util.LinkedList;
025: import java.util.HashMap;
026: import java.util.HashSet;
027: import java.util.Stack;
028: import java.util.Collections;
029: import java.lang.reflect.Method;
030:
031: import javax.ejb.EJBObject;
032: import javax.ejb.EJBException;
033: import javax.transaction.Status;
034: import javax.transaction.Transaction;
035: import javax.transaction.Synchronization;
036:
037: import org.jboss.invocation.Invocation;
038:
039: /**
040: *
041: * This lock allows multiple read locks concurrently. Once a writer
042: * has requested the lock, future read-lock requests whose transactions
043: * do not already have the read lock will block until all writers are
044: * done -- then all the waiting readers will concurrently go (depending
045: * on the reentrant setting / methodLock). A reader who promotes gets
046: * first crack at the write lock -- ahead of other waiting writers. If
047: * there is already a reader that is promoting, we throw an inconsistent
048: * read exception. Of course, writers have to wait for all read-locks
049: * to release before taking the write lock.
050: *
051: * @author <a href="pete@subx.com">Peter Murray</a>
052: *
053: * @version $Revision: 57209 $
054: *
055: * <p><b>Revisions:</b><br>
056: * <p><b>2002/6/4: yarrumretep</b>
057: * <ol>
058: * <li>Initial revision
059: * </ol>
060: */
061: public class SimpleReadWriteEJBLock extends BeanLockSupport {
062: int writersWaiting = 0;
063: Transaction promotingReader = null;
064: Transaction writer = null;
065: HashSet readers = new HashSet();
066: Object methodLock = new Object();
067: boolean trace = log.isTraceEnabled();
068:
069: private void trace(Transaction tx, String message) {
070: trace(tx, message, null);
071: }
072:
073: private void trace(Transaction tx, String message, Method method) {
074: if (method != null)
075: log.trace("LOCK(" + id + "):" + message + " : " + tx
076: + " - " + method.getDeclaringClass().getName()
077: + "." + method.getName());
078: else
079: log.trace("LOCK(" + id + "):" + message + " : " + tx);
080: }
081:
082: public void schedule(Invocation mi) {
083: boolean reading = mi.getMethod() == null ? false : container
084: .getBeanMetaData().isMethodReadOnly(
085: mi.getMethod().getName());
086: Transaction miTx = mi.getTransaction();
087:
088: sync();
089: try {
090: if (reading) {
091: if (trace)
092: trace(miTx, "READ (RQ)", mi.getMethod());
093: getReadLock(miTx);
094: if (trace)
095: trace(miTx, "READ (GT)", mi.getMethod());
096: } else {
097: if (trace)
098: trace(miTx, "WRITE (RQ)", mi.getMethod());
099: getWriteLock(miTx);
100: if (trace)
101: trace(miTx, "WRITE (GT)", mi.getMethod());
102: }
103: } finally {
104: releaseSync();
105: }
106: }
107:
108: private void getReadLock(Transaction tx) {
109: boolean done = false;
110:
111: while (!done) {
112: if (tx == null) {
113: done = writer == null;
114: } else if (readers.contains(tx)) {
115: done = true;
116: } else if (writer == null && promotingReader == null
117: && writersWaiting == 0) {
118: try {
119: ReadLockReliever reliever = getReliever();
120: reliever.setup(this , tx);
121: tx.registerSynchronization(reliever);
122: } catch (Exception e) {
123: throw new EJBException(e);
124: }
125: readers.add(tx);
126: done = true;
127: } else if (writer != null && writer.equals(tx)) {
128: done = true;
129: }
130:
131: if (!done) {
132: if (trace)
133: trace(tx, "READ (WT) writer:" + writer
134: + " writers waiting: " + writersWaiting
135: + " reader count: " + readers.size());
136:
137: waitAWhile(tx);
138: }
139: }
140: }
141:
142: private void getWriteLock(Transaction tx) {
143: boolean done = false;
144: boolean isReader;
145:
146: if (tx == null)
147: throw new EJBException(
148: "Write lock requested without transaction.");
149:
150: isReader = readers.contains(tx);
151: writersWaiting++;
152: while (!done) {
153: if (writer == null
154: && (readers.isEmpty() || (readers.size() == 1 && isReader))) {
155: writersWaiting--;
156: promotingReader = null;
157: writer = tx;
158: done = true;
159: } else if (writer != null && writer.equals(tx)) {
160: writersWaiting--;
161: done = true;
162: } else {
163: if (isReader) {
164: if (promotingReader != null
165: && !promotingReader.equals(tx)) {
166: writersWaiting--;
167: throw new EJBException(
168: "Contention on read lock promotion for bean. Exception in second transaction");
169: }
170: promotingReader = tx;
171: }
172:
173: if (trace)
174: trace(tx, "WRITE (WT) writer:" + writer
175: + " writers waiting: " + writersWaiting
176: + " reader count: " + readers.size());
177:
178: waitAWhile(tx);
179: }
180: }
181: }
182:
183: /**
184: * Use readers as a semaphore object to avoid
185: * creating another object
186: */
187: private void waitAWhile(Transaction tx) {
188: releaseSync();
189: try {
190: synchronized (readers) {
191: try {
192: readers.wait(txTimeout);
193: } catch (InterruptedException e) {
194: }
195: checkTransaction(tx);
196: }
197: } finally {
198: sync();
199: }
200: }
201:
202: /**
203: * Use readers as a semaphore object to avoid
204: * creating another object
205: */
206: private void notifyWaiters() {
207: synchronized (readers) {
208: readers.notifyAll();
209: }
210: }
211:
212: private void releaseReadLock(Transaction transaction) {
213: if (trace)
214: trace(transaction, "READ (UL)");
215:
216: if (!readers.remove(transaction))
217: throw new IllegalStateException(
218: "ReadWriteEJBLock: Read lock released when it wasn't taken");
219:
220: notifyWaiters();
221: }
222:
223: private void releaseWriteLock(Transaction transaction) {
224: if (trace)
225: trace(transaction, "WRITE (UL)");
226:
227: if (synched == null)
228: throw new IllegalStateException(
229: "ReadWriteEJBLock: Do not call nextTransaction while not synched!");
230:
231: if (writer != null && !writer.equals(transaction))
232: throw new IllegalStateException(
233: "ReadWriteEJBLock: can't unlock a write lock with a different transaction");
234:
235: writer = null;
236: notifyWaiters();
237: }
238:
239: public void endTransaction(Transaction transaction) {
240: releaseWriteLock(transaction);
241: }
242:
243: public void wontSynchronize(Transaction transaction) {
244: releaseWriteLock(transaction);
245: }
246:
247: public void endInvocation(Invocation mi) {
248: }
249:
250: private static Stack kRecycledRelievers = new Stack();
251:
252: static synchronized ReadLockReliever getReliever() {
253: ReadLockReliever reliever;
254: if (!kRecycledRelievers.empty())
255: reliever = (ReadLockReliever) kRecycledRelievers.pop();
256: else
257: reliever = new ReadLockReliever();
258:
259: return reliever;
260: }
261:
262: private static class ReadLockReliever implements Synchronization {
263: SimpleReadWriteEJBLock lock;
264: Transaction transaction;
265:
266: protected void finalize() {
267: recycle();
268: }
269:
270: protected void recycle() {
271: lock = null;
272: transaction = null;
273: kRecycledRelievers.push(this );
274: }
275:
276: void setup(SimpleReadWriteEJBLock lock, Transaction transaction) {
277: this .lock = lock;
278: this .transaction = transaction;
279: }
280:
281: public void beforeCompletion() {
282: }
283:
284: public void afterCompletion(int status) {
285: lock.sync();
286: try {
287: lock.releaseReadLock(transaction);
288: } finally {
289: lock.releaseSync();
290: }
291: recycle();
292: }
293: }
294:
295: private void checkTransaction(Transaction tx) {
296: try {
297: if (tx != null
298: && tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
299: throw new EJBException(
300: "Transaction marked for rollback - probably a timeout.");
301: } catch (Exception e) {
302: throw new EJBException(e);
303: }
304: }
305: }
|