/** * Decode the LONG_RECORD pointer that has previously been fetched into the Value. This will * replace the byte array in that value with the actual long value. * * @param value * @param minimumBytesToFetch * @throws PersistitException */ void fetchLongRecord(final Value value, final int minimumBytesToFetch, final long timeout) throws PersistitException { Buffer buffer = null; try { final byte[] rawBytes = value.getEncodedBytes(); final int rawSize = value.getEncodedSize(); if (rawSize != LONGREC_SIZE) { corrupt("Invalid LONG_RECORD value size=" + rawSize + " but should be " + LONGREC_SIZE); } if ((rawBytes[0] & 0xFF) != LONGREC_TYPE) { corrupt( "Invalid LONG_RECORD value type=" + (rawBytes[0] & 0xFF) + " but should be " + LONGREC_TYPE); } final int longSize = Buffer.decodeLongRecordDescriptorSize(rawBytes, 0); final long startAtPage = Buffer.decodeLongRecordDescriptorPointer(rawBytes, 0); int remainingSize = Math.min(longSize, minimumBytesToFetch); value.ensureFit(remainingSize); value.setEncodedSize(remainingSize); int offset = 0; System.arraycopy( rawBytes, LONGREC_PREFIX_OFFSET, value.getEncodedBytes(), offset, LONGREC_PREFIX_SIZE); offset += LONGREC_PREFIX_SIZE; remainingSize -= LONGREC_PREFIX_SIZE; long page = startAtPage; for (int count = 0; page != 0 && offset < minimumBytesToFetch; count++) { if (remainingSize <= 0) { corrupt( "Invalid LONG_RECORD remaining size=" + remainingSize + " of " + rawSize + " in page " + page); } buffer = _volume.getPool().get(_volume, page, false, true, timeout); if (buffer.getPageType() != PAGE_TYPE_LONG_RECORD) { corrupt( "LONG_RECORD chain is invalid at page " + page + " - invalid page type: " + buffer); } int segmentSize = buffer.getBufferSize() - HEADER_SIZE; if (segmentSize > remainingSize) { segmentSize = remainingSize; } System.arraycopy( buffer.getBytes(), HEADER_SIZE, value.getEncodedBytes(), offset, segmentSize); offset += segmentSize; remainingSize -= segmentSize; // previousPage = page; page = buffer.getRightSibling(); buffer.releaseTouched(); buffer = null; if (count > MAX_LONG_RECORD_CHAIN) { if (count > MAX_LONG_RECORD_CHAIN) { corrupt("LONG_RECORD chain starting at " + startAtPage + " is too long"); } } } value.setLongSize(rawSize); value.setEncodedSize(offset); } finally { if (buffer != null) { buffer.releaseTouched(); } } }
/** * Create a new LONG_RECORD chain and stores the supplied byte array in the pages of this chain. * The chain is written in right-to-left order so that any page having a right pointer points to a * valid successor. * * <p>Each page is written with its own timestamp (necessary to satisfy write order invariant). * Therefore a checkpoint could occur during the middle, after some pages have been assigned a * timestamp and before others. This means that a crash recovery could recover the tail of a * chain, but not its head. This does not cause corruption, but does cause permanent loss of the * pages that were recovered at the right end but never linked to a data page. Current remedy: * save/reload data. Such dangling chains can be detected by IntegrityCheck and a future remedy * would be for IntegrityCheck to move them back to the garbage chain. * * <p>If this method is called in the context of a transaction, it writes each page immediately to * the journal. This allows recovery to rebuild the long record for a recovered transaction that * committed after the keystone checkpoint. * * @param value The value. Must be in "long record mode" * @param inTxn indicates whether this operation is within the context of a transaction. * @throws PersistitException */ long storeLongRecord(final Value value, final boolean inTxn) throws PersistitException { value.changeLongRecordMode(true); // Calculate how many LONG_RECORD pages we will need. // boolean completed = false; final int longSize = value.getLongSize(); final byte[] longBytes = value.getLongBytes(); final byte[] rawBytes = value.getEncodedBytes(); final int maxSegmentSize = _volume.getPool().getBufferSize() - HEADER_SIZE; Debug.$assert0.t(value.isLongRecordMode()); Debug.$assert0.t(rawBytes.length == LONGREC_SIZE); System.arraycopy(longBytes, 0, rawBytes, LONGREC_PREFIX_OFFSET, LONGREC_PREFIX_SIZE); long looseChain = 0; sequence(LONG_RECORD_ALLOCATE_A); Buffer buffer = null; int offset = LONGREC_PREFIX_SIZE + (((longSize - LONGREC_PREFIX_SIZE - 1) / maxSegmentSize) * maxSegmentSize); try { for (; ; ) { while (offset >= LONGREC_PREFIX_SIZE) { buffer = _volume.getStructure().allocPage(); final long timestamp = _persistit.getTimestampAllocator().updateTimestamp(); buffer.writePageOnCheckpoint(timestamp); buffer.init(PAGE_TYPE_LONG_RECORD); int segmentSize = longSize - offset; if (segmentSize > maxSegmentSize) segmentSize = maxSegmentSize; Debug.$assert0.t( segmentSize >= 0 && offset >= 0 && offset + segmentSize <= longBytes.length && HEADER_SIZE + segmentSize <= buffer.getBytes().length); System.arraycopy(longBytes, offset, buffer.getBytes(), HEADER_SIZE, segmentSize); final int end = HEADER_SIZE + segmentSize; if (end < buffer.getBufferSize()) { buffer.clearBytes(end, buffer.getBufferSize()); } buffer.setRightSibling(looseChain); looseChain = buffer.getPageAddress(); buffer.setDirtyAtTimestamp(timestamp); if (inTxn) { buffer.writePage(); } buffer.releaseTouched(); offset -= maxSegmentSize; buffer = null; } final long page = looseChain; looseChain = 0; Buffer.writeLongRecordDescriptor(value.getEncodedBytes(), longSize, page); completed = true; return page; } } finally { if (buffer != null) buffer.releaseTouched(); if (looseChain != 0) { _volume.getStructure().deallocateGarbageChain(looseChain, 0); } if (!completed) { value.changeLongRecordMode(false); } } }