Skip to content

Commit 07e3641

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 852a5cd commit 07e3641

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)));
@@ -1413,30 +1434,38 @@ tblspc_redo(XLogRecPtr lsn, XLogRecord *record)
14131434
xl_tblspc_drop_rec *xlrec = (xl_tblspc_drop_rec *) XLogRecGetData(record);
14141435

14151436
/*
1416-
* If we issued a WAL record for a drop tablespace it is because there
1417-
* were no files in it at all. That means that no permanent objects
1418-
* can exist in it at this point.
1437+
* If we issued a WAL record for a drop tablespace it implies that
1438+
* there were no files in it at all when the DROP was done. That means
1439+
* that no permanent objects can exist in it at this point.
14191440
*
14201441
* It is possible for standby users to be using this tablespace as a
14211442
* location for their temporary files, so if we fail to remove all
14221443
* files then do conflict processing and try again, if currently
14231444
* enabled.
1445+
*
1446+
* Other possible reasons for failure include bollixed file permissions
1447+
* on a standby server when they were okay on the primary, etc etc.
1448+
* There's not much we can do about that, so just remove what we can
1449+
* and press on.
14241450
*/
14251451
if (!destroy_tablespace_directories(xlrec->ts_id, true))
14261452
{
14271453
ResolveRecoveryConflictWithTablespace(xlrec->ts_id);
14281454

14291455
/*
14301456
* If we did recovery processing then hopefully the backends who
1431-
* wrote temp files should have cleaned up and exited by now. So
1432-
* lets recheck before we throw an error. If !process_conflicts
1433-
* then this will just fail again.
1457+
* wrote temp files should have cleaned up and exited by now. So
1458+
* retry before complaining. If we fail again, this is just a LOG
1459+
* condition, because it's not worth throwing an ERROR for (as
1460+
* that would crash the database and require manual intervention
1461+
* before we could get past this WAL record on restart).
14341462
*/
14351463
if (!destroy_tablespace_directories(xlrec->ts_id, true))
1436-
ereport(ERROR,
1464+
ereport(LOG,
14371465
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1438-
errmsg("tablespace %u is not empty",
1439-
xlrec->ts_id)));
1466+
errmsg("directories for tablespace %u could not be removed",
1467+
xlrec->ts_id),
1468+
errhint("You can remove the directories manually if necessary.")));
14401469
}
14411470
}
14421471
else
@@ -1452,14 +1481,14 @@ tblspc_desc(StringInfo buf, uint8 xl_info, char *rec)
14521481
{
14531482
xl_tblspc_create_rec *xlrec = (xl_tblspc_create_rec *) rec;
14541483

1455-
appendStringInfo(buf, "create ts: %u \"%s\"",
1484+
appendStringInfo(buf, "create tablespace: %u \"%s\"",
14561485
xlrec->ts_id, xlrec->ts_path);
14571486
}
14581487
else if (info == XLOG_TBLSPC_DROP)
14591488
{
14601489
xl_tblspc_drop_rec *xlrec = (xl_tblspc_drop_rec *) rec;
14611490

1462-
appendStringInfo(buf, "drop ts: %u", xlrec->ts_id);
1491+
appendStringInfo(buf, "drop tablespace: %u", xlrec->ts_id);
14631492
}
14641493
else
14651494
appendStringInfo(buf, "UNKNOWN");

0 commit comments

Comments
 (0)