@@ -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