CF7016 Fix LSN of logical decoding message — Review

code-review · generated from canonical Markdown

Type
code-review
Source
postgresql-master
Slug
commitfest-7016-review
Tags
code-reviewcommitfest-7016lsn-logical-messagepg-review-artifact-v2postgresqlreview-report-v2
Updated
2026-08-03T19:12:22.448741+00:00
Verdict / Summaryready-for-committer (test follow-up required)
Front matter
schema: review-report-v2
title: "CF7016 Fix LSN of logical decoding message — Review"
commitfest: 61
patch: 7016
author: Atsushi Torikoshi
canonical_message_id: d99c688994ab3a998afe26e61fe4f69f@oss.nttdata.com
head: 7cb9060dcde08ca63df1f0d701b0674e0e8ae811
diff_sha: 88d6a2831768958d8824f8c59c6feddeaec96889
verdict: "ready-for-committer (test follow-up required)"
blocker_split:
  verified: 0
  unverified: 0
tags:
  - code-review
  - commitfest-7016
  - lsn-logical-message
  - pg-review-artifact-v2
  - postgresql
  - review-report-v2
summary: "The one-line decode.c fix (buf->endptr to buf->origptr) is correct, safe, documented, and supported by pgsql-hackers thread consensus. One test-only MAJOR (F-001) requires strengthening 020_messages.pl to actually assert LSN correctness. Verdict: ready-for-committer (test follow-up required)."

CF7016 — Fix LSN of logical decoding message

Deterministic Verdict

ready-for-committer (test follow-up required)

Scope

Uncommitted diff vs HEAD 7cb9060dcde:

FileChange
src/backend/replication/logical/decode.c line 671buf->endptr → buf->origptr in logicalmsg_decode()
src/test/subscription/t/020_messages.pl line 110Removes lsn = '$message_lsn' AND filter from WHERE clause

The patch makes the LSN reported for pg_logical_emit_message() (the message_lsn exposed to output plugins and the logical replication protocol) equal to the start LSN of the LogicalMessage WAL record (buf->origptr), instead of the end LSN (buf->endptr). All other decoded DML records already use origptr. The change aligns the implementation with both the surrounding code and the existing documentation.

Findings

F-001

(MAJOR, tests) — Test coverage regression

Location: src/test/subscription/t/020_messages.pl:97-99,110

Mechanism: The v2 patch removes the lsn = '$message_lsn' filter from the WHERE clause (line 110) but keeps the $message_lsn variable assignment (lines 97-99) dead/unused. The only remaining assertion for the non-transactional message is is($result, qq(77|0), ...) which checks byte values only — no LSN assertion. The test would pass even if the buf->endptr→buf->origptr fix were reverted, so it does not guard against regression of this specific bug. Yugo Nagata explicitly requested the dead variable cleanup on 2026-06-09; v2 did not address it.

Verification: verified — inspection + reproduction (two-run TAP gate). The patched test (v2) does NOT catch the bug; reverting decode.c to endptr would still pass this test.

Critical correction: The test-author's original premise (asserting LSN equality between pg_logical_emit_message() return and the slot's lsn column) was empirically proven wrong. pg_logical_emit_message() returns XLogInsert() = EndRecPtr (the record end), not origptr (the start). On base code, both the slot and the function return endptr — they match. The fix makes the slot report origptr (start), so the correct oracle is slot_lsn < pg_logical_emit_message()_return, not equality.

Repair packet:

  • Instruction: Capture pg_logical_emit_message() return into $txn_message_lsn (transactional) and $message_lsn (non-transactional). Add (lsn < '$..._lsn'::pg_lsn)::text to the SELECT columns. Assert true|1|pgoutput (transactional) and true|77|0 (non-transactional). Remove the dead lsn = '$message_lsn' AND filter; keep WHERE xid = 0. Both variables are now LIVE.
  • Code example (exact-postimage):

``perl is($result, qq(true|77|0), 'non-transactional message slot LSN is the record start (less than pg_logical_emit_message() return); message is M, flag is non-transactional'); ``

  • Patch (HEAD-bound, unified-diff): sha256 683e7d4b1787e2833eea14a206add2a20f62c737ddc587928dd7f3e13c84b9d0, applies to HEAD 7cb9060dcde08ca63df1f0d701b0674e0e8ae811.
  • Repair validation: passed — two-run TAP gate:
  • Run A (base/endptr + test v3): FAILED 2/5 subtests. Transactional got false|1|pgoutput, expected true|1|pgoutput. Non-transactional got false|77|0, expected true|77|0. Byte checks pass; only LSN wrong — catches the bug.
  • Run B (fixed/origptr + test v3): PASSED 5/5 subtests. Two-run gate satisfied: fail-on-old, pass-on-new.
  • Test example: TAP test in src/test/subscription/t/020_messages.pl. The two is() assertions with the (lsn < $..._lsn::pg_lsn)::text comparison are the oracle. On base (endptr): slot lsn == emit return, so lsn < emit_return is false → FAIL. On fixed (origptr): slot lsn = origptr < endptr = emit return, so lsn < emit_return is true → PASS.
F-002

(MINOR, doc) — Missing explanatory comment

Location: src/backend/replication/logical/decode.c:671

Mechanism: The patch changes LSN semantics (endptr → origptr) with no comment. The original endptr choice (commit 3fe3511d05127c, 2016) was never explained. A future reader could revert the fix without understanding why origptr is correct.

Verification: verified — inspection.

Repair packet:

  • Instruction: Add a 5-line block comment immediately before the ReorderBufferQueueMessage() call explaining that origptr (record start) is used for consistency with other decoded change types and per the documentation for the message callback LSN.
  • Code example (exact-postimage):

```c /*

  • Use the record's start LSN (origptr), consistent with other decoded
  • change types (INSERT/UPDATE/DELETE all queue with buf->origptr). The
  • documentation says the message callback receives the WAL location of the
  • message, i.e. the record start, not the end.

*/ ReorderBufferQueueMessage(ctx->reorder, xid, snapshot, buf->origptr, ```

  • Patch (HEAD-bound, unified-diff): sha256 0cdac90f5dd0dd6356ee9ae23c9b35abd4e9eb1c0361d6f51d3703daec99fe6e, applies to HEAD 7cb9060dcde08ca63df1f0d701b0674e0e8ae811.
  • Repair validation: passed — apply-check OK, apply OK, diff-check OK (no whitespace errors), compile: gcc -c -o decode.o decode.c exited 0, no warnings, decode.o rebuilt (17976 bytes). Documentation-only; no codegen change.
  • Test example: waived (design-choice) — comment-only change; compile is the only relevant gate. Runtime evidence from F-001's two-run TAP gate already exercises the changed code path.
NIT-001

(NIT, release-process) — Back-patch / release-note consideration

Location: doc/src/sgml/release-19.sgml (release notes)

Mechanism: The LSN change is observable to consumers. On the base code, the slot's lsn column for a logical message equals EndRecPtr (end of record). After the fix, it equals ReadRecPtr (start of record). A consumer (e.g. Debezium) comparing pre-fix to post-fix LSNs for the same WAL record would see a different (smaller) value. The original bug report explicitly mentions the Debezium data-loss issue. The thread (message 5f78375e8107feba80374921cfd87633, 2026-07-15) discusses back-patch options.

Verification: verified — inspection.

Repair: No code change. Add a release note when backpatching: "The LSN reported for logical decoding messages (pg_logical_emit_message) is now the WAL record start LSN, consistent with other decoded change types. Previously it was the record end LSN." All repair fields waived (required-external-resource) — this is a release-note process step, not a code defect.

Verification Summary

FindingSeverityClassStatusMethodpass_on_new
F-001MAJORtestsverifiedinspection + two-run TAPtrue (corrected repair)
F-002MINORdocverifiedinspectiontrue (comment-only)
NIT-001NITrelease-processverifiedinspectionn/a

Repair Validation Summary

FindingPatch sha256Validation statusMethodsEvidence
F-001683e7d4b...passedapply-check, diff-check, targeted-test, repro, two-runRun A: FAIL 2/5 (catches bug). Run B: PASS 5/5. Two-run gate satisfied.
F-0020cdac90f...passedapply-check, diff-check, compile, static-checkapply OK, diff-check OK, compile exit 0, no warnings.
NIT-001n/awaivedstatic-checkNo code change to validate. Release-note process step.

All verified findings have concrete code examples and HEAD-bound unified-diff patches with SHA-256 hashes. F-001's two-run TAP gate provides the strongest evidence: the test fails on base (endptr) and passes on fixed (origptr), proving it catches the bug. F-002's compile validation confirms the comment addition is syntactically correct. NIT-001 requires only a release note when backpatched.

Changed-Code Coverage

Coverage requirement

Measurement

Execution lane

LaneStatusEvidence
host-disposablepassedTAP test t/020_messages.pl: All tests successful (5 tests, 2 wallclock secs). Fresh coverage-instrumented build at frozen HEAD with CF7016 patch applied.

Raw metrics

MetricCount
changed executable lines1
covered changed lines1
compiler uncovered changed lines0
semantic uncovered changed lines0
structural uncovered changed lines0
changed lines without coverage coordinates0
changed branches0
covered changed branches0
integrity errors0

Changed-line coverage: 1/1 (100%). The single changed line (decode.c:671) has gcovr execution count 9 — executed 9 times during the test run. The line is a function call argument with no branches.

Changed line detail

path: src/backend/replication/logical/decode.c
line: 671
function: logicalmsg_decode
count: 9
source: ReorderBufferQueueMessage(ctx->reorder, xid, snapshot, buf->origptr,

Touched function diagnostic

FunctionPathLinesEnteredWhole-function lines (covered/total)Whole-function branches (covered/total)
logicalmsg_decodesrc/backend/replication/logical/decode.c607–677true26/330/0

The whole_function_branches total of 0 reflects gcovr's decision analysis heuristic which did not identify source-level conditional decisions on the changed line (line 671 is a function call, not a control-flow construct). The function does contain branches elsewhere (line 668 if (!message->transactional) with 3/6 true/false split), but these are outside the changed scope.

Uncovered regions

None. All changed executable lines are covered.

Uncovered branches

None. The changed line has no branches.

Excluded changed coordinates

None. No compiler-no-coverage-coordinate, structural-attribution, or raw-compiler-branch-attribution exclusions.

Non-instrumented changes

PathReason
src/test/subscription/t/020_messages.plTAP (Perl) test file — not C code, not instrumentable by gcov. Changed line 110 removes a WHERE clause filter to verify messages by xid=0 regardless of LSN, validating the endptr→origptr fix.

COV-* findings

None. The changed line is fully covered by the existing test t/020_messages.pl, which emits both transactional and non-transactional logical messages.

Residual limits

pgsql-hackers Thread Ledger

Message chronology (8 messages)

#DateSenderKindSummary
12026-06-08Torikoshipatchv1: reports bug, traces to commit 3fe3511d05127c, Debezium data-loss motivation
22026-06-09NagatareviewConfirms doc says "WAL location of the message" = origptr. Requests dead $message_lsn cleanup.
32026-06-09HoriguchireviewIndependent confirmation: origptr is correct per protocol doc
42026-06-11Torikoshiauthor-replySummarizes consensus; socializes consumer-impact rationale
52026-06-18KurodareviewNo blockers; Fujitsu's proprietary consumer unaffected
62026-06-19Torikoshiauthor-replyDebezium discussion ongoing
72026-07-15Torikoshipatchv2; argues for minor-release fix (option 2.1); v2 does NOT address Nagata's TEST-001 request
82026-07-21SolaireviewFunctional verification on PG19beta2; "patch looks good to me"

Per-finding dispositions

FindingDispositionThread evidence
F-001 (dead variable + weakened test)already-raisedNagata (msg 2) explicitly requested cleanup; v2 did not address
F-002 (missing comment)newNo thread participant raised the missing comment
NIT-001 (release-note)already-raisedAuthor (msg 7) raised back-patch/release-note consideration

Development-branch catversion exception

Not applicable — no catalog change in this patch. No catversion bump required.

Security Review

No security findings. The one-line LSN change aligns logical messages with DML ordering, closes a consumer-side data-loss pitfall, and touches no privilege boundary, catalog write, SQL-string construction, crypto path, or memory-safety-sensitive code. The test-only filter removal introduces no injection surface. Safe from the pg-security-reviewer perspective.

Aggregated Test Plan

  1. F-001 TAP test (020_messages.pl): Two assertions (lsn < $..._lsn::pg_lsn)::text with expected 'true|1|pgoutput' (transactional) and 'true|77|0' (non-transactional). Two-run gate: FAIL on base (endptr), PASS on fixed (origptr). Promote to Aggregated Test Plan.
  2. F-002: No runtime test (comment-only). Compile gate only.
  3. NIT-001: No test (release note only).

Residual risk

Provenance / Model routes

SeatModelHandoff report slugmarkdown_sha256
pg-patch-reviewerglm-5.2:cloud (fixed)commitfest-7016-review-handoff-patch-reviewer8d6e6dd844ed173b8f936449a548c19e3e54a0403534f35f948905f4540d9ff5
pg-security-reviewerglm-5.2:cloud (fixed)commitfest-7016-review-handoff-security-reviewer0ae92f5aff4ab78abe6c78aa46a6be658e2220321bc29aa051e300c86b5240dd
pg-test-authorglm-5.2:cloud (fixed)commitfest-7016-review-handoff-test-author30e1315395dc345c8d24318e3397e9510e52509a4271e6a31904e459f4aaf271
pg-finding-verifierglm-5.2:cloud (fixed)commitfest-7016-review-handoff-verifierf3d76441e55812abad1c80817ab16a0816a22a787501005d067d6aa1c2444188
pg-change-coverage-reviewerglm-5.2:cloud (fixed)commitfest-7016-review-handoff-coverage-reviewerd1f098f4e3808314ddeb3df337d1a0a998fcac949b938017abd6f95a771342bc
thread-ledger(coordinator)commitfest-7016-thread-ledger289374851a4c070c3bccaa2f3d80e89b59c3b534b21be3d96d9a7a35b8c92655

All handoff reports verified by the coordinator via get_report: source postgresql-master, scope_identity 88d6a2831768958d8824f8c59c6feddeaec96889, metadata schema amauta-review-handoff/v1.

v2 Artifact Validation

The structured pg-review-artifact-v2 JSON was assembled and validated with validate_review_artifact.py. Result: VALIDATION PASSED. Exit code 0.

Maintainer

decode.c recent top authors (3-year window): Peter Eisentraut (4), Álvaro Herrera (3), Masahiko Sawada (3). Last commit touching logicalmsg_decode: 1fdbca159e00 (Masahiko Sawada, 2026-01-28).

Filesystem boundary compliance