Skip to content

fix: P6 and P8, plus the Android NDK r29 and ocr-review CI breakages - #206

Merged
gburd merged 11 commits into
masterfrom
fix/p6-p8-ci
Sep 21, 2026
Merged

gburd merged 11 commits into
masterfrom
fix/p6-p8-ci

Conversation

@gburd

@gburd gburd commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Four release blockers for v2026.09.12.

P6 — DB_BACKUP_NO_LOGS accepted and silently ignored

The flag appeared exactly twice in the tree (its #define and db_backup.c's accepted-flag mask) and was read nowhere, so a backup taken with it copied every log file. Fixed by guarding the backup_read_log_dir call.

Checked the interaction rather than assuming: copy_min stays 0 when logs are skipped and the DB_BACKUP_UPDATE test is remove_max < copy_min, never true at 0.

Measured after: 2 db files and 0 log files, against a control backup with 62.

P8 — DB_NOFLUSH made an environment unusable

LAST_PANIC_CHECK_BEFORE_IO tested the public DB_ENV_NOFLUSH and expanded to an unconditional return (0) inside __os_physwrite's write loop and both arms of __os_io. Every read and write reported success and moved no data — SIGBUS with a zero-length region file (shared), DB_PAGE_NOTFOUND (private). Present verbatim since Oracle 5.1.29.

It could not simply be deleted: env_open.c sets NOPANIC|NOFLUSH during panic teardown, and NOPANIC makes PANIC_ISSET false — so the NOFLUSH test is what actually stops the I/O there. Two unrelated meanings shared one bit.

Split them: teardown now sets internal-only DB_ENV_NOIO (0x00400000, a process-local dbenv->flags bit — no region-layout, no ABI consequence). DB_ENV_NOFLUSH keeps its one real meaning, already implemented at env_open.c:844.

Retiring the XFAILs, and replacing the teeth they provided

All three of P6/P7/P8 flipped XFAIL→PASS with no edit to their tests — what the mechanism was for. Their XFAIL branches are now FAIL branches.

That removes the tier's must-fail teeth: FLAGAPI_STRICT=1 now passes, so the step asserting it must fail had nothing left to fail on — the vacuous-green shape with nine recorded instances here. Replaced with test/c/flagapi-sabotage.sh, which removes the DB_BACKUP_NO_LOGS guard (that is P6), rebuilds, and requires backup_nologs specifically to FAIL, restoring the tree via a trap on every exit path.

arm result
clean tree 29/29 modes PASS, 0 XFAIL
sabotaged tree caught — backup_nologs: FAIL

Android cross-build on NDK r29

The aarch64-linux-android24-clang wrapper is a shell script that execs a sibling clang. r29 ships the wrapper but not the sibling, so it existed, was executable, and died with bin/clang: No such file or directory — which meson surfaced only as Unknown compiler(s). The [ -x "$CC" ] guard passed because the file was there; testing -x is not enough, the compiler has to be run.

Now probes with --version and falls back to the real clang with an explicit --target. The flag goes in meson's [binaries] list, not c_args, so it applies to meson's own sanity check too.

ocr-review

The repo variable was created as OCR_BEDROCK_VARIABLE while all eight references read OCR_BEDROCK_MODEL — the model string arrived empty. Renamed (config, not code).

The second half is not fixable from the repository and is now documented in the workflow header: vars.AWS_ROLE_ARN points into AWS account 492372116879, which is no longer active, so the OIDC step cannot mint credentials until it's repointed. Both this job and Android are continue-on-error.

Four independent fixes, grouped because they are all release blockers for
v2026.09.12.

P6 -- DB_BACKUP_NO_LOGS was accepted and silently ignored

The flag appeared exactly twice in the tree: its #define and db_backup.c's
accepted-flag mask. It was read NOWHERE, so a backup taken with it copied every
log file. Documented behaviour is "back up only the *.db files, do not backup
the log files" (docs_src/api/c/envbackup.md). Fixed by guarding the
backup_read_log_dir call.

Checked the interaction rather than assuming: copy_min stays 0 when logs are
skipped, and the DB_BACKUP_UPDATE consistency test is `remove_max < copy_min`,
which is never true at 0, so NO_LOGS+UPDATE does not false-trigger. Measured
after: 2 db files and 0 log files, against a control backup with 62.

P8 -- DB_NOFLUSH made an environment unusable

LAST_PANIC_CHECK_BEFORE_IO tested the PUBLIC DB_ENV_NOFLUSH and expanded to an
unconditional `return (0)' inside __os_physwrite's write loop and both arms of
__os_io. So under DB_NOFLUSH every read and write in the library reported
success and moved no data: SIGBUS in __env_alloc_init with a zero-length region
file on a shared environment, DB_PAGE_NOTFOUND on the first DB->open on a
private one. Present verbatim since Oracle 5.1.29 -- 15 years.

The clause could NOT simply be deleted. env_open.c sets NOPANIC|NOFLUSH during
panic teardown, and NOPANIC makes PANIC_ISSET false, so PANIC_CHECK alone would
not stop the I/O on that path -- the NOFLUSH test is what does. Two unrelated
meanings were sharing one bit.

Split them: panic teardown now sets a new internal-only DB_ENV_NOIO (0x00400000,
a process-local dbenv->flags bit, so no region-layout and no ABI consequence --
the three-gate question), and the macro tests that. DB_ENV_NOFLUSH keeps its one
real meaning, "do not flush the cache on close", which was already correctly
implemented at env_open.c:844. Perl's constants.h still resolves DB_ENV_NOFLUSH
to the same value, so the bindings are unaffected.

Retiring the XFAILs, and replacing the teeth they provided

All three of P6/P7/P8 flipped from XFAIL to PASS with NO EDIT to their tests,
which is what the XFAIL mechanism was for. Their XFAIL branches are now FAIL
branches: with the defects fixed, the old signature means a regression.

That removes the tier's must-fail teeth -- FLAGAPI_STRICT=1 now passes, so the
step asserting it must FAIL had nothing left to fail on, which is precisely the
vacuous-green shape this project has nine recorded instances of. Replaced with
test/c/flagapi-sabotage.sh: it removes the DB_BACKUP_NO_LOGS guard (which IS
defect P6), rebuilds, and requires backup_nologs specifically to FAIL, restoring
the tree on every exit path via a trap. Verified in both directions -- clean
tree 29/29 PASS, sabotaged tree caught with "backup_nologs: FAIL".

Android cross-build on NDK r29

The per-API wrapper (aarch64-linux-android24-clang) is a shell script that execs
a sibling `clang'. NDK r29 ships the wrapper but not that sibling, so the
wrapper existed, was executable, and died with "bin/clang: No such file or
directory" -- which meson surfaced only as "Unknown compiler(s)". The existing
`[ -x "$CC" ]' guard passed, because the file was there; testing -x is not
enough, the compiler has to be RUN. Now probes with --version and falls back to
the real clang with an explicit --target, which is all the wrapper was doing.
The --target goes in meson's [binaries] list rather than c_args so it also
applies to meson's own sanity check. Also verifies llvm-ar/strip/ranlib and
prints the bin/ listing on failure instead of failing obscurely.

ocr-review

The repo variable was created as OCR_BEDROCK_VARIABLE while all eight references
read OCR_BEDROCK_MODEL, so the model string arrived empty and the guard tripped
on every run. Renamed the variable (config, not code).

The second half of that failure is NOT fixable from the repository and is now
documented in the workflow header: vars.AWS_ROLE_ARN points into AWS account
492372116879, which is no longer active, so the OIDC credential step cannot mint
credentials until it is repointed. Both ocr-review and the Android job are
continue-on-error/advisory, so neither blocks a merge.
@github-actions

Copy link
Copy Markdown
Contributor

Coccinelle convention checks

No new violations. ✅

Resolved since baseline (3) -- update dist/cocci/baseline.txt to lock these in.
rule_mutex_unbalanced|MUTEX_UNBALANCED|src/btree/bt_curadj.c|return (ret);
rule_mutex_unbalanced|MUTEX_UNBALANCED|src/crypto/mersenne/mt19937db.c|return (ret);
rule_mutex_unbalanced|MUTEX_UNBALANCED|src/mp/mp_register.c|return (ret);

@github-actions

Copy link
Copy Markdown
Contributor

ABI diff vs v2026.09.11 (libabigail — authoritative)

abidiff exit bitmask: 0

Removed exported symbols (nm -D, _NNNN version suffix normalized)

None.


Advisory: libabigail/nm is the authoritative binary-ABI check; Coccinelle is complementary source-level early warning. See dist/cocci/README.md.

… +x the sabotage script

Two gates caught two real defects in the previous commit, both in the
"generated files that are committed" class:

1. dist/s_execbits found test/c/flagapi-sabotage.sh at mode 100644. The local
   chmod +x never reached git's index, so the script would have been unrunnable
   for everyone else -- and since it IS the flag tier's teeth, the tier would
   have lost its must-fail arm silently. Fixed with git update-index --chmod=+x.

2. windows msbuild failed with "DB_ENV_NOIO: undeclared identifier" in five
   os_windows translation units. build_windows/db.h and build_android/db.h are
   COMMITTED generated files, so adding a flag to src/dbinc/db.in does not reach
   them until s_windows/s_android are run. Regenerated both.

Regenerating also swept up pre-existing drift in the Android headers, unrelated
to this change and now correct: clib_port.h still carried WinCE comments, and
db_config.h and db_int.h still referenced VxWorks (HAVE_MUTEX_VXWORKS,
selectLib.h) long after those ports were removed. The committed copies had not
been regenerated since.

build_windows/db.h moved by exactly 8 lines and none of the five CRLF phantom
files appear, so the skip-worktree set is still doing its job.
…st tag

The previous commit fixed the wrong half. It handled "the per-API clang wrapper
exists but cannot exec its sibling clang", which was the visible symptom on r29 --
but the diagnostic it added then reported bin/ as EMPTY, which means the
hard-coded host tag was wrong and every path built from it pointed at nothing.
linux-x86_64 is simply not what r29 ships under prebuilt/.

So the assumption itself was the defect, in both places it appears:

  - the host tag: now tries the known tags (linux-x86_64, linux-aarch64 and the
    Darwin pair), takes the first that actually HAS a bin/, and failing that
    finds any executable clang anywhere under the NDK root.
  - the compiler: unchanged from the previous commit -- probe with --version and
    fall back to bare clang plus an explicit --target, because testing -x cannot
    tell a working wrapper from one whose sibling is missing.

Both fallbacks were verified against constructed NDK trees rather than only in
CI: a tree with the unusual host tag "linux-musl-x86_64" and no per-API wrapper
at all is driven correctly ("using .../clang --target=aarch64-linux-android24"),
and a tree with an empty prebuilt/ now fails naming the real problem and listing
what it did find.

That second check caught a bug in this commit's own error path: `dirname' of an
empty find result yields ".", so the failure message listed the repository
directory as the NDK's bin/. Guarded.
…lang-NN

Third pass, and the previous two were each fixing a symptom one layer out.

What r29 actually ships, read off the failure output rather than guessed: bin/
contains the per-API wrappers for EVERY api level (aarch64-linux-android21-clang
through -android30-clang, plus the ++ forms) and NO bare clang at all. The
wrappers exec "$(dirname $0)/clang", which therefore does not exist, so each one
dies with "bin/clang: No such file or directory" -- and meson surfaces that only
as "Unknown compiler(s)".

The previous commit's fallback looked for exactly "$BIN/clang", which is the one
name r29 does not provide, so it reported "no runnable NDK compiler" while the
compiler was sitting right there under a versioned name.

Now: probe the wrapper by RUNNING it; if that fails, try $BIN/clang, then
$BIN/clang-NN newest-first, then any executable clang or clang-NN anywhere under
the NDK root, accepting the first that answers --version. Drive whatever is found
with an explicit --target, which is all the wrapper was doing. The --target is
passed via meson's [binaries] list rather than c_args so it also covers meson's
own compiler sanity check.

Verified against a constructed NDK in exactly r29's shape -- wrappers present,
no bare clang, real driver as clang-21 -- which now resolves correctly:

  note: ...aarch64-linux-android24-clang is not runnable;
        using .../clang-21 --target=aarch64-linux-android24

and against the same tree with clang-21 removed, where the diagnostic now prints
WHY the wrapper failed (the exec error) plus the non-wrapper contents of bin/,
instead of a bare list truncated to wrappers.

The lesson worth keeping: "file exists and is executable" says nothing about a
wrapper script, and the first two attempts here both trusted a path I had not
run.
…iler

Fourth pass. The compiler now resolves on r29 -- the note fires and clang-NN is
found -- and the next hardcoded path immediately failed: llvm-ar, llvm-strip and
llvm-ranlib were still addressed as "$BIN/<tool>", and r29 does not put them
there either.

The pattern was the defect, not any one path. Each of these four commits fixed a
single assumed location while leaving its siblings assumed, so every pass moved
the failure one line down. All tool lookups now go through one find_tool helper:
$BIN/<tool>, then $BIN/<tool>-NN newest-first, then anywhere under the NDK root,
then the un-prefixed name (llvm-ar and ar are interchangeable on a clang
toolchain). The resolved values flow into the meson cross file and the strip
invocation instead of being re-derived from $BIN.

Verified against a constructed tree in r29's shape with the llvm tools
deliberately moved OUT of bin/ into a sibling prebuilt path: both the compiler
(clang-21 plus --target) and the relocated tools resolve.

Whether this is the last relocation I cannot claim -- I have been wrong about
that three times in a row, and the honest statement is that the script no longer
hardcodes any toolchain path, so the next layout change should be absorbed rather
than needing a fifth commit. The job is continue-on-error, so it has never
blocked the release.
Fifth pass, and this one removes the requirement rather than chasing the path.

The r29 failure was "NDK tool not found: llvm-strip" and "llvm-ranlib" while
llvm-ar resolved fine. Two causes, both mine:

  - the fallback searched with `find -type f', and llvm-strip and llvm-ranlib are
    SYMLINKS (to llvm-objcopy and llvm-ar) in some NDK layouts, so a -type f
    search skips them entirely while llvm-ar, a real file, is found.
  - more importantly, they were treated as required at all. meson builds a shared
    library perfectly well with no strip or ranlib entry in the cross file, and
    this script's own strip step was already best-effort with `|| true'. Being
    strict about two optional tools is what turned a layout change into a hard
    error.

So: find_tool now accepts -type f OR -type l and also tries the un-prefixed name
(llvm-ar and ar are interchangeable here), and only the ARCHIVER is required.
Missing strip/ranlib print a note and are OMITTED from the cross file rather than
emitted as empty paths that meson would try to exec. The strip step is guarded.

Verified against two constructed trees, checking the generated cross file both
times rather than only the exit status:

  bin/ = clang + llvm-ar only     -> two notes, build proceeds, cross file has
                                     c/cpp/ar and no strip or ranlib lines
  bin/ = clang + all three tools  -> strip and ranlib present, and correctly
                                     under [binaries], not [host_machine]

That last check is why the insertion uses sed against the `ar =' line instead of
appending to the file, which would have put them in the wrong section.
The compiler and the archiver now resolve on r29, and the failure moved to the
link step:

  ERROR: Unable to detect linker for compiler `.../clang-21 --target=... -Wl,--version`
  error: unable to execute command: Executable "ld.lld" doesn't exist!

ld.lld IS present in that same bin/ -- it appeared in the directory listing an
earlier pass printed. The problem is a consequence of bypassing the per-API
wrapper: clang locates ld.lld relative to what it considers its install root, and
clang-21 invoked by absolute path does not treat that bin/ as one. The wrapper had
been supplying this implicitly.

-B<dir> names the tool directory explicitly, so it goes into the [binaries] entry
alongside --target. Verified on a constructed r29-shaped tree by reading the
generated cross file, not just the exit status:

  c = ['.../bin/clang-21', '--target=aarch64-linux-android24', '-B.../bin']

Each pass here has been the same mistake in a new place: replacing one thing the
wrapper did without asking what else it did. --target, the tool lookups, and now
-B are all things it was doing for us.
…er validated

Stepping back after five passes at r29's layout. The thing I should have checked
first: this job has failed on EVERY run since it was added -- 18 consecutive
failures across three days -- and continue-on-error reported the workflow as
"success" throughout, so nothing surfaced it. The pin's own comment said r29
"matches the nixpkgs ndk-bundle this path was validated against", and that was
simply not true: there is no run in which it worked.

So the Android cross-build was never green, and I have been treating a
never-validated pin as a regression.

r29 ships the per-API wrappers (aarch64-linux-androidNN-clang for every level) but
NOT the sibling `clang' those wrappers exec, and its ld.lld is not where a
directly-invoked clang-NN looks for it. Bypassing the wrapper therefore trades one
missing tool for the next, which is exactly what each pass found: clang, then
llvm-ar, then llvm-strip/ranlib, then ld.lld.

Pinned r27c, an LTS release with the conventional layout. The discovery work in
build_android.sh is kept -- it is what makes the script survive a layout change
instead of hard-coding five paths -- but the pin is now a version whose layout is
known good, so the job can actually prove the cross-build rather than exercise
fallbacks.

If r27c goes green, the job should lose continue-on-error in a follow-up, because
an advisory gate that has never once passed is not advisory, it is decorative.
…ailing

ocr-review has been failing on every PR. Half of that was the variable-name
mismatch fixed earlier (OCR_BEDROCK_VARIABLE vs OCR_BEDROCK_MODEL); the other
half cannot be fixed from the repository, because vars.AWS_ROLE_ARN points into
AWS account 492372116879, which is no longer active, so the OIDC step cannot mint
credentials.

Until that is repointed, the honest behaviour is to SKIP, not to fail. A guard
step checks AWS_ROLE_ARN, AWS_REGION and OCR_BEDROCK_MODEL, emits a ::notice
naming whichever are missing, and every subsequent step is conditioned on it.

The reason to bother, given the job is already continue-on-error: an advisory job
that fails on every single PR trains everyone to ignore it, and then it is not
advisory, it is noise that hides the next real failure. This is the same shape as
the Android job, which turned out to have never passed in 18 runs while the
workflow reported success.

No pre-existing `if:' conditions were replaced -- verified the diff removes no
lines.
…link

r27c went green, then went red on the very next run with the SAME pin. That can
only be the environment, and it was: local-cache: true restores a partially
populated NDK, the per-API wrapper becomes unrunnable, and build_android.sh's
fallback then quietly resolves clang-18 directly and fails much later at the
linker with "Unable to detect linker for compiler".

Two changes, because the caching was the cause but the fallback is what made it
confusing:

  - local-cache: false. A fresh download costs a couple of minutes and makes a
    green run mean the toolchain is actually complete.
  - a link probe before meson runs: compile and link a trivial shared object with
    the resolved compiler, and fail immediately if that does not work. meson
    reports a missing ld.lld as a problem with the COMPILER, which sends you to
    the wrong place; the probe says the linker is absent and, when it sees ld.lld
    in the error, names the cache as the likely cause. It also stops a
    fallback-resolved clang from being accepted when it cannot produce a binary.

The fallback now also announces that a working NDK should not need it, so its
appearance in a log reads as a symptom rather than as normal operation.

Probe verified both ways against constructed toolchains: one whose clang answers
--version but fails to link reports "the resolved toolchain cannot link" plus the
ld.lld hint, and one that links cleanly prints "== link probe: ok".
…is unset

The config check added in the previous commit was necessary but not sufficient.
vars.AWS_ROLE_ARN is present and well-formed, so the guard passed, and the job
still failed -- on the assume itself:

  Could not assume role with OIDC: Not authorized to perform
  sts:AssumeRoleWithWebIdentity

That is the dead account (492372116879) and it is not detectable by inspecting
configuration; it only shows up on attempting the call. So the credentials step is
now continue-on-error with an id, a following step emits a ::notice explaining
what to repoint, and each downstream step additionally requires
steps.aws.outcome == 'success'.

Result: with no Bedrock config the job skips, with config but a dead role it skips
with a note naming the fix, and with a live role it reviews as before. It stops
being a red mark on every pull request for a reason no contributor can act on.
@gburd
gburd merged commit c878920 into master Sep 21, 2026
65 checks passed
@gburd
gburd deleted the fix/p6-p8-ci branch September 21, 2026 23:18
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.

1 participant