@@ -2938,3 +2938,60 @@ func TestValuerWithValueReceiverGivenNilValue(t *testing.T) {
2938
2938
// This test will panic on the INSERT if ConvertValue() does not check for typed nil before calling Value()
2939
2939
})
2940
2940
}
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