Skip to content

Commit 56b6772

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 fd84cd8 commit 56b6772

File tree

4 files changed

+69
-52
lines changed

4 files changed

+69
-52
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

+26-17
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,14 +32,14 @@ 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.signature
3637
import org.utbot.rd.*
3738
import org.utbot.rd.exceptions.InstantProcessDeathException
3839
import org.utbot.rd.generated.SettingForResult
3940
import org.utbot.rd.generated.SettingsModel
4041
import org.utbot.rd.generated.settingsModel
42+
import org.utbot.rd.generated.synchronizationModel
4143
import org.utbot.rd.loggers.UtRdKLoggerFactory
4244
import org.utbot.sarif.SourceFindingStrategy
4345
import java.io.File
@@ -163,7 +165,8 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
163165
private val sourceFindingStrategies = ConcurrentHashMap<Long, SourceFindingStrategy>()
164166

165167
fun setupUtContext(classpathForUrlsClassloader: List<String>) {
166-
engineModel.setupUtContext.start(lifetime, SetupContextParams(classpathForUrlsClassloader))
168+
assertReadAccessNotAllowed()
169+
engineModel.setupUtContext.startBlocking(SetupContextParams(classpathForUrlsClassloader))
167170
}
168171

169172
private fun computeSourceFileByClass(params: ComputeSourceFileByClassArguments): String =
@@ -184,6 +187,8 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
184187
jdkInfo: JdkInfo,
185188
isCancelled: (Unit) -> Boolean
186189
) {
190+
assertReadAccessNotAllowed()
191+
187192
engineModel.isCancelled.set(handler = isCancelled)
188193
instrumenterAdapterModel.computeSourceFileByClass.set(handler = this::computeSourceFileByClass)
189194

@@ -193,19 +198,18 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
193198
dependencyPaths,
194199
JdkInfo(jdkInfo.path.pathString, jdkInfo.version)
195200
)
196-
engineModel.createTestGenerator.start(lifetime, params)
201+
engineModel.createTestGenerator.startBlocking(params)
197202
}
198203

199204
fun obtainClassId(canonicalName: String): ClassId {
200-
assertIsNonDispatchThread()
205+
assertReadAccessNotAllowed()
201206
return kryoHelper.readObject(engineModel.obtainClassId.startBlocking(canonicalName))
202207
}
203208

204209
fun findMethodsInClassMatchingSelected(clazzId: ClassId, srcMethods: List<MemberInfo>): List<ExecutableId> {
205-
assertIsNonDispatchThread()
206-
assertIsReadAccessAllowed()
210+
assertReadAccessNotAllowed()
207211

208-
val srcSignatures = srcMethods.map { it.signature() }
212+
val srcSignatures = runReadAction { srcMethods.map { it.signature() } }
209213
val rdSignatures = srcSignatures.map { Signature(it.name, it.parameterTypes) }
210214
val binaryClassId = kryoHelper.writeObject(clazzId)
211215
val arguments = FindMethodsInClassMatchingSelectedArguments(binaryClassId, rdSignatures)
@@ -215,10 +219,13 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
215219
}
216220

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

221-
val bySignature = methods.associate { it.signature() to it.paramNames() }
224+
val bySignature = executeWithTimeoutSuspended {
225+
DumbService.getInstance(project).runReadActionInSmartMode(Computable {
226+
methods.associate { it.signature() to it.paramNames() }
227+
})
228+
}
222229
val arguments = FindMethodParamNamesArguments(
223230
kryoHelper.writeObject(classId),
224231
kryoHelper.writeObject(bySignature)
@@ -253,7 +260,7 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
253260
fuzzingValue: Double,
254261
searchDirectory: String
255262
): RdTestGenerationResult {
256-
assertIsNonDispatchThread()
263+
assertReadAccessNotAllowed()
257264
val params = GenerateParams(
258265
mockInstalled,
259266
staticsMockingIsConfigured,
@@ -290,7 +297,7 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
290297
enableTestsTimeout: Boolean,
291298
testClassPackageName: String
292299
): Pair<String, UtilClassKind?> {
293-
assertIsNonDispatchThread()
300+
assertReadAccessNotAllowed()
294301
val params = RenderParams(
295302
testSetsId,
296303
kryoHelper.writeObject(classUnderTest),
@@ -352,7 +359,7 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
352359
generatedTestsCode: String,
353360
sourceFindingStrategy: SourceFindingStrategy
354361
): String {
355-
assertIsNonDispatchThread()
362+
assertReadAccessNotAllowed()
356363

357364
val params = WriteSarifReportArguments(testSetsId, reportFilePath.pathString, generatedTestsCode)
358365

@@ -361,7 +368,7 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
361368
}
362369

363370
fun generateTestsReport(model: GenerateTestsModel, eventLogMessage: String?): Triple<String, String?, Boolean> {
364-
assertIsNonDispatchThread()
371+
assertReadAccessNotAllowed()
365372

366373
val forceMockWarning = UtbotBundle.takeIf(
367374
"test.report.force.mock.warning",
@@ -410,11 +417,13 @@ class EngineProcess private constructor(val project: Project, rdProcess: Process
410417

411418
fun <T> executeWithTimeoutSuspended(block: () -> T): T {
412419
try {
413-
synchronizationModel.suspendTimeoutTimer.startBlocking(true)
420+
assertReadAccessNotAllowed()
421+
protocol.synchronizationModel.suspendTimeoutTimer.startBlocking(true)
414422
return block()
415423
}
416424
finally {
417-
synchronizationModel.suspendTimeoutTimer.startBlocking(false)
425+
assertReadAccessNotAllowed()
426+
protocol.synchronizationModel.suspendTimeoutTimer.startBlocking(false)
418427
}
419428
}
420429
}

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)