From 51ba2eadfd2a4f56130d026febb939ecdaa0ea91 Mon Sep 17 00:00:00 2001 From: "Andrey M. Borodin" Date: Sun, 28 Jan 2024 23:10:55 +0500 Subject: [PATCH v2 3/3] Test multixact CV sleep --- src/backend/access/transam/multixact.c | 7 ++ src/backend/utils/misc/injection_point.c | 73 +++++++++++++++++++ src/include/utils/injection_point.h | 6 ++ .../injection_points--1.0.sql | 11 +++ .../injection_points/injection_points.c | 29 ++++++++ .../modules/injection_points/t/001_wait.pl | 43 +++++++++++ 6 files changed, 169 insertions(+) diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 03fcd25d4c..363a8bb1d7 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -89,6 +89,7 @@ #include "storage/proc.h" #include "storage/procarray.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" @@ -822,8 +823,12 @@ MultiXactIdCreateFromMembers(int nmembers, MultiXactMember *members) * in vacuum. During vacuum, in particular, it would be unacceptable to * keep OldestMulti set, in case it runs for long. */ + INJECTION_POINT_PREPARE("GetNewMultiXactId-done"); + multi = GetNewMultiXactId(nmembers, &offset); + INJECTION_POINT_RUN_PREPARED(); + /* Make an XLOG entry describing the new MXID. */ xlrec.mid = multi; xlrec.moff = offset; @@ -1408,6 +1413,8 @@ retry: LWLockRelease(MultiXactOffsetSLRULock); CHECK_FOR_INTERRUPTS(); + INJECTION_POINT("GetMultiXactIdMembers-CV-sleep"); + ConditionVariableSleep(&MultiXactState->nextoff_cv, WAIT_EVENT_NEXT_MXMEMBERS); ConditionVariableCancelSleep(); diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c index 398ef2cf30..c021889138 100644 --- a/src/backend/utils/misc/injection_point.c +++ b/src/backend/utils/misc/injection_point.c @@ -335,3 +335,76 @@ InjectionPointRun(const char *name) elog(ERROR, "Injection points are not supported by this build"); #endif } + +InjectionPointCallback prepared_injection_callback = NULL; +const char* prepared_injection_callback_name; + +void +InjectionPointPrepare(const char *name) +{ +#ifdef USE_INJECTION_POINTS + InjectionPointEntry *entry_by_name; + bool found; + Assert(prepared_injection_callback == NULL); + + LWLockAcquire(InjectionPointLock, LW_SHARED); + entry_by_name = (InjectionPointEntry *) + hash_search(InjectionPointHash, name, + HASH_FIND, &found); + LWLockRelease(InjectionPointLock); + + /* + * If not found, do nothing and remove it from the local cache if it + * existed there. + */ + if (!found) + { + injection_point_cache_remove(name); + return; + } + + /* + * Check if the callback exists in the local cache, to avoid unnecessary + * external loads. + */ + prepared_injection_callback = injection_point_cache_get(name); + if (prepared_injection_callback == NULL) + { + char path[MAXPGPATH]; + + /* not found in local cache, so load and register */ + snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path, + entry_by_name->library, DLSUFFIX); + + if (!pg_file_exists(path)) + elog(ERROR, "could not find library \"%s\" for injection point \"%s\"", + path, name); + + prepared_injection_callback = (InjectionPointCallback) + load_external_function(path, entry_by_name->function, false, NULL); + + if (prepared_injection_callback == NULL) + elog(ERROR, "could not find function \"%s\" in library \"%s\" for injection point \"%s\"", + entry_by_name->function, path, name); + + /* add it to the local cache when found */ + injection_point_cache_add(name, prepared_injection_callback); + } + + prepared_injection_callback_name = name; +#else + elog(ERROR, "Injection points are not supported by this build"); +#endif +} + +void +InjectionPointRunPrepared() +{ +#ifdef USE_INJECTION_POINTS + if (prepared_injection_callback != NULL) + prepared_injection_callback(prepared_injection_callback_name); + prepared_injection_callback = NULL; +#else + elog(ERROR, "Injection points are not supported by this build"); +#endif +} diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h index e07f6b7024..73d3aeac8a 100644 --- a/src/include/utils/injection_point.h +++ b/src/include/utils/injection_point.h @@ -16,8 +16,12 @@ */ #ifdef USE_INJECTION_POINTS #define INJECTION_POINT(name) InjectionPointRun(name) +#define INJECTION_POINT_PREPARE(name) InjectionPointPrepare(name) +#define INJECTION_POINT_RUN_PREPARED() InjectionPointRunPrepared() #else #define INJECTION_POINT(name) ((void) name) +#define INJECTION_POINT_PREPARE(name) ((void) name) +#define INJECTION_POINT_RUN_PREPARED() #endif /* @@ -34,5 +38,7 @@ extern void InjectionPointAttach(const char *name, extern void InjectionPointRun(const char *name); extern void InjectionPointDetach(const char *name); extern bool InjectionPointIsAttach(const char *name); +extern void InjectionPointPrepare(const char *name); +extern void InjectionPointRunPrepared(void); #endif /* INJECTION_POINT_H */ diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql index 5944c41716..d3ebda5964 100644 --- a/src/test/modules/injection_points/injection_points--1.0.sql +++ b/src/test/modules/injection_points/injection_points--1.0.sql @@ -33,3 +33,14 @@ CREATE FUNCTION injection_points_detach(IN point_name TEXT) RETURNS void AS 'MODULE_PATHNAME', 'injection_points_detach' LANGUAGE C STRICT PARALLEL UNSAFE; + + +CREATE FUNCTION create_test_multixact() +RETURNS xid +AS 'MODULE_PATHNAME', 'create_test_multixact' +LANGUAGE C STRICT PARALLEL UNSAFE; + +CREATE FUNCTION read_test_multixact(xid) +RETURNS void +AS 'MODULE_PATHNAME', 'read_test_multixact' +LANGUAGE C STRICT PARALLEL UNSAFE; \ No newline at end of file diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c index fbb30b15ad..a0b9379886 100644 --- a/src/test/modules/injection_points/injection_points.c +++ b/src/test/modules/injection_points/injection_points.c @@ -109,3 +109,32 @@ injection_points_detach(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } + +#include "access/multixact.h" +#include "access/xact.h" + +PG_FUNCTION_INFO_V1(create_test_multixact); +Datum +create_test_multixact(PG_FUNCTION_ARGS) +{ + MultiXactId id; + MultiXactIdSetOldestMember(); + id = MultiXactIdCreate(GetCurrentTransactionId(), MultiXactStatusUpdate, + GetCurrentTransactionId(), MultiXactStatusForShare); + PG_RETURN_TRANSACTIONID(id); +} + +PG_FUNCTION_INFO_V1(read_test_multixact); +Datum +read_test_multixact(PG_FUNCTION_ARGS) +{ + MultiXactId id = PG_GETARG_TRANSACTIONID(0); + MultiXactMember *members; + INJECTION_POINT("read_test_multixact"); + /* discard caches */ + AtEOXact_MultiXact(); + + if (GetMultiXactIdMembers(id,&members,false, false) == -1) + elog(ERROR, "MultiXactId not found"); + PG_RETURN_VOID(); +} \ No newline at end of file diff --git a/src/test/modules/injection_points/t/001_wait.pl b/src/test/modules/injection_points/t/001_wait.pl index 98f7a8bcef..448dc97759 100644 --- a/src/test/modules/injection_points/t/001_wait.pl +++ b/src/test/modules/injection_points/t/001_wait.pl @@ -36,5 +36,48 @@ is($result, '0', 'wait injection point set'); $bg->quit; +# Test for Multixact generation edge case +$node->safe_psql('postgres', q(select injection_points_attach('read_test_multixact','wait'))); +$node->safe_psql('postgres', q(select injection_points_attach('GetMultiXactIdMembers-CV-sleep','notice'))); + +# This session must observe sleep on CV when generating multixact. +# To achive this it first will create a multixact, then pause before reading it. +my $observer = $node->background_psql('postgres'); + +$observer->query_until(qr/start/, +q( + \echo start + select read_test_multixact(create_test_multixact()); +)); + +# This session will create next Multixact, it's necessary to avoid edge case 1 (see multixact.c) +my $creator = $node->background_psql('postgres'); +$node->safe_psql('postgres', q(select injection_points_attach('GetNewMultiXactId-done','wait'))); + +# We expect this query to hand in critical section after generating new multixact, +# but before filling it's offset into SLRU +$creator->query_until(qr/start/, q( + \echo start + select create_test_multixact(); +)); + +# Now we are sure we can reach edge case 2. Proceed session that is reading that multixact. +$node->safe_psql('postgres', q(select injection_points_detach('read_test_multixact'))); + +# Release critical section. We have to do this so everyon can proceed. +# But this is inherent race condition, I hope the tast will not be unstable here. +# The only way to stabilize it will be adding some sleep here. +$node->safe_psql('postgres', q(select injection_points_detach('GetNewMultiXactId-done'))); + +# Here goes the whole purpose of this test: see that sleep in fact occured. +ok( pump_until( + $observer->{run}, $observer->{timeout}, + \$observer->{stderr}, qr/notice triggered for injection point GetMultiXactIdMembers-CV-sleep/), + "sleep observed"); + +$observer->quit; + +$creator->quit; + $node->stop; done_testing(); -- 2.37.1 (Apple Git-137.1)