Conceptually, a relation fork organized as a conveyor belt has three parts:
- Payload. The payload is whatever data the user of this module wishes
- to store. The conveyor belt system avoids making any assumptions about how
- payload pages are used, with one exception: it's expected that after
- a payload page is initialized, PageIsNew() will return false. Apart from
- that, this module doesn't know or care about their contents.
+ to store. The conveyor belt doesn't care what you store in a payload page,
+ but it does require that you store something: each time a payload page is
+ initialized, it must end up with either pd_lower > SizeOfPageHeaderData,
+ or pd_lower < BLCKSZ.
- Index. The index translates logical page numbers to physical block
numbers. The intention is that pages might be physically relocated - e.g.
void
cb_insert_payload_page(RelFileNode *rnode, ForkNumber fork, Buffer metabuffer,
BlockNumber payloadblock, Buffer payloadbuffer,
- bool needs_xlog, bool page_std)
+ bool needs_xlog)
{
Page metapage;
Page payloadpage;
if (needs_xlog)
{
XLogRecPtr lsn;
- int flags = REGBUF_FORCE_IMAGE;
-
- if (page_std)
- flags |= REGBUF_STANDARD;
XLogBeginInsert();
XLogRegisterBlock(0, rnode, fork, CONVEYOR_METAPAGE, metapage,
REGBUF_STANDARD);
XLogRegisterBlock(1, rnode, fork, payloadblock,
- payloadpage, flags);
+ payloadpage, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
lsn = XLogInsert(RM_CONVEYOR_ID,
XLOG_CONVEYOR_INSERT_PAYLOAD_PAGE);
static Buffer ConveyorBeltExtend(ConveyorBelt *cb, BlockNumber blkno,
BlockNumber *possibly_not_on_disk_blkno);
static Buffer ConveyorBeltRead(ConveyorBelt *cb, BlockNumber blkno, int mode);
+static Buffer ConveyorBeltPageIsUnused(Page page);
/*
* Handle used to mediate access to a conveyor belt.
* page = BufferGetPage(buffer);
* START_CRIT_SECTION();
* // set page contents
- * ConveyorBeltPerformInsert(cb, buffer, page_std);
+ * ConveyorBeltPerformInsert(cb, buffer);
* END_CRIT_SECTION();
* ConveyorBeltCleanupInsert(cb, buffer);
*
* so could result in undetected deadlock on the buffer LWLocks, or cause
* a relcache flush that would break ConveyorBeltPerformInsert().
*
+ * Also note that the "set page contents" step must put some data in the
+ * page, so that either pd_lower is greater than the minimum value
+ * (SizeOfPageHeaderData) or pd_upper is less than the maximum value
+ * (BLCKSZ).
+ *
* In future, we might want to provide the caller with an alternative to
* calling ConveyorBeltPerformInsert, because that just logs an FPI for
* the new page, and some callers might prefer to manage their own xlog
ConveyorBeltRead(cb, next_blkno, BUFFER_LOCK_EXCLUSIVE);
/*
- * If the target buffer is still new, we're done. Otherwise,
+ * If the target buffer is still unused, we're done. Otherwise,
* someone else grabbed that page before we did, so we must fall
* through and retry.
*/
- if (PageIsNew(BufferGetPage(buffer)))
+ if (ConveyorBeltPageIsUnused(BufferGetPage(buffer)))
{
/*
* Remember things that we'll need to know when the caller
* See ConveyorBeltGetNewPage for the intended usage of this fucntion.
*/
void
-ConveyorBeltPerformInsert(ConveyorBelt *cb, Buffer buffer, bool page_std)
+ConveyorBeltPerformInsert(ConveyorBelt *cb, Buffer buffer)
{
bool needs_xlog;
cb->cb_insert_buffer, buffer);
}
+ /*
+ * ConveyorBeltPageIsUnused is used by ConveyorBeltGetNewPage to figure
+ * out whether a concurrent inserter got there first. Here, we're the
+ * concurrent inserter, and must have initialized the page in a way that
+ * makes that function return false for the newly-inserted page, so that
+ * other backends can tell we got here first.
+ */
+ if (ConveyorBeltPageIsUnused(BufferGetPage(buffer)))
+ elog(ERROR, "can't insert an unused page");
+
/* Caller should be doing this inside a critical section. */
Assert(CritSectionCount > 0);
cb_insert_payload_page(cb->cb_insert_relfilenode, cb->cb_fork,
cb->cb_insert_metabuffer,
cb->cb_insert_block, buffer,
- needs_xlog, page_std);
+ needs_xlog);
/*
* Buffer locks will be released by ConveyorBeltCleanupInsert, but we can
return buffer;
}
+/*
+ * We consider a page unused if it's either new (i.e. all zeroes) or if
+ * neither pd_lower nor pd_upper have moved.
+ */
+static Buffer
+ConveyorBeltPageIsUnused(Page page)
+{
+ PageHeader ph = (PageHeader) page;
+
+ if (PageIsNew(page))
+ return true;
+
+ return (ph->pd_lower <= SizeOfPageHeaderData && ph->pd_upper == BLCKSZ);
+}
+
/*
* Find a free segment by searching all of the FSM pages that currently exist,
* and if that doesn't turn up anything, adding a new FSM page.