[cancellation] IJPL-236372: Do not allow resurrection of job-bridging progress indicators

(cherry picked from commit a591d2cc33015eab4c75b4227060b8854ad3edb4)

IJ-CR-193221

GitOrigin-RevId: c4ed067bdac37147c7fc2e84b130253b44f2bde6
This commit is contained in:
Konstantin Nisht
2026-03-02 18:02:20 +00:00
committed by intellij-monorepo-bot
parent b148cf89ee
commit 09df581bea
5 changed files with 68 additions and 8 deletions
@@ -0,0 +1,47 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.progress
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.annotations.ApiStatus
/**
* A base class for progress indicators that depend on an instance of a job.
*
* The motivation is the following:
* ```kotlin
* coroutineToIndicator { indicator ->
* runProcess({}, indicator)
* }
* ```
* Here, [ProgressManager.runProcess] calls [ProgressIndicator.start] on `indicator`,
* which would lead to removal of cancellations status for [EmptyProgressIndicator]. To avoid it, we reimplement [EmptyProgressIndicatorBase] directly
*/
@ApiStatus.Internal
@Suppress("UsagesOfObsoleteApi")
internal abstract class BridgeJobIndicatorBase(modalityState: ModalityState): EmptyProgressIndicatorBase(modalityState), StandardProgressIndicator {
companion object {
private val PLACEHOLDER: Throwable = Throwable(
"Dummy throwable that indicates cancellation of EmptyProgressIndicator.\nSet `ide.rich.cancellation.traces` to `true` to get real origin of cancellation.")
}
@Volatile
private var cancellationCause: Throwable? = null
override fun cancel() {
cancellationCause = if (Registry.`is`("ide.rich.cancellation.traces", false)) {
Throwable("Origin of cancellation of $this")
}
else {
PLACEHOLDER
}
}
override fun isCanceled(): Boolean {
return cancellationCause != null
}
override fun getCancellationCause(): Throwable? {
return cancellationCause.takeIf { it != PLACEHOLDER }
}
}
@@ -3,6 +3,7 @@ package com.intellij.openapi.progress;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.util.registry.Registry;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.ApiStatus.Obsolete;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -42,8 +43,14 @@ public class EmptyProgressIndicator extends EmptyProgressIndicatorBase implement
ProgressManager.canceled(this);
}
final @Nullable Throwable getCancellationCause() {
return myCancellationRequester;
@ApiStatus.Internal
@Override
protected final @Nullable Throwable getCancellationCause() {
if (myCancellationRequester != PLACEHOLDER) {
return myCancellationRequester;
} else {
return null;
}
}
@Override
@@ -3,8 +3,10 @@ package com.intellij.openapi.progress;
import com.intellij.diagnostic.PluginException;
import com.intellij.openapi.application.ModalityState;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.ApiStatus.Obsolete;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
@@ -72,14 +74,19 @@ public abstract class EmptyProgressIndicatorBase implements ProgressIndicator {
public final void checkCanceled() {
if (isCanceled() && myNonCancelableSectionCount == 0 && !Cancellation.isInNonCancelableSection()) {
ProcessCanceledException e = new ProcessCanceledException();
if (this instanceof EmptyProgressIndicator) {
Throwable cause = Objects.requireNonNull(((EmptyProgressIndicator)this).getCancellationCause());
e.addSuppressed(cause);
Throwable cancellationCause = getCancellationCause();
if (cancellationCause != null) {
e.addSuppressed(cancellationCause);
}
throw e;
}
}
@ApiStatus.Internal
protected @Nullable Throwable getCancellationCause() {
return null;
}
@Override
public void setText(String text) { }
@@ -7,8 +7,7 @@ import com.intellij.platform.util.progress.RawProgressReporter
internal class RawProgressReporterIndicator(
private val reporter: RawProgressReporter,
contextModality: ModalityState,
) : EmptyProgressIndicator(contextModality) {
) : BridgeJobIndicatorBase(contextModality) {
override fun setText(text: String?) {
reporter.text(text)
}
@@ -558,7 +558,7 @@ private fun <T> contextToIndicator(ctx: CoroutineContext, action: () -> T): T {
* We keep this class as an inheritor of [EmptyProgressIndicator] for ease of debugging -- if one sees an instance of this class,
* it means that the currently used indicator depends on some job.
*/
private class JobDependentIndicator(modalityState: ModalityState): EmptyProgressIndicator(modalityState)
private class JobDependentIndicator(modalityState: ModalityState): BridgeJobIndicatorBase(modalityState)
@Throws(CancellationException::class)
@Internal