Developing with Application Recovery and Restart

This article illustrates the reliability concepts that are discussed in the companion topic, Understanding Application Recovery and Restart. It describes the Windows APIs and programming techniques used to implement Application Recovery and Application Restart (ARR) in managed applications. The article concludes with code examples that demonstrate combining Application Recovery and Application Restart to implement highly available and reliable applications. The source code for a sample application that uses ARR can be found in the Application Recovery and Restart Samples topic.

Application Recovery

The Application Recovery feature requires an application to register itself with Windows Error Reporting (WER). As part of an application's registration, the application provides the address of the function to call when WER is ready to force the application to shut down. This recovery function is responsible for performing any recovery tasks, detecting when the end-user has canceled the recovery phase and acting accordingly, periodically pinging WER to indicate that the application is still alive and working, and indicating to WER when it has finished its recovery tasks and the application is ready to be terminated.

Currently, there is no managed API for Application Recovery. However, the unmanaged APIs can be called using P/Invoke declarations as illustrated in this topic. For the declarations shown in this section, examples that demonstrate calling most of these functions can be found in the Application Recovery and Restart section.

To register for Application Recovery, call the RegisterApplicationRecoveryCallback function. The following code example shows the declaration required to call this function from a managed application.

[DllImport("kernel32.dll")]
public static extern int RegisterApplicationRecoveryCallback(
    RecoveryDelegate recoveryCallback,
    string parameterData,
    uint pingInterval,
    uint flags);

The recoveryCallback parameter is a delegate that WER can use to invoke the recovery method. The following code example demonstrates declaring a delegate for a recovery method that takes a single string parameter and returns an integer.

public delegate int RecoveryDelegate(string parameterData);

The RegisterApplicationRecoveryCallback function'sparameterData parameter is used to pass an application-specific data structure to the recovery method when it is invoked. In this example, the data passed to the recovery method is a string. For an example that demonstrates passing an application defined object during registration, see the Application Recovery and Restart section.

The pingInterval parameter determines how often the recovery function will ping WER to indicate that the recovery is not yet complete. To ping WER, an application calls the ApplicationRecoveryInProgress function. The following code example shows the declaration required to call this function from a managed application.

[DllImport("kernel32.dll")]
public static extern int ApplicationRecoveryInProgress(
    out bool canceled);

The application can check the value returned by the canceled parameter to determine whether the application user wants to cancel the application's recovery.

When the recovery function is finished, as its last call it indicates to WER that the application is ready for termination by calling the ApplicationRecoveryFinished function. The following code example shows the declaration required to call this function from a managed application.

[DllImport("kernel32.dll")]
public static extern void ApplicationRecoveryFinished(
    bool success);

To unregister a previously registered application, call the UnregisterApplicationRecoveryCallback function. The following code example shows the declaration required to call this function from a managed application.

[DllImport("kernel32.dll")]
public static extern int UnregisterApplicationRecoveryCallback();

To view the current recovery settings for an application, call the GetApplicationRecoveryCallback function. The following code example shows the declaration required to call this function from a managed application.

[DllImport("kernel32.dll")]
public static extern int GetApplicationRecoveryCallback(
    IntPtr processHandle,
    out RecoveryDelegate recoveryCallback,
    out string parameter,
    out uint pingInterval,
    out uint flags);

The function takes a handle for the current process and returns the current settings for recovery, including the recovery function delegate, the application-specific data to pass to the recovery function, and the time interval for pinging WER. The flags parameter is not used.

The following code example demonstrates getting the handle for the current process in a managed application.

IntPtr myHandle = System.Diagnostics.Process.GetCurrentProcess().Handle

Application Restart

The Application Restart feature is very simple to use requiring only a single function call. Currently, there is no managed API for Application Restart. However, the unmanaged APIs can be called using P/Invoke as illustrated in this section. To have WER automatically restart an application after a crash, call the RegisterApplicationRestart function. A code sample that demonstrates calling this function is shown in the Application Recovery and Restart section. The following code example shows the declaration required to call this function from a managed application.

[DllImport("kernel32.dll")]
public static extern int RegisterApplicationRestart(
    [MarshalAs(UnmanagedType.BStr)] string commandLineArgs,
    int flags);

The commandLineArgs parameter specifies the command line that will be passed to the application when it is restarted. Each time the application starts, the command-line arguments can be used to determine whether the application has started normally or is being restarted. This determination will allow the application to perform any special processing related to restarting, such as restoring the application's prior state.

The flags parameter specifies the conditions under which the application should not be restarted by WER after being shut down. The following class defines a set of flag values that can be used by a managed application.

[Flags]
public enum RestartRestrictions
{
    None = 0,
    NotOnCrash = 1,
    NotOnHang = 2,
    NotOnPatch = 4,
    NotOnReboot = 8
}

To unregister a previously registered application, call the UnregisterApplicationRestart function. The following code example shows the declaration required to call this function from a managed application.

[DllImport("kernel32.dll")]
public static extern int UnregisterApplicationRestart();

To view the current restart settings for an application, call the GetApplicationRestartSettings function. The following code example shows the declaration required to call this function from a managed application.

[DllImport("KERNEL32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int GetApplicationRestartSettings(
    IntPtr process,
    IntPtr commandLine,
    ref uint size,
    out uint flags);

This function takes a handle for the current process and returns the current settings for the command line to use for restarts, the size of the command line, and the restart restrictions specified when the application registered. The following code example demonstrates using this function. Note that the GetApplicationRestartSettings function is called twice. The first call specifies a null buffer for the command line, indicating that the function should return the number of characters in the command line. Using that value, the function allocates an appropriately sized buffer and passes a pointer to it into the second call to GetApplicationRestartSettings. The string is extracted from the pointer using the Marshal.PtrToStringUni method. The buffer is freed after the settings are written to the console.

private static void DisplayRestartSettings()
{
    IntPtr cmdptr = IntPtr.Zero;
    uint size=0, flags;

    // Find out how big a buffer to allocate
    // for the command line.
    ArrImports.GetApplicationRestartSettings(
    Process.GetCurrentProcess().Handle,
    IntPtr.Zero,
    ref size,
    out flags);

    // Allocate the buffer on the unmanaged heap.
    cmdptr = Marshal.AllocHGlobal((int) size * sizeof(char));

    // Get the settings using the buffer.
    int ret = ArrImports.GetApplicationRestartSettings(
    Process.GetCurrentProcess().Handle,
    cmdptr,
    ref size,
    out flags);

    // Read the buffer's contents as a unicode string.
    string cmd = Marshal.PtrToStringUni(cmdptr);

    Console.WriteLine("cmdline: {0} size: {1} flags: {2}",
        cmd, size, flags);
    Console.WriteLine();

    // Free the buffer.
    Marshal.FreeHGlobal(cmdptr);
}

Application Recovery and Restart

Application Recovery and Application Restart are independent features that can be combined to provide a significantly improved user experience after an application crash. When a fatal error occurs, an application can first use Application Recovery to persist data and application state information before shutting down. Then, using Application Restart, the application can be restarted automatically and restore itself to its previous state. This section demonstrates using Application Recovery and Application Restart together to first preserve data before crashing and then restoring the previous application state when the application is restarted.

The code sample used in this section is a managed console application that implements a simple data entry system for creating playlists. The application user can type "!!" in response to most prompts to force the application to crash for demonstration purposes The complete implementation for the sample can be found in the Application Recovery and Restart Samples topic.

The playlist data entry system uses two files, one for storing the saved collection of playlists (playlists.txt) and one for persisting data for a playlist that has not yet been saved. This file's naming convention is "current_user_name".pl3work, where current_user_name is the name of the user who launched the application. The files reside in the same directory as the application executable.

On startup, the application behaves as follows:

  1. Register for Application Recovery.

  2. Register for Application Restart.

  3. If restarted, check to see if there is an incomplete playlist from the prior session.

  4. If there is a previous playlist, allow the user to view it and resume data entry or discard it.

In the code examples shown below, ArrImports is a static class that contains the P/Invoke declarations for the ARR APIs. The RecoveryData class is used to pass information into the recovery function. RecoveryData stores the user name for the current user.

The following code registers the application for Application Recovery specifying that the RecoveryProcedure method will execute if the application crashes.

private static void RegisterForRecovery()
{
    // Create the delegate that will invoke the recovery method.
    RecoveryDelegate recoveryCallback = 
         new RecoveryDelegate(RecoveryProcedure);
    uint pingInterval = 5000, flags = 0;
    RecoveryData parameter = new RecoveryData(Environment.UserName);

    // Register for recovery notification.
    int regReturn = ArrImports.RegisterApplicationRecoveryCallback(
        recoveryCallback,
        parameter,
        pingInterval,
        flags);
}

The following code example registers the application for Application Restart. When the application is restarted, the command-line argument will be /restart.

private static void RegisterForRestart()
{
    // Register for automatic restart if the application 
    // was terminated for any reason.
    ArrImports.RegisterApplicationRestart("/restart", 
        (int)RestartRestrictions.None);
}

When the application crashes, the RecoveryProcedure method is executed. It creates and enables a timer to periodically call ApplicationRecoveryInProgress. It then closes the file that contains the current unsaved playlist, if there is one. For demonstration purposes only, this method puts the current thread to sleep to simulate a long-running recovery where the periodic call to ApplicationRecoveryInProgress is mandatory to allow recovery to complete. When the thread reawakens, the method indicates to WER that it is finished by calling ApplicationRecoveryFinished. The following code shows the implementation of the RecoveryProcedure method.

static int RecoveryProcedure(RecoveryData parameter)
{
    Console.WriteLine("Recovery in progress for {0}",
        parameter.CurrentUser);

    // Set up timer to notify WER that recovery work is in progress.
    Timer pinger = new Timer(4000);
    pinger.Elapsed += new ElapsedEventHandler(PingSystem);
    pinger.Enabled = true;

    // Do recovery work here.
    if (log != null)
        log.Close();

    // Simulate long running recovery.
    System.Threading.Thread.Sleep(9000);

    // Indicate that recovery work is done.
    Console.WriteLine("Application shutting down...");
    ArrImports.ApplicationRecoveryFinished(true);
    return 0;
}

The following code shows the implementation of the PingSystem method that is periodically called by the timer during recovery. This method also checks to see if the user wants to cancel the recovery. If so, the application is immediately terminated.

private static void PingSystem(object source, ElapsedEventArgs e)
{
    bool isCanceled;
    ArrImports.ApplicationRecoveryInProgress(out isCanceled);
    if (isCanceled)
    {
        Console.WriteLine("Recovery has been canceled by user.");
        Environment.Exit(2);
    }
}

When the application restarts after a crash (as indicated by the values passed to the command line at startup), the RecoverLastSession method is called to determine if there is an unsaved playlist from the previous session. If there is an unsaved playlist, it is displayed, and the user can choose to resume work or discard it. This method returns true when there is a playlist from the previous session and false if there is no playlist in progress or it is not in a useable state. The following code example shows the implementation of the RecoverLastSession method.

private static bool RecoverLastSession(string command)
{
    // Gets file name based on current user.
    string worklog = GetWorkLogName();

    // Is there an open work log?
    if (!File.Exists(worklog))
    {
        Console.WriteLine("No file found for session recovery.");
        Console.WriteLine();
        return false;
    }

    // There is a work log. 
    // If it is empty then there is no session to resume.
    string session = File.ReadAllText(worklog);
    if (session == String.Empty)
    {
        return false;
    }

    // Display the work log for the previous session and 
    // see if user wants to resume.
    Console.Write("There is a play list in progress: ");
    Console.WriteLine();
    Console.WriteLine(session);
    Console.WriteLine();
    string answer = null;
    do
    {
        Console.WriteLine(
            "Please enter 'r' to resume or 'd' to discard.");
        answer = Console.ReadLine().ToLower();
    } while (answer != "r" && answer != "d");

    if (answer == "d")
    {
        // Delete previous session's work log and exit.
        File.Delete(GetWorkLogName());
        return false;
    }

    // Signal to the Main method that there is a session to resume.
    return true;
}

See Also

Concepts

Understanding Application Recovery and Restart

Application Recovery and Restart Samples

Application Recovery and Restart