001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.db.store;
031:
032: import com.caucho.log.Log;
033: import com.caucho.util.Alarm;
034: import com.caucho.util.L10N;
035:
036: import java.sql.SQLException;
037: import java.util.logging.Level;
038: import java.util.logging.Logger;
039:
040: /**
041: * Locking for tables/etc.
042: */
043: public final class Lock {
044: private final static L10N L = new L10N(Lock.class);
045: private final static Logger log = Log.open(Lock.class);
046:
047: private final String _id;
048:
049: // count of threads trying to upgrade from a read lock
050: private int _tryUpgradeCount;
051: // count of threads trying to get a write lock
052: private int _tryWriteCount;
053:
054: // count of threads with a read currently running
055: private int _readCount;
056: // true if a thread has a write lock
057: private boolean _isWrite;
058:
059: private Thread _owner;
060:
061: public Lock(String id) {
062: _id = id;
063: }
064:
065: /**
066: * Returns the lock identifier.
067: */
068: public String getId() {
069: return _id;
070: }
071:
072: /**
073: * Tries to get a read lock.
074: *
075: * @param timeout how long to wait for a timeout
076: */
077: void lockRead(Transaction xa, long timeout)
078: throws LockTimeoutException {
079: if (log.isLoggable(Level.FINEST)) {
080: log.finest(this + " lockRead (read:" + _readCount
081: + " write:" + _isWrite + " try-write:"
082: + _tryWriteCount + ")");
083: }
084:
085: long start = Alarm.getCurrentTime();
086: long expire = start + timeout;
087:
088: synchronized (this ) {
089: long now;
090:
091: while (true) {
092: if (!_isWrite && _tryWriteCount == 0) {
093: _readCount++;
094: return;
095: }
096:
097: long delta = expire - Alarm.getCurrentTime();
098:
099: if (delta < 0 || Alarm.isTest())
100: break;
101:
102: try {
103: wait(delta);
104: } catch (InterruptedException e) {
105: throw new LockTimeoutException(e);
106: }
107: }
108:
109: if (!Alarm.isTest()) {
110: printOwnerStack();
111: Thread.dumpStack();
112: }
113:
114: throw new LockTimeoutException(
115: L
116: .l(
117: "{0} lockRead timed out ({1}ms) read-count:{2} try-writers:{3} is-write:{4}",
118: this , Alarm.getCurrentTime()
119: - start, _readCount,
120: _tryWriteCount, _isWrite));
121: }
122: }
123:
124: /**
125: * Clears a read lock.
126: */
127: void unlockRead() throws SQLException {
128: synchronized (this ) {
129: _readCount--;
130:
131: if (_readCount < 0)
132: Thread.dumpStack();
133:
134: if (log.isLoggable(Level.FINEST)) {
135: log.finest(this + " unlockRead (read:" + _readCount
136: + " write:" + _isWrite + " try-write:"
137: + _tryWriteCount + ")");
138: }
139:
140: notifyAll();
141: }
142: }
143:
144: /**
145: * Tries to get a write lock.
146: *
147: * @param timeout how long to wait for a timeout
148: */
149: void lockReadAndWrite(Transaction xa, long timeout)
150: throws SQLException {
151: if (log.isLoggable(Level.FINEST)) {
152: log.finest(this + " lockReadAndWrite (read:" + _readCount
153: + " write:" + _isWrite + " try-write:"
154: + _tryWriteCount + ")");
155: }
156:
157: long start = Alarm.getCurrentTime();
158: long expire = start + timeout;
159: boolean isOkay = false;
160:
161: synchronized (this ) {
162: _tryWriteCount++;
163:
164: // XXX: temp debug only
165: if (_owner == null)
166: _owner = Thread.currentThread();
167:
168: try {
169: while (true) {
170: if (!_isWrite && _readCount == _tryUpgradeCount) {
171: _readCount++;
172: _isWrite = true;
173: _owner = Thread.currentThread();
174: return;
175: }
176:
177: long delta = expire - Alarm.getCurrentTime();
178:
179: if (delta < 0 || Alarm.isTest())
180: break;
181:
182: try {
183: wait(delta);
184: } catch (InterruptedException e) {
185: throw new LockTimeoutException(e);
186: }
187: }
188:
189: if (!Alarm.isTest()) {
190: printOwnerStack();
191: Thread.dumpStack();
192: }
193:
194: throw new LockTimeoutException(
195: L
196: .l(
197: "{0} lockReadAndWrite timed out ({1}ms) readers:{2} is-write:{3} try-writers:{4} try-upgrade:{5}",
198: this ,
199: (Alarm.getCurrentTime() - start),
200: _readCount, _isWrite,
201: _tryWriteCount,
202: _tryUpgradeCount));
203: } finally {
204: _tryWriteCount--;
205:
206: notifyAll();
207: }
208: }
209: }
210:
211: /**
212: * Tries to get a write lock, but does not wait if other threads are
213: * reading or writing. insert() uses this call to avoid blocking when
214: * allocating a new row.
215: *
216: * @return true if the write was successful
217: */
218: public boolean lockReadAndWriteNoWait() throws SQLException {
219: if (log.isLoggable(Level.FINEST)) {
220: log.finest(this + " lockReadAndWriteNoWait (read:"
221: + _readCount + " write:" + _isWrite + " try-write:"
222: + _tryWriteCount + ")");
223: }
224:
225: synchronized (this ) {
226: // XXX: temp debug only
227: if (_owner == null)
228: _owner = Thread.currentThread();
229:
230: if (_readCount == 0 && !_isWrite) {
231: _owner = Thread.currentThread();
232: _readCount++;
233: _isWrite = true;
234: return true;
235: }
236: }
237:
238: return false;
239: }
240:
241: /**
242: * Clears a write lock.
243: */
244: void unlockReadAndWrite() throws SQLException {
245: synchronized (this ) {
246: _readCount--;
247: _isWrite = false;
248: _owner = null;
249:
250: notifyAll();
251: }
252:
253: if (log.isLoggable(Level.FINEST)) {
254: log.finest(this + " unlockReadAndWrite (read:" + _readCount
255: + " write:" + _isWrite + " try-write:"
256: + _tryWriteCount + ")");
257: }
258: }
259:
260: /**
261: * Tries to get a write lock when already have a read lock.
262: *
263: * @param timeout how long to wait for a timeout
264: */
265: void lockWrite(Transaction xa, long timeout) throws SQLException {
266: if (log.isLoggable(Level.FINEST)) {
267: log.finest(this + " lockWrite (read:" + _readCount
268: + " write:" + _isWrite + " try-write:"
269: + _tryWriteCount + ")");
270: }
271:
272: long start = Alarm.getCurrentTime();
273: long expire = start + timeout;
274:
275: synchronized (this ) {
276: _tryWriteCount++;
277: _tryUpgradeCount++;
278:
279: // XXX: temp debug only
280: if (_owner == null)
281: _owner = Thread.currentThread();
282:
283: try {
284: while (true) {
285: if (!_isWrite && _readCount == _tryUpgradeCount) {
286: _isWrite = true;
287: _owner = Thread.currentThread();
288: return;
289: }
290:
291: long delta = expire - Alarm.getCurrentTime();
292:
293: if (delta < 0 || Alarm.isTest())
294: break;
295:
296: try {
297: wait(delta);
298: } catch (InterruptedException e) {
299: throw new LockTimeoutException(e);
300: }
301: }
302:
303: if (!Alarm.isTest()) {
304: printOwnerStack();
305: Thread.dumpStack();
306: }
307:
308: throw new LockTimeoutException(
309: L
310: .l(
311: "{0} lockWrite timed out ({1}ms) readers:{2} try-writers:{3} upgrade:{4}",
312: this , Alarm.getCurrentTime()
313: - start, _readCount,
314: _tryWriteCount,
315: _tryUpgradeCount));
316: } finally {
317: _tryWriteCount--;
318: _tryUpgradeCount--;
319:
320: notifyAll();
321: }
322: }
323: }
324:
325: /**
326: * Clears a write lock.
327: */
328: void unlockWrite() throws SQLException {
329: synchronized (this ) {
330: _isWrite = false;
331: _owner = null;
332:
333: notifyAll();
334: }
335:
336: if (log.isLoggable(Level.FINEST)) {
337: log.finest(this + " unlockWrite (read:" + _readCount
338: + " write:" + _isWrite + " try-write:"
339: + _tryWriteCount + ")");
340: }
341: }
342:
343: /**
344: * Waits until all the writers drain before committing, see Block.commit()
345: */
346: void waitForCommit() {
347: Thread.yield();
348:
349: synchronized (this ) {
350: while (true) {
351: if (!_isWrite && _tryWriteCount == 0) {
352: return;
353: }
354:
355: if (Alarm.isTest())
356: return;
357:
358: try {
359: wait(1000L);
360: } catch (InterruptedException e) {
361: log.log(Level.FINER, e.toString(), e);
362: }
363: }
364: }
365: }
366:
367: private void printOwnerStack() {
368: Thread thread = _owner;
369:
370: if (thread == null)
371: return;
372:
373: System.out.println("Owner-stack");
374: StackTraceElement[] stack = thread.getStackTrace();
375: for (int i = 0; i < stack.length; i++)
376: System.out.println(stack[i]);
377: }
378:
379: public String toString() {
380: return "Lock[" + _id + "]";
381: }
382: }
|