[threading] IJPL-195357: Make installThreadContext a real higher-order function

GitOrigin-RevId: c3a674d64040c3457fea17bc137b4a9e819eef8a
This commit is contained in:
Konstantin Nisht
2025-07-07 12:44:10 +00:00
committed by intellij-monorepo-bot
parent 7b5d58b273
commit 3c8cb9147e
34 changed files with 129 additions and 91 deletions
@@ -81,7 +81,7 @@ internal class PlatformActivityTrackerService(private val scope: CoroutineScope)
fun <T> trackConfigurationActivityBlocking(kind: ActivityKey, action: () -> T): T {
val currentContext = currentThreadContext()
return withObservationTracker(kind) { observationTracker ->
installThreadContext(currentContext + observationTracker, true).use {
installThreadContext(currentContext + observationTracker, true) {
action()
}
}
@@ -38,7 +38,7 @@ interface SideEffectGuard {
@JvmStatic
fun <T> computeWithAllowedSideEffectsBlocking(effects: EnumSet<EffectType>, action: () -> T): T {
val context = currentThreadContext()
return installThreadContext(context + AllowedSideEffectsElement(effects), replace = true).use {
return installThreadContext(context + AllowedSideEffectsElement(effects), replace = true) {
action()
}
}
@@ -160,7 +160,7 @@ private fun <T> runBlockingCancellable(allowOrphan: Boolean, compensateParalleli
private fun getLockContext(currentThreadContext: CoroutineContext): Pair<CoroutineContext, AccessToken> {
val parallelize = with(ApplicationManager.getApplication()) {
installThreadContext(currentThreadContext).use {
installThreadContext(currentThreadContext) {
isReadAccessAllowed
}
}
@@ -303,7 +303,7 @@ suspend fun <T> blockingContextScope(action: () -> T): T {
fun <T> withCurrentThreadCoroutineScopeBlocking(action: () -> T): Pair<T, Job> {
val currentContext = currentThreadContext()
val checkpoint = getFixThreadScopeElements(currentContext)
return installThreadContext(currentContext + checkpoint, true).use {
return installThreadContext(currentContext + checkpoint, true) {
val actionResult = try {
action()
}
@@ -428,7 +428,7 @@ fun CoroutineContext.prepareForInstallation(): CoroutineContext = this.minusKey(
@Throws(ProcessCanceledException::class)
internal fun <T> blockingContextInner(currentContext: CoroutineContext, action: () -> T): T {
val context = currentContext.prepareForInstallation()
return installThreadContext(context).use {
return installThreadContext(context) {
action()
}
}
@@ -84,8 +84,8 @@ class CodeInsightContextManagerImpl(
override fun <Result> performCodeInsightSession(context: CodeInsightContext, block: CodeInsightSession.() -> Result): Result {
val session = CodeInsightSessionImpl(context)
installThreadContext(currentThreadContext() + CodeInsightSessionElement(session)).use {
return block(session)
return installThreadContext(currentThreadContext() + CodeInsightSessionElement(session)) {
block(session)
}
}
@@ -476,9 +476,10 @@ public final class ProgressRunner<R> {
childContext.runInChildContext(() -> {
CoroutineContext effectiveContext =
ThreadContext.currentThreadContext().plus(asContextElement(progressIndicator.getModalityState()).plus(sharedPermit));
try (AccessToken ignored = ThreadContext.installThreadContext(effectiveContext, true)) {
ThreadContext.installThreadContext(effectiveContext, true, () -> {
runnable.run();
}
return Unit.INSTANCE;
});
});
};
switch (myThreadToUse) {
@@ -94,12 +94,21 @@ open class ExecutionManagerImpl(private val project: Project, coroutineScope: Co
return currentThreadContext()[EnvDataContextElement]?.dataContext
}
@Suppress("unused") // used in 3rd party plugin
@Internal
fun withEnvironmentDataContext(dataContext: DataContext?): AccessToken {
val context = currentThreadContext()
return installThreadContext(context + EnvDataContextElement(dataContext), true)
}
@Internal
fun <T> withEnvironmentDataContext(dataContext: DataContext?, action: () -> T): T {
val context = currentThreadContext()
return installThreadContext(context + EnvDataContextElement(dataContext), true).use {
action()
}
}
private class EnvDataContextElement(val dataContext: DataContext?) : CoroutineContext.Element, IntelliJContextElement {
companion object : CoroutineContext.Key<EnvDataContextElement>
@@ -731,7 +740,7 @@ open class ExecutionManagerImpl(private val project: Project, coroutineScope: Co
@ApiStatus.Internal
fun executeConfiguration(environment: ExecutionEnvironment, showSettings: Boolean, assignNewId: Boolean = true) {
withEnvironmentDataContext(environment.dataContext).use {
withEnvironmentDataContext(environment.dataContext) {
val runnerAndConfigurationSettings = environment.runnerAndConfigurationSettings
val project = environment.project
val runner = environment.runner
@@ -741,14 +750,14 @@ open class ExecutionManagerImpl(private val project: Project, coroutineScope: Co
handleExecutionError(environment, ExecutionException(
ProgramRunnerUtil.getCannotRunOnErrorMessage(environment.runProfile, environment.executionTarget)))
processNotStarted(environment, null)
return
return@withEnvironmentDataContext
}
if (!DumbService.isDumb(project)) {
if (showSettings && runnerAndConfigurationSettings.isEditBeforeRun) {
if (!RunDialog.editConfiguration(environment, ExecutionBundle.message("dialog.title.edit.configuration", 0))) {
processNotStarted(environment, null)
return
return@withEnvironmentDataContext
}
editConfigurationUntilSuccess(environment, assignNewId)
}
@@ -777,7 +786,7 @@ open class ExecutionManagerImpl(private val project: Project, coroutineScope: Co
.expireWith(this)
.submit(AppExecutorUtil.getAppExecutorService())
}
return
return@withEnvironmentDataContext
}
}
@@ -359,7 +359,7 @@ class RunnerAndConfigurationSettingsImpl @JvmOverloads constructor(
var warning = ReadAction.nonBlocking<RuntimeConfigurationException?> {
try {
ExecutionManagerImpl.withEnvironmentDataContext(dataContext).use {
ExecutionManagerImpl.withEnvironmentDataContext(dataContext) {
configuration.checkConfiguration()
}
}
@@ -1397,7 +1397,7 @@ class NestedLocksThreadingSupport : ThreadingSupport {
finally {
// non-cancellable section here because we need to prohibit prompt cancellation of lock acquisition in this `finally`
// otherwise the outer release in `runWriteIntentReadAction` would fail with NPE
installThreadContext(currentThreadContext().minusKey(Job), true).use {
installThreadContext(currentThreadContext().minusKey(Job), true) {
state.acquireWriteIntentPermit()
}
}
@@ -1,7 +1,6 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.concurrency;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationUtil;
import com.intellij.openapi.diagnostic.Logger;
@@ -397,9 +396,16 @@ public final class JobLauncherImpl extends JobLauncher {
result[0] = true;
break;
}
try (AccessToken ignored = ThreadContext.installThreadContext(myContext, true)) {
ProgressManager.checkCanceled();
if (!thingProcessor.process(element)) {
try {
T finalElement = element;
boolean shouldBreak = ThreadContext.installThreadContext(myContext, true, () -> {
ProgressManager.checkCanceled();
if (!thingProcessor.process(finalElement)) {
return true;
}
return false;
});
if (shouldBreak) {
break;
}
}
@@ -794,7 +794,7 @@ class IdeEventQueue private constructor() : EventQueue() {
// the manual call of the former event's dispatch() is required here because EventQueue.invokeAndWait() expects
// that the invocation event's notifier is signaled and isDispatched() == true.
// If not dispatch the original event, it hangs forever
installThreadContext(captured).use {
installThreadContext(captured) {
event.dispatch()
}
})
@@ -1159,7 +1159,7 @@ open class ActionManagerImpl protected constructor(private val coroutineScope: C
ClientId.coroutineContext() +
ActionContextElement.create(actionId, event.place, event.inputEvent, component)
val coroutineContext2 = coroutineContext + ThreadScopeCheckpoint(coroutineContext) // permit `currentThreadCoroutineScope` inside
installThreadContext(coroutineContext2.minusKey(ContinuationInterceptor), replace = true).use { _ ->
installThreadContext(coroutineContext2.minusKey(ContinuationInterceptor), replace = true) {
SlowOperations.startSection(SlowOperations.ACTION_PERFORM).use { _ ->
runnable.run()
}
@@ -53,8 +53,6 @@ import com.intellij.ui.ComponentUtil;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.util.*;
import com.intellij.util.concurrency.*;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.concurrency.annotations.RequiresWriteLock;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.Topic;
import com.intellij.util.ui.EDT;
@@ -69,7 +67,6 @@ import org.jetbrains.annotations.*;
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
@@ -567,10 +564,10 @@ public final class ApplicationImpl extends ClientAwareComponentManager implement
new Runnable() {
@Override
public void run() {
try (AccessToken ignored = ThreadContext.installThreadContext(
ThreadContext.currentThreadContext().plus(asContextElement(modalityState)), true)) {
ThreadContext.installThreadContext(ThreadContext.currentThreadContext().plus(asContextElement(modalityState)), true, () -> {
runIntendedWriteActionOnCurrentThread(runnable);
}
return Unit.INSTANCE;
});
}
@Override
@@ -481,9 +481,9 @@ public final class NonBlockingReadActionImpl<T> implements NonBlockingReadAction
try {
boolean computationSuccessful;
if (AppExecutorUtil.propagateContext()) {
try (AccessToken ignored = ThreadContext.installThreadContext(myChildContext.getContext(), true)) {
computationSuccessful = attemptComputation();
}
computationSuccessful = ThreadContext.installThreadContext(myChildContext.getContext(), true, () -> {
return attemptComputation();
});
} else {
computationSuccessful = attemptComputation();
}
@@ -519,9 +519,10 @@ public final class NonBlockingReadActionImpl<T> implements NonBlockingReadAction
else {
context = ThreadContext.currentThreadContext();
}
try (AccessToken ignored = ThreadContext.installThreadContext(context, true)) {
ThreadContext.installThreadContext(context, true, () -> {
attemptComputation();
}
return Unit.INSTANCE;
});
if (isDone()) {
if (isCancelled()) {
@@ -748,9 +749,10 @@ public final class NonBlockingReadActionImpl<T> implements NonBlockingReadAction
if (isSucceeded()) { // in case when another thread managed to cancel it just before `setResult`
try {
if (AppExecutorUtil.propagateContext()) {
try (AccessToken ignored = ThreadContext.installThreadContext(myChildContext.getContext(), false)) {
ThreadContext.installThreadContext(myChildContext.getContext(), false, () -> {
builder.myUiThreadAction.accept(result);
}
return Unit.INSTANCE;
});
} else {
builder.myUiThreadAction.accept(result);
}
@@ -21,7 +21,7 @@ internal fun <X> cancellableReadActionInternal(ctx: CoroutineContext, action: ()
// A child Job is started to be externally cancellable by a write action without cancelling the current Job.
val readJob = Job(parent = ctx[Job])
return try {
installThreadContext(ctx.prepareForInstallation() + readJob).use {
installThreadContext(ctx.prepareForInstallation() + readJob) {
var resultRef: Value<X>? = null
val application = ApplicationManagerEx.getApplicationEx()
val cancellation = CannotReadException.jobCancellation(readJob)
@@ -443,9 +443,10 @@ public class DialogWrapperPeerImpl extends DialogWrapperPeer {
? new Pair<>(EmptyCoroutineContext.INSTANCE, emptyFunction)
: IntelliJLockingUtil.getGlobalThreadingSupport().getPermitAsContextElement(ThreadContext.currentThreadContext(), true);
lockContextWrapper = (r) -> {
try (AccessToken ignored = ThreadContext.installThreadContext(pair.getFirst(), true)) {
ThreadContext.installThreadContext(pair.getFirst(), true, () -> {
r.run();
}
return Unit.INSTANCE;
});
};
lockCleanup = pair.getSecond();
}
@@ -631,7 +631,7 @@ private object WindowCloseListener : WindowAdapter() {
if (app != null && !app.isDisposed) {
// The project closing process is also subject to cancellation checks.
// Here we run the closing process in the scope of the application, so that the user gets the chance to abort a project closing process.
installThreadContext(service<CoreUiCoroutineScopeHolder>().coroutineScope.coroutineContext).use {
installThreadContext(service<CoreUiCoroutineScopeHolder>().coroutineScope.coroutineContext) {
frameHelper.windowClosing(project)
}
}
@@ -12,7 +12,7 @@ class ThreadContextTest {
@Test
fun `reset context`() {
assertNull(currentThreadContextOrNull())
installThreadContext(EmptyCoroutineContext).use {
installThreadContext(EmptyCoroutineContext) {
assertNotNull(currentThreadContextOrNull())
resetThreadContext() {
assertNull(currentThreadContextOrNull())
@@ -25,17 +25,17 @@ class ThreadContextTest {
@Test
fun `replace context`() {
val outerElement1 = TestElement("outer1")
installThreadContext(outerElement1).use {
installThreadContext(outerElement1) {
assertSame(currentThreadContext(), outerElement1)
val innerElement1 = TestElement("inner1")
installThreadContext(innerElement1, replace = true).use {
installThreadContext(innerElement1, replace = true) {
assertSame(currentThreadContext(), innerElement1)
}
assertSame(currentThreadContext(), outerElement1)
val innerElement2 = TestElement2("inner2")
installThreadContext(innerElement2, replace = true).use {
installThreadContext(innerElement2, replace = true) {
assertSame(currentThreadContext(), innerElement2)
}
assertSame(currentThreadContext(), outerElement1)
@@ -1,6 +1,7 @@
// 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.execution.wsl;
import com.intellij.concurrency.ThreadContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
@@ -87,9 +88,7 @@ public final class WslDistributionSafeNullableLazyValueTest {
return CompletableFuture
.supplyAsync(
() -> {
try (var ignored = installThreadContext(coroutineContext, true)) {
return lazyValue.getValue();
}
return ThreadContext.installThreadContext(coroutineContext, true, () -> lazyValue.getValue());
},
AppExecutorUtil.getAppExecutorService())
.get(WAIT_TIMEOUT_IN_SECONDS, SECONDS);
@@ -232,11 +231,11 @@ public final class WslDistributionSafeNullableLazyValueTest {
}));
EdtTestUtil.runInEdtAndGet(() -> {
try (var ignored = installThreadContext(coroutineContext, false)) {
return ThreadContext.installThreadContext(coroutineContext, false, () -> {
final String result = lazyValue.getValueOrElse("not yet");
Assertions.assertThat(result).isEqualTo("not yet");
return null;
}
});
});
error.join();
@@ -276,19 +275,20 @@ public final class WslDistributionSafeNullableLazyValueTest {
return CompletableFuture
.allOf(
CompletableFuture.runAsync(() -> {
try (var ignored = installThreadContext(coroutineContext, false)) {
ThreadContext.installThreadContext(coroutineContext, false, () -> {
try {
lazyValue.getValue();
ProgressManager.checkCanceled();
}
catch (ProcessCanceledException e) {
return;
return null;
}
Assertions.fail("This line must not execute, because the progress must have been cancelled");
}
return null;
});
}),
CompletableFuture.runAsync(() -> {
try (var ignored = installThreadContext(coroutineContext, false)) {
installThreadContext(coroutineContext, false, () -> {
ProgressManager.checkCanceled(); // Should not be canceled by the moment.
try {
Thread.sleep(100);
@@ -297,7 +297,8 @@ public final class WslDistributionSafeNullableLazyValueTest {
// Nothing.
}
getJob(currentThreadContext()).cancel(new CancellationException());
}
return null;
});
})
).get();
}));
@@ -318,11 +319,12 @@ public final class WslDistributionSafeNullableLazyValueTest {
return prepareThreadContext(coroutineContext -> safeRethrow(() -> {
CompletableFuture
.runAsync(() -> safeRethrow(() -> {
try (var ignored = installThreadContext(coroutineContext, false)) {
installThreadContext(coroutineContext, false, () -> {
Assertions.assertThat(lazyValue.getValue()).isEqualTo("finished");
ProgressManager.checkCanceled(); // Should not throw.
return null;
}
});
return null;
}))
.get();
return null;
@@ -408,12 +410,14 @@ public final class WslDistributionSafeNullableLazyValueTest {
try {
return ProgressManager.getInstance().runProcess(
() -> prepareThreadContext(coroutineContext -> {
try (var ignored = installThreadContext(coroutineContext, true)) {
return body.compute();
}
catch (Exception err) {
throw new Holder(err);
}
return installThreadContext(coroutineContext, true, () -> {
try {
return body.compute();
}
catch (Exception err) {
throw new Holder(err);
}
});
}),
new ProgressIndicatorBase());
}
@@ -279,7 +279,7 @@ class ActionUpdaterTest {
e!!
val actualElement = currentThreadContext()[MyContextElement]
assertEquals(1, actualElement?.value, "MyContextElement must be propagated to getChildren")
installThreadContext(currentThreadContext() + MyContextElement(2), true).use {
installThreadContext(currentThreadContext() + MyContextElement(2), true) {
e.updateSession.sharedData(key) {
val actualElement = currentThreadContext()[MyContextElement]
assertEquals(2, actualElement?.value, "MyContextElement must be propagated to sharedData")
@@ -65,7 +65,7 @@ class DefaultModalityTest : CancellationTest() {
val outerModality = createFakeModality()
val nestedModality = createFakeModality()
val nestedModality2 = createFakeModality()
installThreadContext(outerModality.asContextElement()).use {
installThreadContext(outerModality.asContextElement()) {
assertModality(outerModality)
withIndicator(EmptyProgressIndicator(nestedModality)) {
assertModality(nestedModality) // IJPL-155640
@@ -28,7 +28,7 @@ class ExistingThreadContextTest : CancellationTest() {
@Test
fun context() {
val tc = Dispatchers.Default + TestElement("e")
installThreadContext(tc).use {
installThreadContext(tc) {
assertSame(tc, currentThreadContextOrNull())
prepareThreadContext { prepared ->
assertNull(currentThreadContextOrNull())
@@ -42,7 +42,7 @@ class ExistingThreadContextTest : CancellationTest() {
fun cancellation() {
val t = object : Throwable() {}
val ce = assertThrows<CeProcessCanceledException> {
installThreadContext(Job()).use {
installThreadContext(Job()) {
throw assertThrows<CeProcessCanceledException> {
prepareThreadContextTest { currentJob ->
testNoExceptions()
@@ -67,7 +67,7 @@ class ExistingThreadContextTest : CancellationTest() {
val job = Job()
val t = Throwable()
val ce = assertThrows<ProcessCanceledException> {
installThreadContext(job).use {
installThreadContext(job) {
throw assertThrows<CeProcessCanceledException> {
prepareThreadContextTest { currentJob ->
testNoExceptions()
@@ -71,7 +71,7 @@ fun <T> prepareThreadContextTest(action: (Job) -> T): T {
assertNull(currentThreadContextOrNull())
assertNull(ProgressManager.getGlobalProgressIndicator())
val job = assertNotNull(ctx[Job])
installThreadContext(ctx).use {
installThreadContext(ctx) {
assertSame(job, Cancellation.currentJob())
action(job)
}
@@ -141,7 +141,7 @@ class CancellationPropagationTest {
@Test
fun `expired invokeLater does not prevent completion of parent job`(): Unit = timeoutRunBlocking(60.seconds) {
installThreadContext(coroutineContext).use {
installThreadContext(coroutineContext) {
val expired = AtomicBoolean(false)
ApplicationManager.getApplication().withModality {
val runnable = Runnable {
@@ -934,7 +934,7 @@ class CancellationPropagationTest {
assertTrue(Cancellation.isInNonCancelableSection())
}
Cancellation.executeInNonCancelableSection {
installThreadContext(Job(currentThreadContext().job), true).use {
installThreadContext(Job(currentThreadContext().job), true) {
assertFalse(Cancellation.isInNonCancelableSection())
}
}
@@ -17,8 +17,10 @@ import com.intellij.testFramework.LightPlatformTestCase
import com.intellij.util.Alarm
import com.intellij.util.application
import io.kotest.assertions.failure
import kotlinx.coroutines.*
import java.lang.Runnable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import javax.swing.SwingUtilities
@@ -148,7 +150,7 @@ class ClientIdPropagationTest : LightPlatformTestCase() {
private fun doTest(expectedClientId: ClientId = TEST_CLIENT_ID, controllerContainerClientId: ClientId = TEST_CLIENT_ID, testRunnable: Runnable) {
service<ClientSessionsManager<ClientAppSession>>().registerSession(testRootDisposable,
TestClientAppSession(application as ApplicationImpl, controllerContainerClientId))
installThreadContext(ClientIdContextElement(TEST_CLIENT_ID)).use {
installThreadContext(ClientIdContextElement(TEST_CLIENT_ID)) {
testRunnable.run()
}
@@ -149,7 +149,7 @@ class CurrentThreadCoroutineScopeTest {
fun `blockingContextScope retains only those elements that were present at the moment of invocation`(): Unit = timeoutRunBlocking {
withContext(E2() + E3()) {
blockingContextScope {
installThreadContext(currentThreadContext() + E1(), true).use {
installThreadContext(currentThreadContext() + E1(), true) {
application.executeOnPooledThread {
val context = currentThreadContext()
assertNull(context[E1])
@@ -165,7 +165,7 @@ class CurrentThreadCoroutineScopeTest {
fun `fixCurrentThreadScope captures the context of its definition`(): Unit = timeoutRunBlocking {
withContext(E1()) {
val (_, job) = withCurrentThreadCoroutineScopeBlocking {
installThreadContext(currentThreadContext() + E2(), true).use {
installThreadContext(currentThreadContext() + E2(), true) {
currentThreadCoroutineScope().launch {
val context = currentThreadContext()
assertNotNull(context[E1])
@@ -205,7 +205,7 @@ class CurrentThreadCoroutineScopeTest {
val latch = Job(coroutineContext.job)
val wasCancelled = AtomicBoolean(false)
installThreadContext(currentThreadContext() + parentJob).use {
installThreadContext(currentThreadContext() + parentJob) {
val (_, job) = withCurrentThreadCoroutineScopeBlocking {
currentThreadCoroutineScope().launch {
try {
@@ -91,7 +91,7 @@ class ImplicitBlockingContextTest {
withContext(E()) {
val context = coroutineContext
val newContext = currentThreadContext() + F()
installThreadContext(newContext).use {
installThreadContext(newContext) {
assertEquals(IntellijCoroutines.currentThreadCoroutineContext(), context)
assertNotEquals(context, currentThreadContext())
assertEquals(newContext, currentThreadContext())
@@ -142,12 +142,12 @@ class ImplicitBlockingContextTest {
assertEquals(e, currentThreadContext()[E])
}
installThreadContext(currentThreadContext() + e).use {
installThreadContext(currentThreadContext() + e) {
`has e, not f`()
@Suppress("SSBasedInspection")
CoroutineScope(f + handler).launch(start = CoroutineStart.UNDISPATCHED) {
`has f, not e`()
installThreadContext(e + handler).use {
installThreadContext(e + handler) {
`has e, not f`()
}
`has f, not e`()
@@ -139,7 +139,7 @@ class ThreadContextPropagationTest {
val element = TestElement("element")
withContext(element) {
suspendCancellableCoroutine { continuation ->
installThreadContext(continuation.context).use { // install context in calling thread
installThreadContext(continuation.context) { // install context in calling thread
submit { // switch to another thread
val result: Result<Unit> = runCatching {
assertSame(element, currentThreadContext()[TestElementKey]) // the same element must be present in another thread context
@@ -16,7 +16,7 @@ import org.junit.jupiter.api.Assertions
internal suspend fun doPropagationTest(submit: (() -> Unit) -> Unit) {
return suspendCancellableCoroutine { continuation ->
val element = TestElement("element")
installThreadContext(element).use { // install context in calling thread
installThreadContext(element) { // install context in calling thread
submit { // switch to another thread
val result: Result<Unit> = runCatching {
Assertions.assertSame(element, currentThreadContext()[TestElementKey]) // the same element must be present in another thread context
+2
View File
@@ -12,7 +12,9 @@
*f:com.intellij.concurrency.ThreadContext
- sf:currentThreadContext():kotlin.coroutines.CoroutineContext
- sf:installThreadContext(kotlin.coroutines.CoroutineContext,Z):com.intellij.openapi.application.AccessToken
- sf:installThreadContext(kotlin.coroutines.CoroutineContext,Z,kotlin.jvm.functions.Function0):java.lang.Object
- bs:installThreadContext$default(kotlin.coroutines.CoroutineContext,Z,I,java.lang.Object):com.intellij.openapi.application.AccessToken
- bs:installThreadContext$default(kotlin.coroutines.CoroutineContext,Z,kotlin.jvm.functions.Function0,I,java.lang.Object):java.lang.Object
- sf:resetThreadContext():com.intellij.openapi.application.AccessToken
- sf:resetThreadContext(kotlin.jvm.functions.Function0):java.lang.Object
*:com.intellij.openapi.diagnostic.ReportingClassSubstitutor
@@ -150,7 +150,7 @@ open class AsyncPromise<T> private constructor(
override fun <SUB_RESULT : Any?> then(done: Function<in T, out SUB_RESULT>): Promise<SUB_RESULT> {
return AsyncPromise(wrapWithCancellationPropagation { ctx ->
f.thenApply { t ->
installThreadContext(ctx, true).use {
installThreadContext(ctx, true) {
done.`fun`(t)
}
}
@@ -297,8 +297,19 @@ fun <T> resetThreadContext(action: () -> T): T {
* Installs [coroutineContext] as the current thread context.
* If [replace] is `false` (default) and the current thread already has context, then this function logs an error.
*
*/
fun <T> installThreadContext(coroutineContext: CoroutineContext, replace: Boolean = false, action: () -> T): T {
installThreadContext(coroutineContext, replace = replace).use {
return action()
}
}
/**
* This function is not visible in stacktraces. Consider using a sibling higher-order function
*
* @return handle to restore the previous thread context
*/
@Deprecated("Use higher-order function for installation of thread context")
fun installThreadContext(coroutineContext: CoroutineContext, replace: Boolean = false): AccessToken {
return withThreadLocal(tlCoroutineContext) { previousContext ->
@OptIn(InternalCoroutinesApi::class)
@@ -134,6 +134,8 @@ public final class Cancellation {
if (isInNonCancelableSectionInternal()) {
return computable.compute();
}
// we use a deprecated method here to handle the exception correctly
//noinspection deprecation
try (@NotNull AccessToken ignored = ThreadContext.installThreadContext(
ThreadContext.currentThreadContext().plus(NonCancellable.INSTANCE), true)) {
return computable.compute();
@@ -80,15 +80,16 @@ final class ContextCallable<V> implements Callable<V> {
}
else {
Supplier<RunResult<V, Exception>> temp = () -> {
try (AccessToken ignored = ThreadContext.installThreadContext(myChildContext.getContext(), true);
AccessToken ignored2 = myChildContext.applyContextActions(false)) {
try {
return new RunResult<>(myCallable.call());
return ThreadContext.installThreadContext(myChildContext.getContext(), true, () -> {
try (AccessToken ignored2 = myChildContext.applyContextActions(false)) {
try {
return new RunResult<>(myCallable.call());
}
catch (Exception e) {
return new RunResult<>(e);
}
}
catch (Exception e) {
return new RunResult<>(e);
}
}
});
};
Continuation<Unit> continuation = myChildContext.getContinuation();
if (continuation == null) {
@@ -229,8 +229,8 @@ fun createChildContextWithContextJob(debugName: @NonNls String) : ChildContext =
@Internal
fun createChildContextIgnoreStructuredConcurrency(debugName: @NonNls String) : ChildContext {
// probably we need to exclude some elements like PlatformActivityTrackerService.ObservationTracker
installThreadContext(currentThreadContext().minusKey(BlockingJob), true).use {
return createChildContext(debugName)
return installThreadContext(currentThreadContext().minusKey(BlockingJob), true) {
createChildContext(debugName)
}
}
@@ -544,7 +544,7 @@ internal fun capturePropagationContext(
val capturedRunnable1 = captureClientIdInRunnable(runnable)
val capturedRunnable2 = Runnable {
// no cancellation tracker here: this is a periodic runnable that is restarted
installThreadContext(childContext.context, false).use {
installThreadContext(childContext.context, false) {
childContext.applyContextActions(false).use {
capturedRunnable1.run()
}