fix(storage): rebind postgres tx prepares and guard stale retries - #1051
fix(storage): rebind postgres tx prepares and guard stale retries#1051ddbaron wants to merge 2 commits into
Conversation
Adds connection-backed PostgreSQL coverage for the transactional prepare rebinding and the paired-execution staleness guard: - reproduce the pre-fix failure at the SQL level (SQLSTATE 42601) - assert the reaper UPDATE statements reach the driver rebound to $n - drive MarkStaleExecutions, MarkStaleWorkflowExecutions and RetryStaleWorkflowExecutions end to end so a row stale on both clocks is reaped while a workflow whose paired execution is still fresh survives - cover the guard's status filter (a terminal paired execution does not shield a stale workflow) Live tests are gated on POSTGRES_TEST_URL, refuse non-loopback hosts, and create a throwaway database per test.
|
@santoshkumarradha This closes the gap that came up when #1046 merged. In that thread you noted that on the PostgreSQL backend neither stale reaper runs at all, because What I understood from that:
What I did:
Two pre-existing gaps I deliberately left alone: a Postgres test file sits behind the If you would rather this were done differently, tell me and I will change it. |
📊 Coverage gateThresholds from
✅ Gate passedNo surface regressed past the allowed threshold and the aggregate stayed above the floor. |
📐 Patch coverage gateThreshold: 80% on lines this PR touches vs
✅ Patch gate passedEvery surface whose lines were touched by this PR has patch coverage at or above the threshold. |
Problem
On the PostgreSQL backend the stale cleanup path does nothing. Both reapers return
n=0with:sqlTxembeds*sql.Txand overridesExec,Query, andQueryRowso?placeholders are rebound to$n, but it never overrodePrepareContextorPrepare. A prepared statement created inside a transaction therefore went to the driver with?placeholders intact and failed to prepare on PostgreSQL, which is whyMarkStaleExecutionsandMarkStaleWorkflowExecutionsnever reaped anything in cloud mode.RetryStaleWorkflowExecutionshas a related gap. It selects on the workflow clock alone and runs before the reaper, so once the path works it would start re-dispatching workflows whose paired execution is still active.Change
sqlTx.PrepareContextand the deprecatedsqlTx.Prepare, both rebinding the query before delegating to*sql.Tx, matching whatsqlDatabasealready did.RetryStaleWorkflowExecutionsconsult the pairedexecutionsactivity clock, the same wayMarkStaleWorkflowExecutionsdoes.RowsAffected > 0gating and retry-count semantics are unchanged.Verification
Focused validation passes:
The full control-plane suite was also run. Storage and handlers pass; unrelated pre-existing failures remain in
internal/core/servicesandinternal/packages(environment and path expectations). No unrelated package was changed.Live PostgreSQL
Run against a throwaway PostgreSQL 16 container, loopback only on a dynamically allocated port, per-test throwaway database, container and volume removed afterwards.
The storage suite reads
POSTGRES_TEST_URL. With it set,go test ./internal/storage/ -run TestPostgres -v -count=1gives 18 passed, 0 failed, 1 skipped (the skip is the pre-existing unconditionalTestPostgresStorage_DatabaseCreation). The whole storage package also passes with a live database configured.Evidence that this pins the fix rather than merely passing next to it:
?placeholders straight to PostgreSQL fails withSQLSTATE 42601, the error reported above.$1..$8for the workflow and retry UPDATEs and$1..$6for the execution sync UPDATE.status=timeout), a workflow whose paired execution has fresh activity survives (status=running), the retry guard re-dispatches a genuinely stale workflow (status=pending retry_count=1) and leaves a freshly heartbeating one alone, and the terminal-paired-execution branch still reaps.sqlTx.Prepare*overrides on this branch makes the three new live tests fail with the original error.Known harness gaps left untouched:
coverage_postgres_test.gosits behind theintegrationbuild tag with a hardcoded DSN, andlocks_test.go'ssetupPostgresTestStoragereturns nil unconditionally, so both skip regardless ofPOSTGRES_TEST_URL.go test -racefor this package also aborts in theboltdb/boltv1.3.1 dependency during SQLite initialization, independent of this change.One deployment note: the workflow-side reaper only started consulting the execution clock in #1046, so this fix changes behaviour only on a control plane that includes that change.