PY-20370 Update cache of PyPI packages atomically under dedicated lock
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 10 Aug 2016 14:16:33 +0000 (17:16 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Thu, 11 Aug 2016 10:51:20 +0000 (13:51 +0300)
Otherwise, multiple threads might try to update this cache simultaneously
to get the latest cached version of an installed package. Considering
the fact that the corresponding HTTP request scraps web-page and then
returns the list of 85k+ package names, it easily leads to OutOfMemoryError
and disrupted IDE responsiveness.

python/src/com/jetbrains/python/packaging/PyPIPackageUtil.java

index 196219ecce0226e63a899776a082476f1e95cced..d068138b88e8bdb908e9307711ff5dafa42dd17b 100644 (file)
@@ -86,7 +86,15 @@ public class PyPIPackageUtil {
       LOG.error("Cannot find \"packages\". " + e.getMessage());
     }
   }
+
+  /**
+   * Prevents simultaneous updates of {@link PyPackageService#PY_PACKAGES}
+   * because the corresponding response contains tons of data and multiple
+   * queries at the same time can cause memory issues. 
+   */
+  private final Object myPyPIPackageCacheUpdateLock = new Object();
   
+
   private PyPIPackageUtil() {
     try {
       final DefaultXmlRpcTransportFactory factory = new PyPIXmlRpcTransportFactory(new URL(PYPI_URL));
@@ -414,9 +422,11 @@ public class PyPIPackageUtil {
   @NotNull
   public Map<String, String> loadAndGetPackages() throws IOException {
     Map<String, String> pyPIPackages = getPyPIPackages();
-    if (pyPIPackages.isEmpty()) {
-      updatePyPICache(PyPackageService.getInstance());
-      pyPIPackages = getPyPIPackages();
+    synchronized (myPyPIPackageCacheUpdateLock) {
+      if (pyPIPackages.isEmpty()) {
+        updatePyPICache(PyPackageService.getInstance());
+        pyPIPackages = getPyPIPackages();
+      }
     }
     return pyPIPackages;
   }