mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-CR-56509 CancellationCheck is added
Relates to #KT-35135 GitOrigin-RevId: c74d2ca3f6a6c2d1cebc922ee9d903fb5c631d3d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5b0f17f657
commit
451b06bb3e
@@ -0,0 +1,83 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.progress.impl
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
/**
|
||||
* It is used to check if [ProgressManager.checkCanceled] is invoked often enough - at least once per a threshold ms.
|
||||
*
|
||||
* For global usage [CancellationCheck.runWithCancellationCheck] could be used:
|
||||
* - it has to be enabled with a registry key `ide.cancellation.check.enabled`, it is disabled by default
|
||||
* - threshold (in ms) is specified with a registry key `ide.cancellation.check.threshold`, default is 500
|
||||
*/
|
||||
class CancellationCheck private constructor(val thresholdMs: () -> Long, val checkEnabled: () -> Boolean) {
|
||||
|
||||
@TestOnly
|
||||
internal constructor(thresholdMs: Long): this(thresholdMs = { thresholdMs }, checkEnabled = { true })
|
||||
|
||||
private val statusRecord = ThreadLocal.withInitial { CanceledStatusRecord() }
|
||||
private val hook = CoreProgressManager.CheckCanceledHook { indicator ->
|
||||
checkCancellationDiff(statusRecord.get())
|
||||
return@CheckCanceledHook indicator != null
|
||||
}
|
||||
|
||||
private fun checkCancellationDiff(record: CanceledStatusRecord) {
|
||||
if (record.enabled) {
|
||||
val now = System.currentTimeMillis()
|
||||
val diff = now - record.timestamp
|
||||
if (diff > thresholdMs()) {
|
||||
LOG.error("${Thread.currentThread().name} last checkCanceled was $diff ms ago")
|
||||
}
|
||||
record.timestamp = now
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableCancellationTimer(record: CanceledStatusRecord, enabled: Boolean) {
|
||||
val progressManagerImpl = ProgressManager.getInstance() as ProgressManagerImpl
|
||||
|
||||
if (enabled) progressManagerImpl.addCheckCanceledHook(hook) else progressManagerImpl.removeCheckCanceledHook(hook)
|
||||
record.enabled = enabled
|
||||
record.timestamp = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
fun <T> withCancellationCheck(block: () -> T): T {
|
||||
if (!checkEnabled()) return block()
|
||||
|
||||
val record = statusRecord.get()
|
||||
if (record.enabled) return block()
|
||||
|
||||
enableCancellationTimer(record, true)
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
try {
|
||||
checkCancellationDiff(record)
|
||||
}
|
||||
finally {
|
||||
enableCancellationTimer(record,false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class CanceledStatusRecord(var enabled: Boolean = false, var timestamp: Long = System.currentTimeMillis())
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(CancellationCheck::class.java)
|
||||
|
||||
@JvmStatic
|
||||
private val INSTANCE: CancellationCheck =
|
||||
CancellationCheck(
|
||||
thresholdMs = { Registry.intValue("ide.cancellation.check.threshold").toLong() },
|
||||
checkEnabled = { Registry.`is`("ide.cancellation.check.enabled") }
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun <T> runWithCancellationCheck(block: () -> T): T =
|
||||
INSTANCE.withCancellationCheck(block)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.progress.impl;
|
||||
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
|
||||
import com.intellij.testFramework.LightPlatformTestCase;
|
||||
|
||||
public class CancellationCheckTest extends LightPlatformTestCase {
|
||||
|
||||
private final ProgressIndicator myProgressIndicator = new ProgressIndicatorBase();
|
||||
|
||||
public void testNormal() {
|
||||
int period = 10;
|
||||
int times = 10;
|
||||
CancellationCheck cancellation = new CancellationCheck(times * period);
|
||||
|
||||
runWithCheckCancellation(cancellation, period, times, 1);
|
||||
}
|
||||
|
||||
public void testReentrant() {
|
||||
int period = 10;
|
||||
int times = 5;
|
||||
CancellationCheck cancellation = new CancellationCheck(times * period);
|
||||
|
||||
runWithCheckCancellation(cancellation, period, times, 3);
|
||||
}
|
||||
|
||||
public void testExceededThreshold() {
|
||||
CancellationCheck cancellation = new CancellationCheck(1);
|
||||
|
||||
assertThrows(Throwable.class,
|
||||
"AWT-EventQueue-0 last checkCanceled was ",
|
||||
() -> {
|
||||
runWithCheckCancellation(cancellation, 10, 1, 1);
|
||||
});
|
||||
}
|
||||
|
||||
private void runWithCheckCancellation(CancellationCheck cancellation, int period, int times, int depth) {
|
||||
cancellation.withCancellationCheck(() -> {
|
||||
for (int attempt = 0; attempt < times * 2; attempt++) {
|
||||
myProgressIndicator.checkCanceled();
|
||||
try {
|
||||
Thread.sleep(period);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (depth - 1 > 0) {
|
||||
runWithCheckCancellation(cancellation, period, times, depth - 1);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1805,6 +1805,11 @@ ide.io.preemptive.safe.write=false
|
||||
ide.io.preemptive.safe.write.description=Activates more destructive variant of Settings | Appearance & Behavior | System Settings | Use "safe write" - \
|
||||
which deletes and recreates files instead of overwriting them in-place.
|
||||
|
||||
ide.cancellation.check.enabled=false
|
||||
ide.cancellation.check.enabled.description=Enable checkCanceled invocation frequency check
|
||||
ide.cancellation.check.threshold=500
|
||||
ide.cancellation.check.threshold.description=Report error if checkCanceled invoked more than threshold ms ago
|
||||
|
||||
light.edit.file.open.enabled=false
|
||||
light.edit.file.open.enabled.description=Open files in off-project editor instead of creating a temporary project
|
||||
|
||||
|
||||
Reference in New Issue
Block a user