IDEA-CR-10973 VFSTestListener updates packages synchronously, one refresh at time
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 22 Jun 2016 14:19:45 +0000 (17:19 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Thu, 23 Jun 2016 14:40:58 +0000 (17:40 +0300)
python/src/com/jetbrains/python/packaging/PyPackageManagerImpl.java
python/src/com/jetbrains/python/packaging/PyPackageUtil.java
python/src/com/jetbrains/python/testing/PyIntegratedToolsProjectConfigurator.java
python/src/com/jetbrains/python/testing/VFSTestFrameworkListener.java

index 2a4333cc84f81f45a854e0c2e6dea4c6009a4c43..fce9cffa796119a317e61cb4f8287805e4a60242 100644 (file)
@@ -49,6 +49,7 @@ import org.jetbrains.annotations.Nullable;
 import java.io.File;
 import java.io.IOException;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * @author vlan
@@ -77,7 +78,7 @@ public class PyPackageManagerImpl extends PyPackageManager {
   private static final String UNTAR = "untar";
 
   @Nullable private volatile List<PyPackage> myPackagesCache = null;
-  private volatile boolean myUpdatingCache = false;
+  private final AtomicBoolean myUpdatingCache = new AtomicBoolean(false);
 
   @NotNull final private Sdk mySdk;
 
@@ -358,7 +359,6 @@ public class PyPackageManagerImpl extends PyPackageManager {
   public List<PyPackage> refreshAndGetPackages(boolean alwaysRefresh) throws ExecutionException {
     final List<PyPackage> currentPackages = myPackagesCache;
     if (alwaysRefresh || currentPackages == null) {
-      myUpdatingCache = true;
       try {
         final List<PyPackage> packages = collectPackages();
         LOG.debug("Packages installed in " + mySdk.getName() + ": " + packages);
@@ -369,25 +369,12 @@ public class PyPackageManagerImpl extends PyPackageManager {
         myPackagesCache = Collections.emptyList();
         throw e;
       }
-      finally {
-        myUpdatingCache = false;
-      }
     }
     return Collections.unmodifiableList(currentPackages);
   }
 
   private void refreshPackagesSynchronously() {
-    assert !ApplicationManager.getApplication().isDispatchThread();
-    if (myUpdatingCache) {
-      return;
-    }
-    try {
-      LOG.info("Refreshing installed packages for SDK " + mySdk.getHomePath());
-      refreshAndGetPackages(true);
-    }
-    catch (ExecutionException e) {
-      LOG.warn(e);
-    }
+    PyPackageUtil.updatePackagesSynchronouslyWithGuard(this, myUpdatingCache);
   }
 
   @Nullable
index 214b5694719bfa6d34614f1a8c48fe01db7af3c8..b1e2ffe071d5ea18171d3d067b234fbcceac8f78 100644 (file)
@@ -18,6 +18,7 @@ package com.jetbrains.python.packaging;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.execution.ExecutionException;
+import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
@@ -51,6 +52,7 @@ import org.jetbrains.annotations.Nullable;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * @author vlan
@@ -293,6 +295,35 @@ public class PyPackageUtil {
     return packagesRef.get();
   }
 
+  /**
+   * Run unconditional update of the list of packages installed in SDK. Normally only one such of updates should run at time.
+   * This behavior in enforced by the parameter isUpdating.
+   * 
+   * @param manager    package manager for SDK
+   * @param isUpdating flag indicating whether another refresh is already running
+   * @return whether packages were refreshed successfully, e.g. this update wasn't cancelled because of another refresh in progress
+   */
+  public static boolean updatePackagesSynchronouslyWithGuard(@NotNull PyPackageManager manager, @NotNull AtomicBoolean isUpdating) {
+    assert !ApplicationManager.getApplication().isDispatchThread();
+    if (!isUpdating.compareAndSet(false, true)) {
+      return false;
+    }
+    try {
+      if (manager instanceof PyPackageManagerImpl) {
+        LOG.info("Refreshing installed packages for SDK " + ((PyPackageManagerImpl)manager).getSdk().getHomePath());
+      }
+      manager.refreshAndGetPackages(true);
+    }
+    catch (ExecutionException e) {
+      LOG.warn(e);
+    }
+    finally {
+      isUpdating.set(false);
+    }
+    return true;
+  }
+  
+
   @Nullable
   public static PyPackage findPackage(@NotNull List<PyPackage> packages, @NotNull String name) {
     for (PyPackage pkg : packages) {
index 3ad43f6d6c868aac56f61d6f4be29f5eaf64fa1a..8cf525865f0feddc79bc78e7bcfbdee4361fe4a4 100644 (file)
@@ -38,6 +38,7 @@ import com.jetbrains.python.PythonModuleTypeBase;
 import com.jetbrains.python.documentation.PyDocumentationSettings;
 import com.jetbrains.python.documentation.docstrings.DocStringFormat;
 import com.jetbrains.python.documentation.docstrings.DocStringUtil;
+import com.jetbrains.python.packaging.PyPackage;
 import com.jetbrains.python.packaging.PyPackageUtil;
 import com.jetbrains.python.psi.*;
 import com.jetbrains.python.sdk.PythonSdkType;
@@ -114,17 +115,20 @@ public class PyIntegratedToolsProjectConfigurator implements DirectoryProjectCon
         //check if installed in sdk
         final Sdk sdk = PythonSdkType.findPythonSdk(module);
         if (sdk != null && sdk.getSdkType() instanceof PythonSdkType) {
-          final Boolean nose = VFSTestFrameworkListener.isTestFrameworkInstalled(sdk, PyNames.NOSE_TEST);
-          final Boolean pytest = VFSTestFrameworkListener.isTestFrameworkInstalled(sdk, PyNames.PY_TEST);
-          final Boolean attest = VFSTestFrameworkListener.isTestFrameworkInstalled(sdk, PyNames.AT_TEST);
-          if (nose != null && nose)
-            testRunner = PythonTestConfigurationsModel.PYTHONS_NOSETEST_NAME;
-          else if (pytest != null && pytest)
-            testRunner = PythonTestConfigurationsModel.PY_TEST_NAME;
-          else if (attest != null && attest)
-            testRunner = PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME;
-          if (!testRunner.isEmpty()) {
-            LOG.debug("Test runner '" + testRunner + "' was detected from SDK " + sdk);
+          final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
+          if (packages != null) {
+            final boolean nose = PyPackageUtil.findPackage(packages, PyNames.NOSE_TEST) != null;
+            final boolean pytest = PyPackageUtil.findPackage(packages, PyNames.PY_TEST) != null;
+            final boolean attest = PyPackageUtil.findPackage(packages, PyNames.AT_TEST) != null;
+            if (nose)
+              testRunner = PythonTestConfigurationsModel.PYTHONS_NOSETEST_NAME;
+            else if (pytest)
+              testRunner = PythonTestConfigurationsModel.PY_TEST_NAME;
+            else if (attest)
+              testRunner = PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME;
+            if (!testRunner.isEmpty()) {
+              LOG.debug("Test runner '" + testRunner + "' was detected from SDK " + sdk);
+            }
           }
         }
       }
index 50c3aac817d81ee65718012dabdee890807c2c53..d193dc2aabc930a6138a851bd0204c1d4bc37f87 100644 (file)
@@ -33,18 +33,21 @@ import com.intellij.util.ui.update.MergingUpdateQueue;
 import com.intellij.util.ui.update.Update;
 import com.jetbrains.python.PyNames;
 import com.jetbrains.python.packaging.PyPackage;
+import com.jetbrains.python.packaging.PyPackageManager;
 import com.jetbrains.python.packaging.PyPackageUtil;
 import com.jetbrains.python.sdk.PySdkUtil;
 import com.jetbrains.python.sdk.PythonSdkType;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * User: catherine
  */
 public class VFSTestFrameworkListener {
   private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.testing.VFSTestFrameworkListener");
+  private final AtomicBoolean myIsUpdating = new AtomicBoolean(false);
   private final MergingUpdateQueue myQueue;
   private final PyTestFrameworkService myService;
 
@@ -111,16 +114,20 @@ public class VFSTestFrameworkListener {
   /**
    * @return null if we can't be sure
    */
-  public static Boolean isTestFrameworkInstalled(Sdk sdk, String testPackageName) {
+  public Boolean isTestFrameworkInstalled(Sdk sdk, String testPackageName) {
     if (sdk == null || StringUtil.isEmptyOrSpaces(sdk.getHomePath())) {
       LOG.info("Searching test runner in empty sdk");
       return null;
     }
-    final List<PyPackage> packages = PyPackageUtil.refreshAndGetPackagesModally(sdk);
-    if (packages == null) {
-      return null;
+    final PyPackageManager manager = PyPackageManager.getInstance(sdk);
+    final boolean refreshed = PyPackageUtil.updatePackagesSynchronouslyWithGuard(manager, myIsUpdating);
+    if (refreshed) {
+      final List<PyPackage> packages = manager.getPackages();
+      if (packages != null) {
+        return PyPackageUtil.findPackage(packages, testPackageName) != null;
+      }
     }
-    return PyPackageUtil.findPackage(packages, testPackageName) != null;
+    return null;
   }
 
   public static VFSTestFrameworkListener getInstance() {