Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock cache metadata files for access #849

Merged
merged 8 commits into from
Aug 22, 2018
Merged

Lock cache metadata files for access #849

merged 8 commits into from
Aug 22, 2018

Conversation

briandealwis
Copy link
Member

Acquire a file lock before retrieving and writing out the cache metadata file.

Note that this only serializes writing to the file but does not verify that the file has not been written to since it was initially read. The worst case is that some set of cached layers will be discarded and refetched on a subsequent build.

Fixes #848

JsonTemplateMapper.readJsonFromFile(cacheMetadataJsonFile, CacheMetadataTemplate.class);
return CacheMetadataTranslator.fromTemplate(cacheMetadataJson, cacheDirectory);

// channel is closed by inputStream.close()
Copy link
Contributor

@coollog coollog Aug 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should include these as methods on JsonTemplateMapper/Blobs? Like JsonTemplateMapper#readJsonFromFile and Blobs#writeToFile?

@briandealwis
Copy link
Member Author

Failed on Ubuntu:

com.google.cloud.tools.jib.gradle.JibPluginTest > testGetProjectDependencyAssembleTasks FAILED
    java.lang.ExceptionInInitializerError
        at java.util.concurrent.ConcurrentHashMap.fullAddCount(ConcurrentHashMap.java:2526)
        at java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2266)
        at java.util.concurrent.ConcurrentHashMap.replaceNode(ConcurrentHashMap.java:1166)
        at java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:1097)
        at org.mockito.internal.util.concurrent.WeakConcurrentMap.expungeStaleEntries(WeakConcurrentMap.java:127)
        at org.mockito.internal.util.concurrent.WeakConcurrentMap$WithInlinedExpunction.containsKey(WeakConcurrentMap.java:260)
        at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.isMock(MockMethodAdvice.java:116)
        at java.net.URL.hashCode(URL.java:882)
        at java.security.CodeSource.hashCode(CodeSource.java:121)
        at java.util.HashMap.hash(HashMap.java:339)
        at java.util.HashMap.get(HashMap.java:557)
        at java.security.SecureClassLoader.getProtectionDomain(SecureClassLoader.java:204)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at org.gradle.testfixtures.internal.ProjectBuilderImpl.getGlobalServices(ProjectBuilderImpl.java:121)
        at org.gradle.testfixtures.internal.ProjectBuilderImpl.getUserHomeServices(ProjectBuilderImpl.java:114)
        at org.gradle.testfixtures.internal.ProjectBuilderImpl.createProject(ProjectBuilderImpl.java:91)
        at org.gradle.testfixtures.ProjectBuilder.build(ProjectBuilder.java:116)
        at com.google.cloud.tools.jib.gradle.JibPluginTest.testGetProjectDependencyAssembleTasks(JibPluginTest.java:91)

        Caused by:
        java.lang.NullPointerException
            at java.util.concurrent.ThreadLocalRandom.getProbe(ThreadLocalRandom.java:980)
            at java.util.concurrent.ConcurrentHashMap.fullAddCount(ConcurrentHashMap.java:2526)
            at java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2266)
            at java.util.concurrent.ConcurrentHashMap.replaceNode(ConcurrentHashMap.java:1166)
            at java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:1097)
            at org.mockito.internal.util.concurrent.WeakConcurrentMap.expungeStaleEntries(WeakConcurrentMap.java:127)
            at org.mockito.internal.util.concurrent.WeakConcurrentMap$WithInlinedExpunction.containsKey(WeakConcurrentMap.java:260)
            at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.isMock(MockMethodAdvice.java:116)
            at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.isMocked(MockMethodAdvice.java:121)
            at java.util.Hashtable.isEmpty(Hashtable.java:245)
            at sun.misc.VM.getSavedProperty(VM.java:258)
            at java.util.concurrent.ThreadLocalRandom.initialSeed(ThreadLocalRandom.java:139)
            at java.util.concurrent.ThreadLocalRandom.<clinit>(ThreadLocalRandom.java:136)
            ... 27 more

I hope this locking isn't tickling some underlying bug as the stack trace looks like JDK-8150014 (fixed in Java 9; dupes JDK-8150667, JDK-8150365).

* @return the {@link BlobDescriptor} of the written BLOB
* @throws IOException if writing the BLOB fails
*/
default BlobDescriptor writeTo(Path jsonFile, boolean acquireLock) throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I was referring to placing the method in Blobs#writeToFile (similar to Blobs#writeToString and Blobs#writeToByteArray - Blobs is intended to be static methods that operate on Blob). This will help keep the Blob interface simple and avoid having to use default interface methods.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't think of Blobs. Will look.

public static <T extends JsonTemplate> T readJsonFromFile(
Path jsonFile, Class<T> templateClass, boolean acquireLock) throws IOException {
if (!acquireLock) {
return objectMapper.readValue(Files.newInputStream(jsonFile), templateClass);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here we can either:

  • call out to the method without the extra acquireLock parameter, or
  • remove that other method without the acquireLock parameter, or
  • remove the acquireLock parameter and rename this method into something like readJsonFromFileWithLocking

@briandealwis
Copy link
Member Author

Huh failed again. In the build logs, Maven reports the version as:

+ cd jib-maven-plugin
+ ./mvnw clean install -B -U -X
Apache Maven 3.5.2 (138edd61fd100ec658bfa2d307c43b76940a5d7d; 2017-10-18T00:58:13-07:00)
Maven home: /home/kbuilder/.m2/wrapper/dists/apache-maven-3.5.2-bin/28qa8v9e2mq69covern8vmdkj0/apache-maven-3.5.2
Java version: 1.8.0_171, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-8-oracle/jre

@coollog
Copy link
Contributor

coollog commented Aug 20, 2018

Did you experience failure again after the new commits?

@briandealwis
Copy link
Member Author

Yeah, on MacOS this time. I don't see it locally. Maybe something fixed between 1.8.0_171 and _181.

@Test
public void testWriteToFileWithLock_newFile() throws IOException {
String expected = "crepecake";
File file = File.createTempFile("blob", "bin");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency in this codebase, let's prefer java.nio for files over java.io - so Files.createTempFile and Files.delete

@@ -77,6 +108,7 @@ private void verifyBlobWriteTo(String expected, Blob blob) throws IOException {
byte[] expectedBytes = expected.getBytes(StandardCharsets.UTF_8);
Assert.assertEquals(expectedBytes.length, blobDescriptor.getSize());

@SuppressWarnings("resource") // no leak
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can actually change the mocked OutputStream to just Bytestreams.nullOutputStream() to avoid the suppression.

@@ -89,4 +90,33 @@ public void testWriteJson() throws DigestException, IOException, URISyntaxExcept

Assert.assertEquals(expectedJson, jsonStream.toString());
}

@Test
public void tedstReadJsonWithLock() throws IOException, URISyntaxException, DigestException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/tedst/test/

File file = File.createTempFile("blob", "bin");
Assert.assertTrue(file.delete()); // ensure it doesn't exist
Path blobFile = Files.createTempFile("blob", "bin");
Assert.assertTrue(Files.deleteIfExists(blobFile)); // ensure it doesn't exist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the file exist always?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope so. But Files.delete() is void, so at least this way we fail if something isn't right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants