-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Memdb Txn Commit race condition fix (#16871)
* Add a test to reproduce the race condition * Fix race condition by publishing the event after the commit and adding a lock to prevent out of order events. * split publish to generate the list of events before committing the transaction. * add changelog * remove extra func * Apply suggestions from code review Co-authored-by: Dan Upton <[email protected]> * add comment to explain test --------- Co-authored-by: Dan Upton <[email protected]>
- Loading branch information
1 parent
1384b34
commit b85a149
Showing
4 changed files
with
168 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:bug | ||
Fix a race condition where an event is published before the data associated is commited to memdb. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package state | ||
|
||
import ( | ||
"fmt" | ||
"github.com/hashicorp/go-memdb" | ||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
"golang.org/x/sync/errgroup" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func testValidSchema() *memdb.DBSchema { | ||
return &memdb.DBSchema{ | ||
Tables: map[string]*memdb.TableSchema{ | ||
"main": { | ||
Name: "main", | ||
Indexes: map[string]*memdb.IndexSchema{ | ||
"id": { | ||
Name: "id", | ||
Unique: true, | ||
Indexer: &memdb.StringFieldIndex{Field: "ID"}, | ||
}, | ||
"foo": { | ||
Name: "foo", | ||
Indexer: &memdb.StringFieldIndex{Field: "Foo"}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
type TestObject struct { | ||
ID string | ||
Foo string | ||
} | ||
|
||
// This test verify that the new data in a TXN is commited at the time that publishFunc is called. | ||
// To do so, the publish func is mocked, a read on ch1 means that publish is called and blocked, | ||
// ch2 permit to control the publish func and unblock it when receiving a signal. | ||
func Test_txn_Commit(t *testing.T) { | ||
db, err := memdb.NewMemDB(testValidSchema()) | ||
require.NoError(t, err) | ||
publishFunc := mockPublishFuncType{} | ||
tx := txn{ | ||
Txn: db.Txn(true), | ||
Index: 0, | ||
publish: publishFunc.Execute, | ||
} | ||
ch1 := make(chan struct{}) | ||
ch2 := make(chan struct{}) | ||
getCh := make(chan memdb.ResultIterator) | ||
group := errgroup.Group{} | ||
group.Go(func() error { | ||
after := time.After(2 * time.Second) | ||
select { | ||
case <-ch1: | ||
tx2 := txn{ | ||
Txn: db.Txn(false), | ||
Index: 0, | ||
publish: publishFunc.Execute, | ||
} | ||
get, err := tx2.Get("main", "id") | ||
if err != nil { | ||
return err | ||
} | ||
close(ch2) | ||
getCh <- get | ||
case <-after: | ||
close(ch2) | ||
return fmt.Errorf("test timed out") | ||
} | ||
return nil | ||
}) | ||
|
||
publishFunc.On("Execute", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { | ||
close(ch1) | ||
<-ch2 | ||
}).Return(nil) | ||
|
||
err = tx.Insert("main", TestObject{ID: "1", Foo: "foo"}) | ||
require.NoError(t, err) | ||
err = tx.Commit() | ||
require.NoError(t, err) | ||
get := <-getCh | ||
require.NotNil(t, get) | ||
next := get.Next() | ||
require.NotNil(t, next) | ||
|
||
val := next.(TestObject) | ||
require.Equal(t, val.ID, "1") | ||
require.Equal(t, val.Foo, "foo") | ||
|
||
err = group.Wait() | ||
require.NoError(t, err) | ||
|
||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.