diff --git a/platform/core-api/api-dump-unreviewed.txt b/platform/core-api/api-dump-unreviewed.txt
index 6a57074f89aa..774b658dd00f 100644
--- a/platform/core-api/api-dump-unreviewed.txt
+++ b/platform/core-api/api-dump-unreviewed.txt
@@ -2662,7 +2662,6 @@ c:com.intellij.openapi.vfs.InvalidVirtualFileAccessException
com.intellij.openapi.vfs.LocalFileProvider
com.intellij.openapi.vfs.NonPhysicalFileSystem
f:com.intellij.openapi.vfs.PersistentFSConstants
-- sf:FILE_LENGTH_TO_CACHE_THRESHOLD:J
- s:getMaxIntellisenseFileSize():I
- s:setMaxIntellisenseFileSize(I):V
a:com.intellij.openapi.vfs.ReadonlyStatusHandler
diff --git a/platform/core-api/src/com/intellij/openapi/vfs/PersistentFSConstants.java b/platform/core-api/src/com/intellij/openapi/vfs/PersistentFSConstants.java
index 6b668b18cb16..41db2a3627ca 100644
--- a/platform/core-api/src/com/intellij/openapi/vfs/PersistentFSConstants.java
+++ b/platform/core-api/src/com/intellij/openapi/vfs/PersistentFSConstants.java
@@ -3,24 +3,35 @@ package com.intellij.openapi.vfs;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.io.FileUtilRt;
-import com.intellij.openapi.vfs.limits.FileSizeLimit;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.TestOnly;
+import static com.intellij.util.SystemProperties.getIntProperty;
+
public final class PersistentFSConstants {
- /** Max file size to cache inside VFS */
- public static final long FILE_LENGTH_TO_CACHE_THRESHOLD = FileSizeLimit.getFileLengthToCacheThreshold();
+ /**
+ * Max file size to cache inside VFS.
+ * Cache huge files is rarely useful, but time-consuming (freeze-prone), and could quickly overflow VFS content storage capacity
+ */
+ @ApiStatus.Internal
+ public static final int MAX_FILE_LENGTH_TO_CACHE = getIntProperty(
+ "idea.vfs.max-file-length-to-cache",
+ FileUtilRt.MEGABYTE
+ );
/**
- * Must always be in range [0, {@link #FILE_LENGTH_TO_CACHE_THRESHOLD}]
+ * Must always be in range [0, {@link FileUtilRt#LARGE_FOR_CONTENT_LOADING}]
*
* Currently, this is always true, because
- *
FILE_LENGTH_TO_CACHE_THRESHOLD = ... = max(20Mb, userFileSizeLimit, userContentLoadLimit)
+ * LARGE_FOR_CONTENT_LOADING = ... = max(20Mb, userFileSizeLimit, userContentLoadLimit)
* but could be changed in the future, hence .min(...) here is to ensure that.
+ * TODO: move into FileSizeLimit
*/
- private static int ourMaxIntellisenseFileSize = Math.min(FileUtilRt.getUserFileSizeLimit(), (int)FILE_LENGTH_TO_CACHE_THRESHOLD);
+ private static int ourMaxIntellisenseFileSize = Math.min(FileUtilRt.getUserFileSizeLimit(), FileUtilRt.LARGE_FOR_CONTENT_LOADING);
/** @deprecated Prefer using {@link com.intellij.openapi.vfs.limits.FileSizeLimit#getIntellisenseLimit(String)} */
+ //TODO: move into FileSizeLimit
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated()
public static int getMaxIntellisenseFileSize() {
diff --git a/platform/core-api/src/com/intellij/openapi/vfs/limits/FileSizeLimit.kt b/platform/core-api/src/com/intellij/openapi/vfs/limits/FileSizeLimit.kt
index 981c9cec76c1..e5685fb58a68 100644
--- a/platform/core-api/src/com/intellij/openapi/vfs/limits/FileSizeLimit.kt
+++ b/platform/core-api/src/com/intellij/openapi/vfs/limits/FileSizeLimit.kt
@@ -21,11 +21,6 @@ interface FileSizeLimit {
private val limitsByExtension: AtomicReference?> = AtomicReference(null)
- /** Max file size to cache inside VFS */
- @JvmStatic
- @ApiStatus.Internal
- fun getFileLengthToCacheThreshold(): Int = FileUtilRt.LARGE_FOR_CONTENT_LOADING
-
private fun getLimitsByExtension(): Map {
return limitsByExtension.get() ?: limitsByExtension.updateAndGet { getLimits() }!!
}
@@ -56,12 +51,12 @@ interface FileSizeLimit {
@JvmStatic
fun getContentLoadLimit(extension: String?): Int {
@Suppress("DEPRECATION")
- val limit = findApplicable(extension ?: "")?.content ?: FileUtilRt.LARGE_FOR_CONTENT_LOADING
+ val limit = findApplicable(extension ?: "")?.content ?: getDefaultContentLoadLimit()
return limit
}
@JvmStatic
- fun getDefaultContentLoadLimit(): Int = getContentLoadLimit(null)
+ fun getDefaultContentLoadLimit(): Int = FileUtilRt.LARGE_FOR_CONTENT_LOADING
@JvmStatic
fun getIntellisenseLimit(): Int = getIntellisenseLimit(null)
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
index 06af21288aa0..b0a902df24c3 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
@@ -155,7 +155,7 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
setupOTelMonitoring(TelemetryManager.getInstance().getMeter(PlatformScopesKt.VFS));
- LOG.info("VFS.maxFileLengthToCache: " + PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD);
+ LOG.info("VFS.MAX_FILE_LENGTH_TO_CACHE: " + PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE);
}
@ApiStatus.Internal
@@ -909,7 +909,7 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
}
private static boolean shouldCacheFileContentInVFS(long fileLength) {
- return fileLength <= PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD;
+ return fileLength <= PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE;
}
private @NotNull InputStream createReplicatorAndStoreContent(@NotNull VirtualFile file,
@@ -1541,8 +1541,7 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
if (!(vf instanceof VirtualDirectoryImpl)) {
return;
}
- parent =
- (VirtualDirectoryImpl)vf; // retain in `myIdToDirCache` at least for the duration of this block, so that subsequent `findFileById` won't crash
+ parent = (VirtualDirectoryImpl)vf; // retain in `myIdToDirCache` at least for the duration of this block, so that subsequent `findFileById` won't crash
NewVirtualFileSystem fs = getFileSystem(parent);
List childrenAdded = new ArrayList<>(createEvents.size());
@@ -1559,8 +1558,7 @@ public final class PersistentFSImpl extends PersistentFS implements Disposable {
childrenAdded.sort(ChildInfo.BY_ID);
boolean caseSensitive = parent.isCaseSensitive();
vfsPeer.update(parent, parentId, oldChildren -> oldChildren.merge(vfsPeer, childrenAdded, caseSensitive));
- parent.createAndAddChildren(childrenAdded, false, (__, ___) -> {
- });
+ parent.createAndAddChildren(childrenAdded, false, (__, ___) -> {});
saveScannedChildrenRecursively(createEvents, fs, parent.isCaseSensitive());
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSLoader.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSLoader.java
index 4dfba5a4550f..67c471400ba8 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSLoader.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSLoader.java
@@ -4,9 +4,7 @@ package com.intellij.openapi.vfs.newvfs.persistent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.util.io.FileUtilRt;
-import com.intellij.platform.util.io.storages.blobstorage.StreamlinedBlobStorageHelper;
-import com.intellij.platform.util.io.storages.blobstorage.StreamlinedBlobStorageOverMMappedFile;
+import com.intellij.openapi.vfs.PersistentFSConstants;
import com.intellij.openapi.vfs.newvfs.persistent.dev.content.CompressingAlgo;
import com.intellij.openapi.vfs.newvfs.persistent.dev.content.ContentHashEnumeratorOverDurableEnumerator;
import com.intellij.openapi.vfs.newvfs.persistent.dev.content.ContentStorageAdapter;
@@ -15,6 +13,8 @@ import com.intellij.openapi.vfs.newvfs.persistent.dev.enumerator.DurableStringEn
import com.intellij.openapi.vfs.newvfs.persistent.recovery.VFSRecoverer;
import com.intellij.openapi.vfs.newvfs.persistent.recovery.VFSRecoveryInfo;
import com.intellij.platform.util.io.storages.StorageFactory;
+import com.intellij.platform.util.io.storages.blobstorage.StreamlinedBlobStorageHelper;
+import com.intellij.platform.util.io.storages.blobstorage.StreamlinedBlobStorageOverMMappedFile;
import com.intellij.platform.util.io.storages.mmapped.MMappedFileStorageFactory;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.concurrency.SequentialTaskExecutor;
@@ -23,7 +23,9 @@ import com.intellij.util.io.*;
import com.intellij.util.io.blobstorage.SpaceAllocationStrategy;
import com.intellij.util.io.blobstorage.SpaceAllocationStrategy.DataLengthPlusFixedPercentStrategy;
import com.intellij.util.io.blobstorage.StreamlinedBlobStorage;
-import com.intellij.util.io.storage.*;
+import com.intellij.util.io.storage.RefCountingContentStorage;
+import com.intellij.util.io.storage.RefCountingContentStorageImpl;
+import com.intellij.util.io.storage.VFSContentStorage;
import com.intellij.util.io.storage.lf.RefCountingContentStorageImplLF;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@@ -529,10 +531,10 @@ public final class PersistentFSLoader {
//Use larger pages: content storage is usually quite big.
int pageSize = 64 * IOUtil.MiB;
- if (pageSize <= FileUtilRt.LARGE_FOR_CONTENT_LOADING) {
+ if (pageSize <= PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE) {
//pageSize is an upper limit on record size for AppendOnlyLogOverMMappedFile:
LOG.warn("ContentStorage.pageSize(=" + pageSize + ") " +
- "must be > FileUtilRt.LARGE_FOR_CONTENT_LOADING(=" + FileUtilRt.LARGE_FOR_CONTENT_LOADING + "b), " +
+ "must be > PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE(=" + PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE + "b), " +
"otherwise large content can't fit");
}
CompressingAlgo compressionAlgo = switch (FSRecordsImpl.COMPRESSION_ALGO) {
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java
index 4c4cf2c355af..328a225d6f23 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/fileTypes/impl/FileTypesTest.java
@@ -899,7 +899,7 @@ public class FileTypesTest extends HeavyPlatformTestCase {
if (random.nextInt(3) == 0) {
WriteCommandAction.writeCommandAction(getProject()).run(() -> {
- byte[] bytes = new byte[(int)PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD + (isText ? 1 : 0)];
+ byte[] bytes = new byte[PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE + (isText ? 1 : 0)];
Arrays.fill(bytes, (byte)' ');
virtualFile.setBinaryContent(bytes);
});
@@ -928,7 +928,7 @@ public class FileTypesTest extends HeavyPlatformTestCase {
FrequentEventDetector.disableUntil(getTestRootDisposable());
File f = createTempFile("xx.asd_kjf_hlk_asj_dhf",
- StringUtil.repeatSymbol(' ', (int)PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD - 100));
+ StringUtil.repeatSymbol(' ', PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE - 100));
VirtualFile virtualFile = getVirtualFile(f);
assertEquals(PlainTextFileType.INSTANCE, getFileType(virtualFile));
PsiFile psiFile = getPsiManager().findFile(virtualFile);
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/PersistentFsTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/PersistentFsTest.java
index 586d06ccab52..2fc57216682a 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/PersistentFsTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/PersistentFsTest.java
@@ -18,6 +18,7 @@ import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.io.ByteArraySequence;
import com.intellij.openapi.util.io.FileAttributes;
import com.intellij.openapi.util.io.FileUtil;
@@ -66,6 +67,7 @@ import static com.intellij.testFramework.EdtTestUtil.runInEdtAndWait;
import static com.intellij.testFramework.UsefulTestCase.*;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -73,7 +75,6 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.junit.Assert.*;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
@@ -1021,14 +1022,17 @@ public class PersistentFsTest extends BareTestFixtureTestCase {
@Test
public void testContentReadingWhileModification() throws IOException {
- byte[] initialContent = StringUtil.repeat("one_two", 500_000).getBytes(UTF_8);
+ byte[] initialContent = StringUtil.repeat(
+ "one_two",
+ PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE / "one_two".length() - 1
+ ).getBytes(UTF_8);
File file = tempDirectory.newFile("test.txt", initialContent);
VirtualFile vFile = refreshAndFind(file);
int id = ((VirtualFileWithId)vFile).getId();
vFile.contentsToByteArray();
InputStream stream = FSRecords.getInstance().readContent(id);
- assertNotNull(stream);
+ assertNotNull("Content must be cached", stream);
byte[] bytes = stream.readNBytes(initialContent.length);
assertArrayEquals(initialContent, bytes);
InputStream stream2 = FSRecords.getInstance().readContent(id);
@@ -1040,6 +1044,38 @@ public class PersistentFsTest extends BareTestFixtureTestCase {
assertArrayEquals(initialContent, ArrayUtil.mergeArrays(portion1, portion2));
}
+ @Test
+ public void testHugeFileContentIsNotCachedInVFS() throws IOException {
+ byte[] hugeContent = StringUtil.repeat(
+ "anything",
+ PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE / "anything".length() + 1
+ ).getBytes(UTF_8);
+ assertTrue("Content must be larger than limit",
+ hugeContent.length > PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE);
+
+ File file = tempDirectory.newFile("test.txt", hugeContent);
+ VirtualFile vFile = refreshAndFind(file);
+ int id = ((VirtualFileWithId)vFile).getId();
+
+ //load content: for smaller files this should trigger file content caching, but not for huge files
+ vFile.contentsToByteArray();
+
+ assertNull("Content must NOT be cached in VFS during reading",
+ FSRecords.getInstance().readContent(id));
+
+ //save content: for smaller files this should trigger file content caching, but not for huge files
+ hugeContent[10] = 'A';//introduce a change
+ ApplicationManager.getApplication().runWriteAction((ThrowableComputable)() -> {
+ try (var out = vFile.getOutputStream(this)) {
+ out.write(hugeContent);
+ }
+ return null;
+ });
+
+ assertNull("Content must NOT be cached in VFS during saving",
+ FSRecords.getInstance().readContent(id));
+ }
+
@Test
public void testSearchingForJarRootWhenItsNotCached() throws IOException {
// IDEA-341011 PersistentFSImpl.cacheMissedRootFromPersistence fails to find jars because it uses name instead of path
diff --git a/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java b/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java
index b59ce81f4d23..a4b41d063a8d 100644
--- a/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java
+++ b/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java
@@ -30,11 +30,9 @@ public final class FileUtilRt {
public static final int MEGABYTE = KILOBYTE * KILOBYTE;
/**
- * This threshold has a mixed semantics, it is used in at least 2 roles:
- * 1. Max file size to store ('cache') in VFS (i.e. PersistentFSImpl)
- * 2. Just 'big enough' file size, to switch from simple one-chunk loading to incremental loading, or not load it at all
- * (e.g. {@linkplain #isTooLarge(long)}, JupyterFileUtils, JBZipEntry)
- * TODO RC: those roles are different enough to split them into independent thresholds
+ * File size that is 'big enough' to load.
+ * Used to either skip the file content loading completely, if larger -- or at least to switch from simple
+ * one-chunk loading to more memory-efficient incremental loading
* @deprecated Prefer using @link {@link com.intellij.openapi.vfs.limits.FileSizeLimit#getContentLoadLimit}
*/
@SuppressWarnings("DeprecatedIsStillUsed")
diff --git a/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java b/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java
index fa8023fea2a8..905713d70e94 100644
--- a/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java
+++ b/platform/vcs-api/src/com/intellij/vcsUtil/VcsUtil.java
@@ -64,7 +64,7 @@ public final class VcsUtil {
}
private static int computeLoadedFileSize() {
- long result = PersistentFSConstants.FILE_LENGTH_TO_CACHE_THRESHOLD;
+ long result = PersistentFSConstants.MAX_FILE_LENGTH_TO_CACHE;
try {
String userLimitKb = System.getProperty(MAX_VCS_LOADED_SIZE_KB);
if (userLimitKb != null) {