-
Notifications
You must be signed in to change notification settings - Fork 84
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
fix: correctly handle empty family labels #175
fix: correctly handle empty family labels #175
Conversation
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.
Thank you @tylerlevine for reporting and providing a patch!
let family = | ||
Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10))); | ||
registry.register("my_histogram", "My histogram", family.clone()); |
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.
Just to make sure we are on the same page, you don't need to wrap a Histogram
in a Family
.
let family = | |
Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10))); | |
registry.register("my_histogram", "My histogram", family.clone()); | |
let histogram = Histogram::new(exponential_buckets(1.0, 2.0, 10)); | |
registry.register("my_histogram", "My histogram", historgam.clone()); |
This should fix the encoding failure, correct?
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.
Ah - this makes more sense now. I didn't realize that the Family
wrapper was optional. Using the histogram directly indeed does fix the encoding failure.
src/encoding/text.rs
Outdated
registry.register("my_histogram", "My histogram", family.clone()); | ||
|
||
family | ||
.get_or_create(&()) |
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.
In what use-case would you want a Family
with a ()
as a label set?
To remove this footgun, shall we remove the EncodeLabelSet
implementation for ()
?
Lines 352 to 356 in 23c31bd
impl EncodeLabelSet for () { | |
fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { | |
Ok(()) | |
} | |
} |
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.
Now that I understand that the Family
wrapper is optional, I agree that using ()
as the label set doesn't make much sense.
However, when I originally ran into this issue I was using an empty struct with the #[derive(EncodeLabelSet)]
attribute, which has the same erroneous behavior. I was intending to go back and add any needed labels to the struct later but noticed this issue before I could do that. I switched to ()
when I was simplifying my code for posting here.
Removing the EncodeLabelSet
implementation from ()
seems like a good start, but ideally a fix would cover the empty struct case as well.
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 took a look at removing the EncodeLabelSet
implementation for ()
, but it looks like that impl is relied on in at least these four places:
client_rust/src/encoding/text.rs
Line 189 in 1f3923a
self.encode_labels::<()>(None)?; |
client_rust/src/encoding/text.rs
Line 213 in 1f3923a
self.encode_labels::<()>(None)?; |
client_rust/src/encoding/text.rs
Line 269 in 1f3923a
self.encode_labels::<()>(None)?; |
client_rust/src/encoding/text.rs
Line 276 in 1f3923a
self.encode_labels::<()>(None)?; |
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 would assume that we can replace ()
with a private uninhabited type that implements EncodeLabelSet
. Something along the lines of:
enum Never {}
impl EncodeLabelSet for Never {
// ...
}
// ...
// Then in the above cases:
self.encode_labels::<Never>(None)?;
Want to give this a shot @tylerlevine ?
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 idea. Yes, let me give it a try.
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.
Friendly ping @tylerlevine. What do you think of the suggestion above?
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.
@mxinden Looks like that works fine. I cleaned up a bit and updated the PR.
} | ||
|
||
#[test] | ||
fn encode_histogram_family_with_empty_struct_family_labels() { |
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.
Added this second test to give a more realistic use case of empty family labels.
cf8671e
to
baadd90
Compare
src/encoding/text.rs
Outdated
if !self.const_labels.is_empty() || additional_labels.is_some() { | ||
self.writer.write_str(",")?; | ||
let mut string_writer = String::new(); | ||
labels.encode(LabelSetEncoder::new(&mut string_writer).into())?; | ||
|
||
if !string_writer.is_empty() { | ||
if !self.const_labels.is_empty() || additional_labels.is_some() { | ||
self.writer.write_str(",")?; | ||
} | ||
self.writer.write_str(string_writer.as_str())?; | ||
} | ||
|
||
labels.encode(LabelSetEncoder::new(self.writer).into())?; |
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.
Sorry for only raising this now.
This change is not ideal, as it doubles the allocations required for self.family_labels
, i.e. adds unnecessary allocations to the happy path.
I see multiple ways to still remove the bug (i.e. an invalid dangling ,
) without the allocation:
- Fail to derive
EncodeLabelSet
on an emptystruct
. This is a partial fix only as one could still go through e.g. aVec<(String, String)>
. - Wrap
self.writer
with a thin wrapper, that simply slips in a,
in case any of itsstd::fmt::Write
methods are called. In other words that adds a,
at very first in casefamily_labels
actually attempts to write anything.
Does the above make sense? I am in favor of (2). What do you think?
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.
Yes, that makes perfect sense to me - thanks for the suggestions. Apologies for my inactivity on this PR, but I'll go for option 2 once I get some time to work on this again. The approach seems pretty straightforward to me.
Friendly ping @tylerlevine. |
372291e
to
f4af9cd
Compare
Signed-off-by: Tyler Levine <[email protected]>
…te uninhabited type Signed-off-by: Tyler Levine <[email protected]>
Signed-off-by: Tyler Levine <[email protected]>
f4af9cd
to
8f1b569
Compare
Thanks for the ping @mxinden - I've updated the PR to implement suggestion 2 from your earlier 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.
Looks good to me. Thank you!
Just missing a changelog entry, otherwise this is good to go.
Merged through #224. Thank you @tylerlevine for the work! |
No description provided.