-
Notifications
You must be signed in to change notification settings - Fork 1k
Fix actual master and add -race
in CI
#907
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
e112011
fix a linter warnings
atercattus 2d7f728
fix a datarace in tests
atercattus 2bb8fe3
Merge branch 'master' of github.com:go-mysql-org/go-mysql into fix-ac…
lance6716 e3476d9
add `-race`
lance6716 f6e046b
fix UT
lance6716 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 |
---|---|---|
|
@@ -10,15 +10,18 @@ import ( | |
"reflect" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"sync/atomic" | ||
"testing" | ||
"time" | ||
|
||
"github.com/go-mysql-org/go-mysql/client" | ||
"github.com/go-mysql-org/go-mysql/mysql" | ||
"github.com/go-mysql-org/go-mysql/server" | ||
"github.com/pingcap/errors" | ||
"github.com/siddontang/go/log" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/go-mysql-org/go-mysql/client" | ||
"github.com/go-mysql-org/go-mysql/mysql" | ||
"github.com/go-mysql-org/go-mysql/server" | ||
) | ||
|
||
var _ server.Handler = &mockHandler{} | ||
|
@@ -32,15 +35,19 @@ type testServer struct { | |
|
||
type mockHandler struct { | ||
// the number of times a query executed | ||
queryCount int | ||
queryCount atomic.Int32 | ||
modifier *sync.WaitGroup | ||
} | ||
|
||
func TestDriverOptions_SetRetriesOn(t *testing.T) { | ||
log.SetLevel(log.LevelDebug) | ||
srv := CreateMockServer(t) | ||
defer srv.Stop() | ||
var wg sync.WaitGroup | ||
srv.handler.modifier = &wg | ||
wg.Add(3) | ||
|
||
conn, err := sql.Open("mysql", "[email protected]:3307/test?readTimeout=1s") | ||
conn, err := sql.Open("mysql", "[email protected]:3307/test?readTimeout=100ms") | ||
defer func() { | ||
_ = conn.Close() | ||
}() | ||
|
@@ -52,17 +59,21 @@ func TestDriverOptions_SetRetriesOn(t *testing.T) { | |
// we want to get a golang database/sql/driver ErrBadConn | ||
require.ErrorIs(t, err, sqlDriver.ErrBadConn) | ||
|
||
wg.Wait() | ||
// here we issue assert that even though we only issued 1 query, that the retries | ||
// remained on and there were 3 calls to the DB. | ||
require.Equal(t, 3, srv.handler.queryCount) | ||
require.EqualValues(t, 3, srv.handler.queryCount.Load()) | ||
} | ||
|
||
func TestDriverOptions_SetRetriesOff(t *testing.T) { | ||
log.SetLevel(log.LevelDebug) | ||
srv := CreateMockServer(t) | ||
defer srv.Stop() | ||
var wg sync.WaitGroup | ||
srv.handler.modifier = &wg | ||
wg.Add(1) | ||
|
||
conn, err := sql.Open("mysql", "[email protected]:3307/test?readTimeout=1s&retries=off") | ||
conn, err := sql.Open("mysql", "[email protected]:3307/test?readTimeout=100ms&retries=off") | ||
defer func() { | ||
_ = conn.Close() | ||
}() | ||
|
@@ -73,9 +84,10 @@ func TestDriverOptions_SetRetriesOff(t *testing.T) { | |
// we want the native error from this driver implementation | ||
require.ErrorIs(t, err, mysql.ErrBadConn) | ||
|
||
wg.Wait() | ||
// here we issue assert that even though we only issued 1 query, that the retries | ||
// remained on and there were 3 calls to the DB. | ||
require.Equal(t, 1, srv.handler.queryCount) | ||
require.EqualValues(t, 1, srv.handler.queryCount.Load()) | ||
} | ||
|
||
func TestDriverOptions_SetCollation(t *testing.T) { | ||
|
@@ -153,7 +165,7 @@ func TestDriverOptions_ReadTimeout(t *testing.T) { | |
srv := CreateMockServer(t) | ||
defer srv.Stop() | ||
|
||
conn, err := sql.Open("mysql", "[email protected]:3307/test?readTimeout=1s") | ||
conn, err := sql.Open("mysql", "[email protected]:3307/test?readTimeout=100ms") | ||
defer func() { | ||
_ = conn.Close() | ||
}() | ||
|
@@ -309,7 +321,13 @@ func (h *mockHandler) UseDB(dbName string) error { | |
} | ||
|
||
func (h *mockHandler) handleQuery(query string, binary bool, args []interface{}) (*mysql.Result, error) { | ||
h.queryCount++ | ||
defer func() { | ||
if h.modifier != nil { | ||
h.modifier.Done() | ||
} | ||
}() | ||
|
||
h.queryCount.Add(1) | ||
ss := strings.Split(query, " ") | ||
switch strings.ToLower(ss[0]) { | ||
case "select": | ||
|
@@ -327,7 +345,7 @@ func (h *mockHandler) handleQuery(query string, binary bool, args []interface{}) | |
}, binary) | ||
} else { | ||
if strings.Contains(query, "slow") { | ||
time.Sleep(time.Second * 5) | ||
time.Sleep(time.Second) | ||
} | ||
|
||
var aValue uint64 = 1 | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 don't understand why we have this race. (Although I can reproduce it with
-race
test in my local environment.)In the unit test (I choose
TestDriverOptions_SetRetriesOff
), the happened-before relation should beconn.QueryContent
starts ->mockHandler.handleQuery
modifiesh.queryCount
->conn.QueryContent
ends ->TestDriverOptions_SetRetriesOff
readssrv.handler.queryCount
. I need more time to check it 😢Because even if we use atomic variable to avoid race on this variable, the race of testing logic will let the check fail if atomic.Load happens before atomic.Add
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'll look into this more closely too.
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 think we should stop the mock server before reading the queryCount:
What do you think about 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.
With the original
int
variable, I see the data race is still reported even if we manually stop the mock server. So it's likely that with atomic variables the Load may still happen before AddSorry I'm oncall this week, I think I can start to locate the problem in next week.
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.
We have unsynchronized access to this
int
from different goroutines that are created here:TestDriverOptions_SetRetriesOn sends 3 queries, and we panic on the second.
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.
Oh I have understood now. We have set
readTimeout
for the testsTestDriverOptions_SetRetriesOn
andTestDriverOptions_SetRetriesOff
. So whenconn.QueryContext
returns it's caused by the timeout, rather than receieved the response. It means whenconn.QueryContext
returns(*mockHandler).handleQuery
may not be executed to generate the response.I was mistakenly thought that there's a happened-before relation of
h.queryCount++
->(*mockHandler).handleQuery
finished -> mock server write response ->conn.QueryContext
read the response and return -> testing goroutine readssrv.handler.queryCount
. But it's not true because the returning ofconn.QueryContext
is not caused by getting a response. So the write and read ofqueryCount
indeed has no happened-before relation which is a data race.And I suggest this fix
We add synchronization and still use
int
. It does not rely on time interval 1 second (readTimeout
) or 5 seconds (we inject the sleep in around line 345 of driver_options_test.go). And we can reduce these two values now.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.
In this case, we don't need
int
, becauseWaitGroup.Wait()
will hang until the required number of requests.The only case is if there are more retries than necessary (eg.
h.queryCount==4 and wg.Add(3)
). But here will we get unsynchronized access?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 agree we can remove the
int
now. And maybe add-race
to ci.yml to find other unsynchronized access and fail that PR.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.
Ok. I'll update this PR
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.
Hi, if you are busy (not response in a few days) I can update this PR.