/*
* Copyright (c) 2010-2012 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package krati.core.array;
import java.io.IOException;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.ConcurrentLinkedQueue;
import krati.core.StoreParams;
import org.apache.log4j.Logger;
import krati.Mode;
import krati.Persistable;
import krati.PersistableListener;
import krati.array.Array;
import krati.array.DataArray;
import krati.core.array.SimpleDataArrayCompactor.CompactionUpdateBatch;
import krati.core.array.entry.Entry;
import krati.core.array.entry.EntryPersistAdapter;
import krati.core.array.entry.EntryValue;
import krati.core.segment.AddressFormat;
import krati.core.segment.Segment;
import krati.core.segment.SegmentException;
import krati.core.segment.SegmentIndexBuffer;
import krati.core.segment.SegmentManager;
import krati.core.segment.SegmentOverflowException;
import krati.io.Closeable;
/**
* SimpleDataArray provides an array like interface to <code>get</code> and <code>set</code>
* raw bytes at a specified array index. This class provides the core code for implementing
* different sub-classes of {@link krati.store.DataStore DataStore} and {@link krati.store.DataSet DataSet}.
*
* <p>
* This class is not thread-safe by design. It is expected that the conditions below hold within one JVM.
* <pre>
* 1. There is one and only one instance of SimpleDataArray for a given home directory.
* 2. There is one and only one thread calling setData and transferTo methods at any given time.
* </pre>
* </p>
* <p>
* It is expected that this class is used in the case of multiple readers and single writer.
* </p>
* @author jwu
*
* <p>
* 05/09, 2011 - added support for Closeable <br/>
* 05/22, 2011 - fixed method close() <br/>
* 05/23, 2011 - sync compaction batches in method close() <br/>
* 05/26, 2011 - added methods for partially reading data bytes <br/>
* 06/22, 2011 - catch SegmentException in method set() for safety <br/>
* 06/03, 2012 - fixed problematic sync upon calling method close() <br/>
* 06/11, 2012 - Simplified compaction update <br/>
* 08/31, 2012 - Enabled segment index buffer <br/>
* 09/09, 2012 - Removed throttling as compaction is efficient with SIB <br/>
*/
public class SimpleDataArray implements DataArray, Persistable, Closeable {
private final static Logger _log = Logger.getLogger(SimpleDataArray.class);
/**
* The internal address array (i.e. indexes.dat).
*/
protected final AddressArray _addressArray;
/**
* The internal address format encoding the Segment Id and the offset inside the Segment.
*/
protected final AddressFormat _addressFormat;
/**
* The Segment manager which manages Segments and the meta data associated with Segments.
*/
protected final SegmentManager _segmentManager;
/**
* The Segment compactor which reclaims a fragmented Segment by transferring data into a new one.
*/
protected final SimpleDataArrayCompactor _compactor;
/**
* The load factor of Segment, below which a Segment is eligible for compaction (i.e. being reclaimed).
*/
protected final double _segmentCompactFactor;
/**
* Current working segment to append data to.
*/
private volatile Segment _segment;
/**
* The mode can only be <code>Mode.INIT</code>, <code>Mode.OPEN</code> and <code>Mode.CLOSED</code>.
*/
private volatile Mode _mode = Mode.INIT;
/**
* Current working segment index buffer.
*/
private volatile SegmentIndexBuffer _sib;
/**
* Segment index buffer is enabled by default.
*/
private volatile boolean _sibEnabled = true;
/**
* The Persistable event listener, default <code>null</code>.
*/
private volatile PersistableListener _listener = null;
/**
* The high water mark set so far.
*/
volatile long _hwmSet = 0;
/**
* The readers' view of high water mark for volatile piggyback.
*/
volatile long _hwmGet = 0;
/**
* Constructs a DataArray with Segment Compact Factor default to 0.5.
*
* @param addressArray the array of addresses (i.e. pointers to Segment).
* @param segmentManager the segment manager for loading, creating, freeing, maintaining segments.
*/
public SimpleDataArray(AddressArray addressArray, SegmentManager segmentManager) {
this(addressArray, segmentManager, StoreParams.SEGMENT_COMPACT_FACTOR_DEFAULT);
}
/**
* Constructs a DataArray.
*
* @param addressArray the array of addresses (i.e. pointers to Segment).
* @param segmentManager the segment manager for loading, creating, freeing, maintaining segments.
* @param segmentCompactFactor the load factor below which a segment is eligible for compaction. The recommended value is 0.5.
*/
public SimpleDataArray(AddressArray addressArray,
SegmentManager segmentManager,
double segmentCompactFactor) {
this._addressArray = addressArray;
this._segmentManager = segmentManager;
this._segmentCompactFactor = segmentCompactFactor;
this._addressFormat = new AddressFormat();
// Add segment persist listener
addressArray.setPersistListener(new SegmentPersistListener());
// Start segment data compactor
_compactor = new SimpleDataArrayCompactor(this, getSegmentCompactFactor());
_compactor.start();
this.init();
this._mode = Mode.OPEN;
_log.info("mode=" + _mode);
}
/**
* Consumes a {@link CompactionUpdateBatch} produced by the Segment compactor.
*
* @param updateBatch - the batch of compaction updates produced by the Segment compactor.
* @throws Exception if the updateBatch cannot be consumed
*/
private void consumeCompaction(CompactionUpdateBatch updateBatch) throws Exception {
int ignoreCount = 0;
int updateCount = updateBatch.size();
int totalIgnoreBytes = 0;
int totalUpdateBytes = updateBatch.getDataSizeTotal();
/* Using lwMark is to guarantee that redo logs from compaction will not increase
* the hwMark of the underlying array file. If hwMark is being used, the current
* redo entry is discarded upon crash. Upon restart, the hwMark from the underlying
* array file may prevent updates in the lost redo entry from being resent.
*/
long updateScn = _addressArray.getLWMark();
Segment segTarget = updateBatch.getTargetSegment();
for(int i = 0; i < updateCount; i++) {
int index = updateBatch.getUpdateIndex(i);
long origAddr = updateBatch.getOriginDataAddr(i);
long currAddr = getAddress(index);
if(currAddr == 0 || /* data at the given index is deleted by writer */
currAddr != origAddr /* data at the given index is updated by writer */) {
/*
* The address generated by the compactor is obsolete.
*/
int updateBytes = updateBatch.getUpdateDataSize(i);
totalIgnoreBytes += updateBytes;
ignoreCount++;
} else {
/*
* The address generated by the compactor has not been touched by the writer.
* Update the address array directly.
*/
setCompactionAddress(index, updateBatch.getUpdateDataAddr(i), updateScn);
}
}
int consumeCount = updateCount - ignoreCount;
int totalConsumeBytes = totalUpdateBytes - totalIgnoreBytes;
if(_log.isTraceEnabled()) {
_log.trace("consumed compaction batch " +
updateBatch.getDescriptiveId() +
" updates " + consumeCount + "/" + updateCount +
" bytes " + totalConsumeBytes + "/" + totalUpdateBytes);
}
// Update segment load size
segTarget.decrLoadSize(totalIgnoreBytes);
}
/**
* Consumes a {@link CompactionUpdateBatch} if available.
*
* @return <code>true</code> if a {@link CompactionUpdateBatch} is found and then consumed.
* Otherwise, <code>false</code>.
*/
protected boolean consumeCompactionBatch() {
/*
* Consume one compaction update batch generated by the compactor.
*/
CompactionUpdateBatch updateBatch = _compactor.pollCompactionBatch();
if(updateBatch != null) {
try {
consumeCompaction(updateBatch);
} catch (Exception e) {
_log.error("failed to consume compaction batch " + updateBatch.getDescriptiveId(), e);
} finally {
_compactor.recycleCompactionBatch(updateBatch);
}
return true;
}
return false;
}
/**
* Consumes all {@link CompactionUpdateBatch}(es) produced by the Segment compactor.
*/
protected void consumeCompactionBatches() {
while(true) {
CompactionUpdateBatch updateBatch = _compactor.pollCompactionBatch();
if(updateBatch == null) break;
try {
consumeCompaction(updateBatch);
} catch (Exception e) {
_log.error("failed to consume compaction batch " + updateBatch.getDescriptiveId(), e);
} finally {
_compactor.recycleCompactionBatch(updateBatch);
}
}
}
/**
* Sync with the Segment compactor to consume all the produced {@link CompactionUpdateBatch}(es).
*/
protected void syncCompactor() {
ConcurrentLinkedQueue<Segment> queue = _compactor.getCompactedQueue();
while(!queue.isEmpty()) {
Segment seg = queue.remove();
consumeCompactionBatches();
_compactor.getFreeQueue().offer(seg);
}
consumeCompactionBatches();
}
/**
* Submit current segment index buffer for asynchronous handling.
*/
protected void submitSegmentIndexBuffer() {
if(_sibEnabled && !_sib.isDirty()) {
_sib.setSegmentId(_segment.getSegmentId());
_sib.setSegmentLastForcedTime(_segment.getLastForcedTime());
_segmentManager.submit(_sib);
} else {
_segmentManager.remove(_sib);
}
}
/**
* Initialize this SimpleDataArray after it is instantiated.
*/
protected void init() {
try {
// Initialize the current working segment
_segment = _segmentManager.nextSegment();
// Segment index buffer is enabled by default!
_sib = _segmentManager.openSegmentIndexBuffer(_segment.getSegmentId());
if(!_sibEnabled) _sib.markAsDirty();
_log.info("Segment " + _segment.getSegmentId() + " online: " + _segment.getStatus());
} catch(IOException ioe) {
_log.error(ioe.getMessage(), ioe);
throw new SegmentException("Instantiation failed due to " + ioe.getMessage());
}
}
/**
* Gets the {@link AddressFormat} used by this SimpleDataArray to locate data bytes in Segments.
*/
public final AddressFormat getAddressFormat() {
return _addressFormat;
}
/**
* Gets the address array.
*/
public final AddressArray getAddressArray() {
return _addressArray;
}
/**
* Gets the address (long value) at the specified array index.
*
* @param index - the array index.
*/
public final long getAddress(int index) {
_hwmGet = _hwmSet;
return _addressArray.get(index);
}
/**
* Sets the address (long value) at the specified array index.
*
* @param index - the array index.
* @param value - the address value.
* @param scn - the System Change Number (SCN) representing an ever-increasing update order.
* @throws Exception if the address cannot be updated at the specified array index.
*/
protected void setAddress(int index, long value, long scn) throws Exception {
_addressArray.set(index, value, scn);
_hwmSet = Math.max(_hwmSet, scn);
}
/**
* Sets the address (long value), which is produced by the Segment compactor,
* at the specified array index.
*
* @param index - the array index.
* @param value - the address value.
* @param scn - the System Change Number (SCN) representing an ever-increasing update order.
* @throws Exception if the address cannot be updated at the specified array index.
*/
protected void setCompactionAddress(int index, long value, long scn) throws Exception {
_addressArray.setCompactionAddress(index, value, scn);
_hwmSet = Math.max(_hwmSet, scn);
}
/**
* Gets the Segment compact factor, below which a Segment is eligible for compaction (i.e. being reclaimed).
*/
protected double getSegmentCompactFactor() {
return _segmentCompactFactor;
}
/**
* Gets the Segment manager, which manages Segments and the meta data associated with Segments.
*/
protected SegmentManager getSegmentManager() {
return _segmentManager;
}
/**
* Gets the current Segment to which data is written.
*/
protected Segment getCurrentSegment() {
return _segment;
}
/**
* Decrease the load factor of a Segment based on the specified array index.
*
* @param index - the array index.
*/
protected void decrOriginalSegmentLoad(int index) {
try {
long address = getAddress(index);
int segPos = _addressFormat.getOffset(address);
int segInd = _addressFormat.getSegment(address);
int length = _addressFormat.getDataSize(address);
if (segPos >= Segment.dataStartPosition) {
// get data segment
Segment seg = _segmentManager.getSegment(segInd);
// read data length
if(seg != null) seg.decrLoadSize(4 + ((length == 0) ? seg.readInt(segPos) : length));
}
}
catch(IOException e1) {}
catch(IndexOutOfBoundsException e2) {}
}
/**
* Checks if the array index is out of the known bounds.
*
* @param index - the array index
*/
private final void rangeCheck(int index) {
if(!_addressArray.hasIndex(index)) {
throw new ArrayIndexOutOfBoundsException(index);
}
}
/**
* @return <code>true</code> if this array has data at the given index. Otherwise, <code>false</code>.
* @throws ArrayIndexOutOfBoundsException if the index is out of range.
*/
@Override
public boolean hasData(int index) {
rangeCheck(index);
long address = getAddress(index);
int segPos = _addressFormat.getOffset(address);
int segInd = _addressFormat.getSegment(address);
// no data found
if(segPos < Segment.dataStartPosition) return false;
// get data segment
Segment seg = _segmentManager.getSegment(segInd);
if(seg == null) return false;
return true;
}
/**
* @return the length of data at the given index.
* If the given index is out of the array index range, <code>-1<code> is returned.
*/
@Override
public int getLength(int index) {
try {
long address = getAddress(index);
int segPos = _addressFormat.getOffset(address);
int segInd = _addressFormat.getSegment(address);
// no data found
if(segPos < Segment.dataStartPosition) return -1;
// get data segment
Segment seg = _segmentManager.getSegment(segInd);
if(seg == null) return -1;
// read data length
int size = _addressFormat.getDataSize(address);
return (size == 0) ? seg.readInt(segPos) : size;
} catch(Exception e) {
_log.warn(e.getMessage());
return -1;
}
}
/**
* Gets data at a given index.
*
* @param index the array index
* @return the data at a given index.
* @throws ArrayIndexOutOfBoundsException if the index is out of range.
*/
@Override
public byte[] get(int index) {
rangeCheck(index);
try {
long address = getAddress(index);
int segPos = _addressFormat.getOffset(address);
int segInd = _addressFormat.getSegment(address);
// no data found
if(segPos < Segment.dataStartPosition) return null;
// get data segment
Segment seg = _segmentManager.getSegment(segInd);
if(seg == null) return null;
// read data length
int size = _addressFormat.getDataSize(address);
int len = (size == 0) ? seg.readInt(segPos) : size;
// read data into byte array
byte[] data = new byte[len];
if (len > 0) {
seg.read(segPos + 4, data);
}
return data;
} catch(Exception e) {
_log.warn(e.getMessage());
return null;
}
}
/**
* Gets data at a given index.
*
* @param index the array index
* @param data the byte array to fill in
* @return the length of data at the given index.
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* or if the byte array does not have enough space to hold the read data.
*/
@Override
public int get(int index, byte[] data) {
return get(index, data, 0);
}
/**
* Gets data at a given index.
*
* @param index the array index
* @param data the byte array to fill in
* @param offset the offset of the byte array where data is filled in
* @return the length of data at the given index.
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* or if the byte array does not have enough space to hold the read data.
*/
@Override
public int get(int index, byte[] data, int offset) {
rangeCheck(index);
try {
long address = getAddress(index);
int segPos = _addressFormat.getOffset(address);
int segInd = _addressFormat.getSegment(address);
// no data found
if(segPos < Segment.dataStartPosition) return -1;
// get data segment
Segment seg = _segmentManager.getSegment(segInd);
if(seg == null) return -1;
// read data length
int size = _addressFormat.getDataSize(address);
int len = (size == 0) ? seg.readInt(segPos) : size;
// read data into byte array
if (len > 0) {
seg.read(segPos + 4, data, offset, len);
}
return len;
} catch(Exception e) {
_log.warn(e.getMessage());
return -1;
}
}
/**
* Reads data bytes at an index into a byte array.
*
* This method does a full read of data bytes only if the destination byte
* array has enough capacity to store all the bytes from the specified index.
* Otherwise, a partial read is done to fill in the destination byte array.
*
* @param index the array index
* @param dst the byte array to fill in
* @return the total number of bytes read if data is available at the given index.
* Otherwise, <code>-1</code>.
*/
public int read(int index, byte[] dst) {
rangeCheck(index);
try {
long address = getAddress(index);
int segPos = _addressFormat.getOffset(address);
int segInd = _addressFormat.getSegment(address);
// no data found
if(segPos < Segment.dataStartPosition) return -1;
// get data segment
Segment seg = _segmentManager.getSegment(segInd);
if(seg == null) return -1;
// read data length
int size = _addressFormat.getDataSize(address);
int len = (size == 0) ? seg.readInt(segPos) : size;
// read data into byte array
if (len > 0) {
len = Math.min(len, dst.length);
seg.read(segPos + 4, dst, 0, len);
}
return len;
} catch(Exception e) {
_log.warn(e.getMessage());
return -1;
}
}
/**
* Reads data bytes from an offset of data at an index to fill in a byte array.
*
* @param index the array index
* @param offset the offset of data bytes
* @param dst the byte array to fill in
* @return the total number of bytes read if data is available at the given index.
* Otherwise, <code>-1</code>.
*/
public int read(int index, int offset, byte[] dst) {
rangeCheck(index);
try {
long address = getAddress(index);
int segPos = _addressFormat.getOffset(address);
int segInd = _addressFormat.getSegment(address);
// no data found
if(segPos < Segment.dataStartPosition) return -1;
// get data segment
Segment seg = _segmentManager.getSegment(segInd);
if(seg == null) return -1;
// read data length
int size = _addressFormat.getDataSize(address);
int len = (size == 0) ? seg.readInt(segPos) : size;
// read data into byte array
if (len > 0) {
if (len > offset) {
len = Math.min(len - offset, dst.length);
seg.read(segPos + 4 + offset, dst, 0, len);
} else {
return -1;
}
}
return len;
} catch(Exception e) {
_log.warn(e.getMessage());
return -1;
}
}
/**
* Transfers data at a given index to a writable channel.
*
* @param index the array index
* @return the amount of bytes transferred (the length of data at the given index).
* @throws ArrayIndexOutOfBoundsException if the index is out of range.
*/
@Override
public int transferTo(int index, WritableByteChannel channel) {
rangeCheck(index);
try {
long address = getAddress(index);
int segPos = _addressFormat.getOffset(address);
int segInd = _addressFormat.getSegment(address);
// no data found
if(segPos < Segment.dataStartPosition) return -1;
// get data segment
Segment seg = _segmentManager.getSegment(segInd);
if(seg == null) return -1;
// read data length
int size = _addressFormat.getDataSize(address);
int len = (size == 0) ? seg.readInt(segPos) : size;
// transfer data to a writable channel
if (len > 0) {
seg.transferTo(segPos + 4, len, channel);
}
return len;
} catch(Exception e) {
return -1;
}
}
/**
* Sets data at a given index.
*
* @param index the array index
* @param data the data (byte array).
* If <code>null</code>, the data at the given index will be removed.
* @param scn the global scn indicating the sequence of this change
* @throws ArrayIndexOutOfBoundsException if the index is out of range.
*/
@Override
public void set(int index, byte[] data, long scn) throws Exception {
if(data == null) {
set(index, data, 0, 0, scn);
} else {
set(index, data, 0, data.length, scn);
}
}
/**
* Sets data at a given index.
*
* @param index the array index
* @param data the data (byte array)
* If <code>null</code>, the data at the given index will be removed.
* @param offset the offset of byte array where data is read
* @param length the length of data to read from the byte array
* @param scn the global scn indicating the sequence of this change
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* or if the offset and length is not properly specified.
*/
@Override
public void set(int index, byte[] data, int offset, int length, long scn) throws Exception {
rangeCheck(index);
decrOriginalSegmentLoad(index);
// no data
if (data == null) {
setAddress(index, 0, scn);
return;
}
if (offset > data.length || (offset + length) > data.length) {
throw new ArrayIndexOutOfBoundsException(data.length);
}
while(true) {
// get append position
long pos = _segment.getAppendPosition();
try {
// check append position is in range
if ((pos >> _addressFormat.getSegmentShift()) > 0) {
throw new SegmentOverflowException(_segment);
}
// append actual size
_segment.appendInt(length);
// append actual data
if (length > 0) {
_segment.append(data, offset, length);
}
// update addressArray
long address = _addressFormat.composeAddress((int)pos, _segment.getSegmentId(), length);
setAddress(index, address, scn);
// update segment index buffer
if(!_sib.isDirty()) {
_sib.add(index, (int)pos);
}
// consume one compaction batch if available
consumeCompactionBatch();
return;
} catch(SegmentException se) {
_log.info("Segment " + _segment.getSegmentId() + " filled: " + _segment.getStatus());
// submit current segment index buffer
submitSegmentIndexBuffer();
// update next segment and segment index buffer
Segment nextSegment = _compactor.peekTargetSegment();
if(nextSegment != null) {
persist();
_segment = nextSegment;
_compactor.pollTargetSegment();
_log.trace("nextSegment from compactor");
} else {
if(_compactor.isStarted()) {
if(_compactor.getAndDecrementSegmentPermit()) {
persist();
_log.trace("nextSegment permit granted");
_segment = _segmentManager.nextSegment();
} else {
_log.trace("nextSegment permit refused");
// wait until compactor is done
long startTime = System.currentTimeMillis();
while(_compactor.isStarted()) {
consumeCompactionBatches();
}
long elapsedTime = System.currentTimeMillis() - startTime;
_log.info("nextSegment compaction wait " + elapsedTime + " ms");
persist();
// get the next segment available for appending
nextSegment = _compactor.pollTargetSegment();
_segment = (nextSegment != null) ? nextSegment : _segmentManager.nextSegment();
}
} else {
_log.trace("nextSegment");
persist();
// get the next segment available for appending
_segment = _segmentManager.nextSegment();
_compactor.startsCycle();
}
}
if(_sibEnabled) {
_sib = _segmentManager.openSegmentIndexBuffer(_segment.getSegmentId());
} else {
_sib.clear();
_sib.markAsDirty();
_sib.setSegmentId(_segment.getSegmentId());
}
_log.info("Segment " + _segment.getSegmentId() + " online: " + _segment.getStatus());
} catch(Exception e) {
// restore append position
_segment.setAppendPosition(pos);
_segment.force();
throw e;
}
}
}
@Override
public boolean hasIndex(int index) {
return _addressArray.hasIndex(index);
}
@Override
public int length() {
return _addressArray.length();
}
@Override
public long getLWMark() {
return _addressArray.getLWMark();
}
@Override
public long getHWMark() {
return _addressArray.getHWMark();
}
@Override
public synchronized void saveHWMark(long endOfPeriod) throws Exception {
if(isOpen()) {
_addressArray.saveHWMark(endOfPeriod);
}
}
@Override
public synchronized void sync() throws IOException {
if(isOpen()) {
syncInternal();
}
}
/**
* Sync changes/updates to storage devices.
*
* @throws IOException
*/
private void syncInternal() throws IOException {
syncCompactor();
fireBeforePersist();
/* CALLS ORDERED: Need force _segment first and then persist
* _addressArray. During recovery, the _addressArray can always
* point to addresses which are valid though may not reflect the
* most recent address update.
*/
_segment.force();
_addressArray.sync();
_segmentManager.updateMeta();
fireAfterPersist();
}
@Override
public synchronized void persist() throws IOException {
if(isOpen()) {
syncCompactor();
fireBeforePersist();
/* CALLS ORDERED: Need force _segment first and then persist
* _addressArray. During recovery, the _addressArray can always
* point to addresses which are valid though may not reflect the
* most recent address update.
*/
_segment.force();
_addressArray.persist();
_segmentManager.updateMeta();
fireAfterPersist();
}
}
/**
* Clears all data stored in this SimpleDataArray.
* This method is not effective if this SimpleDataArray is not open.
*/
@Override
public synchronized void clear() {
if(isOpen()) {
_compactor.clear();
_addressArray.clear();
_segmentManager.clear();
this.init();
}
}
/**
* Close to quit from serving requests.
*
* @throws IOException if the underlying service cannot be closed properly.
*/
@Override
public synchronized void close() throws IOException {
if (_mode == Mode.CLOSED) {
return;
}
_mode = Mode.CLOSED;
try {
// THE CALLS ORDERED.
_compactor.shutdown(); // shutdown compactor
/* Call syncInternal() to force update changes accumulated in
* the last update batch and those generated by data compaction.
*/
syncInternal(); // consume compaction batches generated during shutdown
/* Submit the current segment index buffer before closing _segmentManager
* so that the last writer segment index buffer can be flushed to disk
* when _segmentManager.close() is being invoked.
*/
submitSegmentIndexBuffer();
_compactor.clear(); // cleanup compactor internal state
_addressArray.close(); // close address array
_segmentManager.close(); // close segment manager
} catch(Exception e) {
_log.error("Failed to close", e);
throw (e instanceof IOException) ? (IOException)e : new IOException(e);
} finally {
_mode = Mode.CLOSED;
_log.info("mode=" + _mode);
}
}
/**
* Open to start serving requests.
*
* @throws IOException if the underlying service cannot be opened properly.
*/
@Override
public synchronized void open() throws IOException {
if (_mode == Mode.OPEN) {
return;
}
try {
_addressArray.open();
_segmentManager.open();
_compactor.start();
init();
_mode = Mode.OPEN;
} catch(Exception e) {
_mode = Mode.CLOSED;
_log.error("Failed to open", e);
_compactor.shutdown();
_compactor.clear();
if (_addressArray.isOpen()) {
_addressArray.close();
}
if (_segmentManager.isOpen()) {
_segmentManager.close();
}
throw (e instanceof IOException) ? (IOException)e : new IOException(e);
} finally {
_log.info("mode=" + _mode);
}
}
@Override
public boolean isOpen() {
return _mode == Mode.OPEN;
}
/**
* SegmentPersistListener is being called back whenever a redo entry is being created.
* This listener does two things:
*
* <ol>
* <li>Forces updates in the current Segment for persistency</li>
* <li>Updates the meta data of all Segments for record keeping</li>
* </ol>
*/
private class SegmentPersistListener extends EntryPersistAdapter {
@Override
public void beforePersist(Entry<? extends EntryValue> e) throws IOException {
fireBeforePersist();
if(_segment != null) {
_segment.force();
}
}
@Override
public void afterPersist(Entry<? extends EntryValue> e) throws IOException {
if(_segmentManager != null) {
_segmentManager.updateMeta();
}
fireAfterPersist();
}
}
@Override
public final Array.Type getType() {
return _addressArray.getType();
}
/**
* Tests if the segment index buffer (SIB) is enabled.
*/
public final boolean isSibEnabled() {
return _sibEnabled;
}
/**
* Enables the segment index buffer (SIB).
*/
public final void setSibEnabled(boolean b) {
if(_sibEnabled != b) {
_sib.markAsDirty();
}
_sibEnabled = b;
}
/**
* Gets the persistable event listener.
*/
public final PersistableListener getPersistableListener() {
return _listener;
}
/**
* Sets the persistable event listener.
*
* @param listener
*/
public final void setPersistableListener(PersistableListener listener) {
this._listener = listener;
}
/**
* Fire beforePersist events to the PersistableListener.
*/
protected void fireBeforePersist() {
PersistableListener l = _listener;
if(l != null) {
try {
l.beforePersist();
} catch(Exception e) {
_log.error("failure on calling beforePersist", e);
}
}
}
/**
* Fire afterPersist events to the PersistableListener.
*/
protected void fireAfterPersist() {
PersistableListener l = _listener;
if(l != null) {
try {
l.afterPersist();
} catch(Exception e) {
_log.error("failure on calling afterPersist", e);
}
}
}
}