aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactSkyKey.java
blob: 770fecc4e2cbb406d39c404f32ae520ae10ceeee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// 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.skyframe;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Interner;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
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 com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import java.util.Collection;

/**
 * A utility class for {@link SkyKey}s coming from {@link Artifact}s. Source artifacts are checked
 * for existence, while output artifacts imply creation of the output file.
 *
 * <p>There are effectively three kinds of output artifact values corresponding to these keys. The
 * first corresponds to an ordinary artifact {@link FileArtifactValue}. It stores the relevant data
 * for the artifact -- digest/mtime and size. The second corresponds to either an "aggregating"
 * artifact -- the output of an aggregating middleman action -- or a TreeArtifact. It stores the
 * relevant data of all its inputs, as well as a combined digest for itself.
 */
@Immutable
@ThreadSafe
public final class ArtifactSkyKey {
  private static final Interner<OwnedArtifact> INTERNER = BlazeInterners.newWeakInterner();

  private ArtifactSkyKey() {}

  @ThreadSafe
  public static SkyKey key(Artifact artifact, boolean isMandatory) {
    return INTERNER.intern(
        artifact.isSourceArtifact()
            ? new OwnedArtifact(artifact, isMandatory)
            : new OwnedArtifact(artifact));
  }

  private static final Function<Artifact, SkyKey> TO_MANDATORY_KEY =
      new Function<Artifact, SkyKey>() {
        @Override
        public SkyKey apply(Artifact artifact) {
          return key(artifact, true);
        }
      };

  @ThreadSafe
  public static Iterable<SkyKey> mandatoryKeys(Iterable<Artifact> artifacts) {
    return Iterables.transform(artifacts, TO_MANDATORY_KEY);
  }

  private static final Function<OwnedArtifact, Artifact> TO_ARTIFACT =
      new Function<OwnedArtifact, Artifact>() {
        @Override
        public Artifact apply(OwnedArtifact key) {
          return key.getArtifact();
        }
      };

  public static Collection<Artifact> artifacts(Collection<? extends OwnedArtifact> keys) {
    return Collections2.transform(keys, TO_ARTIFACT);
  }

  public static Artifact artifact(SkyKey key) {
    return TO_ARTIFACT.apply((OwnedArtifact) key.argument());
  }

  public static boolean equalWithOwner(Artifact first, Artifact second) {
    return first.equals(second) && first.getArtifactOwner().equals(second.getArtifactOwner());
  }

  /**
   * Artifacts are compared using just their paths, but in Skyframe, the configured target that owns
   * an artifact must also be part of the comparison. For example, suppose we build //foo:foo in
   * configurationA, yielding artifact foo.out. If we change the configuration to configurationB in
   * such a way that the path to the artifact does not change, requesting foo.out from the graph
   * will result in the value entry for foo.out under configurationA being returned. This would
   * prevent caching the graph in different configurations, and also causes big problems with change
   * pruning, which assumes the invariant that a value's first dependency will always be the same.
   * In this case, the value entry's old dependency on //foo:foo in configurationA would cause it to
   * request (//foo:foo, configurationA) from the graph, causing an undesired re-analysis of
   * (//foo:foo, configurationA).
   *
   * <p>In order to prevent that, instead of using Artifacts as keys in the graph, we use
   * OwnedArtifacts, which compare for equality using both the Artifact, and the owner. The effect
   * is functionally that of making Artifact.equals() check the owner, but only within Skyframe,
   * since outside of Skyframe it is quite crucial that Artifacts with different owners be able to
   * compare equal.
   */
  static class OwnedArtifact implements SkyKey {
    private final Artifact artifact;
    // Always true for derived artifacts.
    private final boolean isMandatory;
    // TODO(janakr): we may want to remove this field in the future. The expensive hash computation
    // is already cached one level down (in the Artifact), so the CPU overhead here may not be
    // worth the memory. However, when running with +CompressedOops, this field is free, so we leave
    // it. When running with -CompressedOops, we might be able to save memory by using polymorphism
    // for isMandatory and dropping this field.
    private int hashCode = 0;

    /** Constructs an OwnedArtifact wrapper for a source artifact. */
    private OwnedArtifact(Artifact sourceArtifact, boolean mandatory) {
      Preconditions.checkArgument(sourceArtifact.isSourceArtifact());
      this.artifact = Preconditions.checkNotNull(sourceArtifact);
      this.isMandatory = mandatory;
    }

    /**
     * Constructs an OwnedArtifact wrapper for a derived artifact. The mandatory attribute is not
     * needed because a derived artifact must be a mandatory input for some action in order to
     * ensure that it is built in the first place. If it fails to build, then that fact is cached in
     * the node, so any action that has it as a non-mandatory input can retrieve that information
     * from the node.
     */
    private OwnedArtifact(Artifact derivedArtifact) {
      this.artifact = Preconditions.checkNotNull(derivedArtifact);
      Preconditions.checkArgument(!derivedArtifact.isSourceArtifact(), derivedArtifact);
      this.isMandatory = true; // Unused.
    }

    @Override
    public SkyFunctionName functionName() {
      return SkyFunctions.ARTIFACT;
    }

    @Override
    public OwnedArtifact argument() {
      return this;
    }

    @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. Note that we probably don't need to worry about
      // multiple inefficient reads of 'hashCode' on the same thread since it's non-volatile.
      //
      // 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.
      if (hashCode == 0) {
        hashCode = computeHashCode();
      }
      return hashCode;
    }

    private int computeHashCode() {
      int initialHash = artifact.hashCode() + artifact.getArtifactOwner().hashCode();
      return isMandatory ? initialHash : 47 * initialHash + 1;
    }

    @Override
    public boolean equals(Object that) {
      if (this == that) {
        return true;
      }
      if (!(that instanceof OwnedArtifact)) {
        return false;
      }
      OwnedArtifact thatOwnedArtifact = ((OwnedArtifact) that);
      Artifact thatArtifact = thatOwnedArtifact.artifact;
      return equalWithOwner(artifact, thatArtifact) && isMandatory == thatOwnedArtifact.isMandatory;
    }

    Artifact getArtifact() {
      return artifact;
    }

    /**
     * Returns whether the artifact is a mandatory input of its requesting action. May only be
     * called for source artifacts, since a derived artifact must be a mandatory input of some
     * action in order to have been built in the first place.
     */
    public boolean isMandatory() {
      Preconditions.checkState(artifact.isSourceArtifact(), artifact);
      return isMandatory;
    }

    @Override
    public String toString() {
      return artifact.prettyPrint() + " " + artifact.getArtifactOwner();
    }
  }
}