Skip to content

Commit fd8f289

Browse files
committed
[utbot-rd]
Fix deadlock when IDEA process under readlock asks Engine process, which then asks IDEA process for isCancalled which also tries to take readlock, but some write action occured and readlock for isCancelled cant be taken Fix #1213 Fix #1539
1 parent ab1a302 commit fd8f289

File tree

4 files changed

+66
-50
lines changed

4 files changed

+66
-50
lines changed

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt

+15-13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.intellij.ide.fileTemplates.FileTemplateUtil
88
import com.intellij.ide.fileTemplates.JavaTemplateUtil
99
import com.intellij.ide.highlighter.JavaFileType
1010
import com.intellij.openapi.application.ApplicationManager
11+
import com.intellij.openapi.application.readAction
1112
import com.intellij.openapi.application.runReadAction
1213
import com.intellij.openapi.application.runWriteAction
1314
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
@@ -225,7 +226,7 @@ object CodeGenerationController {
225226
.doInspections(AnalysisScope(model.project))
226227
}
227228

228-
private fun proceedTestReport(proc: EngineProcess, model: GenerateTestsModel) = runReadAction {
229+
private fun proceedTestReport(proc: EngineProcess, model: GenerateTestsModel) {
229230
try {
230231
// Parametrized tests are not supported in tests report yet
231232
// TODO JIRA:1507
@@ -675,8 +676,7 @@ object CodeGenerationController {
675676
run(THREAD_POOL, indicator, "Rendering test code") {
676677
val (generatedTestsCode, utilClassKind) = try {
677678
val paramNames = try {
678-
DumbService.getInstance(model.project)
679-
.runReadActionInSmartMode(Computable { proc.findMethodParamNames(classUnderTest, classMethods) })
679+
proc.findMethodParamNames(classUnderTest, classMethods)
680680
} catch (e: Exception) {
681681
logger.warn(e) { "Cannot find method param names for ${classUnderTest.name}" }
682682
reportsCountDown.countDown()
@@ -833,24 +833,26 @@ object CodeGenerationController {
833833
}
834834

835835

836-
private fun eventLogMessage(project: Project): String? {
837-
if (ToolWindowManager.getInstance(project).getToolWindow("Event Log") != null)
838-
return """
836+
private fun eventLogMessage(project: Project): String? = runReadAction {
837+
return@runReadAction if (ToolWindowManager.getInstance(project).getToolWindow("Event Log") != null)
838+
"""
839839
<a href="${TestReportUrlOpeningListener.prefix}${TestReportUrlOpeningListener.eventLogSuffix}">See details in Event Log</a>.
840840
""".trimIndent()
841-
return null
841+
else null
842842
}
843843

844844
private fun showTestsReport(proc: EngineProcess, model: GenerateTestsModel) {
845845
val (notifyMessage, statistics, hasWarnings) = proc.generateTestsReport(model, eventLogMessage(model.project))
846846

847-
if (hasWarnings) {
848-
WarningTestsReportNotifier.notify(notifyMessage)
849-
} else {
850-
TestsReportNotifier.notify(notifyMessage)
851-
}
847+
runReadAction {
848+
if (hasWarnings) {
849+
WarningTestsReportNotifier.notify(notifyMessage)
850+
} else {
851+
TestsReportNotifier.notify(notifyMessage)
852+
}
852853

853-
statistics?.let { DetailsTestsReportNotifier.notify(it) }
854+
statistics?.let { DetailsTestsReportNotifier.notify(it) }
855+
}
854856
}
855857

856858
@Suppress("unused")

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt

+22-20
Original file line numberDiff line numberDiff line change
@@ -192,28 +192,30 @@ object UtTestsDialogProcessor {
192192
}
193193

194194
val (methods, className) = process.executeWithTimeoutSuspended {
195+
var canonicalName = ""
196+
var srcMethods: List<MemberInfo> = emptyList()
195197
DumbService.getInstance(project)
196-
.runReadActionInSmartMode(Computable {
197-
val canonicalName = srcClass.canonicalName
198-
val classId = process.obtainClassId(canonicalName)
199-
psi2KClass[srcClass] = classId
200-
val srcMethods = if (model.extractMembersFromSrcClasses) {
201-
val chosenMethods =
202-
model.selectedMembers.filter { it.member is PsiMethod }
203-
val chosenNestedClasses =
204-
model.selectedMembers.mapNotNull { it.member as? PsiClass }
205-
chosenMethods + chosenNestedClasses.flatMap {
206-
it.extractClassMethodsIncludingNested(false)
198+
.runReadActionInSmartMode(Computable {
199+
canonicalName = srcClass.canonicalName
200+
srcMethods = if (model.extractMembersFromSrcClasses) {
201+
val chosenMethods =
202+
model.selectedMembers.filter { it.member is PsiMethod }
203+
val chosenNestedClasses =
204+
model.selectedMembers.mapNotNull { it.member as? PsiClass }
205+
chosenMethods + chosenNestedClasses.flatMap {
206+
it.extractClassMethodsIncludingNested(false)
207+
}
208+
} else {
209+
srcClass.extractClassMethodsIncludingNested(false)
207210
}
208-
} else {
209-
srcClass.extractClassMethodsIncludingNested(false)
210-
}
211-
process.findMethodsInClassMatchingSelected(
212-
classId,
213-
srcMethods
214-
) to srcClass.name
215-
})
216-
}
211+
})
212+
val classId = process.obtainClassId(canonicalName)
213+
psi2KClass[srcClass] = classId
214+
process.findMethodsInClassMatchingSelected(
215+
classId,
216+
srcMethods
217+
) to srcClass.name
218+
}
217219

218220
if (methods.isEmpty()) {
219221
logger.error { "No methods matching selected found in class $className." }

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt

+23-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.utbot.intellij.plugin.process
22

33
import com.intellij.ide.plugins.cl.PluginClassLoader
4+
import com.intellij.openapi.application.runReadAction
45
import com.intellij.openapi.project.DumbService
56
import com.intellij.openapi.project.Project
7+
import com.intellij.openapi.util.Computable
68
import com.intellij.psi.PsiMethod
79
import com.intellij.psi.impl.file.impl.JavaFileManager
810
import com.intellij.psi.search.GlobalSearchScope
@@ -30,8 +32,7 @@ import org.utbot.instrumentation.util.KryoHelper
3032
import org.utbot.intellij.plugin.UtbotBundle
3133
import org.utbot.intellij.plugin.models.GenerateTestsModel
3234
import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener
33-
import org.utbot.intellij.plugin.util.assertIsNonDispatchThread
34-
import org.utbot.intellij.plugin.util.assertIsReadAccessAllowed
35+
import org.utbot.intellij.plugin.util.assertReadAccessNotAllowed
3536
import org.utbot.intellij.plugin.util.methodDescription
3637
import org.utbot.rd.*
3738
import org.utbot.rd.exceptions.InstantProcessDeathException
@@ -164,7 +165,8 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
164165
private val sourceFindingStrategies = ConcurrentHashMap<Long, SourceFindingStrategy>()
165166

166167
fun setupUtContext(classpathForUrlsClassloader: List<String>) {
167-
engineModel.setupUtContext.start(lifetime, SetupContextParams(classpathForUrlsClassloader))
168+
assertReadAccessNotAllowed()
169+
engineModel.setupUtContext.startBlocking(SetupContextParams(classpathForUrlsClassloader))
168170
}
169171

170172
private fun computeSourceFileByClass(params: ComputeSourceFileByClassArguments): String =
@@ -185,6 +187,8 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
185187
jdkInfo: JdkInfo,
186188
isCancelled: (Unit) -> Boolean
187189
) {
190+
assertReadAccessNotAllowed()
191+
188192
engineModel.isCancelled.set(handler = isCancelled)
189193
instrumenterAdapterModel.computeSourceFileByClass.set(handler = this::computeSourceFileByClass)
190194

@@ -194,19 +198,18 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
194198
dependencyPaths,
195199
JdkInfo(jdkInfo.path.pathString, jdkInfo.version)
196200
)
197-
engineModel.createTestGenerator.start(lifetime, params)
201+
engineModel.createTestGenerator.startBlocking(params)
198202
}
199203

200204
fun obtainClassId(canonicalName: String): ClassId {
201-
assertIsNonDispatchThread()
205+
assertReadAccessNotAllowed()
202206
return kryoHelper.readObject(engineModel.obtainClassId.startBlocking(canonicalName))
203207
}
204208

205209
fun findMethodsInClassMatchingSelected(clazzId: ClassId, srcMethods: List<MemberInfo>): List<ExecutableId> {
206-
assertIsNonDispatchThread()
207-
assertIsReadAccessAllowed()
210+
assertReadAccessNotAllowed()
208211

209-
val srcDescriptions = srcMethods.map { it.methodDescription() }
212+
val srcDescriptions = runReadAction { srcMethods.map { it.methodDescription() } }
210213
val rdDescriptions = srcDescriptions.map { MethodDescription(it.name, it.containingClass, it.parameterTypes) }
211214
val binaryClassId = kryoHelper.writeObject(clazzId)
212215
val arguments = FindMethodsInClassMatchingSelectedArguments(binaryClassId, rdDescriptions)
@@ -216,10 +219,13 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
216219
}
217220

218221
fun findMethodParamNames(classId: ClassId, methods: List<MemberInfo>): Map<ExecutableId, List<String>> {
219-
assertIsNonDispatchThread()
220-
assertIsReadAccessAllowed()
222+
assertReadAccessNotAllowed()
221223

222-
val bySignature = methods.associate { it.methodDescription() to it.paramNames() }
224+
val bySignature = executeWithTimeoutSuspended {
225+
DumbService.getInstance(project).runReadActionInSmartMode(Computable {
226+
methods.associate { it.methodDescription() to it.paramNames() }
227+
})
228+
}
223229
val arguments = FindMethodParamNamesArguments(
224230
kryoHelper.writeObject(classId),
225231
kryoHelper.writeObject(bySignature)
@@ -254,7 +260,7 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
254260
fuzzingValue: Double,
255261
searchDirectory: String
256262
): RdTestGenerationResult {
257-
assertIsNonDispatchThread()
263+
assertReadAccessNotAllowed()
258264
val params = GenerateParams(
259265
mockInstalled,
260266
staticsMockingIsConfigured,
@@ -291,7 +297,7 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
291297
enableTestsTimeout: Boolean,
292298
testClassPackageName: String
293299
): Pair<String, UtilClassKind?> {
294-
assertIsNonDispatchThread()
300+
assertReadAccessNotAllowed()
295301
val params = RenderParams(
296302
testSetsId,
297303
kryoHelper.writeObject(classUnderTest),
@@ -353,7 +359,7 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
353359
generatedTestsCode: String,
354360
sourceFindingStrategy: SourceFindingStrategy
355361
): String {
356-
assertIsNonDispatchThread()
362+
assertReadAccessNotAllowed()
357363

358364
val params = WriteSarifReportArguments(testSetsId, reportFilePath.pathString, generatedTestsCode)
359365

@@ -362,7 +368,7 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
362368
}
363369

364370
fun generateTestsReport(model: GenerateTestsModel, eventLogMessage: String?): Triple<String, String?, Boolean> {
365-
assertIsNonDispatchThread()
371+
assertReadAccessNotAllowed()
366372

367373
val forceMockWarning = UtbotBundle.takeIf(
368374
"test.report.force.mock.warning",
@@ -411,10 +417,12 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
411417

412418
fun <T> executeWithTimeoutSuspended(block: () -> T): T {
413419
try {
420+
assertReadAccessNotAllowed()
414421
protocol.synchronizationModel.suspendTimeoutTimer.startBlocking(true)
415422
return block()
416423
}
417424
finally {
425+
assertReadAccessNotAllowed()
418426
protocol.synchronizationModel.suspendTimeoutTimer.startBlocking(false)
419427
}
420428
}

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IdeaThreadingUtil.kt

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ fun assertIsWriteThread() {
1010
ApplicationManager.getApplication().isWriteThread()
1111
}
1212

13-
fun assertIsReadAccessAllowed() {
13+
fun assertReadAccessAllowed() {
1414
ApplicationManager.getApplication().assertReadAccessAllowed()
1515
}
1616

17-
fun assertIsWriteAccessAllowed() {
17+
fun assertWriteAccessAllowed() {
1818
ApplicationManager.getApplication().assertWriteAccessAllowed()
1919
}
2020

2121
fun assertIsNonDispatchThread() {
2222
ApplicationManager.getApplication().assertIsNonDispatchThread()
23+
}
24+
25+
fun assertReadAccessNotAllowed() {
26+
ApplicationManager.getApplication().assertReadAccessNotAllowed()
2327
}

0 commit comments

Comments
 (0)