1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.store.raw.log.FileLogger |
4 | |
5 | Copyright 1997, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.impl.store.raw.log; |
22 | |
23 | import org.apache.derby.iapi.reference.SQLState; |
24 | import org.apache.derby.iapi.reference.MessageId; |
25 | |
26 | import org.apache.derby.impl.store.raw.log.LogCounter; |
27 | import org.apache.derby.impl.store.raw.log.LogRecord; |
28 | import org.apache.derby.impl.store.raw.log.StreamLogScan; |
29 | |
30 | import org.apache.derby.iapi.store.access.TransactionController; |
31 | |
32 | import org.apache.derby.iapi.store.raw.RawStoreFactory; |
33 | |
34 | import org.apache.derby.iapi.store.raw.log.LogInstant; |
35 | import org.apache.derby.iapi.store.raw.log.Logger; |
36 | import org.apache.derby.iapi.store.raw.log.LogScan; |
37 | |
38 | import org.apache.derby.iapi.store.raw.xact.RawTransaction; |
39 | import org.apache.derby.iapi.store.raw.xact.TransactionFactory; |
40 | import org.apache.derby.iapi.store.raw.xact.TransactionId; |
41 | |
42 | import org.apache.derby.iapi.store.raw.Compensation; |
43 | import org.apache.derby.iapi.store.raw.ContainerHandle; |
44 | import org.apache.derby.iapi.store.raw.LockingPolicy; |
45 | import org.apache.derby.iapi.store.raw.Loggable; |
46 | import org.apache.derby.iapi.store.raw.Page; |
47 | import org.apache.derby.iapi.store.raw.RePreparable; |
48 | import org.apache.derby.iapi.store.raw.Undoable; |
49 | |
50 | import org.apache.derby.iapi.services.io.FormatIdOutputStream; |
51 | |
52 | import org.apache.derby.iapi.services.sanity.SanityManager; |
53 | |
54 | import org.apache.derby.iapi.error.StandardException; |
55 | import org.apache.derby.iapi.services.i18n.MessageService; |
56 | |
57 | import org.apache.derby.iapi.services.io.ArrayInputStream; |
58 | import org.apache.derby.iapi.services.io.ArrayOutputStream; |
59 | import org.apache.derby.iapi.util.ByteArray; |
60 | import org.apache.derby.iapi.services.io.DynamicByteArrayOutputStream; |
61 | |
62 | import org.apache.derby.iapi.services.io.LimitObjectInput; |
63 | import java.io.IOException; |
64 | |
65 | import org.apache.derby.impl.store.raw.data.InitPageOperation; |
66 | |
67 | /** |
68 | Write log records to a log file as a stream |
69 | (ie. log records added to the end of the file, no concept of pages). |
70 | <P> |
71 | The format of a log record that is not a compensation operation is |
72 | <PRE> |
73 | @format_id no formatId, format is implied by the log file format and the |
74 | log record content. |
75 | @purpose the log record and optional data |
76 | @upgrade |
77 | @disk_layout |
78 | Log Record |
79 | (see org.apache.derby.impl.store.raw.log.LogRecord) |
80 | length(int) length of optional data |
81 | optionalData(byte[length]) optional data written by the log record |
82 | @end_format |
83 | </PRE> <HR WIDTH="100%"> |
84 | |
85 | <P> The form of a log record that is a compensation operation is |
86 | <PRE> |
87 | @format_id no formatId, format is implied by the log file format and the |
88 | log record content. |
89 | @purpose undo a previous log record |
90 | @upgrade |
91 | @disk_layout |
92 | Log Record that contains the compenstation operation |
93 | (see org.apache.derby.impl.store.raw.log.LogRecord) |
94 | undoInstant(long) the log instant of the operation that is to be rolled back |
95 | The undo instant is logically part of the LogRecord but is written |
96 | by the logger because it is used and controlled by the rollback |
97 | code but not by the log operation. |
98 | There is no optional data in a compensation operation, all data |
99 | necessary for the rollback must be stored in the operation being |
100 | undone. |
101 | @end_format |
102 | </PRE> |
103 | |
104 | <BR> |
105 | |
106 | <P>Multithreading considerations:<BR> |
107 | Logger must be MT-safe. Each RawTransaction has its own private |
108 | FileLogger object. Each logger has a logOutputBuffer and a log input |
109 | buffer which are used to read and write to the log. Since multiple |
110 | threads can be in the same transaction, fileLogger must be synchronized. |
111 | |
112 | @see LogRecord |
113 | */ |
114 | |
115 | public class FileLogger implements Logger { |
116 | |
117 | private LogRecord logRecord; |
118 | |
119 | protected byte[] encryptionBuffer; |
120 | private DynamicByteArrayOutputStream logOutputBuffer; |
121 | private FormatIdOutputStream logicalOut; |
122 | |
123 | private ArrayInputStream logIn; |
124 | |
125 | private LogToFile logFactory; // actually writes the log records. |
126 | |
127 | /** |
128 | Make a new Logger with its own log record buffers |
129 | MT - not needed for constructor |
130 | */ |
131 | public FileLogger(LogToFile logFactory) { |
132 | |
133 | this.logFactory = logFactory; |
134 | logOutputBuffer = new DynamicByteArrayOutputStream(1024); // init size 1K |
135 | logicalOut = new FormatIdOutputStream(logOutputBuffer); |
136 | |
137 | // logIn and logOutputBuffer must share the same buffer because they |
138 | // combined to form an IO stream to access the same log record. |
139 | // |
140 | // Before each use of logIn, you must reset logIn's data to the |
141 | // byte array you want to read from. |
142 | // |
143 | // To log a record, set logIn's data to point to logOutputBuffer's |
144 | // byte array when you know you have everything you need in the output |
145 | // buffer, then set limit on logIn and send it to the log operation's |
146 | // doMe. |
147 | // |
148 | // Keep in mind the dynamic nature of the logOutputBuffer which means |
149 | // it could switch buffer from underneath the logOutputBuffer on every |
150 | // write. |
151 | logIn = new ArrayInputStream(); |
152 | |
153 | logRecord = new LogRecord(); |
154 | |
155 | } |
156 | |
157 | /** |
158 | Close the logger. |
159 | MT - caller provide synchronization |
160 | (RESOLVE: not called by anyone ??) |
161 | */ |
162 | public void close() throws IOException |
163 | { |
164 | if (logOutputBuffer != null) |
165 | { |
166 | logOutputBuffer.close(); |
167 | logOutputBuffer = null; |
168 | } |
169 | |
170 | logIn = null; |
171 | logFactory = null; |
172 | |
173 | logicalOut = null; |
174 | |
175 | logRecord = null; |
176 | } |
177 | |
178 | /* |
179 | ** Methods of Logger |
180 | */ |
181 | |
182 | /** |
183 | Writes out a log record to the log stream, and call its doMe method to |
184 | apply the change to the rawStore. |
185 | <BR>Any optional data the doMe method need is first written to the log |
186 | stream using operation.writeOptionalData, then whatever is written to |
187 | the log stream is passed back to the operation for the doMe method. |
188 | |
189 | <P>MT - there could be multiple threads running in the same raw |
190 | transactions and they can be calling the same logger to log different |
191 | log operations. This whole method is synchronized to make sure log |
192 | records are logged one at a time. |
193 | |
194 | @param xact the transaction logging the change |
195 | @param operation the log operation |
196 | @return the instant in the log that can be used to identify the log |
197 | record |
198 | |
199 | @exception StandardException Cloudscape Standard error policy |
200 | */ |
201 | public synchronized LogInstant logAndDo(RawTransaction xact, Loggable operation) |
202 | throws StandardException |
203 | { |
204 | boolean isLogPrepared = false; |
205 | |
206 | boolean inUserCode = false; |
207 | byte[] preparedLog; |
208 | |
209 | try { |
210 | |
211 | logOutputBuffer.reset(); |
212 | |
213 | // always use the short Id, only the BeginXact log record contains |
214 | // the XactId (long form) |
215 | TransactionId transactionId = xact.getId(); |
216 | |
217 | // write out the log header with the operation embedded |
218 | // this is by definition not a compensation log record, |
219 | // those are called thru the logAndUndo interface |
220 | logRecord.setValue(transactionId, operation); |
221 | |
222 | inUserCode = true; |
223 | logicalOut.writeObject(logRecord); |
224 | inUserCode = false; |
225 | |
226 | int optionalDataLength = 0; |
227 | int optionalDataOffset = 0; |
228 | int completeLength = 0; |
229 | |
230 | ByteArray preparedLogArray = operation.getPreparedLog(); |
231 | if (preparedLogArray != null) { |
232 | |
233 | preparedLog = preparedLogArray.getArray(); |
234 | optionalDataLength = preparedLogArray.getLength(); |
235 | optionalDataOffset = preparedLogArray.getOffset(); |
236 | |
237 | // There is a race condition if the operation is a begin tran in |
238 | // that between the time the beginXact log record is written to |
239 | // disk and the time the transaction object is updated in the |
240 | // beginXact.doMe method, other log records may be written. |
241 | // This will render the transaction table in an inconsistent state |
242 | // since it may think a later transaction is the earliest |
243 | // transaction or it may think that there is no active transactions |
244 | // where there is a bunch of them sitting on the log. |
245 | // |
246 | // Similarly, there is a race condition for endXact, i.e., |
247 | // 1) endXact is written to the log, |
248 | // 2) checkpoint gets that (committed) transaction as the |
249 | // firstUpdateTransaction |
250 | // 3) the transaction calls postComplete, nulling out itself |
251 | // 4) checkpoint tries to access a closed transaction object |
252 | // |
253 | // The solution is to sync between the time a begin tran or end |
254 | // tran log record is sent to the log stream and its doMe method is |
255 | // called to update the transaction table and in memory state |
256 | // |
257 | // We only need to serialized the begin and end Xact log records |
258 | // because once a transaction has been started and in the |
259 | // transaction table, its order and transaction state does not |
260 | // change. |
261 | // |
262 | // Use the logFactory as the sync object so that a checkpoint can |
263 | // take its snap shot of the undoLWM before or after a transaction |
264 | // is started, but not in the middle. (see LogToFile.checkpoint) |
265 | // |
266 | |
267 | // now set the input limit to be the optional data. |
268 | // This limits amount of data availiable to logIn that doMe can |
269 | // use |
270 | logIn.setData(preparedLog); |
271 | logIn.setPosition(optionalDataOffset); |
272 | logIn.setLimit(optionalDataLength); |
273 | |
274 | if (SanityManager.DEBUG) |
275 | { |
276 | if ((optionalDataLength) != logIn.available()) |
277 | SanityManager.THROWASSERT( |
278 | " stream not set correctly " + |
279 | optionalDataLength + " != " + |
280 | logIn.available()); |
281 | } |
282 | |
283 | } else { |
284 | preparedLog = null; |
285 | optionalDataLength = 0; |
286 | } |
287 | |
288 | logicalOut.writeInt(optionalDataLength); |
289 | completeLength = logOutputBuffer.getPosition() + optionalDataLength; |
290 | |
291 | |
292 | LogInstant logInstant = null; |
293 | int encryptedLength = 0; // in case of encryption, we need to pad |
294 | |
295 | try |
296 | { |
297 | if (logFactory.databaseEncrypted()) |
298 | { |
299 | // we must pad the encryption data to be multiple of block |
300 | // size, which is logFactory.getEncryptionBlockSize() |
301 | encryptedLength = completeLength; |
302 | if ((encryptedLength % logFactory.getEncryptionBlockSize()) != 0) |
303 | encryptedLength = encryptedLength + logFactory.getEncryptionBlockSize() - (encryptedLength % logFactory.getEncryptionBlockSize()); |
304 | |
305 | if (encryptionBuffer == null || |
306 | encryptionBuffer.length < encryptedLength) |
307 | encryptionBuffer = new byte[encryptedLength]; |
308 | |
309 | System.arraycopy(logOutputBuffer.getByteArray(), 0, |
310 | encryptionBuffer, 0, completeLength-optionalDataLength); |
311 | |
312 | if (optionalDataLength > 0) |
313 | System.arraycopy(preparedLog, optionalDataOffset, |
314 | encryptionBuffer, |
315 | completeLength-optionalDataLength, optionalDataLength); |
316 | |
317 | // do not bother to clear out the padding area |
318 | int len = |
319 | logFactory.encrypt(encryptionBuffer, 0, encryptedLength, |
320 | encryptionBuffer, 0); |
321 | |
322 | if (SanityManager.DEBUG) |
323 | SanityManager.ASSERT(len == encryptedLength, |
324 | "encrypted log buffer length != log buffer len"); |
325 | } |
326 | |
327 | if ((operation.group() & (Loggable.FIRST | Loggable.LAST)) != 0) |
328 | { |
329 | synchronized (logFactory) |
330 | { |
331 | long instant = 0; |
332 | |
333 | if (logFactory.databaseEncrypted()) |
334 | { |
335 | // encryption has completely drained both the the |
336 | // logOuputBuffer array and the preparedLog array |
337 | instant = logFactory. |
338 | appendLogRecord(encryptionBuffer, 0, |
339 | encryptedLength, null, |
340 | -1, 0); |
341 | } |
342 | else |
343 | { |
344 | instant = logFactory. |
345 | appendLogRecord(logOutputBuffer.getByteArray(), |
346 | 0, completeLength, preparedLog, |
347 | optionalDataOffset, |
348 | optionalDataLength); |
349 | } |
350 | logInstant = new LogCounter(instant); |
351 | |
352 | operation.doMe(xact, logInstant, logIn); |
353 | } |
354 | } |
355 | else |
356 | { |
357 | long instant = 0; |
358 | |
359 | if (logFactory.databaseEncrypted()) |
360 | { |
361 | // encryption has completely drained both the the |
362 | // logOuputBuffer array and the preparedLog array |
363 | instant = logFactory. |
364 | appendLogRecord(encryptionBuffer, 0, |
365 | encryptedLength, null, -1, 0); |
366 | } |
367 | else |
368 | { |
369 | instant = logFactory. |
370 | appendLogRecord(logOutputBuffer.getByteArray(), 0, |
371 | completeLength, preparedLog, |
372 | optionalDataOffset, |
373 | optionalDataLength); |
374 | } |
375 | |
376 | logInstant = new LogCounter(instant); |
377 | |
378 | operation.doMe(xact, logInstant, logIn); |
379 | } |
380 | |
381 | } |
382 | catch (StandardException se) |
383 | { |
384 | throw logFactory.markCorrupt( |
385 | StandardException.newException( |
386 | SQLState.LOG_DO_ME_FAIL, se, operation)); |
387 | } |
388 | catch (IOException ioe) |
389 | { |
390 | throw logFactory.markCorrupt( |
391 | StandardException.newException( |
392 | SQLState.LOG_DO_ME_FAIL, ioe, operation)); |
393 | } |
394 | finally |
395 | { |
396 | logIn.clearLimit(); |
397 | } |
398 | |
399 | if (SanityManager.DEBUG) |
400 | { |
401 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
402 | { |
403 | SanityManager.DEBUG( |
404 | LogToFile.DBG_FLAG, |
405 | "Write log record: tranId=" + transactionId.toString() + |
406 | " instant: " + logInstant.toString() + " length: " + |
407 | completeLength + "\n" + operation + "\n"); |
408 | } |
409 | } |
410 | return logInstant; |
411 | } |
412 | |
413 | catch (IOException ioe) |
414 | { |
415 | // error writing to the log buffer |
416 | if (inUserCode) |
417 | { |
418 | throw StandardException.newException( |
419 | SQLState.LOG_WRITE_LOG_RECORD, ioe, operation); |
420 | } |
421 | else |
422 | { |
423 | throw StandardException.newException( |
424 | SQLState.LOG_BUFFER_FULL, ioe, operation); |
425 | } |
426 | } |
427 | |
428 | } |
429 | |
430 | /** |
431 | Writes out a compensation log record to the log stream, and call its |
432 | doMe method to undo the change of a previous log operation. |
433 | |
434 | <P>MT - Not needed. A transaction must be single threaded thru undo, each |
435 | RawTransaction has its own logger, therefore no need to synchronize. |
436 | The RawTransaction must handle synchronizing with multiple threads |
437 | during rollback. |
438 | |
439 | @param xact the transaction logging the change |
440 | @param compensation the compensation log operation |
441 | @param undoInstant the log instant of the operation that is to be |
442 | rolled back |
443 | @param in optional data input for the compenastion doMe method |
444 | |
445 | @return the instant in the log that can be used to identify the log |
446 | record |
447 | |
448 | @exception StandardException Cloudscape Standard error policy |
449 | */ |
450 | public LogInstant logAndUndo(RawTransaction xact, |
451 | Compensation compensation, |
452 | LogInstant undoInstant, |
453 | LimitObjectInput in) |
454 | throws StandardException |
455 | { |
456 | boolean inUserCode = false; |
457 | |
458 | try { |
459 | logOutputBuffer.reset(); |
460 | |
461 | TransactionId transactionId = xact.getId(); |
462 | |
463 | // write out the log header with the operation embedded |
464 | logRecord.setValue(transactionId, compensation); |
465 | |
466 | inUserCode = true; |
467 | logicalOut.writeObject(logRecord); |
468 | inUserCode = false; |
469 | |
470 | // write out the undoInstant |
471 | logicalOut.writeLong(((LogCounter)undoInstant).getValueAsLong()); |
472 | |
473 | // in this implemetaion, there is no optional data for the |
474 | // compensation operation. Optional data for the rollback comes |
475 | // from the undoable operation - and is passed into this call. |
476 | int completeLength = logOutputBuffer.getPosition(); |
477 | long instant = 0; |
478 | |
479 | if (logFactory.databaseEncrypted()) |
480 | { |
481 | // we must pad the encryption data to be multiple of block |
482 | // size, which is logFactory.getEncryptionBlockSize() |
483 | int encryptedLength = completeLength; |
484 | if ((encryptedLength % logFactory.getEncryptionBlockSize()) != 0) |
485 | encryptedLength = encryptedLength + logFactory.getEncryptionBlockSize() - (encryptedLength % logFactory.getEncryptionBlockSize()); |
486 | |
487 | if (encryptionBuffer == null || |
488 | encryptionBuffer.length < encryptedLength) |
489 | encryptionBuffer = new byte[encryptedLength]; |
490 | |
491 | System.arraycopy(logOutputBuffer.getByteArray(), 0, |
492 | encryptionBuffer, 0, completeLength); |
493 | |
494 | // do not bother to clear out the padding area |
495 | int len = |
496 | logFactory.encrypt(encryptionBuffer, 0, encryptedLength, |
497 | encryptionBuffer, 0); |
498 | |
499 | if (SanityManager.DEBUG) |
500 | SanityManager.ASSERT(len == encryptedLength, |
501 | "encrypted log buffer length != log buffer len"); |
502 | |
503 | instant = logFactory. |
504 | appendLogRecord(encryptionBuffer, |
505 | 0, encryptedLength, null, 0, 0); |
506 | } |
507 | else |
508 | { |
509 | instant = logFactory. |
510 | appendLogRecord(logOutputBuffer.getByteArray(), |
511 | 0, completeLength, null, 0, 0); |
512 | } |
513 | |
514 | LogInstant logInstant = new LogCounter(instant); |
515 | |
516 | if (SanityManager.DEBUG) |
517 | { |
518 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
519 | { |
520 | SanityManager.DEBUG( |
521 | LogToFile.DBG_FLAG, |
522 | "Write CLR: Xact: " + transactionId.toString() + |
523 | "clrinstant: " + logInstant.toString() + |
524 | " undoinstant " + undoInstant + "\n"); |
525 | } |
526 | } |
527 | |
528 | try |
529 | { |
530 | // in and dataLength contains optional data that was written |
531 | // to the log during a previous call to logAndDo. |
532 | compensation.doMe(xact, logInstant, in); |
533 | } |
534 | catch (StandardException se) |
535 | { |
536 | throw logFactory.markCorrupt( |
537 | StandardException.newException( |
538 | SQLState.LOG_DO_ME_FAIL, se, compensation)); |
539 | } |
540 | catch (IOException ioe) |
541 | { |
542 | throw logFactory.markCorrupt( |
543 | StandardException.newException( |
544 | SQLState.LOG_DO_ME_FAIL, ioe, compensation)); |
545 | } |
546 | |
547 | return logInstant; |
548 | |
549 | } |
550 | catch (IOException ioe) |
551 | { |
552 | if (inUserCode) |
553 | { |
554 | throw StandardException.newException( |
555 | SQLState.LOG_WRITE_LOG_RECORD, ioe, compensation); |
556 | } |
557 | else |
558 | { |
559 | throw StandardException.newException( |
560 | SQLState.LOG_BUFFER_FULL, ioe, compensation); |
561 | } |
562 | } |
563 | } |
564 | |
565 | /** |
566 | Flush the log up to the given log instant. |
567 | |
568 | <P>MT - not needed, wrapper method |
569 | |
570 | @exception StandardException cannot sync log file |
571 | */ |
572 | public void flush(LogInstant where) |
573 | throws StandardException |
574 | { |
575 | if (SanityManager.DEBUG) |
576 | { |
577 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
578 | { |
579 | SanityManager.DEBUG( |
580 | LogToFile.DBG_FLAG, "Flush log to " + where.toString()); |
581 | } |
582 | } |
583 | |
584 | logFactory.flush(where); |
585 | } |
586 | |
587 | /** |
588 | Flush all outstanding log to disk. |
589 | |
590 | <P>MT - not needed, wrapper method |
591 | |
592 | @exception StandardException cannot sync log file |
593 | */ |
594 | public void flushAll () throws StandardException |
595 | { |
596 | logFactory.flushAll(); |
597 | } |
598 | |
599 | /** |
600 | * During recovery re-prepare a transaction. |
601 | * <p> |
602 | * After redo() and undo(), this routine is called on all outstanding |
603 | * in-doubt (prepared) transactions. This routine re-acquires all |
604 | * logical write locks for operations in the xact, and then modifies |
605 | * the transaction table entry to make the transaction look as if it |
606 | * had just been prepared following startup after recovery. |
607 | * <p> |
608 | * |
609 | * @param t is the transaction performing the re-prepare |
610 | * @param prepareId is the transaction ID to be re-prepared |
611 | * @param prepareStopAt is where the log instant (inclusive) where the |
612 | * re-prepare should stop. |
613 | * @param prepareStartAt is the log instant (inclusive) where re-prepare |
614 | * should begin, this is normally the log instant |
615 | * of the last log record of the transaction that |
616 | * is to be re-prepare. If null, then re-prepare |
617 | * starts from the end of the log. |
618 | * |
619 | * @exception StandardException Standard exception policy. |
620 | **/ |
621 | public void reprepare( |
622 | RawTransaction t, |
623 | TransactionId prepareId, |
624 | LogInstant prepareStopAt, |
625 | LogInstant prepareStartAt) |
626 | throws StandardException |
627 | { |
628 | |
629 | if (SanityManager.DEBUG) |
630 | { |
631 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
632 | { |
633 | if (prepareStartAt != null) |
634 | { |
635 | SanityManager.DEBUG( |
636 | LogToFile.DBG_FLAG, |
637 | "----------------------------------------------------\n" + |
638 | "\nBegin of RePrepare : " + prepareId.toString() + |
639 | "start at " + prepareStartAt.toString() + |
640 | " stop at " + prepareStopAt.toString() + |
641 | "\n----------------------------------------------------\n"); |
642 | } |
643 | else |
644 | { |
645 | SanityManager.DEBUG( |
646 | LogToFile.DBG_FLAG, |
647 | "----------------------------------------------------\n" + |
648 | "\nBegin of Reprepare: " + prepareId.toString() + |
649 | "start at end of log stop at " + |
650 | prepareStopAt.toString() + |
651 | "\n----------------------------------------------------\n"); |
652 | } |
653 | } |
654 | } |
655 | |
656 | // statistics |
657 | int clrskipped = 0; |
658 | int logrecordseen = 0; |
659 | |
660 | RePreparable lop = null; |
661 | |
662 | // stream to read the log record - initial size 4096, scanLog needs |
663 | // to resize if the log record is larger than that. |
664 | ArrayInputStream rawInput = null; |
665 | |
666 | try |
667 | { |
668 | StreamLogScan scanLog; |
669 | |
670 | if (prepareStartAt == null) |
671 | { |
672 | // don't know where to start, scan from end of log |
673 | scanLog = |
674 | (StreamLogScan) logFactory.openBackwardsScan(prepareStopAt); |
675 | } |
676 | else |
677 | { |
678 | if (prepareStartAt.lessThan(prepareStopAt)) |
679 | { |
680 | // nothing to prepare! |
681 | return; |
682 | } |
683 | |
684 | scanLog = (StreamLogScan) |
685 | logFactory.openBackwardsScan( |
686 | ((LogCounter) prepareStartAt).getValueAsLong(), |
687 | prepareStopAt); |
688 | } |
689 | |
690 | if (SanityManager.DEBUG) |
691 | SanityManager.ASSERT( |
692 | scanLog != null, "cannot open log for prepare"); |
693 | |
694 | rawInput = new ArrayInputStream(new byte[4096]); |
695 | |
696 | LogRecord record; |
697 | |
698 | while ((record = |
699 | scanLog.getNextRecord(rawInput, prepareId, 0)) |
700 | != null) |
701 | { |
702 | if (SanityManager.DEBUG) |
703 | { |
704 | SanityManager.ASSERT( |
705 | record.getTransactionId().equals(prepareId), |
706 | "getNextRecord return unqualified log rec for prepare"); |
707 | } |
708 | |
709 | logrecordseen++; |
710 | |
711 | if (record.isCLR()) |
712 | { |
713 | clrskipped++; |
714 | |
715 | // the loggable is still in the input stream, get rid of it |
716 | record.skipLoggable(); |
717 | |
718 | // read the prepareInstant |
719 | long prepareInstant = rawInput.readLong(); |
720 | |
721 | if (SanityManager.DEBUG) |
722 | { |
723 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
724 | { |
725 | SanityManager.DEBUG( |
726 | LogToFile.DBG_FLAG, |
727 | "Skipping over CLRs, reset scan to " + |
728 | LogCounter.toDebugString(prepareInstant)); |
729 | } |
730 | } |
731 | |
732 | scanLog.resetPosition(new LogCounter(prepareInstant)); |
733 | // scanLog now positioned at the beginning of the log |
734 | // record that was rolled back by this CLR. |
735 | // The scan is a backward one so getNextRecord will skip |
736 | // over the record that was rolled back and go to the one |
737 | // previous to it |
738 | |
739 | continue; |
740 | } |
741 | |
742 | if (record.requiresPrepareLocks()) |
743 | { |
744 | lop = record.getRePreparable(); |
745 | } |
746 | else |
747 | { |
748 | continue; |
749 | } |
750 | |
751 | if (lop != null) |
752 | { |
753 | // Reget locks based on log record. reclaim all locks with |
754 | // a serializable locking policy, since we are only |
755 | // reclaiming write locks, isolation level does not matter |
756 | // much. |
757 | |
758 | lop.reclaimPrepareLocks( |
759 | t, |
760 | t.newLockingPolicy( |
761 | LockingPolicy.MODE_RECORD, |
762 | TransactionController.ISOLATION_REPEATABLE_READ, |
763 | true)); |
764 | |
765 | if (SanityManager.DEBUG) |
766 | { |
767 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
768 | { |
769 | SanityManager.DEBUG( |
770 | LogToFile.DBG_FLAG, |
771 | "Reprepare log record at instant " + |
772 | scanLog.getInstant() + " : " + lop); |
773 | } |
774 | } |
775 | |
776 | } |
777 | } |
778 | |
779 | } |
780 | catch (ClassNotFoundException cnfe) |
781 | { |
782 | throw logFactory.markCorrupt( |
783 | StandardException.newException(SQLState.LOG_CORRUPTED, cnfe)); |
784 | } |
785 | catch (IOException ioe) |
786 | { |
787 | throw logFactory.markCorrupt( |
788 | StandardException.newException( |
789 | SQLState.LOG_READ_LOG_FOR_UNDO, ioe)); |
790 | } |
791 | catch (StandardException se) |
792 | { |
793 | throw |
794 | logFactory.markCorrupt( |
795 | StandardException.newException( |
796 | SQLState.LOG_UNDO_FAILED, se, |
797 | prepareId, lop, (Object) null)); |
798 | } |
799 | finally |
800 | { |
801 | if (rawInput != null) |
802 | { |
803 | try |
804 | { |
805 | rawInput.close(); |
806 | } |
807 | catch (IOException ioe) |
808 | { |
809 | throw logFactory.markCorrupt( |
810 | StandardException.newException( |
811 | SQLState.LOG_READ_LOG_FOR_UNDO, ioe, prepareId)); |
812 | } |
813 | } |
814 | } |
815 | |
816 | if (SanityManager.DEBUG) |
817 | { |
818 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
819 | { |
820 | SanityManager.DEBUG(LogToFile.DBG_FLAG, "Finish prepare" + |
821 | ", clr skipped = " + clrskipped + |
822 | ", record seen = " + logrecordseen + "\n"); |
823 | } |
824 | } |
825 | |
826 | if (SanityManager.DEBUG) |
827 | { |
828 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
829 | { |
830 | SanityManager.DEBUG( |
831 | LogToFile.DBG_FLAG, |
832 | "----------------------------------------------------\n" + |
833 | "End of recovery rePrepare\n" + |
834 | ", clr skipped = " + clrskipped + |
835 | ", record seen = " + logrecordseen + |
836 | "\n----------------------------------------------------\n"); |
837 | } |
838 | } |
839 | } |
840 | |
841 | |
842 | /** |
843 | Undo a part of or the entire transaction. Begin rolling back the log |
844 | record at undoStartAt and stopping at (inclusive) the log record at |
845 | undoStopAt. |
846 | |
847 | <P>MT - Not needed. A transaction must be single threaded thru undo, |
848 | each RawTransaction has its own logger, therefore no need to |
849 | synchronize. The RawTransaction must handle synchronizing with |
850 | multiple threads during rollback. |
851 | |
852 | @param t the transaction that needs to be rolled back |
853 | @param undoId the transaction ID |
854 | @param undoStopAt the last log record that should be rolled back |
855 | @param undoStartAt the first log record that should be rolled back |
856 | |
857 | @exception StandardException Standard Cloudscape error policy |
858 | |
859 | @see Logger#undo |
860 | */ |
861 | public void undo( |
862 | RawTransaction t, |
863 | TransactionId undoId, |
864 | LogInstant undoStopAt, |
865 | LogInstant undoStartAt) |
866 | throws StandardException |
867 | { |
868 | |
869 | if (SanityManager.DEBUG) |
870 | { |
871 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
872 | { |
873 | if (undoStartAt != null) |
874 | { |
875 | SanityManager.DEBUG( |
876 | LogToFile.DBG_FLAG, |
877 | "\nUndo transaction: " + undoId.toString() + |
878 | "start at " + undoStartAt.toString() + |
879 | " stop at " + undoStopAt.toString() ); |
880 | } |
881 | else |
882 | { |
883 | SanityManager.DEBUG( |
884 | LogToFile.DBG_FLAG, |
885 | "\nUndo transaction: " + undoId.toString() + |
886 | "start at end of log stop at " + undoStopAt.toString()); |
887 | } |
888 | } |
889 | } |
890 | |
891 | // statistics |
892 | int clrgenerated = 0; |
893 | int clrskipped = 0; |
894 | int logrecordseen = 0; |
895 | |
896 | StreamLogScan scanLog; |
897 | Compensation compensation = null; |
898 | Undoable lop = null; |
899 | |
900 | // stream to read the log record - initial size 4096, scanLog needs |
901 | // to resize if the log record is larget than that. |
902 | ArrayInputStream rawInput = null; |
903 | |
904 | try |
905 | { |
906 | if (undoStartAt == null) |
907 | { |
908 | // don't know where to start, rollback from end of log |
909 | |
910 | scanLog = (StreamLogScan) |
911 | logFactory.openBackwardsScan(undoStopAt); |
912 | } |
913 | else |
914 | { |
915 | if (undoStartAt.lessThan(undoStopAt)) |
916 | { |
917 | // nothing to undo! |
918 | return; |
919 | } |
920 | |
921 | long undoStartInstant = |
922 | ((LogCounter) undoStartAt).getValueAsLong(); |
923 | |
924 | scanLog = (StreamLogScan) |
925 | logFactory.openBackwardsScan(undoStartInstant, undoStopAt); |
926 | } |
927 | |
928 | if (SanityManager.DEBUG) |
929 | SanityManager.ASSERT( |
930 | scanLog != null, "cannot open log for undo"); |
931 | |
932 | rawInput = new ArrayInputStream(new byte[4096]); |
933 | |
934 | LogRecord record; |
935 | |
936 | while ((record = |
937 | scanLog.getNextRecord(rawInput, undoId, 0)) |
938 | != null) |
939 | { |
940 | if (SanityManager.DEBUG) |
941 | { |
942 | SanityManager.ASSERT( |
943 | record.getTransactionId().equals(undoId), |
944 | "getNextRecord return unqualified log record for undo"); |
945 | } |
946 | |
947 | logrecordseen++; |
948 | |
949 | if (record.isCLR()) |
950 | { |
951 | clrskipped++; |
952 | |
953 | // the loggable is still in the input stream, get rid of it |
954 | record.skipLoggable(); |
955 | |
956 | // read the undoInstant |
957 | long undoInstant = rawInput.readLong(); |
958 | |
959 | if (SanityManager.DEBUG) |
960 | { |
961 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
962 | { |
963 | SanityManager.DEBUG( |
964 | LogToFile.DBG_FLAG, |
965 | "Skipping over CLRs, reset scan to " + |
966 | LogCounter.toDebugString(undoInstant)); |
967 | } |
968 | } |
969 | |
970 | |
971 | scanLog.resetPosition(new LogCounter(undoInstant)); |
972 | |
973 | // scanLog now positioned at the beginning of the log |
974 | // record that was rolled back by this CLR. |
975 | // The scan is a backward one so getNextRecord will skip |
976 | // over the record that was rolled back and go to the one |
977 | // previous to it |
978 | |
979 | continue; |
980 | } |
981 | |
982 | lop = record.getUndoable(); |
983 | |
984 | if (lop != null) |
985 | { |
986 | int optionalDataLength = rawInput.readInt(); |
987 | int savePosition = rawInput.getPosition(); |
988 | rawInput.setLimit(savePosition, optionalDataLength); |
989 | |
990 | compensation = lop.generateUndo(t, rawInput); |
991 | |
992 | if (SanityManager.DEBUG) |
993 | { |
994 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
995 | { |
996 | SanityManager.DEBUG( |
997 | LogToFile.DBG_FLAG, |
998 | "Rollback log record at instant " + |
999 | LogCounter.toDebugString(scanLog.getInstant()) + |
1000 | " : " + lop); |
1001 | } |
1002 | } |
1003 | |
1004 | clrgenerated++; |
1005 | |
1006 | if (compensation != null) |
1007 | { |
1008 | // generateUndo may have read stuff off the |
1009 | // stream, reset it for the undo operation. |
1010 | rawInput.setLimit(savePosition, optionalDataLength); |
1011 | |
1012 | // log the compensation op that rolls back the |
1013 | // operation at this instant |
1014 | t.logAndUndo( |
1015 | compensation, new LogCounter(scanLog.getInstant()), |
1016 | rawInput); |
1017 | |
1018 | compensation.releaseResource(t); |
1019 | compensation = null; |
1020 | } |
1021 | |
1022 | // if compensation is null, log operation is redo only |
1023 | } |
1024 | // if this is not an undoable operation, continue with next log |
1025 | // record |
1026 | } |
1027 | } |
1028 | catch (ClassNotFoundException cnfe) |
1029 | { |
1030 | throw logFactory.markCorrupt( |
1031 | StandardException.newException(SQLState.LOG_CORRUPTED, cnfe)); |
1032 | } |
1033 | catch (IOException ioe) |
1034 | { |
1035 | throw logFactory.markCorrupt( |
1036 | StandardException.newException( |
1037 | SQLState.LOG_READ_LOG_FOR_UNDO, ioe)); |
1038 | } |
1039 | catch (StandardException se) |
1040 | { |
1041 | // TODO (4327) - exceptions caught here are nested in the exception |
1042 | // below but for some reason the nested exceptions are not logged |
1043 | // or reported in any way. |
1044 | |
1045 | throw logFactory.markCorrupt( |
1046 | StandardException.newException( |
1047 | SQLState.LOG_UNDO_FAILED, se, undoId, lop, compensation)); |
1048 | } |
1049 | finally |
1050 | { |
1051 | if (compensation != null) |
1052 | { |
1053 | // errored out |
1054 | compensation.releaseResource(t); |
1055 | } |
1056 | |
1057 | if (rawInput != null) |
1058 | { |
1059 | try |
1060 | { |
1061 | rawInput.close(); |
1062 | } |
1063 | catch (IOException ioe) |
1064 | { |
1065 | throw logFactory.markCorrupt( |
1066 | StandardException.newException( |
1067 | SQLState.LOG_READ_LOG_FOR_UNDO, ioe, undoId)); |
1068 | } |
1069 | } |
1070 | } |
1071 | |
1072 | if (SanityManager.DEBUG) |
1073 | { |
1074 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
1075 | { |
1076 | SanityManager.DEBUG( |
1077 | LogToFile.DBG_FLAG, |
1078 | "Finish undo" + |
1079 | ", clr generated = " + clrgenerated + |
1080 | ", clr skipped = " + clrskipped + |
1081 | ", record seen = " + logrecordseen + "\n"); |
1082 | } |
1083 | } |
1084 | } |
1085 | |
1086 | |
1087 | /** |
1088 | Recovery Redo loop. |
1089 | |
1090 | <P> The log stream is scanned from the beginning (or |
1091 | from the undo low water mark of a checkpoint) forward until the end. |
1092 | The purpose of the redo pass is to repeat history, i.e, to repeat |
1093 | exactly the same set of changes the rawStore went thru right before it |
1094 | stopped. With each log record that is encountered in the redo pass: |
1095 | <OL> |
1096 | <LI>if it isFirst(), then the transaction factory is called upon to |
1097 | create a new transaction object. |
1098 | <LI>if it needsRedo(), its doMe() is called (if it is a compensation |
1099 | operation, then the undoable operation needs to be created first |
1100 | before the doMe is called). |
1101 | <LI>if it isComplete(), then the transaction object is closed. |
1102 | </OL> |
1103 | |
1104 | <P> MT - caller provides synchronization |
1105 | |
1106 | @param transFactory - the transaction factory |
1107 | @param redoLWM - if checkpoint seen, starting from this point |
1108 | on, apply redo if necessary |
1109 | |
1110 | @return the log instant of the next log record (or the instant just |
1111 | after the last log record). This is used to determine where the log |
1112 | truly ends |
1113 | |
1114 | @exception StandardException Standard Cloudscape error policy |
1115 | @exception IOException error reading log file |
1116 | @exception ClassNotFoundException log file corrupted |
1117 | |
1118 | @see LogToFile#recover |
1119 | */ |
1120 | protected long redo( |
1121 | RawTransaction recoveryTransaction, |
1122 | TransactionFactory transFactory, |
1123 | StreamLogScan redoScan, |
1124 | long redoLWM, |
1125 | long ttabInstant) |
1126 | throws IOException, StandardException, ClassNotFoundException |
1127 | { |
1128 | // begin debug info |
1129 | if (SanityManager.DEBUG) |
1130 | { |
1131 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
1132 | { |
1133 | SanityManager.DEBUG( |
1134 | LogToFile.DBG_FLAG, |
1135 | "In recovery redo, redoLWM = " + redoLWM); |
1136 | } |
1137 | } |
1138 | |
1139 | int scanCount = 0; |
1140 | int redoCount = 0; |
1141 | int prepareCount = 0; |
1142 | int clrCount = 0; |
1143 | int btranCount = 0; |
1144 | int etranCount = 0; |
1145 | |
1146 | // end debug info |
1147 | |
1148 | TransactionId tranId = null; |
1149 | |
1150 | // the current log instant |
1151 | long instant = LogCounter.INVALID_LOG_INSTANT; |
1152 | |
1153 | ////////////////////////////////////////////////////////////////////// |
1154 | // During redo time, the byte array in the logOutputBuffer is not used. |
1155 | // Use it to read the log record - if it is not big enough, scan |
1156 | // will resize it. We could create a brand new log input stream that |
1157 | // has nothing to do with logIn or logOutputBuffer but that seem like |
1158 | // a waste of memory. |
1159 | ////////////////////////////////////////////////////////////////////// |
1160 | logIn.setData(logOutputBuffer.getByteArray()); |
1161 | |
1162 | // use this scan to reconstitute operation to be undone |
1163 | // when we see a CLR in the redo scan |
1164 | StreamLogScan undoScan = null; |
1165 | Loggable op = null; |
1166 | long logEnd = 0; // we need to determine the log's true end |
1167 | |
1168 | try |
1169 | { |
1170 | |
1171 | // scan the log forward in redo pass and go to the end |
1172 | LogRecord record; |
1173 | while((record = |
1174 | redoScan.getNextRecord(logIn, null, 0)) |
1175 | != null) |
1176 | { |
1177 | scanCount++; |
1178 | long undoInstant = 0; |
1179 | |
1180 | // last known good instant |
1181 | instant = redoScan.getInstant(); |
1182 | |
1183 | // last known good log end |
1184 | logEnd = redoScan.getLogRecordEnd(); |
1185 | |
1186 | |
1187 | // NOTE NOTE -- be very careful about the undoInstant, it is |
1188 | // read off the input stream in this debug section. |
1189 | // if we change the log format we will need to change the way |
1190 | // the undo instant is gotten. Also, once it is read off, it |
1191 | // should not be read from the stream any more |
1192 | // NOTE NOTE |
1193 | if (SanityManager.DEBUG) |
1194 | { |
1195 | if (SanityManager.DEBUG_ON(LogToFile.DUMP_LOG_ONLY) || |
1196 | SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
1197 | |
1198 | { |
1199 | if (SanityManager.DEBUG_ON(LogToFile.DUMP_LOG_ONLY)) |
1200 | SanityManager.DEBUG_SET(LogToFile.DBG_FLAG); |
1201 | |
1202 | op = record.getLoggable(); |
1203 | tranId = record.getTransactionId(); |
1204 | if (record.isCLR()) |
1205 | { |
1206 | // !!!!!!! this moves the file pointer |
1207 | undoInstant = logIn.readLong(); |
1208 | |
1209 | SanityManager.DEBUG( |
1210 | LogToFile.DBG_FLAG, |
1211 | "scanned " + tranId + " : " + op + |
1212 | " instant = " + |
1213 | LogCounter.toDebugString(instant) + |
1214 | " undoInstant : " + |
1215 | LogCounter.toDebugString(undoInstant)); |
1216 | } |
1217 | else |
1218 | { |
1219 | SanityManager.DEBUG( |
1220 | LogToFile.DBG_FLAG, |
1221 | "scanned " + tranId + " : " + op + |
1222 | " instant = " + |
1223 | LogCounter.toDebugString(instant) |
1224 | + " logEnd = " + |
1225 | LogCounter.toDebugString(logEnd) |
1226 | + " logIn at " + logIn.getPosition() |
1227 | + " available " + logIn.available()); |
1228 | } |
1229 | |
1230 | // we only want to dump the log, don't touch it |
1231 | if (SanityManager.DEBUG_ON(LogToFile.DUMP_LOG_ONLY)) |
1232 | continue; |
1233 | } |
1234 | } |
1235 | |
1236 | // if the redo scan is between the undoLWM and redoLWM, we only |
1237 | // need to redo begin and end tran. Everything else has |
1238 | // already been flushed by checkpoint |
1239 | if (redoLWM != |
1240 | LogCounter.INVALID_LOG_INSTANT && instant < redoLWM) |
1241 | { |
1242 | if (!(record.isFirst() || |
1243 | record.isComplete() || |
1244 | record.isPrepare())) |
1245 | { |
1246 | continue; |
1247 | } |
1248 | } |
1249 | |
1250 | // get the transaction |
1251 | tranId = record.getTransactionId(); |
1252 | |
1253 | // if this transaction is known to the transaction factory, make |
1254 | // the recoveryTransaction assume its identitiy and properties |
1255 | // otherwise, make it known to the transaction factory |
1256 | if (!transFactory.findTransaction(tranId, recoveryTransaction)) |
1257 | { |
1258 | // transaction not found |
1259 | |
1260 | if (redoLWM != LogCounter.INVALID_LOG_INSTANT && |
1261 | instant < redoLWM && |
1262 | (record.isPrepare() || record.isComplete())) |
1263 | { |
1264 | // What is happening here is that a transaction that |
1265 | // started before the undoLWM has commited by the time |
1266 | // the checkpoint undoLWM was taken. Hence, we only |
1267 | // see the tail end of its log record and its endXact |
1268 | // record. |
1269 | // |
1270 | // NOTE: |
1271 | // Since we didn't see its beginXact, we cannot do the |
1272 | // endXact's doMe either. Also if the endXact, is |
1273 | // actually just a prepare, we don't need to do |
1274 | // anything as the transaction will commit or abort |
1275 | // prior to point we are recovering to. |
1276 | // If it is deemed necessary to do the endXact's doMe, |
1277 | // then we should start the transaction right here. |
1278 | // For now, just completely ignore this transaction |
1279 | // |
1280 | etranCount++; |
1281 | |
1282 | continue; |
1283 | } |
1284 | |
1285 | if ((ttabInstant == LogCounter.INVALID_LOG_INSTANT) && |
1286 | !record.isFirst()) |
1287 | { |
1288 | throw StandardException.newException( |
1289 | SQLState.LOG_UNEXPECTED_RECOVERY_PROBLEM, |
1290 | MessageService.getTextMessage(MessageId.LOG_RECORD_NOT_FIRST,tranId)); |
1291 | |
1292 | } |
1293 | |
1294 | if (SanityManager.DEBUG) |
1295 | { |
1296 | // if we dumped the transaction table but see a non |
1297 | // BeginXact record after the transaction table dump |
1298 | // instant, error. |
1299 | if (ttabInstant != LogCounter.INVALID_LOG_INSTANT) |
1300 | { |
1301 | if (instant > ttabInstant && !record.isFirst()) |
1302 | { |
1303 | SanityManager.THROWASSERT( |
1304 | "log record is Not first but transaction " + |
1305 | "is not in transaction table (2) : " + tranId); |
1306 | } |
1307 | |
1308 | // If we dump the transaction table and the table |
1309 | // does not have the transaction, and we see this |
1310 | // beginXact before the ttab instant, we could have |
1311 | // igored it because we "know" that we should see |
1312 | // the endXact before the ttab instant also. |
1313 | // Leave it in just in case. |
1314 | } |
1315 | } |
1316 | btranCount++; |
1317 | |
1318 | // the long transaction ID is embedded in the beginXact log |
1319 | // record. The short ID is stored in the log record. |
1320 | recoveryTransaction.setTransactionId( |
1321 | record.getLoggable(), tranId); |
1322 | |
1323 | } |
1324 | else |
1325 | { |
1326 | // recoveryTransaction found |
1327 | |
1328 | if ((ttabInstant == LogCounter.INVALID_LOG_INSTANT) && |
1329 | record.isFirst()) |
1330 | { |
1331 | throw StandardException.newException( |
1332 | SQLState.LOG_UNEXPECTED_RECOVERY_PROBLEM, |
1333 | MessageService.getTextMessage(MessageId.LOG_RECORD_FIRST, |
1334 | tranId)); |
1335 | |
1336 | |
1337 | } |
1338 | |
1339 | if (SanityManager.DEBUG) |
1340 | { |
1341 | if (ttabInstant != LogCounter.INVALID_LOG_INSTANT && |
1342 | instant > ttabInstant && |
1343 | record.isFirst()) |
1344 | { |
1345 | SanityManager.THROWASSERT( |
1346 | "log record is first but transaction is " + |
1347 | "already in transaction table (3): " + tranId); |
1348 | } |
1349 | |
1350 | if (record.isPrepare()) |
1351 | prepareCount++; |
1352 | } |
1353 | |
1354 | // if we have a transaction table dumped with the |
1355 | // checkpoint log record, then during the redo scan we may |
1356 | // see the beginXact of a transaction which is already in |
1357 | // the transaction table, just ignore it if it is after the |
1358 | // redoLWM but before the transaction table instant. We |
1359 | // still need to redo any database changes but since the |
1360 | // transaction is already recorded in the transaction |
1361 | // table, ignore it. |
1362 | // |
1363 | if (record.isFirst()) |
1364 | { |
1365 | btranCount++; |
1366 | continue; |
1367 | } |
1368 | } |
1369 | |
1370 | op = record.getLoggable(); |
1371 | |
1372 | if (SanityManager.DEBUG) |
1373 | { |
1374 | if (!record.isCLR()) |
1375 | { |
1376 | if (logIn.available() < 4) |
1377 | { |
1378 | SanityManager.THROWASSERT( |
1379 | "not enough bytes read in : " + |
1380 | logIn.available() + |
1381 | " for " + op + " instant " + |
1382 | LogCounter.toDebugString(instant)); |
1383 | } |
1384 | } |
1385 | } |
1386 | |
1387 | if (SanityManager.DEBUG) |
1388 | { |
1389 | SanityManager.ASSERT( |
1390 | !recoveryTransaction.handlesPostTerminationWork(), |
1391 | "recovery transaction handles post termination work"); |
1392 | } |
1393 | |
1394 | if (op.needsRedo(recoveryTransaction)) |
1395 | { |
1396 | redoCount++; |
1397 | |
1398 | if (record.isCLR()) |
1399 | { |
1400 | clrCount++; |
1401 | |
1402 | // the log operation is not complete, the operation to |
1403 | // undo is stashed away at the undoInstant. |
1404 | // Reconstitute that first. |
1405 | |
1406 | if (SanityManager.DEBUG) |
1407 | SanityManager.ASSERT(op instanceof Compensation); |
1408 | |
1409 | |
1410 | // this value may be set by sanity xxxx |
1411 | if (undoInstant == 0) |
1412 | undoInstant = logIn.readLong(); |
1413 | |
1414 | if (undoScan == null) |
1415 | { |
1416 | undoScan = (StreamLogScan) |
1417 | logFactory.openForwardsScan( |
1418 | undoInstant,(LogInstant)null); |
1419 | } |
1420 | else |
1421 | { |
1422 | undoScan.resetPosition(new LogCounter(undoInstant)); |
1423 | } |
1424 | |
1425 | // undoScan now positioned at the beginning of the log |
1426 | // record was rolled back by this CLR. |
1427 | // The scan is a forward one so getNextRecord will get |
1428 | // the log record that needs to be rolled back. |
1429 | |
1430 | // reuse the buffer in logIn and logIn since CLR |
1431 | // has no optional data and has no use for them anymore |
1432 | logIn.clearLimit(); |
1433 | LogRecord undoRecord = |
1434 | undoScan.getNextRecord(logIn, null, 0); |
1435 | |
1436 | Undoable undoOp = undoRecord.getUndoable(); |
1437 | |
1438 | if (SanityManager.DEBUG) |
1439 | { |
1440 | SanityManager.DEBUG( |
1441 | LogToFile.DBG_FLAG, |
1442 | "Redoing CLR: undoInstant = " + |
1443 | LogCounter.toDebugString(undoInstant) + |
1444 | " clrinstant = " + |
1445 | LogCounter.toDebugString(instant)); |
1446 | |
1447 | SanityManager.ASSERT( |
1448 | undoRecord.getTransactionId().equals(tranId)); |
1449 | |
1450 | SanityManager.ASSERT(undoOp != null); |
1451 | } |
1452 | |
1453 | ((Compensation)op).setUndoOp(undoOp); |
1454 | } |
1455 | |
1456 | // at this point, logIn points to the optional |
1457 | // data of the loggable that is to be redone or to be |
1458 | // rolled back |
1459 | |
1460 | if (SanityManager.DEBUG) |
1461 | { |
1462 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
1463 | { |
1464 | SanityManager.DEBUG( |
1465 | LogToFile.DBG_FLAG, |
1466 | "redoing " + op + |
1467 | " instant = " + |
1468 | LogCounter.toDebugString(instant)); |
1469 | } |
1470 | } |
1471 | |
1472 | int dataLength = logIn.readInt(); |
1473 | logIn.setLimit(logIn.getPosition(), dataLength); |
1474 | |
1475 | // even though the log has already been written, we need to |
1476 | // tie the page to the log stream so that if redo failed |
1477 | // for some reasons, the log factory's corruption will stop |
1478 | // the corrupt page from flushing to disk. |
1479 | |
1480 | op.doMe( |
1481 | recoveryTransaction, |
1482 | new LogCounter(instant), logIn); |
1483 | |
1484 | op.releaseResource(recoveryTransaction); |
1485 | |
1486 | op = null; |
1487 | } |
1488 | |
1489 | // RESOLVE: to speed up undo, may want to update the |
1490 | // LastLogInstant in the transaction table. |
1491 | // Right now, undo always start from the end of the log. |
1492 | |
1493 | // one last thing, if this is the last log record of the |
1494 | // transaction, then commit the transaction and clean up |
1495 | // |
1496 | // 'commit' even though the transaction maybe a rollback |
1497 | // because we already did all the rollback work when redoing |
1498 | // the CLRs. Commit will only flush the log if this session |
1499 | // has written any transaction, so in this case, it is a noop. |
1500 | if (record.isComplete()) |
1501 | { |
1502 | etranCount++; |
1503 | |
1504 | if (SanityManager.DEBUG) |
1505 | SanityManager.ASSERT( |
1506 | !recoveryTransaction.handlesPostTerminationWork(), |
1507 | "recovery xact handles post termination work"); |
1508 | |
1509 | recoveryTransaction.commit(); |
1510 | } |
1511 | } // while redoScan.getNextRecord() != null |
1512 | |
1513 | // If the scan ended in an empty file, update logEnd to reflect that |
1514 | // in order to avoid to continue logging to an older file |
1515 | long end = redoScan.getLogRecordEnd(); |
1516 | if (end != LogCounter.INVALID_LOG_INSTANT |
1517 | && (LogCounter.getLogFileNumber(logEnd) |
1518 | < LogCounter.getLogFileNumber(end))) { |
1519 | logEnd = end; |
1520 | } |
1521 | } |
1522 | catch (StandardException se) |
1523 | { |
1524 | throw StandardException.newException( |
1525 | SQLState.LOG_REDO_FAILED, se, op); |
1526 | } |
1527 | finally |
1528 | { |
1529 | // close all the io streams |
1530 | redoScan.close(); |
1531 | redoScan = null; |
1532 | |
1533 | if (undoScan != null) |
1534 | { |
1535 | undoScan.close(); |
1536 | undoScan = null; |
1537 | } |
1538 | |
1539 | if (op != null) |
1540 | op.releaseResource(recoveryTransaction); |
1541 | |
1542 | } |
1543 | |
1544 | if (SanityManager.DEBUG) |
1545 | { |
1546 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
1547 | { |
1548 | SanityManager.DEBUG( |
1549 | LogToFile.DBG_FLAG, |
1550 | "----------------------------------------------------\n" + |
1551 | "End of recovery redo\n" + |
1552 | "Scanned = " + scanCount + " log records" + |
1553 | ", redid = " + redoCount + |
1554 | " ( clr = " + clrCount + " )" + |
1555 | " begintran = " + btranCount + |
1556 | " endtran = " + etranCount + |
1557 | " preparetran = " + prepareCount + |
1558 | "\n log ends at " + LogCounter.toDebugString(logEnd) + |
1559 | "\n----------------------------------------------------\n"); |
1560 | } |
1561 | } |
1562 | |
1563 | if (SanityManager.DEBUG) |
1564 | { |
1565 | // make sure logEnd and instant is consistent |
1566 | if (instant != LogCounter.INVALID_LOG_INSTANT) |
1567 | { |
1568 | SanityManager.ASSERT( |
1569 | LogCounter.getLogFileNumber(instant) < |
1570 | LogCounter.getLogFileNumber(logEnd) || |
1571 | (LogCounter.getLogFileNumber(instant) == |
1572 | LogCounter.getLogFileNumber(logEnd) && |
1573 | LogCounter.getLogFilePosition(instant) <= |
1574 | LogCounter.getLogFilePosition(logEnd))); |
1575 | } |
1576 | else |
1577 | { |
1578 | SanityManager.ASSERT(logEnd == LogCounter.INVALID_LOG_INSTANT); |
1579 | } |
1580 | } |
1581 | |
1582 | // logEnd is the last good log record position in the log |
1583 | return logEnd; |
1584 | } |
1585 | |
1586 | |
1587 | /** |
1588 | Read the next log record from the scan. |
1589 | |
1590 | <P>MT - caller must provide synchronization (right now, it is only |
1591 | called in recovery to find the checkpoint log record. When this method |
1592 | is called by a more general audience, MT must be revisited). |
1593 | |
1594 | @param scan an opened log scan |
1595 | @param size estimated size of the log record |
1596 | |
1597 | @return the log operation that is the next in the scan, or null if no |
1598 | more log operation in the log scan |
1599 | |
1600 | @exception IOException Error reading the log file |
1601 | @exception StandardException Standard Cloudscape error policy |
1602 | @exception ClassNotFoundException log corrupted |
1603 | */ |
1604 | protected Loggable readLogRecord(StreamLogScan scan, int size) |
1605 | throws IOException, StandardException, ClassNotFoundException |
1606 | { |
1607 | Loggable lop = null; |
1608 | |
1609 | ArrayInputStream logInputBuffer = new ArrayInputStream(new byte[size]); |
1610 | |
1611 | LogRecord record = scan.getNextRecord(logInputBuffer, null, 0); |
1612 | if (record != null) |
1613 | lop = record.getLoggable(); |
1614 | return lop; |
1615 | } |
1616 | |
1617 | } |