pg tx producer paths wake commit-atomically: publish_with_key_tx/enqueue_tx/outbox_enqueue_tx issue pg_notify on their mechanism-named channel (stream name / queue name / derived backing queue) inside the caller's tx — empty payload, best-effort log-and-swallow, no double-wake on the auto-commit paths; shared tx::wake_tx owner + module-doc section pinning the semantics; new tx_tests harness test pinning pre-commit silence, commit delivery on all three channels, and rollback silence; F-1 engine arm retired in review-wave-4 + implementation.md records (tx_publishes_compose_with_the_handle deterministic: 20 solo runs green; pg suite 116/116 x2 vs harness) (task pg-fix-tx-wake, review 002 Finding 2)
This commit is contained in:
1 parent
7ae426a01d
commit
9a5d1f4705
5 files changed
+261
-15
No files matched your search
@@ -54,6 +54,21 @@
|
||||
//! the same byte string ADR-020 §4 pins as the stored row bytes —
|
||||
//! which is valid UTF-8 by construction.
|
||||
//!
|
||||
//! # The tx wakes
|
||||
//!
|
||||
//! The producer paths (`publish_with_key_tx` — which `publish_tx`
|
||||
//! rides, `enqueue_tx`, `outbox_enqueue_tx`) issue `pg_notify` on the
|
||||
//! mechanism's wake channel (the stream name / the queue name / the
|
||||
//! outbox's derived backing-queue name — the mechanism-name-is-the-
|
||||
//! channel realization) **inside the caller's transaction** ([`wake_tx`]).
|
||||
//! NOTIFY's native transactional delivery makes the wake commit-atomic:
|
||||
//! it delivers at the caller's COMMIT, and a rollback discards the row
|
||||
//! *with* its wake (the tx/no-ghosts rows' pin). Wakes are best-effort
|
||||
//! (ADR-006; the engine-wide posture — a wake must never fail a write):
|
||||
//! a failed statement is logged and swallowed, and since a failed
|
||||
//! statement aborts the tx server-side the truth still surfaces typed
|
||||
//! at COMMIT. `run_once` is a pull op, consumer-driven — no wake there.
|
||||
//!
|
||||
//! # Engine-side arithmetic
|
||||
//!
|
||||
//! The stamp/derivation arithmetic lives in [`crate::resolution`]
|
||||
@@ -260,6 +275,26 @@ fn enqueue_row_sql(schema: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// The in-tx wake statement (empty payload — `Wake { channel }` only,
|
||||
/// ADR-008 §3): issued inside the caller's transaction, so NOTIFY's
|
||||
/// native transactional delivery makes it commit-atomic — delivers at
|
||||
/// the caller's COMMIT, discarded with the row by a rollback. Best-
|
||||
/// effort at the op (the engine-wide posture — a wake must never fail
|
||||
/// a write): a failed statement is logged and swallowed; because a
|
||||
/// failed statement aborts the tx server-side, the truth still
|
||||
/// surfaces typed at COMMIT.
|
||||
async fn wake_tx(client: &tokio_postgres::Client, channel: &str) {
|
||||
if let Err(wake_err) = client
|
||||
.query_one("SELECT pg_notify($1, '')", &[&channel])
|
||||
.await
|
||||
{
|
||||
eprintln!(
|
||||
"alkstore-postgres: tx wake failed for {channel:?} \
|
||||
(best-effort: the durable row lands at the caller's commit): {wake_err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a trait-crossing payload `Value` into the exact stored bytes
|
||||
/// (the serde_json serialization — ADR-020 §4). The notify carriage
|
||||
/// reuses it: the JSON serialization is valid UTF-8 text by
|
||||
@@ -282,8 +317,19 @@ impl TxHandle for PgTxHandle {
|
||||
Box::pin(async move {
|
||||
let client = self.client()?;
|
||||
let bytes = encode_payload_bytes(&payload)?;
|
||||
self.enqueue_row(client, &queue, bytes, &opts, plain_queue_default_stamps())
|
||||
.await
|
||||
let id = self
|
||||
.enqueue_row(client, &queue, bytes, &opts, plain_queue_default_stamps())
|
||||
.await?;
|
||||
// Posture-parity wake (the contract obligation is the
|
||||
// stream path's): pg_notify on the queue's wake channel
|
||||
// inside the caller's tx — commit-atomic like every
|
||||
// in-tx notify, matching SQLite's watcher firing on the
|
||||
// committed write. Best-effort (the queues row's pinned
|
||||
// re-poll safety net covers a gap; a failed statement
|
||||
// aborts the tx server-side, so the truth surfaces typed
|
||||
// at COMMIT).
|
||||
wake_tx(client, &queue).await;
|
||||
Ok(id)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -324,6 +370,14 @@ impl TxHandle for PgTxHandle {
|
||||
)
|
||||
.await
|
||||
.map_err(pg_error)?;
|
||||
// Then wake, inside the caller's tx (the contract
|
||||
// obligation — engine-postgres.md pins `pg_notify` as the
|
||||
// streams wake trigger, and core-contract.md pins "events
|
||||
// never require polling to become visible" with rollback
|
||||
// dropping the event row *with* its wake): the stream's
|
||||
// wake channel, the mechanism-name-is-the-channel
|
||||
// realization — the auto-commit publish's twin.
|
||||
wake_tx(client, &stream).await;
|
||||
Ok(row.get(0))
|
||||
})
|
||||
}
|
||||
@@ -556,8 +610,14 @@ impl TxHandle for PgTxHandle {
|
||||
let client = self.client()?;
|
||||
let bytes = encode_payload_bytes(&payload)?;
|
||||
let queue = outbox_backing_queue_name(&outbox);
|
||||
self.enqueue_row(client, &queue, bytes, &opts, outbox_default_stamps())
|
||||
.await
|
||||
let id = self
|
||||
.enqueue_row(client, &queue, bytes, &opts, outbox_default_stamps())
|
||||
.await?;
|
||||
// The outbox's posture-parity twin of `enqueue_tx`'s wake:
|
||||
// the derived backing-queue name is the channel (the
|
||||
// scheduler's fire-wake targets the same name).
|
||||
wake_tx(client, &queue).await;
|
||||
Ok(id)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user