// Copyright 2021 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. #include #include #include #include "src/main/cpp/util/logging.h" #include "src/main/native/darwin/util.h" #include "src/main/native/unix_jni.h" namespace blaze_jni { typedef struct { // Port used to relay sleep call back messages. io_connect_t connect_port; } 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: BAZEL_LOG(USER) << "suspend anomaly due to kIOMessageSystemWillSleep"; suspend_callback(SuspensionReasonSleep); // This needs to be acknowledged to allow sleep. IOAllowPowerChange(state->connect_port, (intptr_t)message_argument); break; case kIOMessageSystemHasPoweredOn: // Note that Macs have a "Dark Wake" mode (also known as PowerNap) which // can have the processors (and disk and network) turn on. // (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). // 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. BAZEL_LOG(USER) << "suspend anomaly due to kIOMessageSystemHasPoweredOn"; suspend_callback(SuspensionReasonWake); break; case kIOMessageSystemWillPowerOn: case kIOMessageSystemWillNotSleep: // We don't handle will not sleep. This can only occur is somebody else // cancels the sleep, and will never occur AFTER a // kIOMessageSystemWillSleep. // We don't handle will power on, we only care when it HAS powered on. default: break; } } void portable_start_suspend_monitoring() { static dispatch_once_t once_token; static SuspendState suspend_state; dispatch_once(&once_token, ^{ dispatch_queue_t queue = bazel::darwin::JniDispatchQueue(); IONotificationPortRef notifyPortRef; io_object_t notifierObject; // Register to receive system sleep notifications. // Testing needs to be done manually. Use the logging to verify // that sleeps are being caught here. suspend_state.connect_port = IORegisterForSystemPower( &suspend_state, ¬ifyPortRef, SleepCallBack, ¬ifierObject); BAZEL_CHECK_NE(suspend_state.connect_port, MACH_PORT_NULL); IONotificationPortSetDispatchQueue(notifyPortRef, queue); // Register to deal with SIGCONT. // We register for SIGCONT because we can't catch SIGSTOP. // 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); BAZEL_CHECK_NE(signal_val, SIG_ERR); dispatch_source_t signal_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGCONT, 0, queue); BAZEL_CHECK_NE(signal_source, nullptr); dispatch_source_set_event_handler(signal_source, ^{ BAZEL_LOG(USER) << "suspend anomaly due to SIGCONT"; suspend_callback(SuspensionReasonSIGCONT); }); dispatch_resume(signal_source); signal_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTSTP, 0, queue); BAZEL_CHECK_NE(signal_source, nullptr); dispatch_source_set_event_handler(signal_source, ^{ BAZEL_LOG(USER) << "suspend anomaly due to SIGTSTP"; suspend_callback(SuspensionReasonSIGTSTP); }); dispatch_resume(signal_source); }); } } // namespace blaze_jni