-
Notifications
You must be signed in to change notification settings - Fork 334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AppLifecycle Restart API #1134
AppLifecycle Restart API #1134
Changes from all commits
0edfb98
f255310
42b9338
c4fbdb0
b1c5fe2
57ca83e
dc9d7f8
724201c
ebc5d9c
1efb599
f49996b
e6baf12
c7c3199
49d6f81
a0b8f0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,316 @@ | ||
# Restart API | ||
|
||
# Contents | ||
|
||
- [Restart API](#restart-api) | ||
- [Contents](#contents) | ||
- [Background](#background) | ||
- [Purpose](#purpose) | ||
- [API](#api) | ||
- [Existing API](#existing-api) | ||
- [Summary](#summary) | ||
- [Existing API Details](#existing-api-details) | ||
- [Existing API](#existing-api-1) | ||
- [New API Details](#new-api-details) | ||
- [New API](#new-api) | ||
- [Description](#description) | ||
- [Mechanism](#mechanism) | ||
- [Examples](#examples) | ||
|
||
# Background | ||
|
||
This spec addresses the **Restart APIs** in the AppInstance component. At a high-level, Win32 apps | ||
can register with the OS to restart in update/hang/reboot scenarios but cannot initiate an explicit | ||
restart with specific arguments/state. This applies to packaged (Desktop Bridge) and unpackaged | ||
apps. This API will address these gaps and provide the ability for any Win32 app to restart | ||
immediately. | ||
|
||
## Purpose | ||
|
||
The goal of this feature is the new function: **Restart**, which will enable any packaged or | ||
unpackaged Win32 app (including Console, WinMain, Windows Forms, WPFI) to terminate and restart | ||
itself on command, and to provide an arbitrary command-line string for the restarted instance. | ||
|
||
This behavior (restarting an app immediately) is not currently available to Win32 applications. This | ||
new method will fill the gap for Win32 applications and align with CoreApplication's existing | ||
``CoreApplication.RequestRestartAsync``. | ||
|
||
# API | ||
|
||
## Existing API | ||
|
||
1. **Restart-me-now**. CoreApplication exposes the | ||
[RequestRestartAsync](https://docs.microsoft.com/en-us/uwp/api/windows.applicationmodel.core.coreapplication.requestrestartasync?view=winrt-18362) | ||
method, which allows an app to terminate and restart itself, and to provide an arbitrary | ||
command-line string for the restarted instance. | ||
|
||
2. **Restart-me-after-termination**. The Win32 API | ||
[RegisterApplicationRestart](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-registerapplicationrestart) | ||
enables an app to register itself to be restarted after termination, and to provide an arbitrary | ||
command-line string for the restarted instance. The reasons for termination include app crash or | ||
hang, app update, or system update. There's also a matching | ||
[UnregisterApplicationRestart](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-unregisterapplicationrestart) | ||
API. | ||
|
||
There are 2 related Win32 APIs that focus on recovery prior to restart: | ||
|
||
1. **Let-me-do-something-before-termination**. An app can call | ||
[RegisterApplicationRecoveryCallback](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-registerapplicationrecoverycallback) | ||
to register a callback for the system to call before terminating the app. If an application | ||
encounters an unhandled exception or becomes unresponsive, Windows Error Reporting (WER) calls | ||
the specified recovery callback, where the app can save state information. The system pings the | ||
app every n seconds to make sure that it hasn't hung in its callback. The app can specify the | ||
ping interval when it registers the callback. In its recovery callback, the app can call | ||
RegisterApplicationRestart a second time, to update the command-line. | ||
|
||
2. **Recovery-in-progress**. While the app is doing work in its recovery callback, the app must | ||
periodically call | ||
[ApplicationRecoveryInProgress](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-applicationrecoveryinprogress) | ||
\-- if it doesn't call within the registered ping interval, WER will terminate the process. | ||
|
||
### Summary | ||
|
||
For Win32 apps (packaged or unpackaged): | ||
|
||
- Applicable restart mechanism: **RegisterApplicationRestart** for OS + app update/restart/crash | ||
scenarios | ||
- Gap: Apps can register with the OS to restart in specific app/OS states, but cannot initiate a | ||
restart from a healthy state | ||
|
||
|
||
## Existing API Details | ||
|
||
### Existing API | ||
|
||
```cpp | ||
HRESULT RegisterApplicationRestart(PCWSTR pwzCommandline, DWORD dwFlags) | ||
``` | ||
Registers an app for restart. More details on the [MSDN | ||
page](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-registerapplicationrestart). | ||
|
||
```cpp | ||
HRESULT UnregisterApplicationRestart() | ||
``` | ||
Unregisters the app for restart, where the app had previously called RegisterApplicationRestart. | ||
More details on the [MSDN | ||
page](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-unregisterapplicationrestart). | ||
|
||
```cpp | ||
public static IAsyncOperation<AppRestartFailureReason> RequestRestartAsync( | ||
string launchArguments) | ||
``` | ||
Static function in CoreApplication. Requests immediate termination and restart. More details on the | ||
[MSDN | ||
page](https://docs.microsoft.com/en-us/uwp/api/windows.applicationmodel.core.coreapplication.requestrestartasync?view=winrt-22000). | ||
|
||
```cpp | ||
public enum AppRestartFailureReason | ||
``` | ||
An existing enum with the following values: | ||
|
||
- 0: RestartPending - a restart is already in progress. | ||
- 1: NotInForeground - an app must be visible and in the foreground when it calls the restart API. | ||
- 2: InvalidUser - could not restart for the specified user. | ||
- 3: Other - unspecified failure. | ||
|
||
```cpp | ||
class LaunchActivatedEventArg | ||
``` | ||
|
||
Relevant Properties: | ||
|
||
- Arguments: gets the arguments that are passed to the app during its launch activation. | ||
- Kind: Gets the reason that this app is being activated. This is of type enum ActivationKind | ||
- PreviousExecutionState: Gets the execution state of the app before this activation. | ||
|
||
## New API Details | ||
|
||
### New API | ||
|
||
```c# | ||
static AppRestartFailureReason Restart(String arguments) | ||
``` | ||
|
||
```c# | ||
namespace Microsoft.Windows.AppLifecycle | ||
{ | ||
enum ExtendedActivationKind | ||
{ | ||
Launch = 0, | ||
Search, | ||
ShareTarget, | ||
File, | ||
Protocol, | ||
FileOpenPicker, | ||
FileSavePicker, | ||
CachedFileUpdater, | ||
ContactPicker, | ||
Device, | ||
PrintTaskSettings, | ||
CameraSettings, | ||
RestrictedLaunch, | ||
AppointmentsProvider, | ||
Contact, | ||
LockScreenCall, | ||
VoiceCommand, | ||
LockScreen, | ||
PickerReturned = 1000, | ||
WalletAction, | ||
PickFileContinuation, | ||
PickSaveFileContinuation, | ||
PickFolderContinuation, | ||
WebAuthenticationBrokerContinuation, | ||
WebAccountProvider, | ||
ComponentUI, | ||
ProtocolForResults = 1009, | ||
ToastNotification, | ||
Print3DWorkflow, | ||
DialReceiver, | ||
DevicePairing, | ||
UserDataAccountsProvider, | ||
FilePickerExperience, | ||
LockScreenComponent, | ||
ContactPanel, | ||
PrintWorkflowForegroundTask, | ||
GameUIProvider, | ||
StartupTask, | ||
CommandLineLaunch, | ||
BarcodeScannerProvider, | ||
PrintSupportJobUI, | ||
PrintSupportSettingsUI, | ||
PhoneCallActivation, | ||
VpnForeground, | ||
// NOTE: Values below 5000 are designated for the platform. The above list is kept in sync with | ||
// Windows.ApplicationModel.Activation.ActivationKind. | ||
|
||
Push = 5000, | ||
}; | ||
|
||
runtimeclass AppActivationArguments | ||
{ | ||
ExtendedActivationKind Kind { get; }; | ||
IInspectable Data{ get; }; | ||
}; | ||
|
||
runtimeclass AppInstance | ||
{ | ||
static AppInstance GetCurrent(); | ||
static Windows.Foundation.Collections.IVector<AppInstance> GetInstances(); | ||
static AppInstance FindOrRegisterForKey(String key); | ||
|
||
// This is the new method exposed by this feature | ||
static Windows.ApplicationModel.Core.AppRestartFailureReason Restart(String arguments); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider putting a "this is the new one!" marker here. Also since this is a new API in 1.1, you'll need to add the proper contract information here, like: [contractversion(1.1)] apicontract AppLifecycleContract{}
[contract(AppLifecycleContract, 1)]
runtimeclass AppInstance
{
// On the new thing
[contract(AppLifecycleContract, 1.1)]
{
static Windows.ApplicationModel.Core.AppRestartFailureReason Restart(String arguments);
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a marker for this. On the note on contract: Is there a template for how/where the contract information belongs? Do I just drop it into the IDL as you've commented above? |
||
|
||
void UnregisterKey(); | ||
Windows.Foundation.IAsyncAction RedirectActivationToAsync(Microsoft.Windows.AppLifecycle.AppActivationArguments args); | ||
Microsoft.Windows.AppLifecycle.AppActivationArguments GetActivatedEventArgs(); | ||
event Windows.Foundation.EventHandler<Microsoft.Windows.AppLifecycle.AppActivationArguments> Activated; | ||
|
||
String Key{ get; }; | ||
Boolean IsCurrent{ get; }; | ||
UInt32 ProcessId{ get; }; | ||
} | ||
|
||
static runtimeclass ActivationRegistrationManager | ||
{ | ||
static void RegisterForFileTypeActivation(String[] supportedFileTypes, String logo, | ||
String displayName, String[] supportedVerbs, String exePath); | ||
static void RegisterForProtocolActivation(String scheme, String logo, String displayName, | ||
String exePath); | ||
static void RegisterForStartupActivation(String taskId, String exePath); | ||
|
||
static void UnregisterForFileTypeActivation(String[] fileTypes, String exePath); | ||
static void UnregisterForProtocolActivation(String scheme, String exePath); | ||
static void UnregisterForStartupActivation(String taskId); | ||
}; | ||
} | ||
``` | ||
There is no relevant RtCop output for this feature. | ||
|
||
### Description | ||
|
||
Equivalent to CoreApplication.RequestRestartAsync, except that it is *not* async. | ||
|
||
- If the restart fails, but the user subsequently launches the app manually, the app will launch | ||
normally and no restart arguments will be passed. | ||
- If the app has any in-process background tasks running when it calls this API, those tasks will be | ||
cancelled in the normal way. Out-of-process background tasks will not be affected. | ||
|
||
The restarted instance will be activated with | ||
Windows.ApplicationModel.Activation.LaunchActivatedEventArgs. | ||
|
||
- When the app is restarted, LaunchActivatedEventArgs.PreviousExecutionState will have the value | ||
Terminated so that the app can distinguish between a resume and a restart. | ||
- The elevation state of the app is also maintained in the new restarted process. | ||
|
||
If the request to restart is successful, the application will be restarted immediately with no | ||
return value. The user does not have to do anything else/handle a return value in any way. | ||
|
||
If the request fails due to a nonfatal reason, a failure reason is returned for why the restart | ||
didn’t successfully complete. This method leverages an existing enum (AppRestartFailureReason) which | ||
has the following relevant return fields. | ||
|
||
- InvalidUser: Could not restart for the specified user. | ||
- RestartPending: A restart is already in progress. | ||
- Other: Unspecified failure. | ||
|
||
### Mechanism | ||
|
||
When an application calls Restart, the mechanism for restart will be as follows: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Markdown nit: Put a blank line before & after number/bullet lists. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done for all lists! |
||
|
||
1. Application calls Restart API. | ||
2. The API calls CreateProcess to launch the agent (a helper EXE in the framework package containing | ||
the API) | ||
3. The Agent will get the executable path of the application. The Agent will then then terminate the | ||
application and then call CreateProcess with the application (restarted). | ||
|
||
### Examples | ||
|
||
```c# | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is real C#, then please use "." instead of "::". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RECOMMEND: Fix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
public class App | ||
{ | ||
// Let's assume this method is run when an app updates assets. | ||
// The new assets have downloaded and installed, and now the app has to request a restart immediately | ||
protected void UpdateInstallComplete() | ||
{ | ||
// Checking if the assets have updated successfully and there are no pending updates | ||
if (!CheckForUpdate()) | ||
{ | ||
AppRestartFailureReason reason = AppInstance.Restart("/RestartCalled"); | ||
switch (reason) | ||
{ | ||
case AppRestartFailureReason.RestartPending: | ||
// In this case the restart could not be completed because another restart is | ||
// pending and we can try to restart again after a period of time. | ||
RetryRestartAfterXSeconds(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why retry? It'll just say "Yup, still pending." |
||
break; | ||
case AppRestartFailureReason.Other: | ||
// In this case, a dialog box will appear informing of an error requesting restart | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can the sample show how the dialog box would get the correct message text to show the end user? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SAMPLE: It will likely say "something went wrong". There's not much to say other than "you will need to manually fix it, user." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||
ShowErrorMessage("Something went wrong. Restart was not successful"); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// In this scenario, assume the app encounters an error during initialization. | ||
// The app displays an error dialog, and after the user clicks OK on the dialog the app must restart. | ||
// This scenario simply displays logging for these use cases | ||
protected void HandleInitializationError() | ||
{ | ||
// Restart in safe mode to avoid whatever made initialization fail | ||
AppRestartFailureReason reason = AppInstance.Restart("/safemode"); | ||
switch (reason) | ||
{ | ||
case AppRestartFailureReason.RestartPending: | ||
Telemetry.WriteLine("Another restart is currently pending."); | ||
break; | ||
case AppRestartFailureReason.InvalidUser: | ||
Telemetry.WriteLine("Current user is not signed in or not a valid user."); | ||
break; | ||
case AppRestartFailureReason.Other: | ||
Telemetry.WriteLine("Failure restarting."); | ||
break; | ||
} | ||
} | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there supposed to be MIDL 3 or something in this file to review? There's details about enum values and stuff, but no clear MIDL section with RtCop output, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And what does the rest of the
AppLifecycle
API look like?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll get the RtCop output in a MIDL section in this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RECOMMEND: Jon to more carefully review before sending review. Hamza to add MIDL3 for the feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Only thing not yet in is the RtCop output.