Skip to content

fix: a suite must not report checks against the previously installed .so - #508

Merged
jdatcmd merged 2 commits into
mainfrom
fix/lib-build-status-check
Aug 8, 2026
Merged

fix: a suite must not report checks against the previously installed .so#508
jdatcmd merged 2 commits into
mainfrom
fix/lib-build-status-check

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Reported by @ChronicallyJD, who caught it the only way it can be caught from the
outside: by fingerprinting the installed .so across a source change and seeing
the same hash twice.

The defect

pgc_setup ran both make steps with no status check:

make -C "$PGC_SRCDIR" PG_CONFIG="$PGC_PG_CONFIG" >/dev/null
make -C "$PGC_SRCDIR" install PG_CONFIG="$PGC_PG_CONFIG" >/dev/null

lib.sh sets set -uo pipefail but not -e, so a failed compile does not
stop the suite. It carries straight on and runs every check against whatever
.so was installed last, then prints a full report for code that does not exist.

Scope: the matrix was never at risk, only the standalone path

The obvious reviewer objection, checked rather than assumed:
run_all_versions.sh status-checks both steps and records
FAIL PG$major (build) / (install), and separately fails the major on compiler
warnings. Suites run under PGC_SKIP_BUILD therefore never built at all and
could not hit this. The gap was exactly and only the standalone
test/<suite>.sh path — which is what a person runs while iterating, i.e. the
case where the source most often does not compile.

Proof by removal

this is not valid C and cannot compile; appended to src/columnar_bloom.c.

With the guard:

-- building
src/columnar_bloom.c:260:1: error: unknown type name 'this'
make: *** [<builtin>: src/columnar_bloom.o] Error 1
FATAL: the build failed, so there is nothing new to test
       (refusing to report checks against the previously installed .so)

No checks run.

With the guard removed, same broken tree:

PASS  the filter still skips groups it can prove empty (#467)
PASS  premise: one row group holds more than the refusal threshold
PASS  a column above the distinct cap is refused a filter, not given a bad one
PASS  and the load itself succeeded, rather than erroring on the way

checks run: 19
bloom_sizing.sh: PASSED

Nineteen green checks, including four premise assertions, against a source file
that cannot compile. That is worse than a wrong answer because it is
indistinguishable from a right one — and it is green, so nobody looks.

Second commit: the trap had to move too

Review caught that the new exit 1 sat between mktemp -d and
trap pgc_teardown EXIT, leaking a workdir on every failed build — precisely
when someone is iterating on code that does not compile. Fixed, proven by
removal:

trap position suite exit workdirs left
above the build (now) 1 0
below the build (first commit) 1 1

Moving the trap alone would have been wrong: pgc_teardown reaches pg_ctl
through pgc_pg, which expands PGC_RUNPG, set after the build. A trap armed
above it with the array unset turns an early failure into an unbound-variable
error under set -u. The privilege block moved up with it.

Passing path unchanged: clean bloom_sizing.sh gives 19 checks, PASSED, 0
workdirs, 0 leftover clusters.

Class

Third instance of the same shape found this week: a step that fails silently and
a report that speaks anyway. #506 is the ClickBench reporter printing the
cold-cache tag on hosts where the drop was refused; #503 lists four sites where a
cost estimate is re-derived and one copy stops moving. The common repair is that
the reporting step must not be reachable when the step it reports on did not
happen.

pgc_setup ran the build and the install with no status check:

    make -C "$PGC_SRCDIR" PG_CONFIG="$PGC_PG_CONFIG" >/dev/null
    make -C "$PGC_SRCDIR" install PG_CONFIG="$PGC_PG_CONFIG" >/dev/null

lib.sh sets `set -uo pipefail` but not -e, so a failed compile did not stop the
suite. It carried on and ran every check against whatever .so was installed
last, then printed a full report for code that does not exist.

Reported by ChronicallyJD, who caught it only by fingerprinting the installed
.so across a source change and seeing the same hash twice.

Proven by removal, with `this is not valid C and cannot compile;` appended to
src/columnar_bloom.c. With the guard:

    -- building
    src/columnar_bloom.c:260:1: error: unknown type name 'this'
    make: *** [<builtin>: src/columnar_bloom.o] Error 1
    FATAL: the build failed, so there is nothing new to test

and no checks run. With the guard removed, the same broken tree:

    PASS  the filter still skips groups it can prove empty (#467)
    PASS  a column above the distinct cap is refused a filter, not given a bad one
    ...
    checks run: 19
    bloom_sizing.sh: PASSED

Nineteen green checks against a source file that cannot compile. That is worse
than a wrong answer, because it is indistinguishable from a right one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeNm2Gw6h16Z123We3F1vJ

@ChronicallyJD ChronicallyJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. The removal proof is the part that should go in the commit message if
it is not already there — 19 green checks, four of them premise assertions,
against a source file containing this is not valid C and cannot compile;
is a
better argument for this guard than any description of it. It is also the exact
shape I hit: my run reported 8 checks including the very check I had just written,
and the only thing that gave it away was the installed .so hash being identical
either side of a source change that could not have produced it.

I checked the obvious follow-on question and it comes out in your favour: the
matrix path was already covered.
run_all_versions.sh status-checks both steps
at :559 and :580 and records FAIL PG$major (build) / (install), so
PGC_SKIP_BUILD suites were never at risk from their own build — the gap was
exactly and only the standalone path this patch fixes. Worth a line in the body,
because the natural reviewer worry is "the authoritative gate skips this block".

One thing to fix, small

The new exit 1 sits between the workdir being created and the trap that
removes it:

lib.sh:108   PGC_WORKDIR="$(mktemp -d /tmp/pgcolumnar-test.XXXXXX)"
lib.sh:125   echo "-- building"      <-- new exit 1 here
lib.sh:127   echo "-- installing"    <-- and here
lib.sh:140   trap pgc_teardown EXIT

So a build failure now leaks /tmp/pgcolumnar-test.XXXXXX every time. Before this
patch the same path ran on to the trap and cleaned up; the guard is right, but it
turns a compile error into an accumulating one. On a box where someone is
iterating on a change that does not compile — which is precisely when this fires —
that is one directory per attempt, and each holds a full data directory once it
gets further.

Cheapest fix is to move trap pgc_teardown EXIT above the build block. It is
already safe to run early: pgc_teardown keys off PGC_PGDATA and PGC_WORKDIR,
both set by then, and stopping a cluster that was never started is a no-op. If you
would rather not move it, rm -rf "$PGC_WORKDIR" before each exit 1 does the
same job less tidily.

Two things I checked and am happy with

  • exit 1 and not exit 2. #499 established 1 for a hard failure and 2 for
    too-few-samples; a build that did not happen is unambiguously the former.
  • Message on stderr, signal in the exit status. A caller that captures only
    stdout still fails, because it fails on the status. That is the right way round —
    the reverse (message on stdout, status 0) is the bug being fixed.

Beyond this PR

You are right that it is the third instance this week, and I would add a fourth
from today: the .so fingerprint is what caught this one, and nothing in the
harness prints it. Every suite already knows $($PGC_PG_CONFIG --pkglibdir), so
one line in pgc_setupecho "-- .so: $(md5sum ...)" — would put the hash in
every log, and a red-on-change proof would then carry its own evidence that the
two arms were different binaries. I have been doing it by hand all day and it has
paid for itself twice. Happy to send that as a follow-up rather than pile it here.

The exit added by the previous commit sat between `mktemp -d` at :108 and
`trap pgc_teardown EXIT`, so a failed build left /tmp/pgcolumnar-test.XXXXXX
behind every time -- and it fires exactly when someone is iterating on code that
does not compile, so they accumulate. Before that commit the path ran on to the
trap. Caught in review by ChronicallyJD.

The trap now arms as soon as there is a directory to remove. Stopping a cluster
that never started is a no-op, so arming it early costs nothing.

The privilege block moved above it rather than the trap alone moving up, which
is the part the review did not have to notice: pgc_teardown reaches pg_ctl
through pgc_pg, which expands PGC_RUNPG, and that array was set AFTER the build.
A trap armed before it would have turned any early failure into an
unbound-variable error under `set -u` instead of a cleanup. PGC_PGDATA was
already set at :109 and needed no move.

Proven by removal, broken source, counting /tmp/pgcolumnar-test.*:

    trap above the build (this commit)   exit 1, workdirs left: 0
    trap below the build (as shipped)    exit 1, workdirs left: 1

And the passing path is unchanged: a clean run of bloom_sizing.sh reports 19
checks, PASSED, leaves 0 workdirs and 0 leftover clusters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeNm2Gw6h16Z123We3F1vJ
@jdatcmd

jdatcmd commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Both points taken. Pushed as 5ec1501.

The workdir leak is real and is fixed

Verified before fixing: mktemp -d at lib.sh:108, my exit 1 at :135, trap pgc_teardown EXIT at :156. Proven by removal, counting /tmp/pgcolumnar-test.* after a failed build:

trap position suite exit workdirs left
above the build (now) 1 0
below the build (as shipped) 1 1

And the passing path is unchanged — a clean bloom_sizing.sh reports 19 checks, PASSED, leaving 0 workdirs and 0 leftover clusters.

One thing the fix needed that the review did not have to catch. Moving the trap alone would have been wrong: pgc_teardown reaches pg_ctl through pgc_pg, which expands PGC_RUNPG, and that array was set at :149after the build. A trap armed above the build with it still unset turns any early failure into an unbound-variable error under set -u instead of a cleanup, which is a worse failure than the leak. So the privilege block moved up with it. PGC_PGDATA was already set at :109 and needed no move.

The matrix path was already covered

Confirmed rather than taken on trust — run_all_versions.sh status-checks both steps and records FAIL PG$major (build) / (install), and separately fails on compiler warnings. So PGC_SKIP_BUILD suites were never at risk from their own build, and the gap was exactly and only the standalone path. Added to the PR body, since it is the first thing a reviewer should worry about.

On the .so hash follow-up

Agreed, and I think it is stronger than a convenience. It is the only reason this defect was findable at all: the evidence was two identical hashes across a source change that could not have produced them. A red-on-change proof that prints its own binary fingerprints stops being an assertion about what was tested and becomes a record of it.

Worth deciding one thing when you write it: printing the hash is useful, but the check that pays is comparing it across the two arms of a removal proof. A hash in a log nobody diffs is another passing-suite line. If pgc_setup records it somewhere a proof can read back, the "before" and "after" arms can assert they were different binaries rather than leaving the reader to trust it.

@jdatcmd
jdatcmd merged commit ed28c32 into main Aug 8, 2026
11 checks passed
@jdatcmd
jdatcmd deleted the fix/lib-build-status-check branch August 8, 2026 16:05
jdatcmd added a commit that referenced this pull request Aug 8, 2026
)

Caught by the five-major gate, on all five majors: pushdown_report and
analyze_stats both failed, and both for the same reason.

ExecScan increments nfiltered1 for every tuple ITS qual rejects, and that is what
EXPLAIN prints as "Rows Removed by Filter". Filtering inside the scan means
ExecScan never sees a rejected row, so the line silently read 0 on every columnar
scan carrying a qual. The plan still looked right; a counter two other suites
depend on had stopped counting.

    FAIL  pushdown off examines every row: got [unset] want [199999]
    FAIL  the executor filters far more rows with pushdown off: got [no] want [yes]
    FAIL  having an index available saves the point lookup real work:
          got [no (0 with the index, 0 without)] want [yes]

The filter now counts the rejection where the executor would have counted it,
and the late-materialization suite pins it next to the feature that broke it, so
this cannot regress silently a second time.

Worth recording how nearly this was missed twice. The first verification of the
fix ran with PGC_SKIP_BUILD=1 after a manual `make` with no `make install`, so
all three suites ran against the PREVIOUSLY INSTALLED .so -- which happened to be
the guard-removed binary left by an earlier removal proof. It reported the
volatility guard broken as well, which is what made it obvious. Fingerprinted:

    3fc733b34589  stale, guard-removed   3 suites failing
    a411738500db  actually installed     14 / 34 / 33 checks, 0 fails

That is #508's defect class wearing a different costume: there the build failed
silently, here the install was never asked for.

Refs #452

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeNm2Gw6h16Z123We3F1vJ
jdatcmd pushed a commit that referenced this pull request Aug 8, 2026
… skips

Three separate defects in one session were a suite reporting checks against a
binary nobody had just built:

  - a compile error the harness did not check, which reported 19 green checks
    against a source file containing invalid C (#508);
  - a PGC_SKIP_BUILD=1 run that skipped the INSTALL, not just the build, and
    exercised a guard-removed leftover from an earlier removal proof -- three
    suites failing for a reason that had nothing to do with the change;
  - objects from one major linked into another's .so, which surfaces as a
    cluster that will not start behind a message naming nothing.

Every one produced a plausible PASS/FAIL list. Every one is one line of md5sum
away from being obvious. So pgc_setup now prints that line on every run, whether
or not anything looks wrong -- the point is precisely that nothing does.

It also makes a red-on-change proof self-evidencing. Two arms reporting the same
hash have proved nothing whatever their check counts say, and that is currently
something each person has to remember to verify by hand.

PGC_SKIP_BUILD is named in the same change because the variable is not what it
says: it reads as "skip the build" and means "skip the build AND the install, and
test whatever is already installed". That is correct for the matrix, which
installs once per major before setting it, and a trap for a person who has just
edited source and run make by hand -- which is exactly how the second defect
above happened.

harness_selftest gains two checks. The first is that the line exists. The second
is the one with teeth: it compares what is INSTALLED against what was just BUILT,
using the build tree as an independent source rather than recomputing the
installed hash the same way twice.

Proved by reproducing the original incident rather than by deleting the check:
build a genuinely different binary, do not install it, run with
PGC_SKIP_BUILD=1.

    installed 8b58d7fdcb0d, built 7564d4f138ed   -> FAIL, as it should

The first attempt at that proof appended a comment to a source file and rebuilt.
The binary was byte-identical, both hashes matched, and the check passed --
which the fingerprint line itself is what revealed. A perturbation that does not
perturb is not a proof, and this one says so out loud.

Refs #508
jdatcmd added a commit that referenced this pull request Aug 8, 2026
test: print the .so under test, and name what PGC_SKIP_BUILD actually skips (#508 follow-up)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants