diff --git a/contrib/pg_buffercache/Makefile b/contrib/pg_buffercache/Makefile
index 5f748543e2ea..0e618f66aec6 100644
--- a/contrib/pg_buffercache/Makefile
+++ b/contrib/pg_buffercache/Makefile
@@ -9,7 +9,7 @@ EXTENSION = pg_buffercache
DATA = pg_buffercache--1.2.sql pg_buffercache--1.2--1.3.sql \
pg_buffercache--1.1--1.2.sql pg_buffercache--1.0--1.1.sql \
pg_buffercache--1.3--1.4.sql pg_buffercache--1.4--1.5.sql \
- pg_buffercache--1.5--1.6.sql
+ pg_buffercache--1.5--1.6.sql pg_buffercache--1.6--1.7.sql
PGFILEDESC = "pg_buffercache - monitoring of shared buffer cache in real-time"
REGRESS = pg_buffercache pg_buffercache_numa
diff --git a/contrib/pg_buffercache/expected/pg_buffercache.out b/contrib/pg_buffercache/expected/pg_buffercache.out
index 9a9216dc7b1b..6b57dff2b612 100644
--- a/contrib/pg_buffercache/expected/pg_buffercache.out
+++ b/contrib/pg_buffercache/expected/pg_buffercache.out
@@ -57,7 +57,7 @@ SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
RESET role;
------
----- Test pg_buffercache_evict* functions
+---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
------
CREATE ROLE regress_buffercache_normal;
SET ROLE regress_buffercache_normal;
@@ -68,6 +68,12 @@ SELECT * FROM pg_buffercache_evict_relation(1);
ERROR: must be superuser to use pg_buffercache_evict_relation()
SELECT * FROM pg_buffercache_evict_all();
ERROR: must be superuser to use pg_buffercache_evict_all()
+SELECT * FROM pg_buffercache_mark_dirty(1);
+ERROR: must be superuser to use pg_buffercache_mark_dirty()
+SELECT * FROM pg_buffercache_mark_dirty_relation(1);
+ERROR: must be superuser to use pg_buffercache_mark_dirty_relation()
+SELECT * FROM pg_buffercache_mark_dirty_all();
+ERROR: must be superuser to use pg_buffercache_mark_dirty_all()
RESET ROLE;
-- These should return nothing, because these are STRICT functions
SELECT * FROM pg_buffercache_evict(NULL);
@@ -82,6 +88,18 @@ SELECT * FROM pg_buffercache_evict_relation(NULL);
| |
(1 row)
+SELECT * FROM pg_buffercache_mark_dirty(NULL);
+ buffer_dirtied | buffer_already_dirty
+----------------+----------------------
+ |
+(1 row)
+
+SELECT * FROM pg_buffercache_mark_dirty_relation(NULL);
+ buffers_dirtied | buffers_already_dirty | buffers_skipped
+-----------------+-----------------------+-----------------
+ | |
+(1 row)
+
-- These should fail because they are not called by valid range of buffers
-- Number of the shared buffers are limited by max integer
SELECT 2147483647 max_buffers \gset
@@ -91,11 +109,18 @@ SELECT * FROM pg_buffercache_evict(0);
ERROR: bad buffer ID: 0
SELECT * FROM pg_buffercache_evict(:max_buffers);
ERROR: bad buffer ID: 2147483647
--- This should fail because pg_buffercache_evict_relation() doesn't accept
--- local relations
+SELECT * FROM pg_buffercache_mark_dirty(-1);
+ERROR: bad buffer ID: -1
+SELECT * FROM pg_buffercache_mark_dirty(0);
+ERROR: bad buffer ID: 0
+SELECT * FROM pg_buffercache_mark_dirty(:max_buffers);
+ERROR: bad buffer ID: 2147483647
+-- These should fail because they don't accept local relations
CREATE TEMP TABLE temp_pg_buffercache();
SELECT * FROM pg_buffercache_evict_relation('temp_pg_buffercache');
ERROR: relation uses local buffers, pg_buffercache_evict_relation() is intended to be used for shared buffers only
+SELECT * FROM pg_buffercache_mark_dirty_relation('temp_pg_buffercache');
+ERROR: relation uses local buffers, pg_buffercache_mark_dirty_relation() is intended to be used for shared buffers only
DROP TABLE temp_pg_buffercache;
-- These shouldn't fail
SELECT buffer_evicted IS NOT NULL FROM pg_buffercache_evict(1);
@@ -117,5 +142,23 @@ SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_relation('shared_pg
t
(1 row)
+SELECT buffers_dirtied IS NOT NULL FROM pg_buffercache_mark_dirty_relation('shared_pg_buffercache');
+ ?column?
+----------
+ t
+(1 row)
+
DROP TABLE shared_pg_buffercache;
+SELECT pg_buffercache_mark_dirty(1) IS NOT NULL;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT pg_buffercache_mark_dirty_all() IS NOT NULL;
+ ?column?
+----------
+ t
+(1 row)
+
DROP ROLE regress_buffercache_normal;
diff --git a/contrib/pg_buffercache/meson.build b/contrib/pg_buffercache/meson.build
index 7cd039a1df9c..7c31141881f6 100644
--- a/contrib/pg_buffercache/meson.build
+++ b/contrib/pg_buffercache/meson.build
@@ -24,6 +24,7 @@ install_data(
'pg_buffercache--1.3--1.4.sql',
'pg_buffercache--1.4--1.5.sql',
'pg_buffercache--1.5--1.6.sql',
+ 'pg_buffercache--1.6--1.7.sql',
'pg_buffercache.control',
kwargs: contrib_data_args,
)
diff --git a/contrib/pg_buffercache/pg_buffercache--1.6--1.7.sql b/contrib/pg_buffercache/pg_buffercache--1.6--1.7.sql
new file mode 100644
index 000000000000..10fce1ba1699
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache--1.6--1.7.sql
@@ -0,0 +1,26 @@
+/* contrib/pg_buffercache/pg_buffercache--1.6--1.7.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.7'" to load this file. \quit
+
+CREATE FUNCTION pg_buffercache_mark_dirty(
+ IN int,
+ OUT buffer_dirtied boolean,
+ OUT buffer_already_dirty boolean)
+AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty'
+LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
+
+CREATE FUNCTION pg_buffercache_mark_dirty_relation(
+ IN regclass,
+ OUT buffers_dirtied int4,
+ OUT buffers_already_dirty int4,
+ OUT buffers_skipped int4)
+AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty_relation'
+LANGUAGE C PARALLEL SAFE VOLATILE STRICT;
+
+CREATE FUNCTION pg_buffercache_mark_dirty_all(
+ OUT buffers_dirtied int4,
+ OUT buffers_already_dirty int4,
+ OUT buffers_skipped int4)
+AS 'MODULE_PATHNAME', 'pg_buffercache_mark_dirty_all'
+LANGUAGE C PARALLEL SAFE VOLATILE;
diff --git a/contrib/pg_buffercache/pg_buffercache.control b/contrib/pg_buffercache/pg_buffercache.control
index b030ba3a6fab..11499550945e 100644
--- a/contrib/pg_buffercache/pg_buffercache.control
+++ b/contrib/pg_buffercache/pg_buffercache.control
@@ -1,5 +1,5 @@
# pg_buffercache extension
comment = 'examine the shared buffer cache'
-default_version = '1.6'
+default_version = '1.7'
module_pathname = '$libdir/pg_buffercache'
relocatable = true
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 3df04c98959e..63e17a8f1d5a 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -25,6 +25,9 @@
#define NUM_BUFFERCACHE_EVICT_ELEM 2
#define NUM_BUFFERCACHE_EVICT_RELATION_ELEM 3
#define NUM_BUFFERCACHE_EVICT_ALL_ELEM 3
+#define NUM_BUFFERCACHE_MARK_DIRTY_ELEM 2
+#define NUM_BUFFERCACHE_MARK_DIRTY_RELATION_ELEM 3
+#define NUM_BUFFERCACHE_MARK_DIRTY_ALL_ELEM 3
#define NUM_BUFFERCACHE_NUMA_ELEM 3
@@ -100,6 +103,9 @@ PG_FUNCTION_INFO_V1(pg_buffercache_usage_counts);
PG_FUNCTION_INFO_V1(pg_buffercache_evict);
PG_FUNCTION_INFO_V1(pg_buffercache_evict_relation);
PG_FUNCTION_INFO_V1(pg_buffercache_evict_all);
+PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty);
+PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty_relation);
+PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty_all);
/* Only need to touch memory once per backend process lifetime */
@@ -777,3 +783,120 @@ pg_buffercache_evict_all(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(result);
}
+
+/*
+ * Try to mark a shared buffer as dirty.
+ */
+Datum
+pg_buffercache_mark_dirty(PG_FUNCTION_ARGS)
+{
+
+ Datum result;
+ TupleDesc tupledesc;
+ HeapTuple tuple;
+ Datum values[NUM_BUFFERCACHE_MARK_DIRTY_ELEM];
+ bool nulls[NUM_BUFFERCACHE_MARK_DIRTY_ELEM] = {0};
+
+ Buffer buf = PG_GETARG_INT32(0);
+ bool buffer_already_dirty;
+
+ if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ pg_buffercache_superuser_check("pg_buffercache_mark_dirty");
+
+ if (buf < 1 || buf > NBuffers)
+ elog(ERROR, "bad buffer ID: %d", buf);
+
+
+ values[0] = BoolGetDatum(MarkDirtyUnpinnedBuffer(buf, &buffer_already_dirty));
+ values[1] = BoolGetDatum(buffer_already_dirty);
+
+ tuple = heap_form_tuple(tupledesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/*
+ * Try to mark specified relation dirty.
+ */
+Datum
+pg_buffercache_mark_dirty_relation(PG_FUNCTION_ARGS)
+{
+ Datum result;
+ TupleDesc tupledesc;
+ HeapTuple tuple;
+ Datum values[NUM_BUFFERCACHE_MARK_DIRTY_RELATION_ELEM];
+ bool nulls[NUM_BUFFERCACHE_MARK_DIRTY_RELATION_ELEM] = {0};
+
+ Oid relOid;
+ Relation rel;
+
+ int32 buffers_already_dirty = 0;
+ int32 buffers_dirtied = 0;
+ int32 buffers_skipped = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ pg_buffercache_superuser_check("pg_buffercache_mark_dirty_relation");
+
+ relOid = PG_GETARG_OID(0);
+
+ rel = relation_open(relOid, AccessShareLock);
+
+ if (RelationUsesLocalBuffers(rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relation uses local buffers, %s() is intended to be used for shared buffers only",
+ "pg_buffercache_mark_dirty_relation")));
+
+ MarkDirtyRelUnpinnedBuffers(rel, &buffers_dirtied, &buffers_already_dirty,
+ &buffers_skipped);
+
+ relation_close(rel, AccessShareLock);
+
+ values[0] = Int32GetDatum(buffers_dirtied);
+ values[1] = Int32GetDatum(buffers_already_dirty);
+ values[2] = Int32GetDatum(buffers_skipped);
+
+ tuple = heap_form_tuple(tupledesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+/*
+ * Try to mark all the shared buffers as dirty.
+ */
+Datum
+pg_buffercache_mark_dirty_all(PG_FUNCTION_ARGS)
+{
+ Datum result;
+ TupleDesc tupledesc;
+ HeapTuple tuple;
+ Datum values[NUM_BUFFERCACHE_MARK_DIRTY_ALL_ELEM];
+ bool nulls[NUM_BUFFERCACHE_MARK_DIRTY_ALL_ELEM] = {0};
+
+ int32 buffers_already_dirty = 0;
+ int32 buffers_dirtied = 0;
+ int32 buffers_skipped = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ pg_buffercache_superuser_check("pg_buffercache_mark_dirty_all");
+
+ MarkDirtyAllUnpinnedBuffers(&buffers_dirtied, &buffers_already_dirty,
+ &buffers_skipped);
+
+ values[0] = Int32GetDatum(buffers_dirtied);
+ values[1] = Int32GetDatum(buffers_already_dirty);
+ values[2] = Int32GetDatum(buffers_skipped);
+
+ tuple = heap_form_tuple(tupledesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
diff --git a/contrib/pg_buffercache/sql/pg_buffercache.sql b/contrib/pg_buffercache/sql/pg_buffercache.sql
index 47cca1907c74..1aa3caecd6f3 100644
--- a/contrib/pg_buffercache/sql/pg_buffercache.sql
+++ b/contrib/pg_buffercache/sql/pg_buffercache.sql
@@ -30,7 +30,7 @@ RESET role;
------
----- Test pg_buffercache_evict* functions
+---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
------
CREATE ROLE regress_buffercache_normal;
@@ -40,12 +40,17 @@ SET ROLE regress_buffercache_normal;
SELECT * FROM pg_buffercache_evict(1);
SELECT * FROM pg_buffercache_evict_relation(1);
SELECT * FROM pg_buffercache_evict_all();
+SELECT * FROM pg_buffercache_mark_dirty(1);
+SELECT * FROM pg_buffercache_mark_dirty_relation(1);
+SELECT * FROM pg_buffercache_mark_dirty_all();
RESET ROLE;
-- These should return nothing, because these are STRICT functions
SELECT * FROM pg_buffercache_evict(NULL);
SELECT * FROM pg_buffercache_evict_relation(NULL);
+SELECT * FROM pg_buffercache_mark_dirty(NULL);
+SELECT * FROM pg_buffercache_mark_dirty_relation(NULL);
-- These should fail because they are not called by valid range of buffers
-- Number of the shared buffers are limited by max integer
@@ -53,11 +58,14 @@ SELECT 2147483647 max_buffers \gset
SELECT * FROM pg_buffercache_evict(-1);
SELECT * FROM pg_buffercache_evict(0);
SELECT * FROM pg_buffercache_evict(:max_buffers);
+SELECT * FROM pg_buffercache_mark_dirty(-1);
+SELECT * FROM pg_buffercache_mark_dirty(0);
+SELECT * FROM pg_buffercache_mark_dirty(:max_buffers);
--- This should fail because pg_buffercache_evict_relation() doesn't accept
--- local relations
+-- These should fail because they don't accept local relations
CREATE TEMP TABLE temp_pg_buffercache();
SELECT * FROM pg_buffercache_evict_relation('temp_pg_buffercache');
+SELECT * FROM pg_buffercache_mark_dirty_relation('temp_pg_buffercache');
DROP TABLE temp_pg_buffercache;
-- These shouldn't fail
@@ -65,6 +73,9 @@ SELECT buffer_evicted IS NOT NULL FROM pg_buffercache_evict(1);
SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_all();
CREATE TABLE shared_pg_buffercache();
SELECT buffers_evicted IS NOT NULL FROM pg_buffercache_evict_relation('shared_pg_buffercache');
+SELECT buffers_dirtied IS NOT NULL FROM pg_buffercache_mark_dirty_relation('shared_pg_buffercache');
DROP TABLE shared_pg_buffercache;
+SELECT pg_buffercache_mark_dirty(1) IS NOT NULL;
+SELECT pg_buffercache_mark_dirty_all() IS NOT NULL;
DROP ROLE regress_buffercache_normal;
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index eeb85a0e0490..719cb561fa48 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -43,6 +43,18 @@
pg_buffercache_evict_all
+
+ pg_buffercache_mark_dirty
+
+
+
+ pg_buffercache_mark_dirty_relation
+
+
+
+ pg_buffercache_mark_dirty_all
+
+
This module provides the pg_buffercache_pages()
function (wrapped in the pg_buffercache view), the
@@ -51,8 +63,11 @@
pg_buffercache_summary() function, the
pg_buffercache_usage_counts() function, the
pg_buffercache_evict() function, the
- pg_buffercache_evict_relation() function and the
- pg_buffercache_evict_all() function.
+ pg_buffercache_evict_relation() function, the
+ pg_buffercache_evict_all() function, the
+ pg_buffercache_mark_dirty() function, the
+ pg_buffercache_mark_dirty_relation() function and the
+ pg_buffercache_mark_dirty_all() function.
@@ -107,6 +122,25 @@
function is restricted to superusers only.
+
+ The pg_buffercache_mark_dirty() function allows a block
+ to be marked as dirty in the buffer pool given a buffer identifier. Use of
+ this function is restricted to superusers only.
+
+
+
+ The pg_buffercache_mark_dirty_relation() function
+ allows all unpinned shared buffers in the relation to be marked as dirty in
+ the buffer pool given a relation identifier. Use of this function is
+ restricted to superusers only.
+
+
+
+ The pg_buffercache_mark_dirty_all() function allows all
+ unpinned shared buffers to be marked as dirty in the buffer pool. Use of
+ this function is restricted to superusers only.
+
+
The pg_buffercache View
@@ -530,6 +564,59 @@
+
+ The pg_buffercache_mark_dirty Function
+
+ The pg_buffercache_mark_dirty() function takes a
+ buffer identifier, as shown in the bufferid
+ column of the pg_buffercache view. It returns
+ information about whether the buffer was marked as dirty. The
+ buffer_dirtied column is true on success, and false if the buffer was
+ already dirty, if the buffer wasn't valid, if it couldn't be marked as
+ dirty because it was pinned. The buffer_already_dirty column is true if
+ the buffer couldn't be marked as dirty because it was already dirty. The
+ result is immediately out of date upon return, as the buffer might become
+ valid again at any time due to concurrent activity. The function is
+ intended for developer testing only.
+
+
+
+
+ The pg_buffercache_mark_dirty_relation Function
+
+ The pg_buffercache_mark_dirty_relation() function is
+ very similar to the
+ pg_buffercache_mark_dirty_relation() function. The
+ difference is, the
+ pg_buffercache_mark_dirty_relation() function takes a
+ relation identifier instead of buffer identifier. It tries to mark all
+ buffers dirty for all forks in that relation.
+
+ It returns the number of marked as dirty buffers, already dirty buffers and
+ the skipped buffers for reasons other than buffers being already dirty.
+ The result is immediately out of date upon return, as the buffer might
+ become valid again at any time due to concurrent activity. The function is
+ intended for developer testing only.
+
+
+
+
+ The pg_buffercache_mark_dirty_all Function
+
+ The pg_buffercache_mark_dirty_all() function is very
+ similar to the pg_buffercache_mark_dirty() function.
+ The difference is, the pg_buffercache_mark_dirty_all()
+ function does not take an argument; instead it tries to mark all buffers
+ dirty in the buffer pool.
+
+ It returns the number of marked as dirty buffers, already dirty buffers and
+ the skipped buffers for reasons other than buffers being already dirty.
+ The result is immediately out of date upon return, as the buffer might
+ become valid again at any time due to concurrent activity. The function is
+ intended for developer testing only.
+
+
+
Sample Output
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index e8544acb7841..c8af92df248a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -6764,6 +6764,194 @@ EvictRelUnpinnedBuffers(Relation rel, int32 *buffers_evicted,
}
}
+/*
+ * Helper function to mark unpinned buffer dirty whose buffer header lock is
+ * already acquired.
+ */
+static bool
+MarkDirtyUnpinnedBufferInternal(Buffer buf, BufferDesc *desc,
+ bool *buffer_already_dirty)
+{
+ uint32 buf_state;
+ bool result = false;
+
+ *buffer_already_dirty = false;
+
+ buf_state = pg_atomic_read_u32(&(desc->state));
+ Assert(buf_state & BM_LOCKED);
+
+ if ((buf_state & BM_VALID) == 0)
+ {
+ UnlockBufHdr(desc, buf_state);
+ return false;
+ }
+
+ /* Check that it's not pinned already. */
+ if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
+ {
+ UnlockBufHdr(desc, buf_state);
+ return false;
+ }
+
+ /* Pin the buffer and then release the buffer spinlock */
+ PinBuffer_Locked(desc);
+
+ /* If it was not already dirty, mark it as dirty. */
+ if (!(buf_state & BM_DIRTY))
+ {
+ LWLockAcquire(BufferDescriptorGetContentLock(desc), LW_EXCLUSIVE);
+ MarkBufferDirty(buf);
+ result = true;
+ LWLockRelease(BufferDescriptorGetContentLock(desc));
+ }
+ else
+ *buffer_already_dirty = true;
+
+ UnpinBuffer(desc);
+
+ return result;
+}
+
+/*
+ * Try to mark the provided shared buffer as dirty.
+ *
+ * This function is intended for testing/development use only!
+ *
+ * Same as EvictUnpinnedBuffer() but with MarkBufferDirty() call inside.
+ *
+ * The buffer_already_dirty parameter is mandatory and indicate if the buffer
+ * could not be dirtied because it is already dirty.
+ *
+ * Returns true if the buffer has successfully been marked as dirty.
+ */
+bool
+MarkDirtyUnpinnedBuffer(Buffer buf, bool *buffer_already_dirty)
+{
+ BufferDesc *desc;
+ bool buffer_dirtied = false;
+
+ Assert(!BufferIsLocal(buf));
+
+ /* Make sure we can pin the buffer. */
+ ResourceOwnerEnlarge(CurrentResourceOwner);
+ ReservePrivateRefCountEntry();
+
+ desc = GetBufferDescriptor(buf - 1);
+ LockBufHdr(desc);
+
+ buffer_dirtied = MarkDirtyUnpinnedBufferInternal(buf, desc, buffer_already_dirty);
+ /* Both can not be true at the same time */
+ Assert(!(buffer_dirtied && *buffer_already_dirty));
+
+ return buffer_dirtied;
+}
+
+/*
+ * Try to mark all the shared buffers containing provided relation's pages as
+ * dirty.
+ *
+ * This function is intended for testing/development use only! See
+ * MarkDirtyUnpinnedBuffer().
+ *
+ * The buffers_* parameters are mandatory and indicate the total count of
+ * buffers that:
+ * - buffers_dirtied - were dirtied
+ * - buffers_already_dirty - were already dirty
+ * - buffers_skipped - could not be dirtied because of the reasons different
+ * than buffer being already dirty
+ */
+void
+MarkDirtyRelUnpinnedBuffers(Relation rel,
+ int32 *buffers_dirtied,
+ int32 *buffers_already_dirty,
+ int32 *buffers_skipped)
+{
+ Assert(!RelationUsesLocalBuffers(rel));
+
+ *buffers_dirtied = 0;
+ *buffers_already_dirty = 0;
+ *buffers_skipped = 0;
+
+ for (int buf = 1; buf <= NBuffers; buf++)
+ {
+ BufferDesc *desc = GetBufferDescriptor(buf - 1);
+ uint32 buf_state = pg_atomic_read_u32(&(desc->state));
+ bool buffer_already_dirty;
+
+ /* An unlocked precheck should be safe and saves some cycles. */
+ if ((buf_state & BM_VALID) == 0 ||
+ !BufTagMatchesRelFileLocator(&desc->tag, &rel->rd_locator))
+ continue;
+
+ /* Make sure we can pin the buffer. */
+ ResourceOwnerEnlarge(CurrentResourceOwner);
+ ReservePrivateRefCountEntry();
+
+ buf_state = LockBufHdr(desc);
+
+ /* recheck, could have changed without the lock */
+ if ((buf_state & BM_VALID) == 0 ||
+ !BufTagMatchesRelFileLocator(&desc->tag, &rel->rd_locator))
+ {
+ UnlockBufHdr(desc, buf_state);
+ continue;
+ }
+
+ if (MarkDirtyUnpinnedBufferInternal(buf, desc, &buffer_already_dirty))
+ (*buffers_dirtied)++;
+ else if (buffer_already_dirty)
+ (*buffers_already_dirty)++;
+ else
+ (*buffers_skipped)++;
+ }
+}
+
+/*
+ * Try to mark all the shared buffers as dirty.
+ *
+ * This function is intended for testing/development use only! See
+ * MarkDirtyUnpinnedBuffer().
+ *
+ * The buffers_* parameters are mandatory and indicate the total count of
+ * buffers that:
+ * - buffers_dirtied - were dirtied
+ * - buffers_already_dirty - were already dirty
+ * - buffers_skipped - could not be dirtied because of the reasons different
+ * than buffer being already dirty
+ */
+void
+MarkDirtyAllUnpinnedBuffers(int32 *buffers_dirtied,
+ int32 *buffers_already_dirty,
+ int32 *buffers_skipped)
+{
+ *buffers_dirtied = 0;
+ *buffers_already_dirty = 0;
+ *buffers_skipped = 0;
+
+ for (int buf = 1; buf <= NBuffers; buf++)
+ {
+ BufferDesc *desc = GetBufferDescriptor(buf - 1);
+ uint32 buf_state;
+ bool buffer_already_dirty;
+
+ buf_state = pg_atomic_read_u32(&desc->state);
+ if (!(buf_state & BM_VALID))
+ continue;
+
+ ResourceOwnerEnlarge(CurrentResourceOwner);
+ ReservePrivateRefCountEntry();
+
+ LockBufHdr(desc);
+
+ if (MarkDirtyUnpinnedBufferInternal(buf, desc, &buffer_already_dirty))
+ (*buffers_dirtied)++;
+ else if (buffer_already_dirty)
+ (*buffers_already_dirty)++;
+ else
+ (*buffers_skipped)++;
+ }
+}
+
/*
* Generic implementation of the AIO handle staging callback for readv/writev
* on local/shared buffers.
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b5f8f3c5d42f..9f6785910e01 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -323,6 +323,14 @@ extern void EvictRelUnpinnedBuffers(Relation rel,
int32 *buffers_evicted,
int32 *buffers_flushed,
int32 *buffers_skipped);
+extern bool MarkDirtyUnpinnedBuffer(Buffer buf, bool *buffer_already_dirty);
+extern void MarkDirtyRelUnpinnedBuffers(Relation rel,
+ int32 *buffers_dirtied,
+ int32 *buffers_already_dirty,
+ int32 *buffers_skipped);
+extern void MarkDirtyAllUnpinnedBuffers(int32 *buffers_dirtied,
+ int32 *buffers_already_dirty,
+ int32 *buffers_skipped);
/* in buf_init.c */
extern void BufferManagerShmemInit(void);