-
-
Notifications
You must be signed in to change notification settings - Fork 111
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
feat(windows): handle a hard windows reset occurring while downloading updated keyman files #13128
base: feat/windows/13167/add-release-kmutex
Are you sure you want to change the base?
feat(windows): handle a hard windows reset occurring while downloading updated keyman files #13128
Conversation
Using the KeymanMutex wrapper to check if a download process is occuring if it isn't and we are in the downloading state this means the download process exited early. We can then clean up any downloaded files and reset the statemachine and check for updates again.
User Test ResultsTest specification and instructions
Test Artifacts |
|
ChangeState(IdleState); | ||
bucStateContext.CurrentState.HandleCheck; | ||
end; | ||
FreeAndNil(FMutex); |
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.
Does it matter that we call FreeAndNil
twice if FMutex.MutexOwned
? (same below)
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.
Yeah good catch, I should exit early to avoid the redundant call. I have also added a comment to make clear to future me or other developers that the mutex needs to be freed before changing state and triggering a force check. Updated to use a try .. finally handling.
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.
FWIW, FreeAndNil
is safe to call on an already-nil variable, but it's definitely a code smell in any case 😁
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.
Updated to use a try .. finally handling.
Note that exiting early still runs the finally section
Test ResultsI tested this issue with the attached Keyman"18.0.184-alpha-test-13128" build(08/02/2025) on Windows 10. Here I am sharing my observation.
|
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.
Asking for some changes to this PR...
So, TKeymanMutex
is my fault and has the terribly named MutexOwned
function which takes ownership of the mutex. Let's fix that -- rename it to TakeOwnership
and add a ReleaseOwnership
function. That means a handful of one-line changes elsewhere but clarifies usage dramatically IMHO!
try | ||
FMutex := TKeymanMutex.Create('KeymanDownloading'); |
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.
This should be outside the try-finally:
try | |
FMutex := TKeymanMutex.Create('KeymanDownloading'); | |
FMutex := TKeymanMutex.Create('KeymanDownloading'); | |
try |
Also, given we use the same name in multiple calls to the constructor, can we make that a constant at top of implementation section?
const
KeymanDownloadMutexName = 'KeymanDownloading';
begin | ||
// Enter DownloadingState | ||
bucStateContext.SetRegistryState(usDownloading); | ||
|
||
RetryCount := 0; | ||
DownloadResult := False; | ||
FMutex := nil; |
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.
FMutex := nil; |
With the construction of FMutex outside try/finally, this is not needed
while (not DownloadResult) and (RetryCount < 3) do | ||
begin | ||
DownloadResult := DownloadUpdatesBackground; | ||
if not DownloadResult then | ||
Inc(RetryCount); | ||
end; |
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.
This could be a repeat
/until
loop which is a little cleaner.
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.
while (not DownloadResult) and (RetryCount < 3) do | |
begin | |
DownloadResult := DownloadUpdatesBackground; | |
if not DownloadResult then | |
Inc(RetryCount); | |
end; | |
RetryCount := 0; | |
repeat | |
DownloadResult := DownloadUpdatesBackground; | |
Inc(RetryCount); | |
until DownloadResult or (RetryCount = 3); |
try | ||
FMutex := TKeymanMutex.Create('KeymanDownloading'); |
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.
try | |
FMutex := TKeymanMutex.Create('KeymanDownloading'); | |
FMutex := TKeymanMutex.Create('KeymanDownloading'); | |
try |
ditto
begin | ||
// Downloading state, in other process, so continue | ||
FMutex := nil; |
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.
FMutex := nil; |
if FMutex.MutexOwned then | ||
begin | ||
bucStateContext.RemoveCachedFiles; | ||
FreeAndNil(FMutex); // Mutex must be freed before changing state |
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.
I think it would it be cleaner to relinquish ownership without freeing here. Add TKeymanMutex.Release
function which calls ReleaseMutex()
FMutex := nil; | ||
// If downloading process is not running clean files and return to idle | ||
try | ||
FMutex := TKeymanMutex.Create('KeymanDownloading'); |
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.
FMutex := nil; | |
// If downloading process is not running clean files and return to idle | |
try | |
FMutex := TKeymanMutex.Create('KeymanDownloading'); | |
// If downloading process is not running clean files and return to idle | |
FMutex := TKeymanMutex.Create('KeymanDownloading'); | |
try |
if FMutex.MutexOwned then | ||
begin | ||
bucStateContext.RemoveCachedFiles; | ||
FreeAndNil(FMutex); // Mutex must be freed before changing state |
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.
ditto .Release
FreeAndNil(FMutex); // Mutex must be freed before changing state | ||
ChangeState(IdleState); | ||
bucStateContext.CurrentState.HandleCheck; | ||
Exit; |
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.
ditto not required
Exit; |
RetryCount := 0; | ||
DownloadResult := False; |
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.
I think these should be immediately before their first use in the repeat/until loop, and then DownloadResult does not need to be pre-initialized either
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.
RetryCount := 0; | |
DownloadResult := False; |
…into feat/windows/13119/handle-reset-downloading # Keyman Conventional Commit suggestions: # # - Link to a Sentry issue with git trailer: # Fixes: _MODULE_-_ID_ # - Give credit to co-authors: # Co-authored-by: _Name_ <_email_> # - Use imperative, present tense ('attach' not 'attaches', 'attached' etc) # - Don't include a period at the end of the title # - Always include a blank line before trailers # - More: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes
Fixes: #13119
When kmshell.exe is started to run configuration on any other number of switches, is in the downloading state, the state machine would just let the execution continue. This PR adds a check to ensure the Download process hasn't crashed in such away that let the state 'stuck' in the downloading state. It know resets the StateMachine if it finds that is the State Machine is in the downloading state but there is no active download process.
I used KeymanMutex because it was quite simple. If however it turns out that the mutex is not removed when the process crashes. Then it maybe better to use something like this https://stackoverflow.com/questions/876224/how-to-check-if-a-process-is-running-using-delphi and check for the
-bd
command line argument.User Testing
TEST_DOWNLOAD_INTERUPTED_TASK_MANAGER
update state
found at (Computer\HKEY_CURRENT_USER\SOFTWARE\Keyman\Keyman Engine).usIdle
check for updates
cd "c:\Program Files (x86)\Keyman\Keyman Desktop"
kmshell.exe -buc
update state
has advanced to eitherusUpdateAvailable
orusDownloading
. Press F5 to refresh the view.Keyman Configuration
right click and chooseEnd Task
from the pop-menuupdate state
still saysusDownloading
.usDownloading
.kmshell.exe -c
usIdle
->usUpdateAvailable
->usDownloading
->usWaitingRestart
usWaitingRestart
.TEST_DOWNLOAD_INTERUPTED_HARD_POWER_OFF
I am not sure if the Test team can complete this test but I tested it twice.
start with Windows
update state
found at (Computer\HKEY_CURRENT_USER\SOFTWARE\Keyman\Keyman Engine).usIdle
check for updates
cd "c:\Program Files (x86)\Keyman\Keyman Desktop"
kmshell.exe -buc
update state
has advanced to eitherusUpdateAvailable
orusDownloading
. Press F5 to refresh the view.update state
still saysusDownloading
using the registry editorusDownloading
.kmshell.exe -c
usIdle
->usUpdateAvailable
->usDownloading
->usWaitingRestart
usWaitingRestart
.