diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java b/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java index 2d5f857f2ec897..f5fac910949934 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/BuildEventStreamer.java @@ -498,12 +498,10 @@ private void maybeReportConfiguration(@Nullable BuildEvent configuration) { BuildEvent event = configuration == null ? NullConfiguration.INSTANCE : configuration; BuildEventId id = event.getEventId(); synchronized (this) { - if (configurationsPosted.contains(id)) { - return; + if (configurationsPosted.add(id)) { + post(event); } - configurationsPosted.add(id); } - post(event); } @Subscribe diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BUILD b/src/test/java/com/google/devtools/build/lib/runtime/BUILD index a92dc311c65935..c71cae169d9a17 100644 --- a/src/test/java/com/google/devtools/build/lib/runtime/BUILD +++ b/src/test/java/com/google/devtools/build/lib/runtime/BUILD @@ -47,6 +47,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/analysis:config/fragment_factory", "//src/main/java/com/google/devtools/build/lib/analysis:config/fragment_options", "//src/main/java/com/google/devtools/build/lib/analysis:config/fragment_registry", + "//src/main/java/com/google/devtools/build/lib/analysis:config/invalid_configuration_exception", "//src/main/java/com/google/devtools/build/lib/analysis:configured_target", "//src/main/java/com/google/devtools/build/lib/analysis:server_directories", "//src/main/java/com/google/devtools/build/lib/analysis:test/test_configuration", @@ -112,7 +113,6 @@ java_library( "//src/test/java/com/google/devtools/build/lib/testutil:TestConstants", "//src/test/java/com/google/devtools/build/lib/testutil:TestThread", "//src/test/java/com/google/devtools/build/lib/testutil:TestUtils", - "//src/test/java/com/google/devtools/build/lib/vfs/util", "//src/test/java/com/google/devtools/common/options:testutils", "//third_party:flogger", "//third_party:guava", diff --git a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java index b6ff83ec8749f3..1f9f9fbb446258 100644 --- a/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java +++ b/src/test/java/com/google/devtools/build/lib/runtime/BuildEventStreamerTest.java @@ -15,6 +15,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static com.google.devtools.build.lib.bugreport.BugReport.constructOomExitMessage; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.joining; @@ -47,6 +48,7 @@ import com.google.devtools.build.lib.analysis.config.CoreOptions; import com.google.devtools.build.lib.analysis.config.FragmentFactory; import com.google.devtools.build.lib.analysis.config.FragmentRegistry; +import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.buildeventservice.BuildEventServiceOptions.BesUploadMode; import com.google.devtools.build.lib.buildeventstream.AnnounceBuildEventTransportsEvent; import com.google.devtools.build.lib.buildeventstream.ArtifactGroupNamer; @@ -60,6 +62,7 @@ import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.Aborted.AbortReason; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.IdCase; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId.NamedSetOfFilesId; import com.google.devtools.build.lib.buildeventstream.BuildEventTransport; import com.google.devtools.build.lib.buildeventstream.BuildEventTransportClosedEvent; @@ -89,6 +92,7 @@ import com.google.devtools.build.lib.vfs.Root; import com.google.devtools.common.options.Options; import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.OptionsParsingResult; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; @@ -100,7 +104,9 @@ import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -781,32 +787,27 @@ public void testArtifactSetsPrecedeReportingEvent() throws InterruptedException streamer.buildEvent(startEvent); // Publish `numEvents` different events that all report the same NamedSet of artifacts on - // `numEvents` different threads. Use latches to ensure: + // `numEvents` different threads. Use a CyclicBarrier and latch to ensure: // // 1. all threads have started, before: // 2. all threads send their event, before: // 3. verifying the recorded events. - CountDownLatch readyToPublishLatch = new CountDownLatch(numEvents); - CountDownLatch startPublishingLatch = new CountDownLatch(1); + CyclicBarrier readyToPublishLatch = new CyclicBarrier(numEvents); CountDownLatch donePublishingLatch = new CountDownLatch(numEvents); for (int i = 0; i < numEvents; i++) { - int num = i; + BuildEvent reportingArtifacts = eventsToPost.get(i); new Thread( () -> { try { - BuildEvent reportingArtifacts = eventsToPost.get(num); - readyToPublishLatch.countDown(); - startPublishingLatch.await(); + readyToPublishLatch.await(); streamer.buildEvent(reportingArtifacts); - } catch (InterruptedException e) { + } catch (InterruptedException | BrokenBarrierException e) { throw new RuntimeException(e); } donePublishingLatch.countDown(); }) .start(); } - readyToPublishLatch.await(); - startPublishingLatch.countDown(); donePublishingLatch.await(); assertThat(streamer.isClosed()).isFalse(); @@ -955,39 +956,10 @@ public void testConsumeAsPairs() { @Test public void testReportedConfigurations() throws Exception { // Verify that configuration events are posted, but only once. - BuildOptions defaultBuildOptions = BuildOptions.of(ImmutableList.of(CoreOptions.class)); BuildEvent startEvent = new GenericBuildEvent( testId("Initial"), ImmutableSet.of(ProgressEvent.INITIAL_PROGRESS_UPDATE)); - BuildConfigurationValue configuration = - BuildConfigurationValue.createForTesting( - defaultBuildOptions, - "some_mnemonic", - "workspace", - /* siblingRepositoryLayout= */ false, - new BlazeDirectories( - new ServerDirectories(outputBase, outputBase, outputBase), - rootDirectory, - /* defaultSystemJavabase= */ null, - "productName"), - new BuildConfigurationValue.GlobalStateProvider() { - @Override - public ActionEnvironment getActionEnvironment(BuildOptions buildOptions) { - return ActionEnvironment.EMPTY; - } - - @Override - public FragmentRegistry getFragmentRegistry() { - return FragmentRegistry.create( - ImmutableList.of(), ImmutableList.of(), ImmutableList.of()); - } - - @Override - public ImmutableSet<String> getReservedActionMnemonics() { - return ImmutableSet.of(); - } - }, - new FragmentFactory()); + BuildConfigurationValue configuration = makeTestingBuildConfigurationValue(); BuildEvent firstWithConfiguration = new GenericConfigurationEvent(testId("first"), configuration.toBuildEvent()); BuildEvent secondWithConfiguration = @@ -1009,6 +981,62 @@ public ImmutableSet<String> getReservedActionMnemonics() { assertThat(allEventsSeen.get(6)).isEqualTo(secondWithConfiguration); } + @Test + public void testReportedConfigurations_concurrent() throws Exception { + // Verify that configuration events are posted, but only once. + BuildEvent startEvent = + new GenericBuildEvent( + testId("Initial"), ImmutableSet.of(ProgressEvent.INITIAL_PROGRESS_UPDATE)); + BuildConfigurationValue configuration = makeTestingBuildConfigurationValue(); + + int numEvents = 100; + List<BuildEvent> eventsToPost = new ArrayList<>(); + for (int i = 0; i < numEvents; i++) { + eventsToPost.add( + new GenericConfigurationEvent(testId("has_config_" + i), configuration.toBuildEvent())); + } + + streamer.buildEvent(startEvent); + // Publish `numEvents` different events that all report the same configuration on `numEvents` + // different threads. Use a CyclicBarrier and latch to ensure: + // + // 1. all threads have started, before: + // 2. all threads send their event, before: + // 3. verifying the recorded events. + CyclicBarrier readyToPublishLatch = new CyclicBarrier(numEvents); + CountDownLatch donePublishingLatch = new CountDownLatch(numEvents); + for (int i = 0; i < numEvents; i++) { + BuildEvent hasConfigEvent = eventsToPost.get(i); + new Thread( + () -> { + try { + readyToPublishLatch.await(); + streamer.buildEvent(hasConfigEvent); + } catch (InterruptedException | BrokenBarrierException e) { + throw new RuntimeException(e); + } + donePublishingLatch.countDown(); + }) + .start(); + } + donePublishingLatch.await(); + + assertThat(streamer.isClosed()).isFalse(); + + List<BuildEvent> allEventsSeen = transport.getEvents(); + + // Two events for each GenericConfigurationEvent: a progress event announcing it and the + // actual GenericConfigurationEvent itself. + assertThat(allEventsSeen).hasSize(3 + (numEvents * 2)); + assertThat(allEventsSeen.get(0).getEventId()).isEqualTo(startEvent.getEventId()); + assertThat(allEventsSeen.get(1).getEventId()).isEqualTo(ProgressEvent.INITIAL_PROGRESS_UPDATE); + assertThat(allEventsSeen.get(2)).isEqualTo(configuration.toBuildEvent()); + for (int idx = 3; idx < allEventsSeen.size(); idx++) { + assertThat(allEventsSeen.get(idx).getEventId().getIdCase()) + .isNotEqualTo(IdCase.CONFIGURATION); + } + } + @Test public void testEarlyFlush() { // Verify that the streamer can handle early calls to flush() and still correctly @@ -1791,6 +1819,38 @@ private OptionsParsingResult createMockOptions() { return options; } + private BuildConfigurationValue makeTestingBuildConfigurationValue() + throws InvalidConfigurationException, OptionsParsingException { + return BuildConfigurationValue.createForTesting( + BuildOptions.of(ImmutableList.of(CoreOptions.class)), + "some_mnemonic", + "workspace", + /* siblingRepositoryLayout= */ false, + new BlazeDirectories( + new ServerDirectories(outputBase, outputBase, outputBase), + rootDirectory, + /* defaultSystemJavabase= */ null, + "productName"), + new BuildConfigurationValue.GlobalStateProvider() { + @Override + public ActionEnvironment getActionEnvironment(BuildOptions buildOptions) { + return ActionEnvironment.EMPTY; + } + + @Override + public FragmentRegistry getFragmentRegistry() { + return FragmentRegistry.create( + ImmutableList.of(), ImmutableList.of(), ImmutableList.of()); + } + + @Override + public ImmutableSet<String> getReservedActionMnemonics() { + return ImmutableSet.of(); + } + }, + new FragmentFactory()); + } + private static DetailedExitCode createGenericDetailedExitCode() { return DetailedExitCode.of( FailureDetail.newBuilder()