-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Throttle.Do does not panic after Finish #1396
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
Throttle.Do does not panic after Finish #1396
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.
Thanks for fixing this @ekalinin . I've added some comments.
Reviewable status: 0 of 2 files reviewed, 4 unresolved discussions (waiting on @ashish-goswami, @ekalinin, @jarifibrahim, and @manishrjain)
batch_test.go, line 128 at r1 (raw file):
// This test ensures we don't panic during flush. func TestFlushPanic(t *testing.T) {
This test can be moved to the y/y_test.go file. You could actually test the throttle type instead of testing the writeBatch.
In your test, you would do
th := NewThrottle(..)
th.Do -> Shouldn't return error
th.Finish -> shouldn't return error
th.Do -> Should return an error
batch_test.go, line 130 at r1 (raw file):
func TestFlushPanic(t *testing.T) { t.Run("after flush", func(t *testing.T) { runBadgerTest(t, nil, func(t *testing.T, db *DB) {
We're opening a new DB here and since we just want to test the throttle type, this would be an overkill :)
y/y.go, line 46 at r1 (raw file):
// ErrDoAfterFinish indicates a pointless Do call after Finish ErrDoAfterFinish = errors.New("Do has no any point after Finish")
Please rephrase the comment and the error message it to
Throttle.Do called after Finish
y/y.go, line 270 at r1 (raw file):
// previously Done workers, it would return it. func (t *Throttle) Do() error { if t.finished {
This would result in a race condition. Two threads can call throttle.Do and throttle.Finish() at the same time. We might need to use an atomic here and do a compare and swap operation.
Something like
Throttle.Finish does
compareAndSwap(finish, 1, 0) => mark as finished. If compareAndSwap returns false, it means
someone called finish for the second time and we should return error (I think think this could
happen because we have sync.Once in finish() function but it's good to add a check.)
Throttle.Do does
if atomic.Load(finish) != 0 { return error }
f1549e0 to
39f1ece
Compare
|
Thanks for comments @jarifibrahim |
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.
Some minor comments. Please address them and this PR is good to go 🎉
Reviewable status: 0 of 2 files reviewed, 2 unresolved discussions (waiting on @ashish-goswami, @ekalinin, and @manishrjain)
y/y.go, line 312 at r2 (raw file):
close(t.ch) close(t.errCh) if !atomic.CompareAndSwapInt32(&t.finished, 0, 1) {
The atomic check would never return false here since it is wrapped inside sync.Do which means this code will be called only once. See this https://play.golang.org/p/OHHqqPClxjb
It should be enough to do a blind update. My previous comment was incorrect. Apologies.
So we can remove the ErrDoubleFinish too.
y/y_test.go, line 265 at r2 (raw file):
require.NoError(t, th.Finish()) require.NotPanics(t, func() { require.Error(t, th.Do())
You can remove the require.NotPanics.
A simple require.Error should be enough.
39f1ece to
9e140e0
Compare
9e140e0 to
638905f
Compare
Done.
Done. |
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.
@ekalinin, consider a scenario where Do() and Finish() are called by different goroutines. In that case, there is a race condition.
Thread 1 passes this check while thread 2 closes the channel and then thread 1 push into closed channel ch.
Try running this scenario using --race flag.
func TestThrottleDoAfterFinish(t *testing.T) {
th := NewThrottle(16)
var wg sync.WaitGroup
for i := 1; i <= 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
err := th.Do()
switch err {
case nil:
th.Done(nil)
case ErrDoAfterFinish:
// pass
default:
panic("Failed test")
}
if i == 5 {
th.Finish()
}
}(i)
}
wg.Wait()
}|
@ekalinin, In the Finish(), you can have an atomic Store operation for |
Thanks for the hint, @NamanJain8! |
|
Hey @ekalinin, we got this PR reviewed internally and there are a few changes needed. @NamanJain8 will make those change and push on this PR. Thank you for helping fix this 🎉 |
Cool! Thanks for help as well :) ! |
…in/badger into fix-panic-at-double-flush
This PR prevents badger from crashing/panicing when Flush() is called after Flush() in writebatch. Instead the second flush now returns ErrCommitAfterFinish.
…deinc#1396) Signed-off-by: thomassong <thomassong2012@gmail.com>
…deinc#1396) Signed-off-by: thomassong <thomassong2012@gmail.com>
…deinc#1396) Signed-off-by: thomassong <thomassong2012@gmail.com>
Fixes #1394
I'm not sure about an error at
Do. Maybe it will be ok to just returnnil.This change is