@Override public SegmentHandle create(String streamSegmentName) throws StreamSegmentException { ensurePreconditions(); synchronized (this.lock) { if (this.streamSegments.containsKey(streamSegmentName)) { throw new StreamSegmentExistsException(streamSegmentName); } StreamSegmentData data = new StreamSegmentData(streamSegmentName, this.syncContext); this.streamSegments.put(streamSegmentName, data); return data.openWrite(); } }
/** * Appends the given data to the end of the StreamSegment. * Note: since this is a custom operation exposed only on InMemoryStorage, there is no need to make it return a Future. * * @param handle A read-write handle for the segment to append to. * @param data An InputStream representing the data to append. * @param length The length of the data to append. */ @SneakyThrows(StreamSegmentException.class) public void append(SegmentHandle handle, InputStream data, int length) { ensurePreconditions(); Preconditions.checkArgument(!handle.isReadOnly(), "Cannot append using a read-only handle."); getStreamSegmentData(handle.getSegmentName()).append(data, length); }
void concat(StreamSegmentData other, long offset) throws BadOffsetException, StreamSegmentSealedException { synchronized (this.context.syncRoot) { // In order to do a proper concat, we need to lock on both the source and the target segments. But since // there's always a possibility of two concurrent calls to concat with swapped arguments, there is a chance // this could deadlock in certain scenarios. One way to avoid that is to ensure that only one call to concat() // can be in progress at any given time (for any instance of InMemoryStorage), thus the need to synchronize // on SyncContext.syncRoot. synchronized (other.lock) { Preconditions.checkState(other.sealed, "Cannot concat segment '%s' into '%s' because it is not sealed.", other.name, this.name); other.checkOpened(); synchronized (this.lock) { checkOpened(); if (offset != this.length) { throw new BadOffsetException(this.name, this.length, offset); } long bytesCopied = 0; int currentBlockIndex = 0; while (bytesCopied < other.length) { byte[] currentBlock = other.data.get(currentBlockIndex); int length = (int) Math.min(currentBlock.length, other.length - bytesCopied); ByteArrayInputStream bis = new ByteArrayInputStream(currentBlock, 0, length); writeInternal(this.length, bis, length); bytesCopied += length; currentBlockIndex++; } } } } }
@GuardedBy("lock") @SneakyThrows(IOException.class) private void writeInternal(long startOffset, InputStream data, int length) throws BadOffsetException, StreamSegmentSealedException { Exceptions.checkArgument(length >= 0, "length", "bad length"); if (startOffset != this.length) { throw new BadOffsetException(this.name, this.length, startOffset); } if (this.sealed) { throw new StreamSegmentSealedException(this.name); } long offset = startOffset; ensureAllocated(offset, length); int writtenBytes = 0; while (writtenBytes < length) { OffsetLocation ol = getOffsetLocation(offset); int readBytes = data.read(this.data.get(ol.bufferSequence), ol.bufferOffset, Math.min(length - writtenBytes, BUFFER_SIZE - ol.bufferOffset)); if (readBytes < 0) { throw new IOException("reached end of stream while still expecting data"); } writtenBytes += readBytes; offset += readBytes; } this.length = Math.max(this.length, startOffset + length); }
int read(long startOffset, byte[] target, int targetOffset, int length) { synchronized (this.lock) { Exceptions.checkArrayRange(targetOffset, length, target.length, "targetOffset", "length"); Exceptions.checkArrayRange(startOffset, length, this.length, "startOffset", "length"); long offset = startOffset; int readBytes = 0; while (readBytes < length) { OffsetLocation ol = getOffsetLocation(offset); int bytesToCopy = Math.min(BUFFER_SIZE - ol.bufferOffset, length - readBytes); System.arraycopy(this.data.get(ol.bufferSequence), ol.bufferOffset, target, targetOffset + readBytes, bytesToCopy); readBytes += bytesToCopy; offset += bytesToCopy; } return readBytes; } }
@Override public void delete(SegmentHandle handle) throws StreamSegmentNotExistsException { ensurePreconditions(); // If we are given a read-only handle, we must ensure the segment is sealed. If the segment can accept modifications // (it is not sealed), then we require a read-write handle. boolean canDelete = !handle.isReadOnly(); if (!canDelete) { synchronized (this.lock) { if (this.streamSegments.containsKey(handle.getSegmentName())) { canDelete = this.streamSegments.get(handle.getSegmentName()).isSealed(); } } } Preconditions.checkArgument(canDelete, "Cannot delete using a read-only handle, unless the segment is sealed."); deleteInternal(handle); }
@Override public void unseal(SegmentHandle handle) throws StreamSegmentException { ensurePreconditions(); getStreamSegmentData(handle.getSegmentName()).markUnsealed(); }
void markSealed() { synchronized (this.lock) { checkOpened(); this.sealed = true; } }
void write(long startOffset, InputStream data, int length) throws BadOffsetException, StreamSegmentSealedException { synchronized (this.lock) { checkOpened(); writeInternal(startOffset, data, length); } }
@Override public void seal(SegmentHandle handle) throws StreamSegmentNotExistsException { ensurePreconditions(); Preconditions.checkArgument(!handle.isReadOnly(), "Cannot seal using a read-only handle."); getStreamSegmentData(handle.getSegmentName()).markSealed(); }
@Override public SegmentProperties getStreamSegmentInfo(String streamSegmentName) throws StreamSegmentNotExistsException { ensurePreconditions(); return getStreamSegmentData(streamSegmentName).getInfo(); }
@Override public void concat(SegmentHandle targetHandle, long offset, String sourceSegment) throws StreamSegmentException { ensurePreconditions(); Preconditions.checkArgument(!targetHandle.isReadOnly(), "Cannot concat using a read-only handle."); AtomicLong newLength = new AtomicLong(); StreamSegmentData sourceData = getStreamSegmentData(sourceSegment); StreamSegmentData targetData = getStreamSegmentData(targetHandle.getSegmentName()); targetData.concat(sourceData, offset); deleteInternal(new InMemorySegmentHandle(sourceSegment, false)); newLength.set(targetData.getInfo().getLength()); }
@GuardedBy("lock") private void ensureAllocated(long startOffset, int length) { long endOffset = startOffset + length; int desiredSize = getOffsetLocation(endOffset).bufferSequence + 1; while (this.data.size() < desiredSize) { this.data.add(new byte[BUFFER_SIZE]); } }
void markUnsealed() { synchronized (this.lock) { checkOpened(); this.sealed = false; } }