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 <structname>pg_buffercache</structname> View @@ -530,6 +564,59 @@ + + The <structname>pg_buffercache_mark_dirty</structname> 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 <structname>pg_buffercache_mark_dirty_relation</structname> 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 <structname>pg_buffercache_mark_dirty_all</structname> 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);