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.db.jdbc.ConnectionImpl;
033: import com.caucho.log.Log;
034: import com.caucho.sql.SQLExceptionWrapper;
035: import com.caucho.util.L10N;
036: import com.caucho.util.LongKeyHashMap;
037:
038: import java.io.IOException;
039: import java.sql.SQLException;
040: import java.util.ArrayList;
041: import java.util.Iterator;
042: import java.util.logging.Level;
043: import java.util.logging.Logger;
044:
045: /**
046: * Represents a single transaction.
047: */
048: public class Transaction extends StoreTransaction {
049: private static final Logger log = Log.open(Transaction.class);
050: private static final L10N L = new L10N(Transaction.class);
051:
052: private static long AUTO_COMMIT_TIMEOUT = 30000L;
053:
054: private boolean _isAutoCommit = true;
055: private ConnectionImpl _conn;
056:
057: private ArrayList<Lock> _readLocks;
058: private ArrayList<Lock> _writeLocks;
059:
060: private LongKeyHashMap<WriteBlock> _writeBlocks;
061:
062: private ArrayList<Block> _updateBlocks;
063:
064: // inodes that need to be deleted on a commit
065: private ArrayList<Inode> _deleteInodes;
066:
067: // inodes that need to be deleted on a rollback
068: private ArrayList<Inode> _addInodes;
069:
070: // blocks that need deallocating on a commit
071: private ArrayList<Block> _deallocateBlocks;
072:
073: private boolean _isRollbackOnly;
074: private SQLException _rollbackExn;
075:
076: private long _timeout = AUTO_COMMIT_TIMEOUT;
077:
078: private Transaction() {
079: }
080:
081: public static Transaction create(ConnectionImpl conn) {
082: Transaction xa = new Transaction();
083:
084: xa.init(conn);
085:
086: return xa;
087: }
088:
089: public static Transaction create() {
090: Transaction xa = new Transaction();
091:
092: return xa;
093: }
094:
095: private void init(ConnectionImpl conn) {
096: _conn = conn;
097: _timeout = AUTO_COMMIT_TIMEOUT;
098: _isRollbackOnly = false;
099: _rollbackExn = null;
100: }
101:
102: /**
103: * Sets the transaction timeout.
104: */
105: public void setTimeout(long timeout) {
106: _timeout = timeout;
107: }
108:
109: /**
110: * Acquires a new read lock.
111: */
112: /*
113: public void addReadLock(Lock lock)
114: {
115: _readLocks.add(lock);
116: }
117: */
118:
119: /**
120: * Acquires a new read lock.
121: */
122: public boolean hasReadLock(Lock lock) {
123: return _readLocks.contains(lock);
124: }
125:
126: /**
127: * Returns true for an auto-commit transaction.
128: */
129: public boolean isAutoCommit() {
130: return _isAutoCommit;
131: }
132:
133: /**
134: * Returns true for an auto-commit transaction.
135: */
136: public void setAutoCommit(boolean autoCommit) {
137: _isAutoCommit = autoCommit;
138: }
139:
140: /**
141: * Acquires a new write lock.
142: */
143: public void lockRead(Lock lock) throws SQLException {
144: if (_isRollbackOnly) {
145: if (_rollbackExn != null)
146: throw _rollbackExn;
147: else
148: throw new SQLException(L
149: .l("can't get lock with rollback transaction"));
150: }
151:
152: try {
153: if (_readLocks == null)
154: _readLocks = new ArrayList<Lock>();
155:
156: if (_readLocks.contains(lock))
157: throw new SQLException(
158: L
159: .l("lockRead must not already have a read lock"));
160:
161: lock.lockRead(this , _timeout);
162: _readLocks.add(lock);
163: } catch (SQLException e) {
164: setRollbackOnly(e);
165:
166: throw e;
167: }
168: }
169:
170: /**
171: * Acquires a new write lock.
172: */
173: public void lockReadAndWrite(Lock lock) throws SQLException {
174: if (_isRollbackOnly) {
175: if (_rollbackExn != null)
176: throw _rollbackExn;
177: else
178: throw new SQLException(L
179: .l("can't get lock with rollback transaction"));
180: }
181:
182: try {
183: if (_readLocks == null)
184: _readLocks = new ArrayList<Lock>();
185: if (_writeLocks == null)
186: _writeLocks = new ArrayList<Lock>();
187:
188: if (_readLocks.contains(lock))
189: throw new SQLException(
190: L
191: .l("lockReadAndWrite cannot already have a read lock"));
192:
193: if (_writeLocks.contains(lock))
194: throw new SQLException(
195: L
196: .l("lockReadAndWrite cannot already have a write lock"));
197:
198: lock.lockReadAndWrite(this , _timeout);
199: _readLocks.add(lock);
200: _writeLocks.add(lock);
201: } catch (SQLException e) {
202: setRollbackOnly(e);
203:
204: throw e;
205: }
206: }
207:
208: /**
209: * Conditionally a new write lock, if no contention exists.
210: */
211: public boolean lockReadAndWriteNoWait(Lock lock)
212: throws SQLException {
213: if (_isRollbackOnly) {
214: if (_rollbackExn != null)
215: throw _rollbackExn;
216: else
217: throw new SQLException(L
218: .l("can't get lock with rollback transaction"));
219: }
220:
221: try {
222: if (_readLocks == null)
223: _readLocks = new ArrayList<Lock>();
224: if (_writeLocks == null)
225: _writeLocks = new ArrayList<Lock>();
226:
227: if (_readLocks.contains(lock))
228: throw new SQLException(
229: L
230: .l("lockReadAndWrite cannot already have a read lock"));
231:
232: if (_writeLocks.contains(lock))
233: throw new SQLException(
234: L
235: .l("lockReadAndWrite cannot already have a write lock"));
236:
237: if (lock.lockReadAndWriteNoWait()) {
238: _readLocks.add(lock);
239: _writeLocks.add(lock);
240:
241: return true;
242: }
243: } catch (SQLException e) {
244: setRollbackOnly(e);
245:
246: throw e;
247: }
248:
249: return false;
250: }
251:
252: /**
253: * Acquires a new write lock.
254: */
255: public void lockWrite(Lock lock) throws SQLException {
256: if (_isRollbackOnly) {
257: if (_rollbackExn != null)
258: throw _rollbackExn;
259: else
260: throw new SQLException(L
261: .l("can't get lock with rollback transaction"));
262: }
263:
264: try {
265: if (_readLocks == null)
266: _readLocks = new ArrayList<Lock>();
267: if (_writeLocks == null)
268: _writeLocks = new ArrayList<Lock>();
269:
270: if (!_readLocks.contains(lock)) {
271: Thread.dumpStack();
272: throw new SQLException(L
273: .l("lockWrite must already have a read lock"));
274: }
275:
276: if (_writeLocks.contains(lock))
277: throw new SQLException(
278: L
279: .l("lockWrite cannot already have a write lock"));
280:
281: lock.lockWrite(this , _timeout);
282: _writeLocks.add(lock);
283: } catch (SQLException e) {
284: setRollbackOnly(e);
285:
286: throw e;
287: }
288: }
289:
290: /**
291: * Adds a block for update.
292: */
293: public void addUpdateBlock(Block block) {
294: if (block == null)
295: return;
296:
297: if (_updateBlocks == null)
298: _updateBlocks = new ArrayList<Block>();
299:
300: if (_updateBlocks.size() == 0
301: || _updateBlocks.get(_updateBlocks.size() - 1) != block)
302: _updateBlocks.add(block);
303: }
304:
305: /**
306: * If auto-commit, commit the read
307: */
308: public void autoCommitRead(Lock lock) throws SQLException {
309: unlockRead(lock);
310: }
311:
312: public void unlockRead(Lock lock) throws SQLException {
313: if (_readLocks.remove(lock))
314: lock.unlockRead();
315: }
316:
317: /**
318: * If auto-commit, commit the write
319: */
320: public void autoCommitWrite(Lock lock) throws SQLException {
321: _readLocks.remove(lock);
322:
323: if (_writeLocks.remove(lock)) {
324: try {
325: commit();
326: } finally {
327: lock.unlockWrite();
328: }
329: }
330: }
331:
332: public void unlockReadAndWrite(Lock lock) throws SQLException {
333: _readLocks.remove(lock);
334:
335: if (_writeLocks.remove(lock)) {
336: lock.unlockReadAndWrite();
337: }
338: }
339:
340: public void unlockWrite(Lock lock) throws SQLException {
341: if (_writeLocks.remove(lock)) {
342: lock.unlockWrite();
343: }
344: }
345:
346: /**
347: * Returns a read block.
348: */
349: public Block readBlock(Store store, long blockAddress)
350: throws IOException {
351: long blockId = store.addressToBlockId(blockAddress);
352:
353: Block block;
354:
355: if (_writeBlocks != null)
356: block = _writeBlocks.get(blockId);
357: else
358: block = null;
359:
360: if (block != null)
361: block.allocate();
362: else
363: block = store.readBlock(blockId);
364:
365: return block;
366: }
367:
368: /**
369: * Returns a modified block.
370: */
371: public WriteBlock getWriteBlock(long blockId) {
372: if (_writeBlocks == null)
373: return null;
374:
375: return _writeBlocks.get(blockId);
376: }
377:
378: /**
379: * Returns a modified block.
380: */
381: public WriteBlock createWriteBlock(Block block) throws IOException {
382: if (block instanceof WriteBlock)
383: return (WriteBlock) block;
384:
385: WriteBlock writeBlock = getWriteBlock(block.getBlockId());
386:
387: if (writeBlock != null) {
388: block.free();
389: writeBlock.allocate();
390: return writeBlock;
391: }
392:
393: if (isAutoCommit())
394: writeBlock = new AutoCommitWriteBlock(block);
395: else {
396: // XXX: locking
397: writeBlock = new XAWriteBlock(block);
398: setBlock(writeBlock);
399: }
400:
401: return writeBlock;
402: }
403:
404: /**
405: * Returns a modified block.
406: */
407: public Block createAutoCommitWriteBlock(Block block)
408: throws IOException {
409: if (block instanceof WriteBlock) {
410: return block;
411: } else {
412: WriteBlock writeBlock = getWriteBlock(block.getBlockId());
413:
414: if (writeBlock != null) {
415: block.free();
416: writeBlock.allocate();
417:
418: return writeBlock;
419: }
420:
421: writeBlock = new AutoCommitWriteBlock(block);
422:
423: // setBlock(writeBlock);
424:
425: return writeBlock;
426: }
427: }
428:
429: /**
430: * Returns a modified block.
431: */
432: public Block allocateRow(Store store) throws IOException {
433: return store.allocateRow();
434: }
435:
436: /**
437: * Returns a modified block.
438: */
439: public void deallocateBlock(Block block) throws IOException {
440: if (isAutoCommit())
441: block.getStore().freeBlock(block.getBlockId());
442: else {
443: if (_deallocateBlocks == null)
444: _deallocateBlocks = new ArrayList<Block>();
445:
446: _deallocateBlocks.add(block);
447: }
448: }
449:
450: /**
451: * Returns a modified block.
452: */
453: public Block createWriteBlock(Store store, long blockAddress)
454: throws IOException {
455: Block block = readBlock(store, blockAddress);
456:
457: return createWriteBlock(block);
458: }
459:
460: /**
461: * Returns a modified block.
462: */
463: private void setBlock(WriteBlock block) {
464: // block.setDirty();
465:
466: if (_writeBlocks == null)
467: _writeBlocks = new LongKeyHashMap<WriteBlock>(8);
468:
469: _writeBlocks.put(block.getBlockId(), block);
470: }
471:
472: /**
473: * Adds inode which should be deleted on a commit.
474: */
475: public void addDeleteInode(Inode inode) {
476: if (_deleteInodes == null)
477: _deleteInodes = new ArrayList<Inode>();
478:
479: _deleteInodes.add(inode);
480: }
481:
482: /**
483: * Adds inode which should be deleted on a rollback.
484: */
485: public void addAddInode(Inode inode) {
486: if (_addInodes == null)
487: _addInodes = new ArrayList<Inode>();
488:
489: _addInodes.add(inode);
490: }
491:
492: public void autoCommit() throws SQLException {
493: if (_isAutoCommit) {
494: ConnectionImpl conn = _conn;
495: _conn = null;
496:
497: if (conn != null) {
498: conn.setTransaction(null);
499: }
500: }
501: }
502:
503: public void setRollbackOnly(SQLException e) {
504: if (_rollbackExn == null)
505: _rollbackExn = e;
506:
507: _isRollbackOnly = true;
508:
509: releaseLocks();
510:
511: // XXX: release write blocks
512: _writeBlocks = null;
513: }
514:
515: public void setRollbackOnly() {
516: setRollbackOnly(null);
517: }
518:
519: public void commit() throws SQLException {
520: try {
521: writeData();
522: } finally {
523: releaseLocks();
524:
525: close();
526: }
527: }
528:
529: public void writeData() throws SQLException {
530: LongKeyHashMap<WriteBlock> writeBlocks = _writeBlocks;
531:
532: if (_deleteInodes != null) {
533: while (_deleteInodes.size() > 0) {
534: Inode inode = _deleteInodes.remove(0);
535:
536: // XXX: should be allocating based on auto-commit
537: inode.remove();
538: }
539: }
540:
541: ArrayList<Block> updateBlocks = _updateBlocks;
542: _updateBlocks = null;
543:
544: if (updateBlocks != null) {
545: while (updateBlocks.size() > 0) {
546: Block block = updateBlocks
547: .remove(updateBlocks.size() - 1);
548:
549: try {
550: block.commit();
551: } catch (IOException e) {
552: log.log(Level.WARNING, e.toString(), e);
553: }
554: }
555: }
556:
557: if (writeBlocks != null) {
558: Iterator<WriteBlock> blockIter = writeBlocks
559: .valueIterator();
560:
561: while (blockIter.hasNext()) {
562: WriteBlock block = blockIter.next();
563:
564: try {
565: block.commit();
566: } catch (IOException e) {
567: log.log(Level.WARNING, e.toString(), e);
568: }
569: }
570:
571: // writeBlocks.clear();
572: }
573:
574: if (_deallocateBlocks != null) {
575: while (_deallocateBlocks.size() > 0) {
576: Block block = _deallocateBlocks.remove(0);
577:
578: try {
579: block.getStore().freeBlock(block.getBlockId());
580: } catch (IOException e) {
581: throw new SQLExceptionWrapper(e);
582: }
583: }
584: }
585: }
586:
587: public void rollback() throws SQLException {
588: releaseLocks();
589:
590: close();
591: }
592:
593: private void releaseLocks() {
594: // need to unlock write before upgrade to block other threads
595: if (_writeLocks != null) {
596: for (int i = 0; i < _writeLocks.size(); i++) {
597: Lock lock = _writeLocks.get(i);
598:
599: if (_readLocks != null)
600: _readLocks.remove(lock);
601:
602: try {
603: lock.unlockReadAndWrite();
604: } catch (Throwable e) {
605: log.log(Level.WARNING, e.toString(), e);
606: }
607: }
608:
609: _writeLocks.clear();
610: }
611:
612: if (_readLocks != null) {
613: for (int i = 0; i < _readLocks.size(); i++) {
614: Lock lock = _readLocks.get(i);
615:
616: try {
617: lock.unlockRead();
618: } catch (Throwable e) {
619: log.log(Level.WARNING, e.toString(), e);
620: }
621: }
622:
623: _readLocks.clear();
624: }
625: }
626:
627: void close() {
628: LongKeyHashMap<WriteBlock> writeBlocks = _writeBlocks;
629: _writeBlocks = null;
630:
631: if (writeBlocks != null) {
632: Iterator<WriteBlock> blockIter = writeBlocks
633: .valueIterator();
634:
635: while (blockIter.hasNext()) {
636: WriteBlock block = blockIter.next();
637:
638: block.destroy();
639: }
640:
641: // writeBlocks.clear();
642: }
643:
644: _isRollbackOnly = false;
645: _rollbackExn = null;
646: }
647: }
|