mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[vfs][cleanup] drop long-unused PersistentFSRecordsOverLockFreePagedStorage implementation
- last used >1year ago GitOrigin-RevId: 6bdbda9dcf57e2c3903314a3cd2b7c2b3dd45783
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e748ba43c3
commit
e59db2dcbb
-879
@@ -1,879 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.vfs.newvfs.persistent;
|
||||
|
||||
import com.intellij.util.io.PagedFileStorageWithRWLockedPageContent;
|
||||
import com.intellij.util.io.pagecache.Page;
|
||||
import com.intellij.util.io.pagecache.PageUnsafe;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.intellij.openapi.vfs.newvfs.persistent.PersistentFSHeaders.*;
|
||||
|
||||
/**
|
||||
* Implementation uses new {@link PagedFileStorageWithRWLockedPageContent}
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public final class PersistentFSRecordsOverLockFreePagedStorage implements PersistentFSRecordsStorage, IPersistentFSRecordsStorage {
|
||||
|
||||
|
||||
/* ================ FILE HEADER FIELDS LAYOUT ======================================================= */
|
||||
|
||||
public static final int HEADER_SIZE = PersistentFSHeaders.HEADER_SIZE;
|
||||
|
||||
/* ================ RECORD FIELDS LAYOUT =========================================================== */
|
||||
|
||||
private static final int PARENT_REF_OFFSET = 0;
|
||||
private static final int PARENT_REF_SIZE = Integer.BYTES;
|
||||
private static final int NAME_REF_OFFSET = PARENT_REF_OFFSET + PARENT_REF_SIZE;
|
||||
private static final int NAME_REF_SIZE = Integer.BYTES;
|
||||
private static final int FLAGS_OFFSET = NAME_REF_OFFSET + NAME_REF_SIZE;
|
||||
private static final int FLAGS_SIZE = Integer.BYTES;
|
||||
private static final int ATTR_REF_OFFSET = FLAGS_OFFSET + FLAGS_SIZE;
|
||||
private static final int ATTR_REF_SIZE = Integer.BYTES;
|
||||
private static final int CONTENT_REF_OFFSET = ATTR_REF_OFFSET + ATTR_REF_SIZE;
|
||||
private static final int CONTENT_REF_SIZE = Integer.BYTES;
|
||||
private static final int MOD_COUNT_OFFSET = CONTENT_REF_OFFSET + CONTENT_REF_SIZE;
|
||||
private static final int MOD_COUNT_SIZE = Integer.BYTES;
|
||||
//RC: moved timestamp 1 field down so both LONG fields are 8-byte aligned (for atomic accesses alignment is important)
|
||||
private static final int TIMESTAMP_OFFSET = MOD_COUNT_OFFSET + MOD_COUNT_SIZE;
|
||||
private static final int TIMESTAMP_SIZE = Long.BYTES;
|
||||
private static final int LENGTH_OFFSET = TIMESTAMP_OFFSET + TIMESTAMP_SIZE;
|
||||
private static final int LENGTH_SIZE = Long.BYTES;
|
||||
|
||||
public static final int RECORD_SIZE_IN_BYTES = LENGTH_OFFSET + LENGTH_SIZE;
|
||||
|
||||
/* ================ RECORD FIELDS LAYOUT end ======================================== */
|
||||
|
||||
private final @NotNull PagedFileStorageWithRWLockedPageContent storage;
|
||||
|
||||
/** How many records were allocated already. allocatedRecordsCount-1 == last record id */
|
||||
private final AtomicInteger allocatedRecordsCount = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* Incremented on each update of anything in the storage -- header, record. Hence be seen as 'version'
|
||||
* of storage content -- not storage format version, but current storage content.
|
||||
* Stored in {@link PersistentFSHeaders#HEADER_GLOBAL_MOD_COUNT_OFFSET} header field.
|
||||
* If a record is updated -> current value of globalModCount is 'stamped' into a record MOD_COUNT field.
|
||||
*/
|
||||
private final AtomicInteger globalModCount = new AtomicInteger(0);
|
||||
|
||||
//cached for faster access:
|
||||
private final transient int pageSize;
|
||||
private final transient int recordsPerPage;
|
||||
|
||||
private final transient HeaderAccessor headerAccessor = new HeaderAccessor(this);
|
||||
|
||||
private final boolean wasClosedProperly;
|
||||
|
||||
public PersistentFSRecordsOverLockFreePagedStorage(final @NotNull PagedFileStorageWithRWLockedPageContent storage) throws IOException {
|
||||
this.storage = storage;
|
||||
|
||||
pageSize = storage.getPageSize();
|
||||
recordsPerPage = pageSize / RECORD_SIZE_IN_BYTES;
|
||||
|
||||
final int recordsCountInStorage = loadRecordsCount(storage.length());
|
||||
allocatedRecordsCount.set(recordsCountInStorage);
|
||||
|
||||
globalModCount.set(getIntHeaderField(HEADER_GLOBAL_MOD_COUNT_OFFSET));
|
||||
|
||||
int connectionStatus = getIntHeaderField(HEADER_CONNECTION_STATUS_OFFSET);
|
||||
wasClosedProperly = (connectionStatus == SAFELY_CLOSED_STAMP);
|
||||
setIntHeaderField(HEADER_CONNECTION_STATUS_OFFSET, IN_USE_STAMP);
|
||||
force();//CONNECTION_STATUS change makes file dirty, but it is unexpected to have freshly opened storage dirty => flush it
|
||||
//MAYBE RC: flushing on storage open is also not good for (startup) performance. Instead, we could update the position in
|
||||
// a page with raw update, without notifying storage of changes, which tricks storage think it is !dirty.
|
||||
// Normally this calls for a bug, but in this particular case flushing connection status change has no sense until
|
||||
// some _other_, actual change(s) happened -- i.e. if connection status change is the _only_ change happened, it is
|
||||
// perfectly fine to lose it, i.e. not store it on .force()/.close() -- we must store connection status only if
|
||||
// some other change(s) happened.
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
int loadRecordsCount(final long storageSizeBytes) throws IOException {
|
||||
if (storageSizeBytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
final long fullPagesOccupied = storageSizeBytes / pageSize;
|
||||
if (fullPagesOccupied == 0) {
|
||||
final long fullRecordsOnHeaderPage = (storageSizeBytes - HEADER_SIZE) / RECORD_SIZE_IN_BYTES;
|
||||
final long recordExcess = (storageSizeBytes - HEADER_SIZE) % RECORD_SIZE_IN_BYTES;
|
||||
if (recordExcess > 0) {
|
||||
throw new IOException(
|
||||
this.storage + " is corrupted: " +
|
||||
"(" + storageSizeBytes + "b on a header page) = (" + fullRecordsOnHeaderPage + " whole records)" +
|
||||
" + (" + recordExcess + "b excess)" +
|
||||
" -> storage is likely truncated, or was created with pageSize != current(" + pageSize + "b)"
|
||||
);
|
||||
}
|
||||
|
||||
return (int)fullRecordsOnHeaderPage;
|
||||
}
|
||||
else {
|
||||
final long lastPageOccupiedBytes = storageSizeBytes % pageSize;
|
||||
|
||||
final long fullRecordsOnLastPage = lastPageOccupiedBytes / RECORD_SIZE_IN_BYTES;
|
||||
final long recordExcess = lastPageOccupiedBytes % RECORD_SIZE_IN_BYTES;
|
||||
if (recordExcess > 0) {
|
||||
throw new IOException(
|
||||
this.storage + " is corrupted: " +
|
||||
"(" + lastPageOccupiedBytes + "b on last page) = (" + fullRecordsOnLastPage + " whole records)" +
|
||||
" + (" + recordExcess + "b excess)" +
|
||||
" -> storage is likely truncated, or was created with pageSize != current(" + pageSize + "b)"
|
||||
);
|
||||
}
|
||||
|
||||
return (int)(
|
||||
(pageSize - HEADER_SIZE) / RECORD_SIZE_IN_BYTES //header page has fewer records!
|
||||
+ (fullPagesOccupied - 1) * recordsPerPage
|
||||
+ fullRecordsOnLastPage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int recordsCount() {
|
||||
return allocatedRecordsCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxAllocatedID() {
|
||||
return allocatedRecordsCount.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidFileId(int recordId) {
|
||||
final int allocatedSoFar = allocatedRecordsCount.get();
|
||||
return FSRecords.NULL_FILE_ID < recordId && recordId <= allocatedSoFar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R readRecord(final int recordId,
|
||||
final @NotNull RecordReader<R> reader) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */false)) {
|
||||
page.lockPageForRead();
|
||||
try {
|
||||
final RecordAccessor recordAccessor = new RecordAccessor(recordId, recordOffsetOnPage, page, allocatedRecordsCount);
|
||||
return reader.readRecord(recordAccessor);
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateRecord(final int recordId,
|
||||
final @NotNull RecordUpdater updater) throws IOException {
|
||||
final int trueRecordId = (recordId <= NULL_ID) ?
|
||||
allocateRecord() :
|
||||
recordId;
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
//RC: hope EscapeAnalysis removes the allocation here:
|
||||
final RecordAccessor recordAccessor = new RecordAccessor(recordId, recordOffsetOnPage, page, allocatedRecordsCount);
|
||||
final boolean updated = updater.updateRecord(recordAccessor);
|
||||
if (updated) {
|
||||
incrementRecordVersion(recordAccessor.pageBuffer, recordOffsetOnPage);
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
}
|
||||
return trueRecordId;
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R readHeader(final @NotNull HeaderReader<R> reader) throws IOException {
|
||||
try (final Page page = storage.pageByOffset(0, /*forWrite: */false)) {
|
||||
page.lockPageForRead();
|
||||
try {
|
||||
return reader.readHeader(headerAccessor);
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHeader(final @NotNull HeaderUpdater updater) throws IOException {
|
||||
try (final Page page = storage.pageByOffset(0, /*forWrite: */true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
if (updater.updateHeader(headerAccessor)) {
|
||||
globalModCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class RecordAccessor implements RecordForUpdate {
|
||||
private final int recordId;
|
||||
private final int recordOffsetInPage;
|
||||
private final Page recordPage;
|
||||
private final transient ByteBuffer pageBuffer;
|
||||
private final AtomicInteger allocatedRecordsCount;
|
||||
|
||||
private RecordAccessor(int recordId,
|
||||
int recordOffsetInPage,
|
||||
PageUnsafe recordPage,
|
||||
AtomicInteger allocatedRecordsCount) {
|
||||
this.recordId = recordId;
|
||||
this.recordOffsetInPage = recordOffsetInPage;
|
||||
this.recordPage = recordPage;
|
||||
pageBuffer = recordPage.rawPageBuffer();
|
||||
this.allocatedRecordsCount = allocatedRecordsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int recordId() {
|
||||
return recordId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeRecordId() {
|
||||
return pageBuffer.getInt(recordOffsetInPage + ATTR_REF_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParent() {
|
||||
return pageBuffer.getInt(recordOffsetInPage + PARENT_REF_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNameId() {
|
||||
return pageBuffer.getInt(recordOffsetInPage + NAME_REF_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
return pageBuffer.getLong(recordOffsetInPage + LENGTH_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return pageBuffer.getLong(recordOffsetInPage + TIMESTAMP_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModCount() {
|
||||
return pageBuffer.getInt(recordOffsetInPage + MOD_COUNT_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentRecordId() {
|
||||
return pageBuffer.getInt(recordOffsetInPage + CONTENT_REF_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @PersistentFS.Attributes int getFlags() {
|
||||
return pageBuffer.getInt(recordOffsetInPage + FLAGS_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttributeRecordId(final int attributeRecordId) {
|
||||
checkValidIdField(recordId, attributeRecordId, "attributeRecordId");
|
||||
pageBuffer.putInt(recordOffsetInPage + ATTR_REF_OFFSET, attributeRecordId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(final int parentId) {
|
||||
checkParentIdIsValid(parentId);
|
||||
pageBuffer.putInt(recordOffsetInPage + PARENT_REF_OFFSET, parentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNameId(final int nameId) {
|
||||
pageBuffer.putInt(recordOffsetInPage + NAME_REF_OFFSET, nameId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setFlags(final @PersistentFS.Attributes int flags) {
|
||||
final int fieldOffsetInPage = recordOffsetInPage + FLAGS_OFFSET;
|
||||
if (pageBuffer.getInt(fieldOffsetInPage) != flags) {
|
||||
pageBuffer.putInt(fieldOffsetInPage, flags);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setLength(final long length) {
|
||||
final int fieldOffsetInPage = recordOffsetInPage + LENGTH_OFFSET;
|
||||
if (pageBuffer.getLong(fieldOffsetInPage) != length) {
|
||||
pageBuffer.putLong(fieldOffsetInPage, length);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTimestamp(final long timestamp) {
|
||||
final int fieldOffsetInPage = recordOffsetInPage + TIMESTAMP_OFFSET;
|
||||
if (pageBuffer.getLong(fieldOffsetInPage) != timestamp) {
|
||||
pageBuffer.putLong(fieldOffsetInPage, timestamp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setContentRecordId(final int contentRecordId) {
|
||||
checkValidIdField(recordId, contentRecordId, "contentRecordId");
|
||||
final int fieldOffsetInPage = recordOffsetInPage + CONTENT_REF_OFFSET;
|
||||
if (pageBuffer.getInt(fieldOffsetInPage) != contentRecordId) {
|
||||
pageBuffer.putInt(fieldOffsetInPage, contentRecordId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkParentIdIsValid(final int parentId) throws IndexOutOfBoundsException {
|
||||
final int recordsAllocatedSoFar = allocatedRecordsCount.get();
|
||||
if (!(NULL_ID <= parentId && parentId <= recordsAllocatedSoFar)) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
"parentId(=" + parentId + ") is outside of allocated IDs range [0, " + recordsAllocatedSoFar + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HeaderAccessor implements HeaderForUpdate {
|
||||
private final @NotNull PersistentFSRecordsOverLockFreePagedStorage records;
|
||||
|
||||
private HeaderAccessor(final @NotNull PersistentFSRecordsOverLockFreePagedStorage records) { this.records = records; }
|
||||
|
||||
@Override
|
||||
public long getTimestamp() throws IOException {
|
||||
return records.getTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() throws IOException {
|
||||
return records.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGlobalModCount() {
|
||||
return records.getGlobalModCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVersion(final int version) throws IOException {
|
||||
records.setVersion(version);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ==== records operations: ================================================================ //
|
||||
|
||||
|
||||
@Override
|
||||
public int allocateRecord() {
|
||||
int recordId = allocatedRecordsCount.incrementAndGet();
|
||||
try {
|
||||
//Not only assigns new record modCount, but also ensures storage file is actually _extended_ to fit a new record.
|
||||
// We calculate allocated records via file size on load, so must extend the file size to include a new record,
|
||||
// otherwise newly allocated record can be lost
|
||||
markRecordAsModified(recordId);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new UncheckedIOException("Can't ensure room for recordId=" + recordId, e);
|
||||
}
|
||||
return recordId;
|
||||
}
|
||||
|
||||
// 'one field at a time' operations
|
||||
|
||||
@Override
|
||||
public void setAttributeRecordId(final int recordId,
|
||||
final int attributeRecordId) throws IOException {
|
||||
checkValidIdField(recordId, attributeRecordId, "attributeRecordId");
|
||||
setIntField(recordId, ATTR_REF_OFFSET, attributeRecordId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeRecordId(final int recordId) throws IOException {
|
||||
return getIntField(recordId, ATTR_REF_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParent(final int recordId) throws IOException {
|
||||
return getIntField(recordId, PARENT_REF_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParent(final int recordId,
|
||||
final int parentId) throws IOException {
|
||||
checkParentIdIsValid(parentId);
|
||||
setIntField(recordId, PARENT_REF_OFFSET, parentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNameId(final int recordId) throws IOException {
|
||||
return getIntField(recordId, NAME_REF_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateNameId(final int recordId,
|
||||
final int nameId) throws IOException {
|
||||
PersistentFSConnection.ensureIdIsValid(nameId);
|
||||
return setIntField(recordId, NAME_REF_OFFSET, nameId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setFlags(final int recordId,
|
||||
final @PersistentFS.Attributes int newFlags) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
final int storedFlags = pageBuffer.getInt(recordOffsetOnPage + FLAGS_OFFSET);
|
||||
final boolean reallyChanged = storedFlags != newFlags;
|
||||
if (reallyChanged) {
|
||||
pageBuffer.putInt(recordOffsetOnPage + FLAGS_OFFSET, newFlags);
|
||||
incrementRecordVersion(pageBuffer, recordOffsetOnPage);
|
||||
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
}
|
||||
return reallyChanged;
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @PersistentFS.Attributes int getFlags(final int recordId) throws IOException {
|
||||
return getIntField(recordId, FLAGS_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength(final int recordId) throws IOException {
|
||||
return getLongField(recordId, LENGTH_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setLength(final int recordId,
|
||||
final long newLength) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
final long storedLength = pageBuffer.getLong(recordOffsetOnPage + LENGTH_OFFSET);
|
||||
final boolean reallyChanged = storedLength != newLength;
|
||||
if (reallyChanged) {
|
||||
pageBuffer.putLong(recordOffsetOnPage + LENGTH_OFFSET, newLength);
|
||||
incrementRecordVersion(pageBuffer, recordOffsetOnPage);
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
}
|
||||
return reallyChanged;
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp(final int recordId) throws IOException {
|
||||
return getLongField(recordId, TIMESTAMP_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTimestamp(final int recordId,
|
||||
final long newTimestamp) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
final long storedTimestamp = pageBuffer.getLong(recordOffsetOnPage + TIMESTAMP_OFFSET);
|
||||
final boolean reallyChanged = storedTimestamp != newTimestamp;
|
||||
if (reallyChanged) {
|
||||
pageBuffer.putLong(recordOffsetOnPage + TIMESTAMP_OFFSET, newTimestamp);
|
||||
incrementRecordVersion(pageBuffer, recordOffsetOnPage);
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
}
|
||||
return reallyChanged;
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModCount(final int recordId) throws IOException {
|
||||
return getIntField(recordId, MOD_COUNT_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markRecordAsModified(final int recordId) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
incrementRecordVersion(page.rawPageBuffer(), recordOffsetOnPage);
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getContentRecordId(final int recordId) throws IOException {
|
||||
return getIntField(recordId, CONTENT_REF_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setContentRecordId(final int recordId,
|
||||
final int newContentRef) throws IOException {
|
||||
checkValidIdField(recordId, newContentRef, "contentRecordId");
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
final int storedContentRefId = pageBuffer.getInt(recordOffsetOnPage + CONTENT_REF_OFFSET);
|
||||
final boolean reallyChanged = storedContentRefId != newContentRef;
|
||||
if (reallyChanged) {
|
||||
pageBuffer.putInt(recordOffsetOnPage + CONTENT_REF_OFFSET, newContentRef);
|
||||
incrementRecordVersion(pageBuffer, recordOffsetOnPage);
|
||||
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
}
|
||||
return reallyChanged;
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanRecord(final int recordId) throws IOException {
|
||||
checkRecordIdIsValid(recordId);
|
||||
|
||||
//fill record with zeroes, by 4 bytes at once:
|
||||
assert RECORD_SIZE_IN_BYTES % Integer.BYTES == 0 : "RECORD_SIZE_IN_BYTES(=" + RECORD_SIZE_IN_BYTES + ") is expected to be 32-aligned";
|
||||
final int recordSizeInInts = RECORD_SIZE_IN_BYTES / Integer.BYTES;
|
||||
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
for (int wordNo = 0; wordNo < recordSizeInInts; wordNo++) {
|
||||
final int offsetOfWord = recordOffsetOnPage + wordNo * Integer.BYTES;
|
||||
pageBuffer.putInt(offsetOfWord, 0);
|
||||
}
|
||||
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processAllRecords(final @NotNull PersistentFSRecordsStorage.FsRecordProcessor processor) throws IOException {
|
||||
final int recordsCount = allocatedRecordsCount.get();
|
||||
for (int recordId = MIN_VALID_ID; recordId <= recordsCount; recordId++) {
|
||||
processor.process(
|
||||
recordId,
|
||||
getNameId(recordId),
|
||||
getFlags(recordId),
|
||||
getParent(recordId),
|
||||
getAttributeRecordId(recordId),
|
||||
getContentRecordId(recordId),
|
||||
/* corrupted = */ false
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============== storage 'global' properties accessors: ============================= //
|
||||
|
||||
@Override
|
||||
public long getTimestamp() throws IOException {
|
||||
return getLongHeaderField(HEADER_TIMESTAMP_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasClosedProperly() throws IOException {
|
||||
return wasClosedProperly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getErrorsAccumulated() throws IOException {
|
||||
return getIntHeaderField(HEADER_ERRORS_ACCUMULATED_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setErrorsAccumulated(int errors) throws IOException {
|
||||
setIntHeaderField(HEADER_ERRORS_ACCUMULATED_OFFSET, errors);
|
||||
globalModCount.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVersion(final int version) throws IOException {
|
||||
setIntHeaderField(HEADER_VERSION_OFFSET, version);
|
||||
setLongHeaderField(HEADER_TIMESTAMP_OFFSET, System.currentTimeMillis());
|
||||
globalModCount.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() throws IOException {
|
||||
return getIntHeaderField(HEADER_VERSION_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGlobalModCount() {
|
||||
return globalModCount.get();
|
||||
}
|
||||
|
||||
|
||||
public long actualDataLength() {
|
||||
final int recordsCount = allocatedRecordsCount.get() + 1;
|
||||
return recordOffsetInFileUnchecked(recordsCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty() {
|
||||
return storage.isDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void force() throws IOException {
|
||||
if (storage.isDirty()) {
|
||||
setIntHeaderField(HEADER_GLOBAL_MOD_COUNT_OFFSET, globalModCount.get());
|
||||
storage.force();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!storage.isClosed()) {
|
||||
setIntHeaderField(HEADER_CONNECTION_STATUS_OFFSET, SAFELY_CLOSED_STAMP);
|
||||
|
||||
force();
|
||||
storage.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeAndClean() throws IOException {
|
||||
close();
|
||||
try {
|
||||
storage.closeAndRemoveAllFiles();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// =============== implementation: addressing ========================================================= //
|
||||
|
||||
/** Without recordId bounds checking */
|
||||
@VisibleForTesting
|
||||
long recordOffsetInFileUnchecked(final int recordId) {
|
||||
//recordId is 1-based, but 0-based is more convenient for the following:
|
||||
final int recordNo = recordId - 1;
|
||||
|
||||
final int recordsOnHeaderPage = (pageSize - HEADER_SIZE) / RECORD_SIZE_IN_BYTES;
|
||||
if (recordNo < recordsOnHeaderPage) {
|
||||
return HEADER_SIZE + recordNo * (long)RECORD_SIZE_IN_BYTES;
|
||||
}
|
||||
|
||||
//as-if there were no header:
|
||||
final int fullPages = recordNo / recordsPerPage;
|
||||
final int recordsOnLastPage = recordNo % recordsPerPage;
|
||||
|
||||
//header on the first page "push out" few records:
|
||||
final int recordsExcessBecauseOfHeader = recordsPerPage - recordsOnHeaderPage;
|
||||
|
||||
//so the last page could turn into +1 page:
|
||||
final int recordsReallyOnLastPage = recordsOnLastPage + recordsExcessBecauseOfHeader;
|
||||
return (long)(fullPages + recordsReallyOnLastPage / recordsPerPage) * pageSize
|
||||
+ (long)(recordsReallyOnLastPage % recordsPerPage) * RECORD_SIZE_IN_BYTES;
|
||||
}
|
||||
|
||||
private long recordOffsetInFile(final int recordId) throws IndexOutOfBoundsException {
|
||||
checkRecordIdIsValid(recordId);
|
||||
return recordOffsetInFileUnchecked(recordId);
|
||||
}
|
||||
|
||||
private void checkRecordIdIsValid(final int recordId) throws IndexOutOfBoundsException {
|
||||
if (!isValidFileId(recordId)) {
|
||||
final int recordsAllocatedSoFar = allocatedRecordsCount.get();
|
||||
throw new IndexOutOfBoundsException(
|
||||
"recordId(=" + recordId + ") is outside of allocated IDs range (0, " + recordsAllocatedSoFar + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkParentIdIsValid(int parentId) throws IndexOutOfBoundsException {
|
||||
final int recordsAllocatedSoFar = allocatedRecordsCount.get();
|
||||
if (!(NULL_ID <= parentId && parentId <= recordsAllocatedSoFar)) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
"parentId(=" + parentId + ") is outside of allocated IDs range [0, " + recordsAllocatedSoFar + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkValidIdField(int recordId,
|
||||
int idFieldValue,
|
||||
@NotNull String fieldName) {
|
||||
if (idFieldValue < NULL_ID) {
|
||||
throw new IllegalArgumentException("file[id: " + recordId + "]." + fieldName + "(=" + idFieldValue + ") must be >=0");
|
||||
}
|
||||
}
|
||||
|
||||
// =============== implementation: record field access ================================================ //
|
||||
|
||||
//each access method acquires a page & acquires appropriate kind of page lock:
|
||||
|
||||
private void setLongField(final int recordId,
|
||||
final int fieldRelativeOffset,
|
||||
final long fieldValue) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
pageBuffer.putLong(recordOffsetOnPage + fieldRelativeOffset, fieldValue);
|
||||
incrementRecordVersion(pageBuffer, recordOffsetOnPage);
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getLongField(final int recordId,
|
||||
final int fieldRelativeOffset) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ false)) {
|
||||
page.lockPageForRead();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
return pageBuffer.getLong(recordOffsetOnPage + fieldRelativeOffset);
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int setIntField(final int recordId,
|
||||
final int fieldRelativeOffset,
|
||||
final int fieldValue) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ true)) {
|
||||
page.lockPageForWrite();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
int previousValue = pageBuffer.getInt(recordOffsetOnPage + fieldRelativeOffset);
|
||||
pageBuffer.putInt(recordOffsetOnPage + fieldRelativeOffset, fieldValue);
|
||||
incrementRecordVersion(pageBuffer, recordOffsetOnPage);
|
||||
page.regionModified(recordOffsetOnPage, RECORD_SIZE_IN_BYTES);
|
||||
return previousValue;
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getIntField(final int recordId,
|
||||
final int fieldRelativeOffset) throws IOException {
|
||||
final long recordOffsetInFile = recordOffsetInFile(recordId);
|
||||
final int recordOffsetOnPage = storage.toOffsetInPage(recordOffsetInFile);
|
||||
try (final PageUnsafe page = (PageUnsafe)storage.pageByOffset(recordOffsetInFile, /*forWrite: */ false)) {
|
||||
page.lockPageForRead();
|
||||
try {
|
||||
final ByteBuffer pageBuffer = page.rawPageBuffer();
|
||||
return pageBuffer.getInt(recordOffsetOnPage + fieldRelativeOffset);
|
||||
}
|
||||
finally {
|
||||
page.unlockPageForRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void incrementRecordVersion(final @NotNull ByteBuffer pageBuffer,
|
||||
final int recordOffsetOnPage) {
|
||||
pageBuffer.putInt(recordOffsetOnPage + MOD_COUNT_OFFSET, globalModCount.incrementAndGet());
|
||||
}
|
||||
|
||||
//============ header fields access: ============================================================ //
|
||||
|
||||
private void setLongHeaderField(final @HeaderOffset int headerRelativeOffsetBytes,
|
||||
final long headerValue) throws IOException {
|
||||
checkHeaderOffset(headerRelativeOffsetBytes);
|
||||
try (final Page page = storage.pageByOffset(headerRelativeOffsetBytes, /*forWrite: */ true)) {
|
||||
page.putLong(headerRelativeOffsetBytes, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
private long getLongHeaderField(final @HeaderOffset int headerRelativeOffsetBytes) throws IOException {
|
||||
checkHeaderOffset(headerRelativeOffsetBytes);
|
||||
try (final Page page = storage.pageByOffset(headerRelativeOffsetBytes, /*forWrite: */ false)) {
|
||||
return page.getLong(headerRelativeOffsetBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIntHeaderField(final @HeaderOffset int headerRelativeOffsetBytes,
|
||||
final int headerValue) throws IOException {
|
||||
checkHeaderOffset(headerRelativeOffsetBytes);
|
||||
try (final Page page = storage.pageByOffset(headerRelativeOffsetBytes, /*forWrite: */ true)) {
|
||||
page.putInt(headerRelativeOffsetBytes, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int getIntHeaderField(final @HeaderOffset int headerRelativeOffsetBytes) throws IOException {
|
||||
checkHeaderOffset(headerRelativeOffsetBytes);
|
||||
try (final Page page = storage.pageByOffset(headerRelativeOffsetBytes, /*forWrite: */ false)) {
|
||||
return page.getInt(headerRelativeOffsetBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkHeaderOffset(final int headerRelativeOffset) {
|
||||
if (!(0 <= headerRelativeOffset && headerRelativeOffset < HEADER_SIZE)) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
"headerFieldOffset(=" + headerRelativeOffset + ") is outside of header [0, " + HEADER_SIZE + ") ");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-38
@@ -4,8 +4,7 @@ package com.intellij.openapi.vfs.newvfs.persistent
|
||||
import com.intellij.platform.util.io.storages.StorageFactory
|
||||
import com.intellij.platform.util.io.storages.mmapped.MMappedFileStorageFactory
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.intellij.util.io.*
|
||||
import com.intellij.util.io.pagecache.impl.PageContentLockingStrategy
|
||||
import com.intellij.util.io.StorageAlreadyInUseException
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
import java.io.IOException
|
||||
@@ -83,44 +82,10 @@ abstract class PersistentFSRecordsStorageFactory(val id: Int) : StorageFactory<P
|
||||
}
|
||||
}
|
||||
|
||||
/** Fallback impl for [OverMMappedFile] if something goes terribly wrong, and we can't fix it quickly */
|
||||
data class OverLockFreeFileCache(val pageSize: Int = PageCacheUtils.DEFAULT_PAGE_SIZE) : PersistentFSRecordsStorageFactory(id = 1) {
|
||||
private val PERSISTENT_FS_STORAGE_CONTEXT_RW = StorageLockContext(true, true, true)
|
||||
|
||||
init {
|
||||
val recordLength = PersistentFSRecordsOverLockFreePagedStorage.RECORD_SIZE_IN_BYTES
|
||||
val recordsArePageAligned = pageSize % recordLength == 0
|
||||
if (!recordsArePageAligned) {
|
||||
throw AssertionError("Bug: record length(=$recordLength) is not aligned with page size(=$pageSize)")
|
||||
}
|
||||
}
|
||||
|
||||
override fun open(storagePath: Path): PersistentFSRecordsOverLockFreePagedStorage {
|
||||
|
||||
if (!PageCacheUtils.LOCK_FREE_PAGE_CACHE_ENABLED) {
|
||||
throw IOException(
|
||||
"Configuration mismatch: PageCacheUtils.LOCK_FREE_PAGE_CACHE_ENABLED=false " +
|
||||
"=> can't create PersistentFSRecordsOverLockFreePagedStorage if FilePageCacheLockFree is disabled")
|
||||
}
|
||||
|
||||
val pagedStorage = PagedFileStorageWithRWLockedPageContent(
|
||||
storagePath,
|
||||
PERSISTENT_FS_STORAGE_CONTEXT_RW,
|
||||
pageSize,
|
||||
IOUtil.useNativeByteOrderForByteBuffers(),
|
||||
PageContentLockingStrategy.LOCK_PER_PAGE
|
||||
)
|
||||
return IOUtil.wrapSafely<PersistentFSRecordsOverLockFreePagedStorage, PagedFileStorageWithRWLockedPageContent, IOException>(
|
||||
pagedStorage) {
|
||||
PersistentFSRecordsOverLockFreePagedStorage(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** For testing/benchmarking: serves as a reference point. Not a prod-level implementation! */
|
||||
data class InMemory(val maxRecordsCount: Int = (1 shl 24)) : PersistentFSRecordsStorageFactory(id = 2) {
|
||||
@Suppress("TestOnlyProblems")
|
||||
override fun open(storagePath: Path) = PersistentFSRecordsOverInMemoryStorage(storagePath, maxRecordsCount)
|
||||
override fun open(storagePath: Path): PersistentFSRecordsOverInMemoryStorage = PersistentFSRecordsOverInMemoryStorage(storagePath, maxRecordsCount)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -146,7 +111,6 @@ abstract class PersistentFSRecordsStorageFactory(val id: Int) : StorageFactory<P
|
||||
|
||||
private fun defaultFromSystemProperties(): PersistentFSRecordsStorageFactory {
|
||||
return when (System.getProperty("vfs.records-storage.impl", "OVER_MMAPPED_FILE")) {
|
||||
"OVER_LOCK_FREE_FILE_CACHE" -> OverLockFreeFileCache()
|
||||
"IN_MEMORY" -> InMemory()
|
||||
else -> OverMMappedFile()
|
||||
}
|
||||
|
||||
-64
@@ -2,10 +2,7 @@
|
||||
package com.intellij.openapi.vfs.newvfs.persistent;
|
||||
|
||||
import com.intellij.openapi.util.IntRef;
|
||||
import com.intellij.util.io.PagedFileStorageWithRWLockedPageContent;
|
||||
import com.intellij.util.io.StorageLockContext;
|
||||
import com.intellij.platform.util.io.storages.mmapped.MMappedFileStorageFactory;
|
||||
import com.intellij.util.io.pagecache.impl.PageContentLockingStrategy;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.runner.Runner;
|
||||
import org.openjdk.jmh.runner.RunnerException;
|
||||
@@ -43,8 +40,6 @@ public class PersistentFSStoragesBenchmarks {
|
||||
//@Param
|
||||
public static final int RECORDS_COUNT = 1 << 22;
|
||||
|
||||
public static final StorageLockContext CONTEXT = new StorageLockContext(true, true, true);
|
||||
|
||||
private static final Unsafe UNSAFE;
|
||||
private static final long BYTE_BUFFER_ADDRESS_FIELD_OFFSET;
|
||||
|
||||
@@ -203,33 +198,6 @@ public class PersistentFSStoragesBenchmarks {
|
||||
}
|
||||
}
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public static class FSRecordsOverLockFreeFPCState {
|
||||
|
||||
private PersistentFSRecordsOverLockFreePagedStorage storage;
|
||||
|
||||
@Setup
|
||||
public void setup(FileState fileState) throws IOException {
|
||||
final int pageSize = 1 << 20;
|
||||
storage = new PersistentFSRecordsOverLockFreePagedStorage(
|
||||
new PagedFileStorageWithRWLockedPageContent(
|
||||
fileState.file,
|
||||
CONTEXT,
|
||||
pageSize,
|
||||
PageContentLockingStrategy.LOCK_PER_PAGE
|
||||
)
|
||||
);
|
||||
for (int i = 0; i < RECORDS_COUNT; i++) {
|
||||
storage.allocateRecord();
|
||||
}
|
||||
}
|
||||
|
||||
@TearDown
|
||||
public void close() throws IOException {
|
||||
storage.close();
|
||||
}
|
||||
}
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
public static class FSRecordsOverMMappedFileState {
|
||||
|
||||
@@ -472,14 +440,6 @@ public class PersistentFSStoragesBenchmarks {
|
||||
return fsRecords_randomReadByField(it, storage);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int FSRecords_OverNewFPC_RandomReadByField(final RecordIterationState it,
|
||||
final FSRecordsOverLockFreeFPCState state) throws IOException {
|
||||
final PersistentFSRecordsStorage storage = state.storage;
|
||||
|
||||
return fsRecords_randomReadByField(it, storage);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int FSRecords_MemoryMapped_RandomReadByRecord(final RecordIterationState it,
|
||||
final FSRecordsOverMMappedFileState state) throws IOException {
|
||||
@@ -488,14 +448,6 @@ public class PersistentFSStoragesBenchmarks {
|
||||
return fsRecords_RandomReadByRecord(it, storage);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int FSRecords_OverNewFPC_RandomReadByRecord(final RecordIterationState it,
|
||||
final FSRecordsOverLockFreeFPCState state) throws IOException {
|
||||
final IPersistentFSRecordsStorage storage = state.storage;
|
||||
|
||||
return fsRecords_RandomReadByRecord(it, storage);
|
||||
}
|
||||
|
||||
private static int fsRecords_randomReadByField(final RecordIterationState it,
|
||||
final PersistentFSRecordsStorage storage) throws IOException {
|
||||
final int recordId = it.nextRandomRecordIndex();
|
||||
@@ -667,14 +619,6 @@ public class PersistentFSStoragesBenchmarks {
|
||||
fsRecords_randomWriteByField(it, storage);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void FSRecords_OverNewFPC_RandomWriteByField(final RecordIterationState it,
|
||||
final FSRecordsOverLockFreeFPCState state) throws IOException {
|
||||
final PersistentFSRecordsStorage storage = state.storage;
|
||||
|
||||
fsRecords_randomWriteByField(it, storage);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void FSRecords_MemoryMapped_RandomWriteByRecord(final RecordIterationState it,
|
||||
final FSRecordsOverMMappedFileState state) throws IOException {
|
||||
@@ -683,14 +627,6 @@ public class PersistentFSStoragesBenchmarks {
|
||||
fsRecords_randomWriteByRecord(it, storage);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void FSRecords_OverNewFPC_RandomWriteByRecord(final RecordIterationState it,
|
||||
final FSRecordsOverLockFreeFPCState state) throws IOException {
|
||||
final IPersistentFSRecordsStorage storage = state.storage;
|
||||
|
||||
fsRecords_randomWriteByRecord(it, storage);
|
||||
}
|
||||
|
||||
private static void fsRecords_randomWriteByField(final RecordIterationState it,
|
||||
final PersistentFSRecordsStorage storage) throws IOException {
|
||||
final int recordId = it.nextRandomRecordIndex();
|
||||
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.openapi.vfs.newvfs.persistent;
|
||||
|
||||
|
||||
import com.intellij.util.io.PageCacheUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static com.intellij.openapi.vfs.newvfs.persistent.PersistentFSRecordsOverLockFreePagedStorage.NULL_ID;
|
||||
import static com.intellij.openapi.vfs.newvfs.persistent.PersistentFSRecordsOverLockFreePagedStorage.RECORD_SIZE_IN_BYTES;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class PersistentFSRecordsStorageOverLockFreePagedStorageTest
|
||||
extends PersistentFSRecordsStorageTestBase<PersistentFSRecordsOverLockFreePagedStorage> {
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: {0}")
|
||||
public static UpdateAPIMethod[] METHODS_TO_TEST() {
|
||||
return new UpdateAPIMethod[]{
|
||||
DEFAULT_API_UPDATE_METHOD,
|
||||
MODERN_API_UPDATE_METHOD
|
||||
};
|
||||
}
|
||||
|
||||
public static final int MAX_RECORDS_TO_INSERT = 1 << 22;
|
||||
|
||||
private static final int PAGE_SIZE = PageCacheUtils.DEFAULT_PAGE_SIZE;
|
||||
|
||||
|
||||
public PersistentFSRecordsStorageOverLockFreePagedStorageTest(final UpdateAPIMethod updateMethodToTest) { super(MAX_RECORDS_TO_INSERT, updateMethodToTest); }
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
assumeTrue(
|
||||
"LockFree FilePageCache must be enabled: see PageCacheUtils.LOCK_FREE_PAGE_CACHE_ENABLED",
|
||||
PageCacheUtils.LOCK_FREE_PAGE_CACHE_ENABLED
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected PersistentFSRecordsOverLockFreePagedStorage openStorage(final Path storagePath) throws IOException {
|
||||
return new PersistentFSRecordsStorageFactory.OverLockFreeFileCache().open(storagePath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordAreAlwaysAlignedFullyOnSinglePage() {
|
||||
final int enoughRecords = (PAGE_SIZE / RECORD_SIZE_IN_BYTES) * 16;
|
||||
|
||||
for (int recordId = 0; recordId < enoughRecords; recordId++) {
|
||||
final long recordOffsetInFile = storage.recordOffsetInFileUnchecked(recordId);
|
||||
final long recordEndOffsetInFile = recordOffsetInFile + RECORD_SIZE_IN_BYTES - 1;
|
||||
assertEquals(
|
||||
"Record(#" + recordId + ", offset: " + recordOffsetInFile + ") must start and end on a same page",
|
||||
recordOffsetInFile / PAGE_SIZE,
|
||||
recordEndOffsetInFile / PAGE_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recordOffsetCalculatedByStorageIsConsistentWithPlainCalculation() {
|
||||
final int enoughRecords = (PAGE_SIZE / RECORD_SIZE_IN_BYTES) * 16;
|
||||
|
||||
long expectedRecordOffsetInFile = PersistentFSRecordsOverLockFreePagedStorage.HEADER_SIZE;
|
||||
for (int recordId = NULL_ID + 1; recordId < enoughRecords; recordId++) {
|
||||
|
||||
final long recordOffsetInFile = storage.recordOffsetInFileUnchecked(recordId);
|
||||
assertEquals("recordOffset(recordId:" + recordId + ") must be " + expectedRecordOffsetInFile,
|
||||
expectedRecordOffsetInFile,
|
||||
recordOffsetInFile
|
||||
);
|
||||
|
||||
expectedRecordOffsetInFile += RECORD_SIZE_IN_BYTES;
|
||||
if (PAGE_SIZE - (expectedRecordOffsetInFile % PAGE_SIZE) < RECORD_SIZE_IN_BYTES) {
|
||||
expectedRecordOffsetInFile = (expectedRecordOffsetInFile / PAGE_SIZE + 1) * PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadRecordsCount_IsConsistentWith_recordOffsetInFile() throws IOException {
|
||||
final int enoughRecords = (PAGE_SIZE / RECORD_SIZE_IN_BYTES) * 16;
|
||||
|
||||
for (int recordId = NULL_ID + 1; recordId < enoughRecords; recordId++) {
|
||||
final long recordOffsetInFile = storage.recordOffsetInFileUnchecked(recordId);
|
||||
final int calculatedRecordNo = storage.loadRecordsCount(recordOffsetInFile);
|
||||
final int expectedRecordNo = recordId - 1;
|
||||
assertEquals("storage(" + recordOffsetInFile + "b) must contain " + expectedRecordNo + " records",
|
||||
expectedRecordNo,
|
||||
calculatedRecordNo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadRecordsCount_ThrowsIOException_IfUnEvenNumberOfRecordsFound() throws IOException {
|
||||
//RC: method is very slow, so use 1 random excess for each record, instead of iterating through
|
||||
// each excess: [1..RECORD_SIZE_IN_BYTES) on each record
|
||||
final int enoughRecords = (PAGE_SIZE / RECORD_SIZE_IN_BYTES) * 16;
|
||||
|
||||
for (int recordId = NULL_ID + 1; recordId < enoughRecords; recordId++) {
|
||||
final long recordOffsetInFile = storage.recordOffsetInFileUnchecked(recordId);
|
||||
final int excessBytes = ThreadLocalRandom.current().nextInt(1, RECORD_SIZE_IN_BYTES);
|
||||
final long storageSizeWithExcess = recordOffsetInFile + excessBytes;
|
||||
try {
|
||||
storage.loadRecordsCount(storageSizeWithExcess);
|
||||
fail(recordOffsetInFile + "b + " + excessBytes + "b: excess contains non-integer records " +
|
||||
"-> must throw 'corrupted' exception");
|
||||
}
|
||||
catch (IOException e) {
|
||||
final String message = e.getMessage();
|
||||
assertTrue(
|
||||
"Exception [" + message + "] must contain 'is corrupted' message",
|
||||
message.contains("is corrupted")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-10
@@ -3,13 +3,11 @@ package com.intellij.openapi.vfs.newvfs.persistent;
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.newvfs.FileAttribute;
|
||||
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSRecordsStorageFactory.OverLockFreeFileCache;
|
||||
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSRecordsStorageFactory.OverMMappedFile;
|
||||
import com.intellij.openapi.vfs.newvfs.persistent.recovery.VFSInitializationResult;
|
||||
import com.intellij.openapi.vfs.newvfs.persistent.recovery.VFSRecoverer;
|
||||
import com.intellij.platform.util.io.storages.StorageTestingUtils;
|
||||
import com.intellij.testFramework.TemporaryDirectory;
|
||||
import com.intellij.util.io.PageCacheUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
@@ -198,10 +196,7 @@ public class VFSInitializationTest {
|
||||
|
||||
|
||||
//skip IN_MEMORY impl, since it is not really persistent
|
||||
//skip OVER_LOCK_FREE_FILE_CACHE impl if !LOCK_FREE_PAGE_CACHE_ENABLED (fails otherwise)
|
||||
List<PersistentFSRecordsStorageFactory> allStorageKinds = PageCacheUtils.LOCK_FREE_PAGE_CACHE_ENABLED ?
|
||||
List.of(new OverLockFreeFileCache(), new OverMMappedFile()) :
|
||||
List.of(new OverMMappedFile());
|
||||
List<PersistentFSRecordsStorageFactory> allStorageKinds = List.of(new OverMMappedFile());
|
||||
|
||||
List<String> filesNotLeadingToVFSRebuild = new ArrayList<>();
|
||||
for (PersistentFSRecordsStorageFactory storageKind : allStorageKinds) {
|
||||
@@ -272,10 +267,7 @@ public class VFSInitializationTest {
|
||||
@Test
|
||||
public void VFS_isRebuilt_OnlyIf_ImplementationVersionChanged() throws Exception {
|
||||
//skip IN_MEMORY impl, since it is not really persistent
|
||||
//skip OVER_LOCK_FREE_FILE_CACHE impl if !LOCK_FREE_PAGE_CACHE_ENABLED (will fail)
|
||||
final List<PersistentFSRecordsStorageFactory> allKinds = PageCacheUtils.LOCK_FREE_PAGE_CACHE_ENABLED ?
|
||||
List.of(new OverLockFreeFileCache(), new OverMMappedFile()) :
|
||||
List.of(new OverMMappedFile());
|
||||
final List<PersistentFSRecordsStorageFactory> allKinds = List.of(new OverMMappedFile());
|
||||
|
||||
//check all combinations (from->to) of implementations:
|
||||
for (PersistentFSRecordsStorageFactory kindBefore : allKinds) {
|
||||
|
||||
Reference in New Issue
Block a user