From 83daf22c670af04cb552b67b5d367711fb78d54c Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Sun, 24 Mar 2019 16:39:35 -0700 Subject: [PATCH 01/11] Support notification on pwsh startup for new updates --- .../RFCNNNN-Notification-On-Version-Update.md | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 1-Draft/RFCNNNN-Notification-On-Version-Update.md diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md new file mode 100644 index 00000000..926f881d --- /dev/null +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -0,0 +1,244 @@ +--- +RFC: RFCXXXX +Author: Dongbo Wang +Status: Draft +SupercededBy: N/A +Version: 1.0 +Area: Console +Comments Due: 4/30/2019 +Plan to implement: Yes +--- + +# Notification on PowerShell version updates + +Today, to find out whether a new version of PowerShell is available, +one has to check the release page of the `PowerShell\PowerShell` repository, +or depend on communication channels like `twitter` or `GitHub Notifications`. +It would be convenient if `pwsh` itself can notify the user of a new update on startup. + +## Motivation + + As a PowerShell user, I get notified when a new version of `pwsh` becomes available. + +## Specification + +### Target Goals + +1. No notification or update check when the running `pwsh` is a self-built version. +No notification or update check for non-interactive sessions. +Also, no notification when the PowerShell banner message is suppressed. + +2. When there is a new update, assuming you use `pwsh` every day +and at least one interactive `pwsh` session lasts long enough for the update check, +then you should be able to see an update notification during the `pwsh` startup on the same day of a release or the next day at the latest. + +3. This feature must have very minimal impact on the startup time of `pwsh`. +This means the check for update must not happen during `pwsh` startup. +The only acceptable extra overhead to the `pwsh` startup should just be the work related to printing the notification. + +4. Check for updates should not blindly run for every interactive `pwsh` session. +For a particular version of `pwsh`, only one check at most can run to complete per a day +no matter how many interactive session of the `pwsh` are started/opened in that day. + +5. After a new update is detected during a successful check, +all subsequent interactive sessions of that version of `pwsh` should show the notification at startup time. +And subsequent checks can be avoided for a reasonable period of time, such as a week. + +6. `pwsh` of preview versions should check for the new preview version as well as the new GA version. +`pwsh` of GA versions should check for the new GA version only. + +7. The notification and update check are not needed in some scenarios, +such as when `pwsh` is in a container image. +Hence, you should be able to suppress them altogether by setting an environment variable. + +### No Goals + +1. Notification shows up right after a new version of `pwsh` is released. + + _This is not a goal._ + Assuming you use `pwsh` interactively every day, + then a notification about the new release may show up on the same day, + but is guaranteed no later than the next day. + +2. If an update check detects a new release, the notification should show up in the same session. + + _This is not a goal._ + An update check should happen way after the startup of an interactive session, + and thus it has no impact on whether or not a notification will be shown at the startup of that session. + If new release is detected, + the subsequent interactive sessions will show a notification about that new release. + +### Implementation + +This section talks about + +- when to do the update check +- how to persist the detected new release for subsequent `pwsh` sessions to use +- how to synchronize update checks from different processes of the same version `pwsh` so that at most only one can run to complete during a day +- how to do the update check +- how to display the notification + +#### When to do the update check + +During the startup, `pwsh` creates a `Task` of the update check work, +but delays the task run for 3 seconds by using `Task.Delay(3000)`. +The typical startup time for `pwsh` with a moderate size profile should be less than 1 second. +Given that, I guess it's reasonable to delay the update check work for 3 seconds, +so that it has close-to-zero impact on the startup performance. + +#### How to persist information about a new version + +The version of new release is persisted using a file, +not as the file content, but instead baked in the file name in the following template, +so that we can avoid extra file loading at the startup. + +```none +_update_ +``` + +The file should be in a folder that is unique to the specific version of `pwsh`. +For example, for the `v6.2.0 pwsh`, the folder `6.2.0` will be created in the `pwsh` cache folder (shown below), +and the update check related files for that version of `pwsh` are put there exclusively. +In this way, the update information for different versions of `pwsh` doesn't interfere with each other. + +- Windows: `$env:LOCALAPPDATA\Microsoft\PowerShell\6.2.0` +- Unix: `$env:HOME/.cache/powershell/6.2.0` + +#### How to synchronize update checks + +The most challenging part is to properly synchronize the update checks started from different `pwsh` processes, +so that for a specific version of `pwsh`, only one update check task, at most, will run to complete per a day. +Other tasks should be able to detect "a check is in progress" or "the check has been done for today" and bail out early, +to avoid any unnecessary network IO or CPU cycles. + +We need two more files to achieve the synchronization, +`sentinel-{year}-{month}-{day}` and `sentinel-{year}-{month}-{day}.done`. +The `{year}-{month}-{day}` part will be filled with the date of current day when the update check task starts to run, +and they will be in the version folder too. + +The file `sentinel-{year}-{month}-{day}` serves as a file lock among `pwsh` processes. +The file `sentinel-{year}-{month}-{day}.done` serves as a flag that indicates a successful update check as been done for the day. +Here are the sample code for doing this synchronization: + +```c# +const string TestDir = @"C:\arena\tmp\updatetest"; +const string FileNameTemplate = "sentinel-{0}-{1}-{2}"; + +static void CheckForUpdate() +{ + // Some pre-validation needs to happen to see if we need to do anything at all. + // - If the current running `pwsh` is a self-built version, let's bail out early. + // - Check if a file like `_update_` already exists. + // If so, check the `LastWriteTime` to see if it's still relatively new, say within a week. + // If so, let's bail out early. + + DateTime today = DateTime.UtcNow; + string todayFileName = string.Format( + CultureInfo.InvariantCulture, + FileNameTemplate, + today.Year.ToString(), + today.Month.ToString(), + today.Day.ToString()); + + string todayFilePath = Path.Combine(TestDir, todayFileName); + string todayDoneFilePath = string.Concat(todayFilePath, ".done"); + + if (File.Exists(todayDoneFilePath)) + { + // A successful update check has been done today, + // so we can bail out early. + return; + } + + try + { + // Use 'todayFilePath' as the file lock -- the update check run from each process will compete on + // creating and holding on the same file. + // - 'FileMode.CreateNew' means only one process can create/hold the file. + // - 'FileOptions.DeleteOnClose' means the file is cleaned up once it's closed. + // - Note that the file will be deleted even if the `pwsh` process terminates before the FileStream is disposed. + using (FileStream s = new FileStream( + todayFilePath, + FileMode.CreateNew, + FileAccess.Write, + FileShare.None, + bufferSize: 1024, + FileOptions.DeleteOnClose)) + { + if (File.Exists(todayDoneFilePath)) + { + // After grab the file lock, it turns out a successful check has finished. + // Then let's bail out early. + return; + } + + // Now it's guaranteed that I'm the only process that reaches here. + foreach (string oldFile in Directory.EnumerateFiles(TestDir, "sentinel-*-*-*.done")) + { + // Clean up the old '.done' file, there should be only one. + File.Delete(oldFile); + } + + // Do the real update check + // - Send HTTP request to query for the new release/pre-release; + // - If there is a valid new release that should be reported to the user, create the file `_update_`, + // or rename the existing `_update_` to `_update_`. + // ... more ... + + // Finally, create the `todayDoneFilePath` file as an indicator that a successful update check has finished today. + new FileStream( + todayDoneFilePath, + FileMode.CreateNew, + FileAccess.Write, + FileShare.None, + bufferSize: 1024, + FileOptions.None).Close(); + } + } + catch (Exception e) + { + // An update check is in progress from another `pwsh` process. So it's OK to just return. + } +} +``` + +With the file lock, only one process can get in the guarded `using` block at a given time. +So only one process will be creating the file `_update_`, or renaming an old such file to reflect the new version. +Yes, other processes could be looking at the old file name (when a `pwsh` session tries to print a notification), +or working with an outdated `FileInfo` object (another update check tries to do a pre-validation). +But it's fine for that to happen: + +- In the former case, that particular `pwsh` session will show a notification about an outdated version, + but the next `pwsh` session will show the right notification. +- In the latter case, the other update check will continue and find the `.done` file already exists for today. + +It's possible that a `pwsh` session terminates while the update check task is still running, +in the middle of the `using` block for example. +Creating the `.done` file is the very last step in the `using` block. +So if the session ends before the `.done` file is created, +another update check will happen when the next `pwsh` session starts and finish the work. + +#### How to do the update check + +This is the easy part. + +- Determine if we need to check pre-releases. +- Send HTTP query request. +- If there is a new update, create the file `_update_` if one doesn't exists yet; + or rename the existing file with the new version. + +#### How to display the notification + +`pwsh` checks to see if notification should be printed only if it's allowed to print the banner message. + +- Run `Directory.EnumerateFiles` with the the version directory and the pattern `_update_v*.*.*` to find such a file. +- If a file path is returned, then get the version information from the file name. +- Use that version to construct the notification message, including the URL to that GitHub release page. + +## Alternate Proposals and Considerations + +When thinking about how to reduce unnecessary update checks, +the first design I had was to depend on the `Day` of the month. +So for instance, we can check for updates every 3 days by checking `DateTime.UtcNow.Day % 3 == 0`. +But that means in the worst case, a user won't be notified of a new release until 3 days after the release. +That makes this feature somewhat broken from the UX perspective. From 576752b84febff3fa3940806886f2cbc38097463 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Mon, 25 Mar 2019 12:41:07 -0700 Subject: [PATCH 02/11] Fix typo -- Non-Goals Co-Authored-By: daxian-dbw --- 1-Draft/RFCNNNN-Notification-On-Version-Update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index 926f881d..4bbba868 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -51,7 +51,7 @@ And subsequent checks can be avoided for a reasonable period of time, such as a such as when `pwsh` is in a container image. Hence, you should be able to suppress them altogether by setting an environment variable. -### No Goals +### Non-Goals 1. Notification shows up right after a new version of `pwsh` is released. From 61f065c49e4395fae4a3cb28fc7bd8f20c7213e7 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Mon, 25 Mar 2019 12:41:28 -0700 Subject: [PATCH 03/11] Fix typo -- per day Co-Authored-By: daxian-dbw --- 1-Draft/RFCNNNN-Notification-On-Version-Update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index 4bbba868..f3f90c3b 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -37,7 +37,7 @@ This means the check for update must not happen during `pwsh` startup. The only acceptable extra overhead to the `pwsh` startup should just be the work related to printing the notification. 4. Check for updates should not blindly run for every interactive `pwsh` session. -For a particular version of `pwsh`, only one check at most can run to complete per a day +For a particular version of `pwsh`, only one check at most can run to complete per day no matter how many interactive session of the `pwsh` are started/opened in that day. 5. After a new update is detected during a successful check, From 33a6db4c18269363665bae7c30e90038f9515702 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 28 Mar 2019 16:46:56 -0700 Subject: [PATCH 04/11] Update to be more accurate on the implementation --- .../RFCNNNN-Notification-On-Version-Update.md | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index f3f90c3b..ade144ed 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -93,7 +93,7 @@ not as the file content, but instead baked in the file name in the following tem so that we can avoid extra file loading at the startup. ```none -_update_ +_update__ ``` The file should be in a folder that is unique to the specific version of `pwsh`. @@ -112,36 +112,37 @@ Other tasks should be able to detect "a check is in progress" or "the check has to avoid any unnecessary network IO or CPU cycles. We need two more files to achieve the synchronization, -`sentinel-{year}-{month}-{day}` and `sentinel-{year}-{month}-{day}.done`. +`"sentinel"` and `"sentinel-{year}-{month}-{day}.done"`. The `{year}-{month}-{day}` part will be filled with the date of current day when the update check task starts to run, and they will be in the version folder too. -The file `sentinel-{year}-{month}-{day}` serves as a file lock among `pwsh` processes. -The file `sentinel-{year}-{month}-{day}.done` serves as a flag that indicates a successful update check as been done for the day. +The file `"sentinel"` serves as a file lock among `pwsh` processes. +The file `"sentinel-{year}-{month}-{day}.done"` serves as a flag that indicates a successful update check as been done for the day. Here are the sample code for doing this synchronization: ```c# const string TestDir = @"C:\arena\tmp\updatetest"; -const string FileNameTemplate = "sentinel-{0}-{1}-{2}"; +const string SentinelFileName = "sentinel"; +const string DoneFileNameTemplate = "sentinel-{0}-{1}-{2}.done"; static void CheckForUpdate() { // Some pre-validation needs to happen to see if we need to do anything at all. // - If the current running `pwsh` is a self-built version, let's bail out early. - // - Check if a file like `_update_` already exists. + // - Check if a file like `_update__` already exists. // If so, check the `LastWriteTime` to see if it's still relatively new, say within a week. // If so, let's bail out early. DateTime today = DateTime.UtcNow; - string todayFileName = string.Format( + string todayDoneFileName = string.Format( CultureInfo.InvariantCulture, - FileNameTemplate, + DoneFileNameTemplate, today.Year.ToString(), today.Month.ToString(), today.Day.ToString()); - string todayFilePath = Path.Combine(TestDir, todayFileName); - string todayDoneFilePath = string.Concat(todayFilePath, ".done"); + string sentinelFilePath = Path.Combine(TestDir, SentinelFileName); + string todayDoneFilePath = Path.Combine(TestDir, todayDoneFileName); if (File.Exists(todayDoneFilePath)) { @@ -152,18 +153,9 @@ static void CheckForUpdate() try { - // Use 'todayFilePath' as the file lock -- the update check run from each process will compete on - // creating and holding on the same file. - // - 'FileMode.CreateNew' means only one process can create/hold the file. - // - 'FileOptions.DeleteOnClose' means the file is cleaned up once it's closed. - // - Note that the file will be deleted even if the `pwsh` process terminates before the FileStream is disposed. - using (FileStream s = new FileStream( - todayFilePath, - FileMode.CreateNew, - FileAccess.Write, - FileShare.None, - bufferSize: 1024, - FileOptions.DeleteOnClose)) + // Use 'sentinelFilePath' as the file lock. + // The update check tasks started by every 'pwsh' process will compete on holding this file. + using (FileStream s = new FileStream(sentinelFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) { if (File.Exists(todayDoneFilePath)) { @@ -186,13 +178,7 @@ static void CheckForUpdate() // ... more ... // Finally, create the `todayDoneFilePath` file as an indicator that a successful update check has finished today. - new FileStream( - todayDoneFilePath, - FileMode.CreateNew, - FileAccess.Write, - FileShare.None, - bufferSize: 1024, - FileOptions.None).Close(); + new FileStream(todayDoneFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None).Close(); } } catch (Exception e) @@ -203,7 +189,7 @@ static void CheckForUpdate() ``` With the file lock, only one process can get in the guarded `using` block at a given time. -So only one process will be creating the file `_update_`, or renaming an old such file to reflect the new version. +So only one process will be creating the file `_update__`, or renaming an old such file to reflect the new version. Yes, other processes could be looking at the old file name (when a `pwsh` session tries to print a notification), or working with an outdated `FileInfo` object (another update check tries to do a pre-validation). But it's fine for that to happen: @@ -220,18 +206,30 @@ another update check will happen when the next `pwsh` session starts and finish #### How to do the update check -This is the easy part. +This is comparatively the easy part. - Determine if we need to check pre-releases. -- Send HTTP query request. -- If there is a new update, create the file `_update_` if one doesn't exists yet; +- Send HTTP query request and parse the response. + Some optimization work is needed in this step (see below). + It would be much better if we can have the latest release/pre-release information stored in a well-known URL, + to make the query easier and take less time. + - GitHub API doesn't support querying for the latest pre-release, + so we need to hit the 'get-all-releases' API `https://api.github.com/repos/PowerShell/PowerShell/releases`. + By default that will return 30 records per page and result in very expensive payload. + As an optimization, we should add `?per_page=4` to make it only return the most recent 4 records. + Most likely, they will include the latest release or pre-release. + - The JSON payload for 4 release records is still a lot, + and thus the deserialization is expensive, taking about 650 ms on my dev machine. + We only care about the `tag_name` and `published_at` attributes, + so it would be desirable to optimize the deserialization to skip the unneeded. +- If there is a new update, create the file `_update__` if one doesn't exists yet; or rename the existing file with the new version. #### How to display the notification `pwsh` checks to see if notification should be printed only if it's allowed to print the banner message. -- Run `Directory.EnumerateFiles` with the the version directory and the pattern `_update_v*.*.*` to find such a file. +- Run `Directory.EnumerateFiles` with the the version directory and the pattern `_update_v*.*.*_????-??-??` to find such a file. - If a file path is returned, then get the version information from the file name. - Use that version to construct the notification message, including the URL to that GitHub release page. From d1f96af57a720b2ac8f19422b8de4e05d9cbe09d Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Thu, 3 Oct 2019 16:48:51 -0700 Subject: [PATCH 05/11] Address comments, update according to the implementation --- .../RFCNNNN-Notification-On-Version-Update.md | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index ade144ed..c382060e 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -68,6 +68,11 @@ Hence, you should be able to suppress them altogether by setting an environment If new release is detected, the subsequent interactive sessions will show a notification about that new release. +3. If a new release is available, `pwsh` is able to automatically upgrade. + + _This is not a goal._ + A notification message is printed, but `pwsh` will not auto-upgrade. + ### Implementation This section talks about @@ -83,7 +88,7 @@ This section talks about During the startup, `pwsh` creates a `Task` of the update check work, but delays the task run for 3 seconds by using `Task.Delay(3000)`. The typical startup time for `pwsh` with a moderate size profile should be less than 1 second. -Given that, I guess it's reasonable to delay the update check work for 3 seconds, +Given that, I think it's reasonable to delay the update check work for 3 seconds, so that it has close-to-zero impact on the startup performance. #### How to persist information about a new version @@ -130,7 +135,7 @@ static void CheckForUpdate() // Some pre-validation needs to happen to see if we need to do anything at all. // - If the current running `pwsh` is a self-built version, let's bail out early. // - Check if a file like `_update__` already exists. - // If so, check the `LastWriteTime` to see if it's still relatively new, say within a week. + // If so, check the `publish-date` to see if it's still relatively new, say within a week. // If so, let's bail out early. DateTime today = DateTime.UtcNow; @@ -155,7 +160,7 @@ static void CheckForUpdate() { // Use 'sentinelFilePath' as the file lock. // The update check tasks started by every 'pwsh' process will compete on holding this file. - using (FileStream s = new FileStream(sentinelFilePath, FileMode.Create, FileAccess.Write, FileShare.None)) + using (FileStream s = new FileStream(sentinelFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, bufferSize: 1, FileOptions.DeleteOnClose)) { if (File.Exists(todayDoneFilePath)) { @@ -188,10 +193,13 @@ static void CheckForUpdate() } ``` +> Note: `FileOptions.DeleteOnClose` is used when opening the sentinel file, +so the sentinel file will be removed after being used as a lock. + With the file lock, only one process can get in the guarded `using` block at a given time. So only one process will be creating the file `_update__`, or renaming an old such file to reflect the new version. Yes, other processes could be looking at the old file name (when a `pwsh` session tries to print a notification), -or working with an outdated `FileInfo` object (another update check tries to do a pre-validation). +or working with an outdated file name (another update check tries to do a pre-validation). But it's fine for that to happen: - In the former case, that particular `pwsh` session will show a notification about an outdated version, @@ -240,3 +248,12 @@ the first design I had was to depend on the `Day` of the month. So for instance, we can check for updates every 3 days by checking `DateTime.UtcNow.Day % 3 == 0`. But that means in the worst case, a user won't be notified of a new release until 3 days after the release. That makes this feature somewhat broken from the UX perspective. + +Another design is to let all `pwsh`, including different versions, share the same update file, +whose name contains both the latest stable release tag and latest preview release tag. +When `pwsh` starts, it parses the file name, compare the latest stable/preview release version with its current version, +and decides if a notification should be printed. +This would reduce the number of helper files in the cache folder, +however, with the cost of additional work at startup time for all versions of `pwsh`. +Especially, for the latest stable or preview `pwsh` in use, it also needs to spend those extra cycles when it should not. +Besides, I think having the helper files isolated in a version folder makes it flexible in case we need to make change to the design at a later time. From 213298b4426a90d24d4f0b0398e0989319179646 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Mon, 28 Oct 2019 13:00:32 -0700 Subject: [PATCH 06/11] Update RFC with the new environment variable settings --- 1-Draft/RFCNNNN-Notification-On-Version-Update.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index c382060e..9f2cbcf1 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -82,6 +82,7 @@ This section talks about - how to synchronize update checks from different processes of the same version `pwsh` so that at most only one can run to complete during a day - how to do the update check - how to display the notification +- how to control the update notification behavior using an environment variable #### When to do the update check @@ -241,6 +242,19 @@ This is comparatively the easy part. - If a file path is returned, then get the version information from the file name. - Use that version to construct the notification message, including the URL to that GitHub release page. +#### How to control the update notification behavior using an environment variable + +The environment variable `POWERSHELL_UPDATECHECK` will be introduced to control the behavior of the update notification feature. +The environment variable supports 3 values: + +- `Default`. This gives you the default behaviors: + - `pwsh` of preview versions check for the new preview version as well as the new GA version. + - `pwsh` of GA versions check for the new GA version only. + +- `Off`. This turns off the update notification feature. + +- `LTS`. `pwsh` of both preview and stable versions check for the new LTS GA version only. + ## Alternate Proposals and Considerations When thinking about how to reduce unnecessary update checks, From 189782fc829a31c7bbdcff3cd73e428c7f25482e Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 19 Nov 2019 16:53:24 -0800 Subject: [PATCH 07/11] Update the RFC based on the implementation --- .../RFCNNNN-Notification-On-Version-Update.md | 101 +++++++++++------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index 9f2cbcf1..d5737456 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -77,12 +77,49 @@ Hence, you should be able to suppress them altogether by setting an environment This section talks about +- how to control the update notification behavior - when to do the update check - how to persist the detected new release for subsequent `pwsh` sessions to use - how to synchronize update checks from different processes of the same version `pwsh` so that at most only one can run to complete during a day - how to do the update check - how to display the notification -- how to control the update notification behavior using an environment variable + +#### How to control the update notification behavior + +The environment variable `POWERSHELL_UPDATECHECK` will be introduced to control the behavior of the update notification feature. +The environment variable supports 3 values: + +- `Off`. This turns off the update notification feature. + +- `Default`. This gives you the default behaviors: + - `pwsh` of preview versions check for the new preview version as well as the new GA version. + - `pwsh` of GA versions check for the new GA version only. + +- `LTS`. `pwsh` of both preview and stable versions check for the new LTS GA version only. + +The notification behavior is mapped to the following enum: + +```c# +private enum NotificationType +{ + /// + /// Turn off the udpate notification. + /// + Off = 0, + + /// + /// Give you the default behaviors: + /// - the preview version 'pwsh' checks for the new preview version and the new GA version. + /// - the GA version 'pwsh' checks for the new GA version only. + /// + Default = 1, + + /// + /// Both preview and GA version 'pwsh' checks for the new LTS version only. + /// + LTS = 2 +} +``` #### When to do the update check @@ -99,9 +136,17 @@ not as the file content, but instead baked in the file name in the following tem so that we can avoid extra file loading at the startup. ```none -_update__ +update__ ``` +A separate file is used for each supported notification type, +indicated by the integer value of the corresponding `NotificationType` member. +For example, +- when the notification type is `NotificationType.Default`, +the file name would be like `update1__`. +- when the notification type is `NotificationType.LTS`, +the file name would be like `update2__`. + The file should be in a folder that is unique to the specific version of `pwsh`. For example, for the `v6.2.0 pwsh`, the folder `6.2.0` will be created in the `pwsh` cache folder (shown below), and the update check related files for that version of `pwsh` are put there exclusively. @@ -113,23 +158,25 @@ In this way, the update information for different versions of `pwsh` doesn't int #### How to synchronize update checks The most challenging part is to properly synchronize the update checks started from different `pwsh` processes, -so that for a specific version of `pwsh`, only one update check task, at most, will run to complete per a day. +so that for a specific version of `pwsh` and a specific notification type, +only one update check task, at most, will run to complete per a day. Other tasks should be able to detect "a check is in progress" or "the check has been done for today" and bail out early, to avoid any unnecessary network IO or CPU cycles. -We need two more files to achieve the synchronization, -`"sentinel"` and `"sentinel-{year}-{month}-{day}.done"`. +For each notification type, we need two more files to achieve the synchronization, +`"_sentinel_"` and `"sentinel-{year}-{month}-{day}.done"`. +The `` part will be the integer value of the corresponding `NotificationType` member. The `{year}-{month}-{day}` part will be filled with the date of current day when the update check task starts to run, and they will be in the version folder too. -The file `"sentinel"` serves as a file lock among `pwsh` processes. -The file `"sentinel-{year}-{month}-{day}.done"` serves as a flag that indicates a successful update check as been done for the day. +The file `"_sentinel_"` serves as a file lock among `pwsh` processes. +The file `"sentinel-{year}-{month}-{day}.done"` serves as a flag that indicates a successful update check as been done for the day. Here are the sample code for doing this synchronization: ```c# const string TestDir = @"C:\arena\tmp\updatetest"; -const string SentinelFileName = "sentinel"; -const string DoneFileNameTemplate = "sentinel-{0}-{1}-{2}.done"; +const string SentinelFileName = "_sentinel1_"; +const string DoneFileNameTemplate = "sentinel1-{0}-{1}-{2}.done"; static void CheckForUpdate() { @@ -217,44 +264,22 @@ another update check will happen when the next `pwsh` session starts and finish This is comparatively the easy part. -- Determine if we need to check pre-releases. +- Determine the URL to use depending on the notification type. + - For latest preview release-info: `https:///preview.json` + - For latest stable release-info: `https:///stable.json` + - For latest LTS release-info: `https:///lts.json` - Send HTTP query request and parse the response. - Some optimization work is needed in this step (see below). - It would be much better if we can have the latest release/pre-release information stored in a well-known URL, - to make the query easier and take less time. - - GitHub API doesn't support querying for the latest pre-release, - so we need to hit the 'get-all-releases' API `https://api.github.com/repos/PowerShell/PowerShell/releases`. - By default that will return 30 records per page and result in very expensive payload. - As an optimization, we should add `?per_page=4` to make it only return the most recent 4 records. - Most likely, they will include the latest release or pre-release. - - The JSON payload for 4 release records is still a lot, - and thus the deserialization is expensive, taking about 650 ms on my dev machine. - We only care about the `tag_name` and `published_at` attributes, - so it would be desirable to optimize the deserialization to skip the unneeded. -- If there is a new update, create the file `_update__` if one doesn't exists yet; +- If there is a new update, create the file `update__` if one doesn't exists yet; or rename the existing file with the new version. #### How to display the notification -`pwsh` checks to see if notification should be printed only if it's allowed to print the banner message. +`pwsh` checks to see if notification should be printed only if it's allowed to print the banner message and feature is not turned off. -- Run `Directory.EnumerateFiles` with the the version directory and the pattern `_update_v*.*.*_????-??-??` to find such a file. +- Run `Directory.EnumerateFiles` with the the version directory and the pattern `update_v*.*.*_????-??-??` to find such a file. - If a file path is returned, then get the version information from the file name. - Use that version to construct the notification message, including the URL to that GitHub release page. -#### How to control the update notification behavior using an environment variable - -The environment variable `POWERSHELL_UPDATECHECK` will be introduced to control the behavior of the update notification feature. -The environment variable supports 3 values: - -- `Default`. This gives you the default behaviors: - - `pwsh` of preview versions check for the new preview version as well as the new GA version. - - `pwsh` of GA versions check for the new GA version only. - -- `Off`. This turns off the update notification feature. - -- `LTS`. `pwsh` of both preview and stable versions check for the new LTS GA version only. - ## Alternate Proposals and Considerations When thinking about how to reduce unnecessary update checks, From c5690d605b454fde9091ddc23fe6548a8d50124b Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 19 Nov 2019 17:06:15 -0800 Subject: [PATCH 08/11] Minor update --- 1-Draft/RFCNNNN-Notification-On-Version-Update.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index d5737456..5269eff0 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -158,8 +158,8 @@ In this way, the update information for different versions of `pwsh` doesn't int #### How to synchronize update checks The most challenging part is to properly synchronize the update checks started from different `pwsh` processes, -so that for a specific version of `pwsh` and a specific notification type, -only one update check task, at most, will run to complete per a day. +**so that for a specific version of `pwsh` and a specific notification type, +only one update check task, at most, will run to complete per a day.** Other tasks should be able to detect "a check is in progress" or "the check has been done for today" and bail out early, to avoid any unnecessary network IO or CPU cycles. From 422728934c13760b64a6d6905374b07f3e1b9b4b Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 4 Feb 2020 11:43:49 -0800 Subject: [PATCH 09/11] Mention the message is printed with foreground and background colors inverted --- 1-Draft/RFCNNNN-Notification-On-Version-Update.md | 1 + 1 file changed, 1 insertion(+) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index 5269eff0..c2d29656 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -279,6 +279,7 @@ This is comparatively the easy part. - Run `Directory.EnumerateFiles` with the the version directory and the pattern `update_v*.*.*_????-??-??` to find such a file. - If a file path is returned, then get the version information from the file name. - Use that version to construct the notification message, including the URL to that GitHub release page. +- The notification message is printed with the foreground and background colors inverted. ## Alternate Proposals and Considerations From cc0a2a975ee10e051106bc36c0a446f561be5615 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 5 Feb 2020 15:37:47 -0800 Subject: [PATCH 10/11] Address comment --- 1-Draft/RFCNNNN-Notification-On-Version-Update.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/1-Draft/RFCNNNN-Notification-On-Version-Update.md index c2d29656..189b1b73 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/1-Draft/RFCNNNN-Notification-On-Version-Update.md @@ -22,6 +22,8 @@ It would be convenient if `pwsh` itself can notify the user of a new update on s ## Specification +This feature is in the PowerShell console host, not in the PowerShell engine. + ### Target Goals 1. No notification or update check when the running `pwsh` is a self-built version. @@ -297,3 +299,6 @@ This would reduce the number of helper files in the cache folder, however, with the cost of additional work at startup time for all versions of `pwsh`. Especially, for the latest stable or preview `pwsh` in use, it also needs to spend those extra cycles when it should not. Besides, I think having the helper files isolated in a version folder makes it flexible in case we need to make change to the design at a later time. + +We may consider to add an API in PowerShell engine to check for updates in future, +so that other hosts can offer similar features using the API. From 5769b2fbc1b992061ab067b9cfcbd0e96dd95a0f Mon Sep 17 00:00:00 2001 From: Joey Aiello Date: Wed, 19 Feb 2020 15:19:54 -0800 Subject: [PATCH 11/11] Prepare RFC0052 (update notifications) for merging as Final --- .../RFC0052-Notification-On-Version-Update.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename 1-Draft/RFCNNNN-Notification-On-Version-Update.md => 5-Final/RFC0052-Notification-On-Version-Update.md (99%) diff --git a/1-Draft/RFCNNNN-Notification-On-Version-Update.md b/5-Final/RFC0052-Notification-On-Version-Update.md similarity index 99% rename from 1-Draft/RFCNNNN-Notification-On-Version-Update.md rename to 5-Final/RFC0052-Notification-On-Version-Update.md index 189b1b73..c2097a17 100644 --- a/1-Draft/RFCNNNN-Notification-On-Version-Update.md +++ b/5-Final/RFC0052-Notification-On-Version-Update.md @@ -1,7 +1,7 @@ --- -RFC: RFCXXXX +RFC: 0052 Author: Dongbo Wang -Status: Draft +Status: Final SupercededBy: N/A Version: 1.0 Area: Console