C++ exception barriers: remaining C-ABI getters#94
Conversation
There was a problem hiding this comment.
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/PschExportBatchto brace-formtry { ... } catch { ... }exception barriers. - Added exception barriers to remaining C-ABI retry/getter functions (
PschGetConsecutiveFailures,PschGetRetryDelayMs,PschResetRetryState) plus a null guard forPschGetConsecutiveFailures. - 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.
0a14144 to
077ac22
Compare
| // 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()); | ||
| } |
There was a problem hiding this comment.
can we use https://en.cppreference.com/cpp/error/terminate_handler to achieve same effect as elog FATAL?
There was a problem hiding this comment.
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.
94d250e to
0ce0477
Compare
| // 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); | ||
|
|
| jq --arg v "$VERSION" \ | ||
| '.version = $v | .provides.pg_stat_ch.version = $v' \ | ||
| "$META" > "$tmp" | ||
| mv "$tmp" "$META" | ||
| echo "updated $META -> $VERSION" |
fde97e5 to
eb7cc35
Compare
0ce0477 to
4d2214d
Compare
eb7cc35 to
5566554
Compare
4d2214d to
3e64292
Compare
5566554 to
0cb3ee0
Compare
3e64292 to
fb45223
Compare
3a6176c to
369b69d
Compare
fb45223 to
7b83369
Compare
369b69d to
68d74e5
Compare
7b83369 to
8ea8b36
Compare
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>
8ea8b36 to
16bd888
Compare
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, andPschExporterInit's catch handlers switched fromreset + return false(latent NPE on the bgworker's next loop) toereport(FATAL)(cleanproc_exit(1); postmaster respawns the worker without DB-wide crash recovery).a7d2ea9— fix: round out exception barriers. Wrap the remaining unguardedextern "C"getters (PschGetConsecutiveFailures,PschGetRetryDelayMs,PschResetRetryState) intry / catch. Drop a load-bearing comment inExportEventsAsArrowInternalandPschExportBatchmarking the live-RAII boundary above the post-export PG accounting calls. CacheNumConsecutiveFailures(); tighten the barrier comment.16bd888— feat: install C++ terminate handler. Backstop for anything the explicit barriers miss. Installed at the top ofPschExporterInit(the build lock from build: forbid C++ sources outside src/export/ #68 makes_PG_initC-only, so it can't hoststd::set_terminate). Converts uncaught C++ throws intoereport(FATAL)→proc_exit(1)so postmaster respawns just our bgworker instead of treatingSIGABRTas a backend crash. The explicit catches still run first; this only fires for what they miss.Test plan
🤖 Generated with Claude Code