Skip to content

Commit f20f794

Browse files
committed
test: Implement TestContextCancelQueryWhileScan
1 parent c0f6b44 commit f20f794

File tree

1 file changed

+57
-0
lines changed

1 file changed

+57
-0
lines changed

driver_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2938,3 +2938,60 @@ func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
29382938
// This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value()
29392939
})
29402940
}
2941+
2942+
// TestRawBytesAreNotModified checks for a race condition that arises when a query context
2943+
// is canceled while a user is calling rows.Scan. This is a more stringent test than the one
2944+
// proposed in https://github.com/golang/go/issues/23519. Here we're explicitly closing the
2945+
// context ourselves (instead of letting the query timeout) and using `sql.RawBytes` to check
2946+
// the contents of our internal buffers. In theory, the `sql.RawBytes` should be invalidated
2947+
// after cancellation, but we can still check them to ensure we never modify the underlying
2948+
// storage.
2949+
func TestRawBytesAreNotModified(t *testing.T) {
2950+
const blob = "0123456789abcdef"
2951+
const contextRaceIterations = 10
2952+
const blobSize = 64 * 1024
2953+
const insertRows = 64
2954+
2955+
largeBlob := strings.Repeat(blob, blobSize/len(blob))
2956+
2957+
runTests(t, dsn, func(dbt *DBTest) {
2958+
dbt.mustExec("CREATE TABLE test (id int, value MEDIUMBLOB) CHARACTER SET utf8")
2959+
for i := 0; i < insertRows; i++ {
2960+
dbt.mustExec("INSERT INTO test VALUES (?, ?)", i+1, largeBlob)
2961+
}
2962+
2963+
for i := 0; i < contextRaceIterations; i++ {
2964+
func() {
2965+
ctx, cancel := context.WithCancel(context.Background())
2966+
defer cancel()
2967+
2968+
rows, err := dbt.db.QueryContext(ctx, `SELECT id, value FROM test`)
2969+
if err != nil {
2970+
t.Fatal(err)
2971+
}
2972+
2973+
var b int
2974+
var raw sql.RawBytes
2975+
for rows.Next() {
2976+
if err := rows.Scan(&b, &raw); err != nil {
2977+
t.Fatal(err)
2978+
}
2979+
2980+
before := string(raw)
2981+
// Cancelling the query here will invalidate the contents of `raw`, because we're
2982+
// closing the `Rows` explicitly. We would still like to support this without corrupting
2983+
// data, however, so we ensure that `raw` remains valid _through close_ to prevent future
2984+
// regressions
2985+
cancel()
2986+
time.Sleep(5 * time.Millisecond)
2987+
after := string(raw)
2988+
2989+
if before != after {
2990+
t.Fatal("the backing storage for sql.RawBytes has been modified")
2991+
}
2992+
}
2993+
rows.Close()
2994+
}()
2995+
}
2996+
})
2997+
}

0 commit comments

Comments
 (0)