Skip to content

Fix: Fix a issue that change always on top settings won't take effect immediately#45994

Merged
vanzue merged 1 commit intomainfrom
dev/vanzue/fix-concurrent-issue
Mar 9, 2026
Merged

Fix: Fix a issue that change always on top settings won't take effect immediately#45994
vanzue merged 1 commit intomainfrom
dev/vanzue/fix-concurrent-issue

Conversation

@vanzue
Copy link
Copy Markdown
Contributor

@vanzue vanzue commented Mar 8, 2026

Summary of the Pull Request

Fix #45993

Always on top have a window proc thread that will reload the settings once file watcher trigger a reload of settings.
And always on top has a worker thread to read the settings at the same time.
So it may happen worker thread will read the stale setting, as a result, user change settings, and try to invoke always on top, as if nothing has changed.
As the issue's recording shows.

PR Checklist

  • Communication: I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
  • Tests: Added/updated and all pass
  • Localization: All end-user-facing strings can be localized
  • Dev docs: Added/updated
  • New binaries: Added on the required places
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

The setting take effect once it's changed:

Alwaysontop-fix.mp4

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a concurrency bug in Always On Top where settings could be read while they’re being reloaded, causing user setting changes (e.g., enable frame, sound, etc.) to not take effect immediately.

Changes:

  • Switches AlwaysOnTop settings storage to an atomic shared_ptr snapshot (std::atomic<std::shared_ptr<const Settings>>) to avoid concurrent mutation/read races.
  • Updates call sites to read settings via AlwaysOnTopSettings::settings() (pointer-style access) and reduces repeated direct access patterns in some places.
  • Batches observer notifications so the new snapshot is stored before notifying observers of changed settings.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp Updated settings reads to use the new shared snapshot; minor local caching added.
src/modules/alwaysontop/AlwaysOnTop/Settings.h Changes settings() API to return a shared_ptr<const Settings> and stores settings atomically.
src/modules/alwaysontop/AlwaysOnTop/Settings.cpp Implements copy-on-write settings reload and stores a new snapshot before notifying observers.
src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp Updates all settings reads to the new API (shared snapshot / pointer access).
Comments suppressed due to low confidence (1)

src/modules/alwaysontop/AlwaysOnTop/WindowBorder.cpp:203

  • In UpdateBorderProperties, the local const auto settings = AlwaysOnTopSettings::settings(); is shadowed by winrt::Windows::UI::ViewManagement::UISettings settings; inside the if block. This compiles but is confusing and makes it easy to accidentally refer to the wrong settings later. Rename the inner WinRT variable (e.g., uiSettings) to avoid shadowing.
    const auto settings = AlwaysOnTopSettings::settings();
    COLORREF color;
    if (settings->frameAccentColor)
    {
        winrt::Windows::UI::ViewManagement::UISettings settings;
        auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent);
        color = RGB(accentValue.R, accentValue.G, accentValue.B);

Comment on lines 239 to 283
@@ -276,7 +279,7 @@ void AlwaysOnTop::ProcessCommand(HWND window)
}
}

if (AlwaysOnTopSettings::settings().enableSound)
if (AlwaysOnTopSettings::settings()->enableSound)
{
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProcessCommand calls AlwaysOnTopSettings::settings() multiple times (blockInGameMode, then later enableSound). Since settings() now returns a shared_ptr, each call does an atomic load + refcount inc/dec; it also means the function can observe different snapshots if a reload happens mid-call. Consider loading once at the start of the function and using that local for all reads within ProcessCommand.

Copilot uses AI. Check for mistakes.
@niels9001
Copy link
Copy Markdown
Collaborator

@vanzue should this be a 0.98 item?

@vanzue
Copy link
Copy Markdown
Contributor Author

vanzue commented Mar 9, 2026

It's not a regression, while it's a bug found during testing, I think we should include it, I will test this more thoroughly today, make sure everything well

@vanzue vanzue added the 0.98 label Mar 9, 2026
@vanzue
Copy link
Copy Markdown
Contributor Author

vanzue commented Mar 9, 2026

A fully strong-consistency fix would require a different architecture, such as synchronous IPC acknowledgements or blocking update propagation, so the current semantic is a trade off between exact next operation effective and the simplicity, verified locally it works as expected, not observe the gap any more.

@vanzue vanzue enabled auto-merge (squash) March 9, 2026 03:21
@vanzue vanzue merged commit 75fb296 into main Mar 9, 2026
19 checks passed
@zateutsch zateutsch added this to the PowerToys 0.98 milestone Mar 11, 2026
@zateutsch zateutsch added the Product-Always On Top Refers to the idea of a Always on Top Powertoy label Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.98 Product-Always On Top Refers to the idea of a Always on Top Powertoy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Always On Top: The settings watcher has concurrent issue

5 participants