-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Improve buffer handling #890
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
Conversation
I will not review PRs until 1.4.1 is released, but...
Could you elaborate about "unnecessary buffer reallocation and invalid assignment"? |
In a number of areas the use of With regards to the "invalid assignment" in the case of Does that answer your question @methane ? |
Have you really checked allocation? We profiled allocations and confirmed minimum allocation
Could you demonstrate the panic by code? This pull request doesn't have test code demonstrates issues fixed by it. |
Sorry I don't understand your question there?
Not going to be the easiest thing to do in a real scenario, but I will give it a shot purely internally which should be easier. |
You didn't point out where unnecessary reallocation happens actually. |
In other words, when I think this PR introduces unnecessary complexity. |
Nope it was more the case that the buffers This was caused by writeExecutePacket append. This was identified by an internal prototype so it's possible this is actually impossible to trigger, so I'm trying to create an reproduction example as requested. |
Ah, nice catch. |
There are a number reasons which spring to mind:
As a final point, which this currently doesn't fix, I would even go so far as Just noticed I didn't mention this also fixes the use of uninitialised values within |
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 really good in general! Thanks!
buffer.go
Outdated
// Round up to the next multiple of the default size | ||
newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize) | ||
copy(newBuf, b.buf) | ||
b.buf = newBuf | ||
} else if need > len(b.buf) { |
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.
This could be nested instead. need > cap(b.buf)
can only be true if also need > len(b.buf)
is true.
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.
If I follow where you're going, it will result in an unneeded comparison in the common case len
== cap
Is this what you're thinking of?
if need > len(b.buf) {
if need > cap(b.buf) {
// create larger buffer
} else {
// expand existing buffer
}
}
I personally find less nesting is clearer and simpler to reason about.
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, except for that it makes an even more common case cheaper: need <= len(b.buf)
packets.go
Outdated
if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) { | ||
maskLen, typesLen := (len(args)+7)/8, 1+2*len(args) | ||
l := pos + maskLen + typesLen | ||
if l > cap(data) { |
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.
same here
If you can demonstrate an issue (e.g. the aforementioned panic) with the current code, we should consider to include this in v1.4.1, otherwise this should be merged only after the bugfix release. |
Closing until I can find a reproduction case. |
No need to close. This is a great PR and we will merge it sooner or later |
Only |
Unfortunately that's not the case even in |
And you make it worse. Please split this PR. You changed too many things at once. |
See #892 which keeps len=cap instead of managing both of len and cap separately. |
Lines 1075 to 1080 in 369b5d6
If you want to check "smaller buffer must not be set" explicitly, I think #892 is small enough |
Looks like we both had very similar ideas around the direction @methane I've updated this PR, taking into account everyone's feedback. Hope this is moving in the right direction. I've currently kept it all in one PR, as all the changes are buffer related, but I can split it up if people think that's required? |
Two added benchmarks are not make sense to me. |
Removed |
I'm not happy with Now callers doesn't know what is the err. |
buffer.go
Outdated
@@ -49,7 +49,7 @@ func (b *buffer) fill(need int) error { | |||
// grow buffer if necessary | |||
// TODO: let the buffer shrink again at some point | |||
// Maybe keep the org buf slice and swap back? | |||
if need > len(b.buf) { | |||
if need > cap(b.buf) { |
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.
Keep using len
here. Read()
uses len
, not cap
.
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.
But the decision to scale the buffer should always cap
based not len
based, which is one of the reasons I explicitly called out the fact that for buf len == cap
I did have the code which expanded len
as a second case but this would never hit due to the 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.
I know it. But no reason to use cap here.
Len is consistent with Read.
I'm missing some context here, it looked like it was logging the error as this is an implementation bug and the original error was being lost hence needed to be logged vs returned. Is that not the case? |
I didn't mean don't log anywhere, I just meant move logging code. But I noticed error log prints filename and lineno. Where Now I prefer not adding |
Logging at all seems questionable, could you explain why its there / what its achieving?
It adds is visibility and the ability to verify code correctness. The current behaviour is totally unexpected, by which I mean the caller needs to know that the function can return nil and has to deal with that and its not mentioned anywhere. Adding an error return makes very clear to any consumer that the call can fail. In addition it can be used by linters such errcheck to improve code quality by identifying missing validation, which would have ensured the other missing check for nil never happened. |
I know them at first. But takeBuffer is special. The caller should know they should log the error.
Existing callers had code comment. I agree it may not enough for new contributors. But just adding comment is enough.
Again, caller should log the error, and errcheck can't find missing logging for it. That's why I suggested logging to buffer.go at first. If logging code is moved to buffer.go, takeBuffer can be typical function. But you didn't and keep takeBuffer special function caller should know how it works.
takeBuffer fails only when very unexpected cases:
The logging code is a gentle version of I have idea to improve this checking and that's why I prefer not adding error return. |
I'm not good at English. Discussion about high level design with you is much hard and Change the cap -> len I pointed before. It's only thing you should do before merge. |
* Eliminate redundant size test in takeBuffer. * Change buffer takeXXX functions to return an error to make it explicit that they can fail. * Add missing error check in handleAuthResult. * Add buffer.store(..) method which can be used by external buffer consumers to update the raw buffer. Also: * Fix some typo's and unnecessary UTF-8 characters in comments. * Improve buffer function docs. * Add comments to explain some non-obvious behaviour around buffer handling. * Add myself / company to AUTHORS.
Your English is great! I very much appreciate you taking the time to talk about this and would gladly help with changes around logging if you would like?
Done |
cap(..)
, which is the expected standard for a slice resizing, for all buffer expansion checks.handleAuthResult
.buffer.store(..)
method, used by external buffer consumers to update the raw buffer if they expand it.Also:
Checklist