@@ -2938,3 +2938,56 @@ 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+ ctx , cancel := context .WithCancel (context .Background ())
2965+ rows , err := dbt .db .QueryContext (ctx , `SELECT id, value FROM test` )
2966+ if err != nil {
2967+ t .Fatal (err )
2968+ }
2969+
2970+ var b int
2971+ var raw sql.RawBytes
2972+ for rows .Next () {
2973+ if err := rows .Scan (& b , & raw ); err != nil {
2974+ t .Fatal (err )
2975+ }
2976+
2977+ before := string (raw )
2978+ // Cancelling the query here will invalidate the contents of `raw`, because we're
2979+ // closing the `Rows` explicitly. We would still like to support this without corrupting
2980+ // data, however, so we ensure that `raw` remains valid _through close_ to prevent future
2981+ // regressions
2982+ cancel ()
2983+ time .Sleep (5 * time .Millisecond )
2984+ after := string (raw )
2985+
2986+ if before != after {
2987+ t .Fatal ("the backing storage for sql.RawBytes has been modified" )
2988+ }
2989+ }
2990+ rows .Close ()
2991+ }
2992+ })
2993+ }
0 commit comments