| From: | Tomas Vondra <tomas(at)vondra(dot)me> |
|---|---|
| To: | Zsolt Parragi <zsolt(dot)parragi(at)percona(dot)com>, Ayush Tiwari <ayushtiwari(dot)slg01(at)gmail(dot)com> |
| Cc: | pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | Re: two statement_timeout race conditions |
| Date: | 2026-11-14 23:33:22 |
| Message-ID: | 77d08d0c-e087-4f68-b4df-c8d8d190756e@vondra.me |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
On 11/15/26 00:06, Zsolt Parragi wrote:
> Hello!
>
> I tested this patch a bit, and claude pointed out another corner case:
> lock_timeout firing during COMMIT still leaks into the next command
>
> finish_xact_command() runs the new discard before the commit:
>
> /* cancel active statement timeout after each command */
> disable_statement_timeout();
>
> if (xact_started)
> {
> CommitTransactionCommand();
>
> But commit can still wait for a heavyweight lock: PreCommit_Notify()
> takes the "database 0" lock, and there's no CHECK_FOR_INTERRUPTS()
> after the grant until the end of the commit. If lock_timeout fires
> just as that lock is granted, the COMMIT succeeds and the next command
> fails. Tiny window, so it needs an additional sleep to be reliably
> reproducible.
>
> The same happens with an extended protocol autocommit SELECT
> pg_notify(...) (committed at Sync) followed by any statement, and with
> a pipelined statement after COMMIT.
>
> This seems to fix it:
>
> @@ finish_xact_command
> CommitTransactionCommand();
>
> + /*
> + * Commit itself can wait for a heavyweight lock (PreCommit_Notify),
> + * after disable_statement_timeout() above already ran. Discard a
> + * lock timeout request left behind by a grant at timeout time.
> + */
> + if (get_timeout_indicator(LOCK_TIMEOUT, true))
> + QueryCancelPending = false;
>
Hmmm. I guess that could happen ...
>
> And in 0002:
>
> QueryCancelPending = false;
> + (void) get_timeout_indicator(LOCK_TIMEOUT, true);
> + (void) get_timeout_indicator(STATEMENT_TIMEOUT, true);
> ereport(WARNING,
> (errmsg("canceling wait for synchronous replication due to user request"),
>
> Won't this result in the same incorrect message pattern the patch
> tries to repair elsewhere?
You mean the "statement timeout" reported as "user request"? I believe
that happens because the timeout indicator is cleared, while the
QueryCancelPending is left set. But here we're resetting both, so I
don't see how could it have the same effect.
What's the scenario that'd lead to the same issue?
regards
--
Tomas Vondra
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Zsolt Parragi | 2026-11-14 23:50:47 | Re: two statement_timeout race conditions |
| Previous Message | Zsolt Parragi | 2026-11-14 23:06:35 | Re: two statement_timeout race conditions |