Skip to content

Commit eb4a78c

Browse files
joelebacopybara-github
authored andcommitted
[Skymeld] De-dup events at BuildDriverFunction.
TopLevelStatusEvents are fired from within BuildDriverFunction, that means there may be duplicated events (e.g. from resets). Up till now, we've been asking the subscribers to do their own de-duplication. This is not ideal since 1) storing the events in deduplicating sets may prevent transitive references from being GCed and 2) Posting the events were observed to be a hot spot for contention [1]. This CL moves the de-duplication into BuildDriverFunction. We exploit the fact that each BuildDriverKey only has 1 effective event per TopLevelStatusEvent type (e.g. a target foo can't be analyzed twice in a build) to prevent sending a 2nd event of the same type. This allows us to store only the type of the sent events, instead of the events themselves. PiperOrigin-RevId: 522334018 Change-Id: I76aed9fa68f8197d8f0a70158198c7341dc3a884
1 parent d49529b commit eb4a78c

File tree

6 files changed

+207
-98
lines changed

6 files changed

+207
-98
lines changed

src/main/java/com/google/devtools/build/lib/buildtool/AnalysisAndExecutionPhaseRunner.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
import com.google.common.collect.ImmutableList;
1717
import com.google.common.collect.ImmutableSet;
18-
import com.google.common.collect.Sets;
1918
import com.google.common.eventbus.Subscribe;
2019
import com.google.common.flogger.GoogleLogger;
2120
import com.google.devtools.build.lib.actions.BuildFailedException;
@@ -58,7 +57,6 @@
5857
import com.google.devtools.common.options.OptionsParsingException;
5958
import java.util.Collection;
6059
import java.util.List;
61-
import java.util.Set;
6260

6361
/**
6462
* Intended drop-in replacement for AnalysisPhaseRunner after we're done with merging Skyframe's
@@ -322,8 +320,6 @@ private static class TopLevelTargetAnalysisWatcher implements AutoCloseable {
322320
private final CommandEnvironment env;
323321
private final BuildRequest buildRequest;
324322
private final BuildOptions buildOptions;
325-
private final Set<TopLevelTargetAnalyzedEvent> processedEvents;
326-
327323
private TopLevelTargetAnalysisWatcher(
328324
Iterable<BlazeModule> blazeModules,
329325
CommandEnvironment env,
@@ -333,7 +329,6 @@ private TopLevelTargetAnalysisWatcher(
333329
this.env = env;
334330
this.buildRequest = buildRequest;
335331
this.buildOptions = buildOptions;
336-
this.processedEvents = Sets.newConcurrentHashSet();
337332
}
338333

339334
/** Creates an AnalysisOperationWatcher and registers it with the provided eventBus. */
@@ -351,13 +346,6 @@ public static TopLevelTargetAnalysisWatcher createAndRegisterWithEventBus(
351346
@Subscribe
352347
public void handleTopLevelEntityAnalysisConcluded(TopLevelTargetAnalyzedEvent e)
353348
throws ViewCreationFailedException, InterruptedException {
354-
// TopLevelTargetAnalyzedEvent originates from within Skyframe, which means there'll likely
355-
// be multiple events fired for the same underlying ConfiguredTarget due to SkyFunction
356-
// restarts. We only process them once.
357-
if (!processedEvents.add(e)) {
358-
return;
359-
}
360-
361349
for (BlazeModule blazeModule : blazeModules) {
362350
blazeModule.afterTopLevelTargetAnalysis(
363351
env, buildRequest, buildOptions, e.configuredTarget());

src/main/java/com/google/devtools/build/lib/runtime/TargetSummaryPublisher.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,6 @@ public void populateTargets(TestFilteringCompleteEvent event) {
118118
/**
119119
* Populates the aggregator for a particular top level target, including test targets.
120120
*
121-
* <p>Since the event is fired from within a SkyFunction, it is possible to receive duplicate
122-
* events. In case of duplication, simply return without creating any new aggregator.
123-
*
124121
* <p>With skymeld, the corresponding AspectCompleteEvents may arrive before the aggregator is set
125122
* up. We therefore need to put those events in a queue and resolve them when the aggregator
126123
* becomes available.

src/main/java/com/google/devtools/build/lib/skyframe/BuildDriverFunction.java

Lines changed: 100 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.common.collect.ImmutableMap;
2626
import com.google.common.collect.ImmutableSet;
2727
import com.google.common.collect.Iterables;
28+
import com.google.common.collect.Maps;
2829
import com.google.common.collect.Sets;
2930
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
3031
import com.google.devtools.build.lib.actions.ActionLookupKey;
@@ -59,6 +60,7 @@
5960
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.SomeExecutionStartedEvent;
6061
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TestAnalyzedEvent;
6162
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelEntityAnalysisConcludedEvent;
63+
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelStatusEventWithType;
6264
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetAnalyzedEvent;
6365
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetPendingExecutionEvent;
6466
import com.google.devtools.build.lib.skyframe.TopLevelStatusEvents.TopLevelTargetReadyForSymlinkPlanting;
@@ -72,7 +74,9 @@
7274
import com.google.devtools.build.skyframe.SkyframeLookupResult;
7375
import java.util.ArrayList;
7476
import java.util.Collections;
77+
import java.util.HashSet;
7578
import java.util.List;
79+
import java.util.Map;
7680
import java.util.Set;
7781
import javax.annotation.Nullable;
7882

@@ -96,6 +100,18 @@ public class BuildDriverFunction implements SkyFunction {
96100
// shutting down of the Executors could lead to a RejectedExecutionException.
97101
private Set<BuildDriverKey> checkedForConflicts = Sets.newConcurrentHashSet();
98102

103+
// Events coming from Skyframe may contain duplicates (because of resets). It would be better to
104+
// de-duplicate at the source to avoid repeated work by each subscriber.
105+
//
106+
// Each top level key has at most 1 effective status event, e.g. a top level target can't be
107+
// analyzed twice in a build. Therefore, to keep track of the posted events, we only need to keep
108+
// the sent event types instead of the events themselves.
109+
//
110+
// We didn't use SkyKeyComputeState since it should only be used as a performance optimization,
111+
// whereas in this situation the state determines the behavior of the SkyFunction.
112+
private Map<BuildDriverKey, Set<TopLevelStatusEvents.Type>> keyToPostedEvents =
113+
Maps.newConcurrentMap();
114+
99115
BuildDriverFunction(
100116
TransitiveActionLookupValuesHelper transitiveActionLookupValuesHelper,
101117
Supplier<IncrementalArtifactConflictFinder> incrementalArtifactConflictFinder,
@@ -160,6 +176,9 @@ public SkyValue compute(SkyKey skyKey, Environment env)
160176
}
161177
}
162178

179+
Set<TopLevelStatusEvents.Type> postedEventsTypes =
180+
keyToPostedEvents.computeIfAbsent(buildDriverKey, (unused) -> new HashSet<>());
181+
163182
Preconditions.checkState(
164183
topLevelSkyValue instanceof ConfiguredTargetValue
165184
|| topLevelSkyValue instanceof TopLevelAspectsValue);
@@ -170,7 +189,18 @@ public SkyValue compute(SkyKey skyKey, Environment env)
170189
// before the TopLevelEntityAnalysisConcludedEvent: when the last of the analysis work is
171190
// concluded, we need to have the *complete* list of analyzed targets ready in
172191
// BuildResultListener.
173-
postTopLevelTargetAnalyzedEvent(env, configuredTargetValue, configuredTarget);
192+
postEventIfNecessary(
193+
postedEventsTypes, env, TopLevelTargetAnalyzedEvent.create(configuredTarget));
194+
// It's possible that this code path is triggered AFTER the analysis cache clean up and the
195+
// transitive packages for package root resolution is already cleared. In such a case, the
196+
// symlinks should have already been planted.
197+
if (configuredTargetValue.getTransitivePackages() != null) {
198+
postEventIfNecessary(
199+
postedEventsTypes,
200+
env,
201+
TopLevelTargetReadyForSymlinkPlanting.create(
202+
configuredTargetValue.getTransitivePackages()));
203+
}
174204

175205
BuildConfigurationValue buildConfigurationValue =
176206
configuredTarget.getConfigurationKey() == null
@@ -196,44 +226,56 @@ public SkyValue compute(SkyKey skyKey, Environment env)
196226

197227
state.checkedForCompatibility = true;
198228
if (!isConfiguredTargetCompatible) {
199-
env.getListener().post(TopLevelTargetSkippedEvent.create(configuredTarget));
229+
postEventIfNecessary(
230+
postedEventsTypes, env, TopLevelTargetSkippedEvent.create(configuredTarget));
200231
// We still record analyzed but skipped tests, as this information is needed for the
201232
// result summary.
202233
if (!NOT_TEST.equals(buildDriverKey.getTestType())) {
203-
env.getListener()
204-
.post(
205-
TestAnalyzedEvent.create(
206-
configuredTarget,
207-
Preconditions.checkNotNull(buildConfigurationValue),
208-
/*isSkipped=*/ true));
234+
postEventIfNecessary(
235+
postedEventsTypes,
236+
env,
237+
TestAnalyzedEvent.create(
238+
configuredTarget,
239+
Preconditions.checkNotNull(buildConfigurationValue),
240+
/* isSkipped= */ true));
209241
}
210242
// Only send the event now to include the compatibility check in the measurement for
211243
// time spent on analysis work.
212-
env.getListener().post(TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey));
244+
postEventIfNecessary(
245+
postedEventsTypes,
246+
env,
247+
TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey));
213248
// We consider the evaluation of this BuildDriverKey successful at this point, even when
214249
// the target is skipped.
250+
removeStatesForKey(buildDriverKey);
215251
return new BuildDriverValue(topLevelSkyValue, /*skipped=*/ true);
216252
}
217253
} catch (TargetCompatibilityCheckException e) {
218254
throw new BuildDriverFunctionException(e);
219255
}
220256
}
221257

222-
env.getListener().post(TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey));
223-
env.getListener()
224-
.post(
225-
TopLevelTargetPendingExecutionEvent.create(
226-
configuredTarget, buildDriverKey.isTest()));
258+
postEventIfNecessary(
259+
postedEventsTypes, env, TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey));
260+
postEventIfNecessary(
261+
postedEventsTypes,
262+
env,
263+
TopLevelTargetPendingExecutionEvent.create(configuredTarget, buildDriverKey.isTest()));
227264
requestConfiguredTargetExecution(
228265
configuredTarget,
229266
buildDriverKey,
230267
actionLookupKey,
231268
buildConfigurationValue,
232269
env,
233-
topLevelArtifactContext);
270+
topLevelArtifactContext,
271+
postedEventsTypes);
234272
} else {
235273
announceAspectAnalysisDoneAndRequestExecution(
236-
buildDriverKey, (TopLevelAspectsValue) topLevelSkyValue, env, topLevelArtifactContext);
274+
buildDriverKey,
275+
(TopLevelAspectsValue) topLevelSkyValue,
276+
env,
277+
topLevelArtifactContext,
278+
postedEventsTypes);
237279
}
238280

239281
if (env.valuesMissing()) {
@@ -245,33 +287,35 @@ public SkyValue compute(SkyKey skyKey, Environment env)
245287
if (EXCLUSIVE.equals(buildDriverKey.getTestType())
246288
|| EXCLUSIVE_IF_LOCAL.equals(buildDriverKey.getTestType())) {
247289
Preconditions.checkState(topLevelSkyValue instanceof ConfiguredTargetValue);
290+
removeStatesForKey(buildDriverKey);
248291
return new ExclusiveTestBuildDriverValue(
249292
topLevelSkyValue, ((ConfiguredTargetValue) topLevelSkyValue).getConfiguredTarget());
250293
}
251294

295+
removeStatesForKey(buildDriverKey);
252296
return new BuildDriverValue(topLevelSkyValue, /*skipped=*/ false);
253297
}
254298

255-
public void resetActionConflictCheckingStatus() {
299+
public void resetStates() {
256300
checkedForConflicts = Sets.newConcurrentHashSet();
301+
keyToPostedEvents = Maps.newConcurrentMap();
257302
}
258303

259-
private static void postTopLevelTargetAnalyzedEvent(
304+
private void removeStatesForKey(BuildDriverKey key) {
305+
checkedForConflicts.remove(key);
306+
keyToPostedEvents.remove(key);
307+
}
308+
309+
private static void postEventIfNecessary(
310+
Set<TopLevelStatusEvents.Type> postedEventsTypes,
260311
Environment env,
261-
ConfiguredTargetValue configuredTargetValue,
262-
ConfiguredTarget configuredTarget) {
263-
env.getListener().post(TopLevelTargetAnalyzedEvent.create(configuredTarget));
264-
// It's possible that this code path is triggered AFTER the analysis cache clean up and the
265-
// transitive packages for package root resolution is already cleared. In such a case, the
266-
// symlinks should have already been planted.
267-
if (configuredTargetValue.getTransitivePackages() != null) {
268-
env.getListener()
269-
.post(
270-
TopLevelTargetReadyForSymlinkPlanting.create(
271-
configuredTargetValue.getTransitivePackages()));
312+
TopLevelStatusEventWithType event) {
313+
if (postedEventsTypes.add(event.getType())) {
314+
env.getListener().post(event);
272315
}
273316
}
274317

318+
275319
/**
276320
* Checks if a ConfiguredTarget is compatible with the platform/environment. See {@link
277321
* TopLevelConstraintSemantics}.
@@ -352,12 +396,13 @@ private void requestConfiguredTargetExecution(
352396
ActionLookupKey actionLookupKey,
353397
BuildConfigurationValue buildConfigurationValue,
354398
Environment env,
355-
TopLevelArtifactContext topLevelArtifactContext)
399+
TopLevelArtifactContext topLevelArtifactContext,
400+
Set<TopLevelStatusEvents.Type> postedEventsTypes)
356401
throws InterruptedException {
357402
ImmutableSet.Builder<Artifact> artifactsToBuild = ImmutableSet.builder();
358403
addExtraActionsIfRequested(
359404
configuredTarget.getProvider(ExtraActionArtifactsProvider.class), artifactsToBuild);
360-
env.getListener().post(SomeExecutionStartedEvent.create());
405+
postEventIfNecessary(postedEventsTypes, env, SomeExecutionStartedEvent.create());
361406
if (NOT_TEST.equals(buildDriverKey.getTestType())) {
362407
declareDependenciesAndCheckValues(
363408
env,
@@ -369,12 +414,13 @@ private void requestConfiguredTargetExecution(
369414
return;
370415
}
371416

372-
env.getListener()
373-
.post(
374-
TestAnalyzedEvent.create(
375-
configuredTarget,
376-
Preconditions.checkNotNull(buildConfigurationValue),
377-
/*isSkipped=*/ false));
417+
postEventIfNecessary(
418+
postedEventsTypes,
419+
env,
420+
TestAnalyzedEvent.create(
421+
configuredTarget,
422+
Preconditions.checkNotNull(buildConfigurationValue),
423+
/* isSkipped= */ false));
378424

379425
if (PARALLEL.equals(buildDriverKey.getTestType())) {
380426
// Only run non-exclusive tests here. Exclusive tests need to be run sequentially later.
@@ -398,12 +444,19 @@ private void announceAspectAnalysisDoneAndRequestExecution(
398444
BuildDriverKey buildDriverKey,
399445
TopLevelAspectsValue topLevelAspectsValue,
400446
Environment env,
401-
TopLevelArtifactContext topLevelArtifactContext)
447+
TopLevelArtifactContext topLevelArtifactContext,
448+
Set<TopLevelStatusEvents.Type> postedEventsTypes)
402449
throws InterruptedException {
403450

404-
env.getListener().post(SomeExecutionStartedEvent.create());
451+
postEventIfNecessary(postedEventsTypes, env, SomeExecutionStartedEvent.create());
405452
ImmutableSet.Builder<Artifact> artifactsToBuild = ImmutableSet.builder();
406453
List<SkyKey> aspectCompletionKeys = new ArrayList<>();
454+
455+
boolean symlinkPlantingEventsSent =
456+
!postedEventsTypes.add(
457+
TopLevelStatusEvents.Type.TOP_LEVEL_TARGET_READY_FOR_SYMLINK_PLANTING);
458+
boolean aspectAnalyzedEventsSent =
459+
!postedEventsTypes.add(TopLevelStatusEvents.Type.ASPECT_ANALYZED);
407460
for (AspectValue aspectValue : topLevelAspectsValue.getTopLevelAspectsValues()) {
408461
AspectKey aspectKey = aspectValue.getKey();
409462
ConfiguredAspect configuredAspect = aspectValue.getConfiguredAspect();
@@ -413,21 +466,25 @@ private void announceAspectAnalysisDoneAndRequestExecution(
413466
// It's possible that this code path is triggered AFTER the analysis cache clean up and the
414467
// transitive packages for package root resolution is already cleared. In such a case, the
415468
// symlinks should have already been planted.
416-
if (aspectValue.getTransitivePackages() != null) {
469+
if (aspectValue.getTransitivePackages() != null && !symlinkPlantingEventsSent) {
417470
env.getListener()
418471
.post(
419472
TopLevelTargetReadyForSymlinkPlanting.create(aspectValue.getTransitivePackages()));
420473
}
421-
env.getListener().post(AspectAnalyzedEvent.create(aspectKey, configuredAspect));
474+
if (!aspectAnalyzedEventsSent) {
475+
env.getListener().post(AspectAnalyzedEvent.create(aspectKey, configuredAspect));
476+
}
422477

423478
aspectCompletionKeys.add(AspectCompletionKey.create(aspectKey, topLevelArtifactContext));
424479
}
480+
425481
// Send the AspectAnalyzedEvents first to make sure the BuildResultListener is up-to-date before
426482
// signaling that the analysis of this top level aspect has concluded.
427-
env.getListener().post(TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey));
428-
483+
postEventIfNecessary(
484+
postedEventsTypes, env, TopLevelEntityAnalysisConcludedEvent.success(buildDriverKey));
429485
declareDependenciesAndCheckValues(
430486
env, Iterables.concat(Artifact.keys(artifactsToBuild.build()), aspectCompletionKeys));
487+
431488
}
432489

433490
/**

src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2446,7 +2446,7 @@ void resetActionConflictsStoredInSkyframe() {
24462446
}
24472447

24482448
public void resetBuildDriverFunction() {
2449-
buildDriverFunction.resetActionConflictCheckingStatus();
2449+
buildDriverFunction.resetStates();
24502450
}
24512451

24522452
/** Resets the incremental artifact conflict finder to ensure incremental correctness. */

0 commit comments

Comments
 (0)