Solving the iMac's Automatic Suspend to RAM Problem with a Custom Shell Script
Automatic suspend to RAM, also known as sleep mode or energy saving, has always been problematic on my wife’s iMac. It either didn’t enter sleep mode when idle, or it would wake up accidentally and fail to fall asleep again. This issue can be frustrating, especially when you’re trying to keep the system energy-efficient.
💤 The Cause: Assertions and Multimedia Apps
The root cause of this issue lies in assertions, which are used by many multimedia apps to prevent the system from going to sleep while they are active. The problem arises when these apps don’t handle assertions properly, leaving the system awake longer than intended. For example:
- Spotify: In the past, it would set an assertion just by opening the app, even if it wasn’t playing any content. Although this may have been fixed in more recent updates, the issue was common.
- Chrome: It sets assertions when there is multimedia content in any of the open tabs, even if the tab isn’t active or playing content.
🛠️ Diagnosing the Issue
I encountered this problem again recently with Chrome. One of the many open tabs had set an assertion, which was preventing the iMac from going to sleep. To identify which processes are preventing sleep, you can run the following command in Terminal:
1
pmset -g assertions
Here’s an example output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ pmset -g assertions
2017-02-16 16:48:14 +0100
Assertion status system-wide:
BackgroundTask 1
ApplePushServiceTask 0
UserIsActive 0
PreventUserIdleDisplaySleep 0
PreventSystemSleep 0
ExternalMedia 0
PreventUserIdleSystemSleep 1
NetworkClientActive 0
Listed by owning process:
pid 237(coreaudiod): [0x000ab1d00001012e] 00:00:05 PreventUserIdleSystemSleep named: "com.apple.audio.context568.preventuseridlesleep"
Created for PID: 55482.
Kernel Assertions: 0x4=USB
id=500 level=255 0x4=USB mod=28.01.17 11:46 description=EHC2 owner=AppleUSBEHCI
id=501 level=255 0x4=USB mod=01.02.17 09:50 description=EHC1 owner=AppleUSBEHCI
In this example, the system shows an active assertion by coreaudiod
, which is a common process for handling audio on macOS. Additionally, other assertions, such as USB-related ones, might be present, further preventing the system from entering sleep mode.
🔧 Previous Solutions and Their Limitations
Previously, there was a tool called “Please Sleep” that could enforce sleep mode on macOS. However, this tool was abandoned and no longer works with macOS 10.10 and onwards. Given that, I decided to create a custom solution: a shell script that can automatically put the iMac to sleep when certain conditions are met.
🖥️ Creating a Custom Shell Script to Force Sleep
The following shell script checks how long the system has been idle and compares that with the configured sleep time. If the idle time exceeds the configured sleep time, the script will force the iMac to sleep.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
# This script checks the system's sleep settings and the device's idle time
# It returns the difference between the two, and if the system is idle too long,
# it forces the system to sleep.
# Get the system's configured sleep time (in minutes)
systemSleepTimeMinutes=`pmset -g | grep "^[ ]*sleep" | awk '{ print $2 }'`
if [ $systemSleepTimeMinutes -gt "0" ]; then
# Convert sleep time to seconds
systemSleepTime=`echo "$systemSleepTimeMinutes * 60" | bc`
# Get the device's idle time (in seconds)
devicesIdleTime=`/usr/sbin/ioreg -c IOHIDSystem | awk '/HIDIdleTime/ {print $NF/1000000000; exit}'`
devicesIdleTime=`sed 's/,/\./' <<< $devicesIdleTime`
# Calculate the time left before sleep
secondsBeforeSleep=`echo "$systemSleepTime - $devicesIdleTime" | bc`
echo "Time before sleep (sec): $secondsBeforeSleep"
# If idle time exceeds the system sleep time, trigger sleep
if (( $(bc <<< "$secondsBeforeSleep < 0.0") )); then
echo "System is idle, going to sleep..."
`pmset sleepnow >/dev/null 2>&1 &`
fi
else
echo "The system is set to never sleep."
exit 0
fi
⚙️ How the Script Works
- Retrieve System Sleep Settings: It first checks how long the system is set to wait before going to sleep by using the
pmset -g
command. It then converts the sleep time into seconds. - Check Device Idle Time: The script uses the
ioreg
command to check how long the system has been idle (i.e., how long since the last user interaction). - Compare Idle Time to Sleep Time: If the idle time exceeds the configured system sleep time, the script triggers a sleep command using
pmset sleepnow
.
⏰ Scheduling the Script with cron
To make the script run automatically, we can use cron, a built-in task scheduler in macOS. Here’s how to set it up:
- Open Terminal and run
crontab -e
to edit the user’s crontab file. - Add the following line to schedule the script to run every 5 minutes:
1
2
# min hour mday month wday command
*/5 * * * * /bin/bash /Applications/_scripts/sleep.sh
This will check the idle time and sleep conditions every 5 minutes. If you prefer, you can adjust the interval based on your needs, but 5 minutes works well, especially since macOS’ energy-saving settings are typically set to 10 minutes. With this setup, the iMac will sleep after approximately 15 minutes of inactivity.
⚠️ Limitations and Drawbacks
While this solution works effectively, there are a couple of potential drawbacks to keep in mind:
Strict Enforcement of Sleep: The script forcefully triggers sleep, which means it may interfere with tasks that are currently running, such as watching a video or performing a large backup. If you don’t want the system to go to sleep while doing these tasks, you can temporarily increase the system’s sleep time in the Energy Saver settings to allow enough time for these processes to complete.
Manual Intervention: If you need the system to stay awake longer for specific activities (like presentations, video watching, etc.), you’ll need to manually adjust the sleep settings, as the script does not account for these exceptions.
🛑 Additional Tips for Managing macOS Sleep Settings
Prevent Sleep During Specific Activities: If you know you will be using your iMac for an extended period (for example, watching a movie or downloading a large file), you can prevent sleep by temporarily disabling sleep via System Preferences > Energy Saver. Alternatively, you can use the
caffeinate
command in the terminal to prevent sleep for a specified period or while a process is running.Example to prevent sleep during a long task:
1
caffeinate -i your-command-here
Using Third-Party Apps: There are various third-party apps, such as Amphetamine or Caffeine, that allow you to easily prevent sleep when needed. These apps provide a more user-friendly interface for controlling your iMac’s sleep behavior.
🧠 Final Thoughts
The issue of the iMac failing to sleep due to stuck assertions can be quite frustrating, especially when it’s caused by apps like Chrome or Spotify. With this simple shell script and a cron job, you can enforce sleep behavior on macOS, ensuring that your iMac follows your energy-saving preferences. While the solution is not perfect and may require manual adjustments during specific tasks, it provides a robust workaround for a problem that many macOS users encounter.
By taking control of your system’s sleep behavior, you can ensure a more efficient and hassle-free experience on your iMac.