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

maxyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudberry.git


The following commit(s) were added to refs/heads/main by this push:
     new df1e2ff5ae7 Prevent CREATE TABLE from using dangling tablespace (#876)
df1e2ff5ae7 is described below

commit df1e2ff5ae7ecfda2f45f7b2f2ea82102ea4547e
Author: Hao Wu <gfphoeni...@gmail.com>
AuthorDate: Mon Jan 20 09:38:49 2025 +0800

    Prevent CREATE TABLE from using dangling tablespace (#876)
    
    When DROP TABLESPACE is running, it's possible to still use the dropping
    tablespace in CREATE TABLE. It's bad behavior that the table may use
    a dropped tablespace, which means that data files are stored out of
    the database.
    
    This commit controls creating files in a tablespace and dropping tablespace
    running in exclusive mode. The key timelines for dropping a tablespace is:
    1. Lock tuple of pg_tablespace in AccessExclusive mode.
    2. CommitTransaction.
    3. Remove directories of the tablespace.
    4. Release the lock of the tablespace tuple.
---
 src/backend/access/transam/xact.c                  |   4 +-
 src/backend/commands/tablespace.c                  |  29 ++++-
 src/backend/storage/lmgr/lmgr.c                    |  21 ++++
 src/include/storage/lmgr.h                         |   2 +
 .../concurrent_drop_truncate_tablespace.out        | 118 ++++++++++++++++-----
 .../sql/concurrent_drop_truncate_tablespace.sql    |  70 +++++++++---
 .../concurrent_drop_truncate_tablespace.out        | 118 ++++++++++++++++-----
 .../sql/concurrent_drop_truncate_tablespace.sql    |  70 +++++++++---
 8 files changed, 351 insertions(+), 81 deletions(-)

diff --git a/src/backend/access/transam/xact.c 
b/src/backend/access/transam/xact.c
index 00b0ece7647..1b73917e40f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2995,6 +2995,8 @@ CommitTransaction(void)
 
        AtEOXact_MultiXact();
 
+       AtCommit_TablespaceStorage();
+
        ResourceOwnerRelease(TopTransactionResourceOwner,
                                                 RESOURCE_RELEASE_LOCKS,
                                                 true, true);
@@ -3026,8 +3028,6 @@ CommitTransaction(void)
        if(Gp_role == GP_ROLE_DISPATCH || IS_SINGLENODE())
                MoveDbSessionLockRelease();
 
-       AtCommit_TablespaceStorage();
-
        /*
         * Send out notification signals to other backends (and do other
         * post-commit NOTIFY cleanup).  This must not happen until after our
diff --git a/src/backend/commands/tablespace.c 
b/src/backend/commands/tablespace.c
index 6d1342370e6..ef8bd610e0b 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -123,6 +123,24 @@ static void ensure_tablespace_directory_is_empty(const Oid 
tablespaceoid, const
 static void unlink_during_redo(Oid tablepace_oid_to_unlink);
 static void unlink_without_redo(Oid tablespace_oid_to_unlink);
 
+static bool
+TablespaceLockTuple(Oid tablespace_oid, LOCKMODE lockmode, bool wait)
+{
+       bool ok = true;
+
+       Assert(OidIsValid(tablespace_oid));
+       Assert(tablespace_oid != GLOBALTABLESPACE_OID);
+       Assert(tablespace_oid != DEFAULTTABLESPACE_OID);
+
+       if (wait)
+               LockSharedObject(TableSpaceRelationId, tablespace_oid, 0, 
lockmode);
+       else
+               ok = ConditionalLockSharedObject(TableSpaceRelationId, 
tablespace_oid,
+                                                                               
 0, lockmode);
+
+       return ok;
+}
+
 /*
  * Each database using a table space is isolated into its own name space
  * by a subdirectory named for the database OID.  On first creation of an
@@ -156,6 +174,9 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool 
isRedo)
        Assert(OidIsValid(spcNode));
        Assert(OidIsValid(dbNode));
 
+       if (spcNode != DEFAULTTABLESPACE_OID && !isRedo)
+               TablespaceLockTuple(spcNode, AccessShareLock, true);
+
        dir = GetDatabasePath(dbNode, spcNode);
 
        if (stat(dir, &st) < 0)
@@ -720,6 +741,13 @@ DropTableSpace(DropTableSpaceStmt *stmt)
        /* DROP hook for the tablespace being removed */
        InvokeObjectDropHook(TableSpaceRelationId, tablespaceoid, 0);
 
+       if (!TablespaceLockTuple(tablespaceoid, AccessExclusiveLock, false))
+               ereport(ERROR,
+                               (errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+                                errmsg("could not lock tablespace \"%s\"",
+                                               tablespacename)));
+       SIMPLE_FAULT_INJECTOR("drop_tablespace_after_acquire_lock");
+
        /*
         * Remove the pg_tablespace tuple (this will roll back if we fail below)
         */
@@ -795,7 +823,6 @@ DropTableSpace(DropTableSpaceStmt *stmt)
 
        /* We keep the lock on pg_tablespace until commit */
        table_close(rel, NoLock);
-       SIMPLE_FAULT_INJECTOR("AfterTablespaceCreateLockRelease");
 
        /*
         * If we are the QD, dispatch this DROP command to all the QEs
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index ad25cec28d7..01fef1dbfa2 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -1153,6 +1153,27 @@ LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
        AcceptInvalidationMessages();
 }
 
+/*
+ *             ConditionalLockSharedObject
+ *
+ * As above, but only lock if we can get the lock without blocking.
+ * Returns true iff the lock was acquired.
+ */
+bool
+ConditionalLockSharedObject(Oid classid, Oid objid, uint16 objsubid,
+                                                       LOCKMODE lockmode)
+{
+       LOCKTAG         tag;
+
+       SET_LOCKTAG_OBJECT(tag,
+                                          InvalidOid,
+                                          classid,
+                                          objid,
+                                          objsubid);
+
+       return (LockAcquire(&tag, lockmode, false, true) != 
LOCKACQUIRE_NOT_AVAIL);
+}
+
 /*
  *             UnlockSharedObject
  */
diff --git a/src/include/storage/lmgr.h b/src/include/storage/lmgr.h
index b873f0d46d7..353fdc8a4c5 100644
--- a/src/include/storage/lmgr.h
+++ b/src/include/storage/lmgr.h
@@ -103,6 +103,8 @@ extern void UnlockDatabaseObject(Oid classid, Oid objid, 
uint16 objsubid,
 /* Lock a shared-across-databases object (other than a relation) */
 extern void LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
                                                         LOCKMODE lockmode);
+extern bool ConditionalLockSharedObject(Oid classid, Oid objid, uint16 
objsubid,
+                                                        LOCKMODE lockmode);
 extern void UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid,
                                                           LOCKMODE lockmode);
 
diff --git 
a/src/test/isolation2/expected/concurrent_drop_truncate_tablespace.out 
b/src/test/isolation2/expected/concurrent_drop_truncate_tablespace.out
index 3d870054f41..501f188fc7d 100644
--- a/src/test/isolation2/expected/concurrent_drop_truncate_tablespace.out
+++ b/src/test/isolation2/expected/concurrent_drop_truncate_tablespace.out
@@ -1,5 +1,12 @@
 -- While a tablespace is being dropped, if any table is created
--- in the same tablespace, the data of that table should not be deleted
+-- in the same tablespace, only one query can be successful.
+-- The behavior guarantees that the table will never use a
+-- dropped or invalid tablespace.
+
+-- start_matchsubs
+-- m/ERROR:  could not create directory "pg_tblspc.*: No such file or 
directory/
+-- s/ERROR:  could not create directory "pg_tblspc.*: No such file or 
directory/ERROR:  could not create directory "pg_tblspc\/XXXX":  No such file 
or directory/
+-- end_matchsubs
 
 -- create a tablespace directory
 !\retcode rm -rf /tmp/concurrent_tblspace;
@@ -16,18 +23,36 @@
 CREATE TABLESPACE concurrent_tblspace LOCATION '/tmp/concurrent_tblspace';
 CREATE
 
--- suspend execution after TablespaceCreateLock is released
-SELECT gp_inject_fault('AfterTablespaceCreateLockRelease', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+-- test 1:
+-- when creating a table using a tablespace, after the tuple of tablespace
+-- is locked, the tablespace is not allowed to drop
+2: begin;
+BEGIN
+2: CREATE TABLE t_in_tablespace(a int, b int) TABLESPACE concurrent_tblspace;
+CREATE
+
+-- drop tablespace will fail: can't acuqire the lock
+DROP TABLESPACE concurrent_tblspace;
+ERROR:  could not lock tablespace "concurrent_tblspace"
+2: rollback;
+ROLLBACK
+
+-- test 2:
+-- if  DROP TABLESPACE acquires lock first and rollback, the blocking CREATE
+-- TABLE will be successful.
+
+-- suspend execution after tablespace lock is acquired
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
  gp_inject_fault 
 -----------------
  Success:        
  Success:        
  Success:        
 (3 rows)
-1&:DROP TABLESPACE concurrent_tblspace;  <waiting ...>
+1&: DROP TABLESPACE concurrent_tblspace;  <waiting ...>
 
 -- wait for the fault to be triggered
-SELECT gp_wait_until_triggered_fault('AfterTablespaceCreateLockRelease', 1, 
dbid) from gp_segment_configuration where content <> -1 and role='p';
+SELECT gp_wait_until_triggered_fault('drop_tablespace_after_acquire_lock', 1, 
dbid) from gp_segment_configuration where content <> -1 and role='p';
  gp_wait_until_triggered_fault 
 -------------------------------
  Success:                      
@@ -35,32 +60,75 @@ SELECT 
gp_wait_until_triggered_fault('AfterTablespaceCreateLockRelease', 1, dbid
  Success:                      
 (3 rows)
 
--- create a table in the same tablespace which is being dropped via a 
concurrent session
-CREATE TABLE drop_tablespace_tbl(a int, b int) TABLESPACE concurrent_tblspace 
DISTRIBUTED BY (a);
+2&: CREATE TABLE t_in_tablespace(a int, b int) TABLESPACE concurrent_tblspace; 
 <waiting ...>
+-- inject an error to ensure that the above DROP command will rollback
+SELECT gp_inject_fault('after_xlog_tblspc_drop', 'error', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+ gp_inject_fault 
+-----------------
+ Success:        
+ Success:        
+ Success:        
+(3 rows)
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'reset', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+ gp_inject_fault 
+-----------------
+ Success:        
+ Success:        
+ Success:        
+(3 rows)
+-- fail
+1<:  <... completed>
+ERROR:  fault triggered, fault name:'after_xlog_tblspc_drop' fault type:'error'
+-- success
+2<:  <... completed>
 CREATE
-INSERT INTO drop_tablespace_tbl SELECT i, i FROM generate_series(1,100)i;
-INSERT 100
--- reset the fault, drop tablespace command will not delete the data files on 
the tablespace
-SELECT gp_inject_fault('AfterTablespaceCreateLockRelease', 'reset', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+-- drop the above table, so the tablespace is empty.
+2: DROP TABLE t_in_tablespace;
+DROP
+SELECT gp_inject_fault('after_xlog_tblspc_drop', 'reset', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+ gp_inject_fault 
+-----------------
+ Success:        
+ Success:        
+ Success:        
+(3 rows)
+
+-- test 3:
+-- if DROP TABLESPACE acquires lock first and going to drop, any CREATE TABLE
+-- will fail
+
+-- suspend execution after tablespace lock is acquired
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+ gp_inject_fault 
+-----------------
+ Success:        
+ Success:        
+ Success:        
+(3 rows)
+1&: DROP TABLESPACE concurrent_tblspace;  <waiting ...>
+
+-- wait for the fault to be triggered
+SELECT gp_wait_until_triggered_fault('drop_tablespace_after_acquire_lock', 1, 
dbid) from gp_segment_configuration where content <> -1 and role='p';
+ gp_wait_until_triggered_fault 
+-------------------------------
+ Success:                      
+ Success:                      
+ Success:                      
+(3 rows)
+
+-- create a table in the same tablespace which is being dropped via a 
concurrent session
+2&:CREATE TABLE drop_tablespace_tbl(a int, b int) TABLESPACE 
concurrent_tblspace DISTRIBUTED BY (a);  <waiting ...>
+-- reset the fault, drop tablespace command will continue
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'reset', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
  gp_inject_fault 
 -----------------
  Success:        
  Success:        
  Success:        
 (3 rows)
+-- success
 1<:  <... completed>
 DROP
--- check data exists
-SELECT count(*) FROM drop_tablespace_tbl;
- count 
--------
- 100   
-(1 row)
--- move to another tablespace and check the data.
-ALTER TABLE drop_tablespace_tbl SET TABLESPACE pg_default;
-ALTER
-SELECT count(*) FROM drop_tablespace_tbl;
- count 
--------
- 100   
-(1 row)
+-- fail
+2<:  <... completed>
+ERROR:  could not create directory "pg_tblspc/33175/GPDB_1_302501601/32799": 
No such file or directory
diff --git a/src/test/isolation2/sql/concurrent_drop_truncate_tablespace.sql 
b/src/test/isolation2/sql/concurrent_drop_truncate_tablespace.sql
index 6ac4648b039..6c57a6488c3 100644
--- a/src/test/isolation2/sql/concurrent_drop_truncate_tablespace.sql
+++ b/src/test/isolation2/sql/concurrent_drop_truncate_tablespace.sql
@@ -1,5 +1,12 @@
 -- While a tablespace is being dropped, if any table is created
--- in the same tablespace, the data of that table should not be deleted
+-- in the same tablespace, only one query can be successful.
+-- The behavior guarantees that the table will never use a
+-- dropped or invalid tablespace.
+
+-- start_matchsubs
+-- m/ERROR:  could not create directory "pg_tblspc.*: No such file or 
directory/
+-- s/ERROR:  could not create directory "pg_tblspc.*: No such file or 
directory/ERROR:  could not create directory "pg_tblspc\/XXXX":  No such file 
or directory/
+-- end_matchsubs
 
 -- create a tablespace directory
 !\retcode rm -rf /tmp/concurrent_tblspace;
@@ -7,22 +14,57 @@
 
 CREATE TABLESPACE concurrent_tblspace LOCATION '/tmp/concurrent_tblspace';
 
--- suspend execution after TablespaceCreateLock is released
-SELECT gp_inject_fault('AfterTablespaceCreateLockRelease', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
-1&:DROP TABLESPACE concurrent_tblspace;
+-- test 1:
+-- when creating a table using a tablespace, after the tuple of tablespace
+-- is locked, the tablespace is not allowed to drop
+2: begin;
+2: CREATE TABLE t_in_tablespace(a int, b int) TABLESPACE concurrent_tblspace;
+
+-- drop tablespace will fail: can't acuqire the lock
+DROP TABLESPACE concurrent_tblspace;
+2: rollback;
+
+-- test 2:
+-- if  DROP TABLESPACE acquires lock first and rollback, the blocking CREATE
+-- TABLE will be successful.
+
+-- suspend execution after tablespace lock is acquired
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+1&: DROP TABLESPACE concurrent_tblspace;
+
+-- wait for the fault to be triggered
+SELECT gp_wait_until_triggered_fault('drop_tablespace_after_acquire_lock', 1, 
dbid)
+   from gp_segment_configuration where content <> -1 and role='p';
+
+2&: CREATE TABLE t_in_tablespace(a int, b int) TABLESPACE concurrent_tblspace;
+-- inject an error to ensure that the above DROP command will rollback
+SELECT gp_inject_fault('after_xlog_tblspc_drop', 'error', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'reset', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+-- fail
+1<:
+-- success
+2<:
+-- drop the above table, so the tablespace is empty.
+2: DROP TABLE t_in_tablespace;
+SELECT gp_inject_fault('after_xlog_tblspc_drop', 'reset', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+
+-- test 3:
+-- if DROP TABLESPACE acquires lock first and going to drop, any CREATE TABLE
+-- will fail
+
+-- suspend execution after tablespace lock is acquired
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+1&: DROP TABLESPACE concurrent_tblspace;
 
 -- wait for the fault to be triggered
-SELECT gp_wait_until_triggered_fault('AfterTablespaceCreateLockRelease', 1, 
dbid)
+SELECT gp_wait_until_triggered_fault('drop_tablespace_after_acquire_lock', 1, 
dbid)
    from gp_segment_configuration where content <> -1 and role='p';
 
 -- create a table in the same tablespace which is being dropped via a 
concurrent session
-CREATE TABLE drop_tablespace_tbl(a int, b int) TABLESPACE concurrent_tblspace 
DISTRIBUTED BY (a);
-INSERT INTO drop_tablespace_tbl SELECT i, i FROM generate_series(1,100)i;
--- reset the fault, drop tablespace command will not delete the data files on 
the tablespace
-SELECT gp_inject_fault('AfterTablespaceCreateLockRelease', 'reset', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+2&:CREATE TABLE drop_tablespace_tbl(a int, b int) TABLESPACE 
concurrent_tblspace DISTRIBUTED BY (a);
+-- reset the fault, drop tablespace command will continue
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'reset', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+-- success
 1<:
--- check data exists
-SELECT count(*) FROM drop_tablespace_tbl;
--- move to another tablespace and check the data.
-ALTER TABLE drop_tablespace_tbl SET TABLESPACE pg_default;
-SELECT count(*) FROM drop_tablespace_tbl;
+-- fail
+2<:
diff --git 
a/src/test/singlenode_isolation2/expected/concurrent_drop_truncate_tablespace.out
 
b/src/test/singlenode_isolation2/expected/concurrent_drop_truncate_tablespace.out
index 3d870054f41..501f188fc7d 100644
--- 
a/src/test/singlenode_isolation2/expected/concurrent_drop_truncate_tablespace.out
+++ 
b/src/test/singlenode_isolation2/expected/concurrent_drop_truncate_tablespace.out
@@ -1,5 +1,12 @@
 -- While a tablespace is being dropped, if any table is created
--- in the same tablespace, the data of that table should not be deleted
+-- in the same tablespace, only one query can be successful.
+-- The behavior guarantees that the table will never use a
+-- dropped or invalid tablespace.
+
+-- start_matchsubs
+-- m/ERROR:  could not create directory "pg_tblspc.*: No such file or 
directory/
+-- s/ERROR:  could not create directory "pg_tblspc.*: No such file or 
directory/ERROR:  could not create directory "pg_tblspc\/XXXX":  No such file 
or directory/
+-- end_matchsubs
 
 -- create a tablespace directory
 !\retcode rm -rf /tmp/concurrent_tblspace;
@@ -16,18 +23,36 @@
 CREATE TABLESPACE concurrent_tblspace LOCATION '/tmp/concurrent_tblspace';
 CREATE
 
--- suspend execution after TablespaceCreateLock is released
-SELECT gp_inject_fault('AfterTablespaceCreateLockRelease', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+-- test 1:
+-- when creating a table using a tablespace, after the tuple of tablespace
+-- is locked, the tablespace is not allowed to drop
+2: begin;
+BEGIN
+2: CREATE TABLE t_in_tablespace(a int, b int) TABLESPACE concurrent_tblspace;
+CREATE
+
+-- drop tablespace will fail: can't acuqire the lock
+DROP TABLESPACE concurrent_tblspace;
+ERROR:  could not lock tablespace "concurrent_tblspace"
+2: rollback;
+ROLLBACK
+
+-- test 2:
+-- if  DROP TABLESPACE acquires lock first and rollback, the blocking CREATE
+-- TABLE will be successful.
+
+-- suspend execution after tablespace lock is acquired
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
  gp_inject_fault 
 -----------------
  Success:        
  Success:        
  Success:        
 (3 rows)
-1&:DROP TABLESPACE concurrent_tblspace;  <waiting ...>
+1&: DROP TABLESPACE concurrent_tblspace;  <waiting ...>
 
 -- wait for the fault to be triggered
-SELECT gp_wait_until_triggered_fault('AfterTablespaceCreateLockRelease', 1, 
dbid) from gp_segment_configuration where content <> -1 and role='p';
+SELECT gp_wait_until_triggered_fault('drop_tablespace_after_acquire_lock', 1, 
dbid) from gp_segment_configuration where content <> -1 and role='p';
  gp_wait_until_triggered_fault 
 -------------------------------
  Success:                      
@@ -35,32 +60,75 @@ SELECT 
gp_wait_until_triggered_fault('AfterTablespaceCreateLockRelease', 1, dbid
  Success:                      
 (3 rows)
 
--- create a table in the same tablespace which is being dropped via a 
concurrent session
-CREATE TABLE drop_tablespace_tbl(a int, b int) TABLESPACE concurrent_tblspace 
DISTRIBUTED BY (a);
+2&: CREATE TABLE t_in_tablespace(a int, b int) TABLESPACE concurrent_tblspace; 
 <waiting ...>
+-- inject an error to ensure that the above DROP command will rollback
+SELECT gp_inject_fault('after_xlog_tblspc_drop', 'error', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+ gp_inject_fault 
+-----------------
+ Success:        
+ Success:        
+ Success:        
+(3 rows)
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'reset', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+ gp_inject_fault 
+-----------------
+ Success:        
+ Success:        
+ Success:        
+(3 rows)
+-- fail
+1<:  <... completed>
+ERROR:  fault triggered, fault name:'after_xlog_tblspc_drop' fault type:'error'
+-- success
+2<:  <... completed>
 CREATE
-INSERT INTO drop_tablespace_tbl SELECT i, i FROM generate_series(1,100)i;
-INSERT 100
--- reset the fault, drop tablespace command will not delete the data files on 
the tablespace
-SELECT gp_inject_fault('AfterTablespaceCreateLockRelease', 'reset', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+-- drop the above table, so the tablespace is empty.
+2: DROP TABLE t_in_tablespace;
+DROP
+SELECT gp_inject_fault('after_xlog_tblspc_drop', 'reset', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+ gp_inject_fault 
+-----------------
+ Success:        
+ Success:        
+ Success:        
+(3 rows)
+
+-- test 3:
+-- if DROP TABLESPACE acquires lock first and going to drop, any CREATE TABLE
+-- will fail
+
+-- suspend execution after tablespace lock is acquired
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+ gp_inject_fault 
+-----------------
+ Success:        
+ Success:        
+ Success:        
+(3 rows)
+1&: DROP TABLESPACE concurrent_tblspace;  <waiting ...>
+
+-- wait for the fault to be triggered
+SELECT gp_wait_until_triggered_fault('drop_tablespace_after_acquire_lock', 1, 
dbid) from gp_segment_configuration where content <> -1 and role='p';
+ gp_wait_until_triggered_fault 
+-------------------------------
+ Success:                      
+ Success:                      
+ Success:                      
+(3 rows)
+
+-- create a table in the same tablespace which is being dropped via a 
concurrent session
+2&:CREATE TABLE drop_tablespace_tbl(a int, b int) TABLESPACE 
concurrent_tblspace DISTRIBUTED BY (a);  <waiting ...>
+-- reset the fault, drop tablespace command will continue
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'reset', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
  gp_inject_fault 
 -----------------
  Success:        
  Success:        
  Success:        
 (3 rows)
+-- success
 1<:  <... completed>
 DROP
--- check data exists
-SELECT count(*) FROM drop_tablespace_tbl;
- count 
--------
- 100   
-(1 row)
--- move to another tablespace and check the data.
-ALTER TABLE drop_tablespace_tbl SET TABLESPACE pg_default;
-ALTER
-SELECT count(*) FROM drop_tablespace_tbl;
- count 
--------
- 100   
-(1 row)
+-- fail
+2<:  <... completed>
+ERROR:  could not create directory "pg_tblspc/33175/GPDB_1_302501601/32799": 
No such file or directory
diff --git 
a/src/test/singlenode_isolation2/sql/concurrent_drop_truncate_tablespace.sql 
b/src/test/singlenode_isolation2/sql/concurrent_drop_truncate_tablespace.sql
index 6ac4648b039..6c57a6488c3 100644
--- a/src/test/singlenode_isolation2/sql/concurrent_drop_truncate_tablespace.sql
+++ b/src/test/singlenode_isolation2/sql/concurrent_drop_truncate_tablespace.sql
@@ -1,5 +1,12 @@
 -- While a tablespace is being dropped, if any table is created
--- in the same tablespace, the data of that table should not be deleted
+-- in the same tablespace, only one query can be successful.
+-- The behavior guarantees that the table will never use a
+-- dropped or invalid tablespace.
+
+-- start_matchsubs
+-- m/ERROR:  could not create directory "pg_tblspc.*: No such file or 
directory/
+-- s/ERROR:  could not create directory "pg_tblspc.*: No such file or 
directory/ERROR:  could not create directory "pg_tblspc\/XXXX":  No such file 
or directory/
+-- end_matchsubs
 
 -- create a tablespace directory
 !\retcode rm -rf /tmp/concurrent_tblspace;
@@ -7,22 +14,57 @@
 
 CREATE TABLESPACE concurrent_tblspace LOCATION '/tmp/concurrent_tblspace';
 
--- suspend execution after TablespaceCreateLock is released
-SELECT gp_inject_fault('AfterTablespaceCreateLockRelease', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
-1&:DROP TABLESPACE concurrent_tblspace;
+-- test 1:
+-- when creating a table using a tablespace, after the tuple of tablespace
+-- is locked, the tablespace is not allowed to drop
+2: begin;
+2: CREATE TABLE t_in_tablespace(a int, b int) TABLESPACE concurrent_tblspace;
+
+-- drop tablespace will fail: can't acuqire the lock
+DROP TABLESPACE concurrent_tblspace;
+2: rollback;
+
+-- test 2:
+-- if  DROP TABLESPACE acquires lock first and rollback, the blocking CREATE
+-- TABLE will be successful.
+
+-- suspend execution after tablespace lock is acquired
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+1&: DROP TABLESPACE concurrent_tblspace;
+
+-- wait for the fault to be triggered
+SELECT gp_wait_until_triggered_fault('drop_tablespace_after_acquire_lock', 1, 
dbid)
+   from gp_segment_configuration where content <> -1 and role='p';
+
+2&: CREATE TABLE t_in_tablespace(a int, b int) TABLESPACE concurrent_tblspace;
+-- inject an error to ensure that the above DROP command will rollback
+SELECT gp_inject_fault('after_xlog_tblspc_drop', 'error', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'reset', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+-- fail
+1<:
+-- success
+2<:
+-- drop the above table, so the tablespace is empty.
+2: DROP TABLE t_in_tablespace;
+SELECT gp_inject_fault('after_xlog_tblspc_drop', 'reset', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+
+-- test 3:
+-- if DROP TABLESPACE acquires lock first and going to drop, any CREATE TABLE
+-- will fail
+
+-- suspend execution after tablespace lock is acquired
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'suspend', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+1&: DROP TABLESPACE concurrent_tblspace;
 
 -- wait for the fault to be triggered
-SELECT gp_wait_until_triggered_fault('AfterTablespaceCreateLockRelease', 1, 
dbid)
+SELECT gp_wait_until_triggered_fault('drop_tablespace_after_acquire_lock', 1, 
dbid)
    from gp_segment_configuration where content <> -1 and role='p';
 
 -- create a table in the same tablespace which is being dropped via a 
concurrent session
-CREATE TABLE drop_tablespace_tbl(a int, b int) TABLESPACE concurrent_tblspace 
DISTRIBUTED BY (a);
-INSERT INTO drop_tablespace_tbl SELECT i, i FROM generate_series(1,100)i;
--- reset the fault, drop tablespace command will not delete the data files on 
the tablespace
-SELECT gp_inject_fault('AfterTablespaceCreateLockRelease', 'reset', dbid) FROM 
gp_segment_configuration WHERE content <> -1 and role='p';
+2&:CREATE TABLE drop_tablespace_tbl(a int, b int) TABLESPACE 
concurrent_tblspace DISTRIBUTED BY (a);
+-- reset the fault, drop tablespace command will continue
+SELECT gp_inject_fault('drop_tablespace_after_acquire_lock', 'reset', dbid) 
FROM gp_segment_configuration WHERE content <> -1 and role='p';
+-- success
 1<:
--- check data exists
-SELECT count(*) FROM drop_tablespace_tbl;
--- move to another tablespace and check the data.
-ALTER TABLE drop_tablespace_tbl SET TABLESPACE pg_default;
-SELECT count(*) FROM drop_tablespace_tbl;
+-- fail
+2<:


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cloudberry.apache.org
For additional commands, e-mail: commits-h...@cloudberry.apache.org

Reply via email to