This is an automated email from the ASF dual-hosted git repository. woblerr pushed a commit to branch upgrade-go-1.25 in repository https://gitbox.apache.org/repos/asf/cloudberry-backup.git
commit 5253c120db68299176cb361bf0c35d07d062f27b Author: woblerr <[email protected]> AuthorDate: Fri Jun 26 00:02:22 2026 +0300 Fix deadlock e2e test transaction handling. The test held locks with raw BEGIN/COMMIT SQL on DBConn and then reused the same connection for polling. With pgx v5 this can release the raw transaction lock, so gpbackup was not blocked and the test observed 0 deadlock traps. Use explicit DBConn transactions, separate polling connections, wait for goroutines before closing connections, and wait for the expected 2 blocked AccessExclusiveLock requests. --- end_to_end/locks_test.go | 61 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/end_to_end/locks_test.go b/end_to_end/locks_test.go index 8b7ee35d..b05b1d17 100644 --- a/end_to_end/locks_test.go +++ b/end_to_end/locks_test.go @@ -3,6 +3,7 @@ package end_to_end_test import ( "fmt" "os/exec" + "sync" "time" "github.com/apache/cloudberry-backup/backup" @@ -290,7 +291,11 @@ var _ = Describe("Deadlock handling", func() { } // Acquire AccessExclusiveLock on public.foo to block gpbackup when it attempts // to grab AccessShareLocks before its metadata dump section. - backupConn.MustExec("BEGIN; LOCK TABLE public.foo IN ACCESS EXCLUSIVE MODE") + initialLockConn := testutils.SetupTestDbConn("testdb") + defer initialLockConn.Close() + initialLockConn.MustBegin() + defer func() { _ = initialLockConn.Rollback() }() + initialLockConn.MustExec("LOCK TABLE public.foo IN ACCESS EXCLUSIVE MODE") args := []string{ "--dbname", "testdb", @@ -298,13 +303,25 @@ var _ = Describe("Deadlock handling", func() { "--jobs", "2", "--verbose"} cmd := exec.Command(gpbackupPath, args...) + + var wg sync.WaitGroup + releaseTriggerLock := make(chan struct{}) + var releaseTriggerLockOnce sync.Once + defer func() { + releaseTriggerLockOnce.Do(func() { close(releaseTriggerLock) }) + wg.Wait() + }() + // Concurrently wait for gpbackup to block when it requests an AccessShareLock on public.foo. Once // that happens, acquire an AccessExclusiveLock on pg_catalog.pg_trigger to block gpbackup during its // trigger metadata dump. Then release the initial AccessExclusiveLock on public.foo (from the // beginning of the test) to unblock gpbackup and let gpbackup move forward to the trigger metadata dump. - anotherConn := testutils.SetupTestDbConn("testdb") - defer anotherConn.Close() + wg.Add(1) go func() { + defer wg.Done() + triggerLockConn := testutils.SetupTestDbConn("testdb") + defer triggerLockConn.Close() + // Query to see if gpbackup's AccessShareLock request on public.foo is blocked checkLockQuery := `SELECT count(*) FROM pg_locks l, pg_class c, pg_namespace n WHERE l.relation = c.oid AND n.oid = c.relnamespace AND n.nspname = 'public' AND c.relname = 'foo' AND l.granted = 'f' AND l.mode = 'AccessShareLock'` @@ -312,7 +329,7 @@ var _ = Describe("Deadlock handling", func() { var gpbackupBlockedLockCount int iterations := 100 for iterations > 0 { - _ = anotherConn.Get(&gpbackupBlockedLockCount, checkLockQuery) + _ = triggerLockConn.Get(&gpbackupBlockedLockCount, checkLockQuery) if gpbackupBlockedLockCount < 1 { time.Sleep(100 * time.Millisecond) iterations-- @@ -325,8 +342,12 @@ var _ = Describe("Deadlock handling", func() { // during the trigger metadata dump so that the test can queue a bunch of // AccessExclusiveLock requests against the test tables. Afterwards, release the // AccessExclusiveLock on public.foo to let gpbackup go to the trigger metadata dump. - anotherConn.MustExec(`BEGIN; LOCK TABLE pg_catalog.pg_trigger IN ACCESS EXCLUSIVE MODE`) - backupConn.MustExec("COMMIT") + triggerLockConn.MustBegin() + defer func() { _ = triggerLockConn.Rollback() }() + triggerLockConn.MustExec(`LOCK TABLE pg_catalog.pg_trigger IN ACCESS EXCLUSIVE MODE`) + initialLockConn.MustCommit() + <-releaseTriggerLock + triggerLockConn.MustCommit() }() // Concurrently wait for gpbackup to block on the trigger metadata dump section. Once we @@ -336,7 +357,9 @@ var _ = Describe("Deadlock handling", func() { "schema2.ao1", "schema2.ao2", "schema2.foo2", "schema2.foo3", "schema2.returns"} lockedTables := []string{`public."FOObar"`, "public.foo"} for _, lockedTable := range lockedTables { + wg.Add(1) go func(lockedTable string) { + defer wg.Done() accessExclusiveLockConn := testutils.SetupTestDbConn("testdb") defer accessExclusiveLockConn.Close() @@ -357,23 +380,32 @@ var _ = Describe("Deadlock handling", func() { } // Queue an AccessExclusiveLock request on a test table which will later // result in a detected deadlock during the gpbackup data dump section. - accessExclusiveLockConn.MustExec(fmt.Sprintf(`BEGIN; LOCK TABLE %s IN ACCESS EXCLUSIVE MODE; COMMIT`, lockedTable)) + accessExclusiveLockConn.MustBegin() + defer func() { _ = accessExclusiveLockConn.Rollback() }() + accessExclusiveLockConn.MustExec(fmt.Sprintf(`LOCK TABLE %s IN ACCESS EXCLUSIVE MODE`, lockedTable)) + accessExclusiveLockConn.MustCommit() }(lockedTable) } - // Concurrently wait for all AccessExclusiveLock requests on all 10 test tables to block. + // Concurrently wait for all AccessExclusiveLock requests on all locked test tables to block. // Once that happens, release the AccessExclusiveLock on pg_catalog.pg_trigger to unblock // gpbackup and let gpbackup move forward to the data dump section. - var accessExclBlockedLockCount int + accessExclBlockedLockCountChan := make(chan int, 1) + wg.Add(1) go func() { + defer wg.Done() + accessExclBlockedLockConn := testutils.SetupTestDbConn("testdb") + defer accessExclBlockedLockConn.Close() + // Query to check for ungranted AccessExclusiveLock requests on our test tables checkLockQuery := `SELECT count(*) FROM pg_locks WHERE granted = 'f' AND mode = 'AccessExclusiveLock'` // Wait up to 10 seconds + var accessExclBlockedLockCount int iterations := 100 for iterations > 0 { - _ = backupConn.Get(&accessExclBlockedLockCount, checkLockQuery) - if accessExclBlockedLockCount < 9 { + _ = accessExclBlockedLockConn.Get(&accessExclBlockedLockCount, checkLockQuery) + if accessExclBlockedLockCount < len(lockedTables) { time.Sleep(100 * time.Millisecond) iterations-- } else { @@ -382,12 +414,15 @@ var _ = Describe("Deadlock handling", func() { } // Unblock gpbackup by releasing AccessExclusiveLock on pg_catalog.pg_trigger - anotherConn.MustExec("COMMIT") + accessExclBlockedLockCountChan <- accessExclBlockedLockCount + releaseTriggerLockOnce.Do(func() { close(releaseTriggerLock) }) }() // gpbackup has finished - output, _ := cmd.CombinedOutput() + output, err := cmd.CombinedOutput() stdout := string(output) + accessExclBlockedLockCount := <-accessExclBlockedLockCountChan + Expect(err).ToNot(HaveOccurred(), "%s", stdout) // Check that 2 deadlock traps were placed during the test Expect(accessExclBlockedLockCount).To(Equal(2)) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
