@@ -2938,3 +2938,58 @@ 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 using
2945+ // `sql.RawBytes` to check the contents of our internal buffers are not modified after an implicit
2946+ // call to `Rows.Close`, so Context cancellation should **not** invalidate the backing buffers.
2947+ func TestRawBytesAreNotModified (t * testing.T ) {
2948+ const blob = "abcdefghijklmnop"
2949+ const contextRaceIterations = 10
2950+ const blobSize = 3000
2951+ const insertRows = 4
2952+
2953+ var sqlBlobs = [2 ]string {
2954+ strings .Repeat (blob , blobSize / len (blob )),
2955+ strings .Repeat (strings .ToUpper (blob ), blobSize / len (blob )),
2956+ }
2957+
2958+ runTests (t , dsn , func (dbt * DBTest ) {
2959+ dbt .mustExec ("CREATE TABLE test (id int, value BLOB) CHARACTER SET utf8" )
2960+ for i := 0 ; i < insertRows ; i ++ {
2961+ dbt .mustExec ("INSERT INTO test VALUES (?, ?)" , i + 1 , sqlBlobs [i & 1 ])
2962+ }
2963+
2964+ for i := 0 ; i < contextRaceIterations ; i ++ {
2965+ func () {
2966+ ctx , cancel := context .WithCancel (context .Background ())
2967+ defer cancel ()
2968+
2969+ rows , err := dbt .db .QueryContext (ctx , `SELECT id, value FROM test` )
2970+ if err != nil {
2971+ t .Fatal (err )
2972+ }
2973+
2974+ var b int
2975+ var raw sql.RawBytes
2976+ for rows .Next () {
2977+ if err := rows .Scan (& b , & raw ); err != nil {
2978+ t .Fatal (err )
2979+ }
2980+
2981+ before := string (raw )
2982+ // Ensure cancelling the query does not corrupt the contents of `raw`
2983+ cancel ()
2984+ time .Sleep (5 * time .Millisecond )
2985+ after := string (raw )
2986+
2987+ if before != after {
2988+ t .Fatal ("the backing storage for sql.RawBytes has been modified" )
2989+ }
2990+ }
2991+ rows .Close ()
2992+ }()
2993+ }
2994+ })
2995+ }
0 commit comments