RELNOTES:Add support for tracking suspensions (sleeps or SIGSTOP) on macOS.
PiperOrigin-RevId: 279085037
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index c7dec59..124cabf 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -1316,6 +1316,7 @@
"//src/main/java/com/google/devtools/build/lib/concurrent",
"//src/main/java/com/google/devtools/build/lib/exec/local:options",
"//src/main/java/com/google/devtools/build/lib/metrics:event",
+ "//src/main/java/com/google/devtools/build/lib/platform:suspend_counter",
"//src/main/java/com/google/devtools/build/lib/profiler",
"//src/main/java/com/google/devtools/build/lib/profiler:profiler-output",
"//src/main/java/com/google/devtools/build/lib/profiler/memory:allocationtracker",
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/NoBuildRequestFinishedEvent.java b/src/main/java/com/google/devtools/build/lib/analysis/NoBuildRequestFinishedEvent.java
index f1294f2..5d45b81 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/NoBuildRequestFinishedEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/NoBuildRequestFinishedEvent.java
@@ -20,6 +20,6 @@
/** {@link BuildEvent} indicating that a request that does not involve building as finished. */
public final class NoBuildRequestFinishedEvent extends BuildCompletingEvent {
public NoBuildRequestFinishedEvent(ExitCode exitCode, long finishTimeMillis) {
- super(exitCode, finishTimeMillis);
+ super(exitCode, finishTimeMillis, false);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventstream/BuildCompletingEvent.java b/src/main/java/com/google/devtools/build/lib/buildeventstream/BuildCompletingEvent.java
index f8d37d8..089db8a 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventstream/BuildCompletingEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/buildeventstream/BuildCompletingEvent.java
@@ -27,17 +27,25 @@
public abstract class BuildCompletingEvent implements BuildEvent {
private final ExitCode exitCode;
private final long finishTimeMillis;
+
+ /** Was the build suspended mid-build (e.g. hardware sleep, SIGSTOP). */
+ private final boolean wasSuspended;
+
private final Collection<BuildEventId> children;
public BuildCompletingEvent(
- ExitCode exitCode, long finishTimeMillis, Collection<BuildEventId> children) {
+ ExitCode exitCode,
+ long finishTimeMillis,
+ Collection<BuildEventId> children,
+ boolean wasSuspended) {
this.exitCode = exitCode;
this.finishTimeMillis = finishTimeMillis;
this.children = children;
+ this.wasSuspended = wasSuspended;
}
- public BuildCompletingEvent(ExitCode exitCode, long finishTimeMillis) {
- this(exitCode, finishTimeMillis, ImmutableList.of());
+ public BuildCompletingEvent(ExitCode exitCode, long finishTimeMillis, boolean wasSuspended) {
+ this(exitCode, finishTimeMillis, ImmutableList.of(), wasSuspended);
}
public ExitCode getExitCode() {
@@ -62,11 +70,17 @@
.setCode(exitCode.getNumericExitCode())
.build();
+ BuildEventStreamProtos.BuildFinished.AnomalyReport protoAnamolyReport =
+ BuildEventStreamProtos.BuildFinished.AnomalyReport.newBuilder()
+ .setWasSuspended(wasSuspended)
+ .build();
+
BuildEventStreamProtos.BuildFinished finished =
BuildEventStreamProtos.BuildFinished.newBuilder()
.setOverallSuccess(ExitCode.SUCCESS.equals(exitCode))
.setExitCode(protoExitCode)
.setFinishTimeMillis(finishTimeMillis)
+ .setAnomalyReport(protoAnamolyReport)
.build();
return GenericBuildEvent.protoChaining(this).setFinished(finished).build();
}
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto b/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto
index df6291d..adfdbbb 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto
+++ b/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto
@@ -640,6 +640,15 @@
int32 code = 2;
}
+ // Things that happened during the build that could be of interest.
+ message AnomalyReport {
+ // Was the build suspended at any time during the build.
+ // Examples of suspensions are SIGSTOP, or the hardware being put to sleep.
+ // If was_suspended is true, then most of the timings for this build are
+ // suspect.
+ bool was_suspended = 1;
+ }
+
// If the build succeeded or failed.
bool overall_success = 1 [deprecated = true];
@@ -651,6 +660,8 @@
// TODO(buchgr): Use google.protobuf.Timestamp once bazel's protoc supports
// it.
int64 finish_time_millis = 2;
+
+ AnomalyReport anomaly_report = 4;
}
message BuildMetrics {
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildResult.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildResult.java
index 8eac1f2..cd9bcba 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildResult.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildResult.java
@@ -41,6 +41,8 @@
private long startTimeMillis = 0; // milliseconds since UNIX epoch.
private long stopTimeMillis = 0;
+ private boolean wasSuspended = false;
+
private Throwable crash = null;
private boolean catastrophe = false;
private boolean stopOnFirstFailure;
@@ -86,6 +88,16 @@
return (stopTimeMillis - startTimeMillis) / 1000.0;
}
+ /** Record if the build was suspended (SIGSTOP or hardware put to sleep). */
+ public void setWasSuspended(boolean wasSuspended) {
+ this.wasSuspended = wasSuspended;
+ }
+
+ /** Whether the build was suspended (SIGSTOP or hardware put to sleep). */
+ public boolean getWasSuspended() {
+ return wasSuspended;
+ }
+
public void setExitCondition(ExitCode exitCondition) {
this.exitCondition = exitCondition;
}
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
index da70c87..b87fd4f 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
@@ -13,6 +13,8 @@
// limitations under the License.
package com.google.devtools.build.lib.buildtool;
+import static com.google.devtools.build.lib.platform.SuspendCounter.suspendCount;
+
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
@@ -262,6 +264,7 @@
BuildRequest request, TargetValidator validator) {
BuildResult result = new BuildResult(request.getStartTime());
maybeSetStopOnFirstFailure(request, result);
+ int startSuspendCount = suspendCount();
Throwable catastrophe = null;
ExitCode exitCode = ExitCode.BLAZE_INTERNAL_ERROR;
try {
@@ -318,7 +321,7 @@
catastrophe = throwable;
Throwables.propagate(throwable);
} finally {
- stopRequest(result, catastrophe, exitCode);
+ stopRequest(result, catastrophe, exitCode, startSuspendCount);
}
return result;
@@ -355,12 +358,17 @@
*
* <p>This logs the build result, cleans up and stops the clock.
*
- * @param crash Any unexpected RuntimeException or Error. May be null
- * @param exitCondition A suggested exit condition from either the build logic or
- * a thrown exception somewhere along the way.
+ * @param result result to update
+ * @param crash any unexpected {@link RuntimeException} or {@link Error}. May be null
+ * @param exitCondition a suggested exit condition from either the build logic or a thrown
+ * exception somewhere along the way
+ * @param startSuspendCount number of suspensions before the build started
*/
- public void stopRequest(BuildResult result, Throwable crash, ExitCode exitCondition) {
+ public void stopRequest(
+ BuildResult result, Throwable crash, ExitCode exitCondition, int startSuspendCount) {
Preconditions.checkState((crash == null) || !exitCondition.equals(ExitCode.SUCCESS));
+ int stopSuspendCount = suspendCount();
+ Preconditions.checkState(startSuspendCount <= stopSuspendCount);
result.setUnhandledThrowable(crash);
result.setExitCondition(exitCondition);
InterruptedException ie = null;
@@ -372,6 +380,7 @@
}
// The stop time has to be captured before we send the BuildCompleteEvent.
result.setStopTime(runtime.getClock().currentTimeMillis());
+ result.setWasSuspended(stopSuspendCount > startSuspendCount);
env.getEventBus()
.post(
new BuildCompleteEvent(
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/buildevent/BuildCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/buildtool/buildevent/BuildCompleteEvent.java
index 87b0f02..86516aa 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/buildevent/BuildCompleteEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/buildevent/BuildCompleteEvent.java
@@ -32,7 +32,7 @@
/** Construct the BuildCompleteEvent. */
public BuildCompleteEvent(BuildResult result, Collection<BuildEventId> children) {
- super(result.getExitCondition(), result.getStopTime(), children);
+ super(result.getExitCondition(), result.getStopTime(), children, result.getWasSuspended());
this.result = checkNotNull(result);
}
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/buildevent/TestingCompleteEvent.java b/src/main/java/com/google/devtools/build/lib/buildtool/buildevent/TestingCompleteEvent.java
index 27f69fd..d98ee79 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/buildevent/TestingCompleteEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/buildevent/TestingCompleteEvent.java
@@ -29,10 +29,11 @@
/**
* Creates a new {@link TestingCompleteEvent}.
*
- * @param exitCode the overall exit code of "bazel test".
+ * @param exitCode the overall exit code of "bazel test".
* @param finishTimeMillis the finish time in milliseconds since the epoch.
+ * @param wasSuspended was the build suspended at any point.
*/
- public TestingCompleteEvent(ExitCode exitCode, long finishTimeMillis) {
- super(exitCode, finishTimeMillis, ImmutableList.of(BuildEventId.buildToolLogs()));
+ public TestingCompleteEvent(ExitCode exitCode, long finishTimeMillis, boolean wasSuspended) {
+ super(exitCode, finishTimeMillis, ImmutableList.of(BuildEventId.buildToolLogs()), wasSuspended);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/platform/BUILD b/src/main/java/com/google/devtools/build/lib/platform/BUILD
index 6e99d41..dd130e8 100644
--- a/src/main/java/com/google/devtools/build/lib/platform/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/platform/BUILD
@@ -20,6 +20,16 @@
)
java_library(
+ name = "suspend_counter",
+ srcs = [
+ "SuspendCounter.java",
+ ],
+ deps = [
+ ":jni_loader",
+ ],
+)
+
+java_library(
name = "jni_loader",
srcs = ["JniLoader.java"],
deps = [
diff --git a/src/main/java/com/google/devtools/build/lib/platform/JniLoader.java b/src/main/java/com/google/devtools/build/lib/platform/JniLoader.java
index fbb20ce..6cefb2b 100644
--- a/src/main/java/com/google/devtools/build/lib/platform/JniLoader.java
+++ b/src/main/java/com/google/devtools/build/lib/platform/JniLoader.java
@@ -20,10 +20,11 @@
/** All classes that extend this class depend on being able to load jni. */
class JniLoader {
- protected JniLoader() {}
+ private static final boolean JNI_ENABLED;
static {
- if (!"0".equals(System.getProperty("io.bazel.EnableJni"))) {
+ JNI_ENABLED = !"0".equals(System.getProperty("io.bazel.EnableJni"));
+ if (JNI_ENABLED) {
switch (OS.getCurrent()) {
case LINUX:
case FREEBSD:
@@ -34,7 +35,15 @@
case WINDOWS:
WindowsJniLoader.loadJni();
break;
+ default:
+ throw new AssertionError("switch statement out of sync with OS values");
}
}
}
+
+ protected JniLoader() {}
+
+ public static boolean jniEnabled() {
+ return JNI_ENABLED;
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/platform/SuspendCounter.java b/src/main/java/com/google/devtools/build/lib/platform/SuspendCounter.java
new file mode 100644
index 0000000..99bb9b8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/platform/SuspendCounter.java
@@ -0,0 +1,28 @@
+// Copyright 2019 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.platform;
+
+/** Native methods for dealing with suspension events. */
+public final class SuspendCounter extends JniLoader {
+ static native int suspendCountJNI();
+
+ /**
+ * The number of times the build has been suspended. Currently this is a hardware sleep and/or the
+ * platform equivalents to a SIGSTOP/SIGTSTP.
+ */
+ public static int suspendCount() {
+ return JniLoader.jniEnabled() ? suspendCountJNI() : 0;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/NoTestsFound.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/NoTestsFound.java
index 8c21790..64b7c48 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/NoTestsFound.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/NoTestsFound.java
@@ -20,7 +20,7 @@
/** This event is posted by the {@link TestCommand} if no tests were found. */
public class NoTestsFound extends BuildCompletingEvent {
- public NoTestsFound(ExitCode exitCode, long finishTimeMillis) {
- super(exitCode, finishTimeMillis);
+ public NoTestsFound(ExitCode exitCode, long finishTimeMillis, boolean wasSuspended) {
+ super(exitCode, finishTimeMillis, wasSuspended);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
index 5163587..bbabc84 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
@@ -138,7 +138,10 @@
env.getReporter().handle(Event.error("Couldn't start the build. Unable to run tests"));
ExitCode exitCode =
buildResult.getSuccess() ? ExitCode.PARSING_FAILURE : buildResult.getExitCondition();
- env.getEventBus().post(new TestingCompleteEvent(exitCode, buildResult.getStopTime()));
+ env.getEventBus()
+ .post(
+ new TestingCompleteEvent(
+ exitCode, buildResult.getStopTime(), buildResult.getWasSuspended()));
return BlazeCommandResult.exitCode(exitCode);
}
// TODO(bazel-team): the check above shadows NO_TESTS_FOUND, but switching the conditions breaks
@@ -150,7 +153,8 @@
ExitCode exitCode =
buildResult.getSuccess() ? ExitCode.NO_TESTS_FOUND : buildResult.getExitCondition();
env.getEventBus()
- .post(new NoTestsFound(exitCode, env.getRuntime().getClock().currentTimeMillis()));
+ .post(
+ new NoTestsFound(exitCode, buildResult.getStopTime(), buildResult.getWasSuspended()));
return BlazeCommandResult.exitCode(exitCode);
}
@@ -170,7 +174,10 @@
ExitCode exitCode = buildSuccess
? (testSuccess ? ExitCode.SUCCESS : ExitCode.TESTS_FAILED)
: buildResult.getExitCondition();
- env.getEventBus().post(new TestingCompleteEvent(exitCode, buildResult.getStopTime()));
+ env.getEventBus()
+ .post(
+ new TestingCompleteEvent(
+ exitCode, buildResult.getStopTime(), buildResult.getWasSuspended()));
return BlazeCommandResult.exitCode(exitCode);
}
diff --git a/src/main/native/unix_jni.cc b/src/main/native/unix_jni.cc
index d2d3cc3..4b38229 100644
--- a/src/main/native/unix_jni.cc
+++ b/src/main/native/unix_jni.cc
@@ -1115,4 +1115,15 @@
return portable_pop_disable_sleep();
}
+/*
+ * Class: com_google_devtools_build_lib_platform_SuspendCounter
+ * Method: suspendCountJNI
+ * Signature: ()I
+ */
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_platform_SuspendCounter_suspendCountJNI(
+ JNIEnv *, jclass) {
+ return portable_suspend_count();
+}
+
} // namespace blaze_jni
diff --git a/src/main/native/unix_jni.h b/src/main/native/unix_jni.h
index 4769458..4b23463 100644
--- a/src/main/native/unix_jni.h
+++ b/src/main/native/unix_jni.h
@@ -110,6 +110,10 @@
int portable_push_disable_sleep();
int portable_pop_disable_sleep();
+// Returns the number of times that the process has been suspended (SIGSTOP,
+// computer put to sleep, etc.) since Bazel started.
+int portable_suspend_count();
+
} // namespace blaze_jni
#endif // BAZEL_SRC_MAIN_NATIVE_UNIX_JNI_H__
diff --git a/src/main/native/unix_jni_darwin.cc b/src/main/native/unix_jni_darwin.cc
index c2d668d..8249d82 100644
--- a/src/main/native/unix_jni_darwin.cc
+++ b/src/main/native/unix_jni_darwin.cc
@@ -17,6 +17,7 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
+#include <IOKit/IOMessage.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <stdlib.h>
#include <string.h>
@@ -26,6 +27,7 @@
#include <sys/types.h>
#include <sys/xattr.h>
+#include <atomic>
// Linting disabled for this line because for google code we could use
// absl::Mutex but we cannot yet because Bazel doesn't depend on absl.
#include <mutex> // NOLINT
@@ -168,4 +170,90 @@
return 0;
}
+typedef struct {
+ // Port used to relay sleep call back messages.
+ io_connect_t connect_port;
+
+ // Count of suspensions. Atomic because it can be read from any java thread
+ // and is written to from a dispatch_queue thread.
+ std::atomic_int suspend_count;
+} SuspendState;
+
+static void SleepCallBack(void *refcon, io_service_t service,
+ natural_t message_type, void *message_argument) {
+ SuspendState *state = (SuspendState *)refcon;
+ switch (message_type) {
+ case kIOMessageCanSystemSleep:
+ // This needs to be handled to allow sleep.
+ IOAllowPowerChange(state->connect_port, (intptr_t)message_argument);
+ break;
+
+ case kIOMessageSystemWillSleep:
+ ++state->suspend_count;
+ // This needs to be acknowledged to allow sleep.
+ IOAllowPowerChange(state->connect_port, (intptr_t)message_argument);
+ break;
+
+ case kIOMessageSystemWillNotSleep:
+ --state->suspend_count;
+ break;
+
+ case kIOMessageSystemWillPowerOn:
+ case kIOMessageSystemHasPoweredOn:
+ // We increment g_suspend_count when we are alerted to the sleep as
+ // opposed to when we wake up, because Macs have a "Dark Wake" mode (also
+ // known as PowerNap) which is when the processors (and disk and network)
+ // turn on for brief periods of time
+ // (https://support.apple.com/en-us/HT204032). Dark Wake does NOT trigger
+ // PowerOn messages through our sleep callbacks, but can allow
+ // builds to proceed for a considerable amount of time (for example if
+ // Time Machine is performing a back up).
+ // There is currently a race condition where a build may finish
+ // between the time we receive the kIOMessageSystemWillSleep and the
+ // machine actually goes to sleep (roughly 20 seconds in my experiments)
+ // or between the time we receive the kIOMessageSystemWillSleep and
+ // kIOMessageSystemWillNotSleep. This will result in us reporting that the
+ // build was suspended when it wasn't. I haven't come up with an smart way
+ // of avoiding this issue, but I don't think we really care. Over
+ // reporting "suspensions" is better than under reporting them.
+ default:
+ break;
+ }
+}
+
+int portable_suspend_count() {
+ static dispatch_once_t once_token;
+ static SuspendState suspend_state;
+ dispatch_once(&once_token, ^{
+ IONotificationPortRef notifyPortRef;
+ io_object_t notifierObject;
+
+ // Register to receive system sleep notifications.
+ suspend_state.connect_port = IORegisterForSystemPower(
+ &suspend_state, ¬ifyPortRef, SleepCallBack, ¬ifierObject);
+ CHECK(suspend_state.connect_port != MACH_PORT_NULL);
+ IONotificationPortSetDispatchQueue(notifyPortRef,
+ DISPATCH_TARGET_QUEUE_DEFAULT);
+
+ // Register to deal with SIGCONT.
+ // We register for SIGCONT because we can't catch SIGSTOP and we can't
+ // distinguish a SIGCONT after a SIGSTOP from a SIGCONT after SIGTSTP.
+ // We do have the potential of "over counting" suspensions if you send
+ // multiple SIGCONTs to a process without a previous SIGSTOP/SIGTSTP,
+ // but there is no reason to send a SIGCONT without a SIGSTOP/SIGTSTP, and
+ // having this functionality gives us some ability to unit test suspension
+ // counts.
+ sig_t signal_val = signal(SIGCONT, SIG_IGN);
+ CHECK(signal_val != SIG_ERR);
+ dispatch_source_t signal_source = dispatch_source_create(
+ DISPATCH_SOURCE_TYPE_SIGNAL, SIGCONT, 0, DISPATCH_TARGET_QUEUE_DEFAULT);
+ CHECK(signal_source != NULL);
+ dispatch_source_set_event_handler(signal_source, ^{
+ ++suspend_state.suspend_count;
+ });
+ dispatch_resume(signal_source);
+ });
+ return suspend_state.suspend_count;
+}
+
} // namespace blaze_jni
diff --git a/src/main/native/unix_jni_freebsd.cc b/src/main/native/unix_jni_freebsd.cc
index be0d59b..ea98fd4 100644
--- a/src/main/native/unix_jni_freebsd.cc
+++ b/src/main/native/unix_jni_freebsd.cc
@@ -104,4 +104,9 @@
return -1;
}
+int portable_suspend_count() {
+ // Currently not implemented.
+ return 0;
+}
+
} // namespace blaze_jni
diff --git a/src/main/native/unix_jni_linux.cc b/src/main/native/unix_jni_linux.cc
index 7408526..0647429 100644
--- a/src/main/native/unix_jni_linux.cc
+++ b/src/main/native/unix_jni_linux.cc
@@ -104,4 +104,9 @@
return -1;
}
+int portable_suspend_count() {
+ // Currently not implemented.
+ return 0;
+}
+
} // namespace blaze_jni
diff --git a/src/main/native/windows/BUILD b/src/main/native/windows/BUILD
index 333baf1..e36c773 100644
--- a/src/main/native/windows/BUILD
+++ b/src/main/native/windows/BUILD
@@ -60,6 +60,7 @@
"jni-util.h",
"processes-jni.cc",
"sleep_prevention_jni.cc",
+ "suspend_counter_jni.cc",
"//src/main/native:jni.h",
"//src/main/native:jni_md.h",
],
diff --git a/src/main/native/windows/suspend_counter_jni.cc b/src/main/native/windows/suspend_counter_jni.cc
new file mode 100644
index 0000000..a6d2dbf
--- /dev/null
+++ b/src/main/native/windows/suspend_counter_jni.cc
@@ -0,0 +1,32 @@
+// Copyright 2019 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.
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>
+#include "src/main/native/jni.h"
+
+/*
+ * Class: com_google_devtools_build_lib_platform_SuspendCounter
+ * Method: suspendCountJNI
+ * Signature: ()I
+ */
+extern "C" JNIEXPORT jint JNICALL
+Java_com_google_devtools_build_lib_platform_SuspendCounter_suspendCountJNI(
+ JNIEnv *, jclass) {
+ // Currently not implemented.
+ return 0;
+}
diff --git a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
index a1c3bc4..aa69aea 100644
--- a/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
+++ b/src/test/java/com/google/devtools/build/lib/buildtool/util/BlazeRuntimeWrapper.java
@@ -356,7 +356,10 @@
this.configurations = lastResult.getBuildConfigurationCollection();
finalizeBuildResult(lastResult);
buildTool.stopRequest(
- lastResult, null, success ? ExitCode.SUCCESS : ExitCode.BUILD_FAILURE);
+ lastResult,
+ null,
+ success ? ExitCode.SUCCESS : ExitCode.BUILD_FAILURE,
+ /*startSuspendCount=*/ 0);
getSkyframeExecutor().notifyCommandComplete(env.getReporter());
}
} finally {
diff --git a/src/test/java/com/google/devtools/build/lib/platform/SuspendCounterTest.java b/src/test/java/com/google/devtools/build/lib/platform/SuspendCounterTest.java
new file mode 100644
index 0000000..e68692e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/platform/SuspendCounterTest.java
@@ -0,0 +1,49 @@
+// Copyright 2019 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.platform;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.build.lib.util.ProcessUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link SuspendCounter}. */
+@RunWith(JUnit4.class)
+public final class SuspendCounterTest {
+
+ @Test
+ public void testSuspendCounter() throws Exception {
+ if (OS.getCurrent() == OS.DARWIN) {
+ int startSuspendCount = SuspendCounter.suspendCount();
+
+ // Send a SIGCONT to ourselves.
+ ProcessBuilder builder =
+ new ProcessBuilder("kill", "-s", "CONT", String.valueOf(ProcessUtils.getpid()));
+ Process process = builder.start();
+ process.waitFor();
+
+ // Allow 10 seconds for signal to propagate.
+ for (int i = 0; i < 1000 && SuspendCounter.suspendCount() <= startSuspendCount; ++i) {
+ Thread.sleep(10 /* milliseconds */);
+ }
+ assertThat(SuspendCounter.suspendCount()).isGreaterThan(startSuspendCount);
+ } else {
+ assertThat(SuspendCounter.suspendCount()).isEqualTo(0);
+ }
+ }
+}