[vfs] IJPL-163709: AppendOnlyLog throws ClosedStorageException instead of NPE

+ In concurrent environment `AOL.close()` could lead to NPE in parallel thread -> use more explicit `ClosedStorageException` -> which is already processed in `FSRecordsImpl.handleError()`, by converting to `AlreadyDisposedException`, which all the code must be ready to process anyway

GitOrigin-RevId: 755b94ea50dd8f47c0a254dbe7d11e69c81fc17f
This commit is contained in:
Ruslan Cheremin
2024-10-14 16:02:16 +02:00
committed by intellij-monorepo-bot
parent 0401ed97c0
commit 996e62647d
2 changed files with 38 additions and 29 deletions

View File

@@ -36,7 +36,7 @@ public interface AppendOnlyLog extends Closeable, Flushable, CleanableStorage {
* returned false definitely means id is not valid -- but returned true means 'id _looks_ like a valid record id',
* because some implementations can't say for sure is id a valid record id or not.
*/
boolean isValidId(long id);
boolean isValidId(long id) throws IOException;
/**
* @return true if all the records were read, false if reader stops the reading prematurely (by
@@ -51,7 +51,7 @@ public interface AppendOnlyLog extends Closeable, Flushable, CleanableStorage {
void flush() throws IOException;
/** @return true if there are no records in the log */
boolean isEmpty();
boolean isEmpty() throws IOException;
/** @return number of records appended to the log */
int recordsCount() throws IOException;

View File

@@ -5,8 +5,9 @@ import com.intellij.openapi.util.IntRef;
import com.intellij.openapi.util.io.ContentTooBigException;
import com.intellij.platform.util.io.storages.AlignmentUtils;
import com.intellij.platform.util.io.storages.mmapped.MMappedFileStorage;
import com.intellij.util.io.Unmappable;
import com.intellij.util.io.ClosedStorageException;
import com.intellij.util.io.IOUtil;
import com.intellij.util.io.Unmappable;
import com.intellij.util.io.blobstorage.ByteBufferReader;
import com.intellij.util.io.blobstorage.ByteBufferWriter;
import org.jetbrains.annotations.ApiStatus;
@@ -388,7 +389,7 @@ public final class AppendOnlyLogOverMMappedFile implements AppendOnlyLog, Unmapp
headerPage = storage.pageByOffset(0L);
ByteBuffer headerPageBuffer = headerPage.rawPageBuffer();
ByteBuffer headerPageBuffer = headerPageBuffer();
if (fileIsEmpty) {
HeaderLayout.putMagicWord(headerPageBuffer, MAGIC_WORD);
HeaderLayout.putImplementationVersion(headerPageBuffer, CURRENT_IMPLEMENTATION_VERSION);
@@ -469,16 +470,16 @@ public final class AppendOnlyLogOverMMappedFile implements AppendOnlyLog, Unmapp
* @return version of the log implementation (i.e., this class) used to create the file.
* Current version is {@link #CURRENT_IMPLEMENTATION_VERSION}
*/
public int getImplementationVersion() {
return HeaderLayout.readImplementationVersion(headerPage.rawPageBuffer());
public int getImplementationVersion() throws IOException {
return HeaderLayout.readImplementationVersion(headerPageBuffer());
}
/** @return version of _data_ stored in records -- up to the client to define/recognize it */
public int getDataVersion() {
public int getDataVersion() throws IOException {
return getIntHeaderField(HeaderLayout.EXTERNAL_VERSION_OFFSET);
}
public void setDataVersion(int version) {
public void setDataVersion(int version) throws IOException {
setIntHeaderField(HeaderLayout.EXTERNAL_VERSION_OFFSET, version);
}
@@ -488,7 +489,7 @@ public final class AppendOnlyLogOverMMappedFile implements AppendOnlyLog, Unmapp
}
/** @return arbitrary (user-defined) value from the Log's header, previously set by {@link #setUserDefinedHeaderField(int, int)} */
public int getUserDefinedHeaderField(int fieldNo) {
public int getUserDefinedHeaderField(int fieldNo) throws IOException {
int headerOffset = HeaderLayout.FIRST_UNUSED_OFFSET + fieldNo * Integer.BYTES;
return getIntHeaderField(headerOffset);
}
@@ -498,7 +499,7 @@ public final class AppendOnlyLogOverMMappedFile implements AppendOnlyLog, Unmapp
* There are 5 slots fieldNo=[0..5] available so far
*/
public void setUserDefinedHeaderField(int fieldNo,
int headerFieldValue) {
int headerFieldValue) throws IOException {
int headerOffset = HeaderLayout.FIRST_UNUSED_OFFSET + fieldNo * Integer.BYTES;
setIntHeaderField(headerOffset, headerFieldValue);
}
@@ -628,7 +629,7 @@ public final class AppendOnlyLogOverMMappedFile implements AppendOnlyLog, Unmapp
}
@Override
public boolean isValidId(long recordId) {
public boolean isValidId(long recordId) throws IOException {
if (recordId <= 0) {
return false;
}
@@ -663,7 +664,7 @@ public final class AppendOnlyLogOverMMappedFile implements AppendOnlyLog, Unmapp
}
@Override
public boolean isEmpty() {
public boolean isEmpty() throws IOException {
return firstUnAllocatedOffset() == HeaderLayout.HEADER_SIZE
&& firstUnCommittedOffset() == HeaderLayout.HEADER_SIZE;
}
@@ -865,34 +866,34 @@ public final class AppendOnlyLogOverMMappedFile implements AppendOnlyLog, Unmapp
}
private long firstUnAllocatedOffset() {
private long firstUnAllocatedOffset() throws IOException {
return getLongHeaderField(HeaderLayout.NEXT_RECORD_TO_BE_ALLOCATED_OFFSET);
}
private boolean casFirstUnAllocatedOffset(long currentValue,
long newValue) {
long newValue) throws IOException {
return INT64_OVER_BYTE_BUFFER.compareAndSet(
headerPage.rawPageBuffer(),
headerPageBuffer(),
HeaderLayout.NEXT_RECORD_TO_BE_ALLOCATED_OFFSET,
currentValue, newValue
);
}
private long firstUnCommittedOffset() {
private long firstUnCommittedOffset() throws IOException {
return getLongHeaderField(HeaderLayout.NEXT_RECORD_TO_BE_COMMITTED_OFFSET);
}
private boolean casFirstUnCommittedOffset(long currentValue, long newValue) {
private boolean casFirstUnCommittedOffset(long currentValue, long newValue) throws IOException {
return INT64_OVER_BYTE_BUFFER.compareAndSet(
headerPage.rawPageBuffer(),
headerPageBuffer(),
HeaderLayout.NEXT_RECORD_TO_BE_COMMITTED_OFFSET,
currentValue, newValue
);
}
private int addToDataRecordsCount(int recordsCommitted) {
private int addToDataRecordsCount(int recordsCommitted) throws IOException {
return (int)INT32_OVER_BYTE_BUFFER.getAndAdd(
headerPage.rawPageBuffer(),
headerPageBuffer(),
HeaderLayout.RECORDS_COUNT_OFFSET,
recordsCommitted
);
@@ -1112,26 +1113,34 @@ public final class AppendOnlyLogOverMMappedFile implements AppendOnlyLog, Unmapp
}
private int getIntHeaderField(int headerRelativeOffsetBytes) {
Objects.checkIndex(headerRelativeOffsetBytes, HeaderLayout.HEADER_SIZE - Integer.BYTES + 1);
return (int)INT32_OVER_BYTE_BUFFER.getVolatile(headerPage.rawPageBuffer(), headerRelativeOffsetBytes);
private ByteBuffer headerPageBuffer() throws IOException {
MMappedFileStorage.Page _headerPage = headerPage;
if(_headerPage == null) {
throw new ClosedStorageException("["+storagePath()+"] is already closed");
}
return _headerPage.rawPageBuffer();
}
private long getLongHeaderField(int headerRelativeOffsetBytes) {
private int getIntHeaderField(int headerRelativeOffsetBytes) throws IOException {
Objects.checkIndex(headerRelativeOffsetBytes, HeaderLayout.HEADER_SIZE - Integer.BYTES + 1);
return (int)INT32_OVER_BYTE_BUFFER.getVolatile(headerPageBuffer(), headerRelativeOffsetBytes);
}
private long getLongHeaderField(int headerRelativeOffsetBytes) throws IOException {
Objects.checkIndex(headerRelativeOffsetBytes, HeaderLayout.HEADER_SIZE - Long.BYTES + 1);
return (long)INT64_OVER_BYTE_BUFFER.getVolatile(headerPage.rawPageBuffer(), headerRelativeOffsetBytes);
return (long)INT64_OVER_BYTE_BUFFER.getVolatile(headerPageBuffer(), headerRelativeOffsetBytes);
}
private void setIntHeaderField(int headerRelativeOffsetBytes,
int headerFieldValue) {
int headerFieldValue) throws IOException {
Objects.checkIndex(headerRelativeOffsetBytes, HeaderLayout.HEADER_SIZE - Integer.BYTES + 1);
INT32_OVER_BYTE_BUFFER.setVolatile(headerPage.rawPageBuffer(), headerRelativeOffsetBytes, headerFieldValue);
INT32_OVER_BYTE_BUFFER.setVolatile(headerPageBuffer(), headerRelativeOffsetBytes, headerFieldValue);
}
private void setLongHeaderField(int headerRelativeOffsetBytes,
long headerFieldValue) {
long headerFieldValue) throws IOException {
Objects.checkIndex(headerRelativeOffsetBytes, HeaderLayout.HEADER_SIZE - Long.BYTES + 1);
INT64_OVER_BYTE_BUFFER.setVolatile(headerPage.rawPageBuffer(), headerRelativeOffsetBytes, headerFieldValue);
INT64_OVER_BYTE_BUFFER.setVolatile(headerPageBuffer(), headerRelativeOffsetBytes, headerFieldValue);
}
//================== alignment: ========================================================================