Skip to content

PostgreSQL: databasevm_step closes an SPI cursor after catching an error without a subtransaction abort (server crash) #71

Description

@andinux

databasevm_step (src/postgresql/database_postgresql.c) catches a PostgreSQL error in PG_CATCH, calls FlushErrorState(), and then keeps working with the SPI cursor: clear_fetch_batch(stmt) and close_portal(stmt), which calls SPI_cursor_close. No subtransaction is rolled back in between.

PostgreSQL releases resources such as LWLocks during (sub)transaction abort. Catching an error and continuing without one is only safe while the error was not raised while holding such a resource. When it was, the cleanup runs with the lock still held.

Observed crash

An assertion-enabled PostgreSQL 17 server terminated during a fragment apply:

TRAP: failed Assert("!LWLockHeldByMe(BufferDescriptorGetContentLock(buf))"), File: "bufmgr.c", Line: 2861
  ExceptionalConditionsigned ...
  ResourceOwnerRelease
  PortalDrop
  SPI_cursor_close
  cloudsync.so(databasevm_step)
  cloudsync.so(cloudsync_payload_apply)

The trigger was a serialization failure raised by SSI inside a heap scan, which happens while a buffer content lock is held. The backend aborted with signal 6 and the server restarted ("terminating any other active server processes").

A release build does not assert, but releasing a buffer pin while its content lock is still held is not safe either.

How it was reached

Applying pieces of one fragmented value from two concurrent SERIALIZABLE transactions, while the per-value advisory lock made the second one wait and read after the first committed. The lock is no longer taken under SERIALIZABLE (ce0557f), which is why the suite is green, but nothing prevents another 40001 or any other error raised under a buffer lock from reaching the same handler.

Suggested fix

Wrap the SPI work in a subtransaction (BeginInternalSubTransaction / RollbackAndReleaseCurrentSubTransaction) so that catching an error goes through a real abort, or re-throw and let the existing savepoints handle it. The cleanup after a caught error should not touch portals or tuple tables before the abort.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions