[refactoring] replace AlreadyDisposed with ClosedStorageException in StreamlinedBlobStorage

- Preparation to move between packages there `AlreadyDisposedException` become unavailable
- Make storage after-close behavior more consistent: _most_ access methods must throw exception

GitOrigin-RevId: 67d99338c031bb7fb63468efe54a82820a2e438f
This commit is contained in:
Ruslan Cheremin
2024-09-17 15:57:28 +02:00
committed by intellij-monorepo-bot
parent f999a929ef
commit b66a4b570c
8 changed files with 112 additions and 56 deletions

View File

@@ -224,7 +224,7 @@ public final class AttributesStorageOverBlobStorage implements VFSAttributesStor
}
@Override
public boolean isEmpty() {
public boolean isEmpty() throws IOException {
return storage.liveRecordsCount() == 0;
}

View File

@@ -113,6 +113,7 @@ public abstract class StreamlinedBlobStorageHelper implements StreamlinedBlobSto
public static final int DATA_FORMAT_VERSION_OFFSET = 48; //int32
@SuppressWarnings("unused")
public static final int FIRST_UNUSED_FIELD_OFFSET = 52;
//Bytes [52..64] is reserved for the generations to come:
@@ -233,39 +234,50 @@ public abstract class StreamlinedBlobStorageHelper implements StreamlinedBlobSto
//monitoring:
@Override
public int liveRecordsCount() {
public int liveRecordsCount() throws ClosedStorageException {
checkNotClosed();
return recordsAllocated.get() - recordsDeleted.get() - recordsRelocated.get();
}
@Override
public int recordsAllocated() {
public int recordsAllocated() throws ClosedStorageException {
checkNotClosed();
return recordsAllocated.get();
}
@Override
public int recordsRelocated() {
public int recordsRelocated() throws ClosedStorageException {
checkNotClosed();
return recordsRelocated.get();
}
@Override
public int recordsDeleted() {
public int recordsDeleted() throws ClosedStorageException {
checkNotClosed();
return recordsDeleted.get();
}
@Override
public long totalLiveRecordsPayloadBytes() {
public long totalLiveRecordsPayloadBytes() throws ClosedStorageException {
checkNotClosed();
return totalLiveRecordsPayloadBytes.get();
}
@Override
public long totalLiveRecordsCapacityBytes() {
public long totalLiveRecordsCapacityBytes() throws ClosedStorageException {
checkNotClosed();
return totalLiveRecordsCapacityBytes.get();
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + storagePath() + "]{nextRecordId: " + nextRecordId() + '}';
try {
return getClass().getSimpleName() + "[" + storagePath() + "]{nextRecordId: " + nextRecordId() + '}';
}
catch (IOException e) {
return getClass().getSimpleName() + "[" + storagePath() + "]{closed}";
}
}
//==================== implementation: ==========================================================================
@@ -314,14 +326,15 @@ public abstract class StreamlinedBlobStorageHelper implements StreamlinedBlobSto
return Math.toIntExact(offsetInFile % pageSize);
}
/** Field could be read as volatile, but writes are protected with this intrinsic lock */
protected int nextRecordId() {
/** @return first un-allocated id, next-to-be-allocated. Read operation: i.e. doesn't change anything. */
protected int nextRecordId() throws IOException {
//Field could be read as volatile, but writes are protected with this intrinsic lock
return nextRecordId;
}
/** Must be called under 'this' lock */
//@GuardedBy(this)
protected void updateNextRecordId(int nextRecordId) {
protected void updateNextRecordId(int nextRecordId) throws IOException {
if (nextRecordId <= NULL_ID) {
throw new IllegalArgumentException("nextRecordId(=" + nextRecordId + ") must be >0");
}
@@ -374,7 +387,7 @@ public abstract class StreamlinedBlobStorageHelper implements StreamlinedBlobSto
int pageSize) throws IOException;
protected void checkRecordIdExists(int recordId) throws IllegalArgumentException {
protected void checkRecordIdExists(int recordId) throws IllegalArgumentException, IOException {
if (!isExistingRecordId(recordId)) {
throw new IllegalArgumentException("recordId(" + recordId + ") is not valid: allocated ids are in (0, " + nextRecordId() + ")");
}
@@ -382,7 +395,7 @@ public abstract class StreamlinedBlobStorageHelper implements StreamlinedBlobSto
protected void checkRedirectToId(int startingRecordId,
int currentRecordId,
int redirectToId) throws RecordAlreadyDeletedException, CorruptedException {
int redirectToId) throws RecordAlreadyDeletedException, IOException {
if (redirectToId == NULL_ID) { //!actual && redirectTo = NULL
throw new RecordAlreadyDeletedException("Can't access record[" + startingRecordId + "/" + currentRecordId + "]: it was deleted");
}
@@ -397,7 +410,7 @@ public abstract class StreamlinedBlobStorageHelper implements StreamlinedBlobSto
* @return true if record with recordId is already allocated.
* It doesn't mean the recordId is valid, though -- it could point to the middle of some record.
*/
protected boolean isRecordIdAllocated(int recordId) {
protected boolean isRecordIdAllocated(int recordId) throws IOException {
return recordId < nextRecordId();
}
@@ -405,7 +418,7 @@ public abstract class StreamlinedBlobStorageHelper implements StreamlinedBlobSto
* @return true if record with recordId is in the range of existing record ids.
* It doesn't mean the recordId is valid, though -- it could point to the middle of some record.
*/
protected boolean isExistingRecordId(int recordId) {
protected boolean isExistingRecordId(int recordId) throws IOException {
return isValidRecordId(recordId) && isRecordIdAllocated(recordId);
}
@@ -517,11 +530,16 @@ public abstract class StreamlinedBlobStorageHelper implements StreamlinedBlobSto
.build();
return meter.batchCallback(
() -> {
recordsAllocated.record(storage.recordsAllocated(), attributes);
recordsRelocated.record(storage.recordsRelocated(), attributes);
recordsDeleted.record(storage.recordsDeleted(), attributes);
totalLiveRecordsPayloadBytes.record(storage.totalLiveRecordsPayloadBytes(), attributes);
totalLiveRecordsCapacityBytes.record(storage.totalLiveRecordsCapacityBytes(), attributes);
try {
recordsAllocated.record(storage.recordsAllocated(), attributes);
recordsRelocated.record(storage.recordsRelocated(), attributes);
recordsDeleted.record(storage.recordsDeleted(), attributes);
totalLiveRecordsPayloadBytes.record(storage.totalLiveRecordsPayloadBytes(), attributes);
totalLiveRecordsCapacityBytes.record(storage.totalLiveRecordsCapacityBytes(), attributes);
}
catch (ClosedStorageException e) {
//just skip
}
},
recordsAllocated, recordsRelocated, recordsDeleted,
totalLiveRecordsPayloadBytes, totalLiveRecordsCapacityBytes

View File

@@ -3,6 +3,7 @@ package com.intellij.openapi.vfs.newvfs.persistent.dev.blobstorage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.IntRef;
import com.intellij.util.io.ClosedStorageException;
import com.intellij.util.io.PagedFileStorageWithRWLockedPageContent;
import com.intellij.util.io.blobstorage.ByteBufferReader;
import com.intellij.util.io.blobstorage.ByteBufferWriter;
@@ -541,7 +542,8 @@ public final class StreamlinedBlobStorageOverLockFreePagedStorage extends Stream
}
@Override
public long sizeInBytes() {
public long sizeInBytes() throws ClosedStorageException {
checkNotClosed();
return pagedStorage.length();
}

View File

@@ -3,9 +3,9 @@ package com.intellij.openapi.vfs.newvfs.persistent.dev.blobstorage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.IntRef;
import com.intellij.serviceContainer.AlreadyDisposedException;
import com.intellij.platform.util.io.storages.mmapped.MMappedFileStorage;
import com.intellij.platform.util.io.storages.mmapped.MMappedFileStorage.Page;
import com.intellij.util.io.ClosedStorageException;
import com.intellij.util.io.blobstorage.ByteBufferReader;
import com.intellij.util.io.blobstorage.ByteBufferWriter;
import com.intellij.util.io.blobstorage.SpaceAllocationStrategy;
@@ -123,17 +123,17 @@ public final class StreamlinedBlobStorageOverMMappedFile extends StreamlinedBlob
}
@Override
public int getStorageVersion() {
public int getStorageVersion() throws IOException {
return readHeaderInt(HeaderLayout.STORAGE_VERSION_OFFSET);
}
@Override
public int getDataFormatVersion() {
public int getDataFormatVersion() throws IOException {
return readHeaderInt(HeaderLayout.DATA_FORMAT_VERSION_OFFSET);
}
@Override
public void setDataFormatVersion(int expectedVersion) {
public void setDataFormatVersion(int expectedVersion) throws IOException {
putHeaderInt(HeaderLayout.DATA_FORMAT_VERSION_OFFSET, expectedVersion);
}
@@ -487,7 +487,7 @@ public final class StreamlinedBlobStorageOverMMappedFile extends StreamlinedBlob
}
@Override
public long sizeInBytes() {
public long sizeInBytes() throws IOException {
return actualLength();
}
@@ -556,30 +556,30 @@ public final class StreamlinedBlobStorageOverMMappedFile extends StreamlinedBlob
// === storage header accessors: ===
private int readHeaderInt(int offset) {
private int readHeaderInt(int offset) throws IOException {
assert (0 <= offset && offset <= HeaderLayout.HEADER_SIZE - Integer.BYTES)
: "header offset(=" + offset + ") must be in [0," + (HeaderLayout.HEADER_SIZE - Integer.BYTES) + "]";
return headerPage.rawPageBuffer().getInt(offset);
return headerPage().rawPageBuffer().getInt(offset);
}
private void putHeaderInt(int offset,
int value) {
int value) throws IOException {
assert (0 <= offset && offset <= HeaderLayout.HEADER_SIZE - Integer.BYTES)
: "header offset(=" + offset + ") must be in [0," + (HeaderLayout.HEADER_SIZE - Integer.BYTES) + "]";
headerPage.rawPageBuffer().putInt(offset, value);
headerPage().rawPageBuffer().putInt(offset, value);
}
private long readHeaderLong(int offset) {
private long readHeaderLong(int offset) throws IOException {
assert (0 <= offset && offset <= HeaderLayout.HEADER_SIZE - Long.BYTES)
: "header offset(=" + offset + ") must be in [0," + (HeaderLayout.HEADER_SIZE - Long.BYTES) + "]";
return headerPage.rawPageBuffer().getLong(offset);
return headerPage().rawPageBuffer().getLong(offset);
}
private void putHeaderLong(int offset,
long value) {
long value) throws IOException {
assert (0 <= offset && offset <= HeaderLayout.HEADER_SIZE - Long.BYTES)
: "header offset(=" + offset + ") must be in [0," + (HeaderLayout.HEADER_SIZE - Long.BYTES) + "]";
headerPage.rawPageBuffer().putLong(offset, value);
headerPage().rawPageBuffer().putLong(offset, value);
}
/**
@@ -587,33 +587,36 @@ public final class StreamlinedBlobStorageOverMMappedFile extends StreamlinedBlob
* File size is almost always larger than that because {@link MMappedFileStorage} pre-allocates each page
* in advance.
*/
private long actualLength() {
private long actualLength() throws IOException {
return idToOffset(nextRecordId());
}
@Override
protected int nextRecordId() {
if (headerPage == null) {
throw new AlreadyDisposedException("Storage is closed");
}
protected int nextRecordId() throws IOException {
Page headerPage = headerPage();
ByteBuffer headerBuffer = headerPage.rawPageBuffer();
return (int)INT_HANDLE.getVolatile(headerBuffer, HeaderLayout.NEXT_RECORD_ID_OFFSET);
}
//@GuardedBy(this)
@Override
protected void updateNextRecordId(int nextRecordId) {
Page _headerPage = headerPage;
if (_headerPage == null) {
throw new AlreadyDisposedException("Storage is closed");
}
protected void updateNextRecordId(int nextRecordId) throws IOException {
if (nextRecordId <= NULL_ID) {
throw new IllegalArgumentException("nextRecordId(=" + nextRecordId + ") must be >0");
}
ByteBuffer headerBuffer = _headerPage.rawPageBuffer();
Page headerPage = headerPage();
ByteBuffer headerBuffer = headerPage.rawPageBuffer();
INT_HANDLE.setVolatile(headerBuffer, HeaderLayout.NEXT_RECORD_ID_OFFSET, nextRecordId);
}
private @NotNull Page headerPage() throws ClosedStorageException {
Page _headerPage = this.headerPage;
if (_headerPage == null) {
throw new ClosedStorageException("Storage is closed");
}
return _headerPage;
}
// === storage records accessors: ===
/**

View File

@@ -3,6 +3,7 @@ package com.intellij.openapi.vfs.newvfs.persistent.dev.blobstorage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.IntRef;
import com.intellij.util.io.ClosedStorageException;
import com.intellij.util.io.DirectBufferWrapper;
import com.intellij.util.io.PagedFileStorage;
import com.intellij.util.io.blobstorage.ByteBufferReader;
@@ -583,7 +584,8 @@ public final class StreamlinedBlobStorageOverPagedStorage extends StreamlinedBlo
@Override
public long sizeInBytes() {
public long sizeInBytes() throws ClosedStorageException {
checkNotClosed();
return pagedStorage.length();
}

View File

@@ -3,6 +3,7 @@ package com.intellij.openapi.vfs.newvfs.impl.heavy.blobstorage;
import com.intellij.openapi.util.IntRef;
import com.intellij.openapi.vfs.newvfs.persistent.dev.blobstorage.StreamlinedBlobStorageHelper;
import com.intellij.util.io.ClosedStorageException;
import com.intellij.util.io.blobstorage.SpaceAllocationStrategy;
import com.intellij.util.io.blobstorage.SpaceAllocationStrategy.DataLengthPlusFixedPercentStrategy;
import com.intellij.util.io.blobstorage.SpaceAllocationStrategy.WriterDecidesStrategy;
@@ -369,11 +370,39 @@ public abstract class StreamlinedBlobStorageTestBase<S extends StreamlinedBlobSt
storage.close();
}
@Test
public void afterClose_storageAccessorsMustThrowException() throws IOException {
storage.close();
assertThrows("Storage must throw exception after close()",
ClosedStorageException.class,
() -> storage.sizeInBytes()
);
assertThrows("Storage must throw exception after close()",
ClosedStorageException.class,
() -> storage.getStorageVersion()
);
assertThrows("Storage must throw exception after close()",
ClosedStorageException.class,
() -> storage.getDataFormatVersion()
);
assertThrows("Storage must throw exception after close()",
ClosedStorageException.class,
() -> storage.liveRecordsCount()
);
}
@Test
public void afterClose_toStringIsStillSafeToCall() throws IOException {
storage.close();
assertNotNull("Ensure .toString() could be called after .close()",
storage.toString());
}
@Test
public void afterCloseAndClean_noFilesRemain() throws IOException {
storage.closeAndClean();
assertFalse(
"No ["+storagePath+"] must remain after .closeAndClean()",
"No [" + storagePath + "] must remain after .closeAndClean()",
Files.exists(storagePath)
);
}

View File

@@ -3,6 +3,8 @@ package com.intellij.util.io.blobstorage;
import org.jetbrains.annotations.ApiStatus;
import java.io.IOException;
/**
* Provides access to storage internal statistics.
* If storage provides access to such a statistics -- it must implement this interface
@@ -11,16 +13,16 @@ import org.jetbrains.annotations.ApiStatus;
public interface BlobStorageStatistics {
/** records currently alive (=allocated-relocated-deleted) */
int liveRecordsCount();
int liveRecordsCount() throws IOException;
/** records allocated since storage creation (including deleted/relocated) */
int recordsAllocated();
int recordsAllocated() throws IOException;
/** records re-allocated -- i.e. recordId is accessible, but the access redirects to another (actual) record */
int recordsRelocated();
int recordsRelocated() throws IOException;
/** records deleted, i.e. un-accessible anymore */
int recordsDeleted();
int recordsDeleted() throws IOException;
/**
@@ -28,11 +30,11 @@ public interface BlobStorageStatistics {
* Includes all the overhead of file/records header, alignment, etc.
* Could be <= file size, if e.g. file is expanded in advance.
*/
long sizeInBytes();
long sizeInBytes() throws IOException;
/** Total size of all alive records' payload, i.e. 'useful size', without any overhead */
long totalLiveRecordsPayloadBytes();
long totalLiveRecordsPayloadBytes() throws IOException;
/** Total size of all alive records' capacity */
long totalLiveRecordsCapacityBytes();
long totalLiveRecordsCapacityBytes() throws IOException;
}

View File

@@ -153,7 +153,7 @@ public interface StreamlinedBlobStorage extends Closeable, AutoCloseable, Forcea
boolean isRecordActual(int recordActualLength);
int liveRecordsCount();
int liveRecordsCount() throws IOException;
/**
* Total size of data in a storage -- including metadata, reserved and deleted
@@ -161,7 +161,7 @@ public interface StreamlinedBlobStorage extends Closeable, AutoCloseable, Forcea
* Not guaranteed to be == actual file size on disk -- disk file could be pre-allocated
* in advance.
*/
long sizeInBytes();
long sizeInBytes() throws IOException;
@Override
boolean isDirty();