Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit44631ee

Browse files
committed
Fix race condition in relcache init file invalidation.
The previous code tried to synchronize by unlinking the init file twice,but that doesn't actually work: it leaves a window wherein a third processcould read the already-stale init file but miss the SI messages that wouldtell it the data is stale. The result would be bizarre failures in catalogaccesses, typically "could not read block 0 in file ..." later duringstartup.Instead, hold RelCacheInitLock across both the unlink and the sending ofthe SI messages. This is more straightforward, and might even be a bitfaster since only one unlink call is needed.This has been wrong since it was put in (in 2002!), so back-patch to allsupported releases.
1 parent443a44b commit44631ee

File tree

3 files changed

+43
-36
lines changed

3 files changed

+43
-36
lines changed

‎src/backend/utils/cache/inval.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -767,10 +767,10 @@ inval_twophase_postcommit(TransactionId xid, uint16 info,
767767
SendSharedInvalidMessage(msg);
768768
break;
769769
caseTWOPHASE_INFO_FILE_BEFORE:
770-
RelationCacheInitFileInvalidate(true);
770+
RelationCacheInitFilePreInvalidate();
771771
break;
772772
caseTWOPHASE_INFO_FILE_AFTER:
773-
RelationCacheInitFileInvalidate(false);
773+
RelationCacheInitFilePostInvalidate();
774774
break;
775775
default:
776776
Assert(false);
@@ -817,7 +817,7 @@ AtEOXact_Inval(bool isCommit)
817817
* unless we committed.
818818
*/
819819
if (transInvalInfo->RelcacheInitFileInval)
820-
RelationCacheInitFileInvalidate(true);
820+
RelationCacheInitFilePreInvalidate();
821821

822822
AppendInvalidationMessages(&transInvalInfo->PriorCmdInvalidMsgs,
823823
&transInvalInfo->CurrentCmdInvalidMsgs);
@@ -826,7 +826,7 @@ AtEOXact_Inval(bool isCommit)
826826
SendSharedInvalidMessage);
827827

828828
if (transInvalInfo->RelcacheInitFileInval)
829-
RelationCacheInitFileInvalidate(false);
829+
RelationCacheInitFilePostInvalidate();
830830
}
831831
elseif (transInvalInfo!=NULL)
832832
{

‎src/backend/utils/cache/relcache.c

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3642,8 +3642,8 @@ write_relcache_init_file(void)
36423642
* updated by SI message processing, but we can't be sure whether what we
36433643
* wrote out was up-to-date.)
36443644
*
3645-
* This mustn't run concurrently withRelationCacheInitFileInvalidate, so
3646-
* grab a serialization lock for the duration.
3645+
* This mustn't run concurrently withthe code that unlinks an init file
3646+
*and sends SI messages, sograb a serialization lock for the duration.
36473647
*/
36483648
LWLockAcquire(RelCacheInitLock,LW_EXCLUSIVE);
36493649

@@ -3707,49 +3707,55 @@ RelationIdIsInInitFile(Oid relationId)
37073707
* changed one or more of the relation cache entries that are kept in the
37083708
* init file.
37093709
*
3710-
* We actually need to remove the init file twice: once just before sending
3711-
* the SI messages that include relcache inval for such relations, and once
3712-
* just after sending them. The unlink before ensures that a backend that's
3713-
* currently starting cannot read the now-obsolete init file and then miss
3714-
* the SI messages that will force it to update its relcache entries. (This
3715-
* works because the backend startup sequence gets into the PGPROC array before
3716-
* trying to load the init file.) The unlink after is to synchronize with a
3717-
* backend that may currently be trying to write an init file based on data
3718-
* that we've just rendered invalid. Such a backend will see the SI messages,
3719-
* but we can't leave the init file sitting around to fool later backends.
3720-
*
3721-
* Ignore any failure to unlink the file, since it might not be there if
3722-
* no backend has been started since the last removal.
3710+
* To be safe against concurrent inspection or rewriting of the init file,
3711+
* we must take RelCacheInitLock, then remove the old init file, then send
3712+
* the SI messages that include relcache inval for such relations, and then
3713+
* release RelCacheInitLock. This serializes the whole affair against
3714+
* write_relcache_init_file, so that we can be sure that any other process
3715+
* that's concurrently trying to create a new init file won't move an
3716+
* already-stale version into place after we unlink. Also, because we unlink
3717+
* before sending the SI messages, a backend that's currently starting cannot
3718+
* read the now-obsolete init file and then miss the SI messages that will
3719+
* force it to update its relcache entries. (This works because the backend
3720+
* startup sequence gets into the sinval array before trying to load the init
3721+
* file.)
3722+
*
3723+
* We take the lock and do the unlink in RelationCacheInitFilePreInvalidate,
3724+
* then release the lock in RelationCacheInitFilePostInvalidate. Caller must
3725+
* send any pending SI messages between those calls.
37233726
*/
37243727
void
3725-
RelationCacheInitFileInvalidate(boolbeforeSend)
3728+
RelationCacheInitFilePreInvalidate(void)
37263729
{
37273730
charinitfilename[MAXPGPATH];
37283731

37293732
snprintf(initfilename,sizeof(initfilename),"%s/%s",
37303733
DatabasePath,RELCACHE_INIT_FILENAME);
37313734

3732-
if (beforeSend)
3733-
{
3734-
/* no interlock needed here */
3735-
unlink(initfilename);
3736-
}
3737-
else
3735+
LWLockAcquire(RelCacheInitLock,LW_EXCLUSIVE);
3736+
3737+
if (unlink(initfilename)<0)
37383738
{
37393739
/*
3740-
* We need to interlock this against write_relcache_init_file, to
3741-
* guard against possibility that someone renames a new-but-
3742-
* already-obsolete init file into place just after we unlink. With
3743-
* the interlock, it's certain that write_relcache_init_file will
3744-
* notice our SI inval message before renaming into place, or else
3745-
* that we will execute second and successfully unlink the file.
3740+
* The file might not be there if no backend has been started since
3741+
* the last removal. But complain about failures other than ENOENT.
3742+
* Fortunately, it's not too late to abort the transaction if we
3743+
* can't get rid of the would-be-obsolete init file.
37463744
*/
3747-
LWLockAcquire(RelCacheInitLock,LW_EXCLUSIVE);
3748-
unlink(initfilename);
3749-
LWLockRelease(RelCacheInitLock);
3745+
if (errno!=ENOENT)
3746+
ereport(ERROR,
3747+
(errcode_for_file_access(),
3748+
errmsg("could not remove cache file \"%s\": %m",
3749+
initfilename)));
37503750
}
37513751
}
37523752

3753+
void
3754+
RelationCacheInitFilePostInvalidate(void)
3755+
{
3756+
LWLockRelease(RelCacheInitLock);
3757+
}
3758+
37533759
/*
37543760
* Remove the init file for a given database during postmaster startup.
37553761
*

‎src/include/utils/relcache.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ extern void AtEOSubXact_RelationCache(bool isCommit, SubTransactionId mySubid,
6868
* Routines to help manage rebuilding of relcache init file
6969
*/
7070
externboolRelationIdIsInInitFile(OidrelationId);
71-
externvoidRelationCacheInitFileInvalidate(boolbeforeSend);
71+
externvoidRelationCacheInitFilePreInvalidate(void);
72+
externvoidRelationCacheInitFilePostInvalidate(void);
7273
externvoidRelationCacheInitFileRemove(constchar*dbPath);
7374

7475
/* should be used only by relcache.c and catcache.c */

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp