Skip to content
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

Ensure equality when hashing default args and no args in actions #10341

Merged
5 commits merged into from
Jun 16, 2021

Conversation

carlos-zamora
Copy link
Member

Summary of the Pull Request

#10297 found a bug in ActionMap where the ToggleCommandPalette key chord could not be found using GetKeyBindingForAction.

This was caused by the following:

  • AddAction: when adding the action, the ActionAndArgs is basically added as {ToggleCommandPalette, ToggleCommandLineArgs{}} (Note the default ctor used for the action args)
  • GetKeyBindingForAction: we're searching for an ActionAndArgs structured as {ToggleCommandPalette, nullptr}
  • Since these are technically two different actions, we are unable to find it.

This issue was fixed by making the Hash(ActionAndArgs) function smarter! If the ActionAndArgs has no args, but the ShortcutAction supports args, generate the args using the default ctor.

By making Hash() smarter, everybody benefits from this logic! We can basically now enforce that ActionAndArgs{ <X>, nullptr } == ActionAndArgs{ <X>, <default_ctor> }.

Validation Steps Performed

@carlos-zamora carlos-zamora added zPreview-Service-Queued-1.13 A floating label that tracks the current Preview version for servicing purposes. zStable-Service-Queued-1.12 A floating label that tracks the current Stable version for servicing purposes. labels Jun 4, 2021
@DHowett
Copy link
Member

DHowett commented Jun 4, 2021

This can't be serviced to Stable as that code doesn't exist in Stable. Be careful with those servicing tags...

src/cascadia/TerminalSettingsModel/ActionMap.cpp Outdated Show resolved Hide resolved
Copy link
Member

@lhecker lhecker left a comment

Choose a reason for hiding this comment

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

Woops I meant to comment and not approve.
I'll request changes, since I can't undo an approval.

@ghost ghost added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jun 5, 2021
@KalleOlaviNiemitalo
Copy link

KalleOlaviNiemitalo commented Jun 5, 2021

It seems static InternalActionID Hash(const Model::ActionAndArgs& actionAndArgs) might have some hash collisions for non-equal inputs but each of its callers assumes that InternalActionID being equal means the ActionAndArgs is equal. That's not caused by this PR, though.

@carlos-zamora
Copy link
Member Author

This can't be serviced to Stable as that code doesn't exist in Stable. Be careful with those servicing tags...

My bad. Wasn't sure if ActionMap was in stable or not. Removing the tag.

@ghost ghost removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jun 7, 2021
@carlos-zamora carlos-zamora removed the zStable-Service-Queued-1.12 A floating label that tracks the current Stable version for servicing purposes. label Jun 7, 2021
@carlos-zamora
Copy link
Member Author

It seems static InternalActionID Hash(const Model::ActionAndArgs& actionAndArgs) might have some hash collisions for non-equal inputs but each of its callers assumes that InternalActionID being equal means the ActionAndArgs is equal. That's not caused by this PR, though.

Yeah. This is where I wish I took a course on cryptography and hashing in college haha. I don't think that theoretically there could be no collisions. That said, in practice, we haven't seen any so we're probably fine for now.

Copy link
Member

@lhecker lhecker left a comment

Choose a reason for hiding this comment

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

Long term a more robust equality-solution would be nice, but that's for another day.
Until then: LGTM.

src/cascadia/TerminalSettingsModel/ActionMap.cpp Outdated Show resolved Hide resolved
@carlos-zamora carlos-zamora added the Needs-Second It's a PR that needs another sign-off label Jun 9, 2021
#define ON_ALL_ACTIONS_WITH_ARGS(action) \
case ShortcutAction::action: \
/* If it does, hash the default values for the args.*/ \
hashedArgs = gsl::narrow_cast<size_t>(make<action##Args>().Hash()); \
Copy link
Member

Choose a reason for hiding this comment

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

I low-key hate that this requires constructing an entire object filled with default values to calculate a hash; it could be wildly expensive. Can you profile to make sure we're not making a bunch of objects just to destroy them moments later? Both CPU and memory-wise.

Copy link
Member

Choose a reason for hiding this comment

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

I feel like we could get around this by maintaining a static .Empty singleton of each of these if this is what we need to Hash... or cache the hash of the empty one as a singleton...

Copy link
Member

@lhecker lhecker Jun 10, 2021

Choose a reason for hiding this comment

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

To be fair though none of the ActionArgs constructors are complex at the moment.

As such I "propose" the following solution:

  • add constexpr to the default constructors of all ActionArgs classes
  • add constexpr to all their Hash() member functions
  • add constexpr to the getters of the ACTION_ARG and WINRT_PROPERTY macros
  • write:
    case ShortcutAction::action:
        hashedArgs = implementation::action##Args{}.Hash();
        break;

This will probably compile to constants in release mode and in debug mode it won't allocate anything on the heap.

Copy link
Member

Choose a reason for hiding this comment

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

That will be illegal because we're not using winrt::make for something that's known to be a runtimeclass.

@ghost ghost added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jun 10, 2021
@carlos-zamora carlos-zamora force-pushed the dev/cazamor/bugfix/hash-hidden-args branch from 03dd81d to a5e6d46 Compare June 10, 2021 18:30
@ghost ghost removed the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jun 10, 2021
@carlos-zamora carlos-zamora force-pushed the dev/cazamor/bugfix/hash-hidden-args branch from a5e6d46 to d83a764 Compare June 10, 2021 21:10
@carlos-zamora
Copy link
Member Author

Added EmptyHash() which lazy loads and caches the hash value of a default constructed action args.

Took the average of 5 perf traces in VS:

Allocations (delta) Heap Size (delta)
main 70511.2 13431.52
d83a764 71268 13472.34

With this change, we see an average increase of 756.8 Allocations and 70.82 Heap Size.

Scenario:

  • First snapshot taken on first ActionMap::AddAction
  • Second snapshot taken after Windows Terminal loads and the Command Palette is opened

These hashes are always first used in AddAction, but then they are used a few times in ActionMap (i.e. adding more actions, action comparison, action queries, etc...).

Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
@DHowett DHowett added the AutoMerge Marked for automatic merge by the bot when requirements are met label Jun 16, 2021
@ghost
Copy link

ghost commented Jun 16, 2021

Hello @DHowett!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit b3b6484 into main Jun 16, 2021
@ghost ghost deleted the dev/cazamor/bugfix/hash-hidden-args branch June 16, 2021 22:43
@DHowett DHowett removed the zPreview-Service-Queued-1.13 A floating label that tracks the current Preview version for servicing purposes. label Jul 7, 2021
DHowett pushed a commit that referenced this pull request Jul 7, 2021
)

## Summary of the Pull Request
#10297 found a bug in `ActionMap` where the `ToggleCommandPalette` key chord could not be found using `GetKeyBindingForAction`.

This was caused by the following:
- `AddAction`: when adding the action, the `ActionAndArgs` is basically added as `{ToggleCommandPalette, ToggleCommandLineArgs{}}` (Note the default ctor used for the action args)
- `GetKeyBindingForAction`: we're searching for an `ActionAndArgs` structured as `{ToggleCommandPalette, nullptr}`
- Since these are _technically_ two different actions, we are unable to find it.

This issue was fixed by making the `Hash(ActionAndArgs)` function smarter! If the `ActionAndArgs` has no args, but the `ShortcutAction` _supports_ args, generate the args using the default ctor.

By making `Hash()` smarter, everybody benefits from this logic! We can basically now enforce that `ActionAndArgs{ <X>, nullptr } == ActionAndArgs{ <X>, <default_ctor> }`.

## Validation Steps Performed
- Added a test.
- Tested this on #10297's branch and this does fix the bug

(cherry picked from commit b3b6484)
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
AutoMerge Marked for automatic merge by the bot when requirements are met Needs-Second It's a PR that needs another sign-off
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants