Skip to content

Commit 4fbce73

Browse files
committed
Add multixact CV sleep test
Previously we used 1ms sleep when we are reading multixact before another multixact without offset yet. a0e0fb1 changed this behavior to CV sleep. This commit adds test for this edge case. To test such race condition we have to use waiting injection point under critical section. Such point requires special injection point loading. Author: Andrey Borodin Reviewed-by: Michael Paquier dummy change 4
1 parent bd06cc3 commit 4fbce73

File tree

6 files changed

+153
-0
lines changed

6 files changed

+153
-0
lines changed

src/backend/access/transam/multixact.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
#include "storage/proc.h"
8989
#include "storage/procarray.h"
9090
#include "utils/fmgrprotos.h"
91+
#include "utils/injection_point.h"
9192
#include "utils/guc_hooks.h"
9293
#include "utils/memutils.h"
9394

@@ -868,6 +869,8 @@ MultiXactIdCreateFromMembers(int nmembers, MultiXactMember *members)
868869
*/
869870
multi = GetNewMultiXactId(nmembers, &offset);
870871

872+
INJECTION_POINT("get-new-multixact-id");
873+
871874
/* Make an XLOG entry describing the new MXID. */
872875
xlrec.mid = multi;
873876
xlrec.moff = offset;
@@ -1480,6 +1483,8 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
14801483
LWLockRelease(lock);
14811484
CHECK_FOR_INTERRUPTS();
14821485

1486+
INJECTION_POINT("get-multixact-member-cv-sleep");
1487+
14831488
ConditionVariableSleep(&MultiXactState->nextoff_cv,
14841489
WAIT_EVENT_MULTIXACT_CREATION);
14851490
slept = true;

src/test/modules/test_slru/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ OBJS = \
66
test_slru.o
77
PGFILEDESC = "test_slru - test module for SLRUs"
88

9+
EXTRA_INSTALL=src/test/modules/injection_points
10+
export enable_injection_points enable_injection_points
11+
TAP_TESTS = 1
12+
913
EXTENSION = test_slru
1014
DATA = test_slru--1.0.sql
1115

src/test/modules/test_slru/meson.build

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,12 @@ tests += {
3232
'regress_args': ['--temp-config', files('test_slru.conf')],
3333
'runningcheck': false,
3434
},
35+
'tap': {
36+
'env': {
37+
'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
38+
},
39+
'tests': [
40+
't/001_multixact.pl'
41+
],
42+
},
3543
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright (c) 2024, PostgreSQL Global Development Group
2+
3+
# This test verifies edge case of reading a multixact:
4+
# when we have multixact that is followed by exactly one another multixact,
5+
# and another multixact have no offset yet, we must wait until this offset
6+
# becomes observable. Previously we used to wait for 1ms in a loop in this
7+
# case, but now we use CV for this. This test is exercising such a sleep.
8+
9+
use strict;
10+
use warnings FATAL => 'all';
11+
12+
use PostgreSQL::Test::Cluster;
13+
use PostgreSQL::Test::Utils;
14+
15+
use Test::More;
16+
17+
if ($ENV{enable_injection_points} ne 'yes')
18+
{
19+
plan skip_all => 'Injection points not supported by this build';
20+
}
21+
22+
my ($node, $result);
23+
24+
$node = PostgreSQL::Test::Cluster->new('multixact_CV_sleep');
25+
$node->init;
26+
$node->append_conf('postgresql.conf',
27+
"shared_preload_libraries = 'test_slru'");
28+
$node->start;
29+
$node->safe_psql('postgres', q(CREATE EXTENSION injection_points));
30+
$node->safe_psql('postgres', q(CREATE EXTENSION test_slru));
31+
32+
# Test for Multixact generation edge case
33+
$node->safe_psql('postgres', q(select injection_points_attach('test-read-multixact','wait')));
34+
$node->safe_psql('postgres', q(select injection_points_attach('get-multixact-member-cv-sleep','wait')));
35+
36+
# This session must observe sleep on CV when generating multixact.
37+
# To achieve this it first will create a multixact, then pause before reading it.
38+
my $observer = $node->background_psql('postgres');
39+
40+
# This query will create multixact, and hand just before reading it.
41+
$observer->query_until(qr/start/,
42+
q(
43+
\echo start
44+
select test_read_multixact(test_create_multixact());
45+
));
46+
$node->wait_for_event('client backend', 'test-read-multixact');
47+
48+
# This session will create next Multixact, it's necessary to avoid edge case 1
49+
# (see multixact.c)
50+
my $creator = $node->background_psql('postgres');
51+
$node->safe_psql('postgres', q(select injection_points_attach('get-new-multixact-id','wait');));
52+
53+
# We expect this query to hand in critical section after generating new multixact,
54+
# but before filling it's offset into SLRU.
55+
# Running injection point under SLRU requires special loading of cache.
56+
$creator->query_until(qr/start/, q(
57+
\echo start
58+
select injection_points_load('get-new-multixact-id');
59+
select test_create_multixact();
60+
));
61+
62+
$node->wait_for_event('client backend', 'get-new-multixact-id');
63+
64+
# Now we are sure we can reach edge case 2.
65+
# Observer is going to read multixact, which has next, but next lacks offset.
66+
$node->safe_psql('postgres', q(select injection_points_wakeup('test-read-multixact')));
67+
68+
69+
$node->wait_for_event('client backend', 'get-multixact-member-cv-sleep');
70+
71+
# Now we have two backends waiting in get-new-multixact-id and
72+
# get-multixact-member-cv-sleep. Also we have 3 injections points set to wait.
73+
# If we wakeup get-multixact-member-cv-sleep it will happen again, so we must
74+
# detach it first. So let's detach all injection points, then wake up all
75+
# backends.
76+
77+
$node->safe_psql('postgres', q(select injection_points_detach('test-read-multixact')));
78+
$node->safe_psql('postgres', q(select injection_points_detach('get-new-multixact-id')));
79+
$node->safe_psql('postgres', q(select injection_points_detach('get-multixact-member-cv-sleep')));
80+
81+
$node->safe_psql('postgres', q(select injection_points_wakeup('get-new-multixact-id')));
82+
$node->safe_psql('postgres', q(select injection_points_wakeup('get-multixact-member-cv-sleep')));
83+
84+
# Background psql will now be able to read the result and disconnect.
85+
$observer->quit;
86+
$creator->quit;
87+
88+
$node->stop;
89+
90+
# If we reached this point - everything is OK.
91+
ok(1);
92+
done_testing();

src/test/modules/test_slru/test_slru--1.0.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ CREATE OR REPLACE FUNCTION test_slru_page_truncate(bigint) RETURNS VOID
1919
AS 'MODULE_PATHNAME', 'test_slru_page_truncate' LANGUAGE C;
2020
CREATE OR REPLACE FUNCTION test_slru_delete_all() RETURNS VOID
2121
AS 'MODULE_PATHNAME', 'test_slru_delete_all' LANGUAGE C;
22+
23+
24+
CREATE OR REPLACE FUNCTION test_create_multixact() RETURNS xid
25+
AS 'MODULE_PATHNAME', 'test_create_multixact' LANGUAGE C;
26+
CREATE OR REPLACE FUNCTION test_read_multixact(xid) RETURNS VOID
27+
AS 'MODULE_PATHNAME', 'test_read_multixact'LANGUAGE C;

src/test/modules/test_slru/test_slru.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414

1515
#include "postgres.h"
1616

17+
#include "access/multixact.h"
1718
#include "access/slru.h"
19+
#include "access/xact.h"
1820
#include "access/transam.h"
1921
#include "miscadmin.h"
2022
#include "storage/fd.h"
2123
#include "storage/ipc.h"
2224
#include "storage/shmem.h"
2325
#include "utils/builtins.h"
26+
#include "utils/injection_point.h"
2427

2528
PG_MODULE_MAGIC;
2629

@@ -36,6 +39,8 @@ PG_FUNCTION_INFO_V1(test_slru_page_sync);
3639
PG_FUNCTION_INFO_V1(test_slru_page_delete);
3740
PG_FUNCTION_INFO_V1(test_slru_page_truncate);
3841
PG_FUNCTION_INFO_V1(test_slru_delete_all);
42+
PG_FUNCTION_INFO_V1(test_create_multixact);
43+
PG_FUNCTION_INFO_V1(test_read_multixact);
3944

4045
/* Number of SLRU page slots */
4146
#define NUM_TEST_BUFFERS 16
@@ -260,3 +265,36 @@ _PG_init(void)
260265
prev_shmem_startup_hook = shmem_startup_hook;
261266
shmem_startup_hook = test_slru_shmem_startup;
262267
}
268+
269+
/*
270+
* Produces multixact with 2 current xids
271+
*/
272+
Datum
273+
test_create_multixact(PG_FUNCTION_ARGS)
274+
{
275+
MultiXactId id;
276+
MultiXactIdSetOldestMember();
277+
id = MultiXactIdCreate(GetCurrentTransactionId(), MultiXactStatusUpdate,
278+
GetCurrentTransactionId(), MultiXactStatusForShare);
279+
PG_RETURN_TRANSACTIONID(id);
280+
}
281+
282+
/*
283+
* Reads given multixact after running an injection point. Discards local cache
284+
* to make real read.
285+
* This function is expected to be used in conjunction with test_create_multixact
286+
* to test CV sleep when reading recent multixact.
287+
*/
288+
Datum
289+
test_read_multixact(PG_FUNCTION_ARGS)
290+
{
291+
MultiXactId id = PG_GETARG_TRANSACTIONID(0);
292+
MultiXactMember *members;
293+
INJECTION_POINT("test-read-multixact");
294+
/* discard caches */
295+
AtEOXact_MultiXact();
296+
297+
if (GetMultiXactIdMembers(id,&members,false, false) == -1)
298+
elog(ERROR, "MultiXactId not found");
299+
PG_RETURN_VOID();
300+
}

0 commit comments

Comments
 (0)