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, &notifyPortRef, SleepCallBack, &notifierObject);
+    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);
+    }
+  }
+}