001: /**
002: * JDBM LICENSE v1.00
003: *
004: * Redistribution and use of this software and associated documentation
005: * ("Software"), with or without modification, are permitted provided
006: * that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain copyright
009: * statements and notices. Redistributions must also contain a
010: * copy of this document.
011: *
012: * 2. Redistributions in binary form must reproduce the
013: * above copyright notice, this list of conditions and the
014: * following disclaimer in the documentation and/or other
015: * materials provided with the distribution.
016: *
017: * 3. The name "JDBM" must not be used to endorse or promote
018: * products derived from this Software without prior written
019: * permission of Cees de Groot. For written permission,
020: * please contact cg@cdegroot.com.
021: *
022: * 4. Products derived from this Software may not be called "JDBM"
023: * nor may "JDBM" appear in their names without prior written
024: * permission of Cees de Groot.
025: *
026: * 5. Due credit should be given to the JDBM Project
027: * (http://jdbm.sourceforge.net/).
028: *
029: * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
030: * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
031: * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
032: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
033: * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
034: * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
036: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
037: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
038: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
039: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
040: * OF THE POSSIBILITY OF SUCH DAMAGE.
041: *
042: * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
043: * Contributions are Copyright (C) 2000 by their associated contributors.
044: *
045: * $Id: TransactionManager.java,v 1.7 2005/06/25 23:12:32 doomdark Exp $
046: */package jdbm.recman;
047:
048: import java.io.*;
049: import java.util.*;
050:
051: /**
052: * This class manages the transaction log that belongs to every
053: * {@link RecordFile}. The transaction log is either clean, or
054: * in progress. In the latter case, the transaction manager
055: * takes care of a roll forward.
056: *<p>
057: * Implementation note: this is a proof-of-concept implementation
058: * which hasn't been optimized for speed. For instance, all sorts
059: * of streams are created for every transaction.
060: */
061: // TODO: Handle the case where we are recovering lg9 and lg0, were we
062: // should start with lg9 instead of lg0!
063: public final class TransactionManager {
064: private RecordFile owner;
065:
066: // streams for transaction log.
067: private FileOutputStream fos;
068: private ObjectOutputStream oos;
069:
070: /**
071: * By default, we keep 10 transactions in the log file before
072: * synchronizing it with the main database file.
073: */
074: static final int DEFAULT_TXNS_IN_LOG = 10;
075:
076: /**
077: * Maximum number of transactions before the log file is
078: * synchronized with the main database file.
079: */
080: private int _maxTxns = DEFAULT_TXNS_IN_LOG;
081:
082: /**
083: * In-core copy of transactions. We could read everything back from
084: * the log file, but the RecordFile needs to keep the dirty blocks in
085: * core anyway, so we might as well point to them and spare us a lot
086: * of hassle.
087: */
088: private ArrayList[] txns = new ArrayList[DEFAULT_TXNS_IN_LOG];
089: private int curTxn = -1;
090:
091: /** Extension of a log file. */
092: static final String extension = ".lg";
093:
094: /**
095: * Instantiates a transaction manager instance. If recovery
096: * needs to be performed, it is done.
097: *
098: * @param owner the RecordFile instance that owns this transaction mgr.
099: */
100: TransactionManager(RecordFile owner) throws IOException {
101: this .owner = owner;
102: recover();
103: open();
104: }
105:
106: /**
107: * Synchronize log file data with the main database file.
108: * <p>
109: * After this call, the main database file is guaranteed to be
110: * consistent and guaranteed to be the only file needed for
111: * backup purposes.
112: */
113: public void synchronizeLog() throws IOException {
114: synchronizeLogFromMemory();
115: }
116:
117: /**
118: * Set the maximum number of transactions to record in
119: * the log (and keep in memory) before the log is
120: * synchronized with the main database file.
121: * <p>
122: * This method must be called while there are no
123: * pending transactions in the log.
124: */
125: public void setMaximumTransactionsInLog(int maxTxns)
126: throws IOException {
127: if (maxTxns <= 0) {
128: throw new IllegalArgumentException(
129: "Argument 'maxTxns' must be greater than 0.");
130: }
131: if (curTxn != -1) {
132: throw new IllegalStateException(
133: "Cannot change setting while transactions are pending in the log");
134: }
135: _maxTxns = maxTxns;
136: txns = new ArrayList[maxTxns];
137: }
138:
139: /** Builds logfile name */
140: private String makeLogName() {
141: return owner.getFileName() + extension;
142: }
143:
144: /** Synchs in-core transactions to data file and opens a fresh log */
145: private void synchronizeLogFromMemory() throws IOException {
146: close();
147:
148: TreeSet blockList = new TreeSet(new BlockIoComparator());
149:
150: int numBlocks = 0;
151: int writtenBlocks = 0;
152: for (int i = 0; i < _maxTxns; i++) {
153: if (txns[i] == null)
154: continue;
155: // Add each block to the blockList, replacing the old copy of this
156: // block if necessary, thus avoiding writing the same block twice
157: for (Iterator k = txns[i].iterator(); k.hasNext();) {
158: BlockIo block = (BlockIo) k.next();
159: if (blockList.contains(block)) {
160: block.decrementTransactionCount();
161: } else {
162: writtenBlocks++;
163: boolean result = blockList.add(block);
164: }
165: numBlocks++;
166: }
167:
168: txns[i] = null;
169: }
170: // Write the blocks from the blockList to disk
171: synchronizeBlocks(blockList.iterator(), true);
172:
173: owner.sync();
174: open();
175: }
176:
177: /** Opens the log file */
178: private void open() throws IOException {
179: fos = new FileOutputStream(makeLogName());
180: oos = new ObjectOutputStream(fos);
181: oos.writeShort(Magic.LOGFILE_HEADER);
182: oos.flush();
183: curTxn = -1;
184: }
185:
186: /** Startup recovery on all files */
187: private void recover() throws IOException {
188: String logName = makeLogName();
189: File logFile = new File(logName);
190: if (!logFile.exists())
191: return;
192: if (logFile.length() == 0) {
193: logFile.delete();
194: return;
195: }
196:
197: FileInputStream fis = new FileInputStream(logFile);
198: ObjectInputStream ois = new ObjectInputStream(fis);
199:
200: try {
201: if (ois.readShort() != Magic.LOGFILE_HEADER)
202: throw new Error("Bad magic on log file");
203: } catch (IOException e) {
204: // corrupted/empty logfile
205: logFile.delete();
206: return;
207: }
208:
209: while (true) {
210: ArrayList blocks = null;
211: try {
212: blocks = (ArrayList) ois.readObject();
213: } catch (ClassNotFoundException e) {
214: throw new Error("Unexcepted exception: " + e);
215: } catch (IOException e) {
216: // corrupted logfile, ignore rest of transactions
217: break;
218: }
219: synchronizeBlocks(blocks.iterator(), false);
220:
221: // ObjectInputStream must match exactly each
222: // ObjectOutputStream created during writes
223: try {
224: ois = new ObjectInputStream(fis);
225: } catch (IOException e) {
226: // corrupted logfile, ignore rest of transactions
227: break;
228: }
229: }
230: owner.sync();
231: logFile.delete();
232: }
233:
234: /** Synchronizes the indicated blocks with the owner. */
235: private void synchronizeBlocks(Iterator blockIterator,
236: boolean fromCore) throws IOException {
237: // write block vector elements to the data file.
238: while (blockIterator.hasNext()) {
239: BlockIo cur = (BlockIo) blockIterator.next();
240: owner.synch(cur);
241: if (fromCore) {
242: cur.decrementTransactionCount();
243: if (!cur.isInTransaction()) {
244: owner.releaseFromTransaction(cur, true);
245: }
246: }
247: }
248: }
249:
250: /** Set clean flag on the blocks. */
251: private void setClean(ArrayList blocks) throws IOException {
252: for (Iterator k = blocks.iterator(); k.hasNext();) {
253: BlockIo cur = (BlockIo) k.next();
254: cur.setClean();
255: }
256: }
257:
258: /** Discards the indicated blocks and notify the owner. */
259: private void discardBlocks(ArrayList blocks) throws IOException {
260: for (Iterator k = blocks.iterator(); k.hasNext();) {
261: BlockIo cur = (BlockIo) k.next();
262: cur.decrementTransactionCount();
263: if (!cur.isInTransaction()) {
264: owner.releaseFromTransaction(cur, false);
265: }
266: }
267: }
268:
269: /**
270: * Starts a transaction. This can block if all slots have been filled
271: * with full transactions, waiting for the synchronization thread to
272: * clean out slots.
273: */
274: void start() throws IOException {
275: curTxn++;
276: if (curTxn == _maxTxns) {
277: synchronizeLogFromMemory();
278: curTxn = 0;
279: }
280: txns[curTxn] = new ArrayList();
281: }
282:
283: /**
284: * Indicates the block is part of the transaction.
285: */
286: void add(BlockIo block) throws IOException {
287: block.incrementTransactionCount();
288: txns[curTxn].add(block);
289: }
290:
291: /**
292: * Commits the transaction to the log file.
293: */
294: void commit() throws IOException {
295: oos.writeObject(txns[curTxn]);
296: sync();
297:
298: // set clean flag to indicate blocks have been written to log
299: setClean(txns[curTxn]);
300:
301: // open a new ObjectOutputStream in order to store
302: // newer states of BlockIo
303: oos = new ObjectOutputStream(fos);
304: }
305:
306: /** Flushes and syncs */
307: private void sync() throws IOException {
308: oos.flush();
309: fos.flush();
310: fos.getFD().sync();
311: }
312:
313: /**
314: * Shutdowns the transaction manager. Resynchronizes outstanding
315: * logs.
316: */
317: void shutdown() throws IOException {
318: synchronizeLogFromMemory();
319: close();
320: }
321:
322: /**
323: * Closes open files.
324: */
325: private void close() throws IOException {
326: sync();
327: oos.close();
328: fos.close();
329: oos = null;
330: fos = null;
331: }
332:
333: /**
334: * Force closing the file without synchronizing pending transaction data.
335: * Used for testing purposes only.
336: */
337: void forceClose() throws IOException {
338: oos.close();
339: fos.close();
340: oos = null;
341: fos = null;
342: }
343:
344: /**
345: * Use the disk-based transaction log to synchronize the data file.
346: * Outstanding memory logs are discarded because they are believed
347: * to be inconsistent.
348: */
349: void synchronizeLogFromDisk() throws IOException {
350: close();
351:
352: for (int i = 0; i < _maxTxns; i++) {
353: if (txns[i] == null)
354: continue;
355: discardBlocks(txns[i]);
356: txns[i] = null;
357: }
358:
359: recover();
360: open();
361: }
362:
363: /** INNER CLASS.
364: * Comparator class for use by the tree set used to store the blocks
365: * to write for this transaction. The BlockIo objects are ordered by
366: * their blockIds.
367: */
368: public static class BlockIoComparator implements Comparator {
369:
370: public int compare(Object o1, Object o2) {
371: BlockIo block1 = (BlockIo) o1;
372: BlockIo block2 = (BlockIo) o2;
373: int result = 0;
374: if (block1.getBlockId() == block2.getBlockId()) {
375: result = 0;
376: } else if (block1.getBlockId() < block2.getBlockId()) {
377: result = -1;
378: } else {
379: result = 1;
380: }
381: return result;
382: }
383:
384: public boolean equals(Object obj) {
385: return super .equals(obj);
386: }
387: } // class BlockIOComparator
388:
389: }
|