Avoid failure if autovacuum tries to access a just-dropped temp namespace.

Such an access became possible when commit 246a6c8f7 added more
aggressive cleanup of orphaned temp relations by autovacuum.
Since autovacuum's snapshot might be slightly stale, it could
attempt to access an already-dropped temp namespace, resulting in
an assertion failure or null-pointer dereference.  (In practice,
since we don't drop temp namespaces automatically but merely
recycle them, this situation could only arise if a superuser does
a manual drop of a temp namespace.  Still, that should be allowed.)

The core of the bug, IMO, is that isTempNamespaceInUse and its callers
failed to think hard about whether to treat "temp namespace isn't there"
differently from "temp namespace isn't in use".  In hopes of forestalling
future mistakes of the same ilk, replace that function with a new one
checkTempNamespaceStatus, which makes the same tests but returns a
three-way enum rather than just a bool.  isTempNamespaceInUse is gone
entirely in HEAD; but just in case some external code is relying on it,
keep it in the back branches, as a bug-compatible wrapper around the
new function.

Per report originally from Prabhat Kumar Sahu, investigated by Mahendra
Singh and Michael Paquier; the final form of the patch is my fault.
This replaces the failed fix attempt in a052f6cbb.

Backpatch as far as v11, as 246a6c8f7 was.

Discussion: https://postgr.es/m/CAKYtNAr9Zq=1-ww4etHo-VCC-k120YxZy5OS01VkaLPaDbv2tg@mail.gmail.com
This commit is contained in:
Tom Lane 2020-02-28 20:28:34 -05:00
parent 32bb4535a0
commit 80d76be51c
3 changed files with 25 additions and 14 deletions

View File

@ -3217,7 +3217,7 @@ isOtherTempNamespace(Oid namespaceId)
}
/*
* isTempNamespaceInUse - is the given namespace owned and actively used
* checkTempNamespaceStatus - is the given namespace owned and actively used
* by a backend?
*
* Note: this can be used while scanning relations in pg_class to detect
@ -3225,8 +3225,8 @@ isOtherTempNamespace(Oid namespaceId)
* given database. The result may be out of date quickly, so the caller
* must be careful how to handle this information.
*/
bool
isTempNamespaceInUse(Oid namespaceId)
TempNamespaceStatus
checkTempNamespaceStatus(Oid namespaceId)
{
PGPROC *proc;
int backendId;
@ -3235,25 +3235,25 @@ isTempNamespaceInUse(Oid namespaceId)
backendId = GetTempNamespaceBackendId(namespaceId);
/* No such temporary namespace? */
/* No such namespace, or its name shows it's not temp? */
if (backendId == InvalidBackendId)
return false;
return TEMP_NAMESPACE_NOT_TEMP;
/* Is the backend alive? */
proc = BackendIdGetProc(backendId);
if (proc == NULL)
return false;
return TEMP_NAMESPACE_IDLE;
/* Is the backend connected to the same database we are looking at? */
if (proc->databaseId != MyDatabaseId)
return false;
return TEMP_NAMESPACE_IDLE;
/* Does the backend own the temporary namespace? */
if (proc->tempNamespaceId != namespaceId)
return false;
return TEMP_NAMESPACE_IDLE;
/* all good to go */
return true;
/* Yup, so namespace is busy */
return TEMP_NAMESPACE_IN_USE;
}
/*

View File

@ -2071,9 +2071,10 @@ do_autovacuum(void)
{
/*
* We just ignore it if the owning backend is still active and
* using the temporary schema.
* using the temporary schema. Also, for safety, ignore it if the
* namespace doesn't exist or isn't a temp namespace after all.
*/
if (!isTempNamespaceInUse(classForm->relnamespace))
if (checkTempNamespaceStatus(classForm->relnamespace) == TEMP_NAMESPACE_IDLE)
{
/*
* The table seems to be orphaned -- although it might be that
@ -2243,7 +2244,7 @@ do_autovacuum(void)
continue;
}
if (isTempNamespaceInUse(classForm->relnamespace))
if (checkTempNamespaceStatus(classForm->relnamespace) != TEMP_NAMESPACE_IDLE)
{
UnlockRelationOid(relid, AccessExclusiveLock);
continue;

View File

@ -37,6 +37,16 @@ typedef struct _FuncCandidateList
Oid args[FLEXIBLE_ARRAY_MEMBER]; /* arg types */
} *FuncCandidateList;
/*
* Result of checkTempNamespaceStatus
*/
typedef enum TempNamespaceStatus
{
TEMP_NAMESPACE_NOT_TEMP, /* nonexistent, or non-temp namespace */
TEMP_NAMESPACE_IDLE, /* exists, belongs to no active session */
TEMP_NAMESPACE_IN_USE /* belongs to some active session */
} TempNamespaceStatus;
/*
* Structure for xxxOverrideSearchPath functions
*/
@ -138,7 +148,7 @@ extern bool isTempToastNamespace(Oid namespaceId);
extern bool isTempOrTempToastNamespace(Oid namespaceId);
extern bool isAnyTempNamespace(Oid namespaceId);
extern bool isOtherTempNamespace(Oid namespaceId);
extern bool isTempNamespaceInUse(Oid namespaceId);
extern TempNamespaceStatus checkTempNamespaceStatus(Oid namespaceId);
extern int GetTempNamespaceBackendId(Oid namespaceId);
extern Oid GetTempToastNamespace(void);
extern void GetTempNamespaceState(Oid *tempNamespaceId,