Saturday, November 11, 2006 4:45 PM bart

Windows Vista - Application Recovery with C#

Introduction

Applications do hang sometimes. Although it's always better to fix the bugs, a smooth "crashing experience" with automatic application restart and data recovery also helps to improve the end-user experience. And we should learn from our mistakes by means of error reporting. To help developers deal with unexpected crashes, Windows Vista offers the Application Recovery API which we'll discuss in this post.

Getting started

As usual on this blog, we'll stick with managed code. Create a simple Console Application in C# called "CrashRecoveryDemo". The application itself will be a simple lottery application that selects 6 different random numbers out of the range [1..42] with some delay (based on the Belgian Lottery). When it's done, we crash the application as a dumb crash simultation :-). The core code looks as follows:

static int[] numbers = new int[6]; static void Main(string[] args) { RunLotteryAndCrash(); } static void RunLotteryAndCrash() { Console.WriteLine("Welcome to the lottery.\n"); Thread.Sleep(2000); Console.WriteLine("The winning numbers are:"); Random rand = new Random(); bool[] b = new bool[42]; for (int j = 1; j <= 6; j++) { Thread.Sleep(5000); int n; do { n = rand.Next(1, 42); } while (b[n]); b[n] = true; Console.WriteLine("Number {0} = {1}", j, n); numbers[j - 1] = n; Thread.Sleep(5000); } throw new Exception("Bang!"); }

Interop with the Application Recovery API

The next big thing is to write the interop stuff with the Application Recovery API. The following four functions are required for our demo:

[DllImport("kernel32.dll", CharSet = CharSet.Auto)] static extern uint RegisterApplicationRestart(string pwzCommandLine, RestartFlags dwFlags); [DllImport("kernel32.dll")] static extern uint RegisterApplicationRecoveryCallback(APPLICATION_RECOVERY_CALLBACK pRecoveryCallback, object pvParameter, int dwPingInterval, int dwFlags); [DllImport("kernel32.dll")] static extern uint ApplicationRecoveryInProgress(out bool pbCancelled); [DllImport("kernel32.dll")] static extern uint ApplicationRecoveryFinished(bool bSuccess); [Flags] enum RestartFlags { NONE = 0, RESTART_CYCLICAL = 1, RESTART_NOTIFY_SOLUTION = 2, RESTART_NOTIFY_FAULT = 4, RESTART_NO_CRASH = 8, RESTART_NO_HANG = 16, RESTART_NO_PATCH = 32, RESTART_NO_REBOOT = 64 } delegate int APPLICATION_RECOVERY_CALLBACK(object pvParameter);

The first one, RegisterApplicationRestart will be used to tell Windows we'd like to have our app restarted when it crashes (or when the Restart Manager - see tomorrow's blog post - wants to do so). Next, there's the trio of recovery-related functions:

  • RegisterApplicationRecoveryCallback registers a callback function that will be called upon recovery of a crashed application. The allows the app to do some cleanup work to recover the data the app was working with.
  • ApplicationRecoveryInProgress is a keep-alive function to be called by the application when recovering data. If the OS receives these pings in time, it won't close the application.
  • ApplicationRecoveryFinished is called by the application when recovery has completed.

Application Restart

Time to change the app to enable application restart. To do so, we need to call RegisterApplicationRestart from our Main method (or elsewhere, when we want to turn it on). Notice you can turn off the Application Restart functionality again using UnregisterApplicationRestart. The function RegisterApplicationRestart takes two arguments:

  • pwzCommandLine contains a string that will be used as a command-line argument when the app is restarted and allows for crash detection.
  • dwFlags is used to configure the behavior of the application restart; e.g. to turn it off in certain cases or to add additional command line parameters if Windows has a solution for the problem.

We'll change Main as follows:

static string crashHint = "Restarted"; static void Main(string[] args) { if (args.Length == 1 && args[0] == crashHint) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("I crashed but Vista restarted me :-)\n"); Console.ResetColor(); RecoveryLottery(); } else { RunLotteryAndCrash(); } }

The crashHint is the text passed on the command-line to indicate an application restart. In here, we simply check for this string to be present. If you have multiple command-line arguments you'll need more complex handling, and you'll copy all current arguments to the RegisterApplicationRestart call to have the app start again with the same arguments (and an additional "crash hint" if you want to do so).

In RunLotteryAndCrash, add the following code:

static void RunLotteryAndCrash() { uint i = RegisterApplicationRestart(crashHint, RestartFlags.NONE); Console.WriteLine("Application restart registration {0}.", i == 0 ? "succeeded" : "failed");

This registers the application for application restart and reports the success/failure. That's it for what application restart is concerned: one single line of code. There's however one caveat: the app needs to run for at least 60 seconds in order to be eligible for automatic restart. This is no problem with our lottery (which takes about 62 seconds to complete), but keep this in mind for other types of apps. This rule avoids an app to monopolize the system by crashing and restarting every few seconds or so.

Recovery

In the previous code fragment you already saw the RecoveryLottery method call. This piece of code will do the actual recovery by reading recovered data from the registry. But first, we need to get a change in storing the data when the app crashes. This is done again in RunLotteryAndCrash by adding another system call, this time to RegisterApplicationRecoveryCallback:

static void RunLotteryAndCrash() { uint i = RegisterApplicationRestart(crashHint, RestartFlags.NONE); Console.WriteLine("Application restart registration {0}.", i == 0 ? "succeeded" : "failed"); i = RegisterApplicationRecoveryCallback(Recovery, "Just something", 50000, 0); Console.WriteLine("Application recovery callback registration {0}.\n", i == 0 ? "succeeded" : "failed");
The four parameters are:
  • A delegate to the Recovery method (see below).
  • An object to be passed to the Recovery method (we won't use it, but I set it to "Just something" to illustrate its use).
  • The recovery ping used by the OS to determine whether we're still alive doing the recovery (works in concert with the ApplicationRecoveryInProgress calls we should make, see further). It's set to 50000 x 100ns.
  • 0 for the reserved flags.

Next, we need the Recovery callback function:

static object c = ""; static int Recovery(object o) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("\nRecovering ... "); Timer t = new Timer(KeepAlive, null, 1000, 1000); RegistryKey key = Registry.CurrentUser.CreateSubKey("SOFTWARE\\RestartManagerDemo"); StringBuilder sb = new StringBuilder(); for (int j = 0; j < 6; j++) { lock (c) { Console.WriteLine("{0}/6", j + 1); } sb.Append(numbers[j] + ","); Thread.Sleep(2000); } key.SetValue("RecoveryData", sb.ToString().Trim(',')); ApplicationRecoveryFinished(true); return 0; }

Relatively easy once again. First, we start a timer to ping (keep alive) the OS every 1 second, to indicate we're still making progress in the recovery. Furthermore, the registry key for recovery is prepared and the lottery numbers are recovered one by one (with some delay to illustrate a long-running recovery process, in which keep-alive is crucial). Finally, the recovered data is saved to the registry (you could use another destination too, e.g. a file) and the OS is told the recovery succeeded by means of the ApplicationRecoveryFinished method call. Don't worry about the lock statements used to synchronize console output, because we have a background keep-alive thread that prints a "spinner" to the console:

static int r = 0; static char[] progressChars = new char[] { '|', '/', '-', '\\' }; static void KeepAlive(object o) { lock (c) { int top = Console.CursorTop; int left = Console.CursorLeft; Console.SetCursorPosition(60, 0); Console.Write(progressChars[r++ % 4]); Console.SetCursorPosition(left, top); } bool cancelled; ApplicationRecoveryInProgress(out cancelled); if (cancelled) { Console.WriteLine("Recovery cancelled"); Environment.FailFast("Recovery cancelled"); } }

This KeepAlive method is invoked by the timer every one second and calls ApplicationRecoveryInProgress to tell the OS we're still busy and we do not hang. The OS hands back a cancelled boolean value that will be true if the user click "Cancel" in the recovery progress dialog. Then we're out of luck and fail fast.

Finally, the recovery method that's called when the app is restarted and detects the restart (by means of the crash hint, see above) is listed below:

static void RecoveryLottery() { RegistryKey key = Registry.CurrentUser.CreateSubKey("SOFTWARE\\RestartManagerDemo"); string s = (string)key.GetValue("RecoveryData"); string[] numbers = s.Split(','); Console.WriteLine("The winning numbers were:"); for (int j = 0; j < 6; j++) Console.WriteLine("Number {0} = {1}", j + 1, numbers[j]); Console.ReadLine(); }

It simply reads from the registry and displays the recovered data.

Demo

Wanna see it in action? Here are a few screenshots that are self-explanatory I guess:

 

 

Enjoy!

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# The November 06 Month Report

Friday, December 01, 2006 4:06 AM by B# .NET Blog

Yet another great (well, at least in my opinion) month of Daily Blogging . Once more, feedback from readers

# Windows Vista Application Recovery on MSDN (woohoo)

Thursday, December 28, 2006 6:03 AM by B# .NET Blog

I couldn't believe my own eyes when reviewing the blog stats of yesterday: a 90% overall increase in

# Crash, Recovery, Restart -> Usar la recovery API de Vista

Tuesday, January 09, 2007 11:30 AM by Blog de todos

Revisen este articulo de como usar la Recovery API de Vista, como bien dice ahi, las aplicaciones se

# Vista Application Recovery

Sunday, October 11, 2009 2:45 AM by Onteora Software

Vista Application Recovery