Skip to content

Commit 9f93780

Browse files
justinhorvitzcopybara-github
authored andcommitted
Create an option to enable GcThrashingDetector.
Also create another option so that we can experiment with and without `RetainedHeapLimiter`. PiperOrigin-RevId: 520726326 Change-Id: I4ddfd91089132b96a9339ac60e03d634fb56c451
1 parent b284477 commit 9f93780

File tree

6 files changed

+130
-5
lines changed

6 files changed

+130
-5
lines changed

src/main/java/com/google/devtools/build/lib/BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,14 +224,18 @@ java_library(
224224
java_library(
225225
name = "runtime/memory_pressure",
226226
srcs = [
227+
"runtime/GcThrashingDetector.java",
227228
"runtime/MemoryPressureEvent.java",
228229
"runtime/MemoryPressureOptions.java",
229230
"runtime/MemoryPressureStatCollector.java",
230231
],
231232
deps = [
233+
"//src/main/java/com/google/devtools/build/lib/bugreport",
234+
"//src/main/java/com/google/devtools/build/lib/clock",
232235
"//src/main/java/com/google/devtools/common/options",
233236
"//src/main/protobuf:memory_pressure_java_proto",
234237
"//third_party:auto_value",
238+
"//third_party:flogger",
235239
"//third_party:guava",
236240
],
237241
)
@@ -263,6 +267,7 @@ java_library(
263267
"runtime/BlazeCommandResult.java",
264268
"runtime/CommandDispatcher.java",
265269
"runtime/CommandLinePathFactory.java",
270+
"runtime/GcThrashingDetector.java",
266271
"runtime/KeepGoingOption.java",
267272
"runtime/LoadingPhaseThreadsOption.java",
268273
"runtime/MemoryPressureEvent.java",

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818
import static com.google.common.collect.ImmutableList.toImmutableList;
1919

2020
import com.google.auto.value.AutoValue;
21+
import com.google.common.annotations.VisibleForTesting;
2122
import com.google.common.collect.ImmutableList;
23+
import com.google.common.eventbus.EventBus;
24+
import com.google.common.eventbus.Subscribe;
2225
import com.google.common.flogger.GoogleLogger;
2326
import com.google.devtools.build.lib.bugreport.BugReporter;
2427
import com.google.devtools.build.lib.bugreport.Crash;
2528
import com.google.devtools.build.lib.bugreport.CrashContext;
29+
import com.google.devtools.build.lib.clock.BlazeClock;
2630
import com.google.devtools.build.lib.clock.Clock;
2731
import java.time.Duration;
2832
import java.time.Instant;
@@ -60,18 +64,37 @@ static Limit of(Duration period, int count) {
6064
}
6165
}
6266

67+
/**
68+
* If enabled in {@link MemoryPressureOptions}, creates a {@link GcThrashingDetector} and
69+
* registers it with the {@link EventBus}.
70+
*/
71+
public static void configureForCommand(MemoryPressureOptions options, EventBus eventBus) {
72+
if (options.gcThrashingLimits.isEmpty() || options.oomMoreEagerlyThreshold == 100) {
73+
return;
74+
}
75+
76+
eventBus.register(
77+
new GcThrashingDetector(
78+
options.oomMoreEagerlyThreshold,
79+
options.gcThrashingLimits,
80+
BlazeClock.instance(),
81+
BugReporter.defaultInstance()));
82+
}
83+
6384
private final int threshold;
6485
private final ImmutableList<SingleLimitTracker> trackers;
6586
private final Clock clock;
6687
private final BugReporter bugReporter;
6788

89+
@VisibleForTesting
6890
GcThrashingDetector(int threshold, List<Limit> limits, Clock clock, BugReporter bugReporter) {
6991
this.threshold = threshold;
7092
this.trackers = limits.stream().map(SingleLimitTracker::new).collect(toImmutableList());
7193
this.clock = clock;
7294
this.bugReporter = bugReporter;
7395
}
7496

97+
@Subscribe // EventBus synchronizes calls by default, making thread safety unnecessary.
7598
void handle(MemoryPressureEvent event) {
7699
if (event.percentTenuredSpaceUsed() < threshold) {
77100
for (var tracker : trackers) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void beforeCommand(CommandEnvironment env) {
5353
MemoryPressureOptions options = env.getOptions().getOptions(MemoryPressureOptions.class);
5454
highWaterMarkLimiter =
5555
new HighWaterMarkLimiter(env.getSkyframeExecutor(), env.getSyscallCache(), options);
56-
56+
GcThrashingDetector.configureForCommand(options, eventBus);
5757
retainedHeapLimiter.setOptions(options);
5858

5959
eventBus.register(this);

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313
// limitations under the License.
1414
package com.google.devtools.build.lib.runtime;
1515

16+
import com.google.common.collect.ImmutableList;
17+
import com.google.devtools.common.options.Converter;
18+
import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter;
19+
import com.google.devtools.common.options.Converters.DurationConverter;
1620
import com.google.devtools.common.options.Converters.PercentageConverter;
1721
import com.google.devtools.common.options.Converters.RangeConverter;
1822
import com.google.devtools.common.options.Option;
1923
import com.google.devtools.common.options.OptionDocumentationCategory;
2024
import com.google.devtools.common.options.OptionEffectTag;
2125
import com.google.devtools.common.options.OptionsBase;
26+
import com.google.devtools.common.options.OptionsParsingException;
2227
import java.time.Duration;
2328

2429
/** Options for responding to memory pressure. */
@@ -91,9 +96,64 @@ public final class MemoryPressureOptions extends OptionsBase {
9196
+ " threshold is exceeded.")
9297
public int skyframeHighWaterMarkFullGcDropsPerInvocation;
9398

99+
@Option(
100+
name = "experimental_gc_thrashing_limits",
101+
defaultValue = "",
102+
documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
103+
effectTags = {OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS},
104+
converter = GcThrashingLimitsConverter.class,
105+
help =
106+
"Limits which, if reached, cause GcThrashingDetector to crash Bazel with an OOM. Each"
107+
+ " limit is specified as <period>:<count> where period is a duration and count is a"
108+
+ " positive integer. If more than --experimental_oom_more_eagerly_threshold percent"
109+
+ " of tenured space (old gen heap) remains occupied after <count> consecutive full"
110+
+ " GCs within <period>, an OOM is triggered. Multiple limits can be specified"
111+
+ " separated by commas.")
112+
public ImmutableList<GcThrashingDetector.Limit> gcThrashingLimits;
113+
114+
@Option(
115+
name = "gc_thrashing_limits_retained_heap_limiter_mutually_exclusive",
116+
defaultValue = "true",
117+
documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
118+
effectTags = {OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS},
119+
help =
120+
"If true, specifying non-empty --experimental_gc_thrashing_limits deactivates"
121+
+ " RetainedHeapLimiter to make it mutually exclusive with GcThrashingDetector."
122+
+ " Setting to false permits both to be active for the same command.")
123+
public boolean gcThrashingLimitsRetainedHeapLimiterMutuallyExclusive;
124+
94125
static final class NonNegativeIntegerConverter extends RangeConverter {
95126
NonNegativeIntegerConverter() {
96127
super(0, Integer.MAX_VALUE);
97128
}
98129
}
130+
131+
static final class GcThrashingLimitsConverter
132+
extends Converter.Contextless<ImmutableList<GcThrashingDetector.Limit>> {
133+
private final CommaSeparatedOptionListConverter commaListConverter =
134+
new CommaSeparatedOptionListConverter();
135+
private final DurationConverter durationConverter = new DurationConverter();
136+
private final RangeConverter positiveIntConverter = new RangeConverter(1, Integer.MAX_VALUE);
137+
138+
@Override
139+
public ImmutableList<GcThrashingDetector.Limit> convert(String input)
140+
throws OptionsParsingException {
141+
ImmutableList.Builder<GcThrashingDetector.Limit> result = ImmutableList.builder();
142+
for (String part : commaListConverter.convert(input)) {
143+
int colonIndex = part.indexOf(':');
144+
if (colonIndex == -1) {
145+
throw new OptionsParsingException("Expected <period>:<count>, got " + part);
146+
}
147+
Duration period = durationConverter.convert(part.substring(0, colonIndex));
148+
int count = positiveIntConverter.convert(part.substring(colonIndex + 1));
149+
result.add(GcThrashingDetector.Limit.of(period, count));
150+
}
151+
return result.build();
152+
}
153+
154+
@Override
155+
public String getTypeDescription() {
156+
return "comma separated pairs of <period>:<count>";
157+
}
158+
}
99159
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ final class RetainedHeapLimiter implements MemoryPressureStatCollector {
4545
private final BugReporter bugReporter;
4646
private final Clock clock;
4747

48-
private volatile MemoryPressureOptions options = Options.getDefaults(MemoryPressureOptions.class);
48+
private volatile MemoryPressureOptions options = inactiveOptions();
4949

5050
private final AtomicBoolean throwingOom = new AtomicBoolean(false);
5151
private final AtomicBoolean heapLimiterTriggeredGc = new AtomicBoolean(false);
@@ -71,7 +71,12 @@ private RetainedHeapLimiter(BugReporter bugReporter, Clock clock) {
7171

7272
@ThreadSafety.ThreadCompatible // Can only be called on the logical main Bazel thread.
7373
void setOptions(MemoryPressureOptions options) {
74-
this.options = options;
74+
if (options.gcThrashingLimitsRetainedHeapLimiterMutuallyExclusive
75+
&& !options.gcThrashingLimits.isEmpty()) {
76+
this.options = inactiveOptions();
77+
} else {
78+
this.options = options;
79+
}
7580
}
7681

7782
// Can be called concurrently, handles concurrent calls with #setThreshold gracefully.
@@ -169,4 +174,10 @@ public void addStatsAndReset(MemoryPressureStats.Builder stats) {
169174
maxConsecutiveIgnoredFullGcsOverThreshold.getAndSet(0));
170175
consecutiveIgnoredFullGcsOverThreshold.set(0);
171176
}
177+
178+
private static MemoryPressureOptions inactiveOptions() {
179+
var options = Options.getDefaults(MemoryPressureOptions.class);
180+
options.oomMoreEagerlyThreshold = 100;
181+
return options;
182+
}
172183
}

src/test/java/com/google/devtools/build/lib/runtime/RetainedHeapLimiterTest.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import static org.mockito.Mockito.verifyNoInteractions;
2424
import static org.mockito.Mockito.verifyNoMoreInteractions;
2525

26+
import com.google.common.collect.ImmutableList;
2627
import com.google.devtools.build.lib.bugreport.BugReporter;
2728
import com.google.devtools.build.lib.bugreport.Crash;
29+
import com.google.devtools.build.lib.runtime.GcThrashingDetector.Limit;
2830
import com.google.devtools.build.lib.runtime.MemoryPressure.MemoryPressureStats;
2931
import com.google.devtools.build.lib.testutil.ManualClock;
3032
import com.google.devtools.common.options.Options;
@@ -277,11 +279,35 @@ public void threshold100_noGcTriggeredEvenWithNonsenseStats() {
277279
}
278280

279281
@Test
280-
public void worksWithoutSettingOptions() {
281-
underTest.handle(percentUsedAfterOrganicFullGc(95));
282+
public void optionsNotSet_disabled() {
283+
underTest.handle(percentUsedAfterOrganicFullGc(99));
282284
assertStats(MemoryPressureStats.newBuilder().setManuallyTriggeredGcs(0));
283285
}
284286

287+
@Test
288+
public void gcThrashingLimitsSet_mutuallyExclusive_disabled() {
289+
options.oomMoreEagerlyThreshold = 90;
290+
options.gcThrashingLimits = ImmutableList.of(Limit.of(Duration.ofMinutes(1), 2));
291+
options.gcThrashingLimitsRetainedHeapLimiterMutuallyExclusive = true;
292+
underTest.setOptions(options);
293+
294+
underTest.handle(percentUsedAfterOrganicFullGc(99));
295+
296+
assertStats(MemoryPressureStats.newBuilder().setManuallyTriggeredGcs(0));
297+
}
298+
299+
@Test
300+
public void gcThrashingLimitsSet_mutuallyInclusive_enabled() {
301+
options.oomMoreEagerlyThreshold = 90;
302+
options.gcThrashingLimits = ImmutableList.of(Limit.of(Duration.ofMinutes(1), 2));
303+
options.gcThrashingLimitsRetainedHeapLimiterMutuallyExclusive = false;
304+
underTest.setOptions(options);
305+
306+
underTest.handle(percentUsedAfterOrganicFullGc(99));
307+
308+
assertStats(MemoryPressureStats.newBuilder().setManuallyTriggeredGcs(1));
309+
}
310+
285311
@Test
286312
public void statsReset() {
287313
options.oomMoreEagerlyThreshold = 90;

0 commit comments

Comments
 (0)