From 2f8e67894cdd0e16f40255617b9674c119587701 Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Wed, 18 Mar 2026 03:14:39 +0800 Subject: [PATCH 1/3] Fix SIGSEGV on segments when creating in-place tablespace When CREATE TABLESPACE ... LOCATION '' is dispatched from QD to QE, the serialization converts the empty string to NULL. On the segment, pstrdup(stmt->location) crashes with SIGSEGV because stmt->location is NULL. Add a NULL guard to treat NULL the same as empty string, preserving in-place tablespace semantics. Fixes https://github.com/apache/cloudberry/issues/1627 --- src/backend/commands/tablespace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index 2416522d016..00fa8bcfb2a 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -328,7 +328,7 @@ CreateTableSpace(CreateTableSpaceStmt *stmt) } if (!location) - location = pstrdup(stmt->location); + location = pstrdup(stmt->location ? stmt->location : ""); if (stmt->filehandler) fileHandler = pstrdup(stmt->filehandler); From 0984db5e610e0126e1b8c4f9686623379295d213 Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Wed, 18 Mar 2026 03:15:50 +0800 Subject: [PATCH 2/3] Fix session lock leak in ALTER DATABASE SET TABLESPACE for utility mode movedb() acquires a session-level AccessExclusiveLock on the database via MoveDbSessionLockAcquire(). The release in CommitTransaction() only checked for GP_ROLE_DISPATCH and IS_SINGLENODE(), missing GP_ROLE_UTILITY. This caused the lock to leak in standalone backends (e.g. TAP tests), triggering a proc.c assertion failure at exit: FailedAssertion("SHMQueueEmpty(&(MyProc->myProcLocks[i]))") Add GP_ROLE_UTILITY to the release condition. Also fix a spurious "could not read symbolic link" log message when dropping in-place tablespaces: readlink() on a directory returns EINVAL, which is expected and can be safely skipped. Fixes https://github.com/apache/cloudberry/issues/1626 --- src/backend/access/transam/xact.c | 16 +++++++++------- src/backend/commands/tablespace.c | 15 ++++++++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index ed655baf989..f8bfa78b28e 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -3036,13 +3036,15 @@ CommitTransaction(void) DoPendingDbDeletes(true); /* - * Only QD holds the session level lock this long for a movedb operation. - * This is to prevent another transaction from moving database objects into - * the source database oid directory while it is being deleted. We don't - * worry about aborts as we release session level locks automatically during - * an abort as opposed to a commit. - */ - if(Gp_role == GP_ROLE_DISPATCH || IS_SINGLENODE()) + * Release the session level lock held for a movedb operation. This is to + * prevent another transaction from moving database objects into the source + * database oid directory while it is being deleted. We don't worry about + * aborts as we release session level locks automatically during an abort + * as opposed to a commit. We must also release in utility mode (e.g. + * standalone backends used in TAP tests). + */ + if (Gp_role == GP_ROLE_DISPATCH || Gp_role == GP_ROLE_UTILITY || + IS_SINGLENODE()) MoveDbSessionLockRelease(); /* diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index 00fa8bcfb2a..e6822521694 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -1230,14 +1230,19 @@ destroy_tablespace_directories(Oid tablespaceoid, bool redo) linkloc = pstrdup(linkloc_with_version_dir); get_parent_directory(linkloc); - /* Remove the symlink target directory if it exists or is valid. */ + /* + * Remove the symlink target directory if it exists or is valid. + * If linkloc is a directory (e.g. in-place tablespace), readlink() + * will fail with EINVAL, which we can safely skip. + */ rllen = readlink(linkloc, link_target_dir, sizeof(link_target_dir)); if(rllen < 0) { - ereport(redo ? LOG : ERROR, - (errcode_for_file_access(), - errmsg("could not read symbolic link \"%s\": %m", - linkloc))); + if (errno != EINVAL) + ereport(redo ? LOG : ERROR, + (errcode_for_file_access(), + errmsg("could not read symbolic link \"%s\": %m", + linkloc))); } else if(rllen >= sizeof(link_target_dir)) { From c09c1a87ec8d5cfd4497dc3ef6261a44420c6a64 Mon Sep 17 00:00:00 2001 From: Jianghua Yang Date: Wed, 18 Mar 2026 07:45:52 +0800 Subject: [PATCH 3/3] Fix PostgresNode.pm for TAP tests on CBDB Two fixes: - Use TestLib::slurp_file instead of PostgreSQL::Test::Utils::slurp_file in adjust_conf(). PostgresNode.pm only imports TestLib, not PostgreSQL::Test::Utils, so the latter is undefined and causes t/101_restore_point_and_startup_pause to fail. - Replace cp with install -m 644 in enable_archiving() archive_command. coreutils 8.32 (Rocky 8, Ubuntu 22.04) uses copy_file_range() in cp which crashes on Docker overlayfs. install does not use copy_file_range(), avoiding the crash. Also add ic-recovery test suite to rocky8 and deb CI pipelines. --- .github/workflows/build-cloudberry-rocky8.yml | 4 +++ .../cloudberry/scripts/parse-results.pl | 31 ++++++++++++++++--- src/test/perl/PostgresNode.pm | 4 +-- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-cloudberry-rocky8.yml b/.github/workflows/build-cloudberry-rocky8.yml index 2abf88060e3..e0936c725c8 100644 --- a/.github/workflows/build-cloudberry-rocky8.yml +++ b/.github/workflows/build-cloudberry-rocky8.yml @@ -339,6 +339,10 @@ jobs: }, {"test":"ic-cbdb-parallel", "make_configs":["src/test/regress:installcheck-cbdb-parallel"] + }, + {"test":"ic-recovery", + "make_configs":["src/test/recovery:installcheck"], + "enable_core_check":false } ] }' diff --git a/devops/build/automation/cloudberry/scripts/parse-results.pl b/devops/build/automation/cloudberry/scripts/parse-results.pl index d09085d5fb9..2c754bcae9d 100755 --- a/devops/build/automation/cloudberry/scripts/parse-results.pl +++ b/devops/build/automation/cloudberry/scripts/parse-results.pl @@ -110,7 +110,7 @@ my @ignored_test_list = (); while (<$fh>) { - # Match the summary lines + # Match the summary lines (pg_regress format) if (/All (\d+) tests passed\./) { $status = 'passed'; $total_tests = $1; @@ -132,8 +132,22 @@ $status = 'failed'; $failed_tests = $1 - $3; $ignored_tests = $3; - $total_tests = $2; - $passed_tests = $2 - $1; + + # TAP/prove summary format: "Files=N, Tests=N, ..." + } elsif (/^Files=\d+, Tests=(\d+),/) { + $total_tests = $1; + + # TAP/prove result: "Result: PASS" or "Result: FAIL" + } elsif (/^Result: PASS/) { + $status = 'passed'; + $passed_tests = $total_tests; + $failed_tests = 0; + } elsif (/^Result: FAIL/) { + $status = 'failed'; + + # TAP individual test failure: " t/xxx.pl (Wstat: ...)" + } elsif (/^\s+(t\/\S+\.pl)\s+\(Wstat:/) { + push @failed_test_list, $1; } # Capture failed tests @@ -150,8 +164,15 @@ # Close the log file close $fh; -# Validate failed test count matches found test names -if ($status eq 'failed' && scalar(@failed_test_list) != $failed_tests) { +# For TAP format, derive failed/passed counts from collected test names +if ($status eq 'failed' && $failed_tests == 0 && scalar(@failed_test_list) > 0) { + $failed_tests = scalar(@failed_test_list); + $passed_tests = $total_tests - $failed_tests if $total_tests > 0; +} + +# Validate failed test count matches found test names (pg_regress format only) +if ($status eq 'failed' && $failed_tests > 0 && scalar(@failed_test_list) > 0 + && scalar(@failed_test_list) != $failed_tests) { print "Error: Found $failed_tests failed tests in summary but found " . scalar(@failed_test_list) . " failed test names\n"; print "Failed test names found:\n"; foreach my $test (@failed_test_list) { diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index 241ed8d49e8..e3010870f35 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -632,7 +632,7 @@ sub adjust_conf my $conffile = $self->data_dir . '/' . $filename; - my $contents = PostgreSQL::Test::Utils::slurp_file($conffile); + my $contents = TestLib::slurp_file($conffile); my @lines = split(/\n/, $contents); my @result; my $eq = $skip_equals ? '' : '= '; @@ -1220,7 +1220,7 @@ sub enable_archiving my $copy_command = $TestLib::windows_os ? qq{copy "%p" "$path\\\\%f"} - : qq{cp "%p" "$path/%f"}; + : qq{install -m 644 "%p" "$path/%f"}; # Enable archive_mode and archive_command on node $self->append_conf(