[debugger-rd] IJPL-160146: Use new withKernel API

GitOrigin-RevId: 698cd83aac3469010f9b6a1d16ab99f0b511621e
This commit is contained in:
Nikolay Rykunov
2024-08-26 11:40:01 +02:00
committed by intellij-monorepo-bot
parent 1a38e580a4
commit 686bcc08a7
5 changed files with 156 additions and 13 deletions

View File

@@ -0,0 +1,138 @@
// 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.openapi.project.impl
import com.intellij.openapi.observable.util.whenDisposed
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.getOrCreateUserData
import com.intellij.platform.kernel.KernelService
import com.intellij.platform.kernel.util.flushLatestChange
import com.intellij.platform.kernel.withKernel
import com.jetbrains.rhizomedb.*
import fleet.kernel.DurableEntityType
import fleet.kernel.change
import fleet.kernel.kernel
import fleet.kernel.shared
import fleet.util.UID
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
val KERNEL_PROJECT_ID = Key.create<UID>("ProjectImpl.KERNEL_PROJECT_ID")
/**
* Represents a project entity that can be shared between backend and frontends.
* The entity is created on project initialization before any services and components are loaded.
*
* To convert a project to the entity use [asEntity]
*/
@ApiStatus.Internal
data class ProjectEntity(override val eid: EID) : Entity {
var projectId: UID by ProjectId
companion object: DurableEntityType<ProjectEntity>(ProjectEntity::class.java.name, "com.intellij", ::ProjectEntity) {
val ProjectId = requiredValue("projectId", UID.serializer(), Indexing.UNIQUE)
}
}
data class LocalProjectEntity(override val eid: EID) : Entity {
val sharedEntity: ProjectEntity by ProjectEntity
val project: Project by Project
companion object: EntityType<LocalProjectEntity>(LocalProjectEntity::class, ::LocalProjectEntity) {
val ProjectEntity = requiredRef<ProjectEntity>("sharedEntity", RefFlags.CASCADE_DELETE_BY)
val Project = requiredTransient<Project>("project")
}
}
/**
* Converts a given project to its corresponding [ProjectEntity].
*
* The method has to be called in a kernel context - see [com.intellij.platform.kernel.KernelService.kernelCoroutineContext]
*
* @return The [ProjectEntity] instance associated with the provided project,
* or null if no such entity is found
*/
@ApiStatus.Internal
fun Project.asEntity(): ProjectEntity? {
return LocalProjectEntity.all().singleOrNull { it.project == this }?.sharedEntity
}
/**
* Converts a given project entity to its corresponding [Project].
*
* The method has to be called in a kernel context - see [com.intellij.platform.kernel.KernelService.kernelCoroutineContext]
*
* @return The [Project] instance associated with the provided entity,
* or null if no such project is found (for example, if [ProjectEntity] doesn't exist anymore).
*/
@ApiStatus.Internal
fun ProjectEntity.asProject(): Project? {
return LocalProjectEntity.all().singleOrNull { it.sharedEntity == this }?.project
}
internal suspend fun Project.createEntity() = withKernel {
val project = this@createEntity
val projectId = project.getOrCreateUserData(KERNEL_PROJECT_ID) { UID.random() }
// TODO it shouldn't be here
change {
shared {
register(ProjectEntity)
}
}
change {
val projectEntity = shared {
/*
This check is added to ensure that only one ProjectEntity is going to be created in split mode.
Two entities are possible due to a different flow in creating a project in split mode.
First, a project is created on the backend (ProjectEntity is created at the same time).
Then a signal about project creation is sent to the frontend via RD protocol.
At the same time, the shared part of Rhizome DB (where ProjectEntity is stored) sends the changes to the frontend.
Events which are coming via RD protocol are not synced with events coming via Rhizome DB.
So it can happen that while on the backend the signal is sent strictly after ProjectEntity creation,
on the frontend the signal can be received before there is ProjectEntity available in DB.
If it happens that the entity has not been found and the frontend creates a new one, Rhizome DB will perform a "rebase"
which basically re-invokes the whole "change" block either on the backend or the frontend side.
*/
val existing = ProjectEntity.all().singleOrNull { it.projectId == projectId }
if (existing != null) {
existing
}
else {
ProjectEntity.new {
it[ProjectEntity.ProjectId] = projectId
}
}
}
LocalProjectEntity.new {
it[LocalProjectEntity.ProjectEntity] = projectEntity
it[LocalProjectEntity.Project] = project
}
}
project.whenDisposed {
runBlockingMaybeCancellable {
removeProjectEntity(project)
}
}
}
private suspend fun removeProjectEntity(project: Project) = withKernel {
change {
shared {
project.asEntity()?.delete()
}
}
// Removing ProjectEntity and LocalProjectEntity is the last operation in most of the tests
// Without calling "flushLatestChange" kernel keeps the project, which causes "testProjectLeak" failures
kernel().flushLatestChange()
}

View File

@@ -106,7 +106,7 @@ private class BackendDebuggerValueLookupHintsRemoteApi : XDebuggerValueLookupHin
} }
private suspend fun getProjectFromUID(projectId: UID): Project? { private suspend fun getProjectFromUID(projectId: UID): Project? {
return withContext(KernelService.kernelCoroutineContext()) { return withKernel {
val projectEntity = entity<ProjectEntity, UID>(ProjectEntity.ProjectId, projectId) val projectEntity = entity<ProjectEntity, UID>(ProjectEntity.ProjectId, projectId)
projectEntity?.asProject() projectEntity?.asProject()
} }

View File

@@ -2,6 +2,7 @@
package com.intellij.xdebugger.impl.frontend.evaluate.quick.common package com.intellij.xdebugger.impl.frontend.evaluate.quick.common
import com.intellij.platform.kernel.KernelService import com.intellij.platform.kernel.KernelService
import com.intellij.platform.kernel.withKernel
import com.intellij.xdebugger.impl.evaluate.XDebuggerValueLookupHideHintsRequestEntity import com.intellij.xdebugger.impl.evaluate.XDebuggerValueLookupHideHintsRequestEntity
import com.intellij.xdebugger.impl.evaluate.XDebuggerValueLookupListeningStartedEntity import com.intellij.xdebugger.impl.evaluate.XDebuggerValueLookupListeningStartedEntity
import fleet.kernel.change import fleet.kernel.change
@@ -15,7 +16,7 @@ import kotlinx.coroutines.withContext
internal fun subscribeForDebuggingStart(cs: CoroutineScope, onStartListening: () -> Unit) { internal fun subscribeForDebuggingStart(cs: CoroutineScope, onStartListening: () -> Unit) {
cs.launch(Dispatchers.Default) { cs.launch(Dispatchers.Default) {
withContext(KernelService.kernelCoroutineContext()) { withKernel {
change { change {
shared { shared {
register(XDebuggerValueLookupListeningStartedEntity) register(XDebuggerValueLookupListeningStartedEntity)
@@ -32,7 +33,7 @@ internal fun subscribeForDebuggingStart(cs: CoroutineScope, onStartListening: ()
internal fun subscribeForValueHintHideRequest(cs: CoroutineScope, onHintHidden: () -> Unit) { internal fun subscribeForValueHintHideRequest(cs: CoroutineScope, onHintHidden: () -> Unit) {
cs.launch(Dispatchers.Default) { cs.launch(Dispatchers.Default) {
withContext(KernelService.kernelCoroutineContext()) { withKernel {
change { change {
shared { shared {
register(XDebuggerValueLookupHideHintsRequestEntity) register(XDebuggerValueLookupHideHintsRequestEntity)
@@ -43,10 +44,12 @@ internal fun subscribeForValueHintHideRequest(cs: CoroutineScope, onHintHidden:
onHintHidden() onHintHidden()
} }
// TODO: support multiple clients by clientId // TODO: support multiple clients by clientId
cs.launch(Dispatchers.Default + KernelService.kernelCoroutineContext()) { cs.launch(Dispatchers.Default) {
change { withKernel {
shared { change {
entity.delete() shared {
entity.delete()
}
} }
} }
} }

View File

@@ -9,6 +9,7 @@ import com.intellij.openapi.project.impl.asEntity
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.platform.kernel.KernelService import com.intellij.platform.kernel.KernelService
import com.intellij.platform.kernel.withKernel
import com.intellij.platform.rpc.RemoteApiProviderService import com.intellij.platform.rpc.RemoteApiProviderService
import com.intellij.platform.util.coroutines.childScope import com.intellij.platform.util.coroutines.childScope
import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint
@@ -51,20 +52,20 @@ open class ValueLookupManagerQuickEvaluateHandler : QuickEvaluateHandler() {
val hintCoroutineScope = editor.childCoroutineScope("ValueLookupManagerValueHintParentScope") val hintCoroutineScope = editor.childCoroutineScope("ValueLookupManagerValueHintParentScope")
val hint: Deferred<AbstractValueHint?> = coroutineScope.async(Dispatchers.IO) { val hint: Deferred<AbstractValueHint?> = coroutineScope.async(Dispatchers.IO) {
withContext(KernelService.kernelCoroutineContext()) { withKernel {
val remoteApi = RemoteApiProviderService.resolve(remoteApiDescriptor<XDebuggerValueLookupHintsRemoteApi>()) val remoteApi = RemoteApiProviderService.resolve(remoteApiDescriptor<XDebuggerValueLookupHintsRemoteApi>())
val projectEntity = project.asEntity() val projectEntity = project.asEntity()
if (projectEntity == null) { if (projectEntity == null) {
return@withContext null return@withKernel null
} }
val projectId = projectEntity.projectId val projectId = projectEntity.projectId
val editorId = editor.editorId() val editorId = editor.editorId()
val canShowHint = remoteApi.canShowHint(projectId, editorId, offset, type) val canShowHint = remoteApi.canShowHint(projectId, editorId, offset, type)
if (!canShowHint) { if (!canShowHint) {
return@withContext null return@withKernel null
} }
val hint = ValueLookupManagerValueHint(hintCoroutineScope, project, projectId, editor, point, type, offset) val hint = ValueLookupManagerValueHint(hintCoroutineScope, project, projectId, editor, point, type, offset)
return@withContext hint return@withKernel hint
} }
} }
val hintPromise = hint.asCompletableFuture().asCancellablePromise() val hintPromise = hint.asCompletableFuture().asCancellablePromise()

View File

@@ -6,6 +6,7 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.platform.kernel.KernelService import com.intellij.platform.kernel.KernelService
import com.intellij.platform.kernel.withKernel
import com.jetbrains.rhizomedb.EID import com.jetbrains.rhizomedb.EID
import com.jetbrains.rhizomedb.Entity import com.jetbrains.rhizomedb.Entity
import fleet.kernel.DurableEntityType import fleet.kernel.DurableEntityType
@@ -47,7 +48,7 @@ class ValueLookupManagerController(private val project: Project, private val cs:
return return
} }
cs.launch(Dispatchers.Main) { cs.launch(Dispatchers.Main) {
withContext(KernelService.kernelCoroutineContext()) { withKernel {
change { change {
shared { shared {
register(XDebuggerValueLookupListeningStartedEntity) register(XDebuggerValueLookupListeningStartedEntity)
@@ -71,7 +72,7 @@ class ValueLookupManagerController(private val project: Project, private val cs:
*/ */
fun hideHint() { fun hideHint() {
cs.launch(Dispatchers.Main) { cs.launch(Dispatchers.Main) {
withContext(KernelService.kernelCoroutineContext()) { withKernel {
change { change {
shared { shared {
register(XDebuggerValueLookupHideHintsRequestEntity) register(XDebuggerValueLookupHideHintsRequestEntity)