@@ -19,6 +19,7 @@ import (
1919 "io/ioutil"
2020 "log"
2121 "math"
22+ "math/rand"
2223 "net"
2324 "net/url"
2425 "os"
@@ -2938,3 +2939,55 @@ func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
29382939 // This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value()
29392940 })
29402941}
2942+
2943+ // TestContextCancelQueryWhileScan checks for race conditions that arise when
2944+ // a query context is canceled while a user is calling rows.Scan(). The code
2945+ // is based on database/sql TestIssue18429.
2946+ // See https://github.com/golang/go/issues/23519
2947+ func TestContextCancelQueryWhileScan (t * testing.T ) {
2948+ const blob = "0123456789abcdef"
2949+ const contextRaceIterations = 1000
2950+ const milliWait = 5
2951+ const blobSize = 64 * 1024
2952+ const insertRows = 64
2953+
2954+ largeBlob := strings .Repeat (blob , blobSize / len (blob ))
2955+
2956+ runTests (t , dsn , func (dbt * DBTest ) {
2957+ dbt .mustExec ("CREATE TABLE test (id int, value MEDIUMBLOB) CHARACTER SET utf8" )
2958+ for i := 0 ; i < insertRows ; i ++ {
2959+ dbt .mustExec ("INSERT INTO test VALUES (?, ?)" , i + 1 , largeBlob )
2960+ }
2961+
2962+ sem := make (chan bool , 20 )
2963+ var wg sync.WaitGroup
2964+ for i := 0 ; i < contextRaceIterations ; i ++ {
2965+ sem <- true
2966+ wg .Add (1 )
2967+ go func () {
2968+ defer func () {
2969+ <- sem
2970+ wg .Done ()
2971+ }()
2972+
2973+ ctx , cancel := context .WithTimeout (context .Background (), time .Duration (rand .Intn (milliWait ))* time .Millisecond )
2974+ defer cancel ()
2975+
2976+ rows , _ := dbt .db .QueryContext (ctx , `SELECT id, value FROM test` )
2977+ if rows != nil {
2978+ var b int
2979+ var s string
2980+ for rows .Next () {
2981+ if rows .Scan (& b , & s ) == nil {
2982+ if len (s ) != blobSize {
2983+ t .Fatal ("mismatch in read buffer" )
2984+ }
2985+ }
2986+ }
2987+ rows .Close ()
2988+ }
2989+ }()
2990+ }
2991+ wg .Wait ()
2992+ })
2993+ }
0 commit comments