| // Copyright 2017 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.skyframe; |
| |
| import com.google.common.collect.Interner; |
| import com.google.devtools.build.lib.concurrent.BlazeInterners; |
| import com.google.devtools.build.lib.util.Preconditions; |
| |
| /** |
| * Basic implementation of {@link SkyKey}. Potentially non-optimal from a memory perspective, since |
| * it uses fields for hash code and {@link #functionName}. The latter should be implemented instead |
| * using polymorphism. See {@code ArtifactSkyKey} for an example. |
| */ |
| public class LegacySkyKey implements SkyKey { |
| private static final Interner<SkyKey> SKY_KEY_INTERNER = BlazeInterners.newWeakInterner(); |
| |
| /** |
| * Creates a {@link SkyKey}. Prefer instead creating custom SkyKeys that are their own arguments, |
| * saving the object wrapper. See {@code ArtifactSkyKey} for an example. |
| */ |
| // TODO(janakr): migrate users of this to use custom SkyKey subclasses and delete this. |
| @Deprecated |
| public static SkyKey create(SkyFunctionName functionName, Object argument) { |
| // Intern to save memory. |
| return SKY_KEY_INTERNER.intern(new LegacySkyKey(functionName, argument)); |
| } |
| |
| private final SkyFunctionName functionName; |
| |
| /** |
| * The name of the value. |
| * |
| * <p>This is deliberately an untyped Object so that we can use arbitrary value types (e.g., |
| * Labels, PathFragments, BuildConfigurations, etc.) as value names without incurring |
| * serialization costs in the in-memory implementation of the graph. |
| */ |
| private final Object argument; |
| |
| /** |
| * Cache the hash code for this object. It might be expensive to compute. It is transient because |
| * argument's hash code might not be stable across JVM instances. |
| */ |
| private transient int hashCode; |
| |
| private LegacySkyKey(SkyFunctionName functionName, Object argument) { |
| this.functionName = Preconditions.checkNotNull(functionName); |
| this.argument = Preconditions.checkNotNull(argument); |
| // 'hashCode' is non-volatile and non-final, so this write may in fact *not* be visible to other |
| // threads. But this isn't a concern from a correctness perspective. See the comments in |
| // #hashCode for more details. |
| this.hashCode = computeHashCode(); |
| } |
| |
| @Override |
| public SkyFunctionName functionName() { |
| return functionName; |
| } |
| |
| @Override |
| public Object argument() { |
| return argument; |
| } |
| |
| @Override |
| public String toString() { |
| return functionName + ":" + argument; |
| } |
| |
| @Override |
| public int hashCode() { |
| // We use the hash code caching strategy employed by java.lang.String. There are three subtle |
| // things going on here: |
| // |
| // (1) We use a value of 0 to indicate that the hash code hasn't been computed and cached yet. |
| // Yes, this means that if the hash code is really 0 then we will "recompute" it each time. But |
| // this isn't a problem in practice since a hash code of 0 should be rare. |
| // |
| // (2) Since we have no synchronization, multiple threads can race here thinking there are the |
| // first one to compute and cache the hash code. |
| // |
| // (3) Moreover, since 'hashCode' is non-volatile, the cached hash code value written from one |
| // thread may not be visible by another. |
| // |
| // All three of these issues are benign from a correctness perspective; in the end we have no |
| // overhead from synchronization, at the cost of potentially computing the hash code more than |
| // once. |
| int h = hashCode; |
| if (h == 0) { |
| h = computeHashCode(); |
| hashCode = h; |
| } |
| return h; |
| } |
| |
| private int computeHashCode() { |
| return 31 * functionName.hashCode() + argument.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| LegacySkyKey other = (LegacySkyKey) obj; |
| if (hashCode() != other.hashCode()) { |
| return false; |
| } |
| return functionName.equals(other.functionName) && argument.equals(other.argument); |
| } |
| } |