IJ-CR-149809 [ijent] IJPL-161967: Initialize Eel right before Workspace model

Since the creation of WSM involves access to project files, we must ensure that Eel is up at this point.
Also fixes IJPL-171263, IJPL-171949, IJPL-172074

(cherry picked from commit 986812336cdbca288deeb5124f51a959182f53a4)

GitOrigin-RevId: c30d7a1673b630bd386e9a42914a3a4f8f6f5f7e
This commit is contained in:
Konstantin Nisht
2024-11-21 16:28:13 +01:00
committed by intellij-monorepo-bot
parent b803acd45f
commit 17cf754719
7 changed files with 114 additions and 112 deletions

View File

@@ -9,6 +9,7 @@ import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.impl.local.LocalPosixEelApiImpl
import com.intellij.platform.eel.impl.local.LocalWindowsEelApiImpl
import com.intellij.platform.util.coroutines.forEachConcurrent
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
@@ -31,11 +32,23 @@ suspend fun Path.getEelApi(): EelApi {
return eels.firstOrNull() ?: if (SystemInfo.isWindows) LocalWindowsEelApiImpl() else LocalPosixEelApiImpl()
}
object EelInitialization {
suspend fun runEelInitialization(project: Project) {
val eels = EP_NAME.extensionList
eels.forEachConcurrent { eelProvider ->
eelProvider.tryInitialize(project)
}
}
}
fun Path.getEelApiBlocking() = runBlockingMaybeCancellable { getEelApi() }
@ApiStatus.Internal
interface EelProvider {
suspend fun getEelApi(path: Path): EelApi?
suspend fun tryInitialize(project: Project)
}
private val EP_NAME = ExtensionPointName<EelProvider>("com.intellij.eelProvider")

View File

@@ -0,0 +1,28 @@
// 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.execution.eel
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.impl.ProjectServiceContainerInitializedListener
import com.intellij.platform.diagnostic.telemetry.impl.span
import com.intellij.platform.eel.provider.EelInitialization
import org.jetbrains.annotations.ApiStatus.Internal
/**
* During the process of project initialization, the IDE interacts with the file system where the project is located on.
* It happens, for example, during the loading of Workspace Model cache.
* Before the first such interaction, we must ensure that the file system is accessible to the IDE.
* Since Eel is responsible for it, it must run *very* early.
*
* On the other hand, we should not access non-local environments excessively. The process of initialization may require IO requests
* which could severely hinder the performance of IDE startup.
* It means that the suitable way to initialize Eel is right before the initialization of a project (when we can decide if we should access the environment),
* and not earlier.
*/
@Internal
class EelProjectServiceInitializer : ProjectServiceContainerInitializedListener {
override suspend fun execute(project: Project, workspaceIndexReady: () -> Unit) {
span("eel initialization") {
EelInitialization.runEelInitialization(project)
}
}
}

View File

@@ -1,75 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.wsl.ijent.nio.toggle
import com.intellij.execution.wsl.WSLDistribution
import com.intellij.execution.wsl.WslDistributionManager
import com.intellij.execution.wsl.WslIjentAvailabilityService
import com.intellij.execution.wsl.WslIjentManager
import com.intellij.openapi.components.serviceAsync
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.modules
import com.intellij.openapi.project.rootManager
import com.intellij.openapi.startup.ProjectActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/**
* Starts the IJent if a project on WSL is opened.
*
* At the moment of writing this string,
* this class was just an optimization handler that speeds up sometimes the first request to the IJent.
* It was not necessary for running the IDE.
*
* See also [IjentWslFileSystemApplicationActivity].
*/
internal class IjentInProjectStarter : ProjectActivity {
override suspend fun execute(project: Project): Unit = coroutineScope {
if (!WslIjentAvailabilityService.getInstance().useIjentForWslNioFileSystem()) {
return@coroutineScope
}
val ijentWslNioFsToggler = IjentWslNioFsToggler.instanceAsync()
if (!ijentWslNioFsToggler.isAvailable) {
return@coroutineScope
}
val allWslDistributions = async(Dispatchers.IO) {
serviceAsync<WslDistributionManager>().installedDistributions
}
val relatedWslDistributions = hashSetOf<WSLDistribution>()
for (module in project.modules) {
for (contentRoot in module.rootManager.contentRoots) {
val path =
try {
contentRoot.toNioPath()
}
catch (_: UnsupportedOperationException) {
continue
}
for (distro in allWslDistributions.await()) {
val matches =
try {
distro.getWslPath(path) != null
}
catch (_: IllegalArgumentException) {
false
}
if (matches) {
relatedWslDistributions += distro
}
}
}
}
for (distro in relatedWslDistributions) {
launch {
serviceAsync<WslIjentManager>().getIjentApi(distro, project, false)
}
}
}
}

View File

@@ -1,29 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.wsl.ijent.nio.toggle
import com.intellij.execution.wsl.WslIjentAvailabilityService
import com.intellij.ide.ApplicationActivity
/**
* This activity registers IJent file systems as early as possible.
*
* It's important to register them before opening projects, because:
* * The project itself may be located on an IJent filesystem.
* * There could have been initialization races during loading the project.
*
* See also [IjentInProjectStarter].
*/
class IjentWslFileSystemApplicationActivity : ApplicationActivity {
override suspend fun execute() {
if (!WslIjentAvailabilityService.getInstance().useIjentForWslNioFileSystem()) {
return
}
val ijentWslNioFsToggler = IjentWslNioFsToggler.instanceAsync()
if (!ijentWslNioFsToggler.isAvailable) {
return
}
ijentWslNioFsToggler.enableForAllWslDistributions()
}
}

View File

@@ -4,8 +4,10 @@ package com.intellij.execution.wsl.ijent.nio.toggle
import com.intellij.diagnostic.VMOptions
import com.intellij.execution.eel.EelApiWithPathsMapping
import com.intellij.execution.wsl.WSLDistribution
import com.intellij.execution.wsl.WslDistributionManager
import com.intellij.execution.wsl.WslIjentAvailabilityService
import com.intellij.execution.wsl.WslIjentManager
import com.intellij.execution.wsl.WslPath
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
@@ -13,11 +15,16 @@ import com.intellij.openapi.components.serviceAsync
import com.intellij.openapi.diagnostic.Attachment
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
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.provider.EelProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.TestOnly
import org.jetbrains.annotations.VisibleForTesting
@@ -86,6 +93,15 @@ class IjentWslNioFsToggler(private val coroutineScope: CoroutineScope) {
)
}
}
/**
* Starts the IJent if a project on WSL is opened.
*
* At the moment of writing this string,
* this class was just an optimization handler that speeds up sometimes the first request to the IJent.
* It was not necessary for running the IDE.
*/
override suspend fun tryInitialize(project: Project) = tryInitializeEelOnWsl(project)
}
private val strategy = run {
@@ -123,4 +139,50 @@ class IjentWslNioFsToggler(private val coroutineScope: CoroutineScope) {
}
}
}
}
}
private suspend fun tryInitializeEelOnWsl(project: Project) = coroutineScope {
if (project.isDefault) {
return@coroutineScope
}
if (!WslIjentAvailabilityService.getInstance().useIjentForWslNioFileSystem()) {
return@coroutineScope
}
val ijentWslNioFsToggler = IjentWslNioFsToggler.instanceAsync()
if (!ijentWslNioFsToggler.isAvailable) {
return@coroutineScope
}
val projectFile = project.projectFilePath
check(projectFile != null) { "Impossible: project is not default, but it does not have project file" }
val path = Path.of(projectFile)
if (!WslPath.isWslUncPath(path.toString())) {
return@coroutineScope
}
launch {
ijentWslNioFsToggler.enableForAllWslDistributions()
}
val allWslDistributions = async(Dispatchers.IO) {
serviceAsync<WslDistributionManager>().installedDistributions
}
for (distro in allWslDistributions.await()) {
val matches =
try {
distro.getWslPath(path) != null
}
catch (_: IllegalArgumentException) {
false
}
if (matches) {
launch {
serviceAsync<WslIjentManager>().getIjentApi(distro, project, false)
}
}
}
}

View File

@@ -6,7 +6,7 @@
<projectServiceContainerInitializedListener
id="moduleBridgeLoaderService"
implementation="com.intellij.workspaceModel.ide.impl.legacyBridge.module.ModuleBridgeLoaderService" order="first"/>
implementation="com.intellij.workspaceModel.ide.impl.legacyBridge.module.ModuleBridgeLoaderService"/>
<projectService serviceInterface="com.intellij.openapi.module.AutomaticModuleUnloader"
serviceImplementation="com.intellij.openapi.module.impl.AutomaticModuleUnloaderImpl" overrides="true"/>
<projectService serviceInterface="com.intellij.openapi.roots.ProjectRootManager"

View File

@@ -131,7 +131,10 @@
<applicationService serviceInterface="com.intellij.openapi.keymap.KeymapManager"
serviceImplementation="com.intellij.openapi.keymap.impl.KeymapManagerImpl"/>
<eelProvider implementation="com.intellij.execution.wsl.ijent.nio.toggle.IjentWslNioFsToggler$WslEelProvider"/>
<eelProvider implementation="com.intellij.execution.wsl.ijent.nio.toggle.IjentWslNioFsToggler$WslEelProvider" os="windows"/>
<projectServiceContainerInitializedListener implementation="com.intellij.execution.eel.EelProjectServiceInitializer"
order="before moduleBridgeLoaderService"/>
<applicationService serviceInterface="com.intellij.openapi.project.ProjectManager"
serviceImplementation="com.intellij.openapi.project.impl.ProjectManagerImpl"
@@ -831,7 +834,8 @@
<projectService serviceInterface="com.intellij.execution.RunManager"
serviceImplementation="com.intellij.execution.impl.RunManagerImpl"/>
<projectServiceContainerInitializedListener implementation="com.intellij.execution.impl.ProjectRunConfigurationInitializer"/>
<projectServiceContainerInitializedListener implementation="com.intellij.execution.impl.ProjectRunConfigurationInitializer"
order="after moduleBridgeLoaderService"/>
<projectFileScanner implementation="com.intellij.execution.impl.RunConfigurationInArbitraryFileScanner"/>
<vfs.asyncListener implementation="com.intellij.execution.impl.RCInArbitraryFileListener"/>
<editorNotificationProvider implementation="com.intellij.execution.impl.RunConfigEditorNotificationProvider"/>
@@ -1842,10 +1846,9 @@
<applicationService serviceImplementation="com.intellij.openapi.wm.impl.customFrameDecorations.frameButtons.LinuxIconThemeConfiguration" headlessImplementation=""
preload="notHeadless" os="linux"/>
<postStartupActivity implementation="com.intellij.execution.wsl.ijent.nio.toggle.IjentInProjectStarter"
os="windows"/>
<applicationActivity implementation="com.intellij.execution.wsl.ijent.nio.toggle.IjentWslFileSystemApplicationActivity"
os="windows"/>
<projectServiceInitializer.essential
implementation="com.intellij.execution.eel.EelProjectServiceInitializer"
order="before moduleBridgeLoaderService"/>
<applicationActivity implementation="com.intellij.ide.SwingTooltipManagerCustomizer"/>
<registryKey key="rhizome.progress"
defaultValue="false"