This is an automated email from the ASF dual-hosted git repository.

oppenheimer01 pushed a commit to branch cbdb-postgres-merge
in repository https://gitbox.apache.org/repos/asf/cloudberry.git

commit 21a23dd63ad2104b5001a77e0bf54e9ca2948257
Author: Zhang Mingli <[email protected]>
AuthorDate: Thu Apr 2 18:21:09 2026 +0800

    Fix SIGSEGV in fsm_extend when vacuuming tables in non-default tablespace
    
    The commit df1e2ff ("Prevent CREATE TABLE from using dangling tablespace")
    added a call to TablespaceLockTuple() inside TablespaceCreateDbspace() for
    every non-default tablespace, including the common path where the 
per-database
    directory already exists.  That lock acquisition calls LockSharedObject(),
    which calls AcceptInvalidationMessages(), allowing pending sinval messages 
to
    be processed at an unexpected point deep inside smgrcreate().
    
    This creates a crash window during VACUUM of a heap table (including 
auxiliary
    tables such as the aoseg table of an AO relation) that lives in a 
non-default
    tablespace and has never been vacuumed before (so no FSM or VM fork exists 
yet):
    
      lazy_scan_heap
      -> visibilitymap_pin -> vm_readbuf -> vm_extend
         smgrcreate(VM fork) + CacheInvalidateSmgr()   <- queues 
SHAREDINVALSMGR_ID
      -> RecordPageWithFreeSpace -> fsm_readbuf -> fsm_extend
         smgrcreate(FSM fork) -> mdcreate
         -> TablespaceCreateDbspace [CBDB-specific for non-default tablespaces]
            -> TablespaceLockTuple -> LockSharedObject
               -> AcceptInvalidationMessages()
                  -> smgrclosenode() -> rel->rd_smgr = NULL
         rel->rd_smgr->smgr_cached_nblocks[FSM_FORKNUM] = ...
         -> SIGSEGV (NULL dereference) at freespace.c:637
    
    Fix: add RelationOpenSmgr(rel) after smgrcreate() in both fsm_extend()
    (freespace.c) and vm_extend() (visibilitymap.c).  RelationOpenSmgr is a
    no-op when rd_smgr is still valid, and re-opens the smgr handle if the
    sinval handler has closed it.  The identical guard already exists earlier
    in both functions for the LockRelationForExtension path.
    
    Add a regression test (vacuum_fsm_nondefault_tablespace) covering both a
    plain heap table and an AO table in a non-default tablespace.
---
 src/test/regress/expected/.gitignore               |  1 +
 src/test/regress/greenplum_schedule                |  1 +
 .../input/vacuum_fsm_nondefault_tablespace.source  | 54 +++++++++++++++++
 .../output/vacuum_fsm_nondefault_tablespace.source | 70 ++++++++++++++++++++++
 src/test/regress/sql/.gitignore                    |  1 +
 5 files changed, 127 insertions(+)

diff --git a/src/test/regress/expected/.gitignore 
b/src/test/regress/expected/.gitignore
index c837ca324d5..f625061dbd6 100644
--- a/src/test/regress/expected/.gitignore
+++ b/src/test/regress/expected/.gitignore
@@ -70,3 +70,4 @@
 /ao_unique_index_partition.out
 /bfv_copy.out
 /copy_encoding_error.out
+/vacuum_fsm_nondefault_tablespace.out
diff --git a/src/test/regress/greenplum_schedule 
b/src/test/regress/greenplum_schedule
index dbcde8bcc5c..84e8766844b 100755
--- a/src/test/regress/greenplum_schedule
+++ b/src/test/regress/greenplum_schedule
@@ -168,6 +168,7 @@ test: instr_in_shmem_verify
 test: partition_locking
 test: vacuum_gp
 # test: resource_queue_stat
+test: vacuum_fsm_nondefault_tablespace
 # background analyze may affect pgstat
 test: pg_stat
 test: bfv_partition qp_misc_rio
diff --git a/src/test/regress/input/vacuum_fsm_nondefault_tablespace.source 
b/src/test/regress/input/vacuum_fsm_nondefault_tablespace.source
new file mode 100644
index 00000000000..adce9ab77de
--- /dev/null
+++ b/src/test/regress/input/vacuum_fsm_nondefault_tablespace.source
@@ -0,0 +1,54 @@
+-- Test: VACUUM on a table in a non-default tablespace does not crash on first 
run.
+--
+-- Bug: SIGSEGV in fsm_extend() (freespace.c:637) when vacuuming a heap table
+-- (or an AO table's aoseg auxiliary table) that resides in a non-default
+-- tablespace for the very first time.
+--
+-- Root cause: commit "Prevent CREATE TABLE from using dangling tablespace"
+-- added TablespaceLockTuple() in TablespaceCreateDbspace() for non-default
+-- tablespaces. That call reaches AcceptInvalidationMessages() via
+-- LockSharedObject(), which processes a pending SHAREDINVALSMGR_ID message
+-- that vm_extend() had queued via CacheInvalidateSmgr(), nullifying
+-- rel->rd_smgr before fsm_extend() dereferences it at freespace.c:637.
+--
+-- Fix: added RelationOpenSmgr(rel) after smgrcreate() in both fsm_extend()
+-- (freespace.c) and vm_extend() (visibilitymap.c) so that rd_smgr is
+-- re-opened if the sinval handler closed it.
+
+CREATE TABLESPACE fsm_ts_test LOCATION '@testtablespace@';
+
+-- Case 1: plain heap table in non-default tablespace, first VACUUM.
+-- Before the fix this crashed with SIGSEGV at freespace.c:637:
+--   vm_extend()  -> CacheInvalidateSmgr  (queues SHAREDINVALSMGR_ID)
+--   fsm_extend() -> smgrcreate -> TablespaceCreateDbspace
+--                -> TablespaceLockTuple  -> AcceptInvalidationMessages
+--                -> processes SHAREDINVALSMGR_ID -> rel->rd_smgr = NULL
+--                -> rel->rd_smgr->smgr_cached_nblocks[...] = ...  SIGSEGV
+CREATE TABLE fsm_ts_heap (id int, val text)
+    TABLESPACE fsm_ts_test
+    DISTRIBUTED BY (id);
+INSERT INTO fsm_ts_heap SELECT i, repeat('x', 80) FROM generate_series(1, 500) 
i;
+VACUUM ANALYZE fsm_ts_heap;
+SELECT count(*) FROM fsm_ts_heap;
+-- Second VACUUM must also succeed (FSM/VM now exist, different code path).
+VACUUM ANALYZE fsm_ts_heap;
+SELECT count(*) FROM fsm_ts_heap;
+DROP TABLE fsm_ts_heap;
+
+-- Case 2: AO table in non-default tablespace.
+-- The crash occurs inside the recursive vacuum of the aoseg auxiliary table
+-- (which is also a heap table stored in the same non-default tablespace).
+CREATE TABLE fsm_ts_ao (id int, val text)
+    USING ao_row
+    TABLESPACE fsm_ts_test
+    DISTRIBUTED BY (id);
+INSERT INTO fsm_ts_ao SELECT i, repeat('y', 80) FROM generate_series(1, 500) i;
+VACUUM ANALYZE fsm_ts_ao;
+SELECT count(*) FROM fsm_ts_ao;
+-- Second VACUUM must also succeed.
+VACUUM ANALYZE fsm_ts_ao;
+SELECT count(*) FROM fsm_ts_ao;
+DROP TABLE fsm_ts_ao;
+
+-- Cleanup.
+DROP TABLESPACE fsm_ts_test;
diff --git a/src/test/regress/output/vacuum_fsm_nondefault_tablespace.source 
b/src/test/regress/output/vacuum_fsm_nondefault_tablespace.source
new file mode 100644
index 00000000000..fc2ff245691
--- /dev/null
+++ b/src/test/regress/output/vacuum_fsm_nondefault_tablespace.source
@@ -0,0 +1,70 @@
+-- Test: VACUUM on a table in a non-default tablespace does not crash on first 
run.
+--
+-- Bug: SIGSEGV in fsm_extend() (freespace.c:637) when vacuuming a heap table
+-- (or an AO table's aoseg auxiliary table) that resides in a non-default
+-- tablespace for the very first time.
+--
+-- Root cause: commit "Prevent CREATE TABLE from using dangling tablespace"
+-- added TablespaceLockTuple() in TablespaceCreateDbspace() for non-default
+-- tablespaces. That call reaches AcceptInvalidationMessages() via
+-- LockSharedObject(), which processes a pending SHAREDINVALSMGR_ID message
+-- that vm_extend() had queued via CacheInvalidateSmgr(), nullifying
+-- rel->rd_smgr before fsm_extend() dereferences it at freespace.c:637.
+--
+-- Fix: added RelationOpenSmgr(rel) after smgrcreate() in both fsm_extend()
+-- (freespace.c) and vm_extend() (visibilitymap.c) so that rd_smgr is
+-- re-opened if the sinval handler closed it.
+CREATE TABLESPACE fsm_ts_test LOCATION '@testtablespace@';
+-- Case 1: plain heap table in non-default tablespace, first VACUUM.
+-- Before the fix this crashed with SIGSEGV at freespace.c:637:
+--   vm_extend()  -> CacheInvalidateSmgr  (queues SHAREDINVALSMGR_ID)
+--   fsm_extend() -> smgrcreate -> TablespaceCreateDbspace
+--                -> TablespaceLockTuple  -> AcceptInvalidationMessages
+--                -> processes SHAREDINVALSMGR_ID -> rel->rd_smgr = NULL
+--                -> rel->rd_smgr->smgr_cached_nblocks[...] = ...  SIGSEGV
+CREATE TABLE fsm_ts_heap (id int, val text)
+    TABLESPACE fsm_ts_test
+    DISTRIBUTED BY (id);
+INSERT INTO fsm_ts_heap SELECT i, repeat('x', 80) FROM generate_series(1, 500) 
i;
+VACUUM ANALYZE fsm_ts_heap;
+SELECT count(*) FROM fsm_ts_heap;
+ count 
+-------
+   500
+(1 row)
+
+-- Second VACUUM must also succeed (FSM/VM now exist, different code path).
+VACUUM ANALYZE fsm_ts_heap;
+SELECT count(*) FROM fsm_ts_heap;
+ count 
+-------
+   500
+(1 row)
+
+DROP TABLE fsm_ts_heap;
+-- Case 2: AO table in non-default tablespace.
+-- The crash occurs inside the recursive vacuum of the aoseg auxiliary table
+-- (which is also a heap table stored in the same non-default tablespace).
+CREATE TABLE fsm_ts_ao (id int, val text)
+    USING ao_row
+    TABLESPACE fsm_ts_test
+    DISTRIBUTED BY (id);
+INSERT INTO fsm_ts_ao SELECT i, repeat('y', 80) FROM generate_series(1, 500) i;
+VACUUM ANALYZE fsm_ts_ao;
+SELECT count(*) FROM fsm_ts_ao;
+ count 
+-------
+   500
+(1 row)
+
+-- Second VACUUM must also succeed.
+VACUUM ANALYZE fsm_ts_ao;
+SELECT count(*) FROM fsm_ts_ao;
+ count 
+-------
+   500
+(1 row)
+
+DROP TABLE fsm_ts_ao;
+-- Cleanup.
+DROP TABLESPACE fsm_ts_test;
diff --git a/src/test/regress/sql/.gitignore b/src/test/regress/sql/.gitignore
index 9b5f3660fa7..3a340338616 100644
--- a/src/test/regress/sql/.gitignore
+++ b/src/test/regress/sql/.gitignore
@@ -64,3 +64,4 @@
 /ao_unique_index_partition.sql
 /bfv_copy.sql
 /copy_encoding_error.sql
+/vacuum_fsm_nondefault_tablespace.sql


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to