Fix race condition in `RuleConfiguredTargetValue.clear`.
Also create a common base class for RuleConfiguredTargetValue and NonRuleConfiguredTargetValue, so that code doesn't need to be duplicated.
PiperOrigin-RevId: 624227864
Change-Id: I92dd138f662422d765e5ba927a1cf9312bb90078
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 325ec13..7ed740b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -3124,6 +3124,7 @@
java_library(
name = "rule_configured_target_value",
srcs = [
+ "BaseRuleConfiguredTargetValue.java",
"NonRuleConfiguredTargetValue.java",
"RuleConfiguredTargetValue.java",
],
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BaseRuleConfiguredTargetValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/BaseRuleConfiguredTargetValue.java
new file mode 100644
index 0000000..b7a11df
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BaseRuleConfiguredTargetValue.java
@@ -0,0 +1,72 @@
+// Copyright 2024 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 static com.google.devtools.build.lib.analysis.config.CommonOptions.EMPTY_OPTIONS;
+
+import com.google.common.base.Preconditions;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.packages.Package;
+import javax.annotation.Nullable;
+
+/** Common base class for configured target values for rules and non-rules. */
+abstract class BaseRuleConfiguredTargetValue<T extends ConfiguredTarget>
+ implements ConfiguredTargetValue {
+ // This variable is non-final because it may be clear()ed to save memory. It is null only after
+ // clear(true) is called.
+ @Nullable private T configuredTarget;
+
+ // May be null after clearing; because transitive packages are not tracked; or after
+ // deserialization.
+ @Nullable private transient NestedSet<Package> transitivePackages;
+
+ BaseRuleConfiguredTargetValue(
+ T configuredTarget, @Nullable NestedSet<Package> transitivePackages) {
+ this.configuredTarget = Preconditions.checkNotNull(configuredTarget);
+ this.transitivePackages = transitivePackages;
+ }
+
+ @Nullable // May be null after clearing.
+ @Override
+ public T getConfiguredTarget() {
+ return configuredTarget;
+ }
+
+ @Nullable
+ @Override
+ public NestedSet<Package> getTransitivePackages() {
+ return transitivePackages;
+ }
+
+ @Override
+ public void clear(boolean clearEverything) {
+ // Snapshot this because this method is sometimes called from multiple threads. We don't need
+ // to actually synchronize: both threads will have the same logic and so there's no risk of
+ // a race condition.
+ T ct = this.configuredTarget;
+ if (ct != null
+ && ct.getConfigurationKey() != null
+ && ct.getConfigurationChecksum().equals(EMPTY_OPTIONS.checksum())) {
+ // Keep these to avoid the need to re-create them later, they are dependencies of the empty
+ // configuration key and will never change.
+ return;
+ }
+ if (clearEverything) {
+ this.configuredTarget = null;
+ }
+ this.transitivePackages = null;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/NonRuleConfiguredTargetValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/NonRuleConfiguredTargetValue.java
index cfaaaee..856256e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/NonRuleConfiguredTargetValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/NonRuleConfiguredTargetValue.java
@@ -13,10 +13,8 @@
// limitations under the License.
package com.google.devtools.build.lib.skyframe;
-import static com.google.devtools.build.lib.analysis.config.CommonOptions.EMPTY_OPTIONS;
import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
@@ -33,13 +31,8 @@
@ThreadSafe
// Reached via OutputFileConfiguredTarget.
@AutoCodec(explicitlyAllowClass = RuleConfiguredTarget.class)
-public final class NonRuleConfiguredTargetValue implements ConfiguredTargetValue {
- // These variables are only non-final because they may be clear()ed to save memory.
- // configuredTarget is null only after it is cleared.
- @Nullable private ConfiguredTarget configuredTarget;
-
- // May be null either after clearing or because transitive packages are not tracked.
- @Nullable private NestedSet<Package> transitivePackages;
+public final class NonRuleConfiguredTargetValue
+ extends BaseRuleConfiguredTargetValue<ConfiguredTarget> implements ConfiguredTargetValue {
@AutoCodec.Instantiator
@VisibleForSerialization
@@ -51,39 +44,13 @@
NonRuleConfiguredTargetValue(
ConfiguredTarget configuredTarget, @Nullable NestedSet<Package> transitivePackages) {
- this.configuredTarget = Preconditions.checkNotNull(configuredTarget);
- this.transitivePackages = transitivePackages;
- }
-
- @Nullable // May be null after clearing.
- @Override
- public ConfiguredTarget getConfiguredTarget() {
- return configuredTarget;
- }
-
- @Nullable
- @Override
- public NestedSet<Package> getTransitivePackages() {
- return transitivePackages;
- }
-
- @Override
- public void clear(boolean clearEverything) {
- if (configuredTarget != null
- && configuredTarget.getConfigurationKey() != null
- && configuredTarget.getConfigurationChecksum().equals(EMPTY_OPTIONS.checksum())) {
- // Keep these to avoid the need to re-create them later, they are dependencies of the empty
- // configuration key and will never change.
- return;
- }
- if (clearEverything) {
- configuredTarget = null;
- }
- transitivePackages = null;
+ super(configuredTarget, transitivePackages);
}
@Override
public String toString() {
- return MoreObjects.toStringHelper(this).add("configuredTarget", configuredTarget).toString();
+ return MoreObjects.toStringHelper(this)
+ .add("configuredTarget", getConfiguredTarget())
+ .toString();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RuleConfiguredTargetValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/RuleConfiguredTargetValue.java
index 3a8ae0d..14f05b6 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RuleConfiguredTargetValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RuleConfiguredTargetValue.java
@@ -14,10 +14,8 @@
package com.google.devtools.build.lib.skyframe;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.devtools.build.lib.analysis.config.CommonOptions.EMPTY_OPTIONS;
import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.analysis.ConfiguredTargetValue;
@@ -33,12 +31,9 @@
@Immutable
@ThreadSafe
public final class RuleConfiguredTargetValue
+ extends BaseRuleConfiguredTargetValue<RuleConfiguredTarget>
implements RuleConfiguredObjectValue, ConfiguredTargetValue {
- // This variable is non-final because it may be clear()ed to save memory. It is null only after
- // clear(true) is called.
- @Nullable private RuleConfiguredTarget configuredTarget;
-
/**
* Operations accessing actions, for example, executing them, should be performed in the same
* Bazel instance that constructs the {@code RuleConfiguredTargetValue} instance and not on a
@@ -47,55 +42,23 @@
@Nullable // Null if deserialized.
private final transient ImmutableList<ActionAnalysisMetadata> actions;
- // May be null after clearing; because transitive packages are not tracked; or after
- // deserialization.
- @Nullable private transient NestedSet<Package> transitivePackages;
-
public RuleConfiguredTargetValue(
RuleConfiguredTarget configuredTarget, @Nullable NestedSet<Package> transitivePackages) {
- this.configuredTarget = Preconditions.checkNotNull(configuredTarget);
- this.transitivePackages = transitivePackages;
+ super(configuredTarget, transitivePackages);
// These are specifically *not* copied to save memory.
this.actions = configuredTarget.getActions();
}
- @Nullable // May be null after clearing.
- @Override
- public RuleConfiguredTarget getConfiguredTarget() {
- return configuredTarget;
- }
-
@Override
public ImmutableList<ActionAnalysisMetadata> getActions() {
return checkNotNull(actions, "actions are not available on deserialized instances");
}
- @Nullable
- @Override
- public NestedSet<Package> getTransitivePackages() {
- return transitivePackages;
- }
-
- @Override
- public void clear(boolean clearEverything) {
- if (configuredTarget != null
- && configuredTarget.getConfigurationKey() != null
- && configuredTarget.getConfigurationChecksum().equals(EMPTY_OPTIONS.checksum())) {
- // Keep these to avoid the need to re-create them later, they are dependencies of the empty
- // configuration key and will never change.
- return;
- }
- if (clearEverything) {
- configuredTarget = null;
- }
- transitivePackages = null;
- }
-
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("actions", actions)
- .add("configuredTarget", configuredTarget)
+ .add("configuredTarget", getConfiguredTarget())
.toString();
}
}