-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Expose codec level stream ID to Envoy::Http::Stream and Envoy::StreamInfo::StreamIdProvider #42704
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
base: main
Are you sure you want to change the base?
Conversation
|
/assign @tyxia |
|
/assign @abeyad |
3f118a3 to
4ca721c
Compare
abeyad
left a comment
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.
thanks Reuben! LGTM, just a couple minor nits/questions
| return value; | ||
| } | ||
|
|
||
| absl::optional<uint32_t> StreamIdProviderImpl::getCodecStreamId() const { return absl::nullopt; } |
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.
why is this implementation returning nullopt? where would this be invoked from?
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.
Good question. In OSS, this StreamIdProvider implementation is used for TCP and UDP proxy sessions which do not have codec level stream ids. In G3 it's used for another thing that also doesn't have a codec level stream id.
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.
Given that getCodecStreamId is only implemented and used in HttpStreamIdProviderImpl, can this function change to be a local member function of HttpStreamIdProviderImpl class?
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.
We could do that but then callers who only have a StreamInfo would have to check if their instance of StreamIdProvider is an HttpStreamIdProviderImpl and then cast it appropriately. This is error-prone and defeats the purpose of the interface only to save a few no-op implementations.
That said, I don't feel strongly about this. @abeyad, do you have an opinion?
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 keeping as an interface method is better. Would rather not have casting if we are passing around a StreamIdProvider in the code.
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.
My point is if this function is only being used by one particular inherited class and remains unused by all other subclasses, then it is probably not a good candidate for interface function. Because it defeats the principle of Object-Oriented design : Pollution of the Base Class, Defeating Polymorphism and Liskov Substitution Principle (LSP)
dynamic_cast is one option that is designed for safe downcasting. Alternatively, we can expose stream_id to class StreamInfo directly rather than StreamIdProvider (like draft here #41913), we can avoid all those no-ops.
I don't have strong opinion if you both think current approach is a good, but i want to be a bit cautious here as it touches Envoy core code/interface
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.
remains unused by all other subclasses / all those no-ops.
There are only two implementations of StreamIdProvider: HttpStreamIdProvider and StreamIdProviderImpl so "all the other subclasses" is literally one class.
Pollution of the Base Class / one particular inherited class
My OO experience is in Java but when I hear "base class" I think of a class that has implementation in it maybe with some functions that are either overridden by or implemented in the concrete subclasses. StreamIdProvider does not have implementation in it, it only has pure virtual functions so it's an interface rather than a base class. That interface has only two implementations and I don't think adding a single function to an interface with two functions could really be considered "pollution".
Defeating Polymorphism
This isn't a well known anti-pattern so I don't know exactly what you mean by this but, in my experience, depending on implementation details is actually what defeats polymorphism (i.e. requiring callers to check if their StreamIdProvider instance is an HttpStreamIdProvider, casting and calling a member function unique to an implementation).
Liskov Substitution Principle (LSP)
Most of the time when I've heard LSP brought up as an argument against a polymorphic function it's when there is a true base class with implementation and one of methods overriden by the subclass causes a base class method to become incorrect or clunky. To be clear, there is no implementation in StreamIdProvider so I don't think this concept applies.
dynamic_cast is one option that is designed for safe downcasting.
Yes, this is an option but requires that we make HttpStreamIdProviderImpl public, it's currently private and IMO doesn't need to be made public.
i want to be a bit cautious here as it touches Envoy core code/interface
Yes, this is a fair point. Is there someone else you would recommend to give their opinion? I can add them as a reviewer here too.
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 StreamIdProvider does have implementation : The code here has function declaration and function definition/implementation ---- {return absl::nullopt;}
Basically ,getCodecStreamId is only applicable to HttpStreamIdProviderImpl, but it is now polluted to StreamIdProvider, StreamIdProviderImpl class . What does those extra functions/interface bring, especially when this can be avoided them cleanly.
When people read the code, they will ask the same question as Ali asked at the beginning. "why is this implementation returning nullopt? where would this be invoked from? " IMHO, A clean interface design probably should be self-explanatory rather than raising this question.
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.
To preface this, we're splitting hairs over a fairly trivial thing. In the end we just need to decide which nit is more important: StreamInfo's API or adding a function to StreamIdProvider that's not used by one of the subclasses.
What does those extra functions/interface bring, especially when we can avoid them cleanly
"Cleanly" is a subjective idea. To me, it's less clean from an API perspective to add a new getter/setter field for stream id in StreamInfo when it already has [get|set]StreamIdProvider, to you it seems like it's less clean from a code cleanliness perspective to add a function to an interface which is only relevant for one of the two subclasses. I don't really know how we normally decide these matters.
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 am not just from code cleanliness perspective, i am more from API perspective.
Here is stream ID from codec which is not same as StreamIdProvider that was designed for providing string view for logging and tracing and integer view for in modulo, etc. calculations. I am fine with extending it but needing nullopt implementation makes me wondering if it is still good to be extended. I also feel "why is this implementation returning nullopt? where would this be invoked from? " is a very legit question to API design.
But again I don't have very strong opinion if you both feel current interface is good.
|
Ping @abeyad (for after vacation) |
tyxia
left a comment
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.
Thanks for working on this!
Mostly LGTM module some comments.
| return value; | ||
| } | ||
|
|
||
| absl::optional<uint32_t> StreamIdProviderImpl::getCodecStreamId() const { return absl::nullopt; } |
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.
Given that getCodecStreamId is only implemented and used in HttpStreamIdProviderImpl, can this function change to be a local member function of HttpStreamIdProviderImpl class?
|
/wait |
Signed-off-by: Reuben Tanner <[email protected]>
4ca721c to
938fce3
Compare
abeyad
left a comment
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.
Thanks Reuben! You'll need an Envoy maintainer to review, Tianyu is a maintainer so once he approves, it can be merged.
Signed-off-by: Reuben Tanner <[email protected]>
Commit Message: Currently, there is no way to access the codec level stream id from either of these classes and we need it as part of some future work. Each codec implements its own
protocolStreamId. Http/1 always returns nullopt and HTTP/2 + HTTP/3 return the stream id the codec level stream operates on. Rather than add a new field to Envoy::StreamInfo to expose this stream ID, we use the existing StreamIdProvider which has historically provided non-codec level IDs for logging.Additional Description:
Risk Level: Low
Testing: Unit tests
Docs Changes: Yes
Release Notes: Yes
Platform Specific Features: No