myPathSpy = (ignored, ignored2) -> {};
+
/**
* Adds a new backend filesystem that handles requests to specific roots.
*
@@ -100,6 +104,27 @@ public final class MultiRoutingFileSystemProvider
}
}
+ public static void setPathSpy(
+ @NotNull FileSystemProvider provider,
+ @NotNull BiConsumer<@NotNull String, @NotNull Boolean> pathSpy
+ ) {
+ if (provider.getClass().getName().equals(MultiRoutingFileSystemProvider.class.getName())) {
+ try {
+ provider.getClass().getMethod("setPathSpy", BiConsumer.class).invoke(provider, pathSpy);
+ }
+ catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ else {
+ throw new IllegalArgumentException(String.format("%s is not an instance of %s", provider, MultiRoutingFileSystemProvider.class));
+ }
+ }
+
+ public void setPathSpy(@NotNull BiConsumer pathSpy) {
+ myPathSpy = pathSpy;
+ }
+
public MultiRoutingFileSystemProvider(FileSystemProvider localFSProvider) {
myLocalProvider = localFSProvider;
myFileSystem = new MultiRoutingFileSystem(this, myLocalProvider.getFileSystem(URI.create("file:///")));
@@ -176,6 +201,7 @@ public final class MultiRoutingFileSystemProvider
FileSystem backend1 = myFileSystem.getBackend(path1String);
FileSystemProvider provider1 = backend1.provider();
if (path2 == null) {
+ myPathSpy.accept(path1String, provider1 != this);
return provider1;
}
@@ -188,14 +214,17 @@ public final class MultiRoutingFileSystemProvider
FileSystemProvider provider2 = backend2.provider();
if (provider1.equals(provider2)) {
+ myPathSpy.accept(path1String, provider1 != this);
return provider1;
}
if (canHandleRouting(provider1, backend2.getPath(path2String))) {
+ myPathSpy.accept(path1String, provider1 != this);
return provider1;
}
if (canHandleRouting(provider2, backend1.getPath(path1String))) {
+ myPathSpy.accept(path2String, provider2 != this);
return provider2;
}
diff --git a/platform/eel-impl/BUILD.bazel b/platform/eel-impl/BUILD.bazel
index ec0af69bd3d8..d8f7fd91dc71 100644
--- a/platform/eel-impl/BUILD.bazel
+++ b/platform/eel-impl/BUILD.bazel
@@ -30,4 +30,25 @@ jvm_library(
],
runtime_deps = [":eel-impl_resources"]
)
-### auto-generated section `build intellij.platform.eel.impl` end
\ No newline at end of file
+### auto-generated section `build intellij.platform.eel.impl` end
+
+### auto-generated section `build intellij.platform.eel.impl.benchmarks` start
+load("@rules_java//java:defs.bzl", "java_library")
+load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_test")
+
+jvm_library(
+ name = "eel-impl-benchmarks_test_lib",
+ visibility = ["//visibility:public"],
+ srcs = glob(["benchmarks/**/*.kt", "benchmarks/**/*.java"], allow_empty = True),
+ deps = [
+ "@lib//:jmh-core",
+ "@lib//:jmh-generator-annprocess",
+ "//platform/eel-impl",
+ ]
+)
+
+jvm_test(
+ name = "eel-impl-benchmarks_test",
+ runtime_deps = [":eel-impl-benchmarks_test_lib"]
+)
+### auto-generated section `build intellij.platform.eel.impl.benchmarks` end
\ No newline at end of file
diff --git a/platform/eel-impl/benchmarks/com/intellij/platform/eel/impl/fs/BenchmarkLauncher.java b/platform/eel-impl/benchmarks/com/intellij/platform/eel/impl/fs/BenchmarkLauncher.java
new file mode 100644
index 000000000000..bf43c97d71db
--- /dev/null
+++ b/platform/eel-impl/benchmarks/com/intellij/platform/eel/impl/fs/BenchmarkLauncher.java
@@ -0,0 +1,12 @@
+// 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.platform.eel.impl.fs;
+
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+public final class BenchmarkLauncher {
+ public static void main(String[] args) throws RunnerException {
+ new Runner(new OptionsBuilder().build()).run();
+ }
+}
diff --git a/platform/eel-impl/benchmarks/com/intellij/platform/eel/impl/fs/EelEarlyAccessFilterBenchmark.java b/platform/eel-impl/benchmarks/com/intellij/platform/eel/impl/fs/EelEarlyAccessFilterBenchmark.java
new file mode 100644
index 000000000000..093d8c12daf9
--- /dev/null
+++ b/platform/eel-impl/benchmarks/com/intellij/platform/eel/impl/fs/EelEarlyAccessFilterBenchmark.java
@@ -0,0 +1,52 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.eel.impl.fs;
+
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.math.BigInteger;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Fork(1)
+@Threads(4)
+@Warmup(iterations = 0) // This code affects users since the beginning of the application. Users don't warm up our products.
+public class EelEarlyAccessFilterBenchmark {
+ @State(Scope.Benchmark)
+ public static class BenchmarkState {
+ final EelEarlyAccessFilter filter = new EelEarlyAccessFilter();
+ final String[] pathFixtures;
+
+ public BenchmarkState() {
+ pathFixtures = new String[10];
+ Random random = new Random(31337);
+ for (int i = 0; i < pathFixtures.length; i++) {
+ byte[] bytes = new byte[random.nextInt(1023) + 1];
+ pathFixtures[i] = new BigInteger(bytes).toString(64);
+ }
+ }
+ }
+
+ @Benchmark
+ public void checkOnePathOneTime(BenchmarkState state, Blackhole blackhole) {
+ blackhole.consume(state.filter.check(state.pathFixtures[0]));
+ }
+
+ @Benchmark
+ public void checkOnePathTenTimes(BenchmarkState state, Blackhole blackhole) {
+ for (int ignored = 0; ignored < 10; ignored++) {
+ blackhole.consume(state.filter.check(state.pathFixtures[0]));
+ }
+ }
+
+ @Benchmark
+ public void checkTenPathsOneTime(BenchmarkState state, Blackhole blackhole) {
+ for (int ignored = 0; ignored < 10; ignored++) {
+ for (int i = 0; i < 10; i++) {
+ blackhole.consume(state.filter.check(state.pathFixtures[i]));
+ }
+ }
+ }
+}
diff --git a/platform/eel-impl/intellij.platform.eel.impl.benchmarks.iml b/platform/eel-impl/intellij.platform.eel.impl.benchmarks.iml
new file mode 100644
index 000000000000..0c026263faa2
--- /dev/null
+++ b/platform/eel-impl/intellij.platform.eel.impl.benchmarks.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/platform/eel-impl/resources/intellij.platform.eel.impl.xml b/platform/eel-impl/resources/intellij.platform.eel.impl.xml
index 1dd0ddefa1e6..91d5973ae8c4 100644
--- a/platform/eel-impl/resources/intellij.platform.eel.impl.xml
+++ b/platform/eel-impl/resources/intellij.platform.eel.impl.xml
@@ -6,5 +6,7 @@
serviceImplementation="com.intellij.platform.eel.impl.local.LocalWindowsEelApiImpl"/>
+
+
\ No newline at end of file
diff --git a/platform/eel-impl/src/com/intellij/platform/eel/impl/fs/EelEarlyAccessApplicationActivity.kt b/platform/eel-impl/src/com/intellij/platform/eel/impl/fs/EelEarlyAccessApplicationActivity.kt
new file mode 100644
index 000000000000..d5b83dcf6677
--- /dev/null
+++ b/platform/eel-impl/src/com/intellij/platform/eel/impl/fs/EelEarlyAccessApplicationActivity.kt
@@ -0,0 +1,60 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.eel.impl.fs
+
+import com.intellij.ide.ApplicationActivity
+import com.intellij.openapi.diagnostic.Attachment
+import com.intellij.openapi.diagnostic.logger
+import com.intellij.platform.core.nio.fs.MultiRoutingFileSystem
+import com.intellij.platform.core.nio.fs.MultiRoutingFileSystemProvider
+import org.jetbrains.annotations.VisibleForTesting
+import java.lang.invoke.MethodHandles
+import java.nio.file.FileSystems
+import java.util.function.BiConsumer
+
+internal class EelEarlyAccessApplicationActivity : ApplicationActivity {
+ override suspend fun execute() {
+ val fs = FileSystems.getDefault()
+ if (fs.javaClass.name != MultiRoutingFileSystem::class.java.name) return
+
+ val logger = logger()
+ val filter = EelEarlyAccessFilter()
+
+ MultiRoutingFileSystemProvider.setPathSpy(fs.provider(), BiConsumer { strPath, isDefaultProvider ->
+ if (isDefaultProvider && strPath.startsWith("\\\\wsl")) {
+ if (filter.check(strPath)) {
+ // Q: What happened?
+ // A: Some code tried to access \\wsl.localhost before the initialization of the IJent file system.
+ // Therefore, the code will access the original filesystem of WSL brought by Microsoft,
+ // and the code won't access our polished file system with various workarounds, optimizations, etc.
+ // Also, the code that triggered this error is an obstacle for implementing DevContainers over Eel.
+ //
+ // Q: How to fix it?
+ // A: Call `com.intellij.platform.eel.provider.EelInitialization.runEelInitialization` in advance.
+ // Sometimes it's easy, sometimes you have to rework the UI/UX, sorry for that.
+ //
+ // Q: Why not initialize the IJent filesystem lazily, right here, at this moment?
+ // A: Although this initialization usually takes a second, sometimes it can be significantly slower.
+ // It depends not only on the I/O speed of the machine, but also on personal configurations of the user.
+ // Being called from EDT, this lazy initialization would lead to yet another freeze.
+ logger.error(
+ "Remote file system accessed before Eel initialization. The description is in the source code.",
+ Attachment("path", strPath.toString()),
+ )
+ }
+ }
+ })
+ }
+}
+
+@VisibleForTesting
+class EelEarlyAccessFilter {
+ private val poorMansBloomFilter = Array(1 shl 16) { true }
+ private val varHandle = MethodHandles.arrayElementVarHandle(poorMansBloomFilter::class.java)
+
+ fun check(strPath: String): Boolean {
+ val hash = strPath.hashCode().let {
+ (it and 0xFFFF) xor (it ushr 16 and 0xFFFF)
+ }
+ return varHandle.getAndSet(poorMansBloomFilter, hash, false) as Boolean
+ }
+}
\ No newline at end of file
diff --git a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ApplicationLoader.kt b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ApplicationLoader.kt
index 13b6b7eb3163..ee47d5ed5749 100644
--- a/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ApplicationLoader.kt
+++ b/platform/platform-impl/bootstrap/src/com/intellij/platform/ide/bootstrap/ApplicationLoader.kt
@@ -281,6 +281,7 @@ private val asyncAppListenerAllowListForNonCorePlugin = java.util.Set.of(
"com.intellij.internal.statistic.updater.StatisticsJobsScheduler",
"com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler",
"org.jetbrains.kotlin.idea.base.plugin.K2UnsupportedPluginsNotificationActivity",
+ "com.intellij.platform.eel.impl.fs.EelEarlyAccessApplicationActivity",
)
private fun CoroutineScope.executeAsyncAppInitListeners() {