1 5 package org.h2.store; 6 7 import java.sql.SQLException ; 8 import java.util.Comparator ; 9 import java.util.HashMap ; 10 11 import org.h2.api.DatabaseEventListener; 12 import org.h2.engine.Constants; 13 import org.h2.engine.Database; 14 import org.h2.engine.Session; 15 import org.h2.util.FileUtils; 16 import org.h2.util.ObjectArray; 17 18 21 public class LogSystem { 22 23 public static final int LOG_WRITTEN = -1; 24 25 private Database database; 26 private ObjectArray activeLogs; 27 private LogFile currentLog; 28 private String fileNamePrefix; 29 private HashMap storages = new HashMap (); 30 private HashMap sessions = new HashMap (); 31 private DataPage rowBuff; 32 private ObjectArray undo; 33 private boolean deleteOldLogFilesAutomatically = true; 35 private long maxLogSize = Constants.DEFAULT_MAX_LOG_SIZE; 36 private boolean readOnly; 37 private boolean flushOnEachCommit; 38 private ObjectArray inDoubtTransactions; 39 private boolean disabled; 40 41 public LogSystem(Database database, String fileNamePrefix, boolean readOnly) throws SQLException { 42 this.database = database; 43 this.readOnly = readOnly; 44 if (database == null || readOnly) { 45 return; 46 } 47 this.fileNamePrefix = fileNamePrefix; 48 rowBuff = DataPage.create(database, Constants.DEFAULT_DATA_PAGE_SIZE); 49 loadActiveLogFiles(); 50 } 51 52 public void setMaxLogSize(long maxSize) { 53 this.maxLogSize = maxSize; 54 } 55 56 public long getMaxLogSize() { 57 return maxLogSize; 58 } 59 60 public boolean containsInDoubtTransactions() { 61 return inDoubtTransactions != null && inDoubtTransactions.size() > 0; 62 } 63 64 private void flushAndCloseUnused() throws SQLException { 65 currentLog.flush(); 66 DiskFile file = database.getDataFile(); 67 if (file == null) { 68 return; 69 } 70 file.flush(); 71 if (containsInDoubtTransactions()) { 72 return; 74 } 75 Session[] sessions = database.getSessions(); 76 int firstUncommittedLog = currentLog.getId(); 77 int firstUncommittedPos = currentLog.getPos(); 78 for (int i = 0; i < sessions.length; i++) { 79 Session session = sessions[i]; 80 int log = session.getFirstUncommittedLog(); 81 int pos = session.getFirstUncommittedPos(); 82 if (pos != LOG_WRITTEN) { 83 if (log < firstUncommittedLog || (log == firstUncommittedLog && pos < firstUncommittedPos)) { 84 firstUncommittedLog = log; 85 firstUncommittedPos = pos; 86 } 87 } 88 } 89 for (int i = activeLogs.size() - 1; i >= 0; i--) { 90 LogFile l = (LogFile) activeLogs.get(i); 91 if (l.getId() < firstUncommittedLog) { 92 l.setFirstUncommittedPos(LOG_WRITTEN); 93 } else if (l.getId() == firstUncommittedLog) { 94 if (firstUncommittedPos == l.getPos()) { 95 l.setFirstUncommittedPos(LOG_WRITTEN); 96 } else { 97 l.setFirstUncommittedPos(firstUncommittedPos); 98 } 99 } 100 } 101 for (int i = 0; i < activeLogs.size(); i++) { 102 LogFile l = (LogFile) activeLogs.get(i); 103 if (l.getFirstUncommittedPos() == LOG_WRITTEN) { 104 l.close(deleteOldLogFilesAutomatically); 105 activeLogs.remove(i); 106 i--; 107 } 108 } 109 } 110 111 public void close() throws SQLException { 112 if (database == null || readOnly) { 113 return; 114 } 115 synchronized (database) { 116 SQLException closeException = null; 118 try { 119 flushAndCloseUnused(); 120 if (!containsInDoubtTransactions()) { 121 checkpoint(); 122 } 123 } catch (SQLException e) { 124 closeException = e; 125 } 126 for (int i = 0; i < activeLogs.size(); i++) { 127 LogFile l = (LogFile) activeLogs.get(i); 128 try { 129 if (l.getFirstUncommittedPos() == LOG_WRITTEN && !containsInDoubtTransactions()) { 131 l.close(deleteOldLogFilesAutomatically); 132 } else { 133 l.close(false); 134 } 135 } catch (SQLException e) { 136 if (closeException == null) { 138 closeException = e; 139 } 140 } 141 } 142 database = null; 143 if (closeException != null) { 144 throw closeException; 145 } 146 } 147 } 148 149 boolean needMoreUndo() { 150 return sessions.size() > 0; 151 } 152 153 void addUndoLogRecord(LogFile log, int logRecordId, int sessionId) { 154 LogRecord record = new LogRecord(log, logRecordId, sessionId); 155 undo.add(record); 156 } 157 158 public boolean recover() throws SQLException { 159 if (database == null) { 160 return false; 161 } 162 synchronized (database) { 163 undo = new ObjectArray(); 164 for (int i = 0; i < activeLogs.size(); i++) { 165 LogFile log = (LogFile) activeLogs.get(i); 166 log.redoAllGoEnd(); 167 } 168 database.getDataFile().flushRedoLog(); 169 database.getIndexFile().flushRedoLog(); 170 int end = currentLog.getPos(); 171 Object [] states = sessions.values().toArray(); 172 inDoubtTransactions = new ObjectArray(); 173 for (int i = 0; i < states.length; i++) { 174 SessionState state = (SessionState) states[i]; 175 if (state.inDoubtTransaction != null) { 176 inDoubtTransactions.add(state.inDoubtTransaction); 177 } 178 } 179 for (int i = undo.size() - 1; i >= 0 && sessions.size() > 0; i--) { 180 database.setProgress(DatabaseEventListener.STATE_RECOVER, null, undo.size() - 1 - i, undo.size()); 181 LogRecord record = (LogRecord) undo.get(i); 182 if (sessions.get(new Integer (record.sessionId)) != null) { 183 record.log.undo(record.logRecordId); 185 database.getDataFile().flushRedoLog(); 186 database.getIndexFile().flushRedoLog(); 187 } 188 } 189 currentLog.go(end); 190 boolean fileChanged = undo.size() > 0; 191 undo = null; 192 storages.clear(); 193 if (fileChanged && !containsInDoubtTransactions()) { 194 checkpoint(); 195 } 196 return fileChanged; 197 } 198 } 199 200 private void loadActiveLogFiles() throws SQLException { 201 String path = FileUtils.getParent(fileNamePrefix); 202 String [] list = FileUtils.listFiles(path); 203 activeLogs = new ObjectArray(); 204 for (int i = 0; i < list.length; i++) { 205 String s = list[i]; 206 LogFile l = LogFile.openIfLogFile(this, fileNamePrefix, s); 207 if (l != null) { 208 if (l.getPos() == LOG_WRITTEN) { 209 l.close(deleteOldLogFilesAutomatically); 210 } else { 211 activeLogs.add(l); 212 } 213 } 214 } 215 activeLogs.sort(new Comparator () { 216 public int compare(Object a, Object b) { 217 return ((LogFile) a).getId() - ((LogFile) b).getId(); 218 } 219 }); 220 if (activeLogs.size() == 0) { 221 LogFile l = new LogFile(this, 0, fileNamePrefix); 222 activeLogs.add(l); 223 } 224 currentLog = (LogFile) activeLogs.get(activeLogs.size() - 1); 225 } 226 227 Storage getStorageForRecovery(int id) throws SQLException { 228 boolean dataFile; 229 if (id < 0) { 230 dataFile = false; 231 id = -id; 232 } else { 233 dataFile = true; 234 } 235 Integer i = new Integer (id); 236 Storage storage = (Storage) storages.get(i); 237 if (storage == null) { 238 storage = database.getStorage(null, id, dataFile); 239 storages.put(i, storage); 240 } 241 return storage; 242 } 243 244 boolean isSessionCommitted(int sessionId, int logId, int pos) { 245 Integer key = new Integer (sessionId); 246 SessionState state = (SessionState) sessions.get(key); 247 if (state == null) { 248 return true; 249 } 250 return state.isCommitted(logId, pos); 251 } 252 253 void setLastCommitForSession(int sessionId, int logId, int pos) { 254 Integer key = new Integer (sessionId); 255 SessionState state = (SessionState) sessions.get(key); 256 if (state == null) { 257 state = new SessionState(); 258 sessions.put(key, state); 259 state.sessionId = sessionId; 260 } 261 state.lastCommitLog = logId; 262 state.lastCommitPos = pos; 263 state.inDoubtTransaction = null; 264 } 265 266 void setPreparedCommitForSession(LogFile log, int sessionId, int pos, String transaction, int blocks) { 267 Integer key = new Integer (sessionId); 268 SessionState state = (SessionState) sessions.get(key); 269 if (state == null) { 270 state = new SessionState(); 271 sessions.put(key, state); 272 state.sessionId = sessionId; 273 } 274 setLastCommitForSession(sessionId, log.getId(), pos); 276 state.inDoubtTransaction = new InDoubtTransaction(log, sessionId, pos, transaction, blocks); 277 } 278 279 public ObjectArray getInDoubtTransactions() { 280 return inDoubtTransactions; 281 } 282 283 void removeSession(int sessionId) { 284 sessions.remove(new Integer (sessionId)); 285 } 286 287 public void prepareCommit(Session session, String transaction) throws SQLException { 288 if (database == null || readOnly) { 289 return; 290 } 291 synchronized (database) { 292 currentLog.prepareCommit(session, transaction); 293 } 294 } 295 296 public void commit(Session session) throws SQLException { 297 if (database == null || readOnly) { 298 return; 299 } 300 synchronized (database) { 301 currentLog.commit(session); 302 session.setAllCommitted(); 303 } 304 } 305 306 public void flush() throws SQLException { 307 if (database == null || readOnly) { 308 return; 309 } 310 synchronized (database) { 311 currentLog.flush(); 312 } 313 } 314 315 public void addTruncate(Session session, DiskFile file, int storageId, int recordId, int blockCount) throws SQLException { 316 if (database == null || disabled) { 317 return; 318 } 319 synchronized (database) { 320 database.checkWritingAllowed(); 321 if (!file.isDataFile()) { 322 storageId = -storageId; 323 } 324 currentLog.addTruncate(session, storageId, recordId, blockCount); 325 if (currentLog.getFileSize() > maxLogSize) { 326 checkpoint(); 327 } 328 } 329 } 330 331 public void add(Session session, DiskFile file, Record record) throws SQLException { 332 if (database == null || disabled) { 333 return; 334 } 335 synchronized (database) { 336 database.checkWritingAllowed(); 337 int storageId = record.getStorageId(); 338 if (!file.isDataFile()) { 339 storageId = -storageId; 340 } 341 int log = currentLog.getId(); 342 int pos = currentLog.getPos(); 343 session.addLogPos(log, pos); 344 record.setLastLog(log, pos); 345 currentLog.add(session, storageId, record); 346 if (currentLog.getFileSize() > maxLogSize) { 347 checkpoint(); 348 } 349 } 350 } 351 352 public void checkpoint() throws SQLException { 353 if (database == null || readOnly || disabled) { 354 return; 355 } 356 synchronized (database) { 357 flushAndCloseUnused(); 358 currentLog = new LogFile(this, currentLog.getId() + 1, fileNamePrefix); 359 activeLogs.add(currentLog); 360 writeSummary(); 361 currentLog.flush(); 362 } 363 } 364 365 private void writeSummary() throws SQLException { 366 if (database == null || readOnly || disabled) { 367 return; 368 } 369 byte[] summary; 370 DiskFile file; 371 file = database.getDataFile(); 372 summary = file.getSummary(); 373 if (summary != null) { 374 currentLog.addSummary(true, summary); 375 } 376 if (database.getLogIndexChanges() || database.getIndexSummaryValid()) { 377 file = database.getIndexFile(); 378 summary = file.getSummary(); 379 if (summary != null) { 380 currentLog.addSummary(false, summary); 381 } 382 } else { 383 currentLog.addSummary(false, null); 385 } 386 } 387 388 Database getDatabase() { 389 return database; 390 } 391 392 DataPage getRowBuffer() { 393 return rowBuff; 394 } 395 396 public void setFlushOnEachCommit(boolean b) { 397 flushOnEachCommit = b; 398 } 399 400 boolean getFlushOnEachCommit() { 401 return flushOnEachCommit; 402 } 403 404 public void sync() throws SQLException { 405 synchronized (database) { 406 if (currentLog != null) { 407 currentLog.flush(); 408 currentLog.sync(); 409 } 410 } 411 } 412 413 public void setDisabled(boolean disabled) { 414 this.disabled = disabled; 415 } 416 417 void addRedoLog(Storage storage, int recordId, int blockCount, DataPage rec) throws SQLException { 418 DiskFile file = storage.getDiskFile(); 419 file.addRedoLog(storage, recordId, blockCount, rec); 420 } 421 422 public void invalidateIndexSummary() throws SQLException { 423 currentLog.addSummary(false, null); 424 } 425 426 } 427 | Popular Tags |