55 "fmt"
66 "log/slog"
77 "maps"
8+ "regexp"
89 "slices"
910 "strings"
1011 "sync/atomic"
@@ -35,12 +36,50 @@ type SlotCheckResult struct {
3536 PublicationExists bool
3637}
3738
38- func (c * PostgresConnector ) CreateReplConn (ctx context.Context , env map [string ]string ) (* pgx.Conn , error ) {
39+ type walSenderTimeout struct {
40+ str string
41+ duration time.Duration
42+ isSet bool
43+ }
44+
45+ var walSenderTimeoutNumbersOnly = regexp .MustCompile (`^\d+$` )
46+
47+ func walSenderTimeoutOverride (ctx context.Context , env map [string ]string ) (walSenderTimeout , error ) {
48+ str , err := internal .PeerDBPostgresWalSenderTimeout (ctx , env )
49+ if err != nil {
50+ return walSenderTimeout {}, fmt .Errorf ("failed to get wal_sender_timeout value: %w" , err )
51+ }
52+ if strings .EqualFold (str , "NONE" ) {
53+ return walSenderTimeout {
54+ str : str ,
55+ duration : 0 ,
56+ isSet : false ,
57+ }, nil
58+ }
59+ parseStr := str
60+ if walSenderTimeoutNumbersOnly .MatchString (parseStr ) {
61+ parseStr += "ms" // A bare integer is interpreted as milliseconds, per Postgres
62+ }
63+ duration , err := time .ParseDuration (parseStr )
64+ if err != nil {
65+ return walSenderTimeout {}, fmt .Errorf ("failed to parse wal_sender_timeout value %q: %w" , parseStr , err )
66+ }
67+ if duration < 0 {
68+ return walSenderTimeout {}, fmt .Errorf ("invalid wal_sender_timeout value: %s" , duration )
69+ }
70+ return walSenderTimeout {
71+ str : str ,
72+ duration : duration ,
73+ isSet : true ,
74+ }, nil
75+ }
76+
77+ func (c * PostgresConnector ) CreateReplConn (ctx context.Context , env map [string ]string ) (* pgx.Conn , walSenderTimeout , error ) {
3978 // create a separate connection for non-replication queries as replication connections cannot
4079 // be used for extended query protocol, i.e. prepared statements
4180 replConfig , err := ParseConfig (c .connStr , c .Config )
4281 if err != nil {
43- return nil , fmt .Errorf ("failed to parse connection string: %w" , err )
82+ return nil , walSenderTimeout {}, fmt .Errorf ("failed to parse connection string: %w" , err )
4483 }
4584
4685 replConfig .Config .RuntimeParams ["timezone" ] = "UTC"
@@ -55,32 +94,33 @@ func (c *PostgresConnector) CreateReplConn(ctx context.Context, env map[string]s
5594 replConfig .Config .RuntimeParams ["standard_conforming_strings" ] = "on"
5695 replConfig .DefaultQueryExecMode = pgx .QueryExecModeSimpleProtocol
5796
58- walSenderTimeout , err := internal . PeerDBPostgresWalSenderTimeout (ctx , env )
97+ wst , err := walSenderTimeoutOverride (ctx , env )
5998 if err != nil {
60- return nil , fmt . Errorf ( "failed to get wal_sender_timeout value: %w" , err )
99+ return nil , walSenderTimeout {} , err
61100 }
62- if ! strings . EqualFold ( walSenderTimeout , "NONE" ) {
63- c .logger .Info ("set wal_sender_timeout" , slog .String ("wal_sender_timeout" , walSenderTimeout ))
64- replConfig .Config .RuntimeParams ["wal_sender_timeout" ] = walSenderTimeout
101+ if wst . isSet {
102+ c .logger .Info ("set wal_sender_timeout" , slog .String ("wal_sender_timeout" , wst . str ))
103+ replConfig .Config .RuntimeParams ["wal_sender_timeout" ] = wst . str
65104 } else {
66105 c .logger .Info ("not setting wal_sender_timeout" )
67106 }
68107
69108 conn , err := NewPostgresConnFromConfig (ctx , replConfig , c .Config .TlsHost , c .rdsAuth , c .ssh )
70109 if err != nil {
71110 internal .LoggerFromCtx (ctx ).Error ("failed to create replication connection" , slog .Any ("error" , err ))
72- return nil , fmt .Errorf ("failed to create replication connection: %w" , err )
111+ return nil , walSenderTimeout {}, fmt .Errorf ("failed to create replication connection: %w" , err )
73112 }
74- return conn , nil
113+ return conn , wst , nil
75114}
76115
77116func (c * PostgresConnector ) SetupReplConn (ctx context.Context , env map [string ]string ) error {
78- conn , err := c .CreateReplConn (ctx , env )
117+ conn , wst , err := c .CreateReplConn (ctx , env )
79118 if err != nil {
80119 return err
81120 }
82121 c .replConn = conn
83- go c .runReplConnKeepalive (ctx , 15 * time .Second , c .ReplPing )
122+ c .walSenderTimeout = wst
123+ go c .runReplConnKeepalive (ctx , 15 * time .Second , c .replPing )
84124 return nil
85125}
86126
@@ -89,7 +129,7 @@ func (c *PostgresConnector) SetupReplConn(ctx context.Context, env map[string]st
89129// from the PullRecords stop: (1) between PullRecords calls (2) within
90130// PullRecords when AddRecord blocks on a full record stream.
91131//
92- // While PullRecords holds replLock inside ReceiveMessage, ReplPing 's TryLock
132+ // While PullRecords holds replLock inside ReceiveMessage, replPing 's TryLock
93133// makes it a no-op. This is safe because ReceiveMessage will always run its
94134// own keepalive on timeout.
95135func (c * PostgresConnector ) runReplConnKeepalive (
@@ -111,8 +151,8 @@ func (c *PostgresConnector) runReplConnKeepalive(
111151 }
112152}
113153
114- // ReplPing sends a standby status update so Postgres doesn't terminate the walsender for inactivity.
115- func (c * PostgresConnector ) ReplPing (ctx context.Context ) error {
154+ // replPing sends a standby status update so Postgres doesn't terminate the walsender for inactivity.
155+ func (c * PostgresConnector ) replPing (ctx context.Context ) error {
116156 if c .replLock .TryLock () {
117157 defer c .replLock .Unlock ()
118158 if c .replState != nil {
0 commit comments