1 /*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.ejb.plugins.lock;
23
24 import java.util.LinkedList;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Stack;
28 import java.util.Collections;
29 import java.lang.reflect.Method;
30
31 import javax.ejb.EJBObject;
32 import javax.ejb.EJBException;
33 import javax.transaction.Status;
34 import javax.transaction.Transaction;
35 import javax.transaction.Synchronization;
36
37 import org.jboss.invocation.Invocation;
38
39 /**
40 *
41 * This lock allows multiple read locks concurrently. Once a writer
42 * has requested the lock, future read-lock requests whose transactions
43 * do not already have the read lock will block until all writers are
44 * done -- then all the waiting readers will concurrently go (depending
45 * on the reentrant setting / methodLock). A reader who promotes gets
46 * first crack at the write lock -- ahead of other waiting writers. If
47 * there is already a reader that is promoting, we throw an inconsistent
48 * read exception. Of course, writers have to wait for all read-locks
49 * to release before taking the write lock.
50 *
51 * @author <a href="pete@subx.com">Peter Murray</a>
52 *
53 * @version $Revision: 81030 $
54 *
55 * <p><b>Revisions:</b><br>
56 * <p><b>2002/6/4: yarrumretep</b>
57 * <ol>
58 * <li>Initial revision
59 * </ol>
60 */
61 public class SimpleReadWriteEJBLock extends BeanLockSupport
62 {
63 int writersWaiting = 0;
64 Transaction promotingReader = null;
65 Transaction writer = null;
66 HashSet readers = new HashSet();
67 Object methodLock = new Object();
68 boolean trace = log.isTraceEnabled();
69
70 private void trace(Transaction tx, String message)
71 {
72 trace(tx, message, null);
73 }
74
75 private void trace(Transaction tx, String message, Method method)
76 {
77 if(method != null)
78 log.trace("LOCK(" + id + "):" + message + " : " + tx + " - " + method.getDeclaringClass().getName() + "." + method.getName());
79 else
80 log.trace("LOCK(" + id + "):" + message + " : " + tx);
81 }
82
83 public void schedule(Invocation mi)
84 {
85 boolean reading = mi.getMethod() == null ? false : container.getBeanMetaData().isMethodReadOnly(mi.getMethod().getName());
86 Transaction miTx = mi.getTransaction();
87
88 sync();
89 try
90 {
91 if(reading)
92 {
93 if(trace)
94 trace(miTx, "READ (RQ)", mi.getMethod());
95 getReadLock(miTx);
96 if(trace)
97 trace(miTx, "READ (GT)", mi.getMethod());
98 }
99 else
100 {
101 if(trace)
102 trace(miTx, "WRITE (RQ)", mi.getMethod());
103 getWriteLock(miTx);
104 if(trace)
105 trace(miTx, "WRITE (GT)", mi.getMethod());
106 }
107 }
108 finally
109 {
110 releaseSync();
111 }
112 }
113
114 private void getReadLock(Transaction tx)
115 {
116 boolean done = false;
117
118 while(!done)
119 {
120 if(tx == null)
121 {
122 done = writer == null;
123 }
124 else if(readers.contains(tx))
125 {
126 done = true;
127 }
128 else if(writer == null && promotingReader == null && writersWaiting == 0)
129 {
130 try
131 {
132 ReadLockReliever reliever = getReliever();
133 reliever.setup(this, tx);
134 tx.registerSynchronization(reliever);
135 }
136 catch (Exception e)
137 {
138 throw new EJBException(e);
139 }
140 readers.add(tx);
141 done = true;
142 }
143 else if (writer != null && writer.equals(tx))
144 {
145 done = true;
146 }
147
148 if(!done)
149 {
150 if(trace)
151 trace(tx, "READ (WT) writer:" + writer + " writers waiting: " + writersWaiting + " reader count: " + readers.size());
152
153 waitAWhile(tx);
154 }
155 }
156 }
157
158 private void getWriteLock(Transaction tx)
159 {
160 boolean done = false;
161 boolean isReader;
162
163 if(tx == null)
164 throw new EJBException("Write lock requested without transaction.");
165
166 isReader = readers.contains(tx);
167 writersWaiting++;
168 while(!done)
169 {
170 if(writer == null && (readers.isEmpty() || (readers.size() == 1 && isReader)))
171 {
172 writersWaiting--;
173 promotingReader = null;
174 writer = tx;
175 done = true;
176 }
177 else if (writer != null && writer.equals(tx))
178 {
179 writersWaiting--;
180 done = true;
181 }
182 else
183 {
184 if(isReader)
185 {
186 if(promotingReader != null && !promotingReader.equals(tx))
187 {
188 writersWaiting--;
189 throw new EJBException("Contention on read lock promotion for bean. Exception in second transaction");
190 }
191 promotingReader = tx;
192 }
193
194 if(trace)
195 trace(tx, "WRITE (WT) writer:" + writer + " writers waiting: " + writersWaiting + " reader count: " + readers.size());
196
197 waitAWhile(tx);
198 }
199 }
200 }
201
202 /**
203 * Use readers as a semaphore object to avoid
204 * creating another object
205 */
206 private void waitAWhile(Transaction tx)
207 {
208 releaseSync();
209 try
210 {
211 synchronized(readers)
212 {
213 try
214 {
215 readers.wait(txTimeout);
216 }
217 catch(InterruptedException e)
218 {}
219 checkTransaction(tx);
220 }
221 }
222 finally
223 {
224 sync();
225 }
226 }
227
228 /**
229 * Use readers as a semaphore object to avoid
230 * creating another object
231 */
232 private void notifyWaiters()
233 {
234 synchronized(readers)
235 {
236 readers.notifyAll();
237 }
238 }
239
240 private void releaseReadLock(Transaction transaction)
241 {
242 if(trace)
243 trace(transaction, "READ (UL)");
244
245 if(!readers.remove(transaction))
246 throw new IllegalStateException("ReadWriteEJBLock: Read lock released when it wasn't taken");
247
248 notifyWaiters();
249 }
250
251 private void releaseWriteLock(Transaction transaction)
252 {
253 if(trace)
254 trace(transaction, "WRITE (UL)");
255
256 if (synched == null)
257 throw new IllegalStateException("ReadWriteEJBLock: Do not call nextTransaction while not synched!");
258
259 if(writer != null && !writer.equals(transaction))
260 throw new IllegalStateException("ReadWriteEJBLock: can't unlock a write lock with a different transaction");
261
262 writer = null;
263 notifyWaiters();
264 }
265
266 public void endTransaction(Transaction transaction)
267 {
268 releaseWriteLock(transaction);
269 }
270
271 public void wontSynchronize(Transaction transaction)
272 {
273 releaseWriteLock(transaction);
274 }
275
276 public void endInvocation(Invocation mi)
277 {
278 }
279
280 private static Stack kRecycledRelievers = new Stack();
281
282 static synchronized ReadLockReliever getReliever()
283 {
284 ReadLockReliever reliever;
285 if(!kRecycledRelievers.empty())
286 reliever = (ReadLockReliever)kRecycledRelievers.pop();
287 else
288 reliever = new ReadLockReliever();
289
290 return reliever;
291 }
292
293 private static class ReadLockReliever implements Synchronization
294 {
295 SimpleReadWriteEJBLock lock;
296 Transaction transaction;
297
298 protected void finalize()
299 {
300 recycle();
301 }
302
303 protected void recycle()
304 {
305 lock = null;
306 transaction = null;
307 kRecycledRelievers.push(this);
308 }
309
310 void setup(SimpleReadWriteEJBLock lock, Transaction transaction)
311 {
312 this.lock = lock;
313 this.transaction = transaction;
314 }
315
316 public void beforeCompletion()
317 {
318 }
319
320 public void afterCompletion(int status)
321 {
322 lock.sync();
323 try
324 {
325 lock.releaseReadLock(transaction);
326 }
327 finally
328 {
329 lock.releaseSync();
330 }
331 recycle();
332 }
333 }
334
335 private void checkTransaction(Transaction tx)
336 {
337 try
338 {
339 if(tx != null && tx.getStatus() == Status.STATUS_MARKED_ROLLBACK)
340 throw new EJBException ("Transaction marked for rollback - probably a timeout.");
341 }
342 catch (Exception e)
343 {
344 throw new EJBException(e);
345 }
346 }
347 }