Skip to content
WindowsDeep Dive Published Updated 5 min readViews unavailable

Windows Application Recovery: Bounded Callbacks, Checkpoints, and Restart Consent

Use WER recovery callbacks for bounded checkpoint work, while separating application crashes, hangs, installer shutdowns, and restart consent.

Windows Application Recovery and Restart (ARR) gives a desktop application a narrowly timed opportunity to preserve state after Windows Error Reporting (WER) detects an unhandled exception or an unresponsive application. It is not a general-purpose background autosave service, a guarantee that damaged process memory is safe to use, or a substitute for handling an installer-driven shutdown. Reliable designs create recoverable checkpoints during ordinary operation, then make the ARR callback small, bounded, and independent of locks that a hung thread may hold.

This article covers the Win32 APIs RegisterApplicationRecoveryCallback, ApplicationRecoveryInProgress, ApplicationRecoveryFinished, and RegisterApplicationRestart. Microsoft lists these APIs for desktop applications beginning with Windows Vista; verify the target SDK and deployment requirements for the application you maintain. The recovery callbacks and restart registration solve related but distinct parts of the lifecycle.

Separate recovery from restart and update shutdown

Recovery is the application’s chance to save data or state. Restart registration tells Windows how the application may be relaunched. Register both before the failure occurs if the application needs both behaviors. If the process crashes or becomes unresponsive, WER invokes the registered recovery callback in the supported recovery path. Registering for restart does not automatically restart the application after a crash or hang: Microsoft documents a user-consent prompt for that case. An update-required restart follows a different path and may be automatic when the installer requests application restart.

An installer does not use the WER recovery callback to save state before replacing a component. Microsoft directs desktop applications to handle WM_QUERYENDSESSION and WM_ENDSESSION for that update case; console applications use their documented control-event path. Keep these shutdown messages separate from crash recovery. A program that waits for the ARR callback to handle every exit can lose state during precisely the maintenance event it intended to support.

Checkpoint before the recovery window

Treat the callback as a last bounded flush, not the only copy of unsaved user work. During normal operation, periodically write a versioned checkpoint or journal to a durable location, validate it, and keep an older known-good generation until the replacement is complete. The recovery path can then finish or mark one bounded unit of work rather than reconstructing an entire session from mutable in-memory objects.

Do not make the callback wait for a worker thread, UI message pump, database connection, or lock that could be held by the unresponsive part of the application. If an unhandled exception corrupted shared state, copying arbitrary pointers is not recovery. Prefer an independently validated snapshot or append-only record prepared during normal operation. Recovery should be idempotent: if it stops halfway, the next normal launch can identify and reject an incomplete generation.

Keep each recovery step inside the ping interval

RegisterApplicationRecoveryCallback accepts a ping interval in milliseconds. Zero selects the documented default of five seconds; the maximum is five minutes. WER terminates recovery if the callback does not call ApplicationRecoveryInProgress within that interval. That API also reports whether the user canceled recovery. The callback should therefore perform bounded work between pings, check cancellation, and call ApplicationRecoveryFinished when it has a final result.

The following C++ fragment is illustrative pseudocode: both SaveOneCheckpointChunk and CheckpointIsComplete are application-specific and must return promptly. Neither may perform an unbounded network request or wait on the ordinary UI thread; if either can take significant time, split it into bounded work and ping again before the interval expires. Microsoft documents ApplicationRecoveryFinished as the callback’s final call; the process terminates when it is called.

#include <windows.h>

bool SaveOneCheckpointChunk(); // Must be bounded and independently safe.
bool CheckpointIsComplete();   // Must also return within the ping interval.

DWORD WINAPI RecoveryCallback(PVOID) {
    BOOL cancelled = FALSE;

    for (;;) {
        HRESULT ping = ApplicationRecoveryInProgress(&cancelled);
        if (FAILED(ping) || cancelled) {
            ApplicationRecoveryFinished(FALSE); // Final call; process terminates.
            return 0;                            // Not reached on documented path.
        }

        if (!SaveOneCheckpointChunk()) {
            ApplicationRecoveryFinished(FALSE); // Final call; process terminates.
            return 0;
        }

        if (CheckpointIsComplete()) {
            ApplicationRecoveryFinished(TRUE);  // Final call; process terminates.
            return 0;
        }
    }
}

HRESULT RegisterRecovery() {
    return RegisterApplicationRecoveryCallback(
        RecoveryCallback,
        nullptr,
        5000,  // Ping at least within the chosen 5-second interval.
        0);    // dwFlags is reserved and must be zero.
}

The loop pings before each bounded chunk so time-consuming work does not silently consume the full interval. Production code should check every HRESULT, make its checkpoint format crash-consistent, and terminate or report failure if the callback cannot establish a valid recovery result. Do not continue as if state were saved after ApplicationRecoveryFinished(FALSE); that call ends the process. A real implementation should also avoid printing sensitive document contents or credentials to diagnostic logs.

Register a safe relaunch contract

RegisterApplicationRestart registers the active instance for restart and accepts command-line arguments, not the executable name. Keep the arguments short, non-secret, and safe to replay. An opaque checkpoint identifier is preferable to embedding document contents, access tokens, or a destructive operation in the command line. Validate the checkpoint on the next launch before restoring it.

Windows can offer the user a restart after a crash or hang; do not promise that the app will relaunch automatically in those cases. Microsoft also documents a 60-second minimum runtime condition intended to prevent restart cycles. For update restarts, the installer must request the applicable restart behavior. Make restart registration current during normal operation, and remove registrations on clean shutdown when the application no longer wants them.

Test failures as separate lifecycle cases

Test recovery and restart independently: an unhandled exception, an unresponsive UI thread, user cancellation, callback work that exceeds its ping interval, failure to write a checkpoint, a clean exit, and an installer-driven shutdown. Verify that the next process rejects a partial checkpoint and does not repeat a non-idempotent operation. Confirm the exact consent and relaunch behavior on the Windows versions your product supports; API registration is not evidence that a test crash will be restarted automatically.

ARR can reduce lost work only when the application has designed for bounded recovery. Persist useful state before trouble, reserve the callback for a small finalization step, respect the ping and cancellation contract, and communicate which recovery paths require user consent. That is a safer promise than treating a restart API as a backup system.

Related:

Sources:

Comments