Skip to content

Conversation

@ravenblackx
Copy link
Contributor

@ravenblackx ravenblackx commented May 29, 2025

Commit Message: Clean up Matcher::MatchResult's unnecessary templates
Additional Description: Since #38726 refactored Matcher impls to recurse inside the match function, there is no longer any need for MatchResults to have the capacity to return sub-match-trees, which means they also don't need to be templates. Cleaning this up makes the code much easier to follow. This may have also caught a few locations where the internal recursion wasn't fully updated correctly (minor negative, it does make one doMatch() implementation slightly more complicated, but the corresponding match() implementation is made much simpler, so still an improvement).
Risk Level: Small, existing tests should be ensuring that behaviors were unchanged.
Testing: Covered by existing tests, which are also cleaner.
Docs Changes: n/a
Release Notes: n/a
Platform Specific Features: n/a

Signed-off-by: Raven Black <ravenblack@dropbox.com>
@repokitteh-read-only
Copy link

As a reminder, PRs marked as draft will not be automatically assigned reviewers,
or be handled by maintainer-oncall triage.

Please mark your PR as ready when you want it to be reviewed!

🐱

Caused by: #39673 was opened by ravenblackx.

see: more, trace.

Signed-off-by: Raven Black <ravenblack@dropbox.com>
Signed-off-by: Raven Black <ravenblack@dropbox.com>
Signed-off-by: Raven Black <ravenblack@dropbox.com>
@ravenblackx ravenblackx marked this pull request as ready for review May 30, 2025 15:02
Signed-off-by: Raven Black <ravenblack@dropbox.com>
@ravenblackx ravenblackx requested a review from kyessenov as a code owner May 30, 2025 17:23
Signed-off-by: Raven Black <ravenblack@dropbox.com>
Signed-off-by: Raven Black <ravenblack@dropbox.com>
@ravenblackx ravenblackx requested a review from adisuissa as a code owner May 30, 2025 19:01
@ravenblackx
Copy link
Contributor Author

/retest

Copy link
Contributor

@keithmattix keithmattix left a comment

Choose a reason for hiding this comment

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

I built a patch off of this branch and it does appear to fix the regression we saw in Istio. Thanks for this!

const auto itr = children_.find(key);
if (itr != children_.end()) {
return {MatchState::MatchComplete, itr->second};
MatchResult result =
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this is the part that fixes the regression we saw in Istio. Could a test be added/adapted to ensure on_no_match for the different tree types works as expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was planning on making a common "matcher tree implementation test" for this purpose that all matcher tree implementations are required to pass, because I agree that it being possible to have this regression was bad. But I would rather add that as a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah that's fair

// The result of a match. There are three possible results:
// - The match could not be completed due to lack of data (InsufficientData)
// - The match was completed, no match found (NoMatch)
// - The match was completed, match found (onMatch() will return the OnMatch).
Copy link
Contributor

Choose a reason for hiding this comment

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

onMatch is no longer present, please update the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

return {MatchState::MatchComplete, absl::nullopt};
return MatchResult::noMatch();
}
if (on_match->matcher_) {
Copy link
Contributor

Choose a reason for hiding this comment

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

on_match could use a variant! then a switch or a visit would make it easier to parse.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but doesn't have to be in this PR. (And that one isn't as big of an improvement since it doesn't get rid of the template. I think it's possible to get rid of the template too, but that's a much more aggressive change.)

Signed-off-by: Raven Black <ravenblack@dropbox.com>
Copy link
Member

@wbpcode wbpcode left a comment

Choose a reason for hiding this comment

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

LGTM overall. This change seems reasonable for me. Only minor comments to the naming, bla, bla...

Comment on lines 134 to 152
struct MatchResult {
private:
struct InsufficientData {};
struct NoMatch {};
using Result = absl::variant<ActionFactoryCb, NoMatch, InsufficientData>;
Result result_;
MatchResult(NoMatch) : result_(NoMatch{}) {}
MatchResult(InsufficientData) : result_(InsufficientData{}) {}

public:
MatchResult(ActionFactoryCb cb) : result_(std::move(cb)) {}
static MatchResult noMatch() { return MatchResult(NoMatch{}); }
static MatchResult insufficientData() { return MatchResult(InsufficientData{}); }
bool isInsufficientData() const { return absl::holds_alternative<InsufficientData>(result_); }
bool isComplete() const { return !isInsufficientData(); }
bool isNoMatch() const { return absl::holds_alternative<NoMatch>(result_); }
bool isMatch() const { return absl::holds_alternative<ActionFactoryCb>(result_); }
ActionFactoryCb actionFactory() const { return absl::get<ActionFactoryCb>(result_); }
ActionPtr action() const { return actionFactory()(); }
Copy link
Member

Choose a reason for hiding this comment

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

nit: we may prefer place the public member at top?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I thought the structs had to be declared before they're used, but apparently not the case in a class declaration. Done.

static MatchResult noMatch() { return MatchResult(NoMatch{}); }
static MatchResult insufficientData() { return MatchResult(InsufficientData{}); }
bool isInsufficientData() const { return absl::holds_alternative<InsufficientData>(result_); }
bool isComplete() const { return !isInsufficientData(); }
Copy link
Member

Choose a reason for hiding this comment

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

I think this isComplete is unncessary.

Copy link
Contributor Author

@ravenblackx ravenblackx Jun 5, 2025

Choose a reason for hiding this comment

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

It's unnecessary but makes it more readable at the user end (!isInsufficientData doesn't describe what the user end cares about in many cases), and also makes it more resilient for future changes - if a fourth state were to be added that also means the match wasn't resolved yet, every location that wrote !isInsufficientData() to mean this would need to update, or if a fourth state was added that also means the match was resolved then every location that wrote isMatch() || isNoMatch() for the same meaning would need to be updated. Having a function for the specific thing that the user cares about means any change can be localized here, and also makes it unambiguous which thing you should use (removing the question "should I use isMatch() || isNoMatch() or should it be !isInsufficientData()?").

Also !isInsufficientData() being a double negative makes it horrible to read. :)

bool isInsufficientData() const { return absl::holds_alternative<InsufficientData>(result_); }
bool isComplete() const { return !isInsufficientData(); }
bool isNoMatch() const { return absl::holds_alternative<NoMatch>(result_); }
bool isMatch() const { return absl::holds_alternative<ActionFactoryCb>(result_); }
Copy link
Member

Choose a reason for hiding this comment

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

isMatch() -> isMatched. To keep same with the FieldMatchResult

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They have different meanings - isMatched() means the result was "it matched", a bool, vs. isMatch() means the result was a match, with an object.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I would be happier to change it to isAction or isActionCb than to isMatched, but I'd rather not change it at all and get it landed so the regression is fixed.)

Copy link
Member

Choose a reason for hiding this comment

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

hmmm, I feel a little weird to these naming and method, but okay.

Signed-off-by: Raven Black <ravenblack@dropbox.com>
Copy link
Member

@wbpcode wbpcode left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks.

@ravenblackx
Copy link
Contributor Author

/retest

@ravenblackx ravenblackx enabled auto-merge (squash) June 5, 2025 18:07
@zirain
Copy link
Member

zirain commented Jun 6, 2025

/retest

@ravenblackx ravenblackx merged commit 2fddebb into envoyproxy:main Jun 6, 2025
25 checks passed
@ravenblackx ravenblackx deleted the matcher_cleanup branch June 6, 2025 14:58
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.

5 participants