IJPL-194952 Revert "[wsl-eel] IJPL-186144: Get rid of IjentWslNioFsToggler"

This reverts commit dae3a216


(cherry picked from commit b7ba1c026d1bcbf8d51f57a64646ce8d3a1db483)

IJ-MR-167682

GitOrigin-RevId: ea15fa242203ade09290ad23e02f767ca03ab72a
This commit is contained in:
Vladimir Lagunov
2025-07-01 14:58:44 +02:00
committed by intellij-monorepo-bot
parent b0537e456c
commit 70908d094a
12 changed files with 389 additions and 205 deletions

View File

@@ -7,9 +7,10 @@ import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.provider.EelNioBridgeService import com.intellij.platform.eel.provider.EelNioBridgeService
import com.intellij.util.containers.forEachGuaranteed import com.intellij.util.containers.forEachGuaranteed
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.job
import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.TestOnly
import org.jetbrains.annotations.VisibleForTesting import org.jetbrains.annotations.VisibleForTesting
import java.io.Closeable
import java.nio.file.FileSystem import java.nio.file.FileSystem
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
@@ -20,19 +21,13 @@ import kotlin.io.path.pathString
@ApiStatus.Internal @ApiStatus.Internal
@VisibleForTesting @VisibleForTesting
class EelNioBridgeServiceImpl(coroutineScope: CoroutineScope) : EelNioBridgeService { class EelNioBridgeServiceImpl(private val coroutineScope: CoroutineScope) : EelNioBridgeService {
private val multiRoutingFileSystemProvider = FileSystems.getDefault().provider() private val multiRoutingFileSystemProvider = FileSystems.getDefault().provider()
private val rootRegistry = ConcurrentHashMap<EelDescriptor, MutableSet<Path>>() private val rootRegistry = ConcurrentHashMap<EelDescriptor, MutableSet<Path>>()
private val fsRegistry = ConcurrentHashMap<String, FileSystem>() private val fsRegistry = ConcurrentHashMap<String, FileSystem>()
private val idRegistry = ConcurrentHashMap<EelDescriptor, String>() private val idRegistry = ConcurrentHashMap<EelDescriptor, String>()
init {
coroutineScope.coroutineContext.job.invokeOnCompletion {
idRegistry.keys().asSequence().forEach { unregister(it) }
}
}
override fun tryGetEelDescriptor(nioPath: Path): EelDescriptor? { override fun tryGetEelDescriptor(nioPath: Path): EelDescriptor? {
return rootRegistry.entries.asSequence() return rootRegistry.entries.asSequence()
.flatMap { (descriptor, paths) -> paths.map { path -> descriptor to path } } .flatMap { (descriptor, paths) -> paths.map { path -> descriptor to path } }
@@ -93,4 +88,36 @@ class EelNioBridgeServiceImpl(coroutineScope: CoroutineScope) : EelNioBridgeServ
return true return true
} }
@TestOnly
fun temporarilyResetState(): Closeable {
val oldRootRegistry = HashMap(rootRegistry)
val oldFsRegistry = HashMap(fsRegistry)
val oldIdRegistry = HashMap(idRegistry)
for (descriptor in rootRegistry.keys.toList()) {
val roots = rootRegistry.remove(descriptor)!!
for (localRoot in roots) {
fsRegistry.compute(localRoot.toString()) { _, existingFileSystem ->
MultiRoutingFileSystemProvider.computeBackend(multiRoutingFileSystemProvider, localRoot.toString(), false, false) { underlyingProvider, actualFs ->
require(existingFileSystem == actualFs)
null
}
null
}
}
}
idRegistry.clear()
return Closeable {
rootRegistry.putAll(oldRootRegistry)
fsRegistry.putAll(oldFsRegistry)
idRegistry.putAll(oldIdRegistry)
for ((localRootString, fs) in fsRegistry) {
MultiRoutingFileSystemProvider.computeBackend(multiRoutingFileSystemProvider, localRootString, false, false) { _, _ -> fs }
}
}
}
} }

View File

@@ -16,6 +16,5 @@
<orderEntry type="module" module-name="intellij.platform.testFramework" /> <orderEntry type="module" module-name="intellij.platform.testFramework" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" /> <orderEntry type="module" module-name="intellij.platform.util.coroutines" />
<orderEntry type="module" module-name="intellij.platform.ide.impl.wsl" /> <orderEntry type="module" module-name="intellij.platform.ide.impl.wsl" />
<orderEntry type="module" module-name="intellij.platform.execution" />
</component> </component>
</module> </module>

View File

@@ -1,24 +1,17 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:JvmName("WslIjentTestUtil") @file:JvmName("WslIjentTestUtil")
package com.intellij.ijent.testFramework.wsl package com.intellij.ijent.testFramework.wsl
import com.intellij.execution.wsl.WSLDistribution
import com.intellij.execution.wsl.WslIjentManager import com.intellij.execution.wsl.WslIjentManager
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.extensions.BaseExtensionPointName
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.platform.eel.impl.provider.EelNioBridgeServiceImpl import com.intellij.platform.eel.impl.provider.EelNioBridgeServiceImpl
import com.intellij.platform.eel.provider.EelInitialization
import com.intellij.platform.eel.provider.EelNioBridgeService import com.intellij.platform.eel.provider.EelNioBridgeService
import com.intellij.platform.eel.provider.EelProvider
import com.intellij.platform.ide.impl.wsl.ProductionWslIjentManager import com.intellij.platform.ide.impl.wsl.ProductionWslIjentManager
import com.intellij.platform.ide.impl.wsl.WslEelProvider import com.intellij.platform.ide.impl.wsl.ijent.nio.toggle.IjentWslNioFsToggler
import com.intellij.platform.util.coroutines.childScope import com.intellij.platform.util.coroutines.childScope
import com.intellij.testFramework.registerExtension
import com.intellij.testFramework.replaceService import com.intellij.testFramework.replaceService
import com.intellij.util.asDisposable
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.concurrent.CancellationException import java.util.concurrent.CancellationException
@@ -30,17 +23,14 @@ fun replaceProductionWslIjentManager(newServiceScope: CoroutineScope) {
replaceService(WslIjentManager::class.java, ::ProductionWslIjentManager, newServiceScope) replaceService(WslIjentManager::class.java, ::ProductionWslIjentManager, newServiceScope)
} }
suspend fun replaceWslServicesAndRunWslEelInitialization(newServiceScope: CoroutineScope, wsl: WSLDistribution) { fun replaceIjentWslNioFsToggler(newServiceScope: CoroutineScope) {
replaceProductionWslIjentManager(newServiceScope) replaceService(IjentWslNioFsToggler::class.java, ::IjentWslNioFsToggler, newServiceScope)
replaceService(EelNioBridgeService::class.java, ::EelNioBridgeServiceImpl, newServiceScope)
replaceExtension(newServiceScope, EelProvider.EP_NAME, WslEelProvider(newServiceScope))
EelInitialization.runEelInitialization(wsl.getUNCRootPath().toString())
} }
private fun <T : Any> replaceExtension(scope: CoroutineScope, name: BaseExtensionPointName<*>, instance: T) { fun temporarilyResetEelNioBridge(serviceScope: CoroutineScope) {
ApplicationManager.getApplication().apply { val guard = (EelNioBridgeService.getInstanceSync() as EelNioBridgeServiceImpl).temporarilyResetState()
extensionArea.getExtensionPoint<T>(name.name).unregisterExtension(instance.javaClass) serviceScope.coroutineContext.job.invokeOnCompletion {
registerExtension(name, instance, scope.asDisposable()) guard.close()
} }
} }

View File

@@ -310,13 +310,16 @@ internal object SystemHealthMonitor {
} }
} }
private fun checkEelVmOptions() { private suspend fun checkEelVmOptions() {
// TODO: remove this check
if (!WslIjentAvailabilityService.getInstance().useIjentForWslNioFileSystem()) return if (!WslIjentAvailabilityService.getInstance().useIjentForWslNioFileSystem()) return
val changedOptions = MultiRoutingFileSystemVmOptionsSetter.ensureInVmOptions() val changedOptions = MultiRoutingFileSystemVmOptionsSetter.ensureInVmOptions()
when { when {
changedOptions.isEmpty() -> Unit changedOptions.isEmpty() -> {
// Since IjentWslNioFsToggler was moved from the core to the module, it can't be accessed here anymore.
// And probably, it's not required anymore.
// IjentWslNioFsToggler.instanceAsync().enableForAllWslDistributions()
}
PluginManagerCore.isRunningFromSources() || AppMode.isDevServer() -> { PluginManagerCore.isRunningFromSources() || AppMode.isDevServer() -> {
logger<MultiRoutingFileSystemVmOptionsSetter>().warn( logger<MultiRoutingFileSystemVmOptionsSetter>().warn(

View File

@@ -1,6 +1,6 @@
<idea-plugin> <idea-plugin>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<eelProvider implementation="com.intellij.platform.ide.impl.wsl.WslEelProvider" os="windows"/> <eelProvider implementation="com.intellij.platform.ide.impl.wsl.ijent.nio.toggle.IjentWslNioFsToggler$WslEelProvider" os="windows"/>
<applicationService serviceInterface="com.intellij.execution.wsl.WslIjentManager" <applicationService serviceInterface="com.intellij.execution.wsl.WslIjentManager"
serviceImplementation="com.intellij.platform.ide.impl.wsl.ProductionWslIjentManager"/> serviceImplementation="com.intellij.platform.ide.impl.wsl.ProductionWslIjentManager"/>
</extensions> </extensions>

View File

@@ -1,171 +1 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. // 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.ide.impl.wsl
import com.intellij.execution.eel.MultiRoutingFileSystemUtils
import com.intellij.execution.ijent.nio.IjentEphemeralRootAwareFileSystemProvider
import com.intellij.execution.wsl.*
import com.intellij.openapi.components.serviceAsync
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.EelOsFamily
import com.intellij.platform.eel.provider.EelNioBridgeService
import com.intellij.platform.eel.provider.EelProvider
import com.intellij.platform.eel.provider.LocalEelDescriptor
import com.intellij.platform.ide.impl.wsl.ijent.nio.IjentWslNioFileSystemProvider
import com.intellij.platform.ijent.IjentPosixApi
import com.intellij.platform.ijent.community.impl.IjentFailSafeFileSystemPosixApi
import com.intellij.platform.ijent.community.impl.nio.IjentNioFileSystemProvider
import com.intellij.platform.ijent.community.impl.nio.telemetry.TracingFileSystemProvider
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.containers.forEachGuaranteed
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.job
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.NonNls
import java.net.URI
import java.nio.file.FileSystemAlreadyExistsException
import java.nio.file.Path
import java.nio.file.spi.FileSystemProvider
import kotlin.io.path.Path
private val WSLDistribution.roots: Set<String>
get() {
val localRoots = mutableSetOf(getWindowsPath("/"))
localRoots.single().let {
localRoots += it.replace("wsl.localhost", "wsl$")
localRoots += it.replace("wsl$", "wsl.localhost")
}
return localRoots
}
private suspend fun WSLDistribution.getIjent(): IjentPosixApi {
return WslIjentManager.instanceAsync().getIjentApi(this, null, false)
}
@ApiStatus.Internal
class WslEelProvider(private val coroutineScope: CoroutineScope) : EelProvider {
private val providersCache = ContainerUtil.createConcurrentWeakMap<String, FileSystemProvider>()
companion object {
private val LOG = logger<WslEelProvider>()
}
override suspend fun tryInitialize(path: String) {
if (!serviceAsync<WslIjentAvailabilityService>().useIjentForWslNioFileSystem()) {
return
}
if (!MultiRoutingFileSystemUtils.isMultiRoutingFsEnabled) {
return
}
if (!WslPath.isWslUncPath(path)) {
return
}
val allWslDistributions = serviceAsync<WslDistributionManager>().installedDistributions
val path = Path.of(path)
val service = serviceAsync<EelNioBridgeService>()
val descriptor = service.tryGetEelDescriptor(path)
if (descriptor != null && descriptor !== LocalEelDescriptor) {
check(descriptor is WslEelDescriptor)
return
}
for (distro in allWslDistributions) {
val matches =
try {
distro.getWslPath(path) != null
}
catch (_: IllegalArgumentException) {
false
}
if (matches) {
service.registerNioWslFs(distro)
}
}
}
private suspend fun EelNioBridgeService.registerNioWslFs(distro: WSLDistribution) {
val descriptor = distro.getIjent().descriptor as WslEelDescriptor
val ijentFsProvider = TracingFileSystemProvider(IjentNioFileSystemProvider.getInstance())
val ijentUri = URI("ijent", "wsl", "/${distro.id}", null, null)
try {
val ijentFs = IjentFailSafeFileSystemPosixApi(coroutineScope) { distro.getIjent() }
val fs = ijentFsProvider.newFileSystem(ijentUri, IjentNioFileSystemProvider.newFileSystemMap(ijentFs))
coroutineScope.coroutineContext.job.invokeOnCompletion {
fs?.close()
}
}
catch (_: FileSystemAlreadyExistsException) {
// Nothing.
}
descriptor.distribution.roots.forEachGuaranteed { localRoot ->
register(localRoot, descriptor, descriptor.distribution.id, false, false) { underlyingProvider, _ ->
val key = if (Registry.`is`("wsl.use.new.filesystem")) localRoot else distro.id
val fileSystemProvider = providersCache.computeIfAbsent(key) {
if (Registry.`is`("wsl.use.new.filesystem")) {
IjentEphemeralRootAwareFileSystemProvider(
root = Path(localRoot),
ijentFsProvider = ijentFsProvider,
originalFsProvider = TracingFileSystemProvider(underlyingProvider),
// FIXME: is this behavior really correct?
//
// It is known that `originalFs.rootDirectories` always returns all WSL drives.
// Also, it is known that `ijentFs.rootDirectories` returns a single WSL drive,
// which is already mentioned in `originalFs.rootDirectories`.
//
// `ijentFs` is usually represented by `IjentFailSafeFileSystemPosixApi`,
// which launches IJent and the corresponding WSL containers lazily.
//
// This function avoids fetching root directories directly from IJent.
// This way, various UI file trees don't start all WSL containers during loading the file system root.
useRootDirectoriesFromOriginalFs = true,
)
}
else {
IjentWslNioFileSystemProvider(
wslDistribution = distro,
ijentFsProvider = ijentFsProvider,
originalFsProvider = TracingFileSystemProvider(underlyingProvider),
)
}
}
val fileSystem = if (fileSystemProvider is IjentEphemeralRootAwareFileSystemProvider) {
fileSystemProvider.getFileSystem(ijentUri)
}
else {
fileSystemProvider.getFileSystem(distro.getUNCRootPath().toUri())
}
LOG.info("Switching $distro to IJent WSL nio.FS: $fileSystem")
fileSystem
}
}
}
}
data class WslEelDescriptor(val distribution: WSLDistribution) : EelDescriptor {
override val osFamily: EelOsFamily = EelOsFamily.Posix
override val userReadableDescription: @NonNls String = "WSL: ${distribution.presentableName}"
override suspend fun toEelApi(): EelApi {
return distribution.getIjent()
}
override fun equals(other: Any?): Boolean {
return other is WslEelDescriptor && other.distribution.id == distribution.id
}
override fun hashCode(): Int {
return distribution.id.hashCode()
}
}

View File

@@ -8,6 +8,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.IntellijInternalApi import com.intellij.openapi.util.IntellijInternalApi
import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.eel.EelDescriptor import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.ide.impl.wsl.ijent.nio.toggle.WslEelDescriptor
import com.intellij.platform.ijent.spi.IjentConnectionStrategy import com.intellij.platform.ijent.spi.IjentConnectionStrategy
import com.intellij.platform.ijent.spi.IjentDeployingOverShellProcessStrategy import com.intellij.platform.ijent.spi.IjentDeployingOverShellProcessStrategy
import com.intellij.util.io.computeDetached import com.intellij.util.io.computeDetached

View File

@@ -0,0 +1,163 @@
// 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.ide.impl.wsl.ijent.nio.toggle
import com.intellij.execution.wsl.WSLDistribution
import com.intellij.execution.wsl.WslDistributionManager
import com.intellij.execution.wsl.WslIjentManager
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.platform.eel.provider.EelNioBridgeService
import com.intellij.platform.ide.impl.wsl.ijent.nio.IjentWslNioFileSystemProvider
import com.intellij.platform.ijent.IjentPosixApi
import com.intellij.platform.ijent.community.impl.IjentFailSafeFileSystemPosixApi
import com.intellij.platform.ijent.community.impl.nio.IjentNioFileSystemProvider
import com.intellij.platform.ijent.community.impl.nio.telemetry.TracingFileSystemProvider
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.containers.forEachGuaranteed
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.job
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.VisibleForTesting
import java.net.URI
import java.nio.file.FileSystem
import java.nio.file.FileSystemAlreadyExistsException
import java.nio.file.spi.FileSystemProvider
import java.util.concurrent.ConcurrentHashMap
import java.util.function.BiConsumer
private suspend fun WSLDistribution.getIjent(): IjentPosixApi {
return WslIjentManager.instanceAsync().getIjentApi(this, null, false)
}
@ApiStatus.Internal
@VisibleForTesting
class IjentWslNioFsToggleStrategy(
private val coroutineScope: CoroutineScope,
) {
internal val enabledInDistros: MutableMap<WSLDistribution, WslEelDescriptor> = ConcurrentHashMap()
private val providersCache = ContainerUtil.createConcurrentWeakMap<String, IjentWslNioFileSystemProvider>()
init {
coroutineScope.coroutineContext.job.invokeOnCompletion {
unregisterAll()
}
}
fun enableForAllWslDistributions() {
val listener = BiConsumer<Set<WSLDistribution>, Set<WSLDistribution>> { old, new ->
// TODO The code is race prone. Frequent creations and deletions of WSL containers may break the state.
for (distro in new - old) {
handleWslDistributionAddition(distro)
}
for (distro in old - new) {
handleWslDistributionDeletion(distro)
}
}
val wslDistributionManager = WslDistributionManager.getInstance()
wslDistributionManager.addWslDistributionsChangeListener(listener)
coroutineScope.coroutineContext.job.invokeOnCompletion {
wslDistributionManager.removeWslDistributionsChangeListener(listener)
}
for (distro in wslDistributionManager.installedDistributions) {
handleWslDistributionAddition(distro)
}
}
private fun handleWslDistributionAddition(distro: WSLDistribution) {
switchToIjentFs(distro)
}
private fun handleWslDistributionDeletion(distro: WSLDistribution) {
val descriptor = enabledInDistros.remove(distro)
if (descriptor != null) {
recomputeEel(descriptor) { _, actualFs ->
actualFs
}
}
}
fun switchToIjentFs(distro: WSLDistribution) {
val ijentFsProvider = TracingFileSystemProvider(IjentNioFileSystemProvider.getInstance())
val descriptor = runBlockingMaybeCancellable { distro.getIjent() }.descriptor as WslEelDescriptor
enabledInDistros[distro] = descriptor
try {
val ijentFs = IjentFailSafeFileSystemPosixApi(coroutineScope) { distro.getIjent() }
ijentFsProvider.newFileSystem(
URI("ijent", "wsl", "/${distro.id}", null, null),
IjentNioFileSystemProvider.newFileSystemMap(ijentFs),
)
}
catch (_: FileSystemAlreadyExistsException) {
// Nothing.
}
recomputeEel(descriptor) { underlyingProvider, _ ->
val fileSystemProvider = providersCache.computeIfAbsent(distro.id) {
IjentWslNioFileSystemProvider(
wslDistribution = distro,
ijentFsProvider = ijentFsProvider,
originalFsProvider = TracingFileSystemProvider(underlyingProvider),
)
}
val fileSystem = fileSystemProvider.getFileSystem(distro.getUNCRootPath().toUri())
LOG.info("Switching $distro to IJent WSL nio.FS: $fileSystem")
fileSystem
}
}
fun switchToTracingWsl9pFs(descriptor: WslEelDescriptor) {
recomputeEel(descriptor) { underlyingProvider, previousFs ->
LOG.info("Switching $descriptor to the original file system but with tracing")
try {
previousFs?.close()
}
catch (_: UnsupportedOperationException) {
// It is expected that the default file system always throws that exception on calling close(),
// but for the sake of following contracts, this method is nonetheless called.
}
TracingFileSystemProvider(underlyingProvider).getLocalFileSystem()
}
}
fun unregisterAll() {
val service = EelNioBridgeService.getInstanceSync()
enabledInDistros.entries.forEachGuaranteed { (_, descriptor) ->
service.unregister(descriptor)
}
enabledInDistros.clear()
}
}
private fun FileSystemProvider.getLocalFileSystem(): FileSystem = getFileSystem(URI.create("file:/"))
private val LOG = logger<IjentWslNioFsToggleStrategy>()
private val WSLDistribution.roots: Set<String>
get() {
val localRoots = mutableSetOf(getWindowsPath("/"))
localRoots.single().let {
localRoots += it.replace("wsl.localhost", "wsl$")
localRoots += it.replace("wsl$", "wsl.localhost")
}
return localRoots
}
private fun recomputeEel(
descriptor: WslEelDescriptor,
action: (underlyingProvider: FileSystemProvider, previousFs: FileSystem?) -> FileSystem?,
) {
val service = EelNioBridgeService.getInstanceSync()
descriptor.distribution.roots.forEachGuaranteed { localRoot ->
service.register(localRoot, descriptor, descriptor.distribution.id, false, false, action)
}
}

View File

@@ -0,0 +1,173 @@
// 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.ide.impl.wsl.ijent.nio.toggle
import com.intellij.diagnostic.VMOptions
import com.intellij.execution.wsl.*
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.components.serviceAsync
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.core.nio.fs.MultiRoutingFileSystemProvider
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.EelOsFamily
import com.intellij.platform.eel.provider.EelProvider
import com.intellij.platform.ide.impl.wsl.ijent.nio.toggle.IjentWslNioFsToggler.WslEelProvider
import kotlinx.coroutines.*
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.TestOnly
import org.jetbrains.annotations.VisibleForTesting
import java.io.BufferedReader
import java.nio.file.FileSystems
import java.nio.file.Path
import kotlin.io.path.bufferedReader
/**
* This service, along with listeners inside it, enables and disables access to WSL drives through IJent.
*/
@Internal
@Service
@VisibleForTesting
class IjentWslNioFsToggler(private val coroutineScope: CoroutineScope) {
companion object {
suspend fun instanceAsync(): IjentWslNioFsToggler = serviceAsync()
fun instance(): IjentWslNioFsToggler = service()
}
val isAvailable: Boolean get() = strategy != null
fun enableForAllWslDistributions() {
logErrorIfNotWindows()
strategy?.enableForAllWslDistributions()
}
@TestOnly
fun switchToIjentFs(distro: WSLDistribution) {
logErrorIfNotWindows()
strategy ?: error("Not available")
strategy.switchToIjentFs(distro)
}
@TestOnly
fun switchToTracingWsl9pFs(descriptor: WslEelDescriptor) {
logErrorIfNotWindows()
strategy ?: error("Not available")
strategy.switchToTracingWsl9pFs(descriptor)
}
@TestOnly
fun unregisterAll() {
logErrorIfNotWindows()
strategy ?: error("Not available")
strategy.unregisterAll()
}
private fun logErrorIfNotWindows() {
if (!SystemInfo.isWindows) {
thisLogger().error("${javaClass.name} should be requested only on Windows")
}
}
// TODO Move to ijent.impl?
internal class WslEelProvider : EelProvider {
suspend fun getApiByDistribution(distro: WSLDistribution): EelApi {
val enabledDistros = serviceAsync<IjentWslNioFsToggler>().strategy?.enabledInDistros
if (enabledDistros == null || distro !in enabledDistros) {
throw IllegalStateException("IJent is not enabled in $distro")
}
return WslIjentManager.getInstance().getIjentApi(distro, null, rootUser = false)
}
override suspend fun tryInitialize(path: String) = tryInitializeEelOnWsl(path)
}
private val strategy = run {
val defaultProvider = FileSystems.getDefault().provider()
when {
!WslIjentAvailabilityService.getInstance().useIjentForWslNioFileSystem() -> null
defaultProvider.javaClass.name == MultiRoutingFileSystemProvider::class.java.name -> {
IjentWslNioFsToggleStrategy(coroutineScope)
}
else -> {
val vmOptions = runCatching {
VMOptions.getUserOptionsFile()?.bufferedReader()?.use<BufferedReader, String> { it.readText() }
?: "<null>"
}.getOrElse<String, String> { err -> err.stackTraceToString() }
val systemProperties = runCatching {
System.getProperties().entries.joinToString("\n") { (k, v) -> "$k=$v" }
}.getOrElse<String, String> { err -> err.stackTraceToString() }
val message = "The default filesystem ${FileSystems.getDefault()} is not ${MultiRoutingFileSystemProvider::class.java}"
logger<IjentWslNioFsToggler>().warn("$message\nVM Options:\n$vmOptions\nSystem properties:\n$systemProperties")
null
}
}
}
}
private suspend fun tryInitializeEelOnWsl(path: String) {
if (!WslIjentAvailabilityService.getInstance().useIjentForWslNioFileSystem()) {
return
}
if (!WslPath.isWslUncPath(path)) {
return
}
val ijentWslNioFsToggler = IjentWslNioFsToggler.instanceAsync()
coroutineScope {
launch {
ijentWslNioFsToggler.enableForAllWslDistributions()
}
val allWslDistributions = async(Dispatchers.IO) {
serviceAsync<WslDistributionManager>().installedDistributions
}
val path = Path.of(path)
for (distro in allWslDistributions.await()) {
val matches =
try {
distro.getWslPath(path) != null
}
catch (_: IllegalArgumentException) {
false
}
if (matches) {
launch {
serviceAsync<WslIjentManager>().getIjentApi(distro, null, false)
}
}
}
}
}
data class WslEelDescriptor(val distribution: WSLDistribution) : EelDescriptor {
override val osFamily: EelOsFamily = EelOsFamily.Posix
override val userReadableDescription: @NonNls String = "WSL: ${distribution.presentableName}"
override suspend fun toEelApi(): EelApi {
return WslEelProvider().getApiByDistribution(distribution)
}
override fun equals(other: Any?): Boolean {
return other is WslEelDescriptor && other.distribution.id == distribution.id
}
override fun hashCode(): Int {
return distribution.id.hashCode()
}
}

View File

@@ -1,5 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Internal @ApiStatus.Internal
package com.intellij.platform.ide.impl.wsl; package com.intellij.platform.ide.impl.wsl.ijent.nio.toggle;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;

View File

@@ -2,12 +2,10 @@
package com.intellij.execution.eel package com.intellij.execution.eel
import com.intellij.diagnostic.VMOptions import com.intellij.diagnostic.VMOptions
import com.intellij.execution.wsl.WslIjentAvailabilityService
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.platform.core.nio.fs.MultiRoutingFileSystem import com.intellij.platform.core.nio.fs.MultiRoutingFileSystem
import com.intellij.platform.core.nio.fs.MultiRoutingFileSystemProvider import com.intellij.platform.core.nio.fs.MultiRoutingFileSystemProvider
import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.ApiStatus
import java.io.BufferedReader
import java.nio.file.FileSystems import java.nio.file.FileSystems
import kotlin.io.path.bufferedReader import kotlin.io.path.bufferedReader

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. // 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.execution.wsl package com.intellij.execution.wsl
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger