From 3b6cbfba2367eabc5f6bfbbdea1fb184aec76253 Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Tue, 7 Apr 2026 23:40:04 -0600 Subject: [PATCH] fix(pg): prevent debug assertion crash on cloudsync_init error path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In cloudsync_init_internal, when cloudsync_init_table returns an error (e.g. unsupported DWS/AWS algorithm), the error path called database_rollback_savepoint() which internally pushes a new active snapshot via database_refresh_snapshot(). The subsequent ereport(ERROR) longjmps through PG_CATCH → SPI_finish → PL/pgSQL exception handler, where EnsurePortalSnapshotExists() finds portal->portalSnapshot still set, triggering Assert(portal->portalSnapshot == NULL) in debug builds. Replace database_rollback_savepoint() with a direct call to RollbackAndReleaseCurrentSubTransaction(), skipping the snapshot refresh. Since we're about to propagate an error, PostgreSQL's error recovery handles snapshot cleanup automatically. Other callers of database_rollback_savepoint are safe because they either run on SQLite, return normally instead of raising ereport(ERROR), or disconnect SPI before the rollback. --- src/postgresql/cloudsync_postgresql.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/postgresql/cloudsync_postgresql.c b/src/postgresql/cloudsync_postgresql.c index 4860153..826bfa8 100644 --- a/src/postgresql/cloudsync_postgresql.c +++ b/src/postgresql/cloudsync_postgresql.c @@ -305,10 +305,19 @@ static bytea *cloudsync_init_internal (cloudsync_context *data, const char *tabl dbutils_settings_set_key_value(data, "schema", cur_schema); } } else { - // In case of error, rollback transaction + // In case of error, rollback sub-transaction and raise. + // We intentionally avoid database_rollback_savepoint() here + // because it calls database_refresh_snapshot() which pushes a + // new active snapshot. Pushing a snapshot after rollback and + // before ereport(ERROR) leaves portal->portalSnapshot non-NULL + // when PL/pgSQL's exception handler later calls + // EnsurePortalSnapshotExists(), triggering + // Assert(portal->portalSnapshot == NULL) on debug builds. char err[1024]; snprintf(err, sizeof(err), "%s", cloudsync_errmsg(data)); - database_rollback_savepoint(data, "cloudsync_init"); + if (GetCurrentTransactionNestLevel() > 1) { + RollbackAndReleaseCurrentSubTransaction(); + } ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("%s", err))); }