Always register dependencies as we encounter them with skylark import lookup caching. Otherwise a nested load statement may throw an exception prior to us having registered the dependencies while using cached values.
PiperOrigin-RevId: 232959833
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
index 007fa9c..789ce09 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectFunction.java
@@ -181,7 +181,7 @@
env.getValueOrThrow(importFileKey, SkylarkImportFailedException.class);
} else {
skylarkImportLookupValue =
- skylarkImportLookupFunctionForInlining.computeWithInlineCalls(importFileKey, env, 1);
+ skylarkImportLookupFunctionForInlining.computeWithInlineCalls(importFileKey, env);
}
if (skylarkImportLookupValue == null) {
Preconditions.checkState(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/CachedSkylarkImportLookupValueAndDeps.java b/src/main/java/com/google/devtools/build/lib/skyframe/CachedSkylarkImportLookupValueAndDeps.java
index 6cf404a..69bd17d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/CachedSkylarkImportLookupValueAndDeps.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/CachedSkylarkImportLookupValueAndDeps.java
@@ -18,7 +18,6 @@
import com.google.devtools.build.skyframe.SkyKey;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
@@ -34,14 +33,16 @@
this.deps = deps;
}
- void traverse(DepGroupConsumer depGroupConsumer) throws InterruptedException {
- deps.traverse(depGroupConsumer, new HashSet<>());
+ void traverse(
+ DepGroupConsumer depGroupConsumer,
+ Set<CachedSkylarkImportLookupFunctionDeps> visitedGlobalDeps)
+ throws InterruptedException {
+ deps.traverse(depGroupConsumer, visitedGlobalDeps);
}
SkylarkImportLookupValue getValue() {
return value;
}
-
static CachedSkylarkImportLookupValueAndDeps.Builder newBuilder() {
return new CachedSkylarkImportLookupValueAndDeps.Builder();
}
@@ -88,7 +89,7 @@
}
}
- private static class CachedSkylarkImportLookupFunctionDeps {
+ static class CachedSkylarkImportLookupFunctionDeps {
private final ImmutableList<Iterable<SkyKey>> directDeps;
private final ImmutableList<CachedSkylarkImportLookupFunctionDeps> transitiveDeps;
@@ -142,6 +143,11 @@
}
}
+ // Reference equality is fine here as most often, the deps objects for any given import
+ // label will be the same. It is technically possible for the same key to have two copies
+ // of associated cached deps but this just means we may visit the same deps more than once. We
+ // don't run the risk of chasing a cycle because these are detected prior to having any
+ // cached values.
private void traverse(
DepGroupConsumer depGroupConsumer, Set<CachedSkylarkImportLookupFunctionDeps> visitedDeps)
throws InterruptedException {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index 724f7fc..d20d47e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -614,8 +614,7 @@
// Inlining calls to SkylarkImportLookupFunction
for (SkyKey importLookupKey : importLookupKeys) {
SkyValue skyValue =
- skylarkImportLookupFunctionForInlining.computeWithInlineCalls(
- importLookupKey, env, importLookupKeys.size());
+ skylarkImportLookupFunctionForInlining.computeWithInlineCalls(importLookupKey, env);
if (skyValue == null) {
Preconditions.checkState(
env.valuesMissing(), "no starlark import value for %s", importLookupKey);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
index 6286a1f..c24b546 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkImportLookupFunction.java
@@ -39,6 +39,7 @@
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.SkylarkExportable;
import com.google.devtools.build.lib.packages.WorkspaceFileValue;
+import com.google.devtools.build.lib.skyframe.CachedSkylarkImportLookupValueAndDeps.CachedSkylarkImportLookupFunctionDeps;
import com.google.devtools.build.lib.skyframe.SkylarkImportLookupValue.SkylarkImportLookupKey;
import com.google.devtools.build.lib.syntax.AssignmentStatement;
import com.google.devtools.build.lib.syntax.BuildFileAST;
@@ -61,9 +62,11 @@
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.ValueOrException;
-import java.util.LinkedHashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -103,8 +106,9 @@
key.workspaceChunk,
key.workspacePath,
env,
- /*alreadyVisited=*/ null,
- /*inlineCachedValueBuilder=*/ null);
+ /*visitedNested=*/ null,
+ /*inlineCachedValueBuilder=*/ null,
+ /*visitedGlobalDeps=*/ null);
} catch (InconsistentFilesystemException e) {
throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
} catch (SkylarkImportFailedException e) {
@@ -113,19 +117,21 @@
}
@Nullable
- SkylarkImportLookupValue computeWithInlineCalls(
- SkyKey skyKey, Environment env, int expectedSizeOfVisitedSet)
+ SkylarkImportLookupValue computeWithInlineCalls(SkyKey skyKey, Environment env)
throws InconsistentFilesystemException, SkylarkImportFailedException, InterruptedException {
- // We use the visited set to track if there are any cyclic dependencies when loading the
- // skylark file.
- LinkedHashMap<Label, CachedSkylarkImportLookupValueAndDeps> visited =
- Maps.newLinkedHashMapWithExpectedSize(expectedSizeOfVisitedSet);
+ // We use the visitedNested set to track if there are any cyclic dependencies when loading the
+ // skylark file and the visitedGlobalDeps set to avoid re-registering previously seen
+ // dependencies. Note that the visitedNested set must use insertion order to display the correct
+ // error.
CachedSkylarkImportLookupValueAndDeps cachedSkylarkImportLookupValueAndDeps =
- computeWithInlineCallsInternal(skyKey, env, visited);
+ computeWithInlineCallsInternal(
+ skyKey,
+ env,
+ /*visitedNested=*/ new LinkedHashSet<>(),
+ /*visitedGlobalDeps=*/ new HashSet<>());
if (cachedSkylarkImportLookupValueAndDeps == null) {
return null;
}
- cachedSkylarkImportLookupValueAndDeps.traverse(env::registerDependencies);
return cachedSkylarkImportLookupValueAndDeps.getValue();
}
@@ -133,14 +139,18 @@
private CachedSkylarkImportLookupValueAndDeps computeWithInlineCallsInternal(
SkyKey skyKey,
Environment env,
- LinkedHashMap<Label, CachedSkylarkImportLookupValueAndDeps> visited)
+ Set<Label> visitedNested,
+ Set<CachedSkylarkImportLookupFunctionDeps> visitedGlobalDeps)
throws InconsistentFilesystemException, SkylarkImportFailedException, InterruptedException {
SkylarkImportLookupKey key = (SkylarkImportLookupKey) skyKey.argument();
- CachedSkylarkImportLookupValueAndDeps precomputedResult = visited.get(key.importLabel);
- if (precomputedResult != null) {
- // We have already registered all the deps for this value.
- return precomputedResult;
+ Label importLabel = key.importLabel;
+
+ if (!visitedNested.add(importLabel)) {
+ ImmutableList<Label> cycle =
+ CycleUtils.splitIntoPathAndChain(Predicates.equalTo(importLabel), visitedNested).second;
+ throw new SkylarkImportFailedException("Starlark import cycle: " + cycle);
}
+
// Note that we can't block other threads on the computation of this value due to a potential
// deadlock on a cycle. Although we are repeating some work, it is possible we have an import
// cycle where one thread starts at one side of the cycle and the other thread starts at the
@@ -148,6 +158,7 @@
CachedSkylarkImportLookupValueAndDeps cachedSkylarkImportLookupValueAndDeps =
skylarkImportLookupValueCache.getIfPresent(skyKey);
if (cachedSkylarkImportLookupValueAndDeps != null) {
+ cachedSkylarkImportLookupValueAndDeps.traverse(env::registerDependencies, visitedGlobalDeps);
return cachedSkylarkImportLookupValueAndDeps;
}
@@ -165,19 +176,19 @@
inlineCachedValueBuilder::noteException);
SkylarkImportLookupValue value =
computeInternal(
- key.importLabel,
+ importLabel,
key.inWorkspace,
key.workspaceChunk,
key.workspacePath,
recordingEnv,
- Preconditions.checkNotNull(visited, key.importLabel),
- inlineCachedValueBuilder);
+ Preconditions.checkNotNull(visitedNested, importLabel),
+ inlineCachedValueBuilder,
+ visitedGlobalDeps);
if (value != null) {
inlineCachedValueBuilder.setValue(value);
cachedSkylarkImportLookupValueAndDeps = inlineCachedValueBuilder.build();
skylarkImportLookupValueCache.put(skyKey, cachedSkylarkImportLookupValueAndDeps);
- visited.put(key.importLabel, cachedSkylarkImportLookupValueAndDeps);
}
return cachedSkylarkImportLookupValueAndDeps;
}
@@ -191,7 +202,7 @@
skylarkImportLookupValueCache =
CacheBuilder.newBuilder()
.concurrencyLevel(BlazeInterners.concurrencyLevel())
- .maximumSize(10000)
+ .maximumSize(20000)
.recordStats()
.build();
}
@@ -206,8 +217,9 @@
int workspaceChunk,
RootedPath workspacePath,
Environment env,
- @Nullable LinkedHashMap<Label, CachedSkylarkImportLookupValueAndDeps> alreadyVisited,
- @Nullable CachedSkylarkImportLookupValueAndDeps.Builder inlineCachedValueBuilder)
+ @Nullable Set<Label> visitedNested,
+ @Nullable CachedSkylarkImportLookupValueAndDeps.Builder inlineCachedValueBuilder,
+ @Nullable Set<CachedSkylarkImportLookupFunctionDeps> visitedGlobalDeps)
throws InconsistentFilesystemException, SkylarkImportFailedException, InterruptedException {
PathFragment filePath = fileLabel.toPathFragment();
@@ -293,7 +305,7 @@
}
Map<SkyKey, SkyValue> skylarkImportMap;
boolean valuesMissing = false;
- if (alreadyVisited == null) {
+ if (visitedNested == null) {
// Not inlining.
Map<SkyKey, ValueOrException<SkylarkImportFailedException>> values =
@@ -314,13 +326,6 @@
inlineCachedValueBuilder,
"Expected inline cached value builder to be not-null when inlining.");
// Inlining calls to SkylarkImportLookupFunction.
- if (alreadyVisited.containsKey(fileLabel)) {
- ImmutableList<Label> cycle =
- CycleUtils.splitIntoPathAndChain(Predicates.equalTo(fileLabel), alreadyVisited.keySet())
- .second;
- throw new SkylarkImportFailedException("Starlark import cycle: " + cycle);
- }
- alreadyVisited.put(fileLabel, null);
skylarkImportMap = Maps.newHashMapWithExpectedSize(imports.size());
Preconditions.checkState(
@@ -330,7 +335,8 @@
Environment strippedEnv = ((RecordingSkyFunctionEnvironment) env).getDelegate();
for (SkyKey importLookupKey : importLookupKeys) {
CachedSkylarkImportLookupValueAndDeps cachedValue =
- this.computeWithInlineCallsInternal(importLookupKey, strippedEnv, alreadyVisited);
+ this.computeWithInlineCallsInternal(
+ importLookupKey, strippedEnv, visitedNested, visitedGlobalDeps);
if (cachedValue == null) {
Preconditions.checkState(
env.valuesMissing(), "no starlark import value for %s", importLookupKey);
@@ -345,7 +351,7 @@
}
}
// All imports traversed, this key can no longer be part of a cycle.
- Preconditions.checkState(alreadyVisited.remove(fileLabel) == null, fileLabel);
+ Preconditions.checkState(visitedNested.remove(fileLabel), fileLabel);
}
if (valuesMissing) {
// This means some imports are unavailable.
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/CachedSkylarkImportLookupValueAndDepsTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/CachedSkylarkImportLookupValueAndDepsTest.java
index 621dedb..c45b1c8 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/CachedSkylarkImportLookupValueAndDepsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/CachedSkylarkImportLookupValueAndDepsTest.java
@@ -19,6 +19,7 @@
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -77,7 +78,7 @@
.build();
List<Iterable<SkyKey>> registeredDeps = new ArrayList<>();
- p.traverse(registeredDeps::add);
+ p.traverse(registeredDeps::add, /*visitedGlobalDeps=*/ new HashSet<>());
assertThat(registeredDeps)
.containsExactly(
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
index 3b342a7..90c7efa 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkIntegrationTest.java
@@ -2809,9 +2809,9 @@
} catch (BuildFileContainsErrorsException e) {
// The reason that this is an exception and not reported to the event handler is that the
// error is reported by the parent sky function, which we don't have here.
- assertThat(e).hasMessageThat().contains("Starlark import cycle");
- assertThat(e).hasMessageThat().contains("test/skylark:ext1.bzl");
- assertThat(e).hasMessageThat().contains("test/skylark:ext2.bzl");
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Starlark import cycle: [//test/skylark:ext1.bzl, //test/skylark:ext2.bzl]");
}
}
@@ -2835,10 +2835,11 @@
} catch (BuildFileContainsErrorsException e) {
// The reason that this is an exception and not reported to the event handler is that the
// error is reported by the parent sky function, which we don't have here.
- assertThat(e).hasMessageThat().contains("Starlark import cycle");
- assertThat(e).hasMessageThat().contains("//test/skylark:ext2.bzl");
- assertThat(e).hasMessageThat().contains("//test/skylark:ext3.bzl");
- assertThat(e).hasMessageThat().contains("//test/skylark:ext4.bzl");
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ "Starlark import cycle: [//test/skylark:ext2.bzl, "
+ + "//test/skylark:ext3.bzl, //test/skylark:ext4.bzl]");
}
}
}