On a macOS machine running v15.0, I have a daemon run by launchd which subscribes to the sleep and wakeup notifications using the IORegisterForSystemPower
method.
void PowerCallBack(void* refCon, io_service_t service, natural_t messageType, void* messageArgument)
{
switch (messageType)
{
case kIOMessageSystemWillSleep:
logger->Debug("Received sleep notification from macOS");
if (refCon)
{
//Handle Sleep
}
IOAllowPowerChange(root_port, (long)messageArgument);
break;
case kIOMessageSystemHasPoweredOn:
logger->Debug("Received wakeup notification from macOS");
if (refCon)
{
// Handle Wakeup
}
break;
default:
break;
}
}
void MacOSNotification::RegisterNotifications()
{
logger->Debug("Registering for notifications from macOS");
powerNotificationThread = [[NSThread alloc] initWithBlock:^{
// Notifier object, used to deregister later
root_port = IORegisterForSystemPower(this, ¬ifyPortRef, PowerCallBack, ¬ifierObject);
if (root_port == 0)
{
return;
}
logger->Debug("Registered for system power notifications from macOS");
// Add the notification port to the application runloop
CFRunLoopAddSource(CFRunLoopGetCurrent(),
IONotificationPortGetRunLoopSource(notifyPortRef),
kCFRunLoopCommonModes);
CFRunLoopRun();
}]; //END OF THREAD BLOCK
[powerNotificationThread start];
}
Using this mechanism, I am getting notifications for normal sleep and wakeup transitions like closing and opening the lid. I need these notifications to terminate/reconnect my connection to a cloud service when we go to sleep/wakeup respectively.
I have noticed from the power logs at /private/var/log/powermanagement
that the after the sleep initiated by lid closing or clicking sleep in the top apple menu (both of which I can detect as they generate power notification), the macOS machine wakes up with the following message from powerd logs:
DarkWake from Deep Idle [CDNP] : due to SMC.OutboxNotEmpty smc.70070000 wifibt/
I do not get any notification for this wakeup and my application threads start running. This happens every 15 to 16 mins from my observation.
After this DarkWake, we go back to 'Maintenance' sleep in under a minute as can be seen by the following powerd log:
Entering Sleep state due to 'Maintenance Sleep':TCPKeepAlive=active
I do not get any notifications for this either.
Is there a way to track and get notified of these DarkWake -> Maintenance sleep cycles? At the very least I would like to log when we go into and come out of these states. Currently I just rely on seeing a 15 min window of no logs to know this must have a DarkWake -> Maintenance sleep cycle.
Also is there a way to make sure my application and its threads are not woken up by DarkWake (like an opt-out)? I would like to make it so that my application only runs when we are properly sleeping and waking.
First off, just to make sure this is clear, the formal situation is that we don't have any API for detecting DarkWake, so you should file a bug asking for us to add one. The informal situation is that even without any specific API the state might be detectable, given some experimentation and testing. That also means that what I'm offering suggestions to try, not guaranteeing anything will work.
I have tried the XPC activity system by writing a small test program which I load up using launchd, making it a LaunchDaemon. What I observe is that I get the activity callback to run only once
Your activity isn't configured to repeat, so it only runs once. Beyond that, you might try immediately responding with "XPC_ACTIVITY_STATE_DEFER" whenever you're run. This will place you task back into "wait" without resetting any time criteria.
Regarding the IOPMFindPowerManagement approach, can you please clarify it a bit more. From the IOPMFindPowerManagement method I get a io_connect_t object. How does this one allow me to get the IOrootDomain IOServiceObject like you say. I am new to this API so could not find a way to do so. Also, I think I need the io_service_t object to pass to the IOServiceAddInterestNotification from the io_connect_t. Can you please point me to the right direction
Sorry about that, I was working fast and didn't look closely enough at the documentation. That code opens the user client, which is not what you want. I've attached some code showing how to get the node you want. Some notes on that code:
-
Using "IORegistryEntryFromPath" is something we usually recommend against, as it assumes the IORegistry's structure will remain constant. This case is an exception, as this particular node is the root of the power plane, which the builds out from that fixed point. Having the node located "somewhere else" would imply such a large scale change that it's unlikely any of this code would work at all. Besides, if it makes you feel better, this is the same way IOPMFindPowerManagement finds the root node.
-
You can also pull the system wide assertion list of the kernel ("IOPMCopyAssertionsStatus") and of user space ("IOPMCopyAssertionsByProcess"). The pid list in particular might be very useful, as I think dark wake is always triggered by the same process.
#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
#import <IOKit/pwr_mgt/IOPM.h>
#import <IOKit/pwr_mgt/IOPMLib.h>
void KE_PrintIOObject(io_service_t myObj)
{
if(myObj != 0) {
NSString* className = CFBridgingRelease(IOObjectCopyClass(myObj));
NSLog(@"Class Name = %@", className);
CFMutableDictionaryRef propDictionary = NULL;
kern_return_t err = IORegistryEntryCreateCFProperties(myObj, &propDictionary, CFAllocatorGetDefault(), 0);
if(kIOReturnSuccess == err) {
CFShow(propDictionary);
CFRelease(propDictionary);
}
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
io_service_t pmService = IORegistryEntryFromPath( kIOMainPortDefault, kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
if( pmService ) {
KE_PrintIOObject(pmService);
}
CFDictionaryRef assertionDictionary = NULL;
kern_return_t err = IOPMCopyAssertionsStatus(&assertionDictionary);
if(kIOReturnSuccess == err) {
CFShow(assertionDictionary);
CFRelease(assertionDictionary);
}
err = IOPMCopyAssertionsByProcess(&assertionDictionary);
if(kIOReturnSuccess == err) {
CFShow(assertionDictionary);
CFRelease(assertionDictionary);
}
}
return 0;
}
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware