Skip to content

Track menus per thread, not per process - #3

Merged
Menelion merged 1 commit into
masterfrom
fix-menu-tracking-thread-scope
Sep 8, 2026
Merged

Menelion merged 1 commit into
masterfrom
fix-menu-tracking-thread-scope

Conversation

@Menelion

@Menelion Menelion commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the CI failure on master. MenuTrackingScope counted tracking depth in a plain static field; it is now [ThreadStatic].

A menu belongs to the thread that created it — TrackPopupMenuEx runs its nested message loop on the calling thread — so the depth belongs to the thread too.

Why it failed on master but not on the branch

It is a race. xUnit runs test classes in parallel, StaRunner gives each test its own thread, and two test classes deliberately enter a tracking scope to prove Rebuild refuses while a popup is open. While either held the scope, an unrelated rebuild in a third class threw Cannot rebuild the menu bar while a popup menu is open.

Not only a test problem

WinForms permits more than one UI thread, each with its own pump. A popup tracked on one would have blocked a legitimate rebuild on another. [ThreadStatic] is what the type's own remarks already claimed it was.

Verification

Tracking_OnOneThread_DoesNotBlockAnother holds a scope on one STA thread and rebuilds a menu bar on another. Removing [ThreadStatic] again makes it fail — checked, not assumed. 154 tests pass, three consecutive runs.

Also included

The sample's category tree right-aligned its text in Hebrew but never mirrored: RightToLeft is ambient, RightToLeftLayout is not. An unmirrored tree keeps its left-to-right key bindings, so Right arrow expanded a node whose children were drawn to the left. Verified via the window's WS_EX_LAYOUTRTL before and after.

https://claude.ai/code/session_01TJ8i7jHmkUjVCjVccLp6Hf

The guard that stops an HMENU being destroyed while Windows is displaying
it counted in a plain static field. TrackPopupMenuEx runs its nested
message loop on the calling thread and the menu it shows belongs to that
thread, so the depth belongs to the thread too.

It surfaced as a CI failure that passed on the branch and failed on
master: xUnit runs test classes in parallel, StaRunner gives each test its
own thread, and two classes deliberately enter a tracking scope to prove
that Rebuild refuses while a popup is open. While either held the scope, an
unrelated rebuild in a third class threw "Cannot rebuild the menu bar while
a popup menu is open". A race, so it failed intermittently.

The same hole is reachable outside the tests. WinForms permits more than
one UI thread, each with its own pump, and a popup tracked on one of them
would have blocked a legitimate rebuild on another. ThreadStatic is what
the type's own remarks always claimed it was.

The regression test holds a scope on one STA thread and rebuilds a menu bar
on another; removing ThreadStatic again makes it fail.

Also, in the sample: the category tree right-aligned its text in Hebrew but
never mirrored, because RightToLeft is an ambient property and
RightToLeftLayout is not. An unmirrored tree keeps its left-to-right key
bindings, so Right arrow expanded a node whose children were drawn to the
left. The listening script now states which arrow should do what, since
that is not something a reader can infer.

Claude-Session: https://claude.ai/code/session_01TJ8i7jHmkUjVCjVccLp6Hf
@Menelion
Menelion merged commit cdc61c0 into master Sep 8, 2026
1 check passed
@Menelion
Menelion deleted the fix-menu-tracking-thread-scope branch September 8, 2026 12:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant