-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
stop the deadline timer in Stream.Read and Write #2519
Conversation
@@ -132,6 +132,7 @@ func (s *receiveStream) readImpl(p []byte) (bool /*stream completed */, int, err | |||
} | |||
if deadlineTimer == nil { | |||
deadlineTimer = utils.NewTimer() | |||
defer deadlineTimer.Stop() |
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 will defer until the function returns. Are you sure this won't happen multiple times.
- Alternatively, why is the deadline timer not defined outside the outer loop?
- More generally, why are we looping in the first place instead of reading one frame and returning?
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 will defer until the function returns. Are you sure this won't happen multiple times.
Yes, deadlineTimer
is only set if deadlineTimer == nil
, so it can only happen once.
Alternatively, why is the deadline timer not defined outside the outer loop?
If no deadline is set, there's no need to start a timer. Note that the user might set a deadline concurrently with a Read
call.
More generally, why are we looping in the first place instead of reading one frame and returning?
That's due to the way frames are processed: every time a STREAM frame is received on that stream, the Read
loop wakes up and tries to read data. STREAM frames might arrive out of order though, so we need to repeat this until a STREAM frame at the current read offset is received.
More generally speaking, I wish there was a way to concurrently use a timer. Then I could just set the timer in SetDeadline
and use it in Read
. This whole dance of saving the deadline as a timestamp and then creating and destroying the timer in Read
is only necessary because Go messed up the timer API.
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, got it.
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 are you sure we can't loop on line 107?
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 are you sure we can't loop on line 107?
I don't understand the question. The loop is on line 107.
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.
Basically, either:
- The loop on line 107 is useless.
- We can reach this code multiple times (creating multiple deadline timers).
My suggestion was moving the deadlineTimer
variable declaration above the outer loop because it's currently inside the outer loop.
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.
The use case is the following:
- The user calls
Read
. The call blocks, since there's no data to read and we're waiting for the STREAM frame at the right offset to arrive. - The user then calls
SetReadDeadline
. - After this, the STREAM frame is received (or the deadline expires).
My suggestion was moving the deadlineTimer variable declaration above the outer loop because it's currently inside the outer loop.
I guess you could declare it as a pointer to a timer (so we don't actually have to start a timer when no deadline is set), and then have a defer
that stops the timer if it's != nil
.
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.
It sounds like you're talking about the inner loop. I'm talking about the outer loop.
I guess you could declare it as a pointer to a timer (so we don't actually have to start a timer when no deadline is set), and then have a defer that stops the timer if it's != nil.
That's what you're already doing, isn't it? I'm literally just suggesting that the declaration on line 115 be moved to line 106. If you do that, looping at line 107 won't re-declare a new deadlineTimer
variable, causing you to allocate a new timer on line 134.
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.
That makes sense. And it's consistent with what we do in the sending half of the stream. Thank you!
Codecov Report
@@ Coverage Diff @@
## master #2519 +/- ##
=======================================
Coverage 86.14% 86.14%
=======================================
Files 122 122
Lines 9536 9538 +2
=======================================
+ Hits 8214 8216 +2
Misses 984 984
Partials 338 338
Continue to review full report at Codecov.
|
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.
LGTM!
When a deadline is set on a stream, the
time.Time
is saved in thedeadline
member variable.When
Read
orWrite
are called, anddeadline
is set, we start a timer in order to return these calls on timer expiration. In the common case, deadlines don't expire, andRead
andWrite
calls return before expiration of the timer. We need to make sure to stop this timer, otherwise it will leak.This is the more probable reason for the performance issue reported in ipfs/kubo#7263, as every single
Read
andWrite
call that returns before the deadline leaks a new timer. This will accumulate a lot more timers than the timer leak that happened when closing a session, or when we received a duplicate Initial, which was fixed in #2516.cc @Stebalien