diff options
author | 2016-08-22 22:00:24 +0000 | |
---|---|---|
committer | 2016-08-23 09:29:38 +0000 | |
commit | 141b6c2a48353a0a4e2c0debe8f040adb41ab349 (patch) | |
tree | 2473d7e4b1fd9be372e11bcaa8390c645938c67d /src/main/java/com/google/devtools/build/lib/actions | |
parent | cf847b82e87340cac005ad1a5f32a59bb8815662 (diff) |
Make Digest (renamed Md5Digest) a little more multi-purpose.
--
MOS_MIGRATED_REVID=130986194
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/actions')
8 files changed, 161 insertions, 170 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java b/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java index b96a9e4111..5b913ad1c3 100644 --- a/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java +++ b/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java @@ -19,7 +19,7 @@ import com.google.common.collect.Iterables; import com.google.devtools.build.lib.actions.ActionAnalysisMetadata.MiddlemanType; import com.google.devtools.build.lib.actions.cache.ActionCache; import com.google.devtools.build.lib.actions.cache.ActionCache.Entry; -import com.google.devtools.build.lib.actions.cache.Digest; +import com.google.devtools.build.lib.actions.cache.DigestUtils; import com.google.devtools.build.lib.actions.cache.Metadata; import com.google.devtools.build.lib.actions.cache.MetadataHandler; import com.google.devtools.build.lib.events.Event; @@ -108,7 +108,7 @@ public class ActionCacheChecker { for (Artifact artifact : artifacts) { mdMap.put(artifact.getExecPathString(), metadataHandler.getMetadataMaybe(artifact)); } - return !Digest.fromMetadata(mdMap).equals(entry.getFileDigest()); + return !DigestUtils.fromMetadata(mdMap).equals(entry.getFileDigest()); } private void reportCommand(EventHandler handler, Action action) { diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java b/src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java index b87dd846ab..5dc88cf0ff 100644 --- a/src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java +++ b/src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java @@ -19,7 +19,6 @@ import com.google.common.collect.Lists; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.PathFragment; - import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; @@ -28,7 +27,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.annotation.Nullable; /** @@ -73,9 +71,9 @@ public interface ActionCache { @Nullable // Null iff the corresponding action does not do input discovery. private final List<String> files; - // If null, digest is non-null and the entry is immutable. + // If null, md5Digest is non-null and the entry is immutable. private Map<String, Metadata> mdMap; - private Digest digest; + private Md5Digest md5Digest; public Entry(String key, boolean discoversInputs) { actionKey = key; @@ -83,10 +81,10 @@ public interface ActionCache { mdMap = new HashMap<>(); } - public Entry(String key, @Nullable List<String> files, Digest digest) { + public Entry(String key, @Nullable List<String> files, Md5Digest md5Digest) { actionKey = key; this.files = files; - this.digest = digest; + this.md5Digest = md5Digest; mdMap = null; } @@ -97,7 +95,7 @@ public interface ActionCache { public void addFile(PathFragment relativePath, Metadata md) { Preconditions.checkState(mdMap != null); Preconditions.checkState(!isCorrupted()); - Preconditions.checkState(digest == null); + Preconditions.checkState(md5Digest == null); String execPath = relativePath.getPathString(); if (discoversInputs()) { @@ -114,17 +112,17 @@ public interface ActionCache { } /** - * Returns the combined digest of the action's inputs and outputs. + * Returns the combined md5Digest of the action's inputs and outputs. * - * This may compresses the data into a more compact representation, and - * makes the object immutable. + * <p>This may compresses the data into a more compact representation, and makes the object + * immutable. */ - public Digest getFileDigest() { - if (digest == null) { - digest = Digest.fromMetadata(mdMap); + public Md5Digest getFileDigest() { + if (md5Digest == null) { + md5Digest = DigestUtils.fromMetadata(mdMap); mdMap = null; } - return digest; + return md5Digest; } /** @@ -153,10 +151,10 @@ public interface ActionCache { StringBuilder builder = new StringBuilder(); builder.append(" actionKey = ").append(actionKey).append("\n"); builder.append(" digestKey = "); - if (digest == null) { - builder.append(Digest.fromMetadata(mdMap)).append(" (from mdMap)\n"); + if (md5Digest == null) { + builder.append(DigestUtils.fromMetadata(mdMap)).append(" (from mdMap)\n"); } else { - builder.append(digest).append("\n"); + builder.append(md5Digest).append("\n"); } if (discoversInputs()) { diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java b/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java index 8cbfd7705b..f0b47f857d 100644 --- a/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java +++ b/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java @@ -26,7 +26,6 @@ import com.google.devtools.build.lib.util.StringIndexer; import com.google.devtools.build.lib.util.VarInt; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.UnixGlob; - import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -352,14 +351,18 @@ public class CompactPersistentActionCache implements ActionCache { // + 16 bytes for the digest // + 5 bytes max for the file list length // + 5 bytes max for each file id - int maxSize = VarInt.MAX_VARINT_SIZE + actionKeyBytes.length + Digest.MD5_SIZE - + VarInt.MAX_VARINT_SIZE + files.size() * VarInt.MAX_VARINT_SIZE; + int maxSize = + VarInt.MAX_VARINT_SIZE + + actionKeyBytes.length + + Md5Digest.MD5_SIZE + + VarInt.MAX_VARINT_SIZE + + files.size() * VarInt.MAX_VARINT_SIZE; ByteArrayOutputStream sink = new ByteArrayOutputStream(maxSize); VarInt.putVarInt(actionKeyBytes.length, sink); sink.write(actionKeyBytes); - entry.getFileDigest().write(sink); + DigestUtils.write(entry.getFileDigest(), sink); VarInt.putVarInt(entry.discoversInputs() ? files.size() : NO_INPUT_DISCOVERY_COUNT, sink); for (String file : files) { @@ -385,7 +388,7 @@ public class CompactPersistentActionCache implements ActionCache { source.get(actionKeyBytes); String actionKey = new String(actionKeyBytes, ISO_8859_1); - Digest digest = Digest.read(source); + Md5Digest md5Digest = DigestUtils.read(source); int count = VarInt.getVarInt(source); ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); @@ -400,8 +403,8 @@ public class CompactPersistentActionCache implements ActionCache { if (source.remaining() > 0) { throw new IOException("serialized entry data has not been fully decoded"); } - return new Entry(actionKey, - count == NO_INPUT_DISCOVERY_COUNT ? null : builder.build(), digest); + return new Entry( + actionKey, count == NO_INPUT_DISCOVERY_COUNT ? null : builder.build(), md5Digest); } catch (BufferUnderflowException e) { throw new IOException("encoded entry data is incomplete", e); } diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java b/src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java deleted file mode 100644 index 06989198d2..0000000000 --- a/src/main/java/com/google/devtools/build/lib/actions/cache/Digest.java +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2014 The Bazel Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package com.google.devtools.build.lib.actions.cache; - -import com.google.common.hash.HashCode; -import com.google.devtools.build.lib.util.Fingerprint; -import com.google.devtools.build.lib.util.VarInt; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Map; - -/** - * A value class for capturing and comparing MD5-based digests. - * - * <p>Note that this class is responsible for digesting file metadata in an - * order-independent manner. Care must be taken to do this properly. The - * digest must be a function of the set of (path, metadata) tuples. While the - * order of these pairs must not matter, it would <b>not</b> be safe to make - * the digest be a function of the set of paths and the set of metadata. - * - * <p>Note that the (path, metadata) tuples must be unique, otherwise the - * XOR-based approach will fail. - */ -public class Digest { - - static final int MD5_SIZE = 16; - - private final byte[] digest; - - /** - * Construct the digest from the given bytes. - * @param digest an MD5 digest. Must be sized properly. - */ - private Digest(byte[] digest) { - this.digest = digest; - } - - /** - * @param source the byte buffer source. - * @return the digest from the given buffer. - * @throws IOException if the byte buffer is incorrectly formatted. - */ - public static Digest read(ByteBuffer source) throws IOException { - int size = VarInt.getVarInt(source); - if (size != MD5_SIZE) { - throw new IOException("Unexpected digest length: " + size); - } - byte[] bytes = new byte[size]; - source.get(bytes); - return new Digest(bytes); - } - - /** - * Write the digest to the output stream. - */ - public void write(OutputStream sink) throws IOException { - VarInt.putVarInt(digest.length, sink); - sink.write(digest); - } - - /** - * @param mdMap A collection of (execPath, Metadata) pairs. - * Values may be null. - * @return an <b>order-independent</b> digest from the given "set" of - * (path, metadata) pairs. - */ - public static Digest fromMetadata(Map<String, Metadata> mdMap) { - byte[] result = new byte[MD5_SIZE]; - // Profiling showed that MD5 engine instantiation was a hotspot, so create one instance for - // this computation to amortize its cost. - Fingerprint fp = new Fingerprint(); - for (Map.Entry<String, Metadata> entry : mdMap.entrySet()) { - xorWith(result, getDigest(fp, entry.getKey(), entry.getValue())); - } - return new Digest(result); - } - - /** - * @return this Digest as a Metadata with no mtime. - */ - public Metadata asMetadata() { - return new Metadata(digest); - } - - @Override - public int hashCode() { - return Arrays.hashCode(digest); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof Digest) && Arrays.equals(digest, ((Digest) obj).digest); - } - - @Override - public String toString() { - return HashCode.fromBytes(digest).toString(); - } - - private static byte[] getDigest(Fingerprint fp, String execPath, Metadata md) { - fp.addStringLatin1(execPath); - - if (md == null) { - // Move along, nothing to see here. - } else if (md.digest == null) { - // Use the timestamp if the digest is not present, but not both. - // Modifying a timestamp while keeping the contents of a file the - // same should not cause rebuilds. - fp.addLong(md.mtime); - } else { - fp.addBytes(md.digest); - } - return fp.digestAndReset(); - } - - /** - * Compute lhs ^= rhs bitwise operation of the arrays. - */ - private static void xorWith(byte[] lhs, byte[] rhs) { - for (int i = 0; i < lhs.length; i++) { - lhs[i] ^= rhs[i]; - } - } -} diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/DigestUtils.java b/src/main/java/com/google/devtools/build/lib/actions/cache/DigestUtils.java index c61e943eda..ef4d9dc4d9 100644 --- a/src/main/java/com/google/devtools/build/lib/actions/cache/DigestUtils.java +++ b/src/main/java/com/google/devtools/build/lib/actions/cache/DigestUtils.java @@ -17,21 +17,33 @@ import com.google.common.io.BaseEncoding; import com.google.devtools.build.lib.profiler.Profiler; import com.google.devtools.build.lib.profiler.ProfilerTask; import com.google.devtools.build.lib.util.BlazeClock; +import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.util.LoggingUtil; import com.google.devtools.build.lib.util.Preconditions; +import com.google.devtools.build.lib.util.VarInt; import com.google.devtools.build.lib.vfs.Path; - import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; - import javax.annotation.Nullable; /** * Utility class for getting md5 digests of files. + * + * <p>Note that this class is responsible for digesting file metadata in an order-independent + * manner. Care must be taken to do this properly. The digest must be a function of the set of + * (path, metadata) tuples. While the order of these pairs must not matter, it would <b>not</b> be + * safe to make the digest be a function of the set of paths and the set of metadata. + * + * <p>Note that the (path, metadata) tuples must be unique, otherwise the XOR-based approach will + * fail. */ public class DigestUtils { + // Object to synchronize on when serializing large file reads. private static final Object MD5_LOCK = new Object(); private static final AtomicBoolean MULTI_THREADED_DIGEST = new AtomicBoolean(false); @@ -122,4 +134,63 @@ public class DigestUtils { return getDigestInternal(path); } } + + /** + * @param source the byte buffer source. + * @return the digest from the given buffer. + * @throws IOException if the byte buffer is incorrectly formatted. + */ + public static Md5Digest read(ByteBuffer source) throws IOException { + int size = VarInt.getVarInt(source); + if (size != Md5Digest.MD5_SIZE) { + throw new IOException("Unexpected digest length: " + size); + } + byte[] bytes = new byte[size]; + source.get(bytes); + return new Md5Digest(bytes); + } + + /** Write the digest to the output stream. */ + public static void write(Md5Digest digest, OutputStream sink) throws IOException { + VarInt.putVarInt(digest.getDigestBytesUnsafe().length, sink); + sink.write(digest.getDigestBytesUnsafe()); + } + + /** + * @param mdMap A collection of (execPath, Metadata) pairs. Values may be null. + * @return an <b>order-independent</b> digest from the given "set" of (path, metadata) pairs. + */ + public static Md5Digest fromMetadata(Map<String, Metadata> mdMap) { + byte[] result = new byte[Md5Digest.MD5_SIZE]; + // Profiling showed that MD5 engine instantiation was a hotspot, so create one instance for + // this computation to amortize its cost. + Fingerprint fp = new Fingerprint(); + for (Map.Entry<String, Metadata> entry : mdMap.entrySet()) { + xorWith(result, getDigest(fp, entry.getKey(), entry.getValue())); + } + return new Md5Digest(result); + } + + private static byte[] getDigest(Fingerprint fp, String execPath, Metadata md) { + fp.addStringLatin1(execPath); + + if (md == null) { + // Move along, nothing to see here. + } else if (md.digest == null) { + // Use the timestamp if the digest is not present, but not both. + // Modifying a timestamp while keeping the contents of a file the + // same should not cause rebuilds. + fp.addLong(md.mtime); + } else { + fp.addBytes(md.digest); + } + return fp.digestAndReset(); + } + + /** Compute lhs ^= rhs bitwise operation of the arrays. */ + private static void xorWith(byte[] lhs, byte[] rhs) { + for (int i = 0; i < lhs.length; i++) { + lhs[i] ^= rhs[i]; + } + } } diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/Md5Digest.java b/src/main/java/com/google/devtools/build/lib/actions/cache/Md5Digest.java new file mode 100644 index 0000000000..75441b52e7 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/actions/cache/Md5Digest.java @@ -0,0 +1,59 @@ +// Copyright 2014 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.actions.cache; + +import com.google.common.hash.HashCode; +import com.google.devtools.build.lib.util.Preconditions; +import java.util.Arrays; + +/** A value class for capturing and comparing MD5-based digests. */ +public class Md5Digest { + + static final int MD5_SIZE = 16; + private final byte[] digest; + + /** + * Construct the digest from the given bytes. + * + * @param digest an MD5 digest. Must be sized properly. + */ + public Md5Digest(byte[] digest) { + Preconditions.checkState(digest.length == MD5_SIZE); + this.digest = digest; + } + + @Override + public int hashCode() { + // We are already dealing with the digest so we can just use portion of it as a hash code. + return digest[0] + (digest[1] << 8) + (digest[2] << 16) + (digest[3] << 24); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof Md5Digest) && Arrays.equals(digest, ((Md5Digest) obj).digest); + } + + @Override + public String toString() { + return HashCode.fromBytes(digest).toString(); + } + + /** + * Warning: This is a mutable wrapper and does not necessarily own the underlying byte[]. Don't + * modify it unless you are sure about it. + */ + public byte[] getDigestBytesUnsafe() { + return digest; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/Metadata.java b/src/main/java/com/google/devtools/build/lib/actions/cache/Metadata.java index 21891300b0..316fd234e5 100644 --- a/src/main/java/com/google/devtools/build/lib/actions/cache/Metadata.java +++ b/src/main/java/com/google/devtools/build/lib/actions/cache/Metadata.java @@ -18,7 +18,6 @@ import com.google.common.io.BaseEncoding; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.util.Preconditions; - import java.util.Arrays; import java.util.Date; diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java b/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java index 6fcb964840..5e74180014 100644 --- a/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java +++ b/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java @@ -18,7 +18,6 @@ import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact; import com.google.devtools.build.lib.actions.MiddlemanAction; import com.google.devtools.build.lib.vfs.FileStatus; - import java.io.IOException; /** Retrieves {@link Metadata} of {@link Artifact}s, and inserts virtual metadata as well. */ @@ -46,8 +45,8 @@ public interface MetadataHandler { */ Metadata getMetadata(Artifact artifact) throws IOException; - /** Sets digest for virtual artifacts (e.g. middlemen). {@code digest} must not be null. */ - void setDigestForVirtualArtifact(Artifact artifact, Digest digest); + /** Sets digest for virtual artifacts (e.g. middlemen). {@code md5Digest} must not be null. */ + void setDigestForVirtualArtifact(Artifact artifact, Md5Digest md5Digest); /** * Registers the given output as contents of a TreeArtifact, without injecting its digest. |