Skip to content

Commit 5fc78ef

Browse files
committed
Avoid throwing ERROR during WAL replay of DROP TABLESPACE.
Although we will not even issue an XLOG_TBLSPC_DROP WAL record unless removal of the tablespace's directories succeeds, that does not guarantee that the same operation will succeed during WAL replay. Foreseeable reasons for it to fail include temp files created in the tablespace by Hot Standby backends, wrong directory permissions on a standby server, etc etc. The original coding threw ERROR if replay failed to remove the directories, but that is a serious overreaction. Throwing an error aborts recovery, and worse means that manual intervention will be needed to get the database to start again, since otherwise the same error will recur on subsequent attempts to replay the same WAL record. And the consequence of failing to remove the directories is only that some probably-small amount of disk space is wasted, so it hardly seems justified to throw an error. Accordingly, arrange to report such failures as LOG messages and keep going when a failure occurs during replay. Back-patch to 9.0 where Hot Standby was introduced. In principle such problems can occur in earlier releases, but Hot Standby increases the odds of trouble significantly. Given the lack of field reports of such issues, I'm satisfied with patching back as far as the patch applies easily.
1 parent 3b157cf commit 5fc78ef

File tree

1 file changed

+47
-18
lines changed

1 file changed

+47
-18
lines changed

src/backend/commands/tablespace.c

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -626,9 +626,13 @@ create_tablespace_directories(const char *location, const Oid tablespaceoid)
626626
/*
627627
* destroy_tablespace_directories
628628
*
629-
* Attempt to remove filesystem infrastructure
629+
* Attempt to remove filesystem infrastructure for the tablespace.
630630
*
631-
* 'redo' indicates we are redoing a drop from XLOG; okay if nothing there
631+
* 'redo' indicates we are redoing a drop from XLOG; in that case we should
632+
* not throw an ERROR for problems, just LOG them. The worst consequence of
633+
* not removing files here would be failure to release some disk space, which
634+
* does not justify throwing an error that would require manual intervention
635+
* to get the database running again.
632636
*
633637
* Returns TRUE if successful, FALSE if some subdirectory is not empty
634638
*/
@@ -681,6 +685,16 @@ destroy_tablespace_directories(Oid tablespaceoid, bool redo)
681685
pfree(linkloc_with_version_dir);
682686
return true;
683687
}
688+
else if (redo)
689+
{
690+
/* in redo, just log other types of error */
691+
ereport(LOG,
692+
(errcode_for_file_access(),
693+
errmsg("could not open directory \"%s\": %m",
694+
linkloc_with_version_dir)));
695+
pfree(linkloc_with_version_dir);
696+
return false;
697+
}
684698
/* else let ReadDir report the error */
685699
}
686700

@@ -694,7 +708,7 @@ destroy_tablespace_directories(Oid tablespaceoid, bool redo)
694708
sprintf(subfile, "%s/%s", linkloc_with_version_dir, de->d_name);
695709

696710
/* This check is just to deliver a friendlier error message */
697-
if (!directory_is_empty(subfile))
711+
if (!redo && !directory_is_empty(subfile))
698712
{
699713
FreeDir(dirdesc);
700714
pfree(subfile);
@@ -704,7 +718,7 @@ destroy_tablespace_directories(Oid tablespaceoid, bool redo)
704718

705719
/* remove empty directory */
706720
if (rmdir(subfile) < 0)
707-
ereport(ERROR,
721+
ereport(redo ? LOG : ERROR,
708722
(errcode_for_file_access(),
709723
errmsg("could not remove directory \"%s\": %m",
710724
subfile)));
@@ -716,31 +730,38 @@ destroy_tablespace_directories(Oid tablespaceoid, bool redo)
716730

717731
/* remove version directory */
718732
if (rmdir(linkloc_with_version_dir) < 0)
719-
ereport(ERROR,
733+
{
734+
ereport(redo ? LOG : ERROR,
720735
(errcode_for_file_access(),
721736
errmsg("could not remove directory \"%s\": %m",
722737
linkloc_with_version_dir)));
738+
pfree(linkloc_with_version_dir);
739+
return false;
740+
}
723741

724742
/*
725743
* Try to remove the symlink. We must however deal with the possibility
726744
* that it's a directory instead of a symlink --- this could happen during
727745
* WAL replay (see TablespaceCreateDbspace), and it is also the case on
728746
* Windows where junction points lstat() as directories.
747+
*
748+
* Note: in the redo case, we'll return true if this final step fails;
749+
* there's no point in retrying it.
729750
*/
730751
linkloc = pstrdup(linkloc_with_version_dir);
731752
get_parent_directory(linkloc);
732753
if (lstat(linkloc, &st) == 0 && S_ISDIR(st.st_mode))
733754
{
734755
if (rmdir(linkloc) < 0)
735-
ereport(ERROR,
756+
ereport(redo ? LOG : ERROR,
736757
(errcode_for_file_access(),
737758
errmsg("could not remove directory \"%s\": %m",
738759
linkloc)));
739760
}
740761
else
741762
{
742763
if (unlink(linkloc) < 0)
743-
ereport(ERROR,
764+
ereport(redo ? LOG : ERROR,
744765
(errcode_for_file_access(),
745766
errmsg("could not remove symbolic link \"%s\": %m",
746767
linkloc)));
@@ -1451,30 +1472,38 @@ tblspc_redo(XLogRecPtr lsn, XLogRecord *record)
14511472
xl_tblspc_drop_rec *xlrec = (xl_tblspc_drop_rec *) XLogRecGetData(record);
14521473

14531474
/*
1454-
* If we issued a WAL record for a drop tablespace it is because there
1455-
* were no files in it at all. That means that no permanent objects
1456-
* can exist in it at this point.
1475+
* If we issued a WAL record for a drop tablespace it implies that
1476+
* there were no files in it at all when the DROP was done. That means
1477+
* that no permanent objects can exist in it at this point.
14571478
*
14581479
* It is possible for standby users to be using this tablespace as a
14591480
* location for their temporary files, so if we fail to remove all
14601481
* files then do conflict processing and try again, if currently
14611482
* enabled.
1483+
*
1484+
* Other possible reasons for failure include bollixed file permissions
1485+
* on a standby server when they were okay on the primary, etc etc.
1486+
* There's not much we can do about that, so just remove what we can
1487+
* and press on.
14621488
*/
14631489
if (!destroy_tablespace_directories(xlrec->ts_id, true))
14641490
{
14651491
ResolveRecoveryConflictWithTablespace(xlrec->ts_id);
14661492

14671493
/*
14681494
* If we did recovery processing then hopefully the backends who
1469-
* wrote temp files should have cleaned up and exited by now. So
1470-
* lets recheck before we throw an error. If !process_conflicts
1471-
* then this will just fail again.
1495+
* wrote temp files should have cleaned up and exited by now. So
1496+
* retry before complaining. If we fail again, this is just a LOG
1497+
* condition, because it's not worth throwing an ERROR for (as
1498+
* that would crash the database and require manual intervention
1499+
* before we could get past this WAL record on restart).
14721500
*/
14731501
if (!destroy_tablespace_directories(xlrec->ts_id, true))
1474-
ereport(ERROR,
1502+
ereport(LOG,
14751503
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1476-
errmsg("tablespace %u is not empty",
1477-
xlrec->ts_id)));
1504+
errmsg("directories for tablespace %u could not be removed",
1505+
xlrec->ts_id),
1506+
errhint("You can remove the directories manually if necessary.")));
14781507
}
14791508
}
14801509
else
@@ -1490,14 +1519,14 @@ tblspc_desc(StringInfo buf, uint8 xl_info, char *rec)
14901519
{
14911520
xl_tblspc_create_rec *xlrec = (xl_tblspc_create_rec *) rec;
14921521

1493-
appendStringInfo(buf, "create ts: %u \"%s\"",
1522+
appendStringInfo(buf, "create tablespace: %u \"%s\"",
14941523
xlrec->ts_id, xlrec->ts_path);
14951524
}
14961525
else if (info == XLOG_TBLSPC_DROP)
14971526
{
14981527
xl_tblspc_drop_rec *xlrec = (xl_tblspc_drop_rec *) rec;
14991528

1500-
appendStringInfo(buf, "drop ts: %u", xlrec->ts_id);
1529+
appendStringInfo(buf, "drop tablespace: %u", xlrec->ts_id);
15011530
}
15021531
else
15031532
appendStringInfo(buf, "UNKNOWN");

0 commit comments

Comments
 (0)