mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-04 17:20:55 +07:00
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:
committed by
intellij-monorepo-bot
parent
b0537e456c
commit
70908d094a
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user