TestConnectContextCancelHandlerRace reproduces the data race that occurred when the application-supplied BuildContextWatcherHandler was armed during the connect handshake. If the context was cancelled while connectOne was still running, a handler that reads *PgConn fields (such as CancelRequestConte
(t *testing.T)
| 4894 | // |
| 4895 | // Race originally reported in https://github.com/jackc/pgx/pull/2534. |
| 4896 | func TestConnectContextCancelHandlerRace(t *testing.T) { |
| 4897 | t.Parallel() |
| 4898 | |
| 4899 | for i := range 10 { |
| 4900 | t.Run(fmt.Sprintf("Stress %d", i), func(t *testing.T) { |
| 4901 | t.Parallel() |
| 4902 | |
| 4903 | script := &pgmock.Script{ |
| 4904 | Steps: []pgmock.Step{ |
| 4905 | pgmock.ExpectAnyMessage(&pgproto3.StartupMessage{ProtocolVersion: pgproto3.ProtocolVersion30, Parameters: map[string]string{}}), |
| 4906 | pgmock.SendMessage(&pgproto3.AuthenticationOk{}), |
| 4907 | pgmock.SendMessage(&pgproto3.BackendKeyData{ProcessID: 12345, SecretKey: []byte{1, 2, 3, 4}}), |
| 4908 | pgmockWaitStep(200 * time.Millisecond), |
| 4909 | pgmock.SendMessage(&pgproto3.ReadyForQuery{TxStatus: 'I'}), |
| 4910 | }, |
| 4911 | } |
| 4912 | |
| 4913 | ln, err := net.Listen("tcp", "127.0.0.1:") |
| 4914 | require.NoError(t, err) |
| 4915 | defer ln.Close() |
| 4916 | |
| 4917 | go func() { |
| 4918 | conn, err := ln.Accept() |
| 4919 | if err != nil { |
| 4920 | return |
| 4921 | } |
| 4922 | defer conn.Close() |
| 4923 | _ = conn.SetDeadline(time.Now().Add(5 * time.Second)) |
| 4924 | _ = script.Run(pgproto3.NewBackend(conn, conn)) |
| 4925 | }() |
| 4926 | |
| 4927 | host, port, _ := strings.Cut(ln.Addr().String(), ":") |
| 4928 | connStr := fmt.Sprintf("sslmode=disable host=%s port=%s user=test database=test", host, port) |
| 4929 | config, err := pgconn.ParseConfig(connStr) |
| 4930 | require.NoError(t, err) |
| 4931 | config.BuildContextWatcherHandler = func(conn *pgconn.PgConn) ctxwatch.Handler { |
| 4932 | return &raceProbeHandler{conn: conn} |
| 4933 | } |
| 4934 | config.ConnectTimeout = 5 * time.Second |
| 4935 | |
| 4936 | ctx, cancel := context.WithCancel(context.Background()) |
| 4937 | // Cancel during the 200ms wait between BackendKeyData and ReadyForQuery, |
| 4938 | // after the auth loop has written pid/secretKey. |
| 4939 | time.AfterFunc(50*time.Millisecond, cancel) |
| 4940 | defer cancel() |
| 4941 | |
| 4942 | pgConn, err := pgconn.ConnectConfig(ctx, config) |
| 4943 | if err == nil { |
| 4944 | closeConn(t, pgConn) |
| 4945 | } |
| 4946 | }) |
| 4947 | } |
| 4948 | } |
| 4949 | |
| 4950 | func TestConnectProtocolVersion32(t *testing.T) { |
| 4951 | t.Parallel() |
nothing calls this directly
no test coverage detected