From 08210fb4d2e737f422e5377b933a7aab5c30b9a9 Mon Sep 17 00:00:00 2001 From: Rustam Vishnyakov Date: Tue, 6 Aug 2019 16:53:57 +0300 Subject: [PATCH] Fixes IDEA-218532 Freezes in CodeStyle.getSettings Code style settings are computed asynchronously on a background thread with a notification sent upon completion. GitOrigin-RevId: 8e82a1e540f1880961788703e42f7f1e21ad62a4 --- .../application/options/CodeStyle.java | 11 +- .../options/CodeStyleCachedValueProvider.java | 149 ++++++++++++++++++ .../options/CodeStyleCachingUtil.java | 75 --------- .../codeStyle/CodeStyleSettingsManager.java | 2 - .../FileCodeStyleProviderTest.java | 1 + .../EditorConfigFileHierarchyServiceImpl.kt | 2 +- 6 files changed, 160 insertions(+), 80 deletions(-) create mode 100644 platform/lang-api/src/com/intellij/application/options/CodeStyleCachedValueProvider.java delete mode 100644 platform/lang-api/src/com/intellij/application/options/CodeStyleCachingUtil.java diff --git a/platform/lang-api/src/com/intellij/application/options/CodeStyle.java b/platform/lang-api/src/com/intellij/application/options/CodeStyle.java index a35e61e26d24..faf81b160c4d 100644 --- a/platform/lang-api/src/com/intellij/application/options/CodeStyle.java +++ b/platform/lang-api/src/com/intellij/application/options/CodeStyle.java @@ -11,6 +11,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.*; import com.intellij.psi.codeStyle.modifier.CodeStyleSettingsModifier; import com.intellij.psi.codeStyle.modifier.TransientCodeStyleSettings; +import com.intellij.psi.util.CachedValuesManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; @@ -70,6 +71,12 @@ public class CodeStyle { */ @NotNull public static CodeStyleSettings getSettings(@NotNull PsiFile file) { + final Project project = file.getProject(); + CodeStyleSettings tempSettings = CodeStyleSettingsManager.getInstance(project).getTemporarySettings(); + if (tempSettings != null) { + return tempSettings; + } + for (FileCodeStyleProvider provider : FileCodeStyleProvider.EP_NAME.getIterable()) { CodeStyleSettings fileSettings = provider.getSettings(file); if (fileSettings != null) { @@ -78,9 +85,9 @@ public class CodeStyle { } if (!file.isPhysical()) { - return getSettings(file.getProject()); + return getSettings(project); } - return CodeStyleCachingUtil.getCachedCodeStyle(file); + return CachedValuesManager.getCachedValue(file, CodeStyleCachedValueProvider.getInstance(file)); } diff --git a/platform/lang-api/src/com/intellij/application/options/CodeStyleCachedValueProvider.java b/platform/lang-api/src/com/intellij/application/options/CodeStyleCachedValueProvider.java new file mode 100644 index 000000000000..919356df5902 --- /dev/null +++ b/platform/lang-api/src/com/intellij/application/options/CodeStyleCachedValueProvider.java @@ -0,0 +1,149 @@ +// 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.application.options; + +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.SimpleModificationTracker; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.modifier.CodeStyleSettingsModifier; +import com.intellij.psi.codeStyle.modifier.TransientCodeStyleSettings; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +class CodeStyleCachedValueProvider implements CachedValueProvider { + private final static Logger LOG = Logger.getInstance(CodeStyleCachedValueProvider.class); + + private final static Key PROVIDER_KEY = Key.create("code.style.cached.value.provider"); + + private final @NotNull PsiFile myFile; + private final @NotNull AsyncComputation myComputation; + + CodeStyleCachedValueProvider(@NotNull PsiFile file) { + myFile = file; + myComputation = new AsyncComputation(); + } + + @NotNull + @Override + public Result compute() { + CodeStyleSettings settings = myComputation.getCurrResult(); + logCached(myFile, settings); + return new Result<>(settings, getDependencies(settings, myComputation)); + } + + @NotNull + Object[] getDependencies(@NotNull CodeStyleSettings settings, @NotNull AsyncComputation computation) { + List dependencies = new ArrayList<>(); + if (settings instanceof TransientCodeStyleSettings) { + dependencies.addAll(((TransientCodeStyleSettings)settings).getDependencies()); + } + else { + dependencies.add(settings.getModificationTracker()); + } + dependencies.add(computation.getTracker()); + return ArrayUtil.toObjectArray(dependencies); + } + + static synchronized CodeStyleCachedValueProvider getInstance(@NotNull PsiFile file) { + CodeStyleCachedValueProvider instance = file.getUserData(PROVIDER_KEY); + if (instance == null) { + instance = new CodeStyleCachedValueProvider(file); + file.putUserData(PROVIDER_KEY, instance); + } + return instance; + } + + private void notifyCachedValueComputed(@NotNull PsiFile file) { + final Application application = ApplicationManager.getApplication(); + if (!application.isUnitTestMode()) { + application.invokeLater(() -> { + final CodeStyleSettingsManager settingsManager = CodeStyleSettingsManager.getInstance(file.getProject()); + settingsManager.fireCodeStyleSettingsChanged(file); + myComputation.reset(); + }); + } + } + + private static void logCached(@NotNull PsiFile file, @NotNull CodeStyleSettings settings) { + LOG.debug(String.format( + "File: %s (%s), cached: %s, tracker: %d", file.getName(), Integer.toHexString(file.hashCode()), settings, + settings.getModificationTracker().getModificationCount())); + } + + /** + * Always contains some result which can be obtained by {@code getCurrResult()} method. Listeners are notified after + * the computation is finished and {@code getCurrResult()} contains a stable computed value. + */ + private class AsyncComputation { + private final AtomicBoolean myIsActive = new AtomicBoolean(); + private volatile @NotNull CodeStyleSettings myCurrResult; + private final @NotNull CodeStyleSettingsManager mySettingsManager; + private final SimpleModificationTracker myTracker = new SimpleModificationTracker(); + + private AsyncComputation() { + mySettingsManager = CodeStyleSettingsManager.getInstance(myFile.getProject()); + //noinspection deprecation + myCurrResult = mySettingsManager.getCurrentSettings(); + } + + private void start() { + final Application application = ApplicationManager.getApplication(); + if (!application.isUnitTestMode()) { + application.executeOnPooledThread(() -> computeSettings()); + } + else { + computeSettings(); + } + } + + private synchronized void computeSettings() { + if (LOG.isDebugEnabled()) { + LOG.debug("Computation started for " + myFile.getName()); + } + @SuppressWarnings("deprecation") + CodeStyleSettings currSettings = mySettingsManager.getCurrentSettings(); + if (currSettings != mySettingsManager.getTemporarySettings()) { + TransientCodeStyleSettings modifiableSettings = new TransientCodeStyleSettings(myFile, currSettings); + for (CodeStyleSettingsModifier modifier : CodeStyleSettingsModifier.EP_NAME.getExtensionList()) { + if (modifier.modifySettings(modifiableSettings, myFile)) { + LOG.debug("Modifier: " + modifier.getClass().getName()); + modifiableSettings.setModifier(modifier); + currSettings = modifiableSettings; + break; + } + } + } + myCurrResult = currSettings; + myTracker.incModificationCount(); + if (LOG.isDebugEnabled()) { + LOG.debug("Computation ended for " + myFile.getName()); + } + notifyCachedValueComputed(myFile); + } + + @NotNull + public CodeStyleSettings getCurrResult() { + if (myIsActive.compareAndSet(false, true)) { + start(); + } + return myCurrResult; + } + + private SimpleModificationTracker getTracker() { + return myTracker; + } + + void reset() { + myIsActive.set(false); + } + } +} diff --git a/platform/lang-api/src/com/intellij/application/options/CodeStyleCachingUtil.java b/platform/lang-api/src/com/intellij/application/options/CodeStyleCachingUtil.java deleted file mode 100644 index 258edaa797d2..000000000000 --- a/platform/lang-api/src/com/intellij/application/options/CodeStyleCachingUtil.java +++ /dev/null @@ -1,75 +0,0 @@ -// 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.application.options; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.psi.PsiFile; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.codeStyle.modifier.CodeStyleSettingsModifier; -import com.intellij.psi.codeStyle.modifier.TransientCodeStyleSettings; -import com.intellij.psi.util.CachedValueProvider; -import com.intellij.psi.util.CachedValuesManager; -import org.jetbrains.annotations.NotNull; - -class CodeStyleCachingUtil { - private final static Logger LOG = Logger.getInstance(CodeStyleCachingUtil.class); - - @NotNull - static CodeStyleSettings getCachedCodeStyle(@NotNull PsiFile file) { - CachedCodeStyleHolder cachedCodeStyleHolder = CachedValuesManager.getCachedValue(file, () -> createHolder(file).getCachedResult()); - return cachedCodeStyleHolder.getCachedSettings(); - } - - private static CachedCodeStyleHolder createHolder(@NotNull PsiFile file) { - CachedCodeStyleHolder holder = new CachedCodeStyleHolder(); - holder.compute(file); - if (LOG.isDebugEnabled()) { - logCached(file, holder); - } - return holder; - } - - static class CachedCodeStyleHolder { - private @NotNull CodeStyleSettings myCachedSettings = CodeStyle.getDefaultSettings(); - - private void compute(@NotNull PsiFile file) { - final CodeStyleSettingsManager settingsManager = CodeStyleSettingsManager.getInstance(file.getProject()); - @SuppressWarnings("deprecation") - CodeStyleSettings currSettings = myCachedSettings = settingsManager.getCurrentSettings(); - if (currSettings != settingsManager.getTemporarySettings()) { - TransientCodeStyleSettings modifiableSettings = new TransientCodeStyleSettings(file, currSettings); - for (CodeStyleSettingsModifier modifier : CodeStyleSettingsModifier.EP_NAME.getExtensionList()) { - if (modifier.modifySettings(modifiableSettings, file)) { - LOG.debug("Modifier: " + modifier.getClass().getName()); - modifiableSettings.setModifier(modifier); - currSettings = modifiableSettings; - break; - } - } - } - myCachedSettings = currSettings; - } - - @NotNull - Object[] getDependencies() { - return myCachedSettings instanceof TransientCodeStyleSettings ? - ((TransientCodeStyleSettings)myCachedSettings).getDependencies().toArray() : - new Object[]{myCachedSettings.getModificationTracker()}; - } - - @NotNull - CodeStyleSettings getCachedSettings() { - return myCachedSettings; - } - - CachedValueProvider.Result getCachedResult() { - return new CachedValueProvider.Result<>(this, this.getDependencies()); - } - } - - private static void logCached(@NotNull PsiFile file, @NotNull CachedCodeStyleHolder holder) { - CodeStyleSettings settings = holder.getCachedSettings(); - LOG.debug(String.format( - "File: %s (%s), cached: %s, tracker: %d", file.getName(), Integer.toHexString(file.hashCode()), settings, settings.getModificationTracker().getModificationCount())); - } -} diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettingsManager.java b/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettingsManager.java index 9923bbd907d8..954f0a48151f 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettingsManager.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettingsManager.java @@ -148,12 +148,10 @@ public class CodeStyleSettingsManager implements PersistentStateComponent