Nathan Bossart 38b602b028 Move dynamically-allocated LWLock tranche names to shared memory.
There are two ways for shared libraries to allocate their own
LWLock tranches.  One way is to call RequestNamedLWLockTranche() in
a shmem_request_hook, which requires the library to be loaded via
shared_preload_libraries.  The other way is to call
LWLockNewTrancheId(), which is not subject to the same
restrictions.  However, LWLockNewTrancheId() does require each
backend to store the tranche's name in backend-local memory via
LWLockRegisterTranche().  This API is a little cumbersome and leads
to things like unhelpful pg_stat_activity.wait_event values in
backends that haven't loaded the library.

This commit moves these LWLock tranche names to shared memory, thus
eliminating the need for each backend to call
LWLockRegisterTranche().  Instead, the tranche name must be
provided to LWLockNewTrancheId(), which immediately makes the name
available to all backends.  Since the tranche name array is
append-only, lookups can ordinarily avoid locking as long as their
local copy of the LWLock counter is greater than the requested
tranche ID.

One downside of this approach is that we now have a hard limit on
both the length of tranche names (NAMEDATALEN-1 bytes) and the
number of dynamically-allocated tranches (256).  Besides a limit of
NAMEDATALEN-1 bytes for tranche names registered via
RequestNamedLWLockTranche(), no such limits previously existed.  We
could avoid these new limits by using dynamic shared memory, but
the complexity involved didn't seem worth it.  We briefly
considered making the tranche limit user-configurable but
ultimately decided against that, too.  Since there is still a lot
of time left in the v19 development cycle, it's possible we will
revisit this choice.

Author: Sami Imseih <samimseih@gmail.com>
Reviewed-by: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Rahila Syed <rahilasyed90@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/CAA5RZ0vvED3naph8My8Szv6DL4AxOVK3eTPS0qXsaKi%3DbVdW2A%40mail.gmail.com
2025-09-03 13:57:48 -05:00

261 lines
6.4 KiB
C

/*--------------------------------------------------------------------------
*
* test_slru.c
* Test correctness of SLRU functions.
*
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/test/modules/test_slru/test_slru.c
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/slru.h"
#include "access/transam.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/shmem.h"
#include "utils/builtins.h"
PG_MODULE_MAGIC;
/*
* SQL-callable entry points
*/
PG_FUNCTION_INFO_V1(test_slru_page_write);
PG_FUNCTION_INFO_V1(test_slru_page_writeall);
PG_FUNCTION_INFO_V1(test_slru_page_read);
PG_FUNCTION_INFO_V1(test_slru_page_readonly);
PG_FUNCTION_INFO_V1(test_slru_page_exists);
PG_FUNCTION_INFO_V1(test_slru_page_sync);
PG_FUNCTION_INFO_V1(test_slru_page_delete);
PG_FUNCTION_INFO_V1(test_slru_page_truncate);
PG_FUNCTION_INFO_V1(test_slru_delete_all);
/* Number of SLRU page slots */
#define NUM_TEST_BUFFERS 16
static SlruCtlData TestSlruCtlData;
#define TestSlruCtl (&TestSlruCtlData)
static shmem_request_hook_type prev_shmem_request_hook = NULL;
static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
static bool
test_slru_scan_cb(SlruCtl ctl, char *filename, int64 segpage, void *data)
{
elog(NOTICE, "Calling test_slru_scan_cb()");
return SlruScanDirCbDeleteAll(ctl, filename, segpage, data);
}
Datum
test_slru_page_write(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
char *data = text_to_cstring(PG_GETARG_TEXT_PP(1));
int slotno;
LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruZeroPage(TestSlruCtl, pageno);
/* these should match */
Assert(TestSlruCtl->shared->page_number[slotno] == pageno);
/* mark the page as dirty so as it would get written */
TestSlruCtl->shared->page_dirty[slotno] = true;
TestSlruCtl->shared->page_status[slotno] = SLRU_PAGE_VALID;
/* write given data to the page, up to the limit of the page */
strncpy(TestSlruCtl->shared->page_buffer[slotno], data,
BLCKSZ - 1);
SimpleLruWritePage(TestSlruCtl, slotno);
LWLockRelease(lock);
PG_RETURN_VOID();
}
Datum
test_slru_page_writeall(PG_FUNCTION_ARGS)
{
SimpleLruWriteAll(TestSlruCtl, true);
PG_RETURN_VOID();
}
Datum
test_slru_page_read(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
bool write_ok = PG_GETARG_BOOL(1);
char *data = NULL;
int slotno;
LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
LWLockAcquire(lock, LW_EXCLUSIVE);
slotno = SimpleLruReadPage(TestSlruCtl, pageno,
write_ok, InvalidTransactionId);
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
Datum
test_slru_page_readonly(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
char *data = NULL;
int slotno;
LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
/* find page in buffers, reading it if necessary */
slotno = SimpleLruReadPage_ReadOnly(TestSlruCtl,
pageno,
InvalidTransactionId);
Assert(LWLockHeldByMe(lock));
data = (char *) TestSlruCtl->shared->page_buffer[slotno];
LWLockRelease(lock);
PG_RETURN_TEXT_P(cstring_to_text(data));
}
Datum
test_slru_page_exists(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
bool found;
LWLock *lock = SimpleLruGetBankLock(TestSlruCtl, pageno);
LWLockAcquire(lock, LW_EXCLUSIVE);
found = SimpleLruDoesPhysicalPageExist(TestSlruCtl, pageno);
LWLockRelease(lock);
PG_RETURN_BOOL(found);
}
Datum
test_slru_page_sync(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
FileTag ftag;
char path[MAXPGPATH];
/* note that this flushes the full file a segment is located in */
ftag.segno = pageno / SLRU_PAGES_PER_SEGMENT;
SlruSyncFileTag(TestSlruCtl, &ftag, path);
elog(NOTICE, "Called SlruSyncFileTag() for segment %" PRIu64 " on path %s",
ftag.segno, path);
PG_RETURN_VOID();
}
Datum
test_slru_page_delete(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
FileTag ftag;
ftag.segno = pageno / SLRU_PAGES_PER_SEGMENT;
SlruDeleteSegment(TestSlruCtl, ftag.segno);
elog(NOTICE, "Called SlruDeleteSegment() for segment %" PRIu64,
ftag.segno);
PG_RETURN_VOID();
}
Datum
test_slru_page_truncate(PG_FUNCTION_ARGS)
{
int64 pageno = PG_GETARG_INT64(0);
SimpleLruTruncate(TestSlruCtl, pageno);
PG_RETURN_VOID();
}
Datum
test_slru_delete_all(PG_FUNCTION_ARGS)
{
/* this calls SlruScanDirCbDeleteAll() internally, ensuring deletion */
SlruScanDirectory(TestSlruCtl, test_slru_scan_cb, NULL);
PG_RETURN_VOID();
}
/*
* Module load callbacks and initialization.
*/
static void
test_slru_shmem_request(void)
{
if (prev_shmem_request_hook)
prev_shmem_request_hook();
/* reserve shared memory for the test SLRU */
RequestAddinShmemSpace(SimpleLruShmemSize(NUM_TEST_BUFFERS, 0));
}
static bool
test_slru_page_precedes_logically(int64 page1, int64 page2)
{
return page1 < page2;
}
static void
test_slru_shmem_startup(void)
{
/*
* Short segments names are well tested elsewhere so in this test we are
* focusing on long names.
*/
const bool long_segment_names = true;
const char slru_dir_name[] = "pg_test_slru";
int test_tranche_id;
int test_buffer_tranche_id;
if (prev_shmem_startup_hook)
prev_shmem_startup_hook();
/*
* Create the SLRU directory if it does not exist yet, from the root of
* the data directory.
*/
(void) MakePGDirectory(slru_dir_name);
/* initialize the SLRU facility */
test_tranche_id = LWLockNewTrancheId("test_slru_tranche");
test_buffer_tranche_id = LWLockNewTrancheId("test_buffer_tranche");
TestSlruCtl->PagePrecedes = test_slru_page_precedes_logically;
SimpleLruInit(TestSlruCtl, "TestSLRU",
NUM_TEST_BUFFERS, 0, slru_dir_name,
test_buffer_tranche_id, test_tranche_id, SYNC_HANDLER_NONE,
long_segment_names);
}
void
_PG_init(void)
{
if (!process_shared_preload_libraries_in_progress)
ereport(ERROR,
(errmsg("cannot load \"%s\" after startup", "test_slru"),
errdetail("\"%s\" must be loaded with \"shared_preload_libraries\".",
"test_slru")));
prev_shmem_request_hook = shmem_request_hook;
shmem_request_hook = test_slru_shmem_request;
prev_shmem_startup_hook = shmem_startup_hook;
shmem_startup_hook = test_slru_shmem_startup;
}