Apps That Break on Resume from Sleep — How Windows Power Events Work and How to Build Business Apps That Survive Them

· · Windows, Power Management, Windows Development, Business Applications, Device Control, Troubleshooting, Win32 API

“I closed the laptop, opened it the next morning, and the business app was full of errors.” “The equipment-monitoring app drops data only after lunch.” “The resident tool that exports to Excel sometimes stops on a connection error.” — These tickets share a single suspect. Sleep.

Business apps from the era when desktop PCs were the mainstream were written on the unspoken assumption that “the PC stays on”. The main battlefield today is the laptop, and by default it sleeps after a few minutes of idle. On a Modern Standby-capable machine, the very semantics of sleep have changed from the traditional model. Aimed at developers writing business apps and equipment-control software on Windows, this article organises, from primary sources, what the OS notifies an app of before and after sleep, what breaks, and how to write an app that survives resume.

1. The Bottom Line First

  • Sleep is an event the app has “no right to refuse”. You are notified just beforehand with WM_POWERBROADCAST (PBT_APMSUSPEND), but the grace period is about 2 seconds, and on an emergency suspend the notification does not even arrive.12
  • On resume from suspend, PBT_APMRESUMEAUTOMATIC arrives, and on a user-initiated resume PBT_APMRESUMESUSPEND arrives as well. Required work such as reconnecting belongs on the former as a rule. Transitions in and out of Modern Standby’s low-power idle do not always line up with these notifications, though, so treat the notifications as an aid.34
  • Design on the assumption that TCP connections, serial ports, and device handles do not survive across resume. Reconnect logic that rebuilds them on a resume notification or a communication error is the main event.
  • Watch timers and the handling of time. Periodic work stops during sleep, and how it fires immediately after resume differs by timer API and runtime. A “huge jump in elapsed time” also happens, so the safe approach is to rebuild the schedule on resume.
  • Suppress sleep explicitly for intervals you do not want to sleep through. Use SetThreadExecutionState (ES_SYSTEM_REQUIRED) or a power request (PowerSetRequest), and always clear it when the work finishes.45
  • On a Modern Standby machine the system still runs intermittently during sleep, but desktop apps are paused. You cannot hold the expectation that “our app should keep running during sleep”.6
  • The standard investigation tools are powercfg (/requests, /lastwake, /sleepstudy) and Kernel-Power in the event log.

2. What Happens Around Sleep — The Flow of Power Events

The OS broadcasts power-state changes to every app as a WM_POWERBROADCAST message.2 There are three main events that involve sleep and resume.

Event Meaning
PBT_APMSUSPEND About to enter sleep (the last chance to prepare)
PBT_APMRESUMEAUTOMATIC Resumed (always arrives on resume)
PBT_APMRESUMESUSPEND Resume caused by a user action (this one is conditional)

PBT_APMSUSPEND is the notification just before sleep, and here you can prepare by closing files and saving state. There are two conditions, though. First, the time allowed for processing is about 2 seconds per app, and if you exceed it the system proceeds without waiting.1 Second, on an emergency suspend such as a critically low battery, it sleeps immediately with no advance notification.2 A design that “must finish before sleep” does not hold. Treat the notification as a chance to “do it if you make it”, and put the main work on the resume side.

The resume side is two stages. PBT_APMRESUMEAUTOMATIC arrives on resume from a suspend transition. On top of that, if the machine resumed because of a user action such as the power button or a key press (or user presence was detected afterwards), PBT_APMRESUMESUSPEND follows. Conversely, an unattended resume for a remote wake over the network or for maintenance delivers only PBT_APMRESUMEAUTOMATIC.3 These two stages are themselves a hint for how to split the work — do mechanical recovery such as rebuilding connections on PBT_APMRESUMEAUTOMATIC, and do user-facing actions such as screen updates or a re-login prompt on PBT_APMRESUMESUSPEND.

Notification flow for sleep and resumePBT_APMSUSPEND arrives just before sleep with about 2 seconds of grace; on resume, PBT_APMRESUMEAUTOMATIC always arrives, and PBT_APMRESUMESUSPEND follows only for a user-initiated resumeAppOSAppOSSleep (code does not run)PBT_APMSUSPEND (about 2 seconds of grace)Save state and close connectionsPBT_APMRESUMEAUTOMATIC (arrives on resume)Reconnect and restore statePBT_APMRESUMESUSPEND (user-initiated resume only)Screen updates and other user-facing work

Figure 1: Notifications are only “a word just beforehand, and one or two words after resume”. The star of recovery is the work on the resume side.

Difference between ordinary sleep and emergency suspendOrdinary sleep delivers PBT_APMSUSPEND just beforehand with about 2 seconds to prepare, but an emergency suspend such as critical battery stops with no advance notification, so a design that depends on the advance notification does not holdOrdinary sleepPBT_APMSUSPEND (about 2 seconds of grace)Prepare, then stopEmergency suspend (critically low battery)Stop with no advance notificationA design that assumes the notification will come does not hold

Figure 2: An emergency suspend comes with no warning. So preparation is “a bonus if you make it”, and the main work goes on the resume side.

Note that WM_POWERBROADCAST does not distinguish the kind of low-power state (sleep versus hibernation).4 The right abstraction for the app is to treat it as one kind of event: “it stopped, and it came back”. Windowless services and console apps can receive the same notifications by using RegisterSuspendResumeNotification in callback form (DEVICE_NOTIFY_CALLBACK).7

Splitting work across the two resume stagesPut mechanical recovery such as reconnecting on PBT_APMRESUMEAUTOMATIC, which arrives on resume; put user-facing work such as screen updates or a re-login prompt on PBT_APMRESUMESUSPEND, which arrives only on a user-initiated resumePBT_APMRESUMEAUTOMATIC (on resume)Mechanical recoveryPBT_APMRESUMESUSPEND (user-initiated resume)User-facing workReconnect and reopen handlesScreen updates and a re-login prompt

Figure 3: The latter does not arrive on an unattended resume, so putting required recovery on the latter will miss it.

3. Modern Standby — The Meaning of “Sleep” Has Changed

Another modern fact to take on board is Modern Standby. Traditional S3 sleep was a simple model that “stopped the system as a whole”; sleep on a Modern Standby machine is a smartphone-like model in which the system keeps running intermittently after the screen goes off.

What matters for a business app here is that desktop apps are paused by the Desktop Activity Moderator (DAM) at the first stage of entering sleep.6 The system itself still runs from time to time to keep the network up and receive notifications, but the components that benefit from that are ones that participate in this mechanism — ordinary desktop-app code does not run. So from a developer’s point of view the conclusion is the same for Modern Standby and for S3 — design on the assumption that your code does not run during sleep.

Difference between traditional sleep and Modern StandbyTraditional S3 sleep stops the system as a whole, while under Modern Standby the system still runs intermittently after the screen goes off. Desktop apps are paused by DAM in either case, so the app's code does not runTraditional S3 sleep: the whole system stopsThe app's code does not runModern Standby: the system runs intermittentlyDesktop apps are paused by DAM

Figure 4: The model has changed, but for a desktop app the conclusion is the same: “you cannot run during sleep”.

Another caution is how little you can rely on the notifications. Under Modern Standby, transitions in and out of low-power idle do not line up with the traditional suspend transition, and a connection can already be broken without a notification ever arriving. Treat the resume notification as an aid, and put reconnection triggered by error detection (Chapter 5) on the main recovery path.

Another difference is the “slippery” feel of the behaviour. Reaching the depths of sleep is staged, and the timing of disconnects and stops is not as sharp as under S3. The distinction between “the screen just went off” and “it slept” is hard for the user to see as well, so when you take a symptom you need to confirm “did they close the lid” and “how many minutes was it left idle”.

4. What Breaks — Classic Symptoms

The TCP connection is dead. During sleep, the other side, NAT, and firewalls treat your silence as a timeout and discard the connection. Worse, the socket on this side does not know about the error, so it fails only when you send or receive after resume. Or worse still, a receive wait never errors at all (that is why you need a keepalive). Database connections and WebSockets have the same shape.

Serial-port and USB-device handles become invalid. A USB-connected device can look, on resume, as if it was “unplugged and plugged back in” once, and the handle you had open starts returning errors. That is the typical pattern of an equipment-control app that “gets a communication error only after lunch”. Reconnect design is also covered in the serial-communication article.

Continuity of time breaks. Timer-driven work such as “poll every 10 seconds” does not fire during sleep. How it fires immediately after resume (expired due work fires once immediately, nothing happens until the next period, and so on) differs by the timer API and runtime you are using, so do not leave the handling of missed ticks to implicit behaviour — the safe approach is to rebuild the schedule on the resume notification. Also, elapsed-time calculations (the difference from the previous timestamp) suddenly become “8 hours’ worth”, and average calculations or timeout judgments break. Scheduled work such as “run every night at 2 AM” simply does not run if the PC is asleep at that time (wake it with Task Scheduler’s wake-from-sleep feature if you need it).

Three shapes in which continuity of time breaksPeriodic work stops during sleep and post-resume firing differs by API, so rebuild the schedule on resume; the difference from the previous timestamp becomes huge after resume, so guard it; scheduled work does not run if the machine is asleep, so consider Task Scheduler's wake-from-sleepPeriodic work: stopsRebuild scheduleElapsed-time: blows upGuard abnormal diffsScheduled: never ranWake-from-sleep

Figure 5: Write timer and time handling on the assumption that “time jumps”. Each of the three shapes has a type of countermeasure.

Three things that break across sleepAcross sleep, a TCP connection has been discarded by a timeout on the other side, a USB device's handle is invalidated as a reconnect, and elapsed-time-based work observes a huge time jump. Recover each with reconnect, reopen, and a difference guardSleep intervalTCP: peer discarded itUSB or elapsed time?USB: handle invalidElapsed time: a jumpDetect + reconnectReopen the deviceGuard abnormal diffs

Figure 6: What breaks falls into three families — “connections”, “handles”, and “continuity of time” — and each has a settled type of recovery.

Re-authentication to shared resources. Network drives and VPNs often need to be re-established after resume, and there is a “startup valley” of a few to several tens of seconds immediately after resume in which access fails. It is safer not to retry everything at once immediately after resume, but to wait a little and retry in stages.

5. Building Apps That Survive Resume

The principle is one thing. Assume that “connections and handles do not survive across sleep”, and structure the app so you can always recover.

Detect resume and recover. When the top-level window’s WM_POWERBROADCAST receives PBT_APMRESUMEAUTOMATIC, discard the connections you hold and rebuild them. The point is not to rely on the resume notification alone. Missed notifications and communication that happens before the notification are both real, so always pair it with a path that “reconnects when a communication error is detected”, and treat the resume notification as a trigger that merely starts that earlier.

// C#: funnel both the resume notification and communication errors into the same reconnect path
protected override void WndProc(ref Message m)
{
    const int WM_POWERBROADCAST = 0x0218;
    const int PBT_APMRESUMEAUTOMATIC = 0x0012;
    if (m.Msg == WM_POWERBROADCAST && (int)m.WParam == PBT_APMRESUMEAUTOMATIC)
    {
        _connectionManager.RequestReconnect();   // idempotent reconnect request
    }
    base.WndProc(ref m);
}

Make the reconnect work itself idempotent (safe no matter how many times it is called), retry on failure with exponential backoff, and in the steady state detect a dead connection early with a keepalive — put those three together as a set, and you will survive not only resume from sleep but also a brief network drop or a device reboot.

Resume-resilient reconnect designThe resume notification, a communication error, and a keepalive failure all funnel into the same idempotent reconnect work, which retries with exponential backoff on failureyesnoResume notification (PBT_APMRESUMEAUTOMATIC)Idempotent reconnect workCommunication-error detectionKeepalive failureSucceeded?Back to normal operationRetry after exponential backoff

Figure 7: Concentrate reconnect into a single idempotent path, and enter the same road from the resume notification, error detection, or the keepalive.

Revisit the handling of time. For work that uses “elapsed time since last time”, put in a guard that invalidates the interval when it detects an abnormally large difference (do not fold it into an average, do not treat it as a timeout). Measuring elapsed time across resume requires keeping a distinction between a clock that advances during sleep (wall-clock time) and time actually spent on work.

Suppress sleep explicitly for intervals you do not want to sleep through. During work that must not be slept through — a data migration, continuous communication with a device, and so on — you can keep the system awake with SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) (add ES_DISPLAY_REQUIRED if you also want to keep the screen on).45 A better-behaved method is the power-request API (PowerCreateRequest + PowerSetRequest), which can attach a reason string, and powercfg /requests will then show “who is blocking it and why”.8 Note that suppression via SetThreadExecutionState is per thread, and you clear it from the same thread that set it. For work that changes threads, such as async/await, use the power-request side, which is managed by a handle. There are cautions. First, what these suppress is automatic idle sleep. They cannot stop an explicit user action such as closing the lid or choosing Sleep from the Start menu, so you cannot skip this chapter’s reconnect design even while suppression is on. Second, on battery on a Modern Standby machine, these power requests are also cut off some time after the sleep timeout elapses. Work that cannot be interrupted must be guaranteed by AC power or by operations.8 Third, always clear it when the work finishes. A missed clear becomes a new bug: “this PC, for some reason, will not sleep”.

Two means of suppressing sleepWhether you use the convenient SetThreadExecutionState or the power-request API that can attach a reason string and is visible to an administrator via powercfg, always clear it when the work endsWork interval that must not be slept throughSetThreadExecutionStatePower request (PowerSetRequest)Convenient — flags onlyWith a reason — visible in powercfgAlways clear it when the work ends

Figure 8: For either means, “clear it when you finish” is an absolute condition. A power request that can make the reason visible is kinder to operations.

Services and windowless apps receive callback notifications with RegisterSuspendResumeNotification (DEVICE_NOTIFY_CALLBACK).7 If continuous operation is a genuine requirement, the root solution is to revisit a design that keeps the work resident on a client PC that sleeps, and move it to the server side or to a machine operated without sleep.

6. Investigation — powercfg and the Event Log

Investigation around power is well served by the tools that ship with the OS.

  • It will not sleep: powercfg /requests lists the processes and drivers that have issued a power request. “The app forgot to clear SetThreadExecutionState” shows up here too.
  • It wakes on its own: powercfg /lastwake shows the most recent wake reason, and powercfg /waketimers shows timers currently reserved to wake the machine.
  • Modern Standby quality: powercfg /sleepstudy generates a report of power consumption and activity per sleep interval.9
  • Confirming the timeline: The Kernel-Power source in the event log (System) keeps records of entering sleep and of resume. Matching them against the app’s log lets you confirm objectively whether “there was a resume just before the error”.
Mapping power-trouble symptoms to investigation commandsFor a will-not-sleep symptom, find who holds a power request with powercfg /requests; for a wakes-on-its-own symptom, find the wake reason with /lastwake and /waketimers; for a timeline, use Kernel-Power in the event logIt will not sleeppowercfg /requestsIt wakes on its ownpowercfg /lastwake and /waketimersWant to confirm the timelineKernel-Power in the event logA forgotten sleep suppression shows up too

Figure 9: Symptoms map to investigation commands in three families. First confirm “did it just sleep”, then split.

In ticket handling, just asking first “was the PC asleep just beforehand (did they close the lid)” speeds the isolation a great deal.

7. Summary

  • Sleep cannot be refused. The advance notification (PBT_APMSUSPEND) is best-effort with about 2 seconds of grace, and it does not come in an emergency. Put the main design on the resume side.
  • Resume notifications are PBT_APMRESUMEAUTOMATIC (on resume from suspend) + PBT_APMRESUMESUSPEND (on a user action). Keep error-driven reconnect on the main path for the case where the notification does not arrive.
  • Assume that connections and handles do not survive across resume, and implement the three-piece set of an idempotent reconnect + exponential backoff + a keepalive.
  • Put a guard against “abnormal differences” on elapsed-time-based work. Design scheduled work on the assumption that it does not run during sleep.
  • For intervals that must not be slept through, suppress sleep explicitly with SetThreadExecutionState or a power request, and always clear it when you finish.
  • Investigation is powercfg (/requests, /lastwake, /sleepstudy) and the Kernel-Power event log. In ticket handling, ask first “did it sleep just beforehand”.

From the app’s point of view, sleep is an event in which “time jumps without warning, connections to the surroundings are cut, and then it comes back”. Whether you have woven that into the design as part of everyday life, rather than as an abnormal situation, is what separates the stability of a business app in the laptop era.

KomuraSoft LLC handles root-cause investigations of bugs such as “communication breaks after resume from sleep” and “the connection to the device drops after lunch”, retrofitting reconnect logic and power-event handling onto existing apps, and design reviews of business apps and equipment-control software that assume laptop operation.

References

  1. Microsoft Learn, PBT_APMSUSPEND event. On this being the event that arrives just before the computer enters the suspend state; on the app being expected to finish the work needed to save data; and on the system allowing about 2 seconds to handle this notification, with an app that continues beyond that being subject to interruption.  2

  2. Microsoft Learn, System Power Management Events. On the system broadcasting operating-mode changes such as sleep in advance; on PBT_APMSUSPEND being notified before idle sleep so you can prepare by closing files and saving data; on an emergency suspend (critical battery and the like) giving no advance notification; on handling of this message being allowed a maximum of 2 seconds per app and being cut off after the timeout; and on every app being notified on resume.  2 3

  3. Microsoft Learn, PBT_APMRESUMESUSPEND event. On this being sent after PBT_APMRESUMEAUTOMATIC on a user-initiated resume or when user input is detected afterwards; on only PBT_APMRESUMEAUTOMATIC being sent for a resume from an external cause such as a remote wake; and on the app being expected to reopen files closed at sleep time and to prepare for user input.  2

  4. Microsoft Learn, WM_POWERBROADCAST message. On PBT_APMRESUMEAUTOMATIC always being sent on resume, with PBT_APMRESUMESUSPEND sent as well on a resume from user input; on this message not distinguishing the kind of low-power state; on details of power-state transitions being recorded in the system event log; and on calling SetThreadExecutionState to prevent the system from entering a low-power state.  2 3 4

  5. Microsoft Learn, SetThreadExecutionState function (winbase.h). On ES_SYSTEM_REQUIRED and ES_DISPLAY_REQUIRED being able to suppress the system’s idle sleep and display power-off; and on declaring continuous suppression with ES_CONTINUOUS and clearing it by calling ES_CONTINUOUS alone when you are finished.  2

  6. Microsoft Learn, Prepare software for modern standby. On the Desktop Activity Moderator (DAM) pausing desktop apps at the first stage of the transition into Modern Standby; and on the system then moving in stages into a low-power phase and a resiliency phase, with only permitted components running intermittently.  2

  7. Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). On this being the API that registers to receive suspend/resume notifications, and on specifying DEVICE_NOTIFY_CALLBACK so that a windowless app or service can receive the notification via a callback in addition to message delivery to a window handle.  2

  8. Microsoft Learn, PowerSetRequest function (winbase.h). On being able to set a request type such as system or display stay-awake on a power-request object created with PowerCreateRequest; on being able to attach a diagnostic reason string; and on outstanding power requests being enumerable with powercfg /requests.  2

  9. Microsoft Learn, Modern standby SleepStudy. On the report generated by powercfg /sleepstudy letting you inspect, per Modern Standby interval, power consumption, activity, and the wake reason (power button, user input, a wake timer, and so on). 

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

Can an app learn about sleep in advance and refuse it?
On current Windows you can receive the notification, but you cannot refuse. Just before sleep, a WM_POWERBROADCAST message delivers a PBT_APMSUSPEND event, and here you can prepare by closing files and saving state, but the time allowed for processing is about 2 seconds per app, and if you exceed it the system proceeds without waiting. On an emergency suspend such as a critically low battery, the advance notification itself does not arrive. A design that "must finish before sleep" therefore does not hold; you need a design that can recover on resume no matter when the cut comes. For a stretch of work you really do not want to sleep through, suppress sleep explicitly with SetThreadExecutionState or a power request (PowerSetRequest).
How do I detect that the machine has resumed?
If the app has a window, handle WM_POWERBROADCAST. On resume from suspend, PBT_APMRESUMEAUTOMATIC arrives, and if the resume was caused by a user action (the power button or a key press), PBT_APMRESUMESUSPEND follows it. An unattended resume that goes back to sleep immediately delivers only PBT_APMRESUMEAUTOMATIC, so the basic split is to put required work such as reconnecting on the PBT_APMRESUMEAUTOMATIC side and user-facing work such as screen updates on the PBT_APMRESUMESUSPEND side. Windowless services and console apps can receive the same notifications via a callback by using RegisterSuspendResumeNotification with DEVICE_NOTIFY_CALLBACK.
Can I keep the app running during sleep?
As a rule, no. During sleep, CPU execution itself stops (on a Modern Standby machine, desktop apps are paused by the Desktop Activity Moderator), and the app's code does not run. There are two choices. One is to suppress sleep only while work is in progress. Specifying ES_SYSTEM_REQUIRED with SetThreadExecutionState, or issuing a power request with PowerCreateRequest/PowerSetRequest, will suppress automatic idle sleep for that interval (you can confirm it with powercfg /requests). That still cannot stop an explicit sleep action such as the user closing the lid, so you still need to be ready for resume even while suppression is on. The other is to accept sleep and design to "catch up after resume". For scheduled work such as a nightly batch, you can also wake the PC with Task Scheduler's "Wake the computer to run this task". Work that truly needs to run continuously belongs on a server or a service configured not to sleep.
Why do TCP connections and serial ports stop working after resume?
Because network adapters and USB devices also drop into a low-power state during sleep. The TCP connection has already been discarded by the other side or by a NAT or firewall timeout, and send/receive after resume returns an error (you often do not notice until it errors). USB-to-serial adapters and the like are sometimes treated as a device removal and reinsertion on resume, and the handle you had open becomes invalid. For both, the correct assumption is that "handles and connections do not survive across resume", and the right answer is to implement reconnect logic that rebuilds the connection on a resume notification or a communication error. Combining a periodic keepalive with retries that use exponential backoff on failure is the established pattern.
How do I investigate unexpected sleep or unexpected resume?
The powercfg command is the first tool. In the "it will not sleep" direction, powercfg /requests lists which processes and drivers have issued a power request that is blocking sleep. In the "it wakes on its own" direction, powercfg /lastwake shows the most recent wake reason and powercfg /waketimers shows timers that are currently reserved to wake the machine. On a Modern Standby machine, powercfg /sleepstudy produces a report of consumption and activity during sleep. Sleep and resume history is also recorded in the event log (the Kernel-Power source in the System log), so you can confirm on a timeline "when it slept, and when and why it woke".

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog