Skip to content

C++ exception barriers: remaining C-ABI getters#94

Merged
JoshDreamland merged 3 commits into
mainfrom
exception-barrier-cleanup
May 13, 2026
Merged

C++ exception barriers: remaining C-ABI getters#94
JoshDreamland merged 3 commits into
mainfrom
exception-barrier-cleanup

Conversation

@JoshDreamland
Copy link
Copy Markdown
Contributor

@JoshDreamland JoshDreamland commented May 11, 2026

Summary

C++ exception safety cleanup for src/export/stats_exporter.cc. Three commits:

  • b4a4181 — more exception barriers (@serprex, amended). Same content as more exception barriers #87 with the function-try-block syntax restored to brace form, and PschExporterInit's catch handlers switched from reset + return false (latent NPE on the bgworker's next loop) to ereport(FATAL) (clean proc_exit(1); postmaster respawns the worker without DB-wide crash recovery).
  • a7d2ea9 — fix: round out exception barriers. Wrap the remaining unguarded extern "C" getters (PschGetConsecutiveFailures, PschGetRetryDelayMs, PschResetRetryState) in try / catch. Drop a load-bearing comment in ExportEventsAsArrowInternal and PschExportBatch marking the live-RAII boundary above the post-export PG accounting calls. Cache NumConsecutiveFailures(); tighten the barrier comment.
  • 16bd888 — feat: install C++ terminate handler. Backstop for anything the explicit barriers miss. Installed at the top of PschExporterInit (the build lock from build: forbid C++ sources outside src/export/ #68 makes _PG_init C-only, so it can't host std::set_terminate). Converts uncaught C++ throws into ereport(FATAL)proc_exit(1) so postmaster respawns just our bgworker instead of treating SIGABRT as a backend crash. The explicit catches still run first; this only fires for what they miss.

Test plan

  • CI green (GCC matrix + Clang job).

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 11, 2026 22:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR rounds out the C++ exception-barrier audit for src/export/stats_exporter.cc, ensuring extern "C" entry points don’t allow C++ exceptions to escape into PostgreSQL C frames, and documenting a key longjmp/RAII boundary around post-export accounting.

Changes:

  • Converted PschExporterInit / PschExportBatch to brace-form try { ... } catch { ... } exception barriers.
  • Added exception barriers to remaining C-ABI retry/getter functions (PschGetConsecutiveFailures, PschGetRetryDelayMs, PschResetRetryState) plus a null guard for PschGetConsecutiveFailures.
  • Added “RAII must not cross longjmp-capable PG calls” invariant comments near post-export accounting points.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/export/stats_exporter.cc
Comment thread src/export/stats_exporter.cc Outdated
Comment thread src/export/stats_exporter.cc Outdated
@JoshDreamland JoshDreamland force-pushed the exception-barrier-cleanup branch from 0a14144 to 077ac22 Compare May 11, 2026 22:39
Copilot AI review requested due to automatic review settings May 12, 2026 02:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines +482 to +492
// Exception barrier: exporter destructors (clickhouse-cpp socket close, gRPC
// stub teardown, protobuf arena release) can throw. Catching here prevents the
// throw from crossing the on_proc_exit chain.
void PschExporterShutdown(void) {
g_exporter.exporter.reset();
try {
g_exporter.exporter.reset();
} catch (const std::bad_alloc&) {
LogExporterWarning("exporter shutdown", "out of memory");
} catch (const std::exception& e) {
LogExporterWarning("exporter shutdown exception", e.what());
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we use https://en.cppreference.com/cpp/error/terminate_handler to achieve same effect as elog FATAL?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Really good call; sorry I didn't think of it after Claude explained to me the circumstances under which Postmaster throws a tantrum. Makes a lot of sense. I think these explicit catches should stay, though—the fatal ones because they provide more accurate logging and more explicitly demonstrate intent, and the non-fatal ones because they're non-fatal and properly rescue the process.

@serprex serprex requested review from iskakaushik and serprex May 12, 2026 05:26
@serprex serprex mentioned this pull request May 12, 2026
Copilot AI review requested due to automatic review settings May 12, 2026 20:27
@JoshDreamland JoshDreamland force-pushed the exception-barrier-cleanup branch from 94d250e to 0ce0477 Compare May 12, 2026 20:27
@JoshDreamland JoshDreamland changed the base branch from main to port-hook-path-to-c May 12, 2026 20:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread src/export/stats_exporter.cc
Comment on lines +395 to +399
// Install the C++ terminate handler before any other C++ work runs in this
// process. See PschTerminateHandler above for the rationale. Idempotent —
// a second call in the same process would just reinstall the same handler.
std::set_terminate(PschTerminateHandler);

Comment thread scripts/bump-version.sh
Comment on lines +41 to +45
jq --arg v "$VERSION" \
'.version = $v | .provides.pg_stat_ch.version = $v' \
"$META" > "$tmp"
mv "$tmp" "$META"
echo "updated $META -> $VERSION"
@JoshDreamland JoshDreamland force-pushed the port-hook-path-to-c branch from fde97e5 to eb7cc35 Compare May 12, 2026 21:05
@JoshDreamland JoshDreamland force-pushed the exception-barrier-cleanup branch from 0ce0477 to 4d2214d Compare May 12, 2026 21:07
@JoshDreamland JoshDreamland force-pushed the port-hook-path-to-c branch from eb7cc35 to 5566554 Compare May 12, 2026 21:44
@JoshDreamland JoshDreamland force-pushed the exception-barrier-cleanup branch from 4d2214d to 3e64292 Compare May 12, 2026 21:44
Copy link
Copy Markdown
Collaborator

@iskakaushik iskakaushik left a comment

Choose a reason for hiding this comment

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

good change!

@JoshDreamland JoshDreamland force-pushed the port-hook-path-to-c branch from 5566554 to 0cb3ee0 Compare May 13, 2026 01:12
@JoshDreamland JoshDreamland force-pushed the exception-barrier-cleanup branch from 3e64292 to fb45223 Compare May 13, 2026 01:12
@JoshDreamland JoshDreamland force-pushed the port-hook-path-to-c branch 2 times, most recently from 3a6176c to 369b69d Compare May 13, 2026 01:26
@JoshDreamland JoshDreamland force-pushed the exception-barrier-cleanup branch from fb45223 to 7b83369 Compare May 13, 2026 01:26
@JoshDreamland JoshDreamland force-pushed the port-hook-path-to-c branch from 369b69d to 68d74e5 Compare May 13, 2026 02:05
@JoshDreamland JoshDreamland force-pushed the exception-barrier-cleanup branch from 7b83369 to 8ea8b36 Compare May 13, 2026 02:05
@JoshDreamland JoshDreamland changed the title C++ exception barriers: brace-form + remaining C-ABI getters C++ exception barriers: remaining C-ABI getters May 13, 2026
Base automatically changed from port-hook-path-to-c to main May 13, 2026 14:45
serprex and others added 3 commits May 13, 2026 10:49
Add try / catch wrappers to the three extern "C" entry points that
were missing them: PschGetConsecutiveFailures, PschGetRetryDelayMs,
PschResetRetryState.  The virtual methods they call are trivial
integer accesses today, but they're part of the StatsExporter
interface; any future override that allocates could throw across the
bgworker's PG_TRY frame.  Match the pattern used by the surrounding
init/batch/shutdown entries: log, return a safe default (0 for the
int getters, no-op for the void resetter).  Also add a null-pointer
guard to PschGetConsecutiveFailures so it matches its sister getters.

Mark the live-RAII boundary in ExportEventsAsArrowInternal and
PschExportBatch where the ArrowBatchBuilder / std::vector<PschEvent>
are still on the stack above the post-export PG accounting calls
(pg_atomic_fetch_add_u64, PschRecordExportSuccess,
RecordExporterFailure).  None of those PG calls longjmp today
(LWLockAcquire is not interruptible; atomics are pure), so this is not
a current bug — but a future longjmping call there would skip C++
destructors and leak the Arrow/protobuf/ZSTD buffers (heap, not
palloc).  Load-bearing comment instead of restructure: tells anyone
adding a PG call here to scope the RAII tighter first.

Small polish from review:
- PschExportBatch's barrier comment now describes the C/C++ boundary
  rather than the specific PG_TRY frame (more accurate vs how the
  hazard actually surfaces).
- PschGetRetryDelayMs caches NumConsecutiveFailures() once instead of
  reading the same atomic three times.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Anything the explicit barriers in stats_exporter.cc miss would default
to std::terminate -> abort -> SIGABRT, which the postmaster treats as a
backend crash and uses to trigger DB-wide crash recovery (every
connection severed, WAL replayed).  Route uncaught C++ exceptions
through ereport(FATAL) instead so they become clean proc_exit(1) calls:
postmaster respawns just our bgworker on its bgw_restart_time.

Installation lives inside PschExporterInit rather than _PG_init.  After
the plugin layer's port to C99 (#88), _PG_init is a C function and
cannot call std::set_terminate; and the bgworker is the only process
that ever runs our C++ exception-throwing code paths, so installing in
its first C++ entry point is both necessary and sufficient.

This is a backstop, not a replacement.  The explicit catches in
PschExportBatch / ExportEventStats / ExportEventsAsArrow still run
first and let us survive an allocation hiccup without losing the
exporter.  PschExporterInit's FATAL catch is also preserved for its
specific, informative error message — the terminate handler would
otherwise log a generic "uncaught bad_alloc".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JoshDreamland JoshDreamland force-pushed the exception-barrier-cleanup branch from 8ea8b36 to 16bd888 Compare May 13, 2026 14:49
@JoshDreamland JoshDreamland merged commit a1c1aa9 into main May 13, 2026
19 checks passed
@JoshDreamland JoshDreamland deleted the exception-barrier-cleanup branch May 13, 2026 15:25
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.

4 participants