-
Notifications
You must be signed in to change notification settings - Fork 889
feat: add WaitUntilEmpty to LogSender #12159
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
This stack of pull requests is managed by Graphite. Learn more about stacking. Join @spikecurtis and the rest of your teammates on |
case <-nevermind: | ||
return | ||
} | ||
}() |
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 are we duplicating logic from SendLoop
here? Since this method doesn't attempt to send, it's quite pointless unless the signaling is happening from a running SendLoop
anyway.
Edit: Ah, nevermind, just realized this is only here to handle the user provided context.
I think this could be greatly simplified:
func (l *LogSender) WaitUntilEmpty(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-l.allSent:
return 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.
The problem with an allSent
channel is how to arrange when it should read. Closing the channel won't work, because you can't "unclose" it if more data gets queued.
Writing to the channel won't work if there are more than one caller to WaitUntilEmpty.
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.
True, it would work better/simpler if the send loop was channel-based as well. In that case, one approach could be this:
func (l *LogSender) WaitUntilEmpty(ctx context.Context) error {
wait := make(chan struct{})
l.waitUntilEmpty <- wait
select {
case <-ctx.Done():
return ctx.Err()
case <-wait:
return nil
}
}
// SendLoop
var waiters []chan struct{}
for {
select {
case <-tick:
case <-l.waitUntilEmpty:
waiters = append(waiters, wait)
}
// ...
if len(l.queues) == 0 {
for _, wait := range waiters {
close(wait)
}
waiters = nil
}
But it's not quite as nice when retrofitted into the mutex style 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 problem there is that it requires SendLoop
to actually be running in order for WaitUntilEmpty()
to return, which it might not be but we are still empty.
Channels are great for communicating between goroutines. Here what we really, actually want is to know when a condition is satisfied, regardless of other running goroutines, and for that sync.Cond
is your friend.
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 had a suggestion for changing WaitUntilEmpty
, but I'll leave it up to you whether or not to implement. Works as-is, too, although I feel mutexes are starting to make this service overly complex (vs using channel based communication).
if len(l.queues) == 0 { | ||
return nil | ||
} | ||
return ctx.Err() |
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 len(l.queues) == 0 { | |
return nil | |
} | |
return ctx.Err() | |
return ctx.Err() |
We don't actually need this check, this way we give priority to the context cancellation, even if we happen to be done at the same time (this can be preferable in some cases).
2a2203e
to
900e32a
Compare
f39d152
to
2f52a67
Compare
2f52a67
to
dfe07f9
Compare
Merge activity
|
We'll need this to be able to tell when all outstanding logs have been sent, as part of graceful shutdown.