1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.store.raw.log.LogAccessFile |
4 | |
5 | Copyright 1999, 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 | |
25 | import org.apache.derby.iapi.services.sanity.SanityManager; |
26 | import org.apache.derby.iapi.error.StandardException; |
27 | |
28 | import org.apache.derby.io.StorageRandomAccessFile; |
29 | |
30 | import java.io.IOException; |
31 | import java.io.OutputStream; |
32 | import java.io.SyncFailedException; |
33 | import java.io.InterruptedIOException; |
34 | import java.util.LinkedList; |
35 | |
36 | import org.apache.derby.iapi.services.io.FormatIdOutputStream; |
37 | import org.apache.derby.iapi.services.io.ArrayOutputStream; |
38 | import org.apache.derby.iapi.store.raw.RawStoreFactory; |
39 | |
40 | |
41 | /** |
42 | Wraps a RandomAccessFile file to provide buffering |
43 | on log writes. Only supports the write calls |
44 | required for the log! |
45 | |
46 | MT - unsafe. Caller of this class must provide synchronization. The one |
47 | exception is with the log file access, LogAccessFile will touch the log |
48 | only inside synchronized block protected by the semaphore, which is |
49 | defined by the creator of this object. |
50 | |
51 | Write to the log buffers are allowed when there are free buffers even |
52 | when dirty buffers are being written(flushed) to the disk by a different |
53 | thread. Only one flush writes to log file at a time, other wait for it to finish. |
54 | |
55 | Except for flushLogAccessFile , SyncAccessLogFile other function callers |
56 | must provide syncronization that will allow only one of them to write to |
57 | the buffers. |
58 | |
59 | Log Buffers are used in circular fashion, each buffer moves through following stages: |
60 | freeBuffers --> dirtyBuffers --> freeBuffers. Movement of buffers from one |
61 | stage to another stage is synchronized using the object(this) of this class. |
62 | |
63 | A Checksum log record that has the checksum value for the data that is |
64 | being written to the disk is generated and written before the actual data. |
65 | Except for the large log records that does not fit into a single buffer, |
66 | checksum is calcualted for a group of log records that are in the buffer |
67 | when buffers is switched. Checksum log record is written into the reserved |
68 | space in the beginning buffer. |
69 | |
70 | In case of a large log record that does not fit into a bufffer, it needs to |
71 | be written directly to the disk instead of going through the log buffers. |
72 | In this case the log record write gets broken into three parts: |
73 | 1) Write checksum log record and LOG RECORD HEADER (length + instant) |
74 | 2) Write the log record. |
75 | 3) Write the trailing length of the log record. |
76 | |
77 | Checksum log records helps in identifying the incomplete log disk writes during |
78 | recovery. This is done by recalculating the checksum value for the data on |
79 | the disk and comparing it to the the value stored in the checksum log |
80 | record. |
81 | |
82 | */ |
83 | public class LogAccessFile |
84 | { |
85 | |
86 | /** |
87 | * The fixed size of a log record is 16 bytes: |
88 | * int length : 4 bytes |
89 | * long instant : 8 bytes |
90 | * int trailing length : 4 bytes |
91 | **/ |
92 | private static final int LOG_RECORD_FIXED_OVERHEAD_SIZE = 16; |
93 | private static final int LOG_RECORD_HEADER_SIZE = 12; //(length + instant) |
94 | private static final int LOG_RECORD_TRAILER_SIZE = 4; //trailing length |
95 | private static final int LOG_NUMBER_LOG_BUFFERS = 3; |
96 | |
97 | |
98 | private LinkedList freeBuffers; //list of free buffers |
99 | private LinkedList dirtyBuffers; //list of dirty buffers to flush |
100 | private LogAccessFileBuffer currentBuffer; //current active buffer |
101 | private boolean flushInProgress = false; |
102 | |
103 | private final StorageRandomAccessFile log; |
104 | |
105 | // log can be touched only inside synchronized block protected by |
106 | // logFileSemaphore. |
107 | private final Object logFileSemaphore; |
108 | |
109 | static int mon_numWritesToLog; |
110 | static int mon_numBytesToLog; |
111 | |
112 | |
113 | //streams used to generated check sume log record ; see if there is any simpler way |
114 | private ArrayOutputStream logOutputBuffer; |
115 | private FormatIdOutputStream logicalOut; |
116 | private boolean directWrite = false; //true when log is written directly to file. |
117 | private long checksumInstant = -1; |
118 | private int checksumLength; |
119 | private int checksumLogRecordSize; //checksumLength + LOG_RECORD_FIXED_OVERHEAD_SIZE |
120 | private boolean writeChecksum; |
121 | private ChecksumOperation checksumLogOperation; |
122 | private LogRecord checksumLogRecord; |
123 | private LogToFile logFactory; |
124 | private boolean databaseEncrypted=false; |
125 | |
126 | public LogAccessFile(LogToFile logFactory, |
127 | StorageRandomAccessFile log, |
128 | int bufferSize) |
129 | { |
130 | if (SanityManager.DEBUG) |
131 | { |
132 | if(SanityManager.DEBUG_ON("LogBufferOff")) |
133 | bufferSize = 10; // make it very tiny |
134 | } |
135 | |
136 | this.log = log; |
137 | logFileSemaphore = log; |
138 | this.logFactory = logFactory; |
139 | |
140 | if (SanityManager.DEBUG) |
141 | SanityManager.ASSERT(LOG_NUMBER_LOG_BUFFERS >= 1); |
142 | |
143 | //initialize buffers lists |
144 | freeBuffers = new LinkedList(); |
145 | dirtyBuffers = new LinkedList(); |
146 | |
147 | |
148 | //add all buffers to free list |
149 | for (int i = 0; i < LOG_NUMBER_LOG_BUFFERS; i++) |
150 | { |
151 | LogAccessFileBuffer b = new LogAccessFileBuffer(bufferSize); |
152 | freeBuffers.addLast(b); |
153 | } |
154 | |
155 | currentBuffer = (LogAccessFileBuffer) freeBuffers.removeFirst(); |
156 | |
157 | // Support for Transaction Log Checksum in Derby was added in 10.1 |
158 | // Check to see if the Store have been upgraded to 10.1 or later before |
159 | // writing the checksum log records. Otherwise recovery will fail |
160 | // incase user tries to revert back to versions before 10.1 in |
161 | // soft upgrade mode. |
162 | writeChecksum = logFactory.checkVersion(RawStoreFactory.DERBY_STORE_MAJOR_VERSION_10, |
163 | RawStoreFactory.DERBY_STORE_MINOR_VERSION_1); |
164 | if(writeChecksum) |
165 | { |
166 | /** |
167 | * setup structures that are required to write the checksum log records |
168 | * for a group of log records are being written to the disk. |
169 | */ |
170 | checksumLogOperation = new ChecksumOperation(); |
171 | checksumLogOperation.init(); |
172 | checksumLogRecord = new LogRecord(); |
173 | |
174 | // Note: Checksum log records are not related any particular transaction, |
175 | // they are written to store a checksum information identify |
176 | // incomplete log record writes. No transacton id is set for this |
177 | // log record. That is why a null argument is passed below |
178 | // setValue(..) call. |
179 | checksumLogRecord.setValue(null, checksumLogOperation); |
180 | |
181 | checksumLength = |
182 | checksumLogRecord.getStoredSize(checksumLogOperation.group(), null) + |
183 | checksumLogOperation.getStoredSize(); |
184 | |
185 | // calculate checksum log operation length when the database is encrypted |
186 | if (logFactory.databaseEncrypted()) |
187 | { |
188 | checksumLength = logFactory.getEncryptedDataLength(checksumLength); |
189 | databaseEncrypted = true; |
190 | } |
191 | checksumLogRecordSize = checksumLength + LOG_RECORD_FIXED_OVERHEAD_SIZE; |
192 | |
193 | //streams required to convert a log record to raw byte array. |
194 | logOutputBuffer = new ArrayOutputStream(); |
195 | logicalOut = new FormatIdOutputStream(logOutputBuffer); |
196 | |
197 | /** initialize the buffer with space reserved for checksum log record in |
198 | * the beginning of the log buffer; checksum record is written into |
199 | * this space when buffer is switched or while doing direct write to the log file. |
200 | */ |
201 | }else |
202 | { |
203 | //checksumming of transaction log feature is not in use. |
204 | checksumLogRecordSize = 0; |
205 | } |
206 | |
207 | currentBuffer.init(checksumLogRecordSize); |
208 | } |
209 | |
210 | |
211 | private byte[] db = new byte[LOG_RECORD_TRAILER_SIZE]; |
212 | |
213 | |
214 | /** |
215 | * Write a single log record to the stream. |
216 | * <p> |
217 | * For performance pass all parameters rather into a specialized routine |
218 | * rather than maintaining the writeInt, writeLong, and write interfaces |
219 | * that this class provides as a standard OutputStream. It will make it |
220 | * harder to use other OutputStream implementations, but makes for less |
221 | * function calls and allows optimizations knowing when to switch buffers. |
222 | * <p> |
223 | * This routine handles all log records which are smaller than one log |
224 | * buffer. If a log record is bigger than a log buffer it calls |
225 | * writeUnbufferedLogRecord(). |
226 | * <p> |
227 | * The log record written will always look the same as if the following |
228 | * code had been executed: |
229 | * writeInt(length) |
230 | * writeLong(instant) |
231 | * write(data, data_offset, (length - optional_data_length) ) |
232 | * |
233 | * if (optional_data_length != 0) |
234 | * write(optional_data, optional_data_offset, optional_data_length) |
235 | * |
236 | * writeInt(length) |
237 | * |
238 | * @param length (data + optional_data) length bytes to write |
239 | * @param instant the log address of this log record. |
240 | * @param data "from" array to copy "data" portion of rec |
241 | * @param data_offset offset in "data" to start copying from. |
242 | * @param optional_data "from" array to copy "optional data" from |
243 | * @param optional_data_offset offset in "optional_data" to start copy from |
244 | * @param optional_data_length length of optional data to copy. |
245 | * |
246 | * @exception StandardException Standard exception policy. |
247 | **/ |
248 | public void writeLogRecord( |
249 | int length, |
250 | long instant, |
251 | byte[] data, |
252 | int data_offset, |
253 | byte[] optional_data, |
254 | int optional_data_offset, |
255 | int optional_data_length) |
256 | throws StandardException, IOException |
257 | { |
258 | int total_log_record_length = length + LOG_RECORD_FIXED_OVERHEAD_SIZE; |
259 | |
260 | if (total_log_record_length <= currentBuffer.bytes_free) |
261 | { |
262 | byte[] b = currentBuffer.buffer; |
263 | int p = currentBuffer.position; |
264 | |
265 | // writeInt(length) |
266 | p = writeInt(length, b, p); |
267 | |
268 | // writeLong(instant) |
269 | p = writeLong(instant, b , p); |
270 | |
271 | // write(data, data_offset, length - optional_data_length) |
272 | int transfer_length = (length - optional_data_length); |
273 | System.arraycopy(data, data_offset, b, p, transfer_length); |
274 | |
275 | p += transfer_length; |
276 | |
277 | if (optional_data_length != 0) |
278 | { |
279 | // write( |
280 | // optional_data, optional_data_offset, optional_data_length); |
281 | |
282 | System.arraycopy( |
283 | optional_data, optional_data_offset, |
284 | b, p, |
285 | optional_data_length); |
286 | |
287 | p += optional_data_length; |
288 | } |
289 | |
290 | // writeInt(length) |
291 | p = writeInt(length, b, p); |
292 | |
293 | currentBuffer.position = p; |
294 | currentBuffer.bytes_free -= total_log_record_length; |
295 | } |
296 | else |
297 | { |
298 | |
299 | /** Because current log record will never fit in a single buffer |
300 | * a direct write to the log file is required instead of |
301 | * writing the log record through the log bufffers. |
302 | */ |
303 | directWrite = true; |
304 | |
305 | byte[] b = currentBuffer.buffer; |
306 | int p = currentBuffer.position; |
307 | |
308 | // writeInt(length) |
309 | p = writeInt(length , b, p); |
310 | |
311 | // writeLong(instant) |
312 | p = writeLong(instant, b, p); |
313 | |
314 | currentBuffer.position = p; |
315 | currentBuffer.bytes_free -= LOG_RECORD_HEADER_SIZE; |
316 | |
317 | /** using a seperate small buffer to write the traling length |
318 | * instead of the log buffer because data portion will be |
319 | * written directly to log file after the log buffer is |
320 | * flushed and the trailing length should be written after that. |
321 | */ |
322 | |
323 | // writeInt(length) |
324 | writeInt(length , db, 0); |
325 | |
326 | if(writeChecksum) |
327 | { |
328 | checksumLogOperation.reset(); |
329 | checksumLogOperation.update(b, checksumLogRecordSize, p - checksumLogRecordSize); |
330 | checksumLogOperation.update(data, data_offset, length - optional_data_length); |
331 | if (optional_data_length != 0) |
332 | { |
333 | checksumLogOperation.update(optional_data, optional_data_offset, optional_data_length); |
334 | } |
335 | |
336 | // update the checksum to include the trailing length. |
337 | checksumLogOperation.update(db, 0, LOG_RECORD_TRAILER_SIZE); |
338 | |
339 | // write checksum log record to the log buffer |
340 | writeChecksumLogRecord(); |
341 | } |
342 | |
343 | |
344 | // now do the writes directly to the log file. |
345 | |
346 | // flush all buffers before wrting directly to the log file. |
347 | flushLogAccessFile(); |
348 | |
349 | // Note:No Special Synchronization required here , |
350 | // There will be nothing to write by flushDirtyBuffers that can run |
351 | // in parallel to the threads that is executing this code. Above |
352 | // flush call should have written all the buffers and NO new log will |
353 | // get added until the following direct log to file call finishes. |
354 | |
355 | |
356 | // write the rest of the log directltly to the log file. |
357 | writeToLog(data, data_offset, length - optional_data_length); |
358 | if (optional_data_length != 0) |
359 | { |
360 | writeToLog( |
361 | optional_data, optional_data_offset, optional_data_length); |
362 | } |
363 | |
364 | // write the trailing length |
365 | writeToLog(db,0, 4); |
366 | directWrite = false; |
367 | } |
368 | } |
369 | |
370 | |
371 | |
372 | private final int writeInt(int i , byte b[], int p) |
373 | { |
374 | |
375 | b[p++] = (byte) ((i >>> 24) & 0xff); |
376 | b[p++] = (byte) ((i >>> 16) & 0xff); |
377 | b[p++] = (byte) ((i >>> 8) & 0xff); |
378 | b[p++] = (byte) (i & 0xff); |
379 | return p; |
380 | } |
381 | |
382 | |
383 | private final int writeLong(long l , byte b[], int p) |
384 | { |
385 | b[p++] = (byte) (((int)(l >>> 56)) & 0xff); |
386 | b[p++] = (byte) (((int)(l >>> 48)) & 0xff); |
387 | b[p++] = (byte) (((int)(l >>> 40)) & 0xff); |
388 | b[p++] = (byte) (((int)(l >>> 32)) & 0xff); |
389 | b[p++] = (byte) (((int)(l >>> 24)) & 0xff); |
390 | b[p++] = (byte) (((int)(l >>> 16)) & 0xff); |
391 | b[p++] = (byte) (((int)(l >>> 8)) & 0xff); |
392 | b[p++] = (byte) (((int)l) & 0xff); |
393 | return p; |
394 | } |
395 | |
396 | public void writeInt(int i) |
397 | { |
398 | |
399 | if (SanityManager.DEBUG) |
400 | { |
401 | SanityManager.ASSERT(currentBuffer.bytes_free >= 4); |
402 | } |
403 | |
404 | currentBuffer.position = |
405 | writeInt(i , currentBuffer.buffer, currentBuffer.position); |
406 | currentBuffer.bytes_free -= 4; |
407 | } |
408 | |
409 | public void writeLong(long l) |
410 | { |
411 | |
412 | if (SanityManager.DEBUG) |
413 | { |
414 | SanityManager.ASSERT(currentBuffer.bytes_free >= 8); |
415 | } |
416 | |
417 | currentBuffer.position = |
418 | writeLong(l , currentBuffer.buffer, currentBuffer.position); |
419 | currentBuffer.bytes_free -= 8; |
420 | } |
421 | |
422 | public void write(int b) |
423 | { |
424 | if (SanityManager.DEBUG) |
425 | { |
426 | SanityManager.ASSERT(currentBuffer.bytes_free > 0); |
427 | } |
428 | |
429 | currentBuffer.buffer[currentBuffer.position++] = (byte) b; |
430 | currentBuffer.bytes_free--; |
431 | } |
432 | |
433 | |
434 | public void write(byte b[], int off, int len) |
435 | { |
436 | if (SanityManager.DEBUG) |
437 | { |
438 | SanityManager.ASSERT(len <= currentBuffer.bytes_free); |
439 | } |
440 | |
441 | System.arraycopy(b, off, currentBuffer.buffer, currentBuffer.position, len); |
442 | currentBuffer.bytes_free -= len; |
443 | currentBuffer.position += len; |
444 | } |
445 | |
446 | |
447 | /** |
448 | * Write data from all dirty buffers into the log file. |
449 | * <p> |
450 | * A call for clients of LogAccessFile to insure that all privately buffered |
451 | * data has been writen to the file - so that reads on the file using one |
452 | * of the various scan classes will see |
453 | * all the data which has been writen to this point. |
454 | * <p> |
455 | * Note that this routine only "writes" the data to the file, this does not |
456 | * mean that the data has been synced to disk unless file was opened in |
457 | * WRITE SYNC mode(rws/rwd). The only way to insure that is by calling |
458 | * is to call syncLogAccessFile() after this call in Non-WRITE sync mode(rw) |
459 | * |
460 | * <p> |
461 | * MT-Safe : parallel thereads can call this function, only one threads does |
462 | * the flush and the other threads waits for the one that is doing the flush to finish. |
463 | * Currently there are two possible threads that can call this function in parallel |
464 | * 1) A Thread that is doing the commit |
465 | * 2) A Thread that is writing to the log and log buffers are full or |
466 | * a log records does not fit in a buffer. (Log Buffers |
467 | * full(switchLogBuffer() or a log record size that is greater than |
468 | * logbuffer size has to be writtern through writeToLog call directlty) |
469 | * Note: writeToLog() is not synchronized on the semaphore |
470 | * that is used to do buffer management to allow writes |
471 | * to the free buffers when flush is in progress. |
472 | **/ |
473 | protected void flushDirtyBuffers() throws IOException |
474 | { |
475 | LogAccessFileBuffer buf = null; |
476 | int noOfBuffers; |
477 | int nFlushed= 0; |
478 | try{ |
479 | synchronized(this) |
480 | { |
481 | /**if some one else flushing wait, otherwise it is possible |
482 | * different threads will get different buffers and order can |
483 | * not be determined. |
484 | * |
485 | **/ |
486 | while(flushInProgress) |
487 | { |
488 | try{ |
489 | wait(); |
490 | }catch (InterruptedException ie) |
491 | { |
492 | //do nothing, let the flush request to complete. |
493 | //because it possible that other thread which is |
494 | //currently might have completed this request also , |
495 | //if exited on interrupt and throw exception, can not |
496 | //be sure whether this transaction is COMMITTED ot not. |
497 | } |
498 | } |
499 | |
500 | noOfBuffers = dirtyBuffers.size(); |
501 | if(noOfBuffers > 0) |
502 | buf = (LogAccessFileBuffer) dirtyBuffers.removeFirst(); |
503 | |
504 | flushInProgress = true; |
505 | } |
506 | |
507 | while(nFlushed < noOfBuffers) |
508 | { |
509 | if (buf.position != 0) |
510 | writeToLog(buf.buffer, 0, buf.position); |
511 | |
512 | nFlushed++; |
513 | synchronized(this) |
514 | { |
515 | //add the buffer that was written previosly to the free list |
516 | freeBuffers.addLast(buf); |
517 | if(nFlushed < noOfBuffers) |
518 | buf = (LogAccessFileBuffer) dirtyBuffers.removeFirst(); |
519 | else |
520 | { |
521 | //see if we can flush more, that came when we are at it. |
522 | //don't flush more than the total number of buffers, |
523 | //that might lead to starvation of the current thread. |
524 | int size = dirtyBuffers.size(); |
525 | if(size > 0 && nFlushed <= LOG_NUMBER_LOG_BUFFERS) |
526 | { |
527 | noOfBuffers += size; |
528 | buf = (LogAccessFileBuffer) dirtyBuffers.removeFirst(); |
529 | } |
530 | } |
531 | } |
532 | } |
533 | |
534 | |
535 | }finally{ |
536 | synchronized(this) |
537 | { |
538 | flushInProgress = false; |
539 | notifyAll(); |
540 | } |
541 | } |
542 | } |
543 | |
544 | |
545 | //flush all the the dirty buffers to disk |
546 | public void flushLogAccessFile() throws IOException, StandardException |
547 | { |
548 | switchLogBuffer(); |
549 | flushDirtyBuffers(); |
550 | } |
551 | |
552 | |
553 | /** |
554 | * Appends the current Buffer to the dirty Buffer list and assigns a free |
555 | * buffer to be the currrent active buffer . Flushing of the buffer |
556 | * to disk is delayed if there is a free buffer available. |
557 | * dirty buffers will be flushed to the disk |
558 | * when flushDirtyBuffers() is invoked by a commit call |
559 | * or when no more free buffers are available. |
560 | */ |
561 | public void switchLogBuffer() throws IOException, StandardException |
562 | { |
563 | |
564 | synchronized(this) |
565 | { |
566 | // ignore empty buffer switch requests |
567 | if(currentBuffer.position == checksumLogRecordSize) |
568 | return; |
569 | |
570 | // calculate the checksum for the current log buffer |
571 | // and write the record to the space reserverd in |
572 | // the beginning of the buffer. |
573 | if(writeChecksum && !directWrite) |
574 | { |
575 | checksumLogOperation.reset(); |
576 | checksumLogOperation.update(currentBuffer.buffer, checksumLogRecordSize, currentBuffer.position - checksumLogRecordSize); |
577 | writeChecksumLogRecord(); |
578 | } |
579 | |
580 | //add the current buffer to the flush buffer list |
581 | dirtyBuffers.addLast(currentBuffer); |
582 | |
583 | //if there is No free buffer, flush the buffers to get a free one |
584 | if(freeBuffers.size() == 0) |
585 | { |
586 | flushDirtyBuffers(); |
587 | //after the flush call there should be a free buffer |
588 | //because this is only methods removes items from |
589 | //free buffers and removal is in synchronized block. |
590 | } |
591 | |
592 | |
593 | // there should be free buffer available at this point. |
594 | if (SanityManager.DEBUG) |
595 | SanityManager.ASSERT(freeBuffers.size() > 0); |
596 | |
597 | //switch over to the next log buffer, let someone else write it. |
598 | currentBuffer = (LogAccessFileBuffer) freeBuffers.removeFirst(); |
599 | currentBuffer.init(checksumLogRecordSize); |
600 | |
601 | if (SanityManager.DEBUG) |
602 | { |
603 | SanityManager.ASSERT(currentBuffer.position == checksumLogRecordSize); |
604 | SanityManager.ASSERT( |
605 | currentBuffer.bytes_free == currentBuffer.length); |
606 | SanityManager.ASSERT(currentBuffer.bytes_free > 0); |
607 | } |
608 | } |
609 | } |
610 | |
611 | |
612 | /** |
613 | * Guarantee all writes up to the last call to flushLogAccessFile on disk. |
614 | * <p> |
615 | * A call for clients of LogAccessFile to insure that all data written |
616 | * up to the last call to flushLogAccessFile() are written to disk. |
617 | * This call will not return until those writes have hit disk. |
618 | * <p> |
619 | * Note that this routine may block waiting for I/O to complete so |
620 | * callers should limit the number of resource held locked while this |
621 | * operation is called. It is expected that the caller |
622 | * Note that this routine only "writes" the data to the file, this does not |
623 | * mean that the data has been synced to disk. The only way to insure that |
624 | * is to first call switchLogBuffer() and then follow by a call of sync(). |
625 | * |
626 | **/ |
627 | public void syncLogAccessFile() |
628 | throws IOException, StandardException |
629 | { |
630 | for( int i=0; ; ) |
631 | { |
632 | // 3311: JVM sync call sometimes fails under high load against NFS |
633 | // mounted disk. We re-try to do this 20 times. |
634 | try |
635 | { |
636 | synchronized( this) |
637 | { |
638 | log.sync( false); |
639 | } |
640 | |
641 | // the sync succeed, so return |
642 | break; |
643 | } |
644 | catch( SyncFailedException sfe ) |
645 | { |
646 | i++; |
647 | try |
648 | { |
649 | // wait for .2 of a second, hopefully I/O is done by now |
650 | // we wait a max of 4 seconds before we give up |
651 | Thread.sleep( 200 ); |
652 | } |
653 | catch( InterruptedException ie ) |
654 | { //does not matter weather I get interrupted or not |
655 | } |
656 | |
657 | if( i > 20 ) |
658 | throw StandardException.newException( |
659 | SQLState.LOG_FULL, sfe); |
660 | } |
661 | } |
662 | } |
663 | |
664 | /** |
665 | The database is being marked corrupted, get rid of file pointer without |
666 | writing out anything more. |
667 | */ |
668 | public void corrupt() throws IOException |
669 | { |
670 | synchronized(logFileSemaphore) |
671 | { |
672 | if (log != null) |
673 | log.close(); |
674 | } |
675 | } |
676 | |
677 | public void close() throws IOException, StandardException |
678 | { |
679 | if (SanityManager.DEBUG) |
680 | { |
681 | if (currentBuffer.position != checksumLogRecordSize) |
682 | SanityManager.THROWASSERT( |
683 | "Log file being closed with data still buffered " + |
684 | currentBuffer.position + " " + currentBuffer.bytes_free); |
685 | } |
686 | |
687 | flushLogAccessFile(); |
688 | |
689 | synchronized(logFileSemaphore) |
690 | { |
691 | if (log != null) |
692 | log.close(); |
693 | } |
694 | } |
695 | |
696 | |
697 | /* write to the log file */ |
698 | private void writeToLog(byte b[], int off, int len) throws IOException |
699 | { |
700 | synchronized(logFileSemaphore) |
701 | { |
702 | if (log != null) |
703 | { |
704 | |
705 | // Try to handle case where user application is throwing |
706 | // random interrupts at cloudscape threads, retry in the case |
707 | // of IO exceptions 5 times. After that hope that it is |
708 | // a real disk problem - an IO error in a write to the log file |
709 | // is going to take down the whole system, so seems worthwhile |
710 | // to retry. |
711 | for (int i = 0; ;i++) |
712 | { |
713 | try |
714 | { |
715 | log.write(b, off, len); |
716 | break; |
717 | } |
718 | catch (IOException ioe) |
719 | { |
720 | // just fall through and rety the log write 1st 5 times. |
721 | |
722 | if (i >= 5) |
723 | throw ioe; |
724 | } |
725 | } |
726 | } |
727 | } |
728 | |
729 | if (SanityManager.DEBUG) |
730 | { |
731 | mon_numWritesToLog++; |
732 | mon_numBytesToLog += len; |
733 | } |
734 | } |
735 | |
736 | /** |
737 | * reserve the space for the checksum log record in the log file. |
738 | * |
739 | * @param length the length of the log record to be written |
740 | * @param logFileNumber current log file number |
741 | * @param currentPosition current position in the log file. |
742 | * |
743 | * @return the space that is needed to write a checksum log record. |
744 | */ |
745 | protected long reserveSpaceForChecksum(int length, long logFileNumber, long currentPosition ) |
746 | throws StandardException, IOException |
747 | { |
748 | |
749 | int total_log_record_length = length + LOG_RECORD_FIXED_OVERHEAD_SIZE; |
750 | boolean reserveChecksumSpace = false; |
751 | |
752 | /* checksum log record is calculated for a group of log |
753 | * records that can fit in to a single buffer or for |
754 | * a single record when it does not fit into |
755 | * a fit into a buffer at all. When a new buffer |
756 | * is required to write a log record, log space |
757 | * has to be reserved before writing the log record |
758 | * becuase checksum is written in the before the |
759 | * log records that are being checksummed. |
760 | * What it also means is a real log instant has to be |
761 | * reserved for writing the checksum log record in addition |
762 | * to the log buffer space. |
763 | */ |
764 | |
765 | |
766 | /* reserve checkum space for new log records if a log buffer switch had |
767 | * happened before because of a explicit log flush requests(like commit) |
768 | * or a long record write |
769 | */ |
770 | if(currentBuffer.position == checksumLogRecordSize) |
771 | { |
772 | // reserver space if log checksum feature is enabled. |
773 | reserveChecksumSpace = writeChecksum; |
774 | } |
775 | else{ |
776 | if (total_log_record_length > currentBuffer.bytes_free) |
777 | { |
778 | // the log record that is going to be written is not |
779 | // going to fit in the current buffer, switch the |
780 | // log buffer to create buffer space for it. |
781 | switchLogBuffer(); |
782 | // reserve space if log checksum feature is enabled. |
783 | reserveChecksumSpace = writeChecksum; |
784 | } |
785 | } |
786 | |
787 | if(reserveChecksumSpace) |
788 | { |
789 | if (SanityManager.DEBUG) |
790 | { |
791 | // Prevoiusly reserved real checksum instant should have been |
792 | // used, before an another one is generated. |
793 | SanityManager.ASSERT(checksumInstant == -1, "CHECKSUM INSTANT IS GETTING OVER WRITTEN"); |
794 | } |
795 | |
796 | checksumInstant = LogCounter.makeLogInstantAsLong(logFileNumber, currentPosition); |
797 | return checksumLogRecordSize; |
798 | }else |
799 | { |
800 | return 0 ; |
801 | } |
802 | } |
803 | |
804 | |
805 | /* |
806 | * generate the checkum log record and write it into the log buffer. |
807 | */ |
808 | private void writeChecksumLogRecord() throws IOException, StandardException |
809 | { |
810 | |
811 | byte[] b = currentBuffer.buffer; |
812 | int p = 0; //checksum is written in the beginning of the buffer |
813 | |
814 | // writeInt(length) |
815 | p = writeInt(checksumLength, b , p); |
816 | |
817 | // writeLong(instant) |
818 | p = writeLong(checksumInstant, b , p); |
819 | |
820 | //write the checksum log operation |
821 | logOutputBuffer.setData(b); |
822 | logOutputBuffer.setPosition(p); |
823 | logicalOut.writeObject(checksumLogRecord); |
824 | |
825 | if(databaseEncrypted) |
826 | { |
827 | //encrypt the checksum log operation part. |
828 | int len = |
829 | logFactory.encrypt(b, LOG_RECORD_HEADER_SIZE, checksumLength, |
830 | b, LOG_RECORD_HEADER_SIZE); |
831 | |
832 | |
833 | if (SanityManager.DEBUG) |
834 | SanityManager.ASSERT(len == checksumLength, |
835 | "encrypted log buffer length != log buffer len"); |
836 | } |
837 | |
838 | p = LOG_RECORD_HEADER_SIZE + checksumLength ; |
839 | |
840 | // writeInt(length) trailing |
841 | p = writeInt(checksumLength, b, p ); |
842 | |
843 | if (SanityManager.DEBUG) |
844 | { |
845 | SanityManager.ASSERT(p == checksumLogRecordSize, "position=" + p + "ckrecordsize=" + checksumLogRecordSize); |
846 | if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) |
847 | { |
848 | SanityManager.DEBUG( |
849 | LogToFile.DBG_FLAG, |
850 | "Write log record: tranId=Null" + |
851 | " instant: " + LogCounter.toDebugString(checksumInstant) + " length: " + |
852 | checksumLength + "\n" + checksumLogOperation + "\n"); |
853 | } |
854 | checksumInstant = -1; |
855 | } |
856 | |
857 | } |
858 | |
859 | |
860 | protected void writeEndMarker(int marker) throws IOException, StandardException |
861 | { |
862 | //flush all the buffers and then write the end marker. |
863 | flushLogAccessFile(); |
864 | |
865 | byte[] b = currentBuffer.buffer; |
866 | int p = 0; //end is written in the beginning of the buffer, no |
867 | //need to checksum a int write. |
868 | p = writeInt(marker , b , p); |
869 | writeToLog(b, 0, p); |
870 | } |
871 | |
872 | |
873 | } |
874 | |
875 | |
876 | |
877 | |
878 | |
879 | |
880 | |
881 | |