agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH 5/5] Remove the old implemenations of XLogRead().
49+ messages / 11 participants
[nested] [flat]
* [PATCH 5/5] Remove the old implemenations of XLogRead().
@ 2019-07-09 09:54 Antonin Houska <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Antonin Houska @ 2019-07-09 09:54 UTC (permalink / raw)
Done in a separate patch because the diff looks harder to read if one function
(XLogRead) is removed and another one (the XLogOpenSegment callback) is added
nearby at the same time (the addition and removal of code can get mixed in the
diff).
---
src/backend/access/transam/xlogutils.c | 125 ----------------------
src/backend/replication/walsender.c | 186 ---------------------------------
src/bin/pg_waldump/pg_waldump.c | 123 ----------------------
3 files changed, 434 deletions(-)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 899bf1b551..c35852d7a1 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -17,14 +17,11 @@
*/
#include "postgres.h"
-#include <unistd.h>
-
#include "access/timeline.h"
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogutils.h"
#include "miscadmin.h"
-#include "pgstat.h"
#include "storage/smgr.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
@@ -639,128 +636,6 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
forget_invalid_pages(rnode, forkNum, nblocks);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- * in timeline 'tli'.
- *
- * Will open, and keep open, one WAL segment stored in the static file
- * descriptor 'sendFile'. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- *
- * XXX This is very similar to pg_waldump's XLogDumpXLogRead and to XLogRead
- * in walsender.c but for small differences (such as lack of elog() in
- * frontend). Probably these should be merged at some point.
- */
-static void
-XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr,
- Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- /* state maintained across calls */
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static TimeLineID sendTLI = 0;
- static uint32 sendOff = 0;
-
- Assert(segsize == wal_segment_size);
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segsize);
-
- /* Do we need to switch to a different xlog segment? */
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, segsize) ||
- sendTLI != tli)
- {
- char path[MAXPGPATH];
-
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, segsize);
-
- XLogFilePath(path, tli, sendSegNo, segsize);
-
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
-
- if (sendFile < 0)
- {
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- path)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendOff = 0;
- sendTLI = tli;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- path, startoff)));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segsize - startoff))
- segbytes = segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendFile, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes <= 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %lu: %m",
- path, sendOff, (unsigned long) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* Determine which timeline to read an xlog page from and set the
* XLogReaderState's currTLI to that timeline ID.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index c64a4bf9f8..68715aed9e 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -249,7 +249,6 @@ static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch);
static void WalSndOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli,
XLogSegment *seg);
-static void XLogReadOld(char *buf, XLogRecPtr startptr, Size count);
/* Initialize walsender process before entering the main command loop */
@@ -2351,191 +2350,6 @@ WalSndKill(int code, Datum arg)
SpinLockRelease(&walsnd->mutex);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- *
- * XXX probably this should be improved to suck data directly from the
- * WAL buffers when possible.
- *
- * Will open, and keep open, one WAL segment stored in the global file
- * descriptor sendFile. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- */
-static void
-XLogReadOld(char *buf, XLogRecPtr startptr, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
- XLogSegNo segno;
-
-retry:
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, wal_segment_size);
-
- if (sendSeg->file < 0 ||
- !XLByteInSeg(recptr, sendSeg->num, sendSeg->size))
- {
- char path[MAXPGPATH];
-
- /* Switch to another logfile segment */
- if (sendSeg->file >= 0)
- close(sendSeg->file);
-
- XLByteToSeg(recptr, sendSeg->num, sendSeg->size);
-
- /*-------
- * When reading from a historic timeline, and there is a timeline
- * switch within this segment, read from the WAL segment belonging
- * to the new timeline.
- *
- * For example, imagine that this server is currently on timeline
- * 5, and we're streaming timeline 4. The switch from timeline 4
- * to 5 happened at 0/13002088. In pg_wal, we have these files:
- *
- * ...
- * 000000040000000000000012
- * 000000040000000000000013
- * 000000050000000000000013
- * 000000050000000000000014
- * ...
- *
- * In this situation, when requested to send the WAL from
- * segment 0x13, on timeline 4, we read the WAL from file
- * 000000050000000000000013. Archive recovery prefers files from
- * newer timelines, so if the segment was restored from the
- * archive on this server, the file belonging to the old timeline,
- * 000000040000000000000013, might not exist. Their contents are
- * equal up to the switchpoint, because at a timeline switch, the
- * used portion of the old segment is copied to the new file.
- *-------
- */
- sendSeg->tli = sendTimeLine;
- if (sendTimeLineIsHistoric)
- {
- XLogSegNo endSegNo;
-
- XLByteToSeg(sendTimeLineValidUpto, endSegNo, wal_segment_size);
- if (sendSeg->num == endSegNo)
- sendSeg->tli = sendTimeLineNextTLI;
- }
-
- XLogFilePath(path, sendSeg->tli, sendSeg->num, wal_segment_size);
-
- sendSeg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
- if (sendSeg->file < 0)
- {
- /*
- * If the file is not found, assume it's because the standby
- * asked for a too old WAL segment that has already been
- * removed or recycled.
- */
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- XLogFileNameP(sendSeg->tli, sendSeg->num))));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendSeg->off = 0;
- }
-
- /* Need to seek in the file? */
- if (sendSeg->off != startoff)
- {
- if (lseek(sendSeg->file, (off_t) startoff, SEEK_SET) < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- startoff)));
- sendSeg->off = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (wal_segment_size - startoff))
- segbytes = wal_segment_size - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendSeg->file, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes < 0)
- {
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %zu: %m",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- sendSeg->off, (Size) segbytes)));
- }
- else if (readbytes == 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("could not read from log segment %s, offset %u: read %d of %zu",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- sendSeg->off, readbytes, (Size) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendSeg->off += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-
- /*
- * After reading into the buffer, check that what we read was valid. We do
- * this after reading, because even though the segment was present when we
- * opened it, it might get recycled or removed while we read it. The
- * read() succeeds in that case, but the data we tried to read might
- * already have been overwritten with new WAL records.
- */
- XLByteToSeg(startptr, segno, wal_segment_size);
- CheckXLogRemoved(segno, ThisTimeLineID);
-
- /*
- * During recovery, the currently-open WAL file might be replaced with the
- * file of the same name retrieved from archive. So we always need to
- * check what we read was valid after reading into the buffer. If it's
- * invalid, we try to open and read the file again.
- */
- if (am_cascading_walsender)
- {
- WalSnd *walsnd = MyWalSnd;
- bool reload;
-
- SpinLockAcquire(&walsnd->mutex);
- reload = walsnd->needreload;
- walsnd->needreload = false;
- SpinLockRelease(&walsnd->mutex);
-
- if (reload && sendSeg->file >= 0)
- {
- close(sendSeg->file);
- sendSeg->file = -1;
-
- goto retry;
- }
- }
-}
-
/*
* Callback for XLogRead() to open the next segment.
*/
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 3e09519f88..cd44090d36 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -14,7 +14,6 @@
#include <dirent.h>
#include <sys/stat.h>
-#include <unistd.h>
#include "access/xlogreader.h"
#include "access/xlogrecord.h"
@@ -335,128 +334,6 @@ XLogDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, XLogSegment *seg)
seg->tli = *tli;
}
-/*
- * Read count bytes from a segment file in the specified directory, for the
- * given timeline, containing the specified record pointer; store the data in
- * the passed buffer.
- */
-static void
-XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
- XLogRecPtr startptr, char *buf, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static uint32 sendOff = 0;
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, WalSegSz);
-
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, WalSegSz))
- {
- char fname[MAXFNAMELEN];
- int tries;
-
- /* Switch to another logfile segment */
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, WalSegSz);
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- /*
- * In follow mode there is a short period of time after the server
- * has written the end of the previous file before the new file is
- * available. So we loop for 5 seconds looking for the file to
- * appear before giving up.
- */
- for (tries = 0; tries < 10; tries++)
- {
- sendFile = open_file_in_directory(directory, fname);
- if (sendFile >= 0)
- break;
- if (errno == ENOENT)
- {
- int save_errno = errno;
-
- /* File not there yet, try again */
- pg_usleep(500 * 1000);
-
- errno = save_errno;
- continue;
- }
- /* Any other error, fall through and fail */
- break;
- }
-
- if (sendFile < 0)
- fatal_error("could not find file \"%s\": %s",
- fname, strerror(errno));
- sendOff = 0;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- fatal_error("could not seek in log file %s to offset %u: %s",
- fname, startoff, strerror(err));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (WalSegSz - startoff))
- segbytes = WalSegSz - startoff;
- else
- segbytes = nbytes;
-
- readbytes = read(sendFile, p, segbytes);
- if (readbytes <= 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
- int save_errno = errno;
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
- errno = save_errno;
-
- if (readbytes < 0)
- fatal_error("could not read from log file %s, offset %u, length %d: %s",
- fname, sendOff, segbytes, strerror(err));
- else if (readbytes == 0)
- fatal_error("could not read from log file %s, offset %u: read %d of %zu",
- fname, sendOff, readbytes, (Size) segbytes);
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* XLogReader read_page callback
*/
--
2.16.4
--=-=-=--
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 4/4] Remove the old implemenations of XLogRead().
@ 2019-09-09 09:53 Antonin Houska <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Antonin Houska @ 2019-09-09 09:53 UTC (permalink / raw)
Done in a separate patch because the diff looks harder to read if one function
(XLogRead) is removed and another one (the XLogOpenSegment callback) is added
nearby at the same time (the addition and removal of code can get mixed in the
diff).
---
src/backend/access/transam/xlogutils.c | 125 -----------------
src/backend/replication/walsender.c | 186 -------------------------
src/bin/pg_waldump/pg_waldump.c | 123 ----------------
3 files changed, 434 deletions(-)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 83b014e04c..5b0ba39f17 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -17,14 +17,11 @@
*/
#include "postgres.h"
-#include <unistd.h>
-
#include "access/timeline.h"
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogutils.h"
#include "miscadmin.h"
-#include "pgstat.h"
#include "storage/smgr.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
@@ -639,128 +636,6 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
forget_invalid_pages(rnode, forkNum, nblocks);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- * in timeline 'tli'.
- *
- * Will open, and keep open, one WAL segment stored in the static file
- * descriptor 'sendFile'. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- *
- * XXX This is very similar to pg_waldump's XLogDumpXLogRead and to XLogRead
- * in walsender.c but for small differences (such as lack of elog() in
- * frontend). Probably these should be merged at some point.
- */
-static void
-XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr,
- Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- /* state maintained across calls */
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static TimeLineID sendTLI = 0;
- static uint32 sendOff = 0;
-
- Assert(segsize == wal_segment_size);
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segsize);
-
- /* Do we need to switch to a different xlog segment? */
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, segsize) ||
- sendTLI != tli)
- {
- char path[MAXPGPATH];
-
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, segsize);
-
- XLogFilePath(path, tli, sendSegNo, segsize);
-
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
-
- if (sendFile < 0)
- {
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- path)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendOff = 0;
- sendTLI = tli;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- path, startoff)));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segsize - startoff))
- segbytes = segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendFile, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes <= 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %lu: %m",
- path, sendOff, (unsigned long) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* Determine which timeline to read an xlog page from and set the
* XLogReaderState's currTLI to that timeline ID.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 0685c320b4..072d46d853 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -249,7 +249,6 @@ static bool TransactionIdInRecentPast(TransactionId xid, uint32 epoch);
static void WalSndOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli,
XLogSegment *seg);
-static void XLogReadOld(char *buf, XLogRecPtr startptr, Size count);
/* Initialize walsender process before entering the main command loop */
@@ -2351,191 +2350,6 @@ WalSndKill(int code, Datum arg)
SpinLockRelease(&walsnd->mutex);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- *
- * XXX probably this should be improved to suck data directly from the
- * WAL buffers when possible.
- *
- * Will open, and keep open, one WAL segment stored in the global file
- * descriptor sendFile. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- */
-static void
-XLogReadOld(char *buf, XLogRecPtr startptr, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
- XLogSegNo segno;
-
-retry:
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, wal_segment_size);
-
- if (sendSeg->file < 0 ||
- !XLByteInSeg(recptr, sendSeg->num, sendSeg->size))
- {
- char path[MAXPGPATH];
-
- /* Switch to another logfile segment */
- if (sendSeg->file >= 0)
- close(sendSeg->file);
-
- XLByteToSeg(recptr, sendSeg->num, sendSeg->size);
-
- /*-------
- * When reading from a historic timeline, and there is a timeline
- * switch within this segment, read from the WAL segment belonging
- * to the new timeline.
- *
- * For example, imagine that this server is currently on timeline
- * 5, and we're streaming timeline 4. The switch from timeline 4
- * to 5 happened at 0/13002088. In pg_wal, we have these files:
- *
- * ...
- * 000000040000000000000012
- * 000000040000000000000013
- * 000000050000000000000013
- * 000000050000000000000014
- * ...
- *
- * In this situation, when requested to send the WAL from
- * segment 0x13, on timeline 4, we read the WAL from file
- * 000000050000000000000013. Archive recovery prefers files from
- * newer timelines, so if the segment was restored from the
- * archive on this server, the file belonging to the old timeline,
- * 000000040000000000000013, might not exist. Their contents are
- * equal up to the switchpoint, because at a timeline switch, the
- * used portion of the old segment is copied to the new file.
- *-------
- */
- sendSeg->tli = sendTimeLine;
- if (sendTimeLineIsHistoric)
- {
- XLogSegNo endSegNo;
-
- XLByteToSeg(sendTimeLineValidUpto, endSegNo, wal_segment_size);
- if (sendSeg->num == endSegNo)
- sendSeg->tli = sendTimeLineNextTLI;
- }
-
- XLogFilePath(path, sendSeg->tli, sendSeg->num, wal_segment_size);
-
- sendSeg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
- if (sendSeg->file < 0)
- {
- /*
- * If the file is not found, assume it's because the standby
- * asked for a too old WAL segment that has already been
- * removed or recycled.
- */
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- XLogFileNameP(sendSeg->tli, sendSeg->num))));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendSeg->off = 0;
- }
-
- /* Need to seek in the file? */
- if (sendSeg->off != startoff)
- {
- if (lseek(sendSeg->file, (off_t) startoff, SEEK_SET) < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- startoff)));
- sendSeg->off = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (wal_segment_size - startoff))
- segbytes = wal_segment_size - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendSeg->file, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes < 0)
- {
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %zu: %m",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- sendSeg->off, (Size) segbytes)));
- }
- else if (readbytes == 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("could not read from log segment %s, offset %u: read %d of %zu",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- sendSeg->off, readbytes, (Size) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendSeg->off += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-
- /*
- * After reading into the buffer, check that what we read was valid. We do
- * this after reading, because even though the segment was present when we
- * opened it, it might get recycled or removed while we read it. The
- * read() succeeds in that case, but the data we tried to read might
- * already have been overwritten with new WAL records.
- */
- XLByteToSeg(startptr, segno, wal_segment_size);
- CheckXLogRemoved(segno, ThisTimeLineID);
-
- /*
- * During recovery, the currently-open WAL file might be replaced with the
- * file of the same name retrieved from archive. So we always need to
- * check what we read was valid after reading into the buffer. If it's
- * invalid, we try to open and read the file again.
- */
- if (am_cascading_walsender)
- {
- WalSnd *walsnd = MyWalSnd;
- bool reload;
-
- SpinLockAcquire(&walsnd->mutex);
- reload = walsnd->needreload;
- walsnd->needreload = false;
- SpinLockRelease(&walsnd->mutex);
-
- if (reload && sendSeg->file >= 0)
- {
- close(sendSeg->file);
- sendSeg->file = -1;
-
- goto retry;
- }
- }
-}
-
/*
* Callback for XLogRead() to open the next segment.
*/
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 3e09519f88..cd44090d36 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -14,7 +14,6 @@
#include <dirent.h>
#include <sys/stat.h>
-#include <unistd.h>
#include "access/xlogreader.h"
#include "access/xlogrecord.h"
@@ -335,128 +334,6 @@ XLogDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli, XLogSegment *seg)
seg->tli = *tli;
}
-/*
- * Read count bytes from a segment file in the specified directory, for the
- * given timeline, containing the specified record pointer; store the data in
- * the passed buffer.
- */
-static void
-XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
- XLogRecPtr startptr, char *buf, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static uint32 sendOff = 0;
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, WalSegSz);
-
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, WalSegSz))
- {
- char fname[MAXFNAMELEN];
- int tries;
-
- /* Switch to another logfile segment */
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, WalSegSz);
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- /*
- * In follow mode there is a short period of time after the server
- * has written the end of the previous file before the new file is
- * available. So we loop for 5 seconds looking for the file to
- * appear before giving up.
- */
- for (tries = 0; tries < 10; tries++)
- {
- sendFile = open_file_in_directory(directory, fname);
- if (sendFile >= 0)
- break;
- if (errno == ENOENT)
- {
- int save_errno = errno;
-
- /* File not there yet, try again */
- pg_usleep(500 * 1000);
-
- errno = save_errno;
- continue;
- }
- /* Any other error, fall through and fail */
- break;
- }
-
- if (sendFile < 0)
- fatal_error("could not find file \"%s\": %s",
- fname, strerror(errno));
- sendOff = 0;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- fatal_error("could not seek in log file %s to offset %u: %s",
- fname, startoff, strerror(err));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (WalSegSz - startoff))
- segbytes = WalSegSz - startoff;
- else
- segbytes = nbytes;
-
- readbytes = read(sendFile, p, segbytes);
- if (readbytes <= 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
- int save_errno = errno;
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
- errno = save_errno;
-
- if (readbytes < 0)
- fatal_error("could not read from log file %s, offset %u, length %d: %s",
- fname, sendOff, segbytes, strerror(err));
- else if (readbytes == 0)
- fatal_error("could not read from log file %s, offset %u: read %d of %zu",
- fname, sendOff, readbytes, (Size) segbytes);
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* XLogReader read_page callback
*/
--
2.22.0
--=-=-=--
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 6/6] Remove the old implemenations of XLogRead().
@ 2019-09-23 05:40 Antonin Houska <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Antonin Houska @ 2019-09-23 05:40 UTC (permalink / raw)
Done in a separate patch because the diff looks harder to read if one function
(XLogRead) is removed and another one (the WALSegmentOpen callback) is added
nearby at the same time (the addition and removal of code can get mixed in the
diff).
---
src/backend/access/transam/xlogutils.c | 125 -----------------
src/backend/replication/walsender.c | 187 -------------------------
src/bin/pg_waldump/pg_waldump.c | 123 ----------------
3 files changed, 435 deletions(-)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 38c3196168..078d58e34c 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -17,14 +17,11 @@
*/
#include "postgres.h"
-#include <unistd.h>
-
#include "access/timeline.h"
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogutils.h"
#include "miscadmin.h"
-#include "pgstat.h"
#include "storage/smgr.h"
#include "utils/guc.h"
#include "utils/hsearch.h"
@@ -639,128 +636,6 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
forget_invalid_pages(rnode, forkNum, nblocks);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- * in timeline 'tli'.
- *
- * Will open, and keep open, one WAL segment stored in the static file
- * descriptor 'sendFile'. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- *
- * XXX This is very similar to pg_waldump's XLogDumpXLogRead and to XLogRead
- * in walsender.c but for small differences (such as lack of elog() in
- * frontend). Probably these should be merged at some point.
- */
-static void
-XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr,
- Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- /* state maintained across calls */
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static TimeLineID sendTLI = 0;
- static uint32 sendOff = 0;
-
- Assert(segsize == wal_segment_size);
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segsize);
-
- /* Do we need to switch to a different xlog segment? */
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, segsize) ||
- sendTLI != tli)
- {
- char path[MAXPGPATH];
-
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, segsize);
-
- XLogFilePath(path, tli, sendSegNo, segsize);
-
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
-
- if (sendFile < 0)
- {
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- path)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendOff = 0;
- sendTLI = tli;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- path, startoff)));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segsize - startoff))
- segbytes = segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendFile, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes <= 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %lu: %m",
- path, sendOff, (unsigned long) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* Determine which timeline to read an xlog page from and set the
* XLogReaderState's currTLI to that timeline ID.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index a7b3e0ecbe..3ea05add53 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -251,8 +251,6 @@ static void WalSndSegmentOpen(XLogSegNo nextSegNo, TimeLineID *tli_p,
int *file_p, WALOpenSegment *seg);
-static void XLogReadOld(char *buf, XLogRecPtr startptr, Size count);
-
/* Initialize walsender process before entering the main command loop */
void
InitWalSender(void)
@@ -2358,191 +2356,6 @@ WalSndKill(int code, Datum arg)
SpinLockRelease(&walsnd->mutex);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- *
- * XXX probably this should be improved to suck data directly from the
- * WAL buffers when possible.
- *
- * Will open, and keep open, one WAL segment stored in the global file
- * descriptor sendFile. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- */
-static void
-XLogReadOld(char *buf, XLogRecPtr startptr, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
- XLogSegNo segno;
-
-retry:
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, wal_segment_size);
-
- if (sendSeg->file < 0 ||
- !XLByteInSeg(recptr, sendSeg->num, sendSeg->size))
- {
- char path[MAXPGPATH];
-
- /* Switch to another logfile segment */
- if (sendSeg->file >= 0)
- close(sendSeg->file);
-
- XLByteToSeg(recptr, sendSeg->num, sendSeg->size);
-
- /*-------
- * When reading from a historic timeline, and there is a timeline
- * switch within this segment, read from the WAL segment belonging
- * to the new timeline.
- *
- * For example, imagine that this server is currently on timeline
- * 5, and we're streaming timeline 4. The switch from timeline 4
- * to 5 happened at 0/13002088. In pg_wal, we have these files:
- *
- * ...
- * 000000040000000000000012
- * 000000040000000000000013
- * 000000050000000000000013
- * 000000050000000000000014
- * ...
- *
- * In this situation, when requested to send the WAL from
- * segment 0x13, on timeline 4, we read the WAL from file
- * 000000050000000000000013. Archive recovery prefers files from
- * newer timelines, so if the segment was restored from the
- * archive on this server, the file belonging to the old timeline,
- * 000000040000000000000013, might not exist. Their contents are
- * equal up to the switchpoint, because at a timeline switch, the
- * used portion of the old segment is copied to the new file.
- *-------
- */
- sendSeg->tli = sendTimeLine;
- if (sendTimeLineIsHistoric)
- {
- XLogSegNo endSegNo;
-
- XLByteToSeg(sendTimeLineValidUpto, endSegNo, wal_segment_size);
- if (sendSeg->num == endSegNo)
- sendSeg->tli = sendTimeLineNextTLI;
- }
-
- XLogFilePath(path, sendSeg->tli, sendSeg->num, wal_segment_size);
-
- sendSeg->file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
- if (sendSeg->file < 0)
- {
- /*
- * If the file is not found, assume it's because the standby
- * asked for a too old WAL segment that has already been
- * removed or recycled.
- */
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- XLogFileNameP(sendSeg->tli, sendSeg->num))));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendSeg->off = 0;
- }
-
- /* Need to seek in the file? */
- if (sendSeg->off != startoff)
- {
- if (lseek(sendSeg->file, (off_t) startoff, SEEK_SET) < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- startoff)));
- sendSeg->off = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (wal_segment_size - startoff))
- segbytes = wal_segment_size - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendSeg->file, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes < 0)
- {
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %zu: %m",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- sendSeg->off, (Size) segbytes)));
- }
- else if (readbytes == 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("could not read from log segment %s, offset %u: read %d of %zu",
- XLogFileNameP(sendSeg->tli, sendSeg->num),
- sendSeg->off, readbytes, (Size) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendSeg->off += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-
- /*
- * After reading into the buffer, check that what we read was valid. We do
- * this after reading, because even though the segment was present when we
- * opened it, it might get recycled or removed while we read it. The
- * read() succeeds in that case, but the data we tried to read might
- * already have been overwritten with new WAL records.
- */
- XLByteToSeg(startptr, segno, wal_segment_size);
- CheckXLogRemoved(segno, ThisTimeLineID);
-
- /*
- * During recovery, the currently-open WAL file might be replaced with the
- * file of the same name retrieved from archive. So we always need to
- * check what we read was valid after reading into the buffer. If it's
- * invalid, we try to open and read the file again.
- */
- if (am_cascading_walsender)
- {
- WalSnd *walsnd = MyWalSnd;
- bool reload;
-
- SpinLockAcquire(&walsnd->mutex);
- reload = walsnd->needreload;
- walsnd->needreload = false;
- SpinLockRelease(&walsnd->mutex);
-
- if (reload && sendSeg->file >= 0)
- {
- close(sendSeg->file);
- sendSeg->file = -1;
-
- goto retry;
- }
- }
-}
-
/*
* Callback for XLogRead() to open the next segment.
*/
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index cd5f589f03..1de8eb350e 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -14,7 +14,6 @@
#include <dirent.h>
#include <sys/stat.h>
-#include <unistd.h>
#include "access/xlogreader.h"
#include "access/xlogrecord.h"
@@ -341,128 +340,6 @@ WALDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli_p, int *file_p,
*file_p = file;
}
-/*
- * Read count bytes from a segment file in the specified directory, for the
- * given timeline, containing the specified record pointer; store the data in
- * the passed buffer.
- */
-static void
-XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
- XLogRecPtr startptr, char *buf, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static uint32 sendOff = 0;
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, WalSegSz);
-
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, WalSegSz))
- {
- char fname[MAXFNAMELEN];
- int tries;
-
- /* Switch to another logfile segment */
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, WalSegSz);
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- /*
- * In follow mode there is a short period of time after the server
- * has written the end of the previous file before the new file is
- * available. So we loop for 5 seconds looking for the file to
- * appear before giving up.
- */
- for (tries = 0; tries < 10; tries++)
- {
- sendFile = open_file_in_directory(directory, fname);
- if (sendFile >= 0)
- break;
- if (errno == ENOENT)
- {
- int save_errno = errno;
-
- /* File not there yet, try again */
- pg_usleep(500 * 1000);
-
- errno = save_errno;
- continue;
- }
- /* Any other error, fall through and fail */
- break;
- }
-
- if (sendFile < 0)
- fatal_error("could not find file \"%s\": %s",
- fname, strerror(errno));
- sendOff = 0;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- fatal_error("could not seek in log file %s to offset %u: %s",
- fname, startoff, strerror(err));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (WalSegSz - startoff))
- segbytes = WalSegSz - startoff;
- else
- segbytes = nbytes;
-
- readbytes = read(sendFile, p, segbytes);
- if (readbytes <= 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
- int save_errno = errno;
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
- errno = save_errno;
-
- if (readbytes < 0)
- fatal_error("could not read from log file %s, offset %u, length %d: %s",
- fname, sendOff, segbytes, strerror(err));
- else if (readbytes == 0)
- fatal_error("could not read from log file %s, offset %u: read %d of %zu",
- fname, sendOff, readbytes, (Size) segbytes);
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* XLogReader read_page callback
*/
--
2.20.1
--=-=-=--
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 2/2] Remove the old implemenations of XLogRead().
@ 2019-09-26 11:51 Antonin Houska <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Antonin Houska @ 2019-09-26 11:51 UTC (permalink / raw)
Done in a separate patch because the diff looks harder to read if one function
(XLogRead) is removed and another one (the WALSegmentOpen callback) is added
nearby at the same time (the addition and removal of code can get mixed in the
diff).
---
src/backend/access/transam/xlogutils.c | 122 ----------------
src/backend/replication/walsender.c | 188 -------------------------
src/bin/pg_waldump/pg_waldump.c | 122 ----------------
3 files changed, 432 deletions(-)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 09d42d3112..002b8f72a9 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -639,128 +639,6 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
forget_invalid_pages(rnode, forkNum, nblocks);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- * in timeline 'tli'.
- *
- * Will open, and keep open, one WAL segment stored in the static file
- * descriptor 'sendFile'. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- *
- * XXX This is very similar to pg_waldump's XLogDumpXLogRead and to XLogRead
- * in walsender.c but for small differences (such as lack of elog() in
- * frontend). Probably these should be merged at some point.
- */
-static void
-XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr,
- Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- /* state maintained across calls */
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static TimeLineID sendTLI = 0;
- static uint32 sendOff = 0;
-
- Assert(segsize == wal_segment_size);
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segsize);
-
- /* Do we need to switch to a different xlog segment? */
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, segsize) ||
- sendTLI != tli)
- {
- char path[MAXPGPATH];
-
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, segsize);
-
- XLogFilePath(path, tli, sendSegNo, segsize);
-
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
-
- if (sendFile < 0)
- {
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- path)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendOff = 0;
- sendTLI = tli;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- path, startoff)));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segsize - startoff))
- segbytes = segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendFile, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes <= 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %lu: %m",
- path, sendOff, (unsigned long) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* Determine which timeline to read an xlog page from and set the
* XLogReaderState's currTLI to that timeline ID.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index dcb84693fc..d7df72fb87 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -253,9 +253,6 @@ static void WalSndSegmentOpen(XLogSegNo nextSegNo, TimeLineID *tli_p,
WALSegmentContext *segcxt);
-static void XLogReadOld(WALSegmentContext *segcxt, char *buf,
- XLogRecPtr startptr, Size count);
-
/* Initialize walsender process before entering the main command loop */
void
InitWalSender(void)
@@ -2375,191 +2372,6 @@ WalSndKill(int code, Datum arg)
SpinLockRelease(&walsnd->mutex);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- *
- * XXX probably this should be improved to suck data directly from the
- * WAL buffers when possible.
- *
- * Will open, and keep open, one WAL segment stored in the global file
- * descriptor sendFile. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- */
-static void
-XLogReadOld(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
- XLogSegNo segno;
-
-retry:
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segcxt->ws_segsize);
-
- if (sendSeg->ws_file < 0 ||
- !XLByteInSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize))
- {
- char path[MAXPGPATH];
-
- /* Switch to another logfile segment */
- if (sendSeg->ws_file >= 0)
- close(sendSeg->ws_file);
-
- XLByteToSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize);
-
- /*-------
- * When reading from a historic timeline, and there is a timeline
- * switch within this segment, read from the WAL segment belonging
- * to the new timeline.
- *
- * For example, imagine that this server is currently on timeline
- * 5, and we're streaming timeline 4. The switch from timeline 4
- * to 5 happened at 0/13002088. In pg_wal, we have these files:
- *
- * ...
- * 000000040000000000000012
- * 000000040000000000000013
- * 000000050000000000000013
- * 000000050000000000000014
- * ...
- *
- * In this situation, when requested to send the WAL from
- * segment 0x13, on timeline 4, we read the WAL from file
- * 000000050000000000000013. Archive recovery prefers files from
- * newer timelines, so if the segment was restored from the
- * archive on this server, the file belonging to the old timeline,
- * 000000040000000000000013, might not exist. Their contents are
- * equal up to the switchpoint, because at a timeline switch, the
- * used portion of the old segment is copied to the new file.
- *-------
- */
- sendSeg->ws_tli = sendTimeLine;
- if (sendTimeLineIsHistoric)
- {
- XLogSegNo endSegNo;
-
- XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize);
- if (sendSeg->ws_segno == endSegNo)
- sendSeg->ws_tli = sendTimeLineNextTLI;
- }
-
- XLogFilePath(path, sendSeg->ws_tli, sendSeg->ws_segno, segcxt->ws_segsize);
-
- sendSeg->ws_file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
- if (sendSeg->ws_file < 0)
- {
- /*
- * If the file is not found, assume it's because the standby
- * asked for a too old WAL segment that has already been
- * removed or recycled.
- */
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno))));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendSeg->ws_off = 0;
- }
-
- /* Need to seek in the file? */
- if (sendSeg->ws_off != startoff)
- {
- if (lseek(sendSeg->ws_file, (off_t) startoff, SEEK_SET) < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- startoff)));
- sendSeg->ws_off = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segcxt->ws_segsize - startoff))
- segbytes = segcxt->ws_segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendSeg->ws_file, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes < 0)
- {
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %zu: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, (Size) segbytes)));
- }
- else if (readbytes == 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("could not read from log segment %s, offset %u: read %d of %zu",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, readbytes, (Size) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendSeg->ws_off += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-
- /*
- * After reading into the buffer, check that what we read was valid. We do
- * this after reading, because even though the segment was present when we
- * opened it, it might get recycled or removed while we read it. The
- * read() succeeds in that case, but the data we tried to read might
- * already have been overwritten with new WAL records.
- */
- XLByteToSeg(startptr, segno, segcxt->ws_segsize);
- CheckXLogRemoved(segno, ThisTimeLineID);
-
- /*
- * During recovery, the currently-open WAL file might be replaced with the
- * file of the same name retrieved from archive. So we always need to
- * check what we read was valid after reading into the buffer. If it's
- * invalid, we try to open and read the file again.
- */
- if (am_cascading_walsender)
- {
- WalSnd *walsnd = MyWalSnd;
- bool reload;
-
- SpinLockAcquire(&walsnd->mutex);
- reload = walsnd->needreload;
- walsnd->needreload = false;
- SpinLockRelease(&walsnd->mutex);
-
- if (reload && sendSeg->ws_file >= 0)
- {
- close(sendSeg->ws_file);
- sendSeg->ws_file = -1;
-
- goto retry;
- }
- }
-}
-
/*
* Callback for XLogRead() to open the next segment.
*/
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index c97376101c..57dd7df56f 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -321,128 +321,6 @@ WALDumpOpenSegment(XLogSegNo nextSegNo, TimeLineID *tli_p, int *file_p,
fname, strerror(errno));
}
-/*
- * Read count bytes from a segment file in the specified directory, for the
- * given timeline, containing the specified record pointer; store the data in
- * the passed buffer.
- */
-static void
-XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
- XLogRecPtr startptr, char *buf, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static uint32 sendOff = 0;
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, WalSegSz);
-
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, WalSegSz))
- {
- char fname[MAXFNAMELEN];
- int tries;
-
- /* Switch to another logfile segment */
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, WalSegSz);
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- /*
- * In follow mode there is a short period of time after the server
- * has written the end of the previous file before the new file is
- * available. So we loop for 5 seconds looking for the file to
- * appear before giving up.
- */
- for (tries = 0; tries < 10; tries++)
- {
- sendFile = open_file_in_directory(directory, fname);
- if (sendFile >= 0)
- break;
- if (errno == ENOENT)
- {
- int save_errno = errno;
-
- /* File not there yet, try again */
- pg_usleep(500 * 1000);
-
- errno = save_errno;
- continue;
- }
- /* Any other error, fall through and fail */
- break;
- }
-
- if (sendFile < 0)
- fatal_error("could not find file \"%s\": %s",
- fname, strerror(errno));
- sendOff = 0;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- fatal_error("could not seek in log file %s to offset %u: %s",
- fname, startoff, strerror(err));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (WalSegSz - startoff))
- segbytes = WalSegSz - startoff;
- else
- segbytes = nbytes;
-
- readbytes = read(sendFile, p, segbytes);
- if (readbytes <= 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
- int save_errno = errno;
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
- errno = save_errno;
-
- if (readbytes < 0)
- fatal_error("could not read from log file %s, offset %u, length %d: %s",
- fname, sendOff, segbytes, strerror(err));
- else if (readbytes == 0)
- fatal_error("could not read from log file %s, offset %u: read %d of %zu",
- fname, sendOff, readbytes, (Size) segbytes);
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* XLogReader read_page callback
*/
--
2.20.1
--=-=-=--
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 2/2] Remove the old implemenations of XLogRead().
@ 2019-10-04 10:07 Antonin Houska <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Antonin Houska @ 2019-10-04 10:07 UTC (permalink / raw)
Done in a separate patch because the diff looks harder to read if one function
(XLogRead) is removed and another one (the WALSegmentOpen callback) is added
nearby at the same time (the addition and removal of code can get mixed in the
diff).
---
src/backend/access/transam/xlogutils.c | 122 ----------------
src/backend/replication/walsender.c | 188 -------------------------
src/bin/pg_waldump/pg_waldump.c | 122 ----------------
3 files changed, 432 deletions(-)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 007974ea99..0211a68640 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -639,128 +639,6 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
forget_invalid_pages(rnode, forkNum, nblocks);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- * in timeline 'tli'.
- *
- * Will open, and keep open, one WAL segment stored in the static file
- * descriptor 'sendFile'. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- *
- * XXX This is very similar to pg_waldump's XLogDumpXLogRead and to XLogRead
- * in walsender.c but for small differences (such as lack of elog() in
- * frontend). Probably these should be merged at some point.
- */
-static void
-XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr,
- Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- /* state maintained across calls */
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static TimeLineID sendTLI = 0;
- static uint32 sendOff = 0;
-
- Assert(segsize == wal_segment_size);
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segsize);
-
- /* Do we need to switch to a different xlog segment? */
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, segsize) ||
- sendTLI != tli)
- {
- char path[MAXPGPATH];
-
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, segsize);
-
- XLogFilePath(path, tli, sendSegNo, segsize);
-
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
-
- if (sendFile < 0)
- {
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- path)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendOff = 0;
- sendTLI = tli;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- path, startoff)));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segsize - startoff))
- segbytes = segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendFile, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes <= 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %lu: %m",
- path, sendOff, (unsigned long) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* Determine which timeline to read an xlog page from and set the
* XLogReaderState's currTLI to that timeline ID.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b21b143f99..76a5477389 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -252,9 +252,6 @@ static void WalSndSegmentOpen(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
TimeLineID *tli_p, int *file_p);
-static void XLogReadOld(WALSegmentContext *segcxt, char *buf,
- XLogRecPtr startptr, Size count);
-
/* Initialize walsender process before entering the main command loop */
void
InitWalSender(void)
@@ -2377,191 +2374,6 @@ WalSndKill(int code, Datum arg)
SpinLockRelease(&walsnd->mutex);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- *
- * XXX probably this should be improved to suck data directly from the
- * WAL buffers when possible.
- *
- * Will open, and keep open, one WAL segment stored in the global file
- * descriptor sendFile. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- */
-static void
-XLogReadOld(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
- XLogSegNo segno;
-
-retry:
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segcxt->ws_segsize);
-
- if (sendSeg->ws_file < 0 ||
- !XLByteInSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize))
- {
- char path[MAXPGPATH];
-
- /* Switch to another logfile segment */
- if (sendSeg->ws_file >= 0)
- close(sendSeg->ws_file);
-
- XLByteToSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize);
-
- /*-------
- * When reading from a historic timeline, and there is a timeline
- * switch within this segment, read from the WAL segment belonging
- * to the new timeline.
- *
- * For example, imagine that this server is currently on timeline
- * 5, and we're streaming timeline 4. The switch from timeline 4
- * to 5 happened at 0/13002088. In pg_wal, we have these files:
- *
- * ...
- * 000000040000000000000012
- * 000000040000000000000013
- * 000000050000000000000013
- * 000000050000000000000014
- * ...
- *
- * In this situation, when requested to send the WAL from
- * segment 0x13, on timeline 4, we read the WAL from file
- * 000000050000000000000013. Archive recovery prefers files from
- * newer timelines, so if the segment was restored from the
- * archive on this server, the file belonging to the old timeline,
- * 000000040000000000000013, might not exist. Their contents are
- * equal up to the switchpoint, because at a timeline switch, the
- * used portion of the old segment is copied to the new file.
- *-------
- */
- sendSeg->ws_tli = sendTimeLine;
- if (sendTimeLineIsHistoric)
- {
- XLogSegNo endSegNo;
-
- XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize);
- if (sendSeg->ws_segno == endSegNo)
- sendSeg->ws_tli = sendTimeLineNextTLI;
- }
-
- XLogFilePath(path, sendSeg->ws_tli, sendSeg->ws_segno, segcxt->ws_segsize);
-
- sendSeg->ws_file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
- if (sendSeg->ws_file < 0)
- {
- /*
- * If the file is not found, assume it's because the standby
- * asked for a too old WAL segment that has already been
- * removed or recycled.
- */
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno))));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendSeg->ws_off = 0;
- }
-
- /* Need to seek in the file? */
- if (sendSeg->ws_off != startoff)
- {
- if (lseek(sendSeg->ws_file, (off_t) startoff, SEEK_SET) < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- startoff)));
- sendSeg->ws_off = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segcxt->ws_segsize - startoff))
- segbytes = segcxt->ws_segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendSeg->ws_file, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes < 0)
- {
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %zu: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, (Size) segbytes)));
- }
- else if (readbytes == 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("could not read from log segment %s, offset %u: read %d of %zu",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, readbytes, (Size) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendSeg->ws_off += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-
- /*
- * After reading into the buffer, check that what we read was valid. We do
- * this after reading, because even though the segment was present when we
- * opened it, it might get recycled or removed while we read it. The
- * read() succeeds in that case, but the data we tried to read might
- * already have been overwritten with new WAL records.
- */
- XLByteToSeg(startptr, segno, segcxt->ws_segsize);
- CheckXLogRemoved(segno, ThisTimeLineID);
-
- /*
- * During recovery, the currently-open WAL file might be replaced with the
- * file of the same name retrieved from archive. So we always need to
- * check what we read was valid after reading into the buffer. If it's
- * invalid, we try to open and read the file again.
- */
- if (am_cascading_walsender)
- {
- WalSnd *walsnd = MyWalSnd;
- bool reload;
-
- SpinLockAcquire(&walsnd->mutex);
- reload = walsnd->needreload;
- walsnd->needreload = false;
- SpinLockRelease(&walsnd->mutex);
-
- if (reload && sendSeg->ws_file >= 0)
- {
- close(sendSeg->ws_file);
- sendSeg->ws_file = -1;
-
- goto retry;
- }
- }
-}
-
/*
* Callback for XLogRead() to open the next segment.
*/
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 4c49d1acf4..39686c235c 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -321,128 +321,6 @@ WALDumpOpenSegment(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
fname, strerror(errno));
}
-/*
- * Read count bytes from a segment file in the specified directory, for the
- * given timeline, containing the specified record pointer; store the data in
- * the passed buffer.
- */
-static void
-XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
- XLogRecPtr startptr, char *buf, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static uint32 sendOff = 0;
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, WalSegSz);
-
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, WalSegSz))
- {
- char fname[MAXFNAMELEN];
- int tries;
-
- /* Switch to another logfile segment */
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, WalSegSz);
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- /*
- * In follow mode there is a short period of time after the server
- * has written the end of the previous file before the new file is
- * available. So we loop for 5 seconds looking for the file to
- * appear before giving up.
- */
- for (tries = 0; tries < 10; tries++)
- {
- sendFile = open_file_in_directory(directory, fname);
- if (sendFile >= 0)
- break;
- if (errno == ENOENT)
- {
- int save_errno = errno;
-
- /* File not there yet, try again */
- pg_usleep(500 * 1000);
-
- errno = save_errno;
- continue;
- }
- /* Any other error, fall through and fail */
- break;
- }
-
- if (sendFile < 0)
- fatal_error("could not find file \"%s\": %s",
- fname, strerror(errno));
- sendOff = 0;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- fatal_error("could not seek in log file %s to offset %u: %s",
- fname, startoff, strerror(err));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (WalSegSz - startoff))
- segbytes = WalSegSz - startoff;
- else
- segbytes = nbytes;
-
- readbytes = read(sendFile, p, segbytes);
- if (readbytes <= 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
- int save_errno = errno;
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
- errno = save_errno;
-
- if (readbytes < 0)
- fatal_error("could not read from log file %s, offset %u, length %d: %s",
- fname, sendOff, segbytes, strerror(err));
- else if (readbytes == 0)
- fatal_error("could not read from log file %s, offset %u: read %d of %zu",
- fname, sendOff, readbytes, (Size) segbytes);
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* XLogReader read_page callback
*/
--
2.20.1
--=-=-=--
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 2/2] Remove the old implemenations of XLogRead().
@ 2019-10-11 10:07 Antonin Houska <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Antonin Houska @ 2019-10-11 10:07 UTC (permalink / raw)
Done in a separate patch because the diff looks harder to read if one function
(XLogRead) is removed and another one (the WALSegmentOpen callback) is added
nearby at the same time (the addition and removal of code can get mixed in the
diff).
---
src/backend/access/transam/xlogutils.c | 122 ----------------
src/backend/replication/walsender.c | 188 -------------------------
src/bin/pg_waldump/pg_waldump.c | 122 ----------------
3 files changed, 432 deletions(-)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 007974ea99..0211a68640 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -639,128 +639,6 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
forget_invalid_pages(rnode, forkNum, nblocks);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- * in timeline 'tli'.
- *
- * Will open, and keep open, one WAL segment stored in the static file
- * descriptor 'sendFile'. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- *
- * XXX This is very similar to pg_waldump's XLogDumpXLogRead and to XLogRead
- * in walsender.c but for small differences (such as lack of elog() in
- * frontend). Probably these should be merged at some point.
- */
-static void
-XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr,
- Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- /* state maintained across calls */
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static TimeLineID sendTLI = 0;
- static uint32 sendOff = 0;
-
- Assert(segsize == wal_segment_size);
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segsize);
-
- /* Do we need to switch to a different xlog segment? */
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, segsize) ||
- sendTLI != tli)
- {
- char path[MAXPGPATH];
-
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, segsize);
-
- XLogFilePath(path, tli, sendSegNo, segsize);
-
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
-
- if (sendFile < 0)
- {
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- path)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendOff = 0;
- sendTLI = tli;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- path, startoff)));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segsize - startoff))
- segbytes = segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendFile, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes <= 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %lu: %m",
- path, sendOff, (unsigned long) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* Determine which timeline to read an xlog page from and set the
* XLogReaderState's currTLI to that timeline ID.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b21b143f99..76a5477389 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -252,9 +252,6 @@ static void WalSndSegmentOpen(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
TimeLineID *tli_p, int *file_p);
-static void XLogReadOld(WALSegmentContext *segcxt, char *buf,
- XLogRecPtr startptr, Size count);
-
/* Initialize walsender process before entering the main command loop */
void
InitWalSender(void)
@@ -2377,191 +2374,6 @@ WalSndKill(int code, Datum arg)
SpinLockRelease(&walsnd->mutex);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- *
- * XXX probably this should be improved to suck data directly from the
- * WAL buffers when possible.
- *
- * Will open, and keep open, one WAL segment stored in the global file
- * descriptor sendFile. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- */
-static void
-XLogReadOld(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
- XLogSegNo segno;
-
-retry:
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segcxt->ws_segsize);
-
- if (sendSeg->ws_file < 0 ||
- !XLByteInSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize))
- {
- char path[MAXPGPATH];
-
- /* Switch to another logfile segment */
- if (sendSeg->ws_file >= 0)
- close(sendSeg->ws_file);
-
- XLByteToSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize);
-
- /*-------
- * When reading from a historic timeline, and there is a timeline
- * switch within this segment, read from the WAL segment belonging
- * to the new timeline.
- *
- * For example, imagine that this server is currently on timeline
- * 5, and we're streaming timeline 4. The switch from timeline 4
- * to 5 happened at 0/13002088. In pg_wal, we have these files:
- *
- * ...
- * 000000040000000000000012
- * 000000040000000000000013
- * 000000050000000000000013
- * 000000050000000000000014
- * ...
- *
- * In this situation, when requested to send the WAL from
- * segment 0x13, on timeline 4, we read the WAL from file
- * 000000050000000000000013. Archive recovery prefers files from
- * newer timelines, so if the segment was restored from the
- * archive on this server, the file belonging to the old timeline,
- * 000000040000000000000013, might not exist. Their contents are
- * equal up to the switchpoint, because at a timeline switch, the
- * used portion of the old segment is copied to the new file.
- *-------
- */
- sendSeg->ws_tli = sendTimeLine;
- if (sendTimeLineIsHistoric)
- {
- XLogSegNo endSegNo;
-
- XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize);
- if (sendSeg->ws_segno == endSegNo)
- sendSeg->ws_tli = sendTimeLineNextTLI;
- }
-
- XLogFilePath(path, sendSeg->ws_tli, sendSeg->ws_segno, segcxt->ws_segsize);
-
- sendSeg->ws_file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
- if (sendSeg->ws_file < 0)
- {
- /*
- * If the file is not found, assume it's because the standby
- * asked for a too old WAL segment that has already been
- * removed or recycled.
- */
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno))));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendSeg->ws_off = 0;
- }
-
- /* Need to seek in the file? */
- if (sendSeg->ws_off != startoff)
- {
- if (lseek(sendSeg->ws_file, (off_t) startoff, SEEK_SET) < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- startoff)));
- sendSeg->ws_off = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segcxt->ws_segsize - startoff))
- segbytes = segcxt->ws_segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendSeg->ws_file, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes < 0)
- {
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %zu: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, (Size) segbytes)));
- }
- else if (readbytes == 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("could not read from log segment %s, offset %u: read %d of %zu",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, readbytes, (Size) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendSeg->ws_off += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-
- /*
- * After reading into the buffer, check that what we read was valid. We do
- * this after reading, because even though the segment was present when we
- * opened it, it might get recycled or removed while we read it. The
- * read() succeeds in that case, but the data we tried to read might
- * already have been overwritten with new WAL records.
- */
- XLByteToSeg(startptr, segno, segcxt->ws_segsize);
- CheckXLogRemoved(segno, ThisTimeLineID);
-
- /*
- * During recovery, the currently-open WAL file might be replaced with the
- * file of the same name retrieved from archive. So we always need to
- * check what we read was valid after reading into the buffer. If it's
- * invalid, we try to open and read the file again.
- */
- if (am_cascading_walsender)
- {
- WalSnd *walsnd = MyWalSnd;
- bool reload;
-
- SpinLockAcquire(&walsnd->mutex);
- reload = walsnd->needreload;
- walsnd->needreload = false;
- SpinLockRelease(&walsnd->mutex);
-
- if (reload && sendSeg->ws_file >= 0)
- {
- close(sendSeg->ws_file);
- sendSeg->ws_file = -1;
-
- goto retry;
- }
- }
-}
-
/*
* Callback for XLogRead() to open the next segment.
*/
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index 4c49d1acf4..39686c235c 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -321,128 +321,6 @@ WALDumpOpenSegment(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
fname, strerror(errno));
}
-/*
- * Read count bytes from a segment file in the specified directory, for the
- * given timeline, containing the specified record pointer; store the data in
- * the passed buffer.
- */
-static void
-XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
- XLogRecPtr startptr, char *buf, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static uint32 sendOff = 0;
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, WalSegSz);
-
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, WalSegSz))
- {
- char fname[MAXFNAMELEN];
- int tries;
-
- /* Switch to another logfile segment */
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, WalSegSz);
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- /*
- * In follow mode there is a short period of time after the server
- * has written the end of the previous file before the new file is
- * available. So we loop for 5 seconds looking for the file to
- * appear before giving up.
- */
- for (tries = 0; tries < 10; tries++)
- {
- sendFile = open_file_in_directory(directory, fname);
- if (sendFile >= 0)
- break;
- if (errno == ENOENT)
- {
- int save_errno = errno;
-
- /* File not there yet, try again */
- pg_usleep(500 * 1000);
-
- errno = save_errno;
- continue;
- }
- /* Any other error, fall through and fail */
- break;
- }
-
- if (sendFile < 0)
- fatal_error("could not find file \"%s\": %s",
- fname, strerror(errno));
- sendOff = 0;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- fatal_error("could not seek in log file %s to offset %u: %s",
- fname, startoff, strerror(err));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (WalSegSz - startoff))
- segbytes = WalSegSz - startoff;
- else
- segbytes = nbytes;
-
- readbytes = read(sendFile, p, segbytes);
- if (readbytes <= 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
- int save_errno = errno;
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
- errno = save_errno;
-
- if (readbytes < 0)
- fatal_error("could not read from log file %s, offset %u, length %d: %s",
- fname, sendOff, segbytes, strerror(err));
- else if (readbytes == 0)
- fatal_error("could not read from log file %s, offset %u: read %d of %zu",
- fname, sendOff, readbytes, (Size) segbytes);
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* XLogReader read_page callback
*/
--
2.20.1
--=-=-=--
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 2/2] Remove the old implemenations of XLogRead().
@ 2019-11-12 10:51 Antonin Houska <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Antonin Houska @ 2019-11-12 10:51 UTC (permalink / raw)
Done in a separate patch because the diff looks harder to read if one function
(XLogRead) is removed and another one (the WALSegmentOpen callback) is added
nearby at the same time (the addition and removal of code can get mixed in the
diff).
---
src/backend/access/transam/xlogutils.c | 122 ----------------
src/backend/replication/walsender.c | 188 -------------------------
src/bin/pg_waldump/pg_waldump.c | 122 ----------------
3 files changed, 432 deletions(-)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 18e436f4fd..98118f484e 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -639,128 +639,6 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
forget_invalid_pages(rnode, forkNum, nblocks);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- * in timeline 'tli'.
- *
- * Will open, and keep open, one WAL segment stored in the static file
- * descriptor 'sendFile'. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- *
- * XXX This is very similar to pg_waldump's XLogDumpXLogRead and to XLogRead
- * in walsender.c but for small differences (such as lack of elog() in
- * frontend). Probably these should be merged at some point.
- */
-static void
-XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr,
- Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- /* state maintained across calls */
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static TimeLineID sendTLI = 0;
- static uint32 sendOff = 0;
-
- Assert(segsize == wal_segment_size);
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segsize);
-
- /* Do we need to switch to a different xlog segment? */
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, segsize) ||
- sendTLI != tli)
- {
- char path[MAXPGPATH];
-
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, segsize);
-
- XLogFilePath(path, tli, sendSegNo, segsize);
-
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
-
- if (sendFile < 0)
- {
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- path)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendOff = 0;
- sendTLI = tli;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- path, startoff)));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segsize - startoff))
- segbytes = segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendFile, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes <= 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %lu: %m",
- path, sendOff, (unsigned long) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* Determine which timeline to read an xlog page from and set the
* XLogReaderState's currTLI to that timeline ID.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 92a539aa5c..80ef5eb909 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -252,9 +252,6 @@ static void WalSndSegmentOpen(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
TimeLineID *tli_p, int *file_p);
-static void XLogReadOld(WALSegmentContext *segcxt, char *buf,
- XLogRecPtr startptr, Size count);
-
/* Initialize walsender process before entering the main command loop */
void
InitWalSender(void)
@@ -2376,191 +2373,6 @@ WalSndKill(int code, Datum arg)
SpinLockRelease(&walsnd->mutex);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- *
- * XXX probably this should be improved to suck data directly from the
- * WAL buffers when possible.
- *
- * Will open, and keep open, one WAL segment stored in the global file
- * descriptor sendFile. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- */
-static void
-XLogReadOld(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
- XLogSegNo segno;
-
-retry:
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segcxt->ws_segsize);
-
- if (sendSeg->ws_file < 0 ||
- !XLByteInSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize))
- {
- char path[MAXPGPATH];
-
- /* Switch to another logfile segment */
- if (sendSeg->ws_file >= 0)
- close(sendSeg->ws_file);
-
- XLByteToSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize);
-
- /*-------
- * When reading from a historic timeline, and there is a timeline
- * switch within this segment, read from the WAL segment belonging
- * to the new timeline.
- *
- * For example, imagine that this server is currently on timeline
- * 5, and we're streaming timeline 4. The switch from timeline 4
- * to 5 happened at 0/13002088. In pg_wal, we have these files:
- *
- * ...
- * 000000040000000000000012
- * 000000040000000000000013
- * 000000050000000000000013
- * 000000050000000000000014
- * ...
- *
- * In this situation, when requested to send the WAL from
- * segment 0x13, on timeline 4, we read the WAL from file
- * 000000050000000000000013. Archive recovery prefers files from
- * newer timelines, so if the segment was restored from the
- * archive on this server, the file belonging to the old timeline,
- * 000000040000000000000013, might not exist. Their contents are
- * equal up to the switchpoint, because at a timeline switch, the
- * used portion of the old segment is copied to the new file.
- *-------
- */
- sendSeg->ws_tli = sendTimeLine;
- if (sendTimeLineIsHistoric)
- {
- XLogSegNo endSegNo;
-
- XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize);
- if (sendSeg->ws_segno == endSegNo)
- sendSeg->ws_tli = sendTimeLineNextTLI;
- }
-
- XLogFilePath(path, sendSeg->ws_tli, sendSeg->ws_segno, segcxt->ws_segsize);
-
- sendSeg->ws_file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
- if (sendSeg->ws_file < 0)
- {
- /*
- * If the file is not found, assume it's because the standby
- * asked for a too old WAL segment that has already been
- * removed or recycled.
- */
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno))));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendSeg->ws_off = 0;
- }
-
- /* Need to seek in the file? */
- if (sendSeg->ws_off != startoff)
- {
- if (lseek(sendSeg->ws_file, (off_t) startoff, SEEK_SET) < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- startoff)));
- sendSeg->ws_off = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segcxt->ws_segsize - startoff))
- segbytes = segcxt->ws_segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendSeg->ws_file, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes < 0)
- {
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %zu: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, (Size) segbytes)));
- }
- else if (readbytes == 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("could not read from log segment %s, offset %u: read %d of %zu",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, readbytes, (Size) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendSeg->ws_off += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-
- /*
- * After reading into the buffer, check that what we read was valid. We do
- * this after reading, because even though the segment was present when we
- * opened it, it might get recycled or removed while we read it. The
- * read() succeeds in that case, but the data we tried to read might
- * already have been overwritten with new WAL records.
- */
- XLByteToSeg(startptr, segno, segcxt->ws_segsize);
- CheckXLogRemoved(segno, ThisTimeLineID);
-
- /*
- * During recovery, the currently-open WAL file might be replaced with the
- * file of the same name retrieved from archive. So we always need to
- * check what we read was valid after reading into the buffer. If it's
- * invalid, we try to open and read the file again.
- */
- if (am_cascading_walsender)
- {
- WalSnd *walsnd = MyWalSnd;
- bool reload;
-
- SpinLockAcquire(&walsnd->mutex);
- reload = walsnd->needreload;
- walsnd->needreload = false;
- SpinLockRelease(&walsnd->mutex);
-
- if (reload && sendSeg->ws_file >= 0)
- {
- close(sendSeg->ws_file);
- sendSeg->ws_file = -1;
-
- goto retry;
- }
- }
-}
-
/*
* Callback for XLogRead() to open the next segment.
*/
diff --git a/src/bin/pg_waldump/pg_waldump.c b/src/bin/pg_waldump/pg_waldump.c
index c0c2590d56..f1908a8868 100644
--- a/src/bin/pg_waldump/pg_waldump.c
+++ b/src/bin/pg_waldump/pg_waldump.c
@@ -320,128 +320,6 @@ WALDumpOpenSegment(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
fname, strerror(errno));
}
-/*
- * Read count bytes from a segment file in the specified directory, for the
- * given timeline, containing the specified record pointer; store the data in
- * the passed buffer.
- */
-static void
-XLogDumpXLogRead(const char *directory, TimeLineID timeline_id,
- XLogRecPtr startptr, char *buf, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static uint32 sendOff = 0;
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, WalSegSz);
-
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, WalSegSz))
- {
- char fname[MAXFNAMELEN];
- int tries;
-
- /* Switch to another logfile segment */
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, WalSegSz);
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- /*
- * In follow mode there is a short period of time after the server
- * has written the end of the previous file before the new file is
- * available. So we loop for 5 seconds looking for the file to
- * appear before giving up.
- */
- for (tries = 0; tries < 10; tries++)
- {
- sendFile = open_file_in_directory(directory, fname);
- if (sendFile >= 0)
- break;
- if (errno == ENOENT)
- {
- int save_errno = errno;
-
- /* File not there yet, try again */
- pg_usleep(500 * 1000);
-
- errno = save_errno;
- continue;
- }
- /* Any other error, fall through and fail */
- break;
- }
-
- if (sendFile < 0)
- fatal_error("could not find file \"%s\": %s",
- fname, strerror(errno));
- sendOff = 0;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
-
- fatal_error("could not seek in log file %s to offset %u: %s",
- fname, startoff, strerror(err));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (WalSegSz - startoff))
- segbytes = WalSegSz - startoff;
- else
- segbytes = nbytes;
-
- readbytes = read(sendFile, p, segbytes);
- if (readbytes <= 0)
- {
- int err = errno;
- char fname[MAXPGPATH];
- int save_errno = errno;
-
- XLogFileName(fname, timeline_id, sendSegNo, WalSegSz);
- errno = save_errno;
-
- if (readbytes < 0)
- fatal_error("could not read from log file %s, offset %u, length %d: %s",
- fname, sendOff, segbytes, strerror(err));
- else if (readbytes == 0)
- fatal_error("could not read from log file %s, offset %u: read %d of %zu",
- fname, sendOff, readbytes, (Size) segbytes);
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* XLogReader read_page callback
*/
--
2.20.1
--=-=-=--
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 2/2] Remove the old implemenations of XLogRead().
@ 2019-11-20 14:11 Antonin Houska <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Antonin Houska @ 2019-11-20 14:11 UTC (permalink / raw)
Done in a separate patch because the diff looks harder to read if one function
(XLogRead) is removed and another one (the WALSegmentOpen callback) is added
nearby at the same time (the addition and removal of code can get mixed in the
diff).
---
src/backend/access/transam/xlogutils.c | 122 ----------------
src/backend/replication/walsender.c | 188 -------------------------
2 files changed, 310 deletions(-)
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index baca17260c..a16d47f156 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -639,128 +639,6 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
forget_invalid_pages(rnode, forkNum, nblocks);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- * in timeline 'tli'.
- *
- * Will open, and keep open, one WAL segment stored in the static file
- * descriptor 'sendFile'. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- *
- * XXX This is very similar to pg_waldump's XLogDumpXLogRead and to XLogRead
- * in walsender.c but for small differences (such as lack of elog() in
- * frontend). Probably these should be merged at some point.
- */
-static void
-XLogReadOld(char *buf, int segsize, TimeLineID tli, XLogRecPtr startptr,
- Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
-
- /* state maintained across calls */
- static int sendFile = -1;
- static XLogSegNo sendSegNo = 0;
- static TimeLineID sendTLI = 0;
- static uint32 sendOff = 0;
-
- Assert(segsize == wal_segment_size);
-
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segsize);
-
- /* Do we need to switch to a different xlog segment? */
- if (sendFile < 0 || !XLByteInSeg(recptr, sendSegNo, segsize) ||
- sendTLI != tli)
- {
- char path[MAXPGPATH];
-
- if (sendFile >= 0)
- close(sendFile);
-
- XLByteToSeg(recptr, sendSegNo, segsize);
-
- XLogFilePath(path, tli, sendSegNo, segsize);
-
- sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY);
-
- if (sendFile < 0)
- {
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- path)));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendOff = 0;
- sendTLI = tli;
- }
-
- /* Need to seek in the file? */
- if (sendOff != startoff)
- {
- if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- path, startoff)));
- }
- sendOff = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segsize - startoff))
- segbytes = segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendFile, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes <= 0)
- {
- char path[MAXPGPATH];
- int save_errno = errno;
-
- XLogFilePath(path, tli, sendSegNo, segsize);
- errno = save_errno;
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %lu: %m",
- path, sendOff, (unsigned long) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendOff += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-}
-
/*
* Determine which timeline to read an xlog page from and set the
* XLogReaderState's currTLI to that timeline ID.
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 11611072b0..d2426b0960 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -252,9 +252,6 @@ static int WalSndSegmentOpen(XLogSegNo nextSegNo, WALSegmentContext *segcxt,
TimeLineID *tli_p);
-static void XLogReadOld(WALSegmentContext *segcxt, char *buf,
- XLogRecPtr startptr, Size count);
-
/* Initialize walsender process before entering the main command loop */
void
InitWalSender(void)
@@ -2376,191 +2373,6 @@ WalSndKill(int code, Datum arg)
SpinLockRelease(&walsnd->mutex);
}
-/*
- * Read 'count' bytes from WAL into 'buf', starting at location 'startptr'
- *
- * XXX probably this should be improved to suck data directly from the
- * WAL buffers when possible.
- *
- * Will open, and keep open, one WAL segment stored in the global file
- * descriptor sendFile. This means if XLogRead is used once, there will
- * always be one descriptor left open until the process ends, but never
- * more than one.
- */
-static void
-XLogReadOld(WALSegmentContext *segcxt, char *buf, XLogRecPtr startptr, Size count)
-{
- char *p;
- XLogRecPtr recptr;
- Size nbytes;
- XLogSegNo segno;
-
-retry:
- p = buf;
- recptr = startptr;
- nbytes = count;
-
- while (nbytes > 0)
- {
- uint32 startoff;
- int segbytes;
- int readbytes;
-
- startoff = XLogSegmentOffset(recptr, segcxt->ws_segsize);
-
- if (sendSeg->ws_file < 0 ||
- !XLByteInSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize))
- {
- char path[MAXPGPATH];
-
- /* Switch to another logfile segment */
- if (sendSeg->ws_file >= 0)
- close(sendSeg->ws_file);
-
- XLByteToSeg(recptr, sendSeg->ws_segno, segcxt->ws_segsize);
-
- /*-------
- * When reading from a historic timeline, and there is a timeline
- * switch within this segment, read from the WAL segment belonging
- * to the new timeline.
- *
- * For example, imagine that this server is currently on timeline
- * 5, and we're streaming timeline 4. The switch from timeline 4
- * to 5 happened at 0/13002088. In pg_wal, we have these files:
- *
- * ...
- * 000000040000000000000012
- * 000000040000000000000013
- * 000000050000000000000013
- * 000000050000000000000014
- * ...
- *
- * In this situation, when requested to send the WAL from
- * segment 0x13, on timeline 4, we read the WAL from file
- * 000000050000000000000013. Archive recovery prefers files from
- * newer timelines, so if the segment was restored from the
- * archive on this server, the file belonging to the old timeline,
- * 000000040000000000000013, might not exist. Their contents are
- * equal up to the switchpoint, because at a timeline switch, the
- * used portion of the old segment is copied to the new file.
- *-------
- */
- sendSeg->ws_tli = sendTimeLine;
- if (sendTimeLineIsHistoric)
- {
- XLogSegNo endSegNo;
-
- XLByteToSeg(sendTimeLineValidUpto, endSegNo, segcxt->ws_segsize);
- if (sendSeg->ws_segno == endSegNo)
- sendSeg->ws_tli = sendTimeLineNextTLI;
- }
-
- XLogFilePath(path, sendSeg->ws_tli, sendSeg->ws_segno, segcxt->ws_segsize);
-
- sendSeg->ws_file = BasicOpenFile(path, O_RDONLY | PG_BINARY);
- if (sendSeg->ws_file < 0)
- {
- /*
- * If the file is not found, assume it's because the standby
- * asked for a too old WAL segment that has already been
- * removed or recycled.
- */
- if (errno == ENOENT)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("requested WAL segment %s has already been removed",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno))));
- else
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not open file \"%s\": %m",
- path)));
- }
- sendSeg->ws_off = 0;
- }
-
- /* Need to seek in the file? */
- if (sendSeg->ws_off != startoff)
- {
- if (lseek(sendSeg->ws_file, (off_t) startoff, SEEK_SET) < 0)
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not seek in log segment %s to offset %u: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- startoff)));
- sendSeg->ws_off = startoff;
- }
-
- /* How many bytes are within this segment? */
- if (nbytes > (segcxt->ws_segsize - startoff))
- segbytes = segcxt->ws_segsize - startoff;
- else
- segbytes = nbytes;
-
- pgstat_report_wait_start(WAIT_EVENT_WAL_READ);
- readbytes = read(sendSeg->ws_file, p, segbytes);
- pgstat_report_wait_end();
- if (readbytes < 0)
- {
- ereport(ERROR,
- (errcode_for_file_access(),
- errmsg("could not read from log segment %s, offset %u, length %zu: %m",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, (Size) segbytes)));
- }
- else if (readbytes == 0)
- {
- ereport(ERROR,
- (errcode(ERRCODE_DATA_CORRUPTED),
- errmsg("could not read from log segment %s, offset %u: read %d of %zu",
- XLogFileNameP(sendSeg->ws_tli, sendSeg->ws_segno),
- sendSeg->ws_off, readbytes, (Size) segbytes)));
- }
-
- /* Update state for read */
- recptr += readbytes;
-
- sendSeg->ws_off += readbytes;
- nbytes -= readbytes;
- p += readbytes;
- }
-
- /*
- * After reading into the buffer, check that what we read was valid. We do
- * this after reading, because even though the segment was present when we
- * opened it, it might get recycled or removed while we read it. The
- * read() succeeds in that case, but the data we tried to read might
- * already have been overwritten with new WAL records.
- */
- XLByteToSeg(startptr, segno, segcxt->ws_segsize);
- CheckXLogRemoved(segno, ThisTimeLineID);
-
- /*
- * During recovery, the currently-open WAL file might be replaced with the
- * file of the same name retrieved from archive. So we always need to
- * check what we read was valid after reading into the buffer. If it's
- * invalid, we try to open and read the file again.
- */
- if (am_cascading_walsender)
- {
- WalSnd *walsnd = MyWalSnd;
- bool reload;
-
- SpinLockAcquire(&walsnd->mutex);
- reload = walsnd->needreload;
- walsnd->needreload = false;
- SpinLockRelease(&walsnd->mutex);
-
- if (reload && sendSeg->ws_file >= 0)
- {
- close(sendSeg->ws_file);
- sendSeg->ws_file = -1;
-
- goto retry;
- }
- }
-}
-
/*
* Callback for XLogRead() to open the next segment.
*/
--
2.20.1
--=-=-=--
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v1 4/9] ci: switch tasks to debugoptimized build
@ 2023-08-08 00:27 Andres Freund <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Andres Freund @ 2023-08-08 00:27 UTC (permalink / raw)
In aggregate the CI tasks burn a lot of cpu hours. Compared to that easy to
read backtraces aren't as important. Still use -ggdb where appropriate, as
that does make backtraces more reliable, particularly in the face of
optimization.
Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
.cirrus.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.cirrus.yml b/.cirrus.yml
index f3d63ff3fb0..bfe251f48e8 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -140,7 +140,7 @@ task:
CCACHE_DIR: /tmp/ccache_dir
CPPFLAGS: -DRELCACHE_FORCE_RELEASE -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
- CFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
depends_on: SanityCheck
only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*freebsd.*'
@@ -181,7 +181,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true -Duuid=bsd -Dtcl_version=tcl86 -Ddtrace=auto \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
-Dextra_lib_dirs=/usr/local/lib -Dextra_include_dirs=/usr/local/include/ \
@@ -275,7 +275,7 @@ task:
ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
# SANITIZER_FLAGS is set in the tasks below
- CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
+ CFLAGS: -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
CXXFLAGS: $CFLAGS
LDFLAGS: $SANITIZER_FLAGS
CC: ccache gcc
@@ -364,7 +364,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
@@ -377,7 +377,7 @@ task:
su postgres <<-EOF
export CC='ccache gcc -m32'
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-Dllvm=disabled \
@@ -434,8 +434,8 @@ task:
CC: ccache cc
CXX: ccache c++
- CFLAGS: -Og -ggdb
- CXXFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
+ CXXFLAGS: -ggdb
depends_on: SanityCheck
only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*(macos|darwin|osx).*'
@@ -487,7 +487,7 @@ task:
configure_script: |
export PKG_CONFIG_PATH="/opt/local/lib/pkgconfig/"
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dextra_include_dirs=/opt/local/include \
-Dextra_lib_dirs=/opt/local/lib \
-Dcassert=true \
--
2.38.0
--uu4yojthqnm7ulmd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v1-0005-ci-Move-use-of-Dsegsize_blocks-6-from-macos-to-li.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v1 4/9] ci: switch tasks to debugoptimized build
@ 2023-08-08 00:27 Andres Freund <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Andres Freund @ 2023-08-08 00:27 UTC (permalink / raw)
In aggregate the CI tasks burn a lot of cpu hours. Compared to that easy to
read backtraces aren't as important. Still use -ggdb where appropriate, as
that does make backtraces more reliable, particularly in the face of
optimization.
Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
.cirrus.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.cirrus.yml b/.cirrus.yml
index f3d63ff3fb0..bfe251f48e8 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -140,7 +140,7 @@ task:
CCACHE_DIR: /tmp/ccache_dir
CPPFLAGS: -DRELCACHE_FORCE_RELEASE -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
- CFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
depends_on: SanityCheck
only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*freebsd.*'
@@ -181,7 +181,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true -Duuid=bsd -Dtcl_version=tcl86 -Ddtrace=auto \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
-Dextra_lib_dirs=/usr/local/lib -Dextra_include_dirs=/usr/local/include/ \
@@ -275,7 +275,7 @@ task:
ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
# SANITIZER_FLAGS is set in the tasks below
- CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
+ CFLAGS: -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
CXXFLAGS: $CFLAGS
LDFLAGS: $SANITIZER_FLAGS
CC: ccache gcc
@@ -364,7 +364,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
@@ -377,7 +377,7 @@ task:
su postgres <<-EOF
export CC='ccache gcc -m32'
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-Dllvm=disabled \
@@ -434,8 +434,8 @@ task:
CC: ccache cc
CXX: ccache c++
- CFLAGS: -Og -ggdb
- CXXFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
+ CXXFLAGS: -ggdb
depends_on: SanityCheck
only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*(macos|darwin|osx).*'
@@ -487,7 +487,7 @@ task:
configure_script: |
export PKG_CONFIG_PATH="/opt/local/lib/pkgconfig/"
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dextra_include_dirs=/opt/local/include \
-Dextra_lib_dirs=/opt/local/lib \
-Dcassert=true \
--
2.38.0
--uu4yojthqnm7ulmd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v1-0005-ci-Move-use-of-Dsegsize_blocks-6-from-macos-to-li.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v1 4/9] ci: switch tasks to debugoptimized build
@ 2023-08-08 00:27 Andres Freund <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Andres Freund @ 2023-08-08 00:27 UTC (permalink / raw)
In aggregate the CI tasks burn a lot of cpu hours. Compared to that easy to
read backtraces aren't as important. Still use -ggdb where appropriate, as
that does make backtraces more reliable, particularly in the face of
optimization.
Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
.cirrus.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.cirrus.yml b/.cirrus.yml
index f3d63ff3fb0..bfe251f48e8 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -140,7 +140,7 @@ task:
CCACHE_DIR: /tmp/ccache_dir
CPPFLAGS: -DRELCACHE_FORCE_RELEASE -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
- CFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
depends_on: SanityCheck
only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*freebsd.*'
@@ -181,7 +181,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true -Duuid=bsd -Dtcl_version=tcl86 -Ddtrace=auto \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
-Dextra_lib_dirs=/usr/local/lib -Dextra_include_dirs=/usr/local/include/ \
@@ -275,7 +275,7 @@ task:
ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
# SANITIZER_FLAGS is set in the tasks below
- CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
+ CFLAGS: -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
CXXFLAGS: $CFLAGS
LDFLAGS: $SANITIZER_FLAGS
CC: ccache gcc
@@ -364,7 +364,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
@@ -377,7 +377,7 @@ task:
su postgres <<-EOF
export CC='ccache gcc -m32'
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-Dllvm=disabled \
@@ -434,8 +434,8 @@ task:
CC: ccache cc
CXX: ccache c++
- CFLAGS: -Og -ggdb
- CXXFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
+ CXXFLAGS: -ggdb
depends_on: SanityCheck
only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\nci-os-only:.*' || $CIRRUS_CHANGE_MESSAGE =~ '.*\nci-os-only:[^\n]*(macos|darwin|osx).*'
@@ -487,7 +487,7 @@ task:
configure_script: |
export PKG_CONFIG_PATH="/opt/local/lib/pkgconfig/"
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dextra_include_dirs=/opt/local/include \
-Dextra_lib_dirs=/opt/local/lib \
-Dcassert=true \
--
2.38.0
--uu4yojthqnm7ulmd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v1-0005-ci-Move-use-of-Dsegsize_blocks-6-from-macos-to-li.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v3 08/10] ci: switch tasks to debugoptimized build
@ 2023-08-08 00:27 Andres Freund <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Andres Freund @ 2023-08-08 00:27 UTC (permalink / raw)
In aggregate the CI tasks burn a lot of cpu hours. Compared to that easy to
read backtraces aren't as important. Still use -ggdb where appropriate, as
that does make backtraces more reliable, particularly in the face of
optimization.
Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
.cirrus.tasks.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index e137769850d..d1730ce08a8 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -136,7 +136,7 @@ task:
CCACHE_DIR: /tmp/ccache_dir
CPPFLAGS: -DRELCACHE_FORCE_RELEASE -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
- CFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
<<: *freebsd_task_template
@@ -171,7 +171,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true -Duuid=bsd -Dtcl_version=tcl86 -Ddtrace=auto \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
-Dextra_lib_dirs=/usr/local/lib -Dextra_include_dirs=/usr/local/include/ \
@@ -266,7 +266,7 @@ task:
ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
# SANITIZER_FLAGS is set in the tasks below
- CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
+ CFLAGS: -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
CXXFLAGS: $CFLAGS
LDFLAGS: $SANITIZER_FLAGS
CC: ccache gcc
@@ -356,7 +356,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
@@ -369,7 +369,7 @@ task:
su postgres <<-EOF
export CC='ccache gcc -m32'
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-Dllvm=disabled \
@@ -427,8 +427,8 @@ task:
CC: ccache cc
CXX: ccache c++
- CFLAGS: -Og -ggdb
- CXXFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
+ CXXFLAGS: -ggdb
<<: *macos_task_template
@@ -479,7 +479,7 @@ task:
configure_script: |
export PKG_CONFIG_PATH="/opt/local/lib/pkgconfig/"
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dextra_include_dirs=/opt/local/include \
-Dextra_lib_dirs=/opt/local/lib \
-Dcassert=true \
--
2.38.0
--uh2yukyzfvojbe2k
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0009-ci-windows-Disabling-write-cache-flushing-during-.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v3 08/10] ci: switch tasks to debugoptimized build
@ 2023-08-08 00:27 Andres Freund <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Andres Freund @ 2023-08-08 00:27 UTC (permalink / raw)
In aggregate the CI tasks burn a lot of cpu hours. Compared to that easy to
read backtraces aren't as important. Still use -ggdb where appropriate, as
that does make backtraces more reliable, particularly in the face of
optimization.
Author:
Reviewed-by:
Discussion: https://postgr.es/m/
Backpatch:
---
.cirrus.tasks.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml
index e137769850d..d1730ce08a8 100644
--- a/.cirrus.tasks.yml
+++ b/.cirrus.tasks.yml
@@ -136,7 +136,7 @@ task:
CCACHE_DIR: /tmp/ccache_dir
CPPFLAGS: -DRELCACHE_FORCE_RELEASE -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
- CFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
<<: *freebsd_task_template
@@ -171,7 +171,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true -Duuid=bsd -Dtcl_version=tcl86 -Ddtrace=auto \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
-Dextra_lib_dirs=/usr/local/lib -Dextra_include_dirs=/usr/local/include/ \
@@ -266,7 +266,7 @@ task:
ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0
# SANITIZER_FLAGS is set in the tasks below
- CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
+ CFLAGS: -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS
CXXFLAGS: $CFLAGS
LDFLAGS: $SANITIZER_FLAGS
CC: ccache gcc
@@ -356,7 +356,7 @@ task:
configure_script: |
su postgres <<-EOF
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-DPG_TEST_EXTRA="$PG_TEST_EXTRA" \
@@ -369,7 +369,7 @@ task:
su postgres <<-EOF
export CC='ccache gcc -m32'
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dcassert=true \
${LINUX_MESON_FEATURES} \
-Dllvm=disabled \
@@ -427,8 +427,8 @@ task:
CC: ccache cc
CXX: ccache c++
- CFLAGS: -Og -ggdb
- CXXFLAGS: -Og -ggdb
+ CFLAGS: -ggdb
+ CXXFLAGS: -ggdb
<<: *macos_task_template
@@ -479,7 +479,7 @@ task:
configure_script: |
export PKG_CONFIG_PATH="/opt/local/lib/pkgconfig/"
meson setup \
- --buildtype=debug \
+ --buildtype=debugoptimized \
-Dextra_include_dirs=/opt/local/include \
-Dextra_lib_dirs=/opt/local/lib \
-Dcassert=true \
--
2.38.0
--uh2yukyzfvojbe2k
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0009-ci-windows-Disabling-write-cache-flushing-during-.patch"
^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v4 4/4] Remove specialized word-length popcount implementations.
@ 2026-01-23 23:31 Nathan Bossart <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Nathan Bossart @ 2026-01-23 23:31 UTC (permalink / raw)
---
src/include/port/pg_bitutils.h | 29 ++++-------------------
src/port/pg_bitutils.c | 27 +++++++--------------
src/port/pg_popcount_aarch64.c | 22 +++++------------
src/port/pg_popcount_x86.c | 43 +---------------------------------
4 files changed, 19 insertions(+), 102 deletions(-)
diff --git a/src/include/port/pg_bitutils.h b/src/include/port/pg_bitutils.h
index 35761f509ec..101815daa98 100644
--- a/src/include/port/pg_bitutils.h
+++ b/src/include/port/pg_bitutils.h
@@ -276,42 +276,21 @@ pg_ceil_log2_64(uint64 num)
return pg_leftmost_one_pos64(num - 1) + 1;
}
-extern int pg_popcount32_portable(uint32 word);
-extern int pg_popcount64_portable(uint64 word);
+extern int pg_popcount32(uint32 word);
+extern int pg_popcount64(uint64 word);
extern uint64 pg_popcount_portable(const char *buf, int bytes);
extern uint64 pg_popcount_masked_portable(const char *buf, int bytes, bits8 mask);
-#ifdef HAVE_X86_64_POPCNTQ
+#if defined(HAVE_X86_64_POPCNTQ) || defined(USE_SVE_POPCNT_WITH_RUNTIME_CHECK)
/*
- * Attempt to use SSE4.2 or AVX-512 instructions, but perform a runtime check
+ * Attempt to use specialized CPU instructions, but perform a runtime check
* first.
*/
-extern PGDLLIMPORT int (*pg_popcount32) (uint32 word);
-extern PGDLLIMPORT int (*pg_popcount64) (uint64 word);
-extern PGDLLIMPORT uint64 (*pg_popcount_optimized) (const char *buf, int bytes);
-extern PGDLLIMPORT uint64 (*pg_popcount_masked_optimized) (const char *buf, int bytes, bits8 mask);
-
-#elif defined(USE_NEON)
-/* Use the Neon version of pg_popcount{32,64} without function pointer. */
-extern int pg_popcount32(uint32 word);
-extern int pg_popcount64(uint64 word);
-
-/*
- * We can try to use an SVE-optimized pg_popcount() on some systems For that,
- * we do use a function pointer.
- */
-#ifdef USE_SVE_POPCNT_WITH_RUNTIME_CHECK
extern PGDLLIMPORT uint64 (*pg_popcount_optimized) (const char *buf, int bytes);
extern PGDLLIMPORT uint64 (*pg_popcount_masked_optimized) (const char *buf, int bytes, bits8 mask);
-#else
-extern uint64 pg_popcount_optimized(const char *buf, int bytes);
-extern uint64 pg_popcount_masked_optimized(const char *buf, int bytes, bits8 mask);
-#endif
#else
/* Use a portable implementation -- no need for a function pointer. */
-extern int pg_popcount32(uint32 word);
-extern int pg_popcount64(uint64 word);
extern uint64 pg_popcount_optimized(const char *buf, int bytes);
extern uint64 pg_popcount_masked_optimized(const char *buf, int bytes, bits8 mask);
diff --git a/src/port/pg_bitutils.c b/src/port/pg_bitutils.c
index ffda75825e5..037b313a46b 100644
--- a/src/port/pg_bitutils.c
+++ b/src/port/pg_bitutils.c
@@ -97,11 +97,11 @@ const uint8 pg_number_of_ones[256] = {
};
/*
- * pg_popcount32_portable
+ * pg_popcount32
* Return the number of 1 bits set in word
*/
int
-pg_popcount32_portable(uint32 word)
+pg_popcount32(uint32 word)
{
#ifdef HAVE__BUILTIN_POPCOUNT
return __builtin_popcount(word);
@@ -119,11 +119,11 @@ pg_popcount32_portable(uint32 word)
}
/*
- * pg_popcount64_portable
+ * pg_popcount64
* Return the number of 1 bits set in word
*/
int
-pg_popcount64_portable(uint64 word)
+pg_popcount64(uint64 word)
{
#ifdef HAVE__BUILTIN_POPCOUNT
#if SIZEOF_LONG == 8
@@ -163,7 +163,7 @@ pg_popcount_portable(const char *buf, int bytes)
while (bytes >= 8)
{
- popcnt += pg_popcount64_portable(*words++);
+ popcnt += pg_popcount64(*words++);
bytes -= 8;
}
@@ -177,7 +177,7 @@ pg_popcount_portable(const char *buf, int bytes)
while (bytes >= 4)
{
- popcnt += pg_popcount32_portable(*words++);
+ popcnt += pg_popcount32(*words++);
bytes -= 4;
}
@@ -211,7 +211,7 @@ pg_popcount_masked_portable(const char *buf, int bytes, bits8 mask)
while (bytes >= 8)
{
- popcnt += pg_popcount64_portable(*words++ & maskv);
+ popcnt += pg_popcount64(*words++ & maskv);
bytes -= 8;
}
@@ -227,7 +227,7 @@ pg_popcount_masked_portable(const char *buf, int bytes, bits8 mask)
while (bytes >= 4)
{
- popcnt += pg_popcount32_portable(*words++ & maskv);
+ popcnt += pg_popcount32(*words++ & maskv);
bytes -= 4;
}
@@ -250,17 +250,6 @@ pg_popcount_masked_portable(const char *buf, int bytes, bits8 mask)
* actual external functions. The compiler should be able to inline the
* portable versions here.
*/
-int
-pg_popcount32(uint32 word)
-{
- return pg_popcount32_portable(word);
-}
-
-int
-pg_popcount64(uint64 word)
-{
- return pg_popcount64_portable(word);
-}
/*
* pg_popcount_optimized
diff --git a/src/port/pg_popcount_aarch64.c b/src/port/pg_popcount_aarch64.c
index ba57f2cd4bd..99fd24eb980 100644
--- a/src/port/pg_popcount_aarch64.c
+++ b/src/port/pg_popcount_aarch64.c
@@ -292,21 +292,11 @@ pg_popcount_masked_optimized(const char *buf, int bytes, bits8 mask)
#endif /* ! USE_SVE_POPCNT_WITH_RUNTIME_CHECK */
/*
- * pg_popcount32
- * Return number of 1 bits in word
+ * pg_popcount64_neon
+ * Return number of 1 bits in word
*/
-int
-pg_popcount32(uint32 word)
-{
- return pg_popcount64((uint64) word);
-}
-
-/*
- * pg_popcount64
- * Return number of 1 bits in word
- */
-int
-pg_popcount64(uint64 word)
+static inline int
+pg_popcount64_neon(uint64 word)
{
/*
* For some compilers, __builtin_popcountl() already emits Neon
@@ -383,7 +373,7 @@ pg_popcount_neon(const char *buf, int bytes)
*/
for (; bytes >= sizeof(uint64); bytes -= sizeof(uint64))
{
- popcnt += pg_popcount64(*((const uint64 *) buf));
+ popcnt += pg_popcount64_neon(*((const uint64 *) buf));
buf += sizeof(uint64);
}
@@ -465,7 +455,7 @@ pg_popcount_masked_neon(const char *buf, int bytes, bits8 mask)
*/
for (; bytes >= sizeof(uint64); bytes -= sizeof(uint64))
{
- popcnt += pg_popcount64(*((const uint64 *) buf) & mask64);
+ popcnt += pg_popcount64_neon(*((const uint64 *) buf) & mask64);
buf += sizeof(uint64);
}
diff --git a/src/port/pg_popcount_x86.c b/src/port/pg_popcount_x86.c
index 0e98f532552..9fd8e18ed16 100644
--- a/src/port/pg_popcount_x86.c
+++ b/src/port/pg_popcount_x86.c
@@ -36,8 +36,6 @@
* operation, but in practice this is close enough, and "sse42" seems easier to
* follow than "popcnt" for these names.
*/
-static inline int pg_popcount32_sse42(uint32 word);
-static inline int pg_popcount64_sse42(uint64 word);
static uint64 pg_popcount_sse42(const char *buf, int bytes);
static uint64 pg_popcount_masked_sse42(const char *buf, int bytes, bits8 mask);
@@ -55,12 +53,8 @@ static uint64 pg_popcount_masked_avx512(const char *buf, int bytes, bits8 mask);
* what the current CPU supports) and then will call the pointer to fulfill the
* caller's request.
*/
-static int pg_popcount32_choose(uint32 word);
-static int pg_popcount64_choose(uint64 word);
static uint64 pg_popcount_choose(const char *buf, int bytes);
static uint64 pg_popcount_masked_choose(const char *buf, int bytes, bits8 mask);
-int (*pg_popcount32) (uint32 word) = pg_popcount32_choose;
-int (*pg_popcount64) (uint64 word) = pg_popcount64_choose;
uint64 (*pg_popcount_optimized) (const char *buf, int bytes) = pg_popcount_choose;
uint64 (*pg_popcount_masked_optimized) (const char *buf, int bytes, bits8 mask) = pg_popcount_masked_choose;
@@ -157,7 +151,7 @@ pg_popcount_avx512_available(void)
#endif /* USE_AVX512_POPCNT_WITH_RUNTIME_CHECK */
/*
- * These functions get called on the first call to pg_popcount32 etc.
+ * These functions get called on the first call to pg_popcount(), etc.
* They detect whether we can use the asm implementations, and replace
* the function pointers so that subsequent calls are routed directly to
* the chosen implementation.
@@ -167,15 +161,11 @@ choose_popcount_functions(void)
{
if (pg_popcount_sse42_available())
{
- pg_popcount32 = pg_popcount32_sse42;
- pg_popcount64 = pg_popcount64_sse42;
pg_popcount_optimized = pg_popcount_sse42;
pg_popcount_masked_optimized = pg_popcount_masked_sse42;
}
else
{
- pg_popcount32 = pg_popcount32_portable;
- pg_popcount64 = pg_popcount64_portable;
pg_popcount_optimized = pg_popcount_portable;
pg_popcount_masked_optimized = pg_popcount_masked_portable;
}
@@ -189,20 +179,6 @@ choose_popcount_functions(void)
#endif
}
-static int
-pg_popcount32_choose(uint32 word)
-{
- choose_popcount_functions();
- return pg_popcount32(word);
-}
-
-static int
-pg_popcount64_choose(uint64 word)
-{
- choose_popcount_functions();
- return pg_popcount64(word);
-}
-
static uint64
pg_popcount_choose(const char *buf, int bytes)
{
@@ -338,23 +314,6 @@ pg_popcount_masked_avx512(const char *buf, int bytes, bits8 mask)
#endif /* USE_AVX512_POPCNT_WITH_RUNTIME_CHECK */
-/*
- * pg_popcount32_sse42
- * Return the number of 1 bits set in word
- */
-static inline int
-pg_popcount32_sse42(uint32 word)
-{
-#ifdef _MSC_VER
- return __popcnt(word);
-#else
- uint32 res;
-
-__asm__ __volatile__(" popcntl %1,%0\n":"=q"(res):"rm"(word):"cc");
- return (int) res;
-#endif
-}
-
/*
* pg_popcount64_sse42
* Return the number of 1 bits set in word
--
2.50.1 (Apple Git-155)
--u/0bDz5KPuHk2IF+--
^ permalink raw reply [nested|flat] 49+ messages in thread
* VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-24 15:35 Jim Jones <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Jim Jones @ 2026-03-24 15:35 UTC (permalink / raw)
To: PostgreSQL Hackers <[email protected]>; +Cc: Antonin Houska <[email protected]>
Hi,
While testing another patch [1], I noticed that REPACK is blocked when a
temporary table is locked in another session. It also turns out that the
same behaviour occurs with VACUUM FULL and CLUSTER:
== session 1 ==
$ psql postgres
psql (19devel)
Type "help" for help.
postgres=# CREATE TEMPORARY TABLE tmp (id int);
CREATE TABLE
postgres=# BEGIN;
LOCK TABLE tmp IN SHARE MODE;
BEGIN
LOCK TABLE
postgres=*#
== session 2 ==
$ psql postgres
psql (19devel)
Type "help" for help.
postgres=# REPACK;
^CCancel request sent
ERROR: canceling statement due to user request
CONTEXT: waiting for AccessExclusiveLock on relation 38458 of database 5
postgres=# VACUUM FULL;
^CCancel request sent
ERROR: canceling statement due to user request
CONTEXT: waiting for AccessExclusiveLock on relation 38458 of database 5
Skipping temporary relations in get_tables_to_repack() and
get_all_vacuum_rels() before they're appended to the list seems to do
the trick -- see attached draft.
I can reproduce the same behaviour with CLUSTER and VACUUM FULL in
PG14-PG18. I took a quick look at the code in PG17 and PG18 and the fix
appears to be straightforward, but before I start working on it, I'd
like to hear your thoughts. Is it worth the effort?
Best, Jim
1 - https://www.postgresql.org/message-id/13637.1774342137%40localhost
Attachments:
[text/x-patch] v1-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch (2.8K, ../../[email protected]/2-v1-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch)
download | inline diff:
From d57d212305abd2cfb15c6c61f98e503e1a736ab2 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Tue, 24 Mar 2026 13:50:26 +0100
Subject: [PATCH v1] Skip other sessions' temp tables in REPACK, CLUSTER, and
VACUUM FULL
get_tables_to_repack() was including other sessions' temporary tables
in the work list, causing REPACK and CLUSTER (without arguments) to
attempt to acquire AccessExclusiveLock on them, potentially blocking
for an extended time. Fix by skipping other-session temp tables early
in get_tables_to_repack(), before they are added to the list. Because
an AccessShareLock has already been acquired per relation at that
point, release it before continuing.
Similarly, get_all_vacuum_rels() suffered from the same problem for
VACUUM FULL. Since no per-relation lock is held during list-building
there, a plain skip suffices.
---
src/backend/commands/cluster.c | 15 +++++++++++++++
src/backend/commands/vacuum.c | 5 +++++
2 files changed, 20 insertions(+)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 09066db095..f701d426dd 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1699,6 +1699,13 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
}
+ /* Skip temp relations belonging to other sessions */
+ if (isOtherTempNamespace(get_rel_namespace(index->indrelid)))
+ {
+ UnlockRelationOid(index->indrelid, AccessShareLock);
+ continue;
+ }
+
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
GetUserId()))
@@ -1753,6 +1760,14 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
}
+ /* Skip temp relations belonging to other sessions */
+ if (class->relpersistence == RELPERSISTENCE_TEMP &&
+ isOtherTempNamespace(class->relnamespace))
+ {
+ UnlockRelationOid(class->oid, AccessShareLock);
+ continue;
+ }
+
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
GetUserId()))
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index bce3a2daa2..642f4fee1c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1062,6 +1062,11 @@ get_all_vacuum_rels(MemoryContext vac_context, int options)
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue;
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ isOtherTempNamespace(classForm->relnamespace))
+ continue;
+
/* check permissions of relation */
if (!vacuum_is_permitted_for_relation(relid, classForm, options))
continue;
--
2.43.0
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-25 01:55 Chao Li <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Chao Li @ 2026-03-25 01:55 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Antonin Houska <[email protected]>
> On Mar 24, 2026, at 23:35, Jim Jones <[email protected]> wrote:
>
> Hi,
>
> While testing another patch [1], I noticed that REPACK is blocked when a
> temporary table is locked in another session. It also turns out that the
> same behaviour occurs with VACUUM FULL and CLUSTER:
>
> == session 1 ==
>
> $ psql postgres
> psql (19devel)
> Type "help" for help.
>
> postgres=# CREATE TEMPORARY TABLE tmp (id int);
> CREATE TABLE
> postgres=# BEGIN;
> LOCK TABLE tmp IN SHARE MODE;
> BEGIN
> LOCK TABLE
> postgres=*#
>
> == session 2 ==
>
> $ psql postgres
> psql (19devel)
> Type "help" for help.
>
> postgres=# REPACK;
> ^CCancel request sent
> ERROR: canceling statement due to user request
> CONTEXT: waiting for AccessExclusiveLock on relation 38458 of database 5
> postgres=# VACUUM FULL;
> ^CCancel request sent
> ERROR: canceling statement due to user request
> CONTEXT: waiting for AccessExclusiveLock on relation 38458 of database 5
>
> Skipping temporary relations in get_tables_to_repack() and
> get_all_vacuum_rels() before they're appended to the list seems to do
> the trick -- see attached draft.
>
> I can reproduce the same behaviour with CLUSTER and VACUUM FULL in
> PG14-PG18. I took a quick look at the code in PG17 and PG18 and the fix
> appears to be straightforward, but before I start working on it, I'd
> like to hear your thoughts. Is it worth the effort?
>
> Best, Jim
>
> 1 - https://www.postgresql.org/message-id/13637.1774342137%40localhost<v1-0001-Skip-other-sessions-te...;
I think skipping temp tables of another session is reasonable, because anyway they are not accessible from the current session, though visible via pg_class.
Looking at the patch:
```
+ /* Skip temp relations belonging to other sessions */
+ if (class->relpersistence == RELPERSISTENCE_TEMP &&
+ isOtherTempNamespace(class->relnamespace))
```
It uses isOtherTempNamespace(), but I noticed that the header comment of the function says:
```
* isOtherTempNamespace - is the given namespace some other backend's
* temporary-table namespace (including temporary-toast-table namespaces)?
*
* Note: for most purposes in the C code, this function is obsolete. Use
* RELATION_IS_OTHER_TEMP() instead to detect non-local temp relations.
```
Then looking at RELATION_IS_OTHER_TEMP():
```
#define RELATION_IS_OTHER_TEMP(relation) \
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
```
It takes a relation as parameter and check relation->rd_islocaltemp, however in the context of this patch, we have only Form_pg_class.
Then checking how rd_islocaltemp is set:
```
case RELPERSISTENCE_TEMP:
if (isTempOrTempToastNamespace(relation->rd_rel->relnamespace))
{
relation->rd_backend = ProcNumberForTempRelations();
relation->rd_islocaltemp = true;
}
else
{
/*
* If it's a temp table, but not one of ours, we have to use
* the slow, grotty method to figure out the owning backend.
*
* Note: it's possible that rd_backend gets set to
* MyProcNumber here, in case we are looking at a pg_class
* entry left over from a crashed backend that coincidentally
* had the same ProcNumber we're using. We should *not*
* consider such a table to be "ours"; this is why we need the
* separate rd_islocaltemp flag. The pg_class entry will get
* flushed if/when we clean out the corresponding temp table
* namespace in preparation for using it.
*/
relation->rd_backend =
GetTempNamespaceProcNumber(relation->rd_rel->relnamespace);
Assert(relation->rd_backend != INVALID_PROC_NUMBER);
relation->rd_islocaltemp = false;
}
break;
```
It uses isTempOrTempToastNamespace(relation->rd_rel->relnamespace) to decide relation->rd_islocaltemp.
So, I think this patch should also use "!isTempOrTempToastNamespace(classForm->relnamespace)" instead of isOtherTempNamespace(class->relnamespace). I tried that locally, and it works for me.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-25 09:05 Jim Jones <[email protected]>
parent: Chao Li <[email protected]>
0 siblings, 2 replies; 49+ messages in thread
From: Jim Jones @ 2026-03-25 09:05 UTC (permalink / raw)
To: Chao Li <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Antonin Houska <[email protected]>
Hi Chao,
Thanks for the thorough review.
On 25/03/2026 02:55, Chao Li wrote:
> It uses isTempOrTempToastNamespace(relation->rd_rel->relnamespace) to decide relation->rd_islocaltemp.
>
> So, I think this patch should also use "!isTempOrTempToastNamespace(classForm->relnamespace)" instead of isOtherTempNamespace(class->relnamespace). I tried that locally, and it works for me.
I agree. In get_all_vacuum_rels() and the pg_class scan branch of
get_tables_to_repack(), relpersistence == RELPERSISTENCE_TEMP already
establishes the relation is a temp table, so
!isTempOrTempToastNamespace() alone is sufficient to identify
other-session temp tables.
In the usingindex path of get_tables_to_repack(), Form_pg_class is not
available, so there is no relpersistence to use as a pre-filter. The
explicit isAnyTempNamespace() check is required to avoid incorrectly
skipping permanent tables. This is pretty much what
isOtherTempNamespace() does internally -- the only change is inlining it
to avoid the obsolete wrapper.
* Skip temp relations belonging to other sessions */
{
Oid nsp = get_rel_namespace(index->indrelid);
if (!isTempOrTempToastNamespace(nsp) && isAnyTempNamespace(nsp))
{
UnlockRelationOid(index->indrelid, AccessShareLock);
continue;
}
}
v2 attached.
Thanks!
Best, Jim
Attachments:
[text/x-patch] v2-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch (3.0K, ../../[email protected]/2-v2-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch)
download | inline diff:
From 5f8528db5d457ddc3158b00b7768c8e088e1e577 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Wed, 25 Mar 2026 09:34:06 +0100
Subject: [PATCH v2] Skip other sessions' temp tables in REPACK, CLUSTER, and
VACUUM FULL
get_tables_to_repack() was including other sessions' temporary tables
in the work list, causing REPACK and CLUSTER (without arguments) to
attempt to acquire AccessExclusiveLock on them, potentially blocking
for an extended time. Fix by skipping other-session temp tables early
in get_tables_to_repack(), before they are added to the list. Because
an AccessShareLock has already been acquired per relation at that
point, release it before continuing.
Similarly, get_all_vacuum_rels() suffered from the same problem for
VACUUM FULL. Since no per-relation lock is held during list-building
there, a plain skip suffices.
Author: Jim Jones <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
src/backend/commands/cluster.c | 19 +++++++++++++++++++
src/backend/commands/vacuum.c | 5 +++++
2 files changed, 24 insertions(+)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 09066db0956..386bd34c16b 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1699,6 +1699,17 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
}
+ /* Skip temp relations belonging to other sessions */
+ {
+ Oid nsp = get_rel_namespace(index->indrelid);
+
+ if (!isTempOrTempToastNamespace(nsp) && isAnyTempNamespace(nsp))
+ {
+ UnlockRelationOid(index->indrelid, AccessShareLock);
+ continue;
+ }
+ }
+
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
GetUserId()))
@@ -1753,6 +1764,14 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
}
+ /* Skip temp relations belonging to other sessions */
+ if (class->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(class->relnamespace))
+ {
+ UnlockRelationOid(class->oid, AccessShareLock);
+ continue;
+ }
+
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
GetUserId()))
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index bce3a2daa24..9b0a5a38a8a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1062,6 +1062,11 @@ get_all_vacuum_rels(MemoryContext vac_context, int options)
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue;
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ continue;
+
/* check permissions of relation */
if (!vacuum_is_permitted_for_relation(relid, classForm, options))
continue;
--
2.43.0
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-25 10:32 Jim Jones <[email protected]>
parent: Jim Jones <[email protected]>
1 sibling, 0 replies; 49+ messages in thread
From: Jim Jones @ 2026-03-25 10:32 UTC (permalink / raw)
To: Chao Li <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Antonin Houska <[email protected]>
I forgot to create a CF entry. Here it is:
https://commitfest.postgresql.org/patch/6616/
Best, Jim
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-25 20:38 Zsolt Parragi <[email protected]>
parent: Jim Jones <[email protected]>
1 sibling, 1 reply; 49+ messages in thread
From: Zsolt Parragi @ 2026-03-25 20:38 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Chao Li <[email protected]>; PostgreSQL Hackers <[email protected]>; Antonin Houska <[email protected]>
Hello!
Shouldn't the patch also include a tap test to verify that the change
works / fails without it?
+ /* Skip temp relations belonging to other sessions */
+ {
+ Oid nsp = get_rel_namespace(index->indrelid);
+
+ if (!isTempOrTempToastNamespace(nsp) && isAnyTempNamespace(nsp))
+ {
Doesn't this result in several repeated syscache lookups?
There's already a SearchSysCacheExsists1 directly above this, then a
get_rel_namespace, then an isAnyTempNamespace. While this probably
isn't performance critical, this should be doable with a single
SearchSysCache1(RELOID...) and then a few conditions, similarly to the
else branch below this?
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-25 23:00 Jim Jones <[email protected]>
parent: Zsolt Parragi <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Jim Jones @ 2026-03-25 23:00 UTC (permalink / raw)
To: Zsolt Parragi <[email protected]>; +Cc: Chao Li <[email protected]>; PostgreSQL Hackers <[email protected]>; Antonin Houska <[email protected]>
Hi
On 25/03/2026 21:38, Zsolt Parragi wrote:
> Shouldn't the patch also include a tap test to verify that the change
> works / fails without it?
Definitely. I just didn't want to invest much time on tests before
getting feedback on the issue itself.
> + /* Skip temp relations belonging to other sessions */
> + {
> + Oid nsp = get_rel_namespace(index->indrelid);
> +
> + if (!isTempOrTempToastNamespace(nsp) && isAnyTempNamespace(nsp))
> + {
>
> Doesn't this result in several repeated syscache lookups?
>
> There's already a SearchSysCacheExsists1 directly above this, then a
> get_rel_namespace, then an isAnyTempNamespace. While this probably
> isn't performance critical, this should be doable with a single
> SearchSysCache1(RELOID...) and then a few conditions, similarly to the
> else branch below this?
You're right. Although it is not performance critical we can solve it
with a single SearchSysCache1.
PFA v3 with the improved fix (0001) and tests (0002).
Thanks for the review!
Best, Jim
Attachments:
[text/x-patch] v3-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch (3.7K, ../../[email protected]/2-v3-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch)
download | inline diff:
From 6699534c03772b0e5b06680b2e382a36eb108a67 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Wed, 25 Mar 2026 23:14:12 +0100
Subject: [PATCH v3 1/2] Skip other sessions' temp tables in REPACK, CLUSTER,
and VACUUM FULL
get_tables_to_repack() was including other sessions' temporary tables
in the work list, causing REPACK and CLUSTER (without arguments) to
attempt to acquire AccessExclusiveLock on them, potentially blocking
for an extended time. Fix by skipping other-session temp tables early
in get_tables_to_repack(), before they are added to the list. Because
an AccessShareLock has already been acquired per relation at that
point, release it before continuing.
Similarly, get_all_vacuum_rels() suffered from the same problem for
VACUUM FULL. Since no per-relation lock is held during list-building
there, a plain skip suffices.
Author: Jim Jones <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
---
src/backend/commands/cluster.c | 25 ++++++++++++++++++++++++-
src/backend/commands/vacuum.c | 5 +++++
2 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 09066db0956..14c11e8e532 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1679,6 +1679,8 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
{
RelToCluster *rtc;
Form_pg_index index;
+ HeapTuple classtup;
+ Form_pg_class classForm;
MemoryContext oldcxt;
index = (Form_pg_index) GETSTRUCT(tuple);
@@ -1693,11 +1695,24 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
/* Verify that the table still exists; skip if not */
- if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(index->indrelid)))
+ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+ if (!HeapTupleIsValid(classtup))
{
UnlockRelationOid(index->indrelid, AccessShareLock);
continue;
}
+ classForm = (Form_pg_class) GETSTRUCT(classtup);
+
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ {
+ ReleaseSysCache(classtup);
+ UnlockRelationOid(index->indrelid, AccessShareLock);
+ continue;
+ }
+
+ ReleaseSysCache(classtup);
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -1753,6 +1768,14 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
}
+ /* Skip temp relations belonging to other sessions */
+ if (class->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(class->relnamespace))
+ {
+ UnlockRelationOid(class->oid, AccessShareLock);
+ continue;
+ }
+
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
GetUserId()))
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index bce3a2daa24..9b0a5a38a8a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1062,6 +1062,11 @@ get_all_vacuum_rels(MemoryContext vac_context, int options)
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue;
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ continue;
+
/* check permissions of relation */
if (!vacuum_is_permitted_for_relation(relid, classForm, options))
continue;
--
2.43.0
[text/x-patch] v3-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch (3.7K, ../../[email protected]/3-v3-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch)
download | inline diff:
From 97d1b2023b8c4ac5d92610e9761d112ab68cbe23 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Wed, 25 Mar 2026 23:30:55 +0100
Subject: [PATCH v3 2/2] Test VACUUM FULL, CLUSTER, and REPACK with locked temp
tables
This test creates a background session with a temp table and marks
its index as clustered (making it visible to both the pg_class scan
used by VACUUM FULL and REPACK, and the pg_index scan used by CLUSTER),
then holds ACCESS SHARE LOCK in an open transaction. Each command
runs with lock_timeout = '1ms'. Since lock_timeout only fires when a
backend actually blocks waiting for a lock, 1ms is sufficient.
---
src/test/modules/test_misc/meson.build | 1 +
.../t/011_vacuum_cluster_temp_tables.pl | 65 +++++++++++++++++++
2 files changed, 66 insertions(+)
create mode 100644 src/test/modules/test_misc/t/011_vacuum_cluster_temp_tables.pl
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 6e8db1621a7..d64e8df56bf 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -19,6 +19,7 @@ tests += {
't/008_replslot_single_user.pl',
't/009_log_temp_files.pl',
't/010_index_concurrently_upsert.pl',
+ 't/011_vacuum_cluster_temp_tables.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/011_vacuum_cluster_temp_tables.pl b/src/test/modules/test_misc/t/011_vacuum_cluster_temp_tables.pl
new file mode 100644
index 00000000000..a612b4d6361
--- /dev/null
+++ b/src/test/modules/test_misc/t/011_vacuum_cluster_temp_tables.pl
@@ -0,0 +1,65 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+#
+# Verify that no-argument VACUUM FULL, CLUSTER, and REPACK skip temporary
+# tables belonging to other sessions.
+#
+# A background session creates a temp table and marks its index as clustered —
+# making it visible to both the pg_class scan (VACUUM FULL, REPACK) and the
+# pg_index scan (CLUSTER) — then holds ACCESS SHARE LOCK in an open transaction.
+# Each command runs with lock_timeout = '1ms'. Since lock_timeout only
+# fires when a backend actually blocks waiting for a lock, 1ms is sufficient.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('vacuum_cluster_temp');
+$node->init;
+$node->start;
+
+# Session 1: build the temp table and hold a conflicting lock.
+my $psql1 = $node->background_psql('postgres');
+
+$psql1->query_safe(
+ q{CREATE TEMP TABLE temp_repack_test (val int);
+ INSERT INTO temp_repack_test VALUES (1);
+ CREATE INDEX temp_repack_idx ON temp_repack_test (val);
+ CLUSTER temp_repack_test USING temp_repack_idx;});
+
+$psql1->query_safe(q{BEGIN});
+$psql1->query_safe(q{LOCK TABLE temp_repack_test IN ACCESS SHARE MODE});
+
+my ($stdout, $stderr, $ret);
+
+# VACUUM FULL — pg_class scan path.
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; VACUUM FULL;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'VACUUM FULL completes without blocking on another session temp table');
+
+# CLUSTER — pg_index scan path (indisclustered entries).
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; CLUSTER;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'CLUSTER completes without blocking on another session temp table');
+
+# REPACK — pg_class scan path.
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; REPACK;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'REPACK completes without blocking on another session temp table');
+
+$psql1->quit;
+
+done_testing();
--
2.43.0
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-26 02:52 Chao Li <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Chao Li @ 2026-03-26 02:52 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; PostgreSQL Hackers <[email protected]>; Antonin Houska <[email protected]>
> On Mar 26, 2026, at 07:00, Jim Jones <[email protected]> wrote:
>
> Hi
>
> On 25/03/2026 21:38, Zsolt Parragi wrote:
>> Shouldn't the patch also include a tap test to verify that the change
>> works / fails without it?
>
> Definitely. I just didn't want to invest much time on tests before
> getting feedback on the issue itself.
>
>> + /* Skip temp relations belonging to other sessions */
>> + {
>> + Oid nsp = get_rel_namespace(index->indrelid);
>> +
>> + if (!isTempOrTempToastNamespace(nsp) && isAnyTempNamespace(nsp))
>> + {
>>
>> Doesn't this result in several repeated syscache lookups?
>>
>> There's already a SearchSysCacheExsists1 directly above this, then a
>> get_rel_namespace, then an isAnyTempNamespace. While this probably
>> isn't performance critical, this should be doable with a single
>> SearchSysCache1(RELOID...) and then a few conditions, similarly to the
>> else branch below this?
>
> You're right. Although it is not performance critical we can solve it
> with a single SearchSysCache1.
>
> PFA v3 with the improved fix (0001) and tests (0002).
>
> Thanks for the review!
>
> Best, Jim<v3-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch><v3-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch>
I don't think such a TAP test is necessary.
One reason is that, if we look at the check right above the new one:
```
/*
* We include partitioned tables here; depending on which operation is
* to be performed, caller will decide whether to process or ignore
* them.
*/
if (classForm->relkind != RELKIND_RELATION &&
classForm->relkind != RELKIND_MATVIEW &&
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue;
```
I don't see a test specifically for that check either. So I don't think we need a test for every individual path.
Second, based on [1] and [2], I got the impression that adding new tests is not always welcome considering overall test runtime. Anyway, maybe I’m wrong, let the committers judge that.
[1] https://postgr.es/m/mtkrkkcn2tlhytumitpch5ubxiprv2jzvprf5r5m3mjeczvq4q@p6wkzbfxuyv2 <https://postgr.es/m/;
[2] https://postgr.es/m/[email protected]
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-26 11:25 Antonin Houska <[email protected]>
parent: Chao Li <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Antonin Houska @ 2026-03-26 11:25 UTC (permalink / raw)
To: Chao Li <[email protected]>; +Cc: Jim Jones <[email protected]>; Zsolt Parragi <[email protected]>; PostgreSQL Hackers <[email protected]>
Chao Li <[email protected]> wrote:
> I don't think such a TAP test is necessary.
+1
--
Antonin Houska
Web: https://www.cybertec-postgresql.com
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-03-26 11:53 Jim Jones <[email protected]>
parent: Antonin Houska <[email protected]>
0 siblings, 2 replies; 49+ messages in thread
From: Jim Jones @ 2026-03-26 11:53 UTC (permalink / raw)
To: Antonin Houska <[email protected]>; Chao Li <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; PostgreSQL Hackers <[email protected]>
On 26/03/2026 12:25, Antonin Houska wrote:
> Chao Li <[email protected]> wrote:
>
>> I don't think such a TAP test is necessary.
> +1
I've kept the tests in a separate file so the committer can easily skip
them if needed.
Thanks for the feedback, everyone!
Best, Jim
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-04-06 18:13 Jim Jones <[email protected]>
parent: Jim Jones <[email protected]>
1 sibling, 1 reply; 49+ messages in thread
From: Jim Jones @ 2026-04-06 18:13 UTC (permalink / raw)
To: Antonin Houska <[email protected]>; Chao Li <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; PostgreSQL Hackers <[email protected]>
rebase
Jim
Attachments:
[text/x-patch] v4-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch (3.7K, ../../[email protected]/2-v4-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch)
download | inline diff:
From 7a50ea8527634a39ccb9abbf8f573883faccd353 Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Wed, 25 Mar 2026 23:14:12 +0100
Subject: [PATCH v4 1/2] Skip other sessions' temp tables in REPACK, CLUSTER,
and VACUUM FULL
get_tables_to_repack() was including other sessions' temporary tables
in the work list, causing REPACK and CLUSTER (without arguments) to
attempt to acquire AccessExclusiveLock on them, potentially blocking
for an extended time. Fix by skipping other-session temp tables early
in get_tables_to_repack(), before they are added to the list. Because
an AccessShareLock has already been acquired per relation at that
point, release it before continuing.
Similarly, get_all_vacuum_rels() suffered from the same problem for
VACUUM FULL. Since no per-relation lock is held during list-building
there, a plain skip suffices.
Author: Jim Jones <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
---
src/backend/commands/cluster.c | 25 ++++++++++++++++++++++++-
src/backend/commands/vacuum.c | 5 +++++
2 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index f241e18b153..ffbc5ed6f85 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1679,6 +1679,8 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
{
RelToCluster *rtc;
Form_pg_index index;
+ HeapTuple classtup;
+ Form_pg_class classForm;
MemoryContext oldcxt;
index = (Form_pg_index) GETSTRUCT(tuple);
@@ -1693,11 +1695,24 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
/* Verify that the table still exists; skip if not */
- if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(index->indrelid)))
+ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+ if (!HeapTupleIsValid(classtup))
{
UnlockRelationOid(index->indrelid, AccessShareLock);
continue;
}
+ classForm = (Form_pg_class) GETSTRUCT(classtup);
+
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ {
+ ReleaseSysCache(classtup);
+ UnlockRelationOid(index->indrelid, AccessShareLock);
+ continue;
+ }
+
+ ReleaseSysCache(classtup);
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -1753,6 +1768,14 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
}
+ /* Skip temp relations belonging to other sessions */
+ if (class->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(class->relnamespace))
+ {
+ UnlockRelationOid(class->oid, AccessShareLock);
+ continue;
+ }
+
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
GetUserId()))
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0ed363d1c85..913dfb4ea8d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1062,6 +1062,11 @@ get_all_vacuum_rels(MemoryContext vac_context, int options)
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue;
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ continue;
+
/* check permissions of relation */
if (!vacuum_is_permitted_for_relation(relid, classForm, options))
continue;
--
2.43.0
[text/x-patch] v4-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch (3.7K, ../../[email protected]/3-v4-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch)
download | inline diff:
From 3e689b85190fc074a4fe7c9e2e1b3f618faf08bf Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Mon, 6 Apr 2026 19:40:56 +0200
Subject: [PATCH v4 2/2] Test VACUUM FULL, CLUSTER, and REPACK with locked temp
tables
This test creates a background session with a temp table and marks
its index as clustered (making it visible to both the pg_class scan
used by VACUUM FULL and REPACK, and the pg_index scan used by CLUSTER),
then holds ACCESS SHARE LOCK in an open transaction. Each command
runs with lock_timeout = '1ms'. Since lock_timeout only fires when a
backend actually blocks waiting for a lock, 1ms is sufficient.
---
src/test/modules/test_misc/meson.build | 1 +
.../t/012_vacuum_cluster_temp_tables.pl | 65 +++++++++++++++++++
2 files changed, 66 insertions(+)
create mode 100644 src/test/modules/test_misc/t/012_vacuum_cluster_temp_tables.pl
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 1b25d98f7f3..8995bc837f2 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -20,6 +20,7 @@ tests += {
't/009_log_temp_files.pl',
't/010_index_concurrently_upsert.pl',
't/011_lock_stats.pl',
+ 't/012_vacuum_cluster_temp_tables.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/012_vacuum_cluster_temp_tables.pl b/src/test/modules/test_misc/t/012_vacuum_cluster_temp_tables.pl
new file mode 100644
index 00000000000..a612b4d6361
--- /dev/null
+++ b/src/test/modules/test_misc/t/012_vacuum_cluster_temp_tables.pl
@@ -0,0 +1,65 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+#
+# Verify that no-argument VACUUM FULL, CLUSTER, and REPACK skip temporary
+# tables belonging to other sessions.
+#
+# A background session creates a temp table and marks its index as clustered —
+# making it visible to both the pg_class scan (VACUUM FULL, REPACK) and the
+# pg_index scan (CLUSTER) — then holds ACCESS SHARE LOCK in an open transaction.
+# Each command runs with lock_timeout = '1ms'. Since lock_timeout only
+# fires when a backend actually blocks waiting for a lock, 1ms is sufficient.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('vacuum_cluster_temp');
+$node->init;
+$node->start;
+
+# Session 1: build the temp table and hold a conflicting lock.
+my $psql1 = $node->background_psql('postgres');
+
+$psql1->query_safe(
+ q{CREATE TEMP TABLE temp_repack_test (val int);
+ INSERT INTO temp_repack_test VALUES (1);
+ CREATE INDEX temp_repack_idx ON temp_repack_test (val);
+ CLUSTER temp_repack_test USING temp_repack_idx;});
+
+$psql1->query_safe(q{BEGIN});
+$psql1->query_safe(q{LOCK TABLE temp_repack_test IN ACCESS SHARE MODE});
+
+my ($stdout, $stderr, $ret);
+
+# VACUUM FULL — pg_class scan path.
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; VACUUM FULL;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'VACUUM FULL completes without blocking on another session temp table');
+
+# CLUSTER — pg_index scan path (indisclustered entries).
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; CLUSTER;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'CLUSTER completes without blocking on another session temp table');
+
+# REPACK — pg_class scan path.
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; REPACK;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'REPACK completes without blocking on another session temp table');
+
+$psql1->quit;
+
+done_testing();
--
2.43.0
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-04-06 22:52 Jim Jones <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Jim Jones @ 2026-04-06 22:52 UTC (permalink / raw)
To: Antonin Houska <[email protected]>; Chao Li <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; PostgreSQL Hackers <[email protected]>
rebase
Jim
Attachments:
[text/x-patch] v5-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch (3.7K, ../../[email protected]/2-v5-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch)
download | inline diff:
From f7876b7c225146fe3deef681841d0ad6d9782a5d Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Tue, 7 Apr 2026 00:27:59 +0200
Subject: [PATCH v5 1/2] Skip other sessions' temp tables in REPACK, CLUSTER,
and VACUUM FULL
get_tables_to_repack() was including other sessions' temporary tables
in the work list, causing REPACK and CLUSTER (without arguments) to
attempt to acquire AccessExclusiveLock on them, potentially blocking
for an extended time. Fix by skipping other-session temp tables early
in get_tables_to_repack(), before they are added to the list. Because
an AccessShareLock has already been acquired per relation at that
point, release it before continuing.
Similarly, get_all_vacuum_rels() suffered from the same problem for
VACUUM FULL. Since no per-relation lock is held during list-building
there, a plain skip suffices.
Author: Jim Jones <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
---
src/backend/commands/repack.c | 25 ++++++++++++++++++++++++-
src/backend/commands/vacuum.c | 5 +++++
2 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 17b639b3b44..1806efddb8f 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2107,6 +2107,8 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
{
RelToCluster *rtc;
Form_pg_index index;
+ HeapTuple classtup;
+ Form_pg_class classForm;
MemoryContext oldcxt;
index = (Form_pg_index) GETSTRUCT(tuple);
@@ -2121,11 +2123,24 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
/* Verify that the table still exists; skip if not */
- if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(index->indrelid)))
+ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+ if (!HeapTupleIsValid(classtup))
{
UnlockRelationOid(index->indrelid, AccessShareLock);
continue;
}
+ classForm = (Form_pg_class) GETSTRUCT(classtup);
+
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ {
+ ReleaseSysCache(classtup);
+ UnlockRelationOid(index->indrelid, AccessShareLock);
+ continue;
+ }
+
+ ReleaseSysCache(classtup);
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2181,6 +2196,14 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
}
+ /* Skip temp relations belonging to other sessions */
+ if (class->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(class->relnamespace))
+ {
+ UnlockRelationOid(class->oid, AccessShareLock);
+ continue;
+ }
+
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
GetUserId()))
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 99d0db82ed7..a4abb29cf64 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1063,6 +1063,11 @@ get_all_vacuum_rels(MemoryContext vac_context, int options)
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue;
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ continue;
+
/* check permissions of relation */
if (!vacuum_is_permitted_for_relation(relid, classForm, options))
continue;
--
2.43.0
[text/x-patch] v5-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch (3.7K, ../../[email protected]/3-v5-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch)
download | inline diff:
From 4cc9f3d4b5aa8c3544599c47ab3ec9a62a96381d Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Mon, 6 Apr 2026 19:40:56 +0200
Subject: [PATCH v5 2/2] Test VACUUM FULL, CLUSTER, and REPACK with locked temp
tables
This test creates a background session with a temp table and marks
its index as clustered (making it visible to both the pg_class scan
used by VACUUM FULL and REPACK, and the pg_index scan used by CLUSTER),
then holds ACCESS SHARE LOCK in an open transaction. Each command
runs with lock_timeout = '1ms'. Since lock_timeout only fires when a
backend actually blocks waiting for a lock, 1ms is sufficient.
---
src/test/modules/test_misc/meson.build | 1 +
.../t/012_vacuum_cluster_temp_tables.pl | 65 +++++++++++++++++++
2 files changed, 66 insertions(+)
create mode 100644 src/test/modules/test_misc/t/012_vacuum_cluster_temp_tables.pl
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 1b25d98f7f3..8995bc837f2 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -20,6 +20,7 @@ tests += {
't/009_log_temp_files.pl',
't/010_index_concurrently_upsert.pl',
't/011_lock_stats.pl',
+ 't/012_vacuum_cluster_temp_tables.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/012_vacuum_cluster_temp_tables.pl b/src/test/modules/test_misc/t/012_vacuum_cluster_temp_tables.pl
new file mode 100644
index 00000000000..a612b4d6361
--- /dev/null
+++ b/src/test/modules/test_misc/t/012_vacuum_cluster_temp_tables.pl
@@ -0,0 +1,65 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+#
+# Verify that no-argument VACUUM FULL, CLUSTER, and REPACK skip temporary
+# tables belonging to other sessions.
+#
+# A background session creates a temp table and marks its index as clustered —
+# making it visible to both the pg_class scan (VACUUM FULL, REPACK) and the
+# pg_index scan (CLUSTER) — then holds ACCESS SHARE LOCK in an open transaction.
+# Each command runs with lock_timeout = '1ms'. Since lock_timeout only
+# fires when a backend actually blocks waiting for a lock, 1ms is sufficient.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('vacuum_cluster_temp');
+$node->init;
+$node->start;
+
+# Session 1: build the temp table and hold a conflicting lock.
+my $psql1 = $node->background_psql('postgres');
+
+$psql1->query_safe(
+ q{CREATE TEMP TABLE temp_repack_test (val int);
+ INSERT INTO temp_repack_test VALUES (1);
+ CREATE INDEX temp_repack_idx ON temp_repack_test (val);
+ CLUSTER temp_repack_test USING temp_repack_idx;});
+
+$psql1->query_safe(q{BEGIN});
+$psql1->query_safe(q{LOCK TABLE temp_repack_test IN ACCESS SHARE MODE});
+
+my ($stdout, $stderr, $ret);
+
+# VACUUM FULL — pg_class scan path.
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; VACUUM FULL;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'VACUUM FULL completes without blocking on another session temp table');
+
+# CLUSTER — pg_index scan path (indisclustered entries).
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; CLUSTER;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'CLUSTER completes without blocking on another session temp table');
+
+# REPACK — pg_class scan path.
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; REPACK;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'REPACK completes without blocking on another session temp table');
+
+$psql1->quit;
+
+done_testing();
--
2.43.0
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-05-01 19:34 Jim Jones <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Jim Jones @ 2026-05-01 19:34 UTC (permalink / raw)
To: Antonin Houska <[email protected]>; Chao Li <[email protected]>; +Cc: Zsolt Parragi <[email protected]>; PostgreSQL Hackers <[email protected]>
rebase
Jim
Attachments:
[text/x-patch] v6-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch (3.7K, ../../[email protected]/2-v6-0002-Test-VACUUM-FULL-CLUSTER-and-REPACK-with-locked-t.patch)
download | inline diff:
From def7fbf223f6f2db2e71eeb42fa10536f16b4c0b Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Fri, 1 May 2026 21:31:25 +0200
Subject: [PATCH v6 2/2] Test VACUUM FULL, CLUSTER, and REPACK with locked temp
tables
This test creates a background session with a temp table and marks
its index as clustered (making it visible to both the pg_class scan
used by VACUUM FULL and REPACK, and the pg_index scan used by CLUSTER),
then holds ACCESS SHARE LOCK in an open transaction. Each command
runs with lock_timeout = '1ms'. Since lock_timeout only fires when a
backend actually blocks waiting for a lock, 1ms is sufficient.
---
src/test/modules/test_misc/meson.build | 1 +
.../t/013_vacuum_cluster_temp_tables.pl | 65 +++++++++++++++++++
2 files changed, 66 insertions(+)
create mode 100644 src/test/modules/test_misc/t/013_vacuum_cluster_temp_tables.pl
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 356d8454b39..e74d461db3a 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -21,6 +21,7 @@ tests += {
't/010_index_concurrently_upsert.pl',
't/011_lock_stats.pl',
't/012_ddlutils.pl',
+ 't/013_vacuum_cluster_temp_tables.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/013_vacuum_cluster_temp_tables.pl b/src/test/modules/test_misc/t/013_vacuum_cluster_temp_tables.pl
new file mode 100644
index 00000000000..a612b4d6361
--- /dev/null
+++ b/src/test/modules/test_misc/t/013_vacuum_cluster_temp_tables.pl
@@ -0,0 +1,65 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+#
+# Verify that no-argument VACUUM FULL, CLUSTER, and REPACK skip temporary
+# tables belonging to other sessions.
+#
+# A background session creates a temp table and marks its index as clustered —
+# making it visible to both the pg_class scan (VACUUM FULL, REPACK) and the
+# pg_index scan (CLUSTER) — then holds ACCESS SHARE LOCK in an open transaction.
+# Each command runs with lock_timeout = '1ms'. Since lock_timeout only
+# fires when a backend actually blocks waiting for a lock, 1ms is sufficient.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('vacuum_cluster_temp');
+$node->init;
+$node->start;
+
+# Session 1: build the temp table and hold a conflicting lock.
+my $psql1 = $node->background_psql('postgres');
+
+$psql1->query_safe(
+ q{CREATE TEMP TABLE temp_repack_test (val int);
+ INSERT INTO temp_repack_test VALUES (1);
+ CREATE INDEX temp_repack_idx ON temp_repack_test (val);
+ CLUSTER temp_repack_test USING temp_repack_idx;});
+
+$psql1->query_safe(q{BEGIN});
+$psql1->query_safe(q{LOCK TABLE temp_repack_test IN ACCESS SHARE MODE});
+
+my ($stdout, $stderr, $ret);
+
+# VACUUM FULL — pg_class scan path.
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; VACUUM FULL;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'VACUUM FULL completes without blocking on another session temp table');
+
+# CLUSTER — pg_index scan path (indisclustered entries).
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; CLUSTER;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'CLUSTER completes without blocking on another session temp table');
+
+# REPACK — pg_class scan path.
+$ret = $node->psql(
+ 'postgres',
+ "SET lock_timeout = '1ms'; REPACK;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($ret, 0,
+ 'REPACK completes without blocking on another session temp table');
+
+$psql1->quit;
+
+done_testing();
--
2.43.0
[text/x-patch] v6-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch (3.7K, ../../[email protected]/3-v6-0001-Skip-other-sessions-temp-tables-in-REPACK-CLUSTER.patch)
download | inline diff:
From a0e7497f095377add06a65b97b79fd0162fabeec Mon Sep 17 00:00:00 2001
From: Jim Jones <[email protected]>
Date: Tue, 7 Apr 2026 00:27:59 +0200
Subject: [PATCH v6 1/2] Skip other sessions' temp tables in REPACK, CLUSTER,
and VACUUM FULL
get_tables_to_repack() was including other sessions' temporary tables
in the work list, causing REPACK and CLUSTER (without arguments) to
attempt to acquire AccessExclusiveLock on them, potentially blocking
for an extended time. Fix by skipping other-session temp tables early
in get_tables_to_repack(), before they are added to the list. Because
an AccessShareLock has already been acquired per relation at that
point, release it before continuing.
Similarly, get_all_vacuum_rels() suffered from the same problem for
VACUUM FULL. Since no per-relation lock is held during list-building
there, a plain skip suffices.
Author: Jim Jones <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
---
src/backend/commands/repack.c | 25 ++++++++++++++++++++++++-
src/backend/commands/vacuum.c | 5 +++++
2 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 9d162957bc3..da4b309c101 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2106,6 +2106,8 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
{
RelToCluster *rtc;
Form_pg_index index;
+ HeapTuple classtup;
+ Form_pg_class classForm;
MemoryContext oldcxt;
index = (Form_pg_index) GETSTRUCT(tuple);
@@ -2120,11 +2122,24 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
/* Verify that the table still exists; skip if not */
- if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(index->indrelid)))
+ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+ if (!HeapTupleIsValid(classtup))
{
UnlockRelationOid(index->indrelid, AccessShareLock);
continue;
}
+ classForm = (Form_pg_class) GETSTRUCT(classtup);
+
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ {
+ ReleaseSysCache(classtup);
+ UnlockRelationOid(index->indrelid, AccessShareLock);
+ continue;
+ }
+
+ ReleaseSysCache(classtup);
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2180,6 +2195,14 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
continue;
}
+ /* Skip temp relations belonging to other sessions */
+ if (class->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(class->relnamespace))
+ {
+ UnlockRelationOid(class->oid, AccessShareLock);
+ continue;
+ }
+
/* noisily skip rels which the user can't process */
if (!repack_is_permitted_for_relation(cmd, class->oid,
GetUserId()))
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 99d0db82ed7..a4abb29cf64 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1063,6 +1063,11 @@ get_all_vacuum_rels(MemoryContext vac_context, int options)
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue;
+ /* Skip temp relations belonging to other sessions */
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
+ !isTempOrTempToastNamespace(classForm->relnamespace))
+ continue;
+
/* check permissions of relation */
if (!vacuum_is_permitted_for_relation(relid, classForm, options))
continue;
--
2.43.0
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-02 14:16 Alexander Korotkov <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Alexander Korotkov @ 2026-05-02 14:16 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Soumya S Murali <[email protected]>; Jim Jones <[email protected]>; Daniil Davydov <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi, Michael!
On Wed, Apr 22, 2026 at 2:41 AM Michael Paquier <[email protected]> wrote:
>
> On Tue, Apr 21, 2026 at 01:54:47PM +0300, Alexander Korotkov wrote:
> > OK. I'm going to push and backpatch if no objections.
>
> Timing is interesting here. Last week I have been doing a on-site
> patch review with a couple of colleagues and myself, and this thread
> has been chosen as part of this exercise. I am attaching them in CC
> of this thread, and replying to this thread was an action item I had
> to act on.
>
> Here is some feedback, based on v18 posted here:
> https://www.postgresql.org/message-id/[email protected]
>
> We have found that the thread does not state clearly what it aims to
> fix. The subject states that it is a bug, perhaps it is but the
> thread does not seem to do a good job in explaining why the current
> superuser behavior is bad, while the behavior of the patch to block
> some commands is better. This should be clearer in explaining why
> this new behavior is better.
>
> The test script is under-documented. There are zero comments that
> actually explain why the patch does what it does. The few comments
> present could be removed: they are copy-pasted of the descriptions of
> the test cases. A much worse thing is this thing:
>
> +# DROP TEMPORARY TABLE from other session
> +$node->safe_psql('postgres', "DROP TABLE $tempschema.foo;");
>
> DROP TABLE on a temporary relation for a superuser is a very useful
> behavior to unstuck autovacuum if a temp relation has been orphaned.
> It looks critical to me to explain that we want to keep this behavior
> for this reason. Using safe_psql() is not really adapted. You should
> use a psql() where we check that the command does not fail, so as the
> test can generate a full report.
>
> The test coverage actually has holes. The three DML patterns INSERT,
> UPDATE and DELETE are covered, and it is missing MERGE. Also, what
> about other patterns like ALTER TABLE, ALTER INDEX, ALTER FUNCTION,
> DROP FUNCTION and the like? There are many object patterns that can
> be schema-qualified, and none of this is covered. What about the new
> REPACK and a bunch of other maintenance commands? There is nothing
> about VACUUM, TRUNCATE, CLUSTER, etc. Just to name a few.
>
> Another question is how do we make sure that new command patterns
> follow the same rule as what is enforced here. It looks like this
> should be at least documented somewhere to be less surprising, but the
> patch does nothing of the kind.
>
> As a whole the patch lacks documentation, comments, and explanations,
> making it difficult to act on.
>
> As a whole, we were not really convinced that this is something that
> needs any kind of specific fix, especially not something that should
> be backpatched.
>
> After saying all that, there is some value in what you are doing here:
> it is true that we lack test coverage in terms of interactions of
> temporary objects across multiple sessions, and that we should have
> some. TAP is adapted for this purpose, isolation tests could be an
> extra one but the schema names make that unpredictible in output. The
> patch unfortunately does a poor job in showing what it wants to
> change. One thing that I would suggest is to *reverse* the order of
> the patches:
> - First have a patch that introduces new tests, that shows the
> original behavior. This needs to be more complete in terms of command
> patterns. The DROP TABLE is one case that we want to keep. This
> should be kept as-is, and it is critical to document the reason why we
> want to keep things this way (aka autovacuum and orphaned tables,
> AFAIK).
> - Then implement the second patch that updates the tests introduced in
> the first patch, so as one can track *what* has changed, and so as one
> does not have to test manually what the original behavior was.
>
> As a whole, this patch needs more work, based on what has been
> currently posted on the lists. That's not ready yet. The backpatch
> question is a separate matter.
Thank you for your feedback. I think the scope of this patch is well
described in [1]. We don't want to restrict the superuser from
something, but our buffer manager just technically can't access the
local buffers of other sessions. Read streams introduced a new code
path for reading relations, which was lacking of the proper check for
local buffers of other sessions. And this patch attempts to fix that.
DROP TABLE is an exclusion. It actually don't need to read contents
of buffers, just drop them. And DropRelationBuffers() have a special
exclusion for this case. So, DROP TABLE appears to be the only
operation that makes sense, it's a conscious exclusion, and there is
no intention to forbid it.
I've revised the patch. 0001 contains tests and states the current
behavior. 0002 contains fix and the corresponding changes in the
tests. I made a change in 0001: removed the check in
ReadBufferExtended(). We added the same check to ReadBuffer_common(),
and I don't think it makes sense to do this check twice in the row.
Links.
1. https://www.postgresql.org/message-id/3529398.1774273446%40sss.pgh.pa.us
------
Regards,
Alexander Korotkov
Supabase
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-02 15:37 Daniil Davydov <[email protected]>
parent: Alexander Korotkov <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Daniil Davydov @ 2026-05-02 15:37 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Jim Jones <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi,
On Sat, May 2, 2026 at 9:16 PM Alexander Korotkov <[email protected]> wrote:
>
> Thank you for your feedback. I think the scope of this patch is well
> described in [1]. We don't want to restrict the superuser from
> something, but our buffer manager just technically can't access the
> local buffers of other sessions. Read streams introduced a new code
> path for reading relations, which was lacking of the proper check for
> local buffers of other sessions. And this patch attempts to fix that.
> DROP TABLE is an exclusion. It actually don't need to read contents
> of buffers, just drop them. And DropRelationBuffers() have a special
> exclusion for this case. So, DROP TABLE appears to be the only
> operation that makes sense, it's a conscious exclusion, and there is
> no intention to forbid it.
Yep, exactly.
> I've revised the patch. 0001 contains tests and states the current
> behavior. 0002 contains fix and the corresponding changes in the
> tests. I made a change in 0001: removed the check in
> ReadBufferExtended(). We added the same check to ReadBuffer_common(),
> and I don't think it makes sense to do this check twice in the row.
Thank you! But I'm afraid that you forgot to attach the patches..
BTW, what do you think about this proposal? :
> On the other hand, we have an error message that says "cannot access...", which
> may look like every kind of "access" is forbidden. I bet that this is the place
> that has confused you. More accurate error message would be "cannot access
> pages..." or "cannot access content...". I think we can change our error
> message in this way. What do you think?
We can easily include it in the first patch.
--
Best regards,
Daniil Davydov
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-02 16:34 Alexander Korotkov <[email protected]>
parent: Daniil Davydov <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Alexander Korotkov @ 2026-05-02 16:34 UTC (permalink / raw)
To: Daniil Davydov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Jim Jones <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
On Sat, May 2, 2026 at 6:37 PM Daniil Davydov <[email protected]> wrote:
> On Sat, May 2, 2026 at 9:16 PM Alexander Korotkov <[email protected]> wrote:
> >
> > Thank you for your feedback. I think the scope of this patch is well
> > described in [1]. We don't want to restrict the superuser from
> > something, but our buffer manager just technically can't access the
> > local buffers of other sessions. Read streams introduced a new code
> > path for reading relations, which was lacking of the proper check for
> > local buffers of other sessions. And this patch attempts to fix that.
> > DROP TABLE is an exclusion. It actually don't need to read contents
> > of buffers, just drop them. And DropRelationBuffers() have a special
> > exclusion for this case. So, DROP TABLE appears to be the only
> > operation that makes sense, it's a conscious exclusion, and there is
> > no intention to forbid it.
>
> Yep, exactly.
>
> > I've revised the patch. 0001 contains tests and states the current
> > behavior. 0002 contains fix and the corresponding changes in the
> > tests. I made a change in 0001: removed the check in
> > ReadBufferExtended(). We added the same check to ReadBuffer_common(),
> > and I don't think it makes sense to do this check twice in the row.
>
> Thank you! But I'm afraid that you forgot to attach the patches..
Here they are.
> BTW, what do you think about this proposal? :
> > On the other hand, we have an error message that says "cannot access...", which
> > may look like every kind of "access" is forbidden. I bet that this is the place
> > that has confused you. More accurate error message would be "cannot access
> > pages..." or "cannot access content...". I think we can change our error
> > message in this way. What do you think?
>
> We can easily include it in the first patch.
This is possible, but I would keep that in a separate patch. We now
have clear scope for both patches: 0001 includes additional tests,
0002 fixes the bug and restores old behavior.
------
Regards,
Alexander Korotkov
Supabase
Attachments:
[application/octet-stream] v20-0002-Prevent-access-to-other-sessions-temp-tables.patch (8.8K, ../../CAPpHfds8pwuwF69JFs0SQz58op-U4ddR3KWv0NVEij2mpkzgpw@mail.gmail.com/2-v20-0002-Prevent-access-to-other-sessions-temp-tables.patch)
download | inline diff:
From 50d2b998cccd5207151fc0aad3a88d9acb19a53b Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 2 May 2026 15:23:42 +0300
Subject: [PATCH v20 2/2] Prevent access to other sessions' temp tables
Commit b7b0f3f2724 ("Use streaming I/O in sequential scans") routed
sequential scans through read_stream_next_buffer(), bypassing the
RELATION_IS_OTHER_TEMP() check in ReadBufferExtended(). As a result,
a superuser can attempt to read or modify temp tables of other
sessions through the read-stream path. When no index is present so
the planner cannot pick an index scan, SELECT/UPDATE/DELETE/MERGE
silently see no rows / report zero affected rows, and COPY produces
an empty output -- because the buffer manager has no visibility into
the owning session's local buffers and silently returns nothing.
INSERT and any path that goes through nbtree (i.e. an index scan)
still error out via the existing check in ReadBufferExtended(), which
is reached from hio.c and nbtree respectively, but this is incidental.
Fix by enforcing RELATION_IS_OTHER_TEMP() at three additional
buffer-manager entry points:
- read_stream_begin_impl() rejects the read at stream setup time,
covering sequential and bitmap scans that go through the
read-stream path.
- ReadBuffer_common() becomes the canonical place for the check,
consolidating the existing one previously kept in
ReadBufferExtended(). All ReadBufferExtended() callers go through
ReadBuffer_common(), so the consolidation is behaviour-preserving.
- StartReadBuffersImpl() catches direct callers of StartReadBuffers()
that bypass both of the above. This is currently defense-in-depth
but documents the contract for future code.
The companion test in src/test/modules/test_misc was added in the
preceding commit; this commit updates the assertions for SELECT,
UPDATE, DELETE, MERGE and COPY (which previously documented the
bug as silent success) to expect the new error.
Author: Jim Jones <[email protected]>
Author: Daniil Davydov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Soumya S Murali <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Discussion: https://postgr.es/m/CAJDiXghdFcZ8%3Dnh4G69te7iRr3Q0uFyXxb3ZdG09_GTNZXwH0g%40mail.gmail.com
Backpatch-through: 17
---
src/backend/storage/aio/read_stream.c | 10 ++++++
src/backend/storage/buffer/bufmgr.c | 33 ++++++++++-------
.../test_misc/t/013_temp_obj_multisession.pl | 35 ++++++++++---------
3 files changed, 50 insertions(+), 28 deletions(-)
diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index 2374b4cd507..a318539e56c 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -776,6 +776,16 @@ read_stream_begin_impl(int flags,
uint32 max_possible_buffer_limit;
Oid tablespace_id;
+ /*
+ * Reject attempts to read non-local temporary relations; we would be
+ * likely to get wrong data since we have no visibility into the owning
+ * session's local buffers.
+ */
+ if (rel && RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
/*
* Decide how many I/Os we will allow to run at the same time. This
* number also affects how far we look ahead for opportunities to start
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 1878efb4aa9..770209d606c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -791,7 +791,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
if (RelationUsesLocalBuffers(reln))
{
- /* see comments in ReadBufferExtended */
+ /* see comments in ReadBuffer_common */
if (RELATION_IS_OTHER_TEMP(reln))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -928,19 +928,10 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
{
Buffer buf;
- /*
- * Reject attempts to read non-local temporary relations; we would be
- * likely to get wrong data since we have no visibility into the owning
- * session's local buffers.
- */
- if (RELATION_IS_OTHER_TEMP(reln))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot access temporary tables of other sessions")));
-
/*
* Read the buffer, and update pgstat counters to reflect a cache hit or
- * miss.
+ * miss. The other-session temp-relation check is enforced by
+ * ReadBuffer_common().
*/
buf = ReadBuffer_common(reln, RelationGetSmgr(reln), 0,
forkNum, blockNum, mode, strategy);
@@ -1292,6 +1283,18 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
int flags;
char persistence;
+ /*
+ * Reject attempts to read non-local temporary relations; we would be
+ * likely to get wrong data since we have no visibility into the owning
+ * session's local buffers. This is the canonical place for the check,
+ * covering the ReadBufferExtended() entry point and any other caller
+ * that supplies a Relation.
+ */
+ if (rel && RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
/*
* Backward compatibility path, most code should use ExtendBufferedRel()
* instead, as acquiring the extension lock inside ExtendBufferedRel()
@@ -1382,6 +1385,12 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
Assert(*nblocks > 0);
Assert(*nblocks <= MAX_IO_COMBINE_LIMIT);
+ /* see comments in ReadBuffer_common */
+ if (operation->rel && RELATION_IS_OTHER_TEMP(operation->rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
if (operation->persistence == RELPERSISTENCE_TEMP)
{
io_context = IOCONTEXT_NORMAL;
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
index 0d211700977..b4442836bef 100644
--- a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -55,25 +55,20 @@ my ($stdout, $stderr);
# DML and SELECT have to read the table's data and therefore go through
# the buffer manager. With no index on the table, the planner cannot
# use index access, so SELECT/UPDATE/DELETE/MERGE/COPY all run through
-# the read-stream path.
-#
-# XXX: in current code, the read-stream path bypasses the
-# RELATION_IS_OTHER_TEMP() check, so these commands silently see no
-# rows / report zero affected rows -- the visible symptom of the bug
-# this test suite documents. A follow-up patch will route the check
-# through read_stream_begin_impl() and these assertions will be
-# updated to expect "cannot access temporary tables of other sessions".
+# the read-stream path and are caught by read_stream_begin_impl().
$node->psql(
'postgres',
"SELECT val FROM $tempschema.foo;",
- stdout => \$stdout,
stderr => \$stderr);
-is($stderr, '', 'SELECT (currently no error -- bug to be fixed)');
+like(
+ $stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'SELECT (seqscan via read_stream)');
# INSERT goes through hio.c which calls ReadBufferExtended() to find a
-# page with free space; that hits the existing check before any data is
-# written. This case currently errors as expected.
+# page with free space; that hits the existing check before any data
+# is written.
$node->psql(
'postgres',
"INSERT INTO $tempschema.foo VALUES (73);",
@@ -86,21 +81,29 @@ $node->psql(
'postgres',
"UPDATE $tempschema.foo SET val = NULL;",
stderr => \$stderr);
-is($stderr, '', 'UPDATE (currently no error -- bug to be fixed)');
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'UPDATE');
$node->psql('postgres', "DELETE FROM $tempschema.foo;", stderr => \$stderr);
-is($stderr, '', 'DELETE (currently no error -- bug to be fixed)');
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'DELETE');
$node->psql(
'postgres',
"MERGE INTO $tempschema.foo USING (VALUES (42)) AS s(val) "
. "ON foo.val = s.val WHEN MATCHED THEN DELETE;",
stderr => \$stderr);
-is($stderr, '', 'MERGE (currently no error -- bug to be fixed)');
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'MERGE');
$node->psql('postgres', "COPY $tempschema.foo TO STDOUT;",
stderr => \$stderr);
-is($stderr, '', 'COPY (currently no error -- bug to be fixed)');
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'COPY');
# DDL and maintenance commands have their own command-specific checks
# (older than the buffer-manager check above), so they fail with
--
2.39.5 (Apple Git-154)
[application/octet-stream] v20-0001-Add-tests-for-cross-session-temp-table-access.patch (13.7K, ../../CAPpHfds8pwuwF69JFs0SQz58op-U4ddR3KWv0NVEij2mpkzgpw@mail.gmail.com/3-v20-0001-Add-tests-for-cross-session-temp-table-access.patch)
download | inline diff:
From de67da121b45e603c81739e273d7cb8cfc059fb1 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 2 May 2026 15:22:26 +0300
Subject: [PATCH v20 1/2] Add tests for cross-session temp table access
Add a TAP test in src/test/modules/test_misc that documents what
happens when one session attempts to read or modify another session's
temporary table. This commit only adds tests; it does not change
backend behaviour, so the assertions reflect current behaviour:
- SELECT, UPDATE, DELETE, MERGE, COPY on a table without an index
silently succeed with no error and zero rows / zero affected rows.
These commands run through the read-stream path, which currently
bypasses the RELATION_IS_OTHER_TEMP() check. This is the
underlying bug to be fixed in a follow-up.
- INSERT errors with "cannot access temporary tables of other
sessions" because hio.c calls ReadBufferExtended() to find a page
with free space and is caught by the existing check there.
- Index scan errors via the same existing check, reached through
nbtree -> ReadBuffer -> ReadBufferExtended.
- TRUNCATE / ALTER TABLE / ALTER INDEX / CLUSTER fail with their
command-specific error messages.
- VACUUM is silently skipped to avoid noise during database-wide
VACUUM (vacuum_rel() returns without warning).
- DROP TABLE is intentionally allowed: DROP does not touch the
table's contents, and autovacuum relies on this to clean up
temp relations orphaned by a crashed backend.
- ALTER FUNCTION / DROP FUNCTION on an owner-created function over
its own temp row type work as catalog operations -- they don't
read the underlying data.
- CREATE FUNCTION from a separate session, using another session's
temp row type as an argument, is allowed but emits a NOTICE: the
function is moved into the creator's pg_temp namespace with an
auto-dependency on the borrowed type, so it disappears together
with the session that created it.
- A bare DROP TABLE on a temp table that has a cross-session
dependent function fails with a catalog-level dependency error.
- When the owner session ends, the normal session-exit cleanup
cascades through DEPENDENCY_NORMAL and removes both the temp
objects and any cross-session functions that depended on them.
Also document the contract for RELATION_IS_OTHER_TEMP() so that
future buffer-access entry points enforce the same rule.
Author: Jim Jones <[email protected]>
Author: Daniil Davydov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Soumya S Murali <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Discussion: https://postgr.es/m/CAJDiXghdFcZ8%3Dnh4G69te7iRr3Q0uFyXxb3ZdG09_GTNZXwH0g%40mail.gmail.com
---
src/include/utils/rel.h | 9 +
src/test/modules/test_misc/meson.build | 1 +
.../test_misc/t/013_temp_obj_multisession.pl | 235 ++++++++++++++++++
3 files changed, 245 insertions(+)
create mode 100644 src/test/modules/test_misc/t/013_temp_obj_multisession.pl
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index cd1e92f2302..ad50e43b801 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -664,6 +664,15 @@ RelationCloseSmgr(Relation relation)
* RELATION_IS_OTHER_TEMP
* Test for a temporary relation that belongs to some other session.
*
+ * Any code path that reads a relation's data must reject such relations:
+ * the owning session keeps the data in its private local buffer pool,
+ * which we cannot inspect. Existing buffer-manager entry points
+ * (ReadBufferExtended(), ReadBuffer_common(), StartReadBuffersImpl(),
+ * read_stream_begin_impl(), PrefetchBuffer()) already enforce this; any
+ * new buffer-access entry point must do the same. Command-level code
+ * (TRUNCATE, ALTER TABLE, VACUUM, CLUSTER, REINDEX, ...) additionally
+ * uses this macro for command-specific error messages.
+ *
* Beware of multiple eval of argument
*/
#define RELATION_IS_OTHER_TEMP(relation) \
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 356d8454b39..969e90b396d 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -21,6 +21,7 @@ tests += {
't/010_index_concurrently_upsert.pl',
't/011_lock_stats.pl',
't/012_ddlutils.pl',
+ 't/013_temp_obj_multisession.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
new file mode 100644
index 00000000000..0d211700977
--- /dev/null
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -0,0 +1,235 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Tests that one session cannot read or modify data in another session's
+# temporary table. Each session keeps its temp data in its own local
+# buffer pool, and a different backend has no visibility into those
+# buffers, so any command that needs to look at the data must be
+# rejected.
+#
+# DROP TABLE is intentionally allowed: it does not touch the table's
+# contents, and autovacuum relies on this to clean up orphaned temp
+# relations left behind by a crashed backend.
+#
+# A regression caught here typically means a new buffer-access entry
+# point bypasses the RELATION_IS_OTHER_TEMP() check. See
+# ReadBuffer_common(), StartReadBuffersImpl(), and read_stream_begin_impl()
+# for the existing checks. When adding a new command or buffer-access
+# path, also add a corresponding case below.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::BackgroundPsql;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('temp_lock');
+$node->init;
+$node->start;
+
+# Owner session. Created via background_psql so it stays alive while
+# the second session probes its temp objects.
+my $psql1 = $node->background_psql('postgres');
+
+# Initially create the table without an index, so read paths go straight
+# through the read-stream / buffer-manager entry points without being
+# masked by an index scan that would hit ReadBuffer_common from nbtree.
+$psql1->query_safe(q(CREATE TEMP TABLE foo AS SELECT 42 AS val;));
+
+# Resolve the owner's temp schema so the probing session can refer to
+# the table by a fully-qualified name.
+my $tempschema = $node->safe_psql(
+ 'postgres',
+ q{
+ SELECT n.nspname
+ FROM pg_class c
+ JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE relname = 'foo' AND relpersistence = 't';
+ }
+);
+chomp $tempschema;
+ok($tempschema =~ /^pg_temp_\d+$/, "got temp schema: $tempschema");
+
+my ($stdout, $stderr);
+
+# DML and SELECT have to read the table's data and therefore go through
+# the buffer manager. With no index on the table, the planner cannot
+# use index access, so SELECT/UPDATE/DELETE/MERGE/COPY all run through
+# the read-stream path.
+#
+# XXX: in current code, the read-stream path bypasses the
+# RELATION_IS_OTHER_TEMP() check, so these commands silently see no
+# rows / report zero affected rows -- the visible symptom of the bug
+# this test suite documents. A follow-up patch will route the check
+# through read_stream_begin_impl() and these assertions will be
+# updated to expect "cannot access temporary tables of other sessions".
+
+$node->psql(
+ 'postgres',
+ "SELECT val FROM $tempschema.foo;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($stderr, '', 'SELECT (currently no error -- bug to be fixed)');
+
+# INSERT goes through hio.c which calls ReadBufferExtended() to find a
+# page with free space; that hits the existing check before any data is
+# written. This case currently errors as expected.
+$node->psql(
+ 'postgres',
+ "INSERT INTO $tempschema.foo VALUES (73);",
+ stderr => \$stderr);
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'INSERT (caught via hio.c)');
+
+$node->psql(
+ 'postgres',
+ "UPDATE $tempschema.foo SET val = NULL;",
+ stderr => \$stderr);
+is($stderr, '', 'UPDATE (currently no error -- bug to be fixed)');
+
+$node->psql('postgres', "DELETE FROM $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'DELETE (currently no error -- bug to be fixed)');
+
+$node->psql(
+ 'postgres',
+ "MERGE INTO $tempschema.foo USING (VALUES (42)) AS s(val) "
+ . "ON foo.val = s.val WHEN MATCHED THEN DELETE;",
+ stderr => \$stderr);
+is($stderr, '', 'MERGE (currently no error -- bug to be fixed)');
+
+$node->psql('postgres', "COPY $tempschema.foo TO STDOUT;",
+ stderr => \$stderr);
+is($stderr, '', 'COPY (currently no error -- bug to be fixed)');
+
+# DDL and maintenance commands have their own command-specific checks
+# (older than the buffer-manager check above), so they fail with
+# command-specific error messages. Verifying them here documents the
+# expected behaviour and guards against accidental removal of those
+# checks.
+
+$node->psql('postgres', "TRUNCATE TABLE $tempschema.foo;",
+ stderr => \$stderr);
+like($stderr,
+ qr/cannot truncate temporary tables of other sessions/,
+ 'TRUNCATE');
+
+$node->psql(
+ 'postgres',
+ "ALTER TABLE $tempschema.foo ALTER COLUMN val TYPE bigint;",
+ stderr => \$stderr);
+like($stderr,
+ qr/cannot alter temporary tables of other sessions/,
+ 'ALTER TABLE');
+
+# VACUUM silently skips other sessions' temp tables (vacuum_rel() returns
+# without warning to avoid noise during database-wide VACUUM). Verify
+# that no error is reported, and that no buffer-access path is hit.
+$node->psql('postgres', "VACUUM $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'VACUUM is silently skipped');
+
+$node->psql('postgres', "CLUSTER $tempschema.foo;", stderr => \$stderr);
+like($stderr,
+ qr/cannot execute CLUSTER on temporary tables of other sessions/,
+ 'CLUSTER');
+
+# Now create an index to exercise the index-scan path. nbtree calls
+# ReadBuffer (which is ReadBufferExtended -> ReadBuffer_common), so
+# this exercises a different chain of buffer-manager entry points.
+$psql1->query_safe(q(CREATE INDEX ON foo(val);));
+
+$node->psql(
+ 'postgres',
+ "SET enable_seqscan = off; SELECT val FROM $tempschema.foo WHERE val = 42;",
+ stderr => \$stderr);
+like(
+ $stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'index scan (ReadBuffer_common via nbtree)');
+
+# ALTER INDEX goes through the same CheckAlterTableIsSafe() path as
+# ALTER TABLE, so it produces the same error.
+$node->psql(
+ 'postgres',
+ "ALTER INDEX $tempschema.foo_val_idx SET (fillfactor = 50);",
+ stderr => \$stderr);
+like($stderr,
+ qr/cannot alter temporary tables of other sessions/,
+ 'ALTER INDEX');
+
+# A function created by the owner in its own pg_temp using its own
+# row type can be observed via the catalog by a separate session.
+# ALTER FUNCTION and DROP FUNCTION on it must work as catalog
+# operations -- they don't read the underlying table -- which
+# documents the boundary between catalog and data access for temp
+# objects.
+$psql1->query_safe(
+ q[CREATE FUNCTION pg_temp.foo_id(r foo) RETURNS int LANGUAGE SQL ]
+ . q[AS 'SELECT r.val';]);
+
+$node->psql(
+ 'postgres',
+ "ALTER FUNCTION $tempschema.foo_id($tempschema.foo) "
+ . "SET search_path = pg_catalog;",
+ stderr => \$stderr);
+is($stderr, '', 'ALTER FUNCTION on function over other session\'s row type');
+
+$node->psql(
+ 'postgres',
+ "DROP FUNCTION $tempschema.foo_id($tempschema.foo);",
+ stderr => \$stderr);
+is($stderr, '', 'DROP FUNCTION on function over other session\'s row type');
+
+# DROP TABLE on another session's temp table is intentionally permitted.
+# DROP doesn't touch the table's contents, and autovacuum relies on this
+# to remove temp relations orphaned by a crashed backend. Verify that
+# the bare DROP succeeds without error.
+$node->psql('postgres', "DROP TABLE $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'DROP TABLE is allowed');
+
+# Cross-session CREATE FUNCTION scenario. The owner creates a fresh
+# temp table foo2 in its pg_temp namespace, and a separate session
+# then creates a function whose argument type is that row type.
+# PostgreSQL allows this and emits a NOTICE: the function is moved
+# into the creator's pg_temp namespace with an auto-dependency on
+# the borrowed type, so it disappears together with the session that
+# created it.
+$psql1->query_safe(q(CREATE TEMP TABLE foo2 AS SELECT 42 AS val;));
+
+$node->psql(
+ 'postgres',
+ "CREATE FUNCTION public.cross_session_func(r $tempschema.foo2) "
+ . "RETURNS int LANGUAGE SQL AS 'SELECT 1';",
+ stderr => \$stderr);
+like(
+ $stderr,
+ qr/function "cross_session_func" will be effectively temporary/,
+ 'CREATE FUNCTION using other session\'s row type is effectively temporary'
+);
+
+# A bare DROP TABLE on foo2 now fails because cross_session_func
+# depends on its row type. This is normal SQL dependency behaviour
+# and documents that DROP itself is not blocked by buffer-manager
+# checks -- we get a catalog-level error instead.
+$node->psql('postgres', "DROP TABLE $tempschema.foo2;", stderr => \$stderr);
+like(
+ $stderr,
+ qr/cannot drop table .*\.foo2 because other objects depend on it/,
+ 'DROP TABLE blocked by cross-session dependency');
+
+# When the owner session ends, its temp objects are dropped via the
+# normal session-exit cleanup, which cascades through
+# DEPENDENCY_NORMAL and also removes the cross-session function that
+# depended on the temp row type. This is the same mechanism
+# autovacuum relies on to clean up temp relations left behind by a
+# crashed backend.
+$psql1->quit;
+
+$node->poll_query_until(
+ 'postgres',
+ "SELECT NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'cross_session_func')"
+) or die "cross_session_func was not cleaned up after owner session exit";
+
+ok(1, 'cross_session_func cleaned up when owner session ends');
+
+done_testing();
--
2.39.5 (Apple Git-154)
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-02 17:32 Jim Jones <[email protected]>
parent: Alexander Korotkov <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Jim Jones @ 2026-05-02 17:32 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; Daniil Davydov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi
On 02/05/2026 18:34, Alexander Korotkov wrote:
> On Sat, May 2, 2026 at 6:37 PM Daniil Davydov <[email protected]> wrote:
>> On Sat, May 2, 2026 at 9:16 PM Alexander Korotkov <[email protected]> wrote:
>>>
>>> Thank you for your feedback. I think the scope of this patch is well
>>> described in [1]. We don't want to restrict the superuser from
>>> something, but our buffer manager just technically can't access the
>>> local buffers of other sessions. Read streams introduced a new code
>>> path for reading relations, which was lacking of the proper check for
>>> local buffers of other sessions. And this patch attempts to fix that.
>>> DROP TABLE is an exclusion. It actually don't need to read contents
>>> of buffers, just drop them. And DropRelationBuffers() have a special
>>> exclusion for this case. So, DROP TABLE appears to be the only
>>> operation that makes sense, it's a conscious exclusion, and there is
>>> no intention to forbid it.
>>
>> Yep, exactly.
+1
>>> I've revised the patch. 0001 contains tests and states the current
>>> behavior. 0002 contains fix and the corresponding changes in the
>>> tests. I made a change in 0001: removed the check in
>>> ReadBufferExtended(). We added the same check to ReadBuffer_common(),
>>> and I don't think it makes sense to do this check twice in the row.
>>
>> Thank you! But I'm afraid that you forgot to attach the patches..
>
> Here they are.
Thanks for the comprehensive additional tests!
In addition to the DROP TABLE exception:
It is also possible to LOCK temporary tables from other sessions:
postgres=# BEGIN;
BEGIN
postgres=*# LOCK TABLE pg_temp_91.t IN ACCESS SHARE MODE ;
LOCK TABLE
pg_temp_91.t lives as long the transaction is open -- even after the
origin session closes, which is totally expected. I'd say it falls into
the same category of DROP TABLE, where the table contents are never
read, so I'd argue it's ok.
>
>> BTW, what do you think about this proposal? :
>>> On the other hand, we have an error message that says "cannot access...", which
>>> may look like every kind of "access" is forbidden. I bet that this is the place
>>> that has confused you. More accurate error message would be "cannot access
>>> pages..." or "cannot access content...". I think we can change our error
>>> message in this way. What do you think?
>>
>> We can easily include it in the first patch.
>
> This is possible, but I would keep that in a separate patch. We now
> have clear scope for both patches: 0001 includes additional tests,
> 0002 fixes the bug and restores old behavior.
+1 for a separate patch. I think the scope of the current patch is good
as-is.
Thanks!
Best, Jim
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-03 08:53 Daniil Davydov <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Daniil Davydov @ 2026-05-03 08:53 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi,
On Sun, May 3, 2026 at 12:32 AM Jim Jones <[email protected]> wrote:
>
> In addition to the DROP TABLE exception:
>
> It is also possible to LOCK temporary tables from other sessions:
>
> postgres=# BEGIN;
> BEGIN
> postgres=*# LOCK TABLE pg_temp_91.t IN ACCESS SHARE MODE ;
> LOCK TABLE
>
> pg_temp_91.t lives as long the transaction is open -- even after the
> origin session closes, which is totally expected. I'd say it falls into
> the same category of DROP TABLE, where the table contents are never
> read, so I'd argue it's ok.
Autovacuum locks orphaned table's oid before deleting it. LOCK TABLE command
also locks the oid of the specified table. If we want to make sure that
autovacuum can acquire the mentioned lock (i.e. cover this behavior using
tests), I suggest testing it using the LOCK TABLE command.
Please, see the attached patch that ensures that cross-session LOCK TABLE works
properly.
--
Best regards,
Daniil Davydov
Attachments:
[text/x-patch] 0001-Test-cross-session-LOCK-TABLE-scenario.patch (2.1K, ../../CAJDiXgi_SW3DVvXb4+n04NA1YfeeXmT3gUnSG7h4JwBd+RdJGg@mail.gmail.com/2-0001-Test-cross-session-LOCK-TABLE-scenario.patch)
download | inline diff:
From 05aaab6a225195246e70ad465a0995c4c0437e23 Mon Sep 17 00:00:00 2001
From: Daniil Davidov <[email protected]>
Date: Sun, 3 May 2026 15:51:06 +0700
Subject: [PATCH] Test cross-session LOCK TABLE scenario
---
.../test_misc/t/013_temp_obj_multisession.pl | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
index b4442836bef..a7369700580 100644
--- a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -220,14 +220,39 @@ like(
qr/cannot drop table .*\.foo2 because other objects depend on it/,
'DROP TABLE blocked by cross-session dependency');
+my $foo2_oid = $node->safe_psql('postgres',
+ "SELECT oid FROM pg_class WHERE relname='foo2';");
+
+# Cross-session LOCK TABLE scenario. Ensure that LockRelationOid is working
+# properly for other temp tables since this mechanism is also used by
+# autovacuum during orphaned tables cleanup.
+my $psql2 = $node->background_psql('postgres');
+$psql2->query_safe(
+ qq{
+ BEGIN;
+ LOCK TABLE $tempschema.foo2 IN ACCESS SHARE MODE;
+});
+
# When the owner session ends, its temp objects are dropped via the
# normal session-exit cleanup, which cascades through
# DEPENDENCY_NORMAL and also removes the cross-session function that
# depended on the temp row type. This is the same mechanism
# autovacuum relies on to clean up temp relations left behind by a
# crashed backend.
+# Access share lock on the foo2 will block session-exit cleanup, because an
+# owner will try to acquire deletion lock all its temp objects via
+# findDependentObjects.
+my $log_offset = -s $node->logfile;
$psql1->quit;
+# Check whether session-exit cleanup is blocked.
+$node->wait_for_log(qr/waiting for AccessExclusiveLock on relation $foo2_oid/,
+ $log_offset);
+
+# Release lock on foo2 and allow session-exit cleanup to finish.
+$psql2->query_safe(q(COMMIT;));
+$psql2->quit;
+
$node->poll_query_until(
'postgres',
"SELECT NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'cross_session_func')"
--
2.43.0
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-03 12:49 Jim Jones <[email protected]>
parent: Daniil Davydov <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Jim Jones @ 2026-05-03 12:49 UTC (permalink / raw)
To: Daniil Davydov <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi Daniil
On 03/05/2026 10:53, Daniil Davydov wrote:
> Autovacuum locks orphaned table's oid before deleting it. LOCK TABLE command
> also locks the oid of the specified table. If we want to make sure that
> autovacuum can acquire the mentioned lock (i.e. cover this behavior using
> tests), I suggest testing it using the LOCK TABLE command.
+1 for the extra test.
> Please, see the attached patch that ensures that cross-session LOCK TABLE works
> properly.
I guess you should either add it to the 0001 sent by Alexander, or
create a 0003. Right now the cfbot is complaining, as
013_temp_obj_multisession.pl still does not exist :)
Best, Jim
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-04 09:31 Daniil Davydov <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Daniil Davydov @ 2026-05-04 09:31 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi,
On Sun, May 3, 2026 at 7:49 PM Jim Jones <[email protected]> wrote:
>
> On 03/05/2026 10:53, Daniil Davydov wrote:
> > Please, see the attached patch that ensures that cross-session LOCK TABLE works
> > properly.
>
> I guess you should either add it to the 0001 sent by Alexander, or
> create a 0003. Right now the cfbot is complaining, as
> 013_temp_obj_multisession.pl still does not exist :)
>
OK, I'll attach this patch as a third one, just for review purposes. After
agreement on its content, it should be included into the 0001 and 0002 patches.
--
Best regards,
Daniil Davydov
Attachments:
[text/x-patch] v21-0002-Prevent-access-to-other-sessions-temp-tables.patch (8.8K, ../../CAJDiXgiyy+GG6a5=nDPQeOQTjdQo53+6N0rAzZ=M3baWGPJNsA@mail.gmail.com/2-v21-0002-Prevent-access-to-other-sessions-temp-tables.patch)
download | inline diff:
From 2ec45b8ec3a0eb56ed1f2d10a0cb89ef57a8f402 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 2 May 2026 15:23:42 +0300
Subject: [PATCH v21 2/3] Prevent access to other sessions' temp tables
Commit b7b0f3f2724 ("Use streaming I/O in sequential scans") routed
sequential scans through read_stream_next_buffer(), bypassing the
RELATION_IS_OTHER_TEMP() check in ReadBufferExtended(). As a result,
a superuser can attempt to read or modify temp tables of other
sessions through the read-stream path. When no index is present so
the planner cannot pick an index scan, SELECT/UPDATE/DELETE/MERGE
silently see no rows / report zero affected rows, and COPY produces
an empty output -- because the buffer manager has no visibility into
the owning session's local buffers and silently returns nothing.
INSERT and any path that goes through nbtree (i.e. an index scan)
still error out via the existing check in ReadBufferExtended(), which
is reached from hio.c and nbtree respectively, but this is incidental.
Fix by enforcing RELATION_IS_OTHER_TEMP() at three additional
buffer-manager entry points:
- read_stream_begin_impl() rejects the read at stream setup time,
covering sequential and bitmap scans that go through the
read-stream path.
- ReadBuffer_common() becomes the canonical place for the check,
consolidating the existing one previously kept in
ReadBufferExtended(). All ReadBufferExtended() callers go through
ReadBuffer_common(), so the consolidation is behaviour-preserving.
- StartReadBuffersImpl() catches direct callers of StartReadBuffers()
that bypass both of the above. This is currently defense-in-depth
but documents the contract for future code.
The companion test in src/test/modules/test_misc was added in the
preceding commit; this commit updates the assertions for SELECT,
UPDATE, DELETE, MERGE and COPY (which previously documented the
bug as silent success) to expect the new error.
Author: Jim Jones <[email protected]>
Author: Daniil Davydov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Soumya S Murali <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Discussion: https://postgr.es/m/CAJDiXghdFcZ8%3Dnh4G69te7iRr3Q0uFyXxb3ZdG09_GTNZXwH0g%40mail.gmail.com
Backpatch-through: 17
---
src/backend/storage/aio/read_stream.c | 10 ++++++
src/backend/storage/buffer/bufmgr.c | 33 ++++++++++-------
.../test_misc/t/013_temp_obj_multisession.pl | 35 ++++++++++---------
3 files changed, 50 insertions(+), 28 deletions(-)
diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index 2374b4cd507..a318539e56c 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -776,6 +776,16 @@ read_stream_begin_impl(int flags,
uint32 max_possible_buffer_limit;
Oid tablespace_id;
+ /*
+ * Reject attempts to read non-local temporary relations; we would be
+ * likely to get wrong data since we have no visibility into the owning
+ * session's local buffers.
+ */
+ if (rel && RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
/*
* Decide how many I/Os we will allow to run at the same time. This
* number also affects how far we look ahead for opportunities to start
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 1878efb4aa9..770209d606c 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -791,7 +791,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
if (RelationUsesLocalBuffers(reln))
{
- /* see comments in ReadBufferExtended */
+ /* see comments in ReadBuffer_common */
if (RELATION_IS_OTHER_TEMP(reln))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -928,19 +928,10 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
{
Buffer buf;
- /*
- * Reject attempts to read non-local temporary relations; we would be
- * likely to get wrong data since we have no visibility into the owning
- * session's local buffers.
- */
- if (RELATION_IS_OTHER_TEMP(reln))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot access temporary tables of other sessions")));
-
/*
* Read the buffer, and update pgstat counters to reflect a cache hit or
- * miss.
+ * miss. The other-session temp-relation check is enforced by
+ * ReadBuffer_common().
*/
buf = ReadBuffer_common(reln, RelationGetSmgr(reln), 0,
forkNum, blockNum, mode, strategy);
@@ -1292,6 +1283,18 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
int flags;
char persistence;
+ /*
+ * Reject attempts to read non-local temporary relations; we would be
+ * likely to get wrong data since we have no visibility into the owning
+ * session's local buffers. This is the canonical place for the check,
+ * covering the ReadBufferExtended() entry point and any other caller
+ * that supplies a Relation.
+ */
+ if (rel && RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
/*
* Backward compatibility path, most code should use ExtendBufferedRel()
* instead, as acquiring the extension lock inside ExtendBufferedRel()
@@ -1382,6 +1385,12 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
Assert(*nblocks > 0);
Assert(*nblocks <= MAX_IO_COMBINE_LIMIT);
+ /* see comments in ReadBuffer_common */
+ if (operation->rel && RELATION_IS_OTHER_TEMP(operation->rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
if (operation->persistence == RELPERSISTENCE_TEMP)
{
io_context = IOCONTEXT_NORMAL;
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
index 0d211700977..b4442836bef 100644
--- a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -55,25 +55,20 @@ my ($stdout, $stderr);
# DML and SELECT have to read the table's data and therefore go through
# the buffer manager. With no index on the table, the planner cannot
# use index access, so SELECT/UPDATE/DELETE/MERGE/COPY all run through
-# the read-stream path.
-#
-# XXX: in current code, the read-stream path bypasses the
-# RELATION_IS_OTHER_TEMP() check, so these commands silently see no
-# rows / report zero affected rows -- the visible symptom of the bug
-# this test suite documents. A follow-up patch will route the check
-# through read_stream_begin_impl() and these assertions will be
-# updated to expect "cannot access temporary tables of other sessions".
+# the read-stream path and are caught by read_stream_begin_impl().
$node->psql(
'postgres',
"SELECT val FROM $tempschema.foo;",
- stdout => \$stdout,
stderr => \$stderr);
-is($stderr, '', 'SELECT (currently no error -- bug to be fixed)');
+like(
+ $stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'SELECT (seqscan via read_stream)');
# INSERT goes through hio.c which calls ReadBufferExtended() to find a
-# page with free space; that hits the existing check before any data is
-# written. This case currently errors as expected.
+# page with free space; that hits the existing check before any data
+# is written.
$node->psql(
'postgres',
"INSERT INTO $tempschema.foo VALUES (73);",
@@ -86,21 +81,29 @@ $node->psql(
'postgres',
"UPDATE $tempschema.foo SET val = NULL;",
stderr => \$stderr);
-is($stderr, '', 'UPDATE (currently no error -- bug to be fixed)');
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'UPDATE');
$node->psql('postgres', "DELETE FROM $tempschema.foo;", stderr => \$stderr);
-is($stderr, '', 'DELETE (currently no error -- bug to be fixed)');
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'DELETE');
$node->psql(
'postgres',
"MERGE INTO $tempschema.foo USING (VALUES (42)) AS s(val) "
. "ON foo.val = s.val WHEN MATCHED THEN DELETE;",
stderr => \$stderr);
-is($stderr, '', 'MERGE (currently no error -- bug to be fixed)');
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'MERGE');
$node->psql('postgres', "COPY $tempschema.foo TO STDOUT;",
stderr => \$stderr);
-is($stderr, '', 'COPY (currently no error -- bug to be fixed)');
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'COPY');
# DDL and maintenance commands have their own command-specific checks
# (older than the buffer-manager check above), so they fail with
--
2.43.0
[text/x-patch] v21-0003-Test-cross-session-LOCK-TABLE-scenario.patch (2.1K, ../../CAJDiXgiyy+GG6a5=nDPQeOQTjdQo53+6N0rAzZ=M3baWGPJNsA@mail.gmail.com/3-v21-0003-Test-cross-session-LOCK-TABLE-scenario.patch)
download | inline diff:
From 05aaab6a225195246e70ad465a0995c4c0437e23 Mon Sep 17 00:00:00 2001
From: Daniil Davidov <[email protected]>
Date: Sun, 3 May 2026 15:51:06 +0700
Subject: [PATCH v21 3/3] Test cross-session LOCK TABLE scenario
---
.../test_misc/t/013_temp_obj_multisession.pl | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
index b4442836bef..a7369700580 100644
--- a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -220,14 +220,39 @@ like(
qr/cannot drop table .*\.foo2 because other objects depend on it/,
'DROP TABLE blocked by cross-session dependency');
+my $foo2_oid = $node->safe_psql('postgres',
+ "SELECT oid FROM pg_class WHERE relname='foo2';");
+
+# Cross-session LOCK TABLE scenario. Ensure that LockRelationOid is working
+# properly for other temp tables since this mechanism is also used by
+# autovacuum during orphaned tables cleanup.
+my $psql2 = $node->background_psql('postgres');
+$psql2->query_safe(
+ qq{
+ BEGIN;
+ LOCK TABLE $tempschema.foo2 IN ACCESS SHARE MODE;
+});
+
# When the owner session ends, its temp objects are dropped via the
# normal session-exit cleanup, which cascades through
# DEPENDENCY_NORMAL and also removes the cross-session function that
# depended on the temp row type. This is the same mechanism
# autovacuum relies on to clean up temp relations left behind by a
# crashed backend.
+# Access share lock on the foo2 will block session-exit cleanup, because an
+# owner will try to acquire deletion lock all its temp objects via
+# findDependentObjects.
+my $log_offset = -s $node->logfile;
$psql1->quit;
+# Check whether session-exit cleanup is blocked.
+$node->wait_for_log(qr/waiting for AccessExclusiveLock on relation $foo2_oid/,
+ $log_offset);
+
+# Release lock on foo2 and allow session-exit cleanup to finish.
+$psql2->query_safe(q(COMMIT;));
+$psql2->quit;
+
$node->poll_query_until(
'postgres',
"SELECT NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'cross_session_func')"
--
2.43.0
[text/x-patch] v21-0001-Add-tests-for-cross-session-temp-table-access.patch (13.7K, ../../CAJDiXgiyy+GG6a5=nDPQeOQTjdQo53+6N0rAzZ=M3baWGPJNsA@mail.gmail.com/4-v21-0001-Add-tests-for-cross-session-temp-table-access.patch)
download | inline diff:
From e3739ee30d6dea0630d56a45e109b0d762cda333 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 2 May 2026 15:22:26 +0300
Subject: [PATCH v21 1/3] Add tests for cross-session temp table access
Add a TAP test in src/test/modules/test_misc that documents what
happens when one session attempts to read or modify another session's
temporary table. This commit only adds tests; it does not change
backend behaviour, so the assertions reflect current behaviour:
- SELECT, UPDATE, DELETE, MERGE, COPY on a table without an index
silently succeed with no error and zero rows / zero affected rows.
These commands run through the read-stream path, which currently
bypasses the RELATION_IS_OTHER_TEMP() check. This is the
underlying bug to be fixed in a follow-up.
- INSERT errors with "cannot access temporary tables of other
sessions" because hio.c calls ReadBufferExtended() to find a page
with free space and is caught by the existing check there.
- Index scan errors via the same existing check, reached through
nbtree -> ReadBuffer -> ReadBufferExtended.
- TRUNCATE / ALTER TABLE / ALTER INDEX / CLUSTER fail with their
command-specific error messages.
- VACUUM is silently skipped to avoid noise during database-wide
VACUUM (vacuum_rel() returns without warning).
- DROP TABLE is intentionally allowed: DROP does not touch the
table's contents, and autovacuum relies on this to clean up
temp relations orphaned by a crashed backend.
- ALTER FUNCTION / DROP FUNCTION on an owner-created function over
its own temp row type work as catalog operations -- they don't
read the underlying data.
- CREATE FUNCTION from a separate session, using another session's
temp row type as an argument, is allowed but emits a NOTICE: the
function is moved into the creator's pg_temp namespace with an
auto-dependency on the borrowed type, so it disappears together
with the session that created it.
- A bare DROP TABLE on a temp table that has a cross-session
dependent function fails with a catalog-level dependency error.
- When the owner session ends, the normal session-exit cleanup
cascades through DEPENDENCY_NORMAL and removes both the temp
objects and any cross-session functions that depended on them.
Also document the contract for RELATION_IS_OTHER_TEMP() so that
future buffer-access entry points enforce the same rule.
Author: Jim Jones <[email protected]>
Author: Daniil Davydov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Soumya S Murali <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Reviewed-by: Alexander Korotkov <[email protected]>
Discussion: https://postgr.es/m/CAJDiXghdFcZ8%3Dnh4G69te7iRr3Q0uFyXxb3ZdG09_GTNZXwH0g%40mail.gmail.com
---
src/include/utils/rel.h | 9 +
src/test/modules/test_misc/meson.build | 1 +
.../test_misc/t/013_temp_obj_multisession.pl | 235 ++++++++++++++++++
3 files changed, 245 insertions(+)
create mode 100644 src/test/modules/test_misc/t/013_temp_obj_multisession.pl
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index cd1e92f2302..ad50e43b801 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -664,6 +664,15 @@ RelationCloseSmgr(Relation relation)
* RELATION_IS_OTHER_TEMP
* Test for a temporary relation that belongs to some other session.
*
+ * Any code path that reads a relation's data must reject such relations:
+ * the owning session keeps the data in its private local buffer pool,
+ * which we cannot inspect. Existing buffer-manager entry points
+ * (ReadBufferExtended(), ReadBuffer_common(), StartReadBuffersImpl(),
+ * read_stream_begin_impl(), PrefetchBuffer()) already enforce this; any
+ * new buffer-access entry point must do the same. Command-level code
+ * (TRUNCATE, ALTER TABLE, VACUUM, CLUSTER, REINDEX, ...) additionally
+ * uses this macro for command-specific error messages.
+ *
* Beware of multiple eval of argument
*/
#define RELATION_IS_OTHER_TEMP(relation) \
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 356d8454b39..969e90b396d 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -21,6 +21,7 @@ tests += {
't/010_index_concurrently_upsert.pl',
't/011_lock_stats.pl',
't/012_ddlutils.pl',
+ 't/013_temp_obj_multisession.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
new file mode 100644
index 00000000000..0d211700977
--- /dev/null
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -0,0 +1,235 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Tests that one session cannot read or modify data in another session's
+# temporary table. Each session keeps its temp data in its own local
+# buffer pool, and a different backend has no visibility into those
+# buffers, so any command that needs to look at the data must be
+# rejected.
+#
+# DROP TABLE is intentionally allowed: it does not touch the table's
+# contents, and autovacuum relies on this to clean up orphaned temp
+# relations left behind by a crashed backend.
+#
+# A regression caught here typically means a new buffer-access entry
+# point bypasses the RELATION_IS_OTHER_TEMP() check. See
+# ReadBuffer_common(), StartReadBuffersImpl(), and read_stream_begin_impl()
+# for the existing checks. When adding a new command or buffer-access
+# path, also add a corresponding case below.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::BackgroundPsql;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('temp_lock');
+$node->init;
+$node->start;
+
+# Owner session. Created via background_psql so it stays alive while
+# the second session probes its temp objects.
+my $psql1 = $node->background_psql('postgres');
+
+# Initially create the table without an index, so read paths go straight
+# through the read-stream / buffer-manager entry points without being
+# masked by an index scan that would hit ReadBuffer_common from nbtree.
+$psql1->query_safe(q(CREATE TEMP TABLE foo AS SELECT 42 AS val;));
+
+# Resolve the owner's temp schema so the probing session can refer to
+# the table by a fully-qualified name.
+my $tempschema = $node->safe_psql(
+ 'postgres',
+ q{
+ SELECT n.nspname
+ FROM pg_class c
+ JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE relname = 'foo' AND relpersistence = 't';
+ }
+);
+chomp $tempschema;
+ok($tempschema =~ /^pg_temp_\d+$/, "got temp schema: $tempschema");
+
+my ($stdout, $stderr);
+
+# DML and SELECT have to read the table's data and therefore go through
+# the buffer manager. With no index on the table, the planner cannot
+# use index access, so SELECT/UPDATE/DELETE/MERGE/COPY all run through
+# the read-stream path.
+#
+# XXX: in current code, the read-stream path bypasses the
+# RELATION_IS_OTHER_TEMP() check, so these commands silently see no
+# rows / report zero affected rows -- the visible symptom of the bug
+# this test suite documents. A follow-up patch will route the check
+# through read_stream_begin_impl() and these assertions will be
+# updated to expect "cannot access temporary tables of other sessions".
+
+$node->psql(
+ 'postgres',
+ "SELECT val FROM $tempschema.foo;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($stderr, '', 'SELECT (currently no error -- bug to be fixed)');
+
+# INSERT goes through hio.c which calls ReadBufferExtended() to find a
+# page with free space; that hits the existing check before any data is
+# written. This case currently errors as expected.
+$node->psql(
+ 'postgres',
+ "INSERT INTO $tempschema.foo VALUES (73);",
+ stderr => \$stderr);
+like($stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'INSERT (caught via hio.c)');
+
+$node->psql(
+ 'postgres',
+ "UPDATE $tempschema.foo SET val = NULL;",
+ stderr => \$stderr);
+is($stderr, '', 'UPDATE (currently no error -- bug to be fixed)');
+
+$node->psql('postgres', "DELETE FROM $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'DELETE (currently no error -- bug to be fixed)');
+
+$node->psql(
+ 'postgres',
+ "MERGE INTO $tempschema.foo USING (VALUES (42)) AS s(val) "
+ . "ON foo.val = s.val WHEN MATCHED THEN DELETE;",
+ stderr => \$stderr);
+is($stderr, '', 'MERGE (currently no error -- bug to be fixed)');
+
+$node->psql('postgres', "COPY $tempschema.foo TO STDOUT;",
+ stderr => \$stderr);
+is($stderr, '', 'COPY (currently no error -- bug to be fixed)');
+
+# DDL and maintenance commands have their own command-specific checks
+# (older than the buffer-manager check above), so they fail with
+# command-specific error messages. Verifying them here documents the
+# expected behaviour and guards against accidental removal of those
+# checks.
+
+$node->psql('postgres', "TRUNCATE TABLE $tempschema.foo;",
+ stderr => \$stderr);
+like($stderr,
+ qr/cannot truncate temporary tables of other sessions/,
+ 'TRUNCATE');
+
+$node->psql(
+ 'postgres',
+ "ALTER TABLE $tempschema.foo ALTER COLUMN val TYPE bigint;",
+ stderr => \$stderr);
+like($stderr,
+ qr/cannot alter temporary tables of other sessions/,
+ 'ALTER TABLE');
+
+# VACUUM silently skips other sessions' temp tables (vacuum_rel() returns
+# without warning to avoid noise during database-wide VACUUM). Verify
+# that no error is reported, and that no buffer-access path is hit.
+$node->psql('postgres', "VACUUM $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'VACUUM is silently skipped');
+
+$node->psql('postgres', "CLUSTER $tempschema.foo;", stderr => \$stderr);
+like($stderr,
+ qr/cannot execute CLUSTER on temporary tables of other sessions/,
+ 'CLUSTER');
+
+# Now create an index to exercise the index-scan path. nbtree calls
+# ReadBuffer (which is ReadBufferExtended -> ReadBuffer_common), so
+# this exercises a different chain of buffer-manager entry points.
+$psql1->query_safe(q(CREATE INDEX ON foo(val);));
+
+$node->psql(
+ 'postgres',
+ "SET enable_seqscan = off; SELECT val FROM $tempschema.foo WHERE val = 42;",
+ stderr => \$stderr);
+like(
+ $stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'index scan (ReadBuffer_common via nbtree)');
+
+# ALTER INDEX goes through the same CheckAlterTableIsSafe() path as
+# ALTER TABLE, so it produces the same error.
+$node->psql(
+ 'postgres',
+ "ALTER INDEX $tempschema.foo_val_idx SET (fillfactor = 50);",
+ stderr => \$stderr);
+like($stderr,
+ qr/cannot alter temporary tables of other sessions/,
+ 'ALTER INDEX');
+
+# A function created by the owner in its own pg_temp using its own
+# row type can be observed via the catalog by a separate session.
+# ALTER FUNCTION and DROP FUNCTION on it must work as catalog
+# operations -- they don't read the underlying table -- which
+# documents the boundary between catalog and data access for temp
+# objects.
+$psql1->query_safe(
+ q[CREATE FUNCTION pg_temp.foo_id(r foo) RETURNS int LANGUAGE SQL ]
+ . q[AS 'SELECT r.val';]);
+
+$node->psql(
+ 'postgres',
+ "ALTER FUNCTION $tempschema.foo_id($tempschema.foo) "
+ . "SET search_path = pg_catalog;",
+ stderr => \$stderr);
+is($stderr, '', 'ALTER FUNCTION on function over other session\'s row type');
+
+$node->psql(
+ 'postgres',
+ "DROP FUNCTION $tempschema.foo_id($tempschema.foo);",
+ stderr => \$stderr);
+is($stderr, '', 'DROP FUNCTION on function over other session\'s row type');
+
+# DROP TABLE on another session's temp table is intentionally permitted.
+# DROP doesn't touch the table's contents, and autovacuum relies on this
+# to remove temp relations orphaned by a crashed backend. Verify that
+# the bare DROP succeeds without error.
+$node->psql('postgres', "DROP TABLE $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'DROP TABLE is allowed');
+
+# Cross-session CREATE FUNCTION scenario. The owner creates a fresh
+# temp table foo2 in its pg_temp namespace, and a separate session
+# then creates a function whose argument type is that row type.
+# PostgreSQL allows this and emits a NOTICE: the function is moved
+# into the creator's pg_temp namespace with an auto-dependency on
+# the borrowed type, so it disappears together with the session that
+# created it.
+$psql1->query_safe(q(CREATE TEMP TABLE foo2 AS SELECT 42 AS val;));
+
+$node->psql(
+ 'postgres',
+ "CREATE FUNCTION public.cross_session_func(r $tempschema.foo2) "
+ . "RETURNS int LANGUAGE SQL AS 'SELECT 1';",
+ stderr => \$stderr);
+like(
+ $stderr,
+ qr/function "cross_session_func" will be effectively temporary/,
+ 'CREATE FUNCTION using other session\'s row type is effectively temporary'
+);
+
+# A bare DROP TABLE on foo2 now fails because cross_session_func
+# depends on its row type. This is normal SQL dependency behaviour
+# and documents that DROP itself is not blocked by buffer-manager
+# checks -- we get a catalog-level error instead.
+$node->psql('postgres', "DROP TABLE $tempschema.foo2;", stderr => \$stderr);
+like(
+ $stderr,
+ qr/cannot drop table .*\.foo2 because other objects depend on it/,
+ 'DROP TABLE blocked by cross-session dependency');
+
+# When the owner session ends, its temp objects are dropped via the
+# normal session-exit cleanup, which cascades through
+# DEPENDENCY_NORMAL and also removes the cross-session function that
+# depended on the temp row type. This is the same mechanism
+# autovacuum relies on to clean up temp relations left behind by a
+# crashed backend.
+$psql1->quit;
+
+$node->poll_query_until(
+ 'postgres',
+ "SELECT NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'cross_session_func')"
+) or die "cross_session_func was not cleaned up after owner session exit";
+
+ok(1, 'cross_session_func cleaned up when owner session ends');
+
+done_testing();
--
2.43.0
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-05-05 14:32 Álvaro Herrera <[email protected]>
parent: Jim Jones <[email protected]>
1 sibling, 1 reply; 49+ messages in thread
From: Álvaro Herrera @ 2026-05-05 14:32 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Antonin Houska <[email protected]>; Chao Li <[email protected]>; Zsolt Parragi <[email protected]>; PostgreSQL Hackers <[email protected]>
On 2026-Mar-26, Jim Jones wrote:
>
> On 26/03/2026 12:25, Antonin Houska wrote:
> > Chao Li <[email protected]> wrote:
> >
> >> I don't think such a TAP test is necessary.
> > +1
>
> I've kept the tests in a separate file so the committer can easily skip
> them if needed.
Thanks for noticing and patching this issue. I have pushed the 0001
patch just now.
I decided against pushing the other patch. Although I would have
preferred to add a test, its cost seems not trivial: there are three
full-database scans in it (one for each command), and that seemed a bit
excessive. (There's also one extra initdb, but I'm not sure that part
is too bad since we've optimized that particular part.)
I also considered backpatching, since the code has been like this
essentially forever (i.e. at least since pg14). However, I don't
remember any complaints about this and I would hate to destabilize
things for people without an excellent reason. Maybe we can reconsider
after this month's minors, if somebody shows up with vehement opinions
about it.
Thanks again,
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"Crear es tan difícil como ser libre" (Elsa Triolet)
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables
@ 2026-05-06 06:33 Jim Jones <[email protected]>
parent: Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Jim Jones @ 2026-05-06 06:33 UTC (permalink / raw)
To: Álvaro Herrera <[email protected]>; +Cc: Antonin Houska <[email protected]>; Chao Li <[email protected]>; Zsolt Parragi <[email protected]>; PostgreSQL Hackers <[email protected]>
On 05/05/2026 16:32, Álvaro Herrera wrote:
> I decided against pushing the other patch. Although I would have
> preferred to add a test, its cost seems not trivial: there are three
> full-database scans in it (one for each command), and that seemed a bit
> excessive. (There's also one extra initdb, but I'm not sure that part
> is too bad since we've optimized that particular part.)
Fair enough.
> I also considered backpatching, since the code has been like this
> essentially forever (i.e. at least since pg14). However, I don't
> remember any complaints about this and I would hate to destabilize
> things for people without an excellent reason. Maybe we can reconsider
> after this month's minors, if somebody shows up with vehement opinions
> about it.
Yeah, since pretty much nobody complained about it, I guess it's indeed
safer to leave it in PG19.
Thanks for pushing it!
Best, Jim
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-07 08:04 Alexander Korotkov <[email protected]>
parent: Daniil Davydov <[email protected]>
0 siblings, 3 replies; 49+ messages in thread
From: Alexander Korotkov @ 2026-05-07 08:04 UTC (permalink / raw)
To: Daniil Davydov <[email protected]>; +Cc: Jim Jones <[email protected]>; Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
On Mon, May 4, 2026 at 12:31 PM Daniil Davydov <[email protected]> wrote:
>
> On Sun, May 3, 2026 at 7:49 PM Jim Jones <[email protected]> wrote:
> >
> > On 03/05/2026 10:53, Daniil Davydov wrote:
> > > Please, see the attached patch that ensures that cross-session LOCK TABLE works
> > > properly.
> >
> > I guess you should either add it to the 0001 sent by Alexander, or
> > create a 0003. Right now the cfbot is complaining, as
> > 013_temp_obj_multisession.pl still does not exist :)
> >
>
> OK, I'll attach this patch as a third one, just for review purposes. After
> agreement on its content, it should be included into the 0001 and 0002 patches.
Thank you. I've integrated your check into 0001, and added an
explicit check that the table gets removed once the lock by other
session is removed.
Let me do a quick summary:
* Our buffer manager is not capable for reading temp tables of other sessions.
* This was covered by explicit checks, but broken since b7b0f3f27241
introduced alternative code path for reading tables.
* This doesn't apply to DROP TABLE. DROP TABLE is a conscious
exclusion and the only operation we can do correctly for other
session' temp tables. There is an explicit exclusion in the code to
skip the attempt to cleanup buffers of other session' temp tables.
* This patchset consists of tests (0001) for various operations with
other session's temp tables including buggy behavior, and the fix
(0002) including changes for tests.
Thus, I don't see the reason why this shouldn't be committed and
backpatched to PG17 (first release containing b7b0f3f27241).
Opinions? Michael?
------
Regards,
Alexander Korotkov
Supabase
Attachments:
[application/octet-stream] v22-0002-Prevent-access-to-other-sessions-temp-tables.patch (8.8K, ../../CAPpHfdsS49OTo2Af2GeoKJD-1Dy-hOzbje_AWOa_bw5Q9kEi7w@mail.gmail.com/2-v22-0002-Prevent-access-to-other-sessions-temp-tables.patch)
download | inline diff:
From 990c7a826eda2865f2e6c2c081a502cb04dba973 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 2 May 2026 15:23:42 +0300
Subject: [PATCH v22 2/2] Prevent access to other sessions' temp tables
Commit b7b0f3f2724 ("Use streaming I/O in sequential scans") routed
sequential scans through read_stream_next_buffer(), bypassing the
RELATION_IS_OTHER_TEMP() check in ReadBufferExtended(). As a result,
a superuser can attempt to read or modify temp tables of other
sessions through the read-stream path. When no index is present so
the planner cannot pick an index scan, SELECT/UPDATE/DELETE/MERGE
silently see no rows / report zero affected rows, and COPY produces
an empty output -- because the buffer manager has no visibility into
the owning session's local buffers and silently returns nothing.
INSERT and any path that goes through nbtree (i.e. an index scan)
still error out via the existing check in ReadBufferExtended(), which
is reached from hio.c and nbtree respectively, but this is incidental.
Fix by enforcing RELATION_IS_OTHER_TEMP() at three additional
buffer-manager entry points:
- read_stream_begin_impl() rejects the read at stream setup time,
covering sequential and bitmap scans that go through the
read-stream path.
- ReadBuffer_common() becomes the canonical place for the check,
consolidating the existing one previously kept in
ReadBufferExtended(). All ReadBufferExtended() callers go through
ReadBuffer_common(), so the consolidation is behaviour-preserving.
- StartReadBuffersImpl() catches direct callers of StartReadBuffers()
that bypass both of the above. This is currently defense-in-depth
but documents the contract for future code.
The companion test in src/test/modules/test_misc was added in the
preceding commit; this commit updates the assertions for SELECT,
UPDATE, DELETE, MERGE and COPY (which previously documented the
bug as silent success) to expect the new error.
Author: Jim Jones <[email protected]>
Author: Daniil Davydov <[email protected]>
Co-authored-by: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Soumya S Murali <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/CAJDiXghdFcZ8%3Dnh4G69te7iRr3Q0uFyXxb3ZdG09_GTNZXwH0g%40mail.gmail.com
Backpatch-through: 17
---
src/backend/storage/aio/read_stream.c | 10 ++++++
src/backend/storage/buffer/bufmgr.c | 33 ++++++++++++-------
.../test_misc/t/013_temp_obj_multisession.pl | 27 +++++++--------
3 files changed, 42 insertions(+), 28 deletions(-)
diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index 2374b4cd507..a318539e56c 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -776,6 +776,16 @@ read_stream_begin_impl(int flags,
uint32 max_possible_buffer_limit;
Oid tablespace_id;
+ /*
+ * Reject attempts to read non-local temporary relations; we would be
+ * likely to get wrong data since we have no visibility into the owning
+ * session's local buffers.
+ */
+ if (rel && RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
/*
* Decide how many I/Os we will allow to run at the same time. This
* number also affects how far we look ahead for opportunities to start
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 1878efb4aa9..cc398db124d 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -791,7 +791,7 @@ PrefetchBuffer(Relation reln, ForkNumber forkNum, BlockNumber blockNum)
if (RelationUsesLocalBuffers(reln))
{
- /* see comments in ReadBufferExtended */
+ /* see comments in ReadBuffer_common */
if (RELATION_IS_OTHER_TEMP(reln))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -928,19 +928,10 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
{
Buffer buf;
- /*
- * Reject attempts to read non-local temporary relations; we would be
- * likely to get wrong data since we have no visibility into the owning
- * session's local buffers.
- */
- if (RELATION_IS_OTHER_TEMP(reln))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot access temporary tables of other sessions")));
-
/*
* Read the buffer, and update pgstat counters to reflect a cache hit or
- * miss.
+ * miss. The other-session temp-relation check is enforced by
+ * ReadBuffer_common().
*/
buf = ReadBuffer_common(reln, RelationGetSmgr(reln), 0,
forkNum, blockNum, mode, strategy);
@@ -1292,6 +1283,18 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
int flags;
char persistence;
+ /*
+ * Reject attempts to read non-local temporary relations; we would be
+ * likely to get wrong data since we have no visibility into the owning
+ * session's local buffers. This is the canonical place for the check,
+ * covering the ReadBufferExtended() entry point and any other caller that
+ * supplies a Relation.
+ */
+ if (rel && RELATION_IS_OTHER_TEMP(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
/*
* Backward compatibility path, most code should use ExtendBufferedRel()
* instead, as acquiring the extension lock inside ExtendBufferedRel()
@@ -1382,6 +1385,12 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
Assert(*nblocks > 0);
Assert(*nblocks <= MAX_IO_COMBINE_LIMIT);
+ /* see comments in ReadBuffer_common */
+ if (operation->rel && RELATION_IS_OTHER_TEMP(operation->rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot access temporary tables of other sessions")));
+
if (operation->persistence == RELPERSISTENCE_TEMP)
{
io_context = IOCONTEXT_NORMAL;
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
index bfcba42acd0..5f3cc7d2fc5 100644
--- a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -55,25 +55,20 @@ my ($stdout, $stderr);
# DML and SELECT have to read the table's data and therefore go through
# the buffer manager. With no index on the table, the planner cannot
# use index access, so SELECT/UPDATE/DELETE/MERGE/COPY all run through
-# the read-stream path.
-#
-# XXX: in current code, the read-stream path bypasses the
-# RELATION_IS_OTHER_TEMP() check, so these commands silently see no
-# rows / report zero affected rows -- the visible symptom of the bug
-# this test suite documents. A follow-up patch will route the check
-# through read_stream_begin_impl() and these assertions will be
-# updated to expect "cannot access temporary tables of other sessions".
+# the read-stream path and are caught by read_stream_begin_impl().
$node->psql(
'postgres',
"SELECT val FROM $tempschema.foo;",
- stdout => \$stdout,
stderr => \$stderr);
-is($stderr, '', 'SELECT (currently no error -- bug to be fixed)');
+like(
+ $stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'SELECT (seqscan via read_stream)');
# INSERT goes through hio.c which calls ReadBufferExtended() to find a
-# page with free space; that hits the existing check before any data is
-# written. This case currently errors as expected.
+# page with free space; that hits the existing check before any data
+# is written.
$node->psql(
'postgres',
"INSERT INTO $tempschema.foo VALUES (73);",
@@ -87,21 +82,21 @@ $node->psql(
'postgres',
"UPDATE $tempschema.foo SET val = NULL;",
stderr => \$stderr);
-is($stderr, '', 'UPDATE (currently no error -- bug to be fixed)');
+like($stderr, qr/cannot access temporary tables of other sessions/, 'UPDATE');
$node->psql('postgres', "DELETE FROM $tempschema.foo;", stderr => \$stderr);
-is($stderr, '', 'DELETE (currently no error -- bug to be fixed)');
+like($stderr, qr/cannot access temporary tables of other sessions/, 'DELETE');
$node->psql(
'postgres',
"MERGE INTO $tempschema.foo USING (VALUES (42)) AS s(val) "
. "ON foo.val = s.val WHEN MATCHED THEN DELETE;",
stderr => \$stderr);
-is($stderr, '', 'MERGE (currently no error -- bug to be fixed)');
+like($stderr, qr/cannot access temporary tables of other sessions/, 'MERGE');
$node->psql('postgres', "COPY $tempschema.foo TO STDOUT;",
stderr => \$stderr);
-is($stderr, '', 'COPY (currently no error -- bug to be fixed)');
+like($stderr, qr/cannot access temporary tables of other sessions/, 'COPY');
# DDL and maintenance commands have their own command-specific checks
# (older than the buffer-manager check above), so they fail with
--
2.39.5 (Apple Git-154)
[application/octet-stream] v22-0001-Add-tests-for-cross-session-temp-table-access.patch (15.4K, ../../CAPpHfdsS49OTo2Af2GeoKJD-1Dy-hOzbje_AWOa_bw5Q9kEi7w@mail.gmail.com/3-v22-0001-Add-tests-for-cross-session-temp-table-access.patch)
download | inline diff:
From 77ae14ba31396e25b7417b9357edb7982611035d Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <[email protected]>
Date: Sat, 2 May 2026 15:22:26 +0300
Subject: [PATCH v22 1/2] Add tests for cross-session temp table access
Add a TAP test in src/test/modules/test_misc that documents what
happens when one session attempts to read or modify another session's
temporary table. This commit only adds tests; it does not change
backend behaviour, so the assertions reflect current behaviour:
- SELECT, UPDATE, DELETE, MERGE, COPY on a table without an index
silently succeed with no error and zero rows / zero affected rows.
These commands run through the read-stream path, which currently
bypasses the RELATION_IS_OTHER_TEMP() check. This is the
underlying bug to be fixed in a follow-up.
- INSERT errors with "cannot access temporary tables of other
sessions" because hio.c calls ReadBufferExtended() to find a page
with free space and is caught by the existing check there.
- Index scan errors via the same existing check, reached through
nbtree -> ReadBuffer -> ReadBufferExtended.
- TRUNCATE / ALTER TABLE / ALTER INDEX / CLUSTER fail with their
command-specific error messages.
- VACUUM is silently skipped to avoid noise during database-wide
VACUUM (vacuum_rel() returns without warning).
- DROP TABLE is intentionally allowed: DROP does not touch the
table's contents, and autovacuum relies on this to clean up
temp relations orphaned by a crashed backend.
- ALTER FUNCTION / DROP FUNCTION on an owner-created function over
its own temp row type work as catalog operations -- they don't
read the underlying data.
- CREATE FUNCTION from a separate session, using another session's
temp row type as an argument, is allowed but emits a NOTICE: the
function is moved into the creator's pg_temp namespace with an
auto-dependency on the borrowed type, so it disappears together
with the session that created it.
- A bare DROP TABLE on a temp table that has a cross-session
dependent function fails with a catalog-level dependency error.
- LOCK TABLE in ACCESS SHARE mode on another session's temp table
succeeds and properly blocks the owner's session-exit cleanup
(which acquires AccessExclusiveLock via findDependentObjects).
This exercises the same LockRelationOid path used by autovacuum
when cleaning up orphaned temp relations.
- When the owner session ends, the normal session-exit cleanup
cascades through DEPENDENCY_NORMAL and removes both the temp
objects and any cross-session functions that depended on them.
Also document the contract for RELATION_IS_OTHER_TEMP() so that
future buffer-access entry points enforce the same rule.
Author: Jim Jones <[email protected]>
Author: Daniil Davydov <[email protected]>
Co-authored-by: Alexander Korotkov <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Soumya S Murali <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Discussion: https://postgr.es/m/CAJDiXghdFcZ8%3Dnh4G69te7iRr3Q0uFyXxb3ZdG09_GTNZXwH0g%40mail.gmail.com
---
src/include/utils/rel.h | 9 +
src/test/modules/test_misc/meson.build | 1 +
.../test_misc/t/013_temp_obj_multisession.pl | 267 ++++++++++++++++++
3 files changed, 277 insertions(+)
create mode 100644 src/test/modules/test_misc/t/013_temp_obj_multisession.pl
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index cd1e92f2302..ad50e43b801 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -664,6 +664,15 @@ RelationCloseSmgr(Relation relation)
* RELATION_IS_OTHER_TEMP
* Test for a temporary relation that belongs to some other session.
*
+ * Any code path that reads a relation's data must reject such relations:
+ * the owning session keeps the data in its private local buffer pool,
+ * which we cannot inspect. Existing buffer-manager entry points
+ * (ReadBufferExtended(), ReadBuffer_common(), StartReadBuffersImpl(),
+ * read_stream_begin_impl(), PrefetchBuffer()) already enforce this; any
+ * new buffer-access entry point must do the same. Command-level code
+ * (TRUNCATE, ALTER TABLE, VACUUM, CLUSTER, REINDEX, ...) additionally
+ * uses this macro for command-specific error messages.
+ *
* Beware of multiple eval of argument
*/
#define RELATION_IS_OTHER_TEMP(relation) \
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 356d8454b39..969e90b396d 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -21,6 +21,7 @@ tests += {
't/010_index_concurrently_upsert.pl',
't/011_lock_stats.pl',
't/012_ddlutils.pl',
+ 't/013_temp_obj_multisession.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
diff --git a/src/test/modules/test_misc/t/013_temp_obj_multisession.pl b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
new file mode 100644
index 00000000000..bfcba42acd0
--- /dev/null
+++ b/src/test/modules/test_misc/t/013_temp_obj_multisession.pl
@@ -0,0 +1,267 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Tests that one session cannot read or modify data in another session's
+# temporary table. Each session keeps its temp data in its own local
+# buffer pool, and a different backend has no visibility into those
+# buffers, so any command that needs to look at the data must be
+# rejected.
+#
+# DROP TABLE is intentionally allowed: it does not touch the table's
+# contents, and autovacuum relies on this to clean up orphaned temp
+# relations left behind by a crashed backend.
+#
+# A regression caught here typically means a new buffer-access entry
+# point bypasses the RELATION_IS_OTHER_TEMP() check. See
+# ReadBuffer_common(), StartReadBuffersImpl(), and read_stream_begin_impl()
+# for the existing checks. When adding a new command or buffer-access
+# path, also add a corresponding case below.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use PostgreSQL::Test::BackgroundPsql;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('temp_lock');
+$node->init;
+$node->start;
+
+# Owner session. Created via background_psql so it stays alive while
+# the second session probes its temp objects.
+my $psql1 = $node->background_psql('postgres');
+
+# Initially create the table without an index, so read paths go straight
+# through the read-stream / buffer-manager entry points without being
+# masked by an index scan that would hit ReadBuffer_common from nbtree.
+$psql1->query_safe(q(CREATE TEMP TABLE foo AS SELECT 42 AS val;));
+
+# Resolve the owner's temp schema so the probing session can refer to
+# the table by a fully-qualified name.
+my $tempschema = $node->safe_psql(
+ 'postgres',
+ q{
+ SELECT n.nspname
+ FROM pg_class c
+ JOIN pg_namespace n ON n.oid = c.relnamespace
+ WHERE relname = 'foo' AND relpersistence = 't';
+ }
+);
+chomp $tempschema;
+ok($tempschema =~ /^pg_temp_\d+$/, "got temp schema: $tempschema");
+
+my ($stdout, $stderr);
+
+# DML and SELECT have to read the table's data and therefore go through
+# the buffer manager. With no index on the table, the planner cannot
+# use index access, so SELECT/UPDATE/DELETE/MERGE/COPY all run through
+# the read-stream path.
+#
+# XXX: in current code, the read-stream path bypasses the
+# RELATION_IS_OTHER_TEMP() check, so these commands silently see no
+# rows / report zero affected rows -- the visible symptom of the bug
+# this test suite documents. A follow-up patch will route the check
+# through read_stream_begin_impl() and these assertions will be
+# updated to expect "cannot access temporary tables of other sessions".
+
+$node->psql(
+ 'postgres',
+ "SELECT val FROM $tempschema.foo;",
+ stdout => \$stdout,
+ stderr => \$stderr);
+is($stderr, '', 'SELECT (currently no error -- bug to be fixed)');
+
+# INSERT goes through hio.c which calls ReadBufferExtended() to find a
+# page with free space; that hits the existing check before any data is
+# written. This case currently errors as expected.
+$node->psql(
+ 'postgres',
+ "INSERT INTO $tempschema.foo VALUES (73);",
+ stderr => \$stderr);
+like(
+ $stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'INSERT (caught via hio.c)');
+
+$node->psql(
+ 'postgres',
+ "UPDATE $tempschema.foo SET val = NULL;",
+ stderr => \$stderr);
+is($stderr, '', 'UPDATE (currently no error -- bug to be fixed)');
+
+$node->psql('postgres', "DELETE FROM $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'DELETE (currently no error -- bug to be fixed)');
+
+$node->psql(
+ 'postgres',
+ "MERGE INTO $tempschema.foo USING (VALUES (42)) AS s(val) "
+ . "ON foo.val = s.val WHEN MATCHED THEN DELETE;",
+ stderr => \$stderr);
+is($stderr, '', 'MERGE (currently no error -- bug to be fixed)');
+
+$node->psql('postgres', "COPY $tempschema.foo TO STDOUT;",
+ stderr => \$stderr);
+is($stderr, '', 'COPY (currently no error -- bug to be fixed)');
+
+# DDL and maintenance commands have their own command-specific checks
+# (older than the buffer-manager check above), so they fail with
+# command-specific error messages. Verifying them here documents the
+# expected behaviour and guards against accidental removal of those
+# checks.
+
+$node->psql('postgres', "TRUNCATE TABLE $tempschema.foo;",
+ stderr => \$stderr);
+like($stderr, qr/cannot truncate temporary tables of other sessions/,
+ 'TRUNCATE');
+
+$node->psql(
+ 'postgres',
+ "ALTER TABLE $tempschema.foo ALTER COLUMN val TYPE bigint;",
+ stderr => \$stderr);
+like($stderr, qr/cannot alter temporary tables of other sessions/,
+ 'ALTER TABLE');
+
+# VACUUM silently skips other sessions' temp tables (vacuum_rel() returns
+# without warning to avoid noise during database-wide VACUUM). Verify
+# that no error is reported, and that no buffer-access path is hit.
+$node->psql('postgres', "VACUUM $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'VACUUM is silently skipped');
+
+$node->psql('postgres', "CLUSTER $tempschema.foo;", stderr => \$stderr);
+like($stderr,
+ qr/cannot execute CLUSTER on temporary tables of other sessions/,
+ 'CLUSTER');
+
+# Now create an index to exercise the index-scan path. nbtree calls
+# ReadBuffer (which is ReadBufferExtended -> ReadBuffer_common), so
+# this exercises a different chain of buffer-manager entry points.
+$psql1->query_safe(q(CREATE INDEX ON foo(val);));
+
+$node->psql(
+ 'postgres',
+ "SET enable_seqscan = off; SELECT val FROM $tempschema.foo WHERE val = 42;",
+ stderr => \$stderr);
+like(
+ $stderr,
+ qr/cannot access temporary tables of other sessions/,
+ 'index scan (ReadBuffer_common via nbtree)');
+
+# ALTER INDEX goes through the same CheckAlterTableIsSafe() path as
+# ALTER TABLE, so it produces the same error.
+$node->psql(
+ 'postgres',
+ "ALTER INDEX $tempschema.foo_val_idx SET (fillfactor = 50);",
+ stderr => \$stderr);
+like($stderr, qr/cannot alter temporary tables of other sessions/,
+ 'ALTER INDEX');
+
+# A function created by the owner in its own pg_temp using its own
+# row type can be observed via the catalog by a separate session.
+# ALTER FUNCTION and DROP FUNCTION on it must work as catalog
+# operations -- they don't read the underlying table -- which
+# documents the boundary between catalog and data access for temp
+# objects.
+$psql1->query_safe(
+ q[CREATE FUNCTION pg_temp.foo_id(r foo) RETURNS int LANGUAGE SQL ]
+ . q[AS 'SELECT r.val';]);
+
+$node->psql(
+ 'postgres',
+ "ALTER FUNCTION $tempschema.foo_id($tempschema.foo) "
+ . "SET search_path = pg_catalog;",
+ stderr => \$stderr);
+is($stderr, '', 'ALTER FUNCTION on function over other session\'s row type');
+
+$node->psql(
+ 'postgres',
+ "DROP FUNCTION $tempschema.foo_id($tempschema.foo);",
+ stderr => \$stderr);
+is($stderr, '', 'DROP FUNCTION on function over other session\'s row type');
+
+# DROP TABLE on another session's temp table is intentionally permitted.
+# DROP doesn't touch the table's contents, and autovacuum relies on this
+# to remove temp relations orphaned by a crashed backend. Verify that
+# the bare DROP succeeds without error.
+$node->psql('postgres', "DROP TABLE $tempschema.foo;", stderr => \$stderr);
+is($stderr, '', 'DROP TABLE is allowed');
+
+# Cross-session CREATE FUNCTION scenario. The owner creates a fresh
+# temp table foo2 in its pg_temp namespace, and a separate session
+# then creates a function whose argument type is that row type.
+# PostgreSQL allows this and emits a NOTICE: the function is moved
+# into the creator's pg_temp namespace with an auto-dependency on
+# the borrowed type, so it disappears together with the session that
+# created it.
+$psql1->query_safe(q(CREATE TEMP TABLE foo2 AS SELECT 42 AS val;));
+
+$node->psql(
+ 'postgres',
+ "CREATE FUNCTION public.cross_session_func(r $tempschema.foo2) "
+ . "RETURNS int LANGUAGE SQL AS 'SELECT 1';",
+ stderr => \$stderr);
+like(
+ $stderr,
+ qr/function "cross_session_func" will be effectively temporary/,
+ 'CREATE FUNCTION using other session\'s row type is effectively temporary'
+);
+
+# A bare DROP TABLE on foo2 now fails because cross_session_func
+# depends on its row type. This is normal SQL dependency behaviour
+# and documents that DROP itself is not blocked by buffer-manager
+# checks -- we get a catalog-level error instead.
+$node->psql('postgres', "DROP TABLE $tempschema.foo2;", stderr => \$stderr);
+like(
+ $stderr,
+ qr/cannot drop table .*\.foo2 because other objects depend on it/,
+ 'DROP TABLE blocked by cross-session dependency');
+
+my $foo2_oid = $node->safe_psql('postgres',
+ "SELECT oid FROM pg_class WHERE relname='foo2';");
+
+# Cross-session LOCK TABLE scenario. Ensure that LockRelationOid is working
+# properly for other temp tables since this mechanism is also used by
+# autovacuum during orphaned tables cleanup.
+my $psql2 = $node->background_psql('postgres');
+$psql2->query_safe(
+ qq{
+ BEGIN;
+ LOCK TABLE $tempschema.foo2 IN ACCESS SHARE MODE;
+});
+
+# When the owner session ends, its temp objects are dropped via the
+# normal session-exit cleanup, which cascades through
+# DEPENDENCY_NORMAL and also removes the cross-session function that
+# depended on the temp row type. This is the same mechanism
+# autovacuum relies on to clean up temp relations left behind by a
+# crashed backend.
+# Access share lock on the foo2 will block session-exit cleanup, because an
+# owner will try to acquire deletion lock all its temp objects via
+# findDependentObjects.
+my $log_offset = -s $node->logfile;
+$psql1->quit;
+
+# Check whether session-exit cleanup is blocked.
+$node->wait_for_log(qr/waiting for AccessExclusiveLock on relation $foo2_oid/,
+ $log_offset);
+
+# Release lock on foo2 and allow session-exit cleanup to finish.
+$psql2->query_safe(q(COMMIT;));
+$psql2->quit;
+
+# After releasing the lock, the owner can finally acquire
+# AccessExclusiveLock on foo2 and finish session-exit cleanup. Verify
+# directly that both foo2 (the locked temp table) and cross_session_func
+# (which depended on its row type) have been dropped. Both being gone
+# confirms the owner's cleanup got past the blocked findDependentObjects()
+# call and completed normally.
+$node->poll_query_until('postgres',
+ "SELECT NOT EXISTS (SELECT 1 FROM pg_class WHERE oid = $foo2_oid)")
+ or die "foo2 was not cleaned up after owner session exit";
+
+is( $node->safe_psql(
+ 'postgres',
+ "SELECT count(*) FROM pg_proc WHERE proname = 'cross_session_func'"),
+ '0',
+ 'cross_session_func cleaned up by cascade from foo2');
+
+done_testing();
--
2.39.5 (Apple Git-154)
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-07 08:43 Jim Jones <[email protected]>
parent: Alexander Korotkov <[email protected]>
2 siblings, 1 reply; 49+ messages in thread
From: Jim Jones @ 2026-05-07 08:43 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; Daniil Davydov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
On 07/05/2026 10:04, Alexander Korotkov wrote:
> Thus, I don't see the reason why this shouldn't be committed and
> backpatched to PG17 (first release containing b7b0f3f27241).
Quick question: should we work on a separate patch for PG14-PG16? In
these versions, the error message is raised depending on the temporary
table's tuple count, as demonstrated in [1]:
== session 1 ==
psql (14.20 (Debian 14.20-1.pgdg13+1))
Type "help" for help.
postgres=# CREATE TEMPORARY TABLE t (id int);
CREATE TABLE
postgres=# \d pg_temp*.*
Table "pg_temp_3.t"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
== session 2 ==
psql (14.20 (Debian 14.20-1.pgdg13+1))
Type "help" for help.
postgres=# SELECT * FROM pg_temp_3.t;
id
----
(0 rows)
== session 1 ==
postgres=# INSERT INTO t VALUES (42);
INSERT 0 1
== session 2 ==
postgres=# SELECT * FROM pg_temp_3.t;
ERROR: cannot access temporary tables of other sessions
Thanks!
Best, Jim
1 -
https://www.postgresql.org/message-id/800c75af-9bd0-48ac-b4bf-54cadf2bc723%40uni-muenster.de
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-07 10:43 Alexander Korotkov <[email protected]>
parent: Jim Jones <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Alexander Korotkov @ 2026-05-07 10:43 UTC (permalink / raw)
To: Jim Jones <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
On Thu, May 7, 2026 at 11:43 AM Jim Jones <[email protected]> wrote:
> On 07/05/2026 10:04, Alexander Korotkov wrote:
> > Thus, I don't see the reason why this shouldn't be committed and
> > backpatched to PG17 (first release containing b7b0f3f27241).
>
> Quick question: should we work on a separate patch for PG14-PG16? In
> these versions, the error message is raised depending on the temporary
> table's tuple count, as demonstrated in [1]:
>
> == session 1 ==
>
> psql (14.20 (Debian 14.20-1.pgdg13+1))
> Type "help" for help.
>
> postgres=# CREATE TEMPORARY TABLE t (id int);
> CREATE TABLE
> postgres=# \d pg_temp*.*
> Table "pg_temp_3.t"
> Column | Type | Collation | Nullable | Default
> --------+---------+-----------+----------+---------
> id | integer | | |
>
> == session 2 ==
>
> psql (14.20 (Debian 14.20-1.pgdg13+1))
> Type "help" for help.
>
> postgres=# SELECT * FROM pg_temp_3.t;
> id
> ----
> (0 rows)
>
> == session 1 ==
>
> postgres=# INSERT INTO t VALUES (42);
> INSERT 0 1
>
> == session 2 ==
>
> postgres=# SELECT * FROM pg_temp_3.t;
> ERROR: cannot access temporary tables of other sessions
Thank you for your question. Yes, PostgreSQL 14-16 first does
RelationGetNumberOfBlocks(), then scans the relation through the
buffer manager. If RelationGetNumberOfBlocks() returns 0, then no
buffers accessed and no error thrown. So, it could scan empty tables
because it doesn't requires accessing local buffers of other sessions.
I would investigate this further. If this doesn't have negative side
effects (like execution of other commands that couldn't be performed
correctly), I would avoid backpatching to these versions.
------
Regards,
Alexander Korotkov
Supabase
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-07 14:22 Tom Lane <[email protected]>
parent: Alexander Korotkov <[email protected]>
2 siblings, 1 reply; 49+ messages in thread
From: Tom Lane @ 2026-05-07 14:22 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Jim Jones <[email protected]>; Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Alexander Korotkov <[email protected]> writes:
> Thus, I don't see the reason why this shouldn't be committed and
> backpatched to PG17 (first release containing b7b0f3f27241).
We are less than 48 hours from code freeze for this month's back
branch releases. I think it's already too late for any inessential
changes, especially if you're less than 100.00% sure of them.
Please hold off until after the release cycle.
regards, tom lane
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-07 16:13 Alexander Korotkov <[email protected]>
parent: Tom Lane <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Alexander Korotkov @ 2026-05-07 16:13 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Jim Jones <[email protected]>; Michael Paquier <[email protected]>; Soumya S Murali <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi, Tom!
On Thu, May 7, 2026 at 5:22 PM Tom Lane <[email protected]> wrote:
> Alexander Korotkov <[email protected]> writes:
> > Thus, I don't see the reason why this shouldn't be committed and
> > backpatched to PG17 (first release containing b7b0f3f27241).
>
> We are less than 48 hours from code freeze for this month's back
> branch releases. I think it's already too late for any inessential
> changes, especially if you're less than 100.00% sure of them.
> Please hold off until after the release cycle.
Thank you for noticing. Yes, I'm not 100% sure new tap tests wouldn't
break buildfarm. It would be pity to break it so close to the release
freeze. I'll wait.
------
Regards,
Alexander Korotkov
Supabase
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-08 06:19 Michael Paquier <[email protected]>
parent: Alexander Korotkov <[email protected]>
2 siblings, 2 replies; 49+ messages in thread
From: Michael Paquier @ 2026-05-08 06:19 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Jim Jones <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
On Thu, May 07, 2026 at 11:04:16AM +0300, Alexander Korotkov wrote:
> Let me do a quick summary:
> * Our buffer manager is not capable for reading temp tables of other sessions.
> * This was covered by explicit checks, but broken since b7b0f3f27241
> introduced alternative code path for reading tables.
> * This doesn't apply to DROP TABLE. DROP TABLE is a conscious
> exclusion and the only operation we can do correctly for other
> session' temp tables. There is an explicit exclusion in the code to
> skip the attempt to cleanup buffers of other session' temp tables.
> * This patchset consists of tests (0001) for various operations with
> other session's temp tables including buggy behavior, and the fix
> (0002) including changes for tests.
>
> Thus, I don't see the reason why this shouldn't be committed and
> backpatched to PG17 (first release containing b7b0f3f27241).
> Opinions? Michael?
Hmm. I don't have any counter-arguments against a backpatch based on
your argument related to b7b0f3f27241. Thanks for reorganizing the
patch set so as the tests happen first, and the changes in the code
become second.
If you wish me to look at this patch set in details, I may be able to
do so around the beginning of next week. I'm not sure that there is a
strong urgency in tackling this issue for this minor release, this
could wait a bit more..
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-08 06:39 Alexander Korotkov <[email protected]>
parent: Michael Paquier <[email protected]>
1 sibling, 0 replies; 49+ messages in thread
From: Alexander Korotkov @ 2026-05-08 06:39 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Jim Jones <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
On Fri, May 8, 2026 at 9:19 AM Michael Paquier <[email protected]> wrote:
> On Thu, May 07, 2026 at 11:04:16AM +0300, Alexander Korotkov wrote:
> > Let me do a quick summary:
> > * Our buffer manager is not capable for reading temp tables of other sessions.
> > * This was covered by explicit checks, but broken since b7b0f3f27241
> > introduced alternative code path for reading tables.
> > * This doesn't apply to DROP TABLE. DROP TABLE is a conscious
> > exclusion and the only operation we can do correctly for other
> > session' temp tables. There is an explicit exclusion in the code to
> > skip the attempt to cleanup buffers of other session' temp tables.
> > * This patchset consists of tests (0001) for various operations with
> > other session's temp tables including buggy behavior, and the fix
> > (0002) including changes for tests.
> >
> > Thus, I don't see the reason why this shouldn't be committed and
> > backpatched to PG17 (first release containing b7b0f3f27241).
> > Opinions? Michael?
>
> Hmm. I don't have any counter-arguments against a backpatch based on
> your argument related to b7b0f3f27241. Thanks for reorganizing the
> patch set so as the tests happen first, and the changes in the code
> become second.
>
> If you wish me to look at this patch set in details, I may be able to
> do so around the beginning of next week. I'm not sure that there is a
> strong urgency in tackling this issue for this minor release, this
> could wait a bit more..
Absolutely, no urgency to include it into this minor release as I
already agreed [1]. You're very welcome if you could take a look in
the beginning of the next week.
Links.
1. https://www.postgresql.org/message-id/CAPpHfdsEr6RF-SkzVGD6PzFusENbpNPxKnRY7iQQ%3DcZQYJia6w%40mail.g...
------
Regards,
Alexander Korotkov
Supabase
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-14 06:12 Alexander Korotkov <[email protected]>
parent: Michael Paquier <[email protected]>
1 sibling, 1 reply; 49+ messages in thread
From: Alexander Korotkov @ 2026-05-14 06:12 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Jim Jones <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi, Michael!
On Fri, May 8, 2026 at 9:19 AM Michael Paquier <[email protected]> wrote:
>
> On Thu, May 07, 2026 at 11:04:16AM +0300, Alexander Korotkov wrote:
> > Let me do a quick summary:
> > * Our buffer manager is not capable for reading temp tables of other sessions.
> > * This was covered by explicit checks, but broken since b7b0f3f27241
> > introduced alternative code path for reading tables.
> > * This doesn't apply to DROP TABLE. DROP TABLE is a conscious
> > exclusion and the only operation we can do correctly for other
> > session' temp tables. There is an explicit exclusion in the code to
> > skip the attempt to cleanup buffers of other session' temp tables.
> > * This patchset consists of tests (0001) for various operations with
> > other session's temp tables including buggy behavior, and the fix
> > (0002) including changes for tests.
> >
> > Thus, I don't see the reason why this shouldn't be committed and
> > backpatched to PG17 (first release containing b7b0f3f27241).
> > Opinions? Michael?
>
> Hmm. I don't have any counter-arguments against a backpatch based on
> your argument related to b7b0f3f27241. Thanks for reorganizing the
> patch set so as the tests happen first, and the changes in the code
> become second.
>
> If you wish me to look at this patch set in details, I may be able to
> do so around the beginning of next week. I'm not sure that there is a
> strong urgency in tackling this issue for this minor release, this
> could wait a bit more..
Any news from your side?
------
Regards,
Alexander Korotkov
Supabase
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-14 06:58 Michael Paquier <[email protected]>
parent: Alexander Korotkov <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Michael Paquier @ 2026-05-14 06:58 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Jim Jones <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
On Thu, May 14, 2026 at 09:12:58AM +0300, Alexander Korotkov wrote:
> On Fri, May 8, 2026 at 9:19 AM Michael Paquier <[email protected]> wrote:
>> If you wish me to look at this patch set in details, I may be able to
>> do so around the beginning of next week. I'm not sure that there is a
>> strong urgency in tackling this issue for this minor release, this
>> could wait a bit more..
>
> Any news from your side?
(Forgot -hackers and other folks in CC, sorry about that.)
Unfortunately I have not been able to get back to it this week, and
next week is moot. Perhaps it is better to not wait for me here, so
feel free to go ahead as you feel.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-14 13:39 Alexander Korotkov <[email protected]>
parent: Michael Paquier <[email protected]>
0 siblings, 2 replies; 49+ messages in thread
From: Alexander Korotkov @ 2026-05-14 13:39 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Jim Jones <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
On Thu, May 14, 2026 at 9:58 AM Michael Paquier <[email protected]> wrote:
> On Thu, May 14, 2026 at 09:12:58AM +0300, Alexander Korotkov wrote:
> > On Fri, May 8, 2026 at 9:19 AM Michael Paquier <[email protected]> wrote:
> >> If you wish me to look at this patch set in details, I may be able to
> >> do so around the beginning of next week. I'm not sure that there is a
> >> strong urgency in tackling this issue for this minor release, this
> >> could wait a bit more..
> >
> > Any news from your side?
>
> (Forgot -hackers and other folks in CC, sorry about that.)
>
> Unfortunately I have not been able to get back to it this week, and
> next week is moot. Perhaps it is better to not wait for me here, so
> feel free to go ahead as you feel.
Thank you for noticing. I've pushed this today. I have to slightly
revise the tests to run on 18 and 17 (different log messages, and
default value of log_lock_waits).
------
Regards,
Alexander Korotkov
Supabase
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-14 16:48 Jim Jones <[email protected]>
parent: Alexander Korotkov <[email protected]>
1 sibling, 0 replies; 49+ messages in thread
From: Jim Jones @ 2026-05-14 16:48 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; Michael Paquier <[email protected]>; +Cc: Daniil Davydov <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi Alexander
On 14/05/2026 15:39, Alexander Korotkov wrote:
> Thank you for noticing. I've pushed this today. I have to slightly
> revise the tests to run on 18 and 17 (different log messages, and
> default value of log_lock_waits).
Awesome. Thanks for taking care of it!
Best, Jim
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-05-14 16:48 Daniil Davydov <[email protected]>
parent: Alexander Korotkov <[email protected]>
1 sibling, 1 reply; 49+ messages in thread
From: Daniil Davydov @ 2026-05-14 16:48 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Jim Jones <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi,
On Thu, May 14, 2026 at 8:39 PM Alexander Korotkov <[email protected]> wrote:
>
> On Thu, May 14, 2026 at 9:58 AM Michael Paquier <[email protected]> wrote:
> > On Thu, May 14, 2026 at 09:12:58AM +0300, Alexander Korotkov wrote:
> > > On Fri, May 8, 2026 at 9:19 AM Michael Paquier <[email protected]> wrote:
> > >> If you wish me to look at this patch set in details, I may be able to
> > >> do so around the beginning of next week. I'm not sure that there is a
> > >> strong urgency in tackling this issue for this minor release, this
> > >> could wait a bit more..
> > >
> > > Any news from your side?
> >
> > (Forgot -hackers and other folks in CC, sorry about that.)
> >
> > Unfortunately I have not been able to get back to it this week, and
> > next week is moot. Perhaps it is better to not wait for me here, so
> > feel free to go ahead as you feel.
>
> Thank you for noticing. I've pushed this today. I have to slightly
> revise the tests to run on 18 and 17 (different log messages, and
> default value of log_lock_waits).
>
Thank you very much for your help!)
BTW, we still have another problem with temp tables. Tom wrote [1] about it
within this thread:
> Reality is that we cannot know whether an
> unqualified-name RangeVar references a temp table until we do a
> catalog lookup, so IMO we should not have a relpersistence field there
> at all. At best it means something quite different from what it means
> elsewhere, and that's a recipe for confusion. But changing that would
> not be a bug fix (AFAIK) but refactoring to reduce the probability of
> future bugs.
I'll try to implement that next month. But if someone gets ahead of me, please
attach me in CC of the new thread.
[1] https://www.postgresql.org/message-id/4075754.1774378690%40sss.pgh.pa.us
--
Best regards,
Daniil Davydov
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-06-30 11:46 Alexander Korotkov <[email protected]>
parent: Daniil Davydov <[email protected]>
0 siblings, 1 reply; 49+ messages in thread
From: Alexander Korotkov @ 2026-06-30 11:46 UTC (permalink / raw)
To: Daniil Davydov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Jim Jones <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi, Daniil!
On Thu, May 14, 2026 at 7:49 PM Daniil Davydov <[email protected]> wrote:
> On Thu, May 14, 2026 at 8:39 PM Alexander Korotkov <[email protected]> wrote:
> >
> > On Thu, May 14, 2026 at 9:58 AM Michael Paquier <[email protected]> wrote:
> > > On Thu, May 14, 2026 at 09:12:58AM +0300, Alexander Korotkov wrote:
> > > > On Fri, May 8, 2026 at 9:19 AM Michael Paquier <[email protected]> wrote:
> > > >> If you wish me to look at this patch set in details, I may be able to
> > > >> do so around the beginning of next week. I'm not sure that there is a
> > > >> strong urgency in tackling this issue for this minor release, this
> > > >> could wait a bit more..
> > > >
> > > > Any news from your side?
> > >
> > > (Forgot -hackers and other folks in CC, sorry about that.)
> > >
> > > Unfortunately I have not been able to get back to it this week, and
> > > next week is moot. Perhaps it is better to not wait for me here, so
> > > feel free to go ahead as you feel.
> >
> > Thank you for noticing. I've pushed this today. I have to slightly
> > revise the tests to run on 18 and 17 (different log messages, and
> > default value of log_lock_waits).
> >
>
> Thank you very much for your help!)
You're welcome )
> BTW, we still have another problem with temp tables. Tom wrote [1] about it
> within this thread:
>
> > Reality is that we cannot know whether an
> > unqualified-name RangeVar references a temp table until we do a
> > catalog lookup, so IMO we should not have a relpersistence field there
> > at all. At best it means something quite different from what it means
> > elsewhere, and that's a recipe for confusion. But changing that would
> > not be a bug fix (AFAIK) but refactoring to reduce the probability of
> > future bugs.
>
> I'll try to implement that next month. But if someone gets ahead of me, please
> attach me in CC of the new thread.
>
> [1] https://www.postgresql.org/message-id/4075754.1774378690%40sss.pgh.pa.us
Just a friendly ping on how is it going.
------
Regards,
Alexander Korotkov
Supabase
^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: Fix bug with accessing to temporary tables of other sessions
@ 2026-06-30 12:01 Daniil Davydov <[email protected]>
parent: Alexander Korotkov <[email protected]>
0 siblings, 0 replies; 49+ messages in thread
From: Daniil Davydov @ 2026-06-30 12:01 UTC (permalink / raw)
To: Alexander Korotkov <[email protected]>; +Cc: Michael Paquier <[email protected]>; Jim Jones <[email protected]>; Soumya S Murali <[email protected]>; Tom Lane <[email protected]>; Stepan Neretin <[email protected]>; pgsql-hackers; Mohamed Ali <[email protected]>; Nazneen Jafri <[email protected]>; Shawn McCoy <[email protected]>
Hi,
On Tue, Jun 30, 2026 at 6:46 PM Alexander Korotkov <[email protected]> wrote:
>
> > BTW, we still have another problem with temp tables. Tom wrote [1] about it
> > within this thread:
> >
> > > Reality is that we cannot know whether an
> > > unqualified-name RangeVar references a temp table until we do a
> > > catalog lookup, so IMO we should not have a relpersistence field there
> > > at all. At best it means something quite different from what it means
> > > elsewhere, and that's a recipe for confusion. But changing that would
> > > not be a bug fix (AFAIK) but refactoring to reduce the probability of
> > > future bugs.
> >
> > I'll try to implement that next month. But if someone gets ahead of me, please
> > attach me in CC of the new thread.
>
> Just a friendly ping on how is it going.
Sorry, I was too busy with my studies and completely forgot about it...
I will start working on the patch within this week. Thanks for reminding me.
BTW, recently I found out that we haven't completely gotten rid of the problem
discussed here, so I started a new thread [1]. I think it might interest you.
[1] https://www.postgresql.org/message-id/flat/CAJDiXgiX2XZBHDNo%2BzBbvku%2BtchrUurvPRaN1_40mEQ1_sG90g%4...
--
Best regards,
Daniil Davydov
^ permalink raw reply [nested|flat] 49+ messages in thread
end of thread, other threads:[~2026-06-30 12:01 UTC | newest]
Thread overview: 49+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-07-09 09:54 [PATCH 5/5] Remove the old implemenations of XLogRead(). Antonin Houska <[email protected]>
2019-09-09 09:53 [PATCH 4/4] Remove the old implemenations of XLogRead(). Antonin Houska <[email protected]>
2019-09-23 05:40 [PATCH 6/6] Remove the old implemenations of XLogRead(). Antonin Houska <[email protected]>
2019-09-26 11:51 [PATCH 2/2] Remove the old implemenations of XLogRead(). Antonin Houska <[email protected]>
2019-10-04 10:07 [PATCH 2/2] Remove the old implemenations of XLogRead(). Antonin Houska <[email protected]>
2019-10-11 10:07 [PATCH 2/2] Remove the old implemenations of XLogRead(). Antonin Houska <[email protected]>
2019-11-12 10:51 [PATCH 2/2] Remove the old implemenations of XLogRead(). Antonin Houska <[email protected]>
2019-11-20 14:11 [PATCH 2/2] Remove the old implemenations of XLogRead(). Antonin Houska <[email protected]>
2023-08-08 00:27 [PATCH v3 08/10] ci: switch tasks to debugoptimized build Andres Freund <[email protected]>
2023-08-08 00:27 [PATCH v1 4/9] ci: switch tasks to debugoptimized build Andres Freund <[email protected]>
2023-08-08 00:27 [PATCH v3 08/10] ci: switch tasks to debugoptimized build Andres Freund <[email protected]>
2023-08-08 00:27 [PATCH v1 4/9] ci: switch tasks to debugoptimized build Andres Freund <[email protected]>
2023-08-08 00:27 [PATCH v1 4/9] ci: switch tasks to debugoptimized build Andres Freund <[email protected]>
2026-01-23 23:31 [PATCH v4 4/4] Remove specialized word-length popcount implementations. Nathan Bossart <[email protected]>
2026-03-24 15:35 VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-03-25 01:55 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Chao Li <[email protected]>
2026-03-25 09:05 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-03-25 10:32 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-03-25 20:38 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Zsolt Parragi <[email protected]>
2026-03-25 23:00 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-03-26 02:52 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Chao Li <[email protected]>
2026-03-26 11:25 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Antonin Houska <[email protected]>
2026-03-26 11:53 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-04-06 18:13 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-04-06 22:52 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-05-01 19:34 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-05-05 14:32 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Álvaro Herrera <[email protected]>
2026-05-06 06:33 ` Re: VACUUM FULL, CLUSTER, and REPACK block on other sessions' temp tables Jim Jones <[email protected]>
2026-05-02 14:16 Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-05-02 15:37 ` Re: Fix bug with accessing to temporary tables of other sessions Daniil Davydov <[email protected]>
2026-05-02 16:34 ` Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-05-02 17:32 ` Re: Fix bug with accessing to temporary tables of other sessions Jim Jones <[email protected]>
2026-05-03 08:53 ` Re: Fix bug with accessing to temporary tables of other sessions Daniil Davydov <[email protected]>
2026-05-03 12:49 ` Re: Fix bug with accessing to temporary tables of other sessions Jim Jones <[email protected]>
2026-05-04 09:31 ` Re: Fix bug with accessing to temporary tables of other sessions Daniil Davydov <[email protected]>
2026-05-07 08:04 ` Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-05-07 08:43 ` Re: Fix bug with accessing to temporary tables of other sessions Jim Jones <[email protected]>
2026-05-07 10:43 ` Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-05-07 14:22 ` Re: Fix bug with accessing to temporary tables of other sessions Tom Lane <[email protected]>
2026-05-07 16:13 ` Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-05-08 06:19 ` Re: Fix bug with accessing to temporary tables of other sessions Michael Paquier <[email protected]>
2026-05-08 06:39 ` Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-05-14 06:12 ` Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-05-14 06:58 ` Re: Fix bug with accessing to temporary tables of other sessions Michael Paquier <[email protected]>
2026-05-14 13:39 ` Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-05-14 16:48 ` Re: Fix bug with accessing to temporary tables of other sessions Jim Jones <[email protected]>
2026-05-14 16:48 ` Re: Fix bug with accessing to temporary tables of other sessions Daniil Davydov <[email protected]>
2026-06-30 11:46 ` Re: Fix bug with accessing to temporary tables of other sessions Alexander Korotkov <[email protected]>
2026-06-30 12:01 ` Re: Fix bug with accessing to temporary tables of other sessions Daniil Davydov <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox