Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8263682

ZipFile.Source.Key clashes in multi-threading environment

XMLWordPrintable

      A DESCRIPTION OF THE PROBLEM :
      The ZipFile caches the input internally in ZipFile.Source.files static field. The cache uses the ZipFile.Source.Key for the key. The ZipFile.Source.Key#equals checks for the utf8 flag, the filename or fileKey and the last modification timestamp. The cache entry is removed once the ZipFile is closed.

      If I have two threads and both of them writes to the same zip file (same path but different content) then there is a possibility that the two different content will share the same key. So the second content will not be visible as the first one is still cached.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :

      A -> create zip: /tmp/file.zip
      A -> new ZipFile(/tmp/file.zip) (opens only does not close)
      B -> create zip: /tmp/file.zip (overwrites A's zip)
      B -> new ZipFile(/tmp/file.zip)
      B -> read from the opened ZipFile: ERROR: gets the content of A's zip)

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Thread B should be able to read the new file not the old.
      ACTUAL -
      Thread B reads the old content not what it created.

      ---------- BEGIN SOURCE ----------
      package com.example;

      import static java.nio.charset.StandardCharsets.UTF_8;
      import static java.nio.file.Files.newOutputStream;
      import static org.assertj.core.api.Assertions.assertThat;

      import java.io.IOException;
      import java.nio.file.Path;
      import java.util.jar.JarEntry;
      import java.util.jar.JarOutputStream;
      import java.util.zip.ZipEntry;
      import java.util.zip.ZipFile;
      import org.junit.jupiter.api.RepeatedTest;
      import org.junit.jupiter.api.io.TempDir;

      public class ZipFileCacheTest {
        @RepeatedTest(100)
        public void cacheKeyError(@TempDir Path temp) throws IOException {
          Path path = temp.resolve("test.jar");
          createZip(path, 1);
          ZipFile zipFile = new ZipFile(path.toFile());
          // The zipFile.close(); is not invoked as I try to simulate what could happen
          // in a multi-threaded execution.
          createZip(path, 2);
          zipFile = new ZipFile(path.toFile());
          ZipEntry zipEntry = zipFile.entries().nextElement();
          zipFile.close();
          assertThat(zipEntry.getName())
              .isEqualTo("entry-2");
        }

        private void createZip(Path path, int count) throws IOException {
          JarOutputStream jarOutputStream = new JarOutputStream(newOutputStream(path));
          JarEntry entry = new JarEntry("entry-" + count);
          jarOutputStream.putNextEntry(entry);
          jarOutputStream.write(("content-" + count).getBytes(UTF_8));
          jarOutputStream.closeEntry();
          jarOutputStream.close();
        }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The only possible workaround to force the threads to synchronize but that is not always possible as background threads from libraries are out of my control.

      FREQUENCY : often


            lancea Lance Andersen
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: