Quadcap Embeddable Database

com/quadcap/sql/file/Log1.java

Go to the documentation of this file.
00001 package com.quadcap.sql.file; 00002 00003 /* Copyright 1999 - 2003 Quadcap Software. All rights reserved. 00004 * 00005 * This software is distributed under the Quadcap Free Software License. 00006 * This software may be used or modified for any purpose, personal or 00007 * commercial. Open Source redistributions are permitted. Commercial 00008 * redistribution of larger works derived from, or works which bundle 00009 * this software requires a "Commercial Redistribution License"; see 00010 * http://www.quadcap.com/purchase. 00011 * 00012 * Redistributions qualify as "Open Source" under one of the following terms: 00013 * 00014 * Redistributions are made at no charge beyond the reasonable cost of 00015 * materials and delivery. 00016 * 00017 * Redistributions are accompanied by a copy of the Source Code or by an 00018 * irrevocable offer to provide a copy of the Source Code for up to three 00019 * years at the cost of materials and delivery. Such redistributions 00020 * must allow further use, modification, and redistribution of the Source 00021 * Code under substantially the same terms as this license. 00022 * 00023 * Redistributions of source code must retain the copyright notices as they 00024 * appear in each source code file, these license terms, and the 00025 * disclaimer/limitation of liability set forth as paragraph 6 below. 00026 * 00027 * Redistributions in binary form must reproduce this Copyright Notice, 00028 * these license terms, and the disclaimer/limitation of liability set 00029 * forth as paragraph 6 below, in the documentation and/or other materials 00030 * provided with the distribution. 00031 * 00032 * The Software is provided on an "AS IS" basis. No warranty is 00033 * provided that the Software is free of defects, or fit for a 00034 * particular purpose. 00035 * 00036 * Limitation of Liability. Quadcap Software shall not be liable 00037 * for any damages suffered by the Licensee or any third party resulting 00038 * from use of the Software. 00039 */ 00040 00041 import java.io.File; 00042 import java.io.FileOutputStream; 00043 import java.io.IOException; 00044 import java.io.RandomAccessFile; 00045 00046 import java.util.ArrayList; 00047 import java.util.List; 00048 import java.util.Map; 00049 import java.util.Properties; 00050 00051 import javax.concurrent.BoundedBuffer; 00052 import javax.concurrent.Channel; 00053 import javax.concurrent.Latch; 00054 00055 import com.quadcap.sql.lock.Transaction; 00056 import com.quadcap.sql.lock.TransactionObserver; 00057 00058 import com.quadcap.util.collections.LongMap; 00059 00060 import com.quadcap.util.ConfigNumber; 00061 import com.quadcap.util.Debug; 00062 import com.quadcap.util.Util; 00063 00064 /** 00065 * Rolling write-ahead log implementation. Checkpoints plus physical logging 00066 * of block writes is coupled with logical logging to support transactions 00067 * rollback and recovery. 00068 * 00069 * @author Stan Bailes 00070 */ 00071 public class Log1 implements Log { 00072 /** the database we're logging for */ 00073 private Datafile db; 00074 00075 /** the database file we're logging for */ 00076 private BlockFile dbFile; 00077 00078 /** Log writer/reader */ 00079 Logger logger; 00080 00081 /** log file directory */ 00082 private File dbRootDir; 00083 00084 /** log sync thread */ 00085 LogSync logSync; 00086 00087 /** log sync thread done, close interlock */ 00088 Latch closeLatch = new Latch(); 00089 00090 /** producer/consumer conduit */ 00092 00093 /** our datatbase's file lock */ 00094 Object fileLock; 00095 00096 /** Map rows during recovery, null otherwise. */ 00097 LongMap rowIdMap = new LongMap(256); 00098 00099 /** "before-images" file */ 00100 FileOutputStream bfo; 00101 boolean bfoActive = false; 00102 00103 boolean recovering = false; 00104 00105 int blockSize; 00106 00107 /*{com.quadcap.util.Config-vars.xml-1020} 00108 * <config-var> 00109 * <config-name>qed.minSyncInterval</config-name> 00110 * <config-dflt>15000 (15 seconds)</config-dflt> 00111 * <config-desc>The minimum interval (in ms) between database 00112 * sync operations.</config-desc> 00113 * </config-var> 00114 */ 00115 long minSyncInterval 00116 = ConfigNumber.find("qed.minSyncInterval", "15000").longValue(); 00117 00118 /*{com.quadcap.util.Config-vars.xml-1021} 00119 * <config-var> 00120 * <config-name>qed.maxSyncInterval</config-name> 00121 * <config-dflt>60000 (60 seconds)</config-dflt> 00122 * <config-desc>The maximum interval (in ms) between database 00123 * sync operations. Use this to help 00124 * limit the logfile, scratch, and before-images files. 00125 * </config-desc> 00126 * </config-var> 00127 */ 00128 long maxSyncInterval 00129 = ConfigNumber.find("qed.maxSyncInterval", "60000").longValue(); 00130 00131 int syncMap = 0 + 00132 (1 << 1) + // flush -> before.sync 00133 (1 << 2) + // flush -> logger.sync 00134 // (1 << 3) + // flush -> db.tempFile.flush() 00135 (1 << 4) + // checkpoint -> dbfile.flush(fastSync) 00136 (1 << 5) + // checkpoint -> logger.sync 00137 (1 << 6) + // checkpoint -> db.checkpoint(fastSync) 00138 (1 << 7) + // save block -> store.sync 00139 0; 00140 00141 //#ifdef DEBUG 00142 String[] syncStrs = { 00143 "Log1.reallyFlush from Log2.flushLog", 00144 "Log1.reallyFlush: bfo.sync()", 00145 "Log1.reallyFlush: logger.sync()", 00146 "Log1.reallyFlush: db.tempFile.flush(!)", 00147 "Log1.reallyCheckpoint: dbfile.flush(!)", 00148 "Log1.reallyCheckpoint: logger.sync()", 00149 "Log1.reallyCheckpoint: dbfile.checkpoint(!)", 00150 "Log1.saveBlock: bfo.sync" 00151 }; 00152 00153 int[] syncCnts = new int[syncStrs.length]; 00154 //#endif 00155 protected boolean checksync(int x) { 00156 //#ifdef DEBUG 00157 syncCnts[x]++; 00158 //Debug.println("checkSync[" + ret + ": " + syncCnts[x] + " - " + syncStrs[x]); 00159 //#endif 00160 boolean ret = (syncMap & (1 << x)) != 0; 00161 return ret; 00162 } 00163 00164 /** 00165 * Constructor for transaction log 00166 * 00167 */ 00168 public Log1() {} 00169 00170 /** 00171 * Initialize the log 00172 * 00173 * @param db the underlying database that we're logging for. 00174 * @param create true if we're creating this database from scratch, 00175 * in which case we can skip any recovery-related activity 00176 */ 00177 public void init(Datafile db, boolean create, Properties props) 00178 throws IOException 00179 { 00180 this.db = db; 00181 this.dbFile = db.file; 00182 this.blockSize = dbFile.getBlockSize(); 00183 this.dbRootDir = db.dbRootDir; 00184 this.fileLock = db.getFileLock(); 00185 /*{com.quadcap.sql.Datafile-conn.xml-23} 00186 * <config-var> 00187 * <config-name>fastSync</config-name> 00188 * <config-dflt>true</config-dflt> 00189 * <config-desc>If <code>true</code>, omit time-consuming 00190 * sync operations to permit greater throughput. 00191 * </config-desc> 00192 * </config-var> 00193 */ 00194 // this.fastSync = 00195 // props.getProperty("fastSync", 00196 // String.valueOf(fastSync)).equalsIgnoreCase("true"); 00197 00198 /*{com.quadcap.sql.Datafile-conn.xml-25} 00199 * <config-var> 00200 * <config-name>minSyncInterval</config-name> 00201 * <config-dflt>from config:qed.minSyncInterval</config-dflt> 00202 * <config-desc>Minimum interval between syncs. Set to zero to 00203 * ensure a sync after every transaction.</config-desc> 00204 * </config-var> 00205 */ 00206 this.minSyncInterval = 00207 Long.parseLong(props.getProperty("minSyncInterval", 00208 String.valueOf(minSyncInterval))); 00209 00210 /*{com.quadcap.sql.Datafile-conn.xml-26} 00211 * <config-var> 00212 * <config-name>maxSyncInterval</config-name> 00213 * <config-dflt>from config:qed.maxSyncInterval</config-dflt> 00214 * <config-desc>Maximum interval between syncs. Use this to help 00215 * limit the logfile, scratch, and before-images files. 00216 * </config-desc> 00217 * </config-var> 00218 */ 00219 this.maxSyncInterval = 00220 Long.parseLong(props.getProperty("maxSyncInterval", 00221 String.valueOf(maxSyncInterval))); 00222 00223 // Gonna leave this undocumented for now since there's only 00224 // one instance of this class.... Too many knobs. 00225 String loggerClass = props.getProperty("loggerClass"); 00226 try { 00227 this.logger = (Logger)(Class.forName(loggerClass).newInstance()); 00228 } catch (Throwable t) { 00229 this.logger = new Logger1(); 00230 } 00231 logger.init(this, create, props); 00232 00233 } 00234 00235 public void start() { 00236 logSync = new LogSync(this); 00237 logSync.setDaemon(true); 00238 logSync.start(); 00239 } 00240 00241 public void remove() throws IOException { 00242 new File(dbRootDir, "logfile").delete(); 00243 } 00244 00245 /** 00246 * Return the database that we're logging for 00247 */ 00248 public Datafile getDatafile() { return db; } 00249 00250 /** 00251 * Return the database root directory 00252 */ 00253 public File getDbRootDir() { return dbRootDir; } 00254 00255 /** 00256 * Add a transaction's log record to the end of the open log file 00257 */ 00258 int pendingBegins = 0; 00259 public void addEntry(LogEntry entry) 00260 throws IOException 00261 { 00262 if (entry.getCode() == LogEntry.BEGIN_TRANSACTION) { 00263 pendingBegins++; 00264 } 00265 put(entry); 00266 } 00267 00268 /** 00269 * Flush and close the log file. 00270 */ 00271 public void close() throws IOException { 00272 put(opClose); 00273 try { 00274 closeLatch.acquire(); 00275 } catch (InterruptedException ex) { 00276 } 00277 } 00278 00279 /** 00280 * Flush all log records to disk. Action not performed on this 00281 * thread, we're in a hurry.... 00282 */ 00283 public void flushLog() throws IOException { 00284 put(opFlush); 00285 } 00286 00287 /** 00288 * Perform a checkpoint operation. 00289 */ 00290 public void checkpoint() throws IOException { 00291 put(opCheckpoint); 00292 } 00293 00294 /** 00295 * Wait for all queued ops to be processed by the log sync thread 00296 */ 00297 public void sync() throws IOException { 00298 Latch latch = new Latch(); 00299 put(new Sync(latch)); 00300 try { 00301 latch.acquire(); 00302 } catch (InterruptedException ex) { 00303 } 00304 } 00305 00306 /** 00307 * Transaction rollback. 00308 */ 00309 public void rollbackTransaction(Transaction trans) 00310 throws IOException 00311 { 00312 put(new Rollback(trans)); 00313 sync(); 00314 } 00315 00316 /** 00317 * Statement rollback. 00318 */ 00319 public void rollbackStatement(Transaction trans, int stmtId) 00320 throws IOException 00321 { 00322 put(new Rollback(trans, stmtId)); 00323 sync(); 00324 } 00325 00326 /** 00327 * Restart from a previous state 00328 */ 00329 public void restart() throws Exception { 00330 reallyRestart(); 00331 } 00332 00333 //------------------------------------------------------------------ 00334 00335 /** 00336 * Retrieve a row mapping. 00337 */ 00338 public final long getRowMap(long rowId) { 00339 long ret = rowId; 00340 if (rowIdMap != null) { 00341 Long mapped = (Long)rowIdMap.get(rowId); 00342 if (mapped != null) ret = mapped.longValue(); 00343 } 00344 return ret; 00345 } 00346 00347 /** 00348 * Remember a row mapping {old,new} The old row (logRow) is now stored 00349 * in a new place (fileRow), so any stored log entries that refer to 00350 * the old row need to be translated to use the new row. 00351 * 00352 * @param logRow the "old" row 00353 * @param fileRow the "new" row 00354 */ 00355 public final void putRowMap(long logRow, long fileRow) { 00356 if (rowIdMap == null) rowIdMap = new LongMap(256); 00357 rowIdMap.put(logRow, new Long(fileRow)); 00358 } 00359 00360 public final void removeRowMap(long row) { 00361 if (rowIdMap != null) rowIdMap.remove(row); 00362 } 00363 00364 void reallyFlush() throws IOException { 00365 //#ifdef DEBUG 00366 if (Trace.bit(18)) { 00367 Debug.println(this + ".reallyFlush()"); 00368 } 00369 //#endif 00370 if (checksync(2)) logger.sync(); 00371 if (db.tempFile != null) { 00372 db.tempFile.flush(!checksync(3)); 00373 } 00374 } 00375 00376 void reallyClose() throws IOException { 00377 try { 00378 if (logSync != null) { 00379 logSync.close(); 00380 } 00381 } finally { 00382 logSync = null; 00383 if (bfo != null) { 00384 try { 00385 bfo.close(); 00386 } finally { 00387 bfo = null; 00388 } 00389 } 00390 } 00391 } 00392 00393 void reallyRollbackTransaction(Transaction tr) throws Exception { 00394 //#ifdef DEBUG 00395 if (Trace.bit(18)) { 00396 Debug.println("reallyRollbackTransaction(" + tr + ")"); 00397 } 00398 //#endif 00399 long t = tr.getTransactionId(); 00400 LogEntry e = logger.getLastOp(t); 00401 try { 00402 while (e != null) { 00403 //Debug.println(" e = " + e); 00404 if (e.getTransactionId() == t) { 00405 switch (e.getCode()) { 00406 case LogEntry.STEP: 00407 if (e.getRedoState() == LogEntry.DONE) { 00408 try { 00409 e.undo(tr, db); 00410 } catch (Throwable th) { 00411 //#ifdef DEBUG 00412 Debug.println("Exception in rollback transaction"); 00413 Debug.print(th); 00414 //#endif 00415 } 00416 logger.setRedoState(e, LogEntry.UNDONE); 00417 } 00418 break; 00419 case LogEntry.BEGIN_TRANSACTION: 00420 e = null; 00421 break; 00422 default: 00423 break; 00424 } 00425 } 00426 e = (e == null || e.getPrev() < 0) ? null : logger.getPrevOp(e); 00427 } 00428 } finally { 00429 rowIdMap = null; 00430 } 00431 } 00432 00433 void reallyRollbackStatement(Transaction tr, int s) throws Exception { 00434 long t = tr.getTransactionId(); 00435 LogEntry e = logger.getLastOp(t); 00436 rowIdMap = null; 00437 try { 00438 while (e != null) { 00439 if (e.getStatementId() == s) { 00440 switch (e.getCode()) { 00441 case LogEntry.STEP: 00442 if (e.getRedoState() == LogEntry.DONE) { 00443 try { 00444 e.undo(tr, db); 00445 } catch (Throwable th) { 00446 //#ifdef DEBUG 00447 Debug.println("Exception in statement rollback"); 00448 Debug.print(th); 00449 //#endif 00450 } 00451 logger.setRedoState(e, LogEntry.UNDONE); 00452 } 00453 break; 00454 case LogEntry.BEGIN_STATEMENT: 00455 //Debug.println("--- end rollbackStatement(" + tr + "," + 00456 // s + ")"); 00457 return; 00458 default: 00459 break; 00460 } 00461 } 00462 e = logger.getPrevOp(e); 00463 } 00464 } finally { 00465 rowIdMap = null; 00466 } 00467 } 00468 00469 LogEntry scanLog(LongMap t) throws IOException { 00470 LogEntry op = logger.getFirstOp(); 00471 LogEntry last = op; 00472 for (; op != null; last = op, op = logger.getNextOp()) { 00473 LogEntry e = op; 00474 if (e != null && e.getCode() == LogEntry.COMMIT) { 00475 //Debug.println(" [T:" + e.getTransactionId() + " committed]"); 00476 t.put(e.getTransactionId(), ""); 00477 } 00478 } 00479 return last; 00480 } 00481 00482 void reallyRestart() throws Exception { 00483 //#ifdef DEBUG 00484 if (Trace.bit(18)) { 00485 Debug.println(toString() + ".reallyRestart()"); 00486 } 00487 //#endif 00488 db.getTempFile(false); // recover the scratch file. 00489 Transaction t = db.makeTransaction(false); 00490 LongMap map = new LongMap(32); 00491 LogEntry last = scanLog(map); 00492 int q = 0; 00493 int checkpointPosition = logger.getCheckpoint(); 00494 int endPosition = logger.getEnd(); 00495 LogEntry op = last; 00496 recovering = true; 00497 try { 00498 for (; op != null; op = logger.getPrevOp(op)) { 00499 LogEntry e = op; 00500 if (e != null && e.getCode() == LogEntry.STEP && 00501 e.getPosition() < checkpointPosition && 00502 map.get(e.getTransactionId()) == null) { 00503 if (e.getRedoState() == LogEntry.DONE) { 00504 //#ifdef DEBUG 00505 if (Trace.bit(18)) { 00506 Debug.println("UNDO[" + e + "]"); 00507 } 00508 //#endif 00509 if (q++ == 0) { 00510 Debug.println("Undoing in-progress transactions..."); 00511 } 00512 e.undo(t, db); 00513 } 00514 } 00515 } 00516 rowIdMap = null; 00517 00518 int p = 0; 00519 for (op = logger.getFirstOp(); op != null; op = logger.getNextOp()) { 00520 LogEntry e = op; 00521 if (e.getPosition() >= endPosition) break; 00522 if (e != null && map.get(e.getTransactionId()) != null) { 00523 if (e.getCode() == LogEntry.STEP) { 00524 if (e.getRedoState() == LogEntry.DONE) { 00525 if (p++ == 0) { 00526 Debug.println("Restoring committed transactions: " + map); 00527 } 00528 //#ifdef DEBUG 00529 if (Trace.bit(18)) { 00530 Debug.println("REDO[" + e + "]"); 00531 } 00532 //#endif 00533 e.redo(t, db); 00534 } 00535 } 00536 } 00537 } 00538 rowIdMap = null; 00539 00540 if (p > 0 || q > 0) { 00541 Debug.println("Recovery complete: " + 00542 p + " redos, " + 00543 q + " undos"); 00544 } 00545 logger.reset(); 00546 logger.sync(); 00547 if (p > 0 || q > 0) { 00548 checkpoint(); 00549 } 00550 } finally { 00551 recovering = false; 00552 } 00553 } 00554 00555 long lastCheckpoint = System.currentTimeMillis(); 00556 00557 void maybeCheckpoint() throws IOException { 00558 long now = System.currentTimeMillis(); 00559 boolean idle = logger.getActiveTransactionCount() == 0; 00560 if (idle) rowIdMap = null; 00561 long interval = idle ? minSyncInterval : maxSyncInterval; 00562 if (now - lastCheckpoint >= interval) { 00563 //#ifdef DEBUG 00564 if (Trace.bit(25)) { 00565 Debug.println("[" + entryCount + BlockStore.rw() + 00566 "] interval " + (now - lastCheckpoint) + 00567 " ms," + (idle ? " IDLE" : "") + 00568 " checkpoint now"); 00569 } 00570 //#endif 00571 00572 reallyCheckpoint(); 00573 00574 //#ifdef DEBUG 00575 if (Trace.bit(25)) { 00576 Debug.println("checkpoint done"); 00577 } 00578 //#endif 00579 } 00580 } 00581 00582 void reallyCheckpoint() throws IOException { 00583 try { 00584 db.flushRoot(); 00585 dbFile.flush(!checksync(4)); 00586 logger.checkpoint(); 00587 00588 int numTrans = logger.getActiveTransactionCount(); 00589 boolean truncate = pendingBegins == 0 && numTrans == 0; 00590 if (truncate) { 00591 logger.reset(); 00592 } 00593 logger.sync(); 00594 db.checkpoint(truncate, !checksync(6)); 00595 db.checkpointHandler(logger.getActiveTransactions()); 00596 dbFile.clearModified(); 00597 } finally { 00598 lastCheckpoint = System.currentTimeMillis(); 00599 rowIdMap = null; 00600 } 00601 //#ifdef DEBUG 00602 if (Trace.bit(21)) { 00603 Debug.println("AFTER Log1.reallyCheckpoint: [" + 00604 logger.getActiveTransactionCount() + "]"); 00605 } 00606 if (false) { 00607 long sum = 0; 00608 long blk = dbFile.getBlockSize(); 00609 for (long x = 0; x < dbFile.getSize(); x += blk) { 00610 Block b = dbFile.getBlock(x / blk); 00611 for (int ix = 0; ix < blk; ix += 8) { 00612 sum += b.readLong(ix); 00613 } 00614 b.decrRefCount(); 00615 } 00616 Debug.println("CHECKPOINT: CHECKSUM(" + dbFile.getSize() + ") bytes = " + 00617 sum); 00618 } 00619 //#endif 00620 } 00621 00622 //#ifdef DEBUG 00623 int entryCount = 0; 00624 //#endif 00625 00626 public void reallyAddEntry(LogEntry entry) throws IOException { 00627 //#ifdef DEBUG 00628 entryCount++; 00629 if (Trace.bit(16)) { 00630 Debug.println(toString() + ".reallyAddEntry(" + entry + ")"); 00631 } 00632 //#endif 00633 try { 00634 logger.put(entry); 00635 if (entry.getCode() == LogEntry.BEGIN_TRANSACTION) { 00636 pendingBegins--; 00637 } 00638 } catch (IOException ex) { 00639 if (ex.toString().indexOf("full") > 0) { // XXX need better way 00640 abortOldestTransaction(entry); 00641 logger.put(entry); 00642 } else { 00643 throw ex; 00644 } 00645 } 00646 } 00647 00648 private void sortEntry(long tId, List save, List discard, LogEntry e) { 00649 if (e.getTransactionId() == tId) { 00650 discard.add(e); 00651 } else { 00652 save.add(e); 00653 } 00654 } 00655 00656 private final void abortOldestTransaction(LogEntry entry) 00657 throws IOException 00658 { 00659 long tId = logger.getOldestTransaction(); 00660 Transaction t = db.findTransaction(tId); 00661 if (t == null) { 00662 Debug.println("Log full, will reset log"); 00663 logger.reset(); 00664 // XXX should this ever happen, we might 00665 // XXX be better off if we aborted all the active transactions 00666 } else { 00667 ArrayList save = new ArrayList(); 00668 Debug.println("Log full, will abort " + t); 00669 try { 00670 // First retrieve all of the pending log entries. There 00671 // may be some for the doomed transaction -- we'll undo 00672 // those first. 00673 ArrayList discard = new ArrayList(); 00674 LogEntry e; 00675 while ((e = (LogEntry)channel.poll(0)) != null) { 00676 sortEntry(tId, save, discard, e); 00677 } 00678 sortEntry(tId, save, discard, entry); 00679 Debug.println("discarding " + discard.size() + 00680 " completed log entries"); 00681 for (int i = discard.size() - 1; i >= 0; i--) { 00682 e = (LogEntry)discard.get(i); 00683 try { 00684 e.undo(t, db); 00685 } catch (Throwable th) { 00686 //#ifdef DEBUG 00687 Debug.println("Error during abort oldest"); 00688 Debug.print(th); 00689 //#endif 00690 } 00691 } 00692 reallyRollbackTransaction(t); 00693 } catch (IOException ex1) { 00694 throw ex1; 00695 } catch (Exception ex2) { 00696 //#ifdef DEBUG 00697 Debug.print(ex2); 00698 //#endif 00699 throw new DatafileException(ex2); 00700 } 00701 TransactionObserver obs = t.getObserver(); 00702 if (obs != null) { 00703 obs.abort(t); 00704 } 00705 reallyCheckpoint(); 00706 // After we've cleaned up the mess from the aborted transaction, 00707 // we need to handle all of the queued log entries that we 00708 // consumed. 00709 Debug.println("handling " + save.size() + " saved log entries"); 00710 for (int i = 0; i < save.size(); i++) { 00711 LogEntry e = (LogEntry)save.get(i); 00712 try { 00713 e.handle(this); 00714 } catch (Throwable th) { 00715 Debug.print(th); 00716 } 00717 } 00718 } 00719 } 00720 00721 /** 00722 * XXXX Problem: If you have "the lock" and you call this when the channel 00723 * is full, you may block. This would be bad, because it might block 00724 * the log thread trying to get the lock, leading to deadlock 00725 */ 00726 public void put(LogEntry h) throws IOException { 00727 while (true) { 00728 try { 00729 channel.put(h); 00730 } catch (InterruptedException ex) { 00731 continue; 00732 } 00733 return; 00734 } 00735 } 00736 00737 /** 00738 * Inner class to bind Flush op to 'log.reallyFlush()' method. 00739 */ 00740 public static class Flush extends LogEntry { 00741 public Flush() { super(FLUSH); } 00742 public void handle(Log log) throws IOException { 00743 ((Log1)log).reallyFlush(); 00744 } 00745 } 00746 00747 /** 00748 * A single static instance of this op is all we need. 00749 */ 00750 static Flush opFlush = new Flush(); 00751 00752 /** 00753 * Inner class to bind Checkpoint op to 'log.reallyCheckpoint' method 00754 */ 00755 public static class Checkpoint extends LogEntry { 00756 public Checkpoint() { super(CHECKPOINT); } 00757 public void handle(Log log) throws IOException { 00758 ((Log1)log).reallyCheckpoint(); 00759 } 00760 } 00761 static Checkpoint opCheckpoint = new Checkpoint(); 00762 00763 /** 00764 * Inner class to bind Close op to 'log.reallyClose()' method 00765 */ 00766 public static class Close extends LogEntry { 00767 public Close() { super(CLOSE); } 00768 public void handle(Log log) throws IOException { 00769 ((Log1)log).reallyClose(); 00770 } 00771 } 00772 00773 /** 00774 * A single static instance of this op is all we need. 00775 */ 00776 static Close opClose = new Close(); 00777 00778 /** 00779 * Inner class for transaction/statement rollback 00780 */ 00781 public class Rollback extends LogEntry { 00782 Transaction t; 00783 public Rollback(Transaction t) { 00784 super(t.getTransactionId(), ROLLBACK); 00785 this.t = t; 00786 } 00787 public Rollback(Transaction t0, int s) { 00788 super(t0.getTransactionId(), s, ROLLBACK); 00789 this.t = t0; 00790 } 00791 public void handle(Log log) throws Exception { 00792 if (stmtId == -1) { 00793 reallyRollbackTransaction(t); 00794 } else { 00795 reallyRollbackStatement(t, stmtId); 00796 } 00797 } 00798 } 00799 00800 /** 00801 * Inner class for log thread syncing 00802 */ 00803 public class Sync extends LogEntry { 00804 Latch latch; 00805 public Sync(Latch latch) { 00806 super(SYNC); 00807 this.latch = latch; 00808 } 00809 public void handle(Log log) throws Exception { 00810 latch.release(); 00811 } 00812 } 00813 00814 /** 00815 * Private inner class which implements single-threaded log file 00816 * writer, using a Channel. 00817 */ 00818 class LogSync extends Thread { 00819 boolean closeMe = false; 00820 Log1 log = null; 00821 LogSync(Log1 log) { super("Log Sync"); this.log = log; } 00822 00823 public void close() { 00824 closeMe = true; 00825 } 00826 00827 public void run() { 00828 try { 00829 while (!closeMe) { 00830 Object obj = channel.poll(500); 00831 if (obj != null) { 00832 // once we've got something to do, keep the lock 00833 // until we're finished. 00834 synchronized (fileLock) { 00835 while (obj != null) { 00836 try { 00837 //#ifdef DEBUG 00838 if (Trace.bit(17)) { 00839 Debug.println("PRE [" + obj + "].handle()"); 00840 } 00841 //#endif 00842 ((LogEntry)obj).handle(log); 00843 //#ifdef DEBUG 00844 if (Trace.bit(15)) { 00845 Debug.println("POST [" + obj + "].handle()"); 00846 } 00847 //#endif 00848 } catch (IOException ex) { 00849 Debug.println("LogSync: Got exception in [" + obj + "].handle()"); 00850 Debug.print(ex); 00851 } catch (Throwable t) { 00852 Debug.println("LogSync: Got exception in [" + obj + "].handle()"); 00853 Debug.print(t); 00854 } 00855 obj = channel.poll(0); 00856 } 00857 if (!closeMe) { 00858 log.maybeCheckpoint(); 00859 } 00860 } 00861 } 00862 } 00863 log = null; 00864 if (logger != null) logger.close(); 00865 } catch (InterruptedException ex) { 00866 Debug.print(ex); 00867 } catch (Throwable t) { 00868 Debug.print(t); 00869 } finally { 00870 //#ifdef DEBUG 00871 //Debug.println("Log Sync thread exiting **** "); 00872 //#endif 00873 closeLatch.release(); 00874 } 00875 } 00876 00877 } 00878 00879 /** 00880 * Are you logging? 00881 * 00882 * Oh yes. 00883 */ 00884 public boolean isLogging() { return true; } 00885 00886 /** 00887 * Are we currently performing recovery? 00888 */ 00889 public boolean inRecovery() { 00890 return recovering; 00891 } 00892 00893 //#ifdef DEBUG 00894 int filepos = 0; 00895 //#endif 00896 00897 /** 00898 * Save a "before" image 00899 */ 00900 byte[] sav = null; 00901 public void saveBlock(long b) throws IOException { 00902 if (sav == null) { 00903 sav = new byte[blockSize + 8]; 00904 } 00905 ByteUtil.putLong(sav, 0, b); 00906 dbFile.store.read(b, sav, 8); 00907 if (bfo == null) resetBlocks(); 00908 bfo.write(sav, 0, sav.length); 00909 bfoActive = true; 00910 //#ifdef DEBUG 00911 if (Trace.bit(19)) Debug.println("saveBlock(" + b + ", " + Block.signature(sav, 8, blockSize) + " @ " + filepos + ")"); 00912 filepos += sav.length; 00913 //#endif 00914 if (checksync(7)) { 00915 try { 00916 bfo.getFD().sync(); 00917 } catch (Throwable t) { 00918 } finally { 00919 bfoActive = false; 00920 } 00921 } 00922 } 00923 00924 /** 00925 * Restore all the "before" images 00926 */ 00927 public void restoreBlocks() throws IOException { 00928 //#ifdef DEBUG 00929 if (Trace.bit(19)) { 00930 Debug.println(db.toString() + 00931 ".restoreBlocks(): size = " + dbFile.getSize()); 00932 } 00933 //#endif 00934 File f = new File(db.getScratchDir(), "before-images"); 00935 int siz = blockSize + 8; 00936 if (f.exists() && f.length() >= siz) { 00937 RandomAccessFile bf = new RandomAccessFile(f, "r"); 00938 try { 00939 byte[] buf = new byte[siz]; 00940 long pos = bf.length() - siz; 00941 while (pos >= 0) { 00942 bf.seek(pos); 00943 bf.read(buf); 00944 long blk = ByteUtil.getLong(buf, 0); 00945 //#ifdef DEBUG 00946 if (Trace.bit(19)) { 00947 Debug.println(db.toString() + " [RESTORE " + blk + " @ " + pos + "]"); 00948 } 00949 //#endif 00950 dbFile.restoreBlock(blk, buf, 8); 00951 pos -= siz; 00952 } 00953 } finally { 00954 bf.close(); 00955 } 00956 } 00957 //#ifdef DEBUG 00958 if (Trace.bit(19)) { 00959 Debug.println(db.toString() + 00960 ".restoreBlocks(): size = " + dbFile.getSize()); 00961 } 00962 if (false) { 00963 long sum = 0; 00964 long blk = dbFile.getBlockSize(); 00965 for (long x = 0; x < dbFile.getSize(); x += blk) { 00966 Block b = dbFile.getBlock(x / blk); 00967 for (int ix = 0; ix < blk; ix += 8) { 00968 sum += b.readLong(ix); 00969 } 00970 b.decrRefCount(); 00971 } 00972 Debug.println("RECOVER: CHECKSUM(" + dbFile.getSize() + ") bytes = " + 00973 sum); 00974 } 00975 //#endif 00976 } 00977 00978 /** 00979 * Reset the "before" list to be empty 00980 */ 00981 public void resetBlocks() throws IOException { 00982 if (bfo != null) { 00983 try { 00984 bfo.close(); 00985 } catch (Throwable t) { 00986 } finally { 00987 bfo = null; 00988 } 00989 } 00990 File b = new File(db.getScratchDir(), "before-images"); 00991 bfo = new FileOutputStream(b); 00992 bfoActive = false; 00993 //#ifdef DEBUG 00994 if (Trace.bit(19)) Debug.println(db.toString() + ".Log1.resetBlocks() @ " + filepos + ", dbFile.size = " + dbFile.getSize()); 00995 filepos = 0; 00996 //#endif 00997 } 00998 00999 //#ifdef DEBUG 01000 public String toString() { 01001 String s = getClass().getName(); 01002 int x = s.lastIndexOf('.'); 01003 if (x >= 0) s = s.substring(x+1); 01004 return s; 01005 } 01006 //#endif 01007 } 01008