agora inbox for pgsql-hackers@postgresql.org
help / color / mirror / Atom feedFrom: Andres Freund <andres@anarazel.de>
Subject: [PATCH v2.8 28/38] aio: Add test_aio module
Date: Wed, 22 Jan 2025 13:44:54 -0500
---
src/include/storage/buf_internals.h | 6 +
src/backend/storage/buffer/bufmgr.c | 8 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
src/test/modules/test_aio/.gitignore | 2 +
src/test/modules/test_aio/Makefile | 27 +
src/test/modules/test_aio/meson.build | 37 ++
src/test/modules/test_aio/t/001_aio.pl | 674 ++++++++++++++++++++
src/test/modules/test_aio/test_aio--1.0.sql | 97 +++
src/test/modules/test_aio/test_aio.c | 616 ++++++++++++++++++
src/test/modules/test_aio/test_aio.control | 3 +
11 files changed, 1466 insertions(+), 6 deletions(-)
create mode 100644 src/test/modules/test_aio/.gitignore
create mode 100644 src/test/modules/test_aio/Makefile
create mode 100644 src/test/modules/test_aio/meson.build
create mode 100644 src/test/modules/test_aio/t/001_aio.pl
create mode 100644 src/test/modules/test_aio/test_aio--1.0.sql
create mode 100644 src/test/modules/test_aio/test_aio.c
create mode 100644 src/test/modules/test_aio/test_aio.control
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index 8a52ace37cd..4b1f52a9fc8 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -434,6 +434,12 @@ extern void IssuePendingWritebacks(WritebackContext *wb_context, IOContext io_co
extern void ScheduleBufferTagForWriteback(WritebackContext *wb_context,
IOContext io_context, BufferTag *tag);
+/* solely to make it easier to write tests */
+extern bool StartBufferIO(BufferDesc *buf, bool forInput, bool nowait);
+extern void TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits,
+ bool forget_owner, bool syncio);
+
+
/* freelist.c */
extern IOContext IOContextForStrategy(BufferAccessStrategy strategy);
extern BufferDesc *StrategyGetBuffer(BufferAccessStrategy strategy,
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 2691757016c..860886d4d90 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -515,10 +515,6 @@ static uint32 WaitBufHdrUnlocked(BufferDesc *buf);
static int SyncOneBuffer(int buf_id, bool skip_recently_used,
WritebackContext *wb_context);
static void WaitIO(BufferDesc *buf);
-static bool StartBufferIO(BufferDesc *buf, bool forInput, bool nowait);
-static void TerminateBufferIO(BufferDesc *buf, bool clear_dirty,
- uint32 set_flag_bits, bool forget_owner,
- bool syncio);
static void AbortBufferIO(Buffer buffer);
static void shared_buffer_write_error_callback(void *arg);
static void local_buffer_write_error_callback(void *arg);
@@ -5927,7 +5923,7 @@ WaitIO(BufferDesc *buf)
* find out if they can perform the I/O as part of a larger operation, without
* waiting for the answer or distinguishing the reasons why not.
*/
-static bool
+bool
StartBufferIO(BufferDesc *buf, bool forInput, bool nowait)
{
uint32 buf_state;
@@ -5984,7 +5980,7 @@ StartBufferIO(BufferDesc *buf, bool forInput, bool nowait)
* resource owner. (forget_owner=false is used when the resource owner itself
* is being released)
*/
-static void
+void
TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits,
bool forget_owner, bool syncio)
{
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 4e4be3fa511..aa1d27bbed3 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -14,6 +14,7 @@ SUBDIRS = \
oauth_validator \
plsample \
spgist_name_ops \
+ test_aio \
test_bloomfilter \
test_copy_callbacks \
test_custom_rmgrs \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 2b057451473..4fda1acfb32 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -1,5 +1,6 @@
# Copyright (c) 2022-2025, PostgreSQL Global Development Group
+subdir('test_aio')
subdir('brin')
subdir('commit_ts')
subdir('delay_execution')
diff --git a/src/test/modules/test_aio/.gitignore b/src/test/modules/test_aio/.gitignore
new file mode 100644
index 00000000000..716e17f5a2a
--- /dev/null
+++ b/src/test/modules/test_aio/.gitignore
@@ -0,0 +1,2 @@
+# Generated subdirectories
+/tmp_check/
diff --git a/src/test/modules/test_aio/Makefile b/src/test/modules/test_aio/Makefile
new file mode 100644
index 00000000000..87d5315ba00
--- /dev/null
+++ b/src/test/modules/test_aio/Makefile
@@ -0,0 +1,27 @@
+# src/test/modules/delay_execution/Makefile
+
+PGFILEDESC = "test_aio - test code for AIO"
+
+MODULE_big = test_aio
+OBJS = \
+ $(WIN32RES) \
+ test_aio.o
+
+EXTENSION = test_aio
+DATA = test_aio--1.0.sql
+
+TAP_TESTS = 1
+
+export enable_injection_points
+export with_liburing
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_aio
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_aio/meson.build b/src/test/modules/test_aio/meson.build
new file mode 100644
index 00000000000..ac846b2b6f3
--- /dev/null
+++ b/src/test/modules/test_aio/meson.build
@@ -0,0 +1,37 @@
+# Copyright (c) 2022-2024, PostgreSQL Global Development Group
+
+test_aio_sources = files(
+ 'test_aio.c',
+)
+
+if host_system == 'windows'
+ test_aio_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'test_aio',
+ '--FILEDESC', 'test_aio - test code for AIO',])
+endif
+
+test_aio = shared_module('test_aio',
+ test_aio_sources,
+ kwargs: pg_test_mod_args,
+)
+test_install_libs += test_aio
+
+test_install_data += files(
+ 'test_aio.control',
+ 'test_aio--1.0.sql',
+)
+
+tests += {
+ 'name': 'test_aio',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'tap': {
+ 'env': {
+ 'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
+ 'with_liburing': liburing.found() ? 'yes' : 'no',
+ },
+ 'tests': [
+ 't/001_aio.pl',
+ ],
+ },
+}
diff --git a/src/test/modules/test_aio/t/001_aio.pl b/src/test/modules/test_aio/t/001_aio.pl
new file mode 100644
index 00000000000..ecd1f3c2f17
--- /dev/null
+++ b/src/test/modules/test_aio/t/001_aio.pl
@@ -0,0 +1,674 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+
+###
+# Test io_method=worker
+###
+my $node_worker = create_node('worker');
+$node_worker->start();
+
+test_generic('worker', $node_worker);
+SKIP:
+{
+ skip 'Injection points not supported by this build', 1
+ unless $ENV{enable_injection_points} eq 'yes';
+ test_inject_worker('worker', $node_worker);
+}
+
+$node_worker->stop();
+
+
+###
+# Test io_method=io_uring
+###
+
+if ($ENV{with_liburing} eq 'yes')
+{
+ my $node_uring = create_node('io_uring');
+ $node_uring->start();
+ test_generic('io_uring', $node_uring);
+ $node_uring->stop();
+}
+
+
+###
+# Test io_method=sync
+###
+
+my $node_sync = create_node('sync');
+
+# just to have one test not use the default auto-tuning
+
+$node_sync->append_conf(
+ 'postgresql.conf', qq(
+io_max_concurrency=4
+));
+
+$node_sync->start();
+test_generic('sync', $node_sync);
+$node_sync->stop();
+
+done_testing();
+
+
+###
+# Test Helpers
+###
+
+sub create_node
+{
+ my $io_method = shift;
+
+ my $node = PostgreSQL::Test::Cluster->new($io_method);
+
+ # Want to test initdb for each IO method, otherwise we could just reuse
+ # the cluster.
+ #
+ # Unfortunately Cluster::init() puts PG_TEST_INITDB_EXTRA_OPTS after the
+ # options specified by ->extra, if somebody puts -c io_method=xyz in
+ # PG_TEST_INITDB_EXTRA_OPTS it would break this test. Fix that up if we
+ # detect it.
+ local $ENV{PG_TEST_INITDB_EXTRA_OPTS} = $ENV{PG_TEST_INITDB_EXTRA_OPTS};
+ if (defined $ENV{PG_TEST_INITDB_EXTRA_OPTS} &&
+ $ENV{PG_TEST_INITDB_EXTRA_OPTS} =~ m/io_method=/)
+ {
+ $ENV{PG_TEST_INITDB_EXTRA_OPTS} .= " -c io_method=$io_method";
+ }
+
+ $node->init(extra => [ '-c', "io_method=$io_method" ]);
+
+ $node->append_conf(
+ 'postgresql.conf', qq(
+shared_preload_libraries=test_aio
+log_min_messages = 'DEBUG3'
+log_statement=all
+restart_after_crash=false
+));
+
+ ok(1, "$io_method: initdb");
+
+ return $node;
+}
+
+sub psql_like
+{
+ my $io_method = shift;
+ my $psql = shift;
+ my $name = shift;
+ my $sql = shift;
+ my $expected_stdout = shift;
+ my $expected_stderr = shift;
+ my ($cmdret, $output);
+
+ ($output, $cmdret) = $psql->query($sql);
+
+ like($output, $expected_stdout, "$io_method: $name: expected stdout");
+ like($psql->{stderr}, $expected_stderr,
+ "$io_method: $name: expected stderr");
+ $psql->{stderr} = '';
+}
+
+sub query_wait_block
+{
+ my $io_method = shift;
+ my $node = shift;
+ my $psql = shift;
+ my $name = shift;
+ my $sql = shift;
+ my $waitfor = shift;
+
+ my $pid = $psql->query_safe('SELECT pg_backend_pid()');
+
+ $psql->{stdin} .= qq($sql;\n);
+ $psql->{run}->pump_nb();
+ ok(1, "$io_method: $name: issued sql");
+
+ $node->poll_query_until('postgres',
+ qq(SELECT wait_event FROM pg_stat_activity WHERE pid = $pid),
+ $waitfor,);
+ ok(1, "$io_method: $name: observed $waitfor wait event");
+}
+
+
+###
+# Sub-tests
+###
+
+sub test_handle
+{
+ my $io_method = shift;
+ my $node = shift;
+
+ my $psql = $node->background_psql('postgres', on_error_stop => 0);
+
+ # leak warning: implicit xact
+ psql_like(
+ $io_method,
+ $psql,
+ "handle_get() leak in implicit xact",
+ qq(SELECT handle_get()),
+ qr/^$/,
+ qr/leaked AIO handle/,
+ "$io_method: leaky handle_get() warns");
+
+ # leak warning: explicit xact
+ psql_like(
+ $io_method, $psql,
+ "handle_get() leak in explicit xact",
+ qq(BEGIN; SELECT handle_get(); COMMIT),
+ qr/^$/, qr/leaked AIO handle/);
+
+
+ # leak warning: explicit xact, rollback
+ psql_like(
+ $io_method,
+ $psql,
+ "handle_get() leak in explicit xact, rollback",
+ qq(BEGIN; SELECT handle_get(); ROLLBACK;),
+ qr/^$/,
+ qr/leaked AIO handle/);
+
+ # leak warning: subtrans
+ psql_like(
+ $io_method,
+ $psql,
+ "handle_get() leak in subxact",
+ qq(BEGIN; SAVEPOINT foo; SELECT handle_get(); COMMIT;),
+ qr/^$/,
+ qr/leaked AIO handle/);
+
+ # leak warning + error: released in different command (thus resowner)
+ psql_like(
+ $io_method,
+ $psql,
+ "handle_release() in different command",
+ qq(BEGIN; SELECT handle_get(); SELECT handle_release_last(); COMMIT;),
+ qr/^$/,
+ qr/leaked AIO handle.*release in unexpected state/ms);
+
+ # no leak, release in same command
+ psql_like(
+ $io_method,
+ $psql,
+ "handle_release() in same command",
+ qq(BEGIN; SELECT handle_get() UNION ALL SELECT handle_release_last(); COMMIT;),
+ qr/^$/,
+ qr/^$/);
+
+ # normal handle use
+ psql_like($io_method, $psql, "handle_get_release()",
+ qq(SELECT handle_get_release()),
+ qr/^$/, qr/^$/);
+
+ # should error out, API violation
+ psql_like($io_method, $psql, "handle_get_twice()",
+ qq(SELECT handle_get_release()),
+ qr/^$/, qr/^$/);
+
+ # recover after error in implicit xact
+ psql_like(
+ $io_method,
+ $psql,
+ "handle error recovery in implicit xact",
+ qq(SELECT handle_get_and_error(); SELECT 'ok', handle_get_release()),
+ qr/^|ok$/,
+ qr/ERROR.*as you command/);
+
+ # recover after error in implicit xact
+ psql_like(
+ $io_method,
+ $psql,
+ "handle error recovery in explicit xact",
+ qq(BEGIN; SELECT handle_get_and_error(); SELECT handle_get_release(), 'ok'; COMMIT;),
+ qr/^|ok$/,
+ qr/ERROR.*as you command/);
+
+ # recover after error in subtrans
+ psql_like(
+ $io_method,
+ $psql,
+ "handle error recovery in explicit subxact",
+ qq(BEGIN; SAVEPOINT foo; SELECT handle_get_and_error(); ROLLBACK TO SAVEPOINT foo; SELECT handle_get_release(); ROLLBACK;),
+ qr/^|ok$/,
+ qr/ERROR.*as you command/);
+
+ $psql->quit();
+}
+
+
+sub test_batch
+{
+ my $io_method = shift;
+ my $node = shift;
+
+ my $psql = $node->background_psql('postgres', on_error_stop => 0);
+
+ # leak warning & recovery: implicit xact
+ psql_like(
+ $io_method,
+ $psql,
+ "batch_start() leak & cleanup in implicit xact",
+ qq(SELECT batch_start()),
+ qr/^$/,
+ qr/open AIO batch at end/,
+ "$io_method: leaky batch_start() warns");
+
+ # leak warning & recovery: explicit xact
+ psql_like(
+ $io_method,
+ $psql,
+ "batch_start() leak & cleanup in explicit xact",
+ qq(BEGIN; SELECT batch_start(); COMMIT;),
+ qr/^$/,
+ qr/open AIO batch at end/,
+ "$io_method: leaky batch_start() warns");
+
+
+ # leak warning & recovery: explicit xact, rollback
+ #
+ # XXX: This doesn't fail right now, due to not getting a chance to do
+ # something at transaction command commit. That's not a correctness issue,
+ # it just means it's a bit harder to find buggy code.
+ #psql_like($io_method, $psql,
+ # "batch_start() leak & cleanup after abort",
+ # qq(BEGIN; SELECT batch_start(); ROLLBACK;),
+ # qr/^$/,
+ # qr/open AIO batch at end/, "$io_method: leaky batch_start() warns");
+
+ # no warning, batch closed in same command
+ psql_like(
+ $io_method,
+ $psql,
+ "batch_start(), batch_end() works",
+ qq(SELECT batch_start() UNION ALL SELECT batch_end()),
+ qr/^$/,
+ qr/^$/,
+ "$io_method: batch_start(), batch_end()");
+
+ $psql->quit();
+}
+
+sub test_io_error
+{
+ my $io_method = shift;
+ my $node = shift;
+ my ($ret, $output);
+
+ my $psql = $node->background_psql('postgres', on_error_stop => 0);
+
+ # verify the error is reported in custom C code
+ ($output, $ret) = $psql->query(
+ qq(SELECT read_rel_block_ll('tbl_corr', 1, wait_complete=>true);));
+ is($ret, 1, "$io_method: read_rel_block_ll() of tbl_corr page fails");
+ like(
+ $psql->{stderr},
+ qr/invalid page in block 1 of relation base\/.*/,
+ "$io_method: read_rel_block_ll() of tbl_corr page reports error");
+ $psql->{stderr} = '';
+
+ # verify the error is reported for bufmgr reads
+ ($output, $ret) =
+ $psql->query(qq(SELECT count(*) FROM tbl_corr WHERE ctid = '(1, 1)'));
+ is($ret, 1, "$io_method: tid scan reading tbl_corr block fails");
+ like(
+ $psql->{stderr},
+ qr/invalid page in block 1 of relation base\/.*/,
+ "$io_method: tid scan reading tbl_corr block reports error");
+ $psql->{stderr} = '';
+
+ # verify the error is reported for bufmgr reads
+ ($output, $ret) =
+ $psql->query(qq(SELECT count(*) FROM tbl_corr WHERE ctid = '(1, 1)'));
+ is($ret, 1, "$io_method: sequential scan reading tbl_corr block fails");
+ like(
+ $psql->{stderr},
+ qr/invalid page in block 1 of relation base\/.*/,
+ "$io_method: sequential scan reading tbl_corr block reports error");
+ $psql->{stderr} = '';
+
+ $psql->quit();
+}
+
+# Test interplay between StartBufferIO and TerminateBufferIO
+sub test_startwait_io
+{
+ my $io_method = shift;
+ my $node = shift;
+ my ($ret, $output);
+
+ my $psql_a = $node->background_psql('postgres', on_error_stop => 0);
+ my $psql_b = $node->background_psql('postgres', on_error_stop => 0);
+
+ # create a buffer we can play around with
+ ($output, $ret) =
+ $psql_a->query(qq(SELECT buffer_create_toy('tbl_ok', 1);));
+ is($ret, 0, "$io_method: create toy succeeds");
+ like($output, qr/^\d+$/, "$io_method: create toy returns numeric");
+ my $buf_id = $output;
+
+ # check that one backend can perform StartBufferIO
+ psql_like(
+ $io_method,
+ $psql_a,
+ "first StartBufferIO",
+ qq(SELECT buffer_call_start_io($buf_id, for_input=>true, nowait=>false);),
+ qr/^t$/,
+ qr/^$/);
+
+ # but not twice on the same buffer (non-waiting)
+ psql_like(
+ $io_method,
+ $psql_a,
+ "second StartBufferIO fails, same session",
+ qq(SELECT buffer_call_start_io($buf_id, for_input=>true, nowait=>true);),
+ qr/^f$/,
+ qr/^$/);
+ psql_like(
+ $io_method,
+ $psql_b,
+ "second StartBufferIO fails, other session",
+ qq(SELECT buffer_call_start_io($buf_id, for_input=>true, nowait=>true);),
+ qr/^f$/,
+ qr/^$/);
+
+ # start io in a different session, will block
+ query_wait_block(
+ $io_method,
+ $node,
+ $psql_b,
+ "blocking start buffer io",
+ qq(SELECT buffer_call_start_io($buf_id, for_input=>true, nowait=>false);),
+ "BufferIo");
+
+ # Terminate the IO, without marking it as success, this should trigger the
+ # waiting session to be able to start the io
+ ($output, $ret) = $psql_a->query(
+ qq(SELECT buffer_call_terminate_io($buf_id, for_input=>true, succeed=>false, io_error=>false, syncio=>true);)
+ );
+ is($ret, 0,
+ "$io_method: blocking start buffer io, terminating io, not valid");
+
+ # Because the IO was terminated, but not marked as valid, second session should get the right to start io
+ pump_until($psql_b->{run}, $psql_b->{timeout}, \$psql_b->{stdout}, qr/t/);
+ ok(1, "$io_method: blocking start buffer io, can start io");
+
+ # terminate the IO again
+ $psql_b->query_safe(
+ qq(SELECT buffer_call_terminate_io($buf_id, for_input=>true, succeed=>false, io_error=>false, syncio=>true);)
+ );
+
+
+ # same as the above scenario, but mark IO as having succeeded
+ ($output, $ret) =
+ $psql_a->query(qq(SELECT buffer_call_start_io($buf_id, true, false);));
+ is($ret, 0,
+ "$io_method: blocking buffer io w/ success: first start buffer io succeeds"
+ );
+ is($output, "t",
+ "$io_method: blocking buffer io w/ success: first start buffer io returns true"
+ );
+
+
+ # start io in a different session, will block
+ query_wait_block(
+ $io_method,
+ $node,
+ $psql_b,
+ "blocking start buffer io",
+ qq(SELECT buffer_call_start_io($buf_id, for_input=>true, nowait=>false);),
+ "BufferIo");
+
+ ($output, $ret) = $psql_a->query(
+ qq(SELECT buffer_call_terminate_io($buf_id, for_input=>true, succeed=>true, io_error=>false, syncio=>true);)
+ );
+ is($ret, 0,
+ "$io_method: blocking start buffer io, terminating IO, valid");
+
+ # Because the IO was terminated, and marked as valid, second session should complete but not need io
+ pump_until($psql_b->{run}, $psql_b->{timeout}, \$psql_b->{stdout}, qr/f/);
+ ok(1, "$io_method: blocking start buffer io, no need to start io");
+
+
+ # buffer is valid now, make it invalid again
+ $buf_id = $psql_a->query_safe(qq(SELECT buffer_create_toy('tbl_ok', 1);));
+
+ $psql_a->quit();
+ $psql_b->quit();
+}
+
+# Test that if the backend issuing a read doesn't wait for the IO's
+# completion, another backend can complete the IO
+sub test_complete_foreign
+{
+ my $io_method = shift;
+ my $node = shift;
+ my ($ret, $output);
+
+ my $psql_a = $node->background_psql('postgres', on_error_stop => 0);
+ my $psql_b = $node->background_psql('postgres', on_error_stop => 0);
+
+ # Issue IO without waiting for completion, then sleep
+ $psql_a->query_safe(
+ qq(SELECT read_rel_block_ll('tbl_ok', 1, wait_complete=>false);));
+
+ # Check that another backend can read the relevant block
+ psql_like(
+ $io_method,
+ $psql_b,
+ "completing read started by sleeping backend",
+ qq(SELECT count(*) FROM tbl_ok WHERE ctid = '(1,1)' LIMIT 1),
+ qr/^1$/,
+ qr/^$/);
+
+ # Issue IO without waiting for completion, then exit
+ $psql_a->query_safe(
+ qq(SELECT read_rel_block_ll('tbl_ok', 1, wait_complete=>false);));
+ $psql_a->reconnect_and_clear();
+
+ # Check that another backend can read the relevant block
+ psql_like(
+ $io_method,
+ $psql_b,
+ "completing read started by exited backend",
+ qq(SELECT count(*) FROM tbl_ok WHERE ctid = '(1,1)' LIMIT 1),
+ qr/^1$/,
+ qr/^$/);
+
+ # Read a tbl_corr block, then sleep. The other session will retry the IO
+ # and also fail. The easiest thing to verify that seems to be to check
+ # that both are in the log.
+ my $log_location = -s $node->logfile;
+ $psql_a->query_safe(
+ qq(SELECT read_rel_block_ll('tbl_corr', 1, wait_complete=>false);));
+
+ psql_like(
+ $io_method,
+ $psql_b,
+ "completing read of tbl_corr block started by other backend",
+ qq(SELECT count(*) FROM tbl_corr WHERE ctid = '(1,1)' LIMIT 1),
+ qr/^$/,
+ qr/invalid page in block/);
+
+ # The log message issued for the read_rel_block_ll() should be logged as a LOG
+ $node->wait_for_log(qr/LOG[^\n]+invalid page in/, $log_location);
+ ok(1,
+ "$io_method: completing read of tbl_corr block started by other backend: LOG message for background read"
+ );
+
+ # But for the SELECT, it should be an ERROR
+ $log_location =
+ $node->wait_for_log(qr/ERROR[^\n]+invalid page in/, $log_location);
+ ok(1,
+ "$io_method: completing read of tbl_corr block started by other backend: ERROR message for foreground read"
+ );
+
+ $psql_a->quit();
+ $psql_b->quit();
+}
+
+sub test_inject
+{
+ my $io_method = shift;
+ my $node = shift;
+ my ($ret, $output);
+
+ my $psql = $node->background_psql('postgres', on_error_stop => 0);
+
+ # injected what we'd expect
+ $psql->query_safe(qq(SELECT inj_io_short_read_attach(8192);));
+ $psql->query_safe(qq(SELECT invalidate_rel_block('tbl_ok', 2);));
+ ($output, $ret) =
+ $psql->query(qq(SELECT count(*) FROM tbl_ok WHERE ctid = '(2, 1)';));
+ is($ret, 0,
+ "$io_method: injection point not triggering failure succeeds");
+
+ # injected a read shorter than a single block, expecting error
+ $psql->query_safe(qq(SELECT inj_io_short_read_attach(17);));
+ $psql->query_safe(qq(SELECT invalidate_rel_block('tbl_ok', 2);));
+ ($output, $ret) =
+ $psql->query(qq(SELECT count(*) FROM tbl_ok WHERE ctid = '(2, 1)';));
+ is($ret, 1, "$io_method: single block short read fails");
+ like(
+ $psql->{stderr},
+ qr/ERROR:.*could not read blocks 2\.\.2 in file "base\/.*": read only 0 of 8192 bytes/,
+ "$io_method: single block short read reports error");
+ $psql->{stderr} = '';
+
+ # shorten multi-block read to a single block, should retry
+ $psql->query_safe(
+ qq(
+SELECT invalidate_rel_block('tbl_ok', 0);
+SELECT invalidate_rel_block('tbl_ok', 1);
+SELECT invalidate_rel_block('tbl_ok', 2);
+SELECT invalidate_rel_block('tbl_ok', 3);
+/* gap */
+SELECT invalidate_rel_block('tbl_ok', 5);
+SELECT invalidate_rel_block('tbl_ok', 6);
+SELECT invalidate_rel_block('tbl_ok', 7);
+SELECT invalidate_rel_block('tbl_ok', 8);
+
+SELECT inj_io_short_read_attach(8192);
+ ));
+ ($output, $ret) = $psql->query(qq(SELECT count(*) FROM tbl_ok;));
+ is($ret, 0, "$io_method: multi block short read is retried");
+
+ # verify that page verification errors are detected even as part of a
+ # shortened multi-block read (tbl_corr, block 1 is tbl_corred)
+ $psql->query_safe(
+ qq(
+SELECT invalidate_rel_block('tbl_corr', 0);
+SELECT invalidate_rel_block('tbl_corr', 1);
+SELECT invalidate_rel_block('tbl_corr', 2);
+SELECT inj_io_short_read_attach(8192);
+ ));
+ ($output, $ret) =
+ $psql->query(qq(SELECT count(*) FROM tbl_corr WHERE ctid < '(2, 1)'));
+ is($ret, 1,
+ "$io_method: shortened multi-block read detects invalid page");
+ like(
+ $psql->{stderr},
+ qr/ERROR:.*invalid page in block 1 of relation base\/.*/,
+ "$io_method: shortened multi-block reads reports invalid page");
+ $psql->{stderr} = '';
+
+ # trigger a hard error, should error out
+ $psql->query_safe(
+ qq(
+SELECT inj_io_short_read_attach(-errno_from_string('EIO'));
+SELECT invalidate_rel_block('tbl_ok', 2);
+ ));
+ ($output, $ret) =
+ $psql->query(qq(SELECT count(*) FROM tbl_ok; SELECT 1;));
+ is($ret, 1, "$io_method: hard IO error is detected");
+ like(
+ $psql->{stderr},
+ qr/ERROR:.*could not read blocks 2\.\.2 in file \"base\/.*\": Input\/output error/,
+ "$io_method: hard IO error is reported");
+ $psql->{stderr} = '';
+
+ $psql->query_safe(
+ qq(
+SELECT inj_io_short_read_detach();
+ ));
+
+ $psql->quit();
+}
+
+sub test_inject_worker
+{
+ my $io_method = shift;
+ my $node = shift;
+ my ($ret, $output);
+
+ my $psql = $node->background_psql('postgres', on_error_stop => 0);
+
+ # trigger a failure to reopen, should error out, but should recover
+ $psql->query_safe(
+ qq(
+SELECT inj_io_reopen_attach();
+SELECT invalidate_rel_block('tbl_ok', 1);
+ ));
+ ($output, $ret) = $psql->query(qq(SELECT count(*) FROM tbl_ok;));
+ is($ret, 1, "$io_method: failure to open is detected");
+ like(
+ $psql->{stderr},
+ qr/ERROR:.*could not read blocks 1\.\.1 in file "base\/.*": No such file or directory/,
+ "$io_method: failure to open is reported");
+ $psql->{stderr} = '';
+
+ $psql->query_safe(
+ qq(
+SELECT inj_io_reopen_detach();
+ ));
+
+ # check that we indeed recover
+ ($output, $ret) = $psql->query(qq(SELECT count(*) FROM tbl_ok;));
+ is($ret, 0, "$io_method: recovers from failure to open ");
+
+
+ $psql->quit();
+}
+
+sub test_generic
+{
+ my $io_method = shift;
+ my $node = shift;
+
+ is($node->safe_psql('postgres', 'SHOW io_method'),
+ $io_method, "$io_method: io_method set correctly");
+
+ $node->safe_psql(
+ 'postgres', qq(
+CREATE EXTENSION test_aio;
+CREATE TABLE tbl_corr(data int not null) WITH (AUTOVACUUM_ENABLED = false);
+CREATE TABLE tbl_ok(data int not null) WITH (AUTOVACUUM_ENABLED = false);
+
+INSERT INTO tbl_corr SELECT generate_series(1, 10000);
+INSERT INTO tbl_ok SELECT generate_series(1, 10000);
+SELECT grow_rel('tbl_corr', 16);
+SELECT grow_rel('tbl_ok', 16);
+
+SELECT corrupt_rel_block('tbl_corr', 1);
+CHECKPOINT;
+));
+
+ test_handle($io_method, $node);
+ test_io_error($io_method, $node);
+ test_batch($io_method, $node);
+ test_startwait_io($io_method, $node);
+ test_complete_foreign($io_method, $node);
+
+ SKIP:
+ {
+ skip 'Injection points not supported by this build', 1
+ unless $ENV{enable_injection_points} eq 'yes';
+ test_inject($io_method, $node);
+ }
+}
diff --git a/src/test/modules/test_aio/test_aio--1.0.sql b/src/test/modules/test_aio/test_aio--1.0.sql
new file mode 100644
index 00000000000..3d4e9bb070f
--- /dev/null
+++ b/src/test/modules/test_aio/test_aio--1.0.sql
@@ -0,0 +1,97 @@
+/* src/test/modules/test_aio/test_aio--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_aio" to load this file. \quit
+
+
+CREATE FUNCTION errno_from_string(sym text)
+RETURNS pg_catalog.int4 STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+
+CREATE FUNCTION grow_rel(rel regclass, nblocks int)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+
+CREATE FUNCTION corrupt_rel_block(rel regclass, blockno int)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION read_rel_block_ll(rel regclass, blockno int, wait_complete bool)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION invalidate_rel_block(rel regclass, blockno int)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION buffer_create_toy(rel regclass, blockno int4)
+RETURNS pg_catalog.int4 STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION buffer_call_start_io(buffer int, for_input bool, nowait bool)
+RETURNS pg_catalog.bool STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION buffer_call_terminate_io(buffer int, for_input bool, succeed bool, io_error bool, syncio bool)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+
+
+/*
+ * Handle related functions
+ */
+CREATE FUNCTION handle_get_and_error()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION handle_get_twice()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION handle_get()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION handle_get_release()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION handle_release_last()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+
+/*
+ * Batchmode related functions
+ */
+CREATE FUNCTION batch_start()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION batch_end()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+
+
+/*
+ * Injection point related functions
+ */
+CREATE FUNCTION inj_io_short_read_attach(result int)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION inj_io_short_read_detach()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION inj_io_reopen_attach()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+
+CREATE FUNCTION inj_io_reopen_detach()
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
diff --git a/src/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c
new file mode 100644
index 00000000000..81b4d732206
--- /dev/null
+++ b/src/test/modules/test_aio/test_aio.c
@@ -0,0 +1,616 @@
+/*-------------------------------------------------------------------------
+ *
+ * delay_execution.c
+ * Test module to allow delay between parsing and execution of a query.
+ *
+ * The delay is implemented by taking and immediately releasing a specified
+ * advisory lock. If another process has previously taken that lock, the
+ * current process will be blocked until the lock is released; otherwise,
+ * there's no effect. This allows an isolationtester script to reliably
+ * test behaviors where some specified action happens in another backend
+ * between parsing and execution of any desired query.
+ *
+ * Copyright (c) 2020-2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/delay_execution/delay_execution.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/relation.h"
+#include "fmgr.h"
+#include "storage/aio.h"
+#include "storage/aio_internal.h"
+#include "storage/buf_internals.h"
+#include "storage/bufmgr.h"
+#include "storage/ipc.h"
+#include "storage/lwlock.h"
+#include "utils/builtins.h"
+#include "utils/injection_point.h"
+#include "utils/rel.h"
+
+
+PG_MODULE_MAGIC;
+
+
+typedef struct InjIoErrorState
+{
+ bool enabled_short_read;
+ bool enabled_reopen;
+
+ bool short_read_result_set;
+ int short_read_result;
+} InjIoErrorState;
+
+static InjIoErrorState * inj_io_error_state;
+
+/* Shared memory init callbacks */
+static shmem_request_hook_type prev_shmem_request_hook = NULL;
+static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
+
+
+static PgAioHandle *last_handle;
+
+
+
+static void
+test_aio_shmem_request(void)
+{
+ if (prev_shmem_request_hook)
+ prev_shmem_request_hook();
+
+ RequestAddinShmemSpace(sizeof(InjIoErrorState));
+}
+
+static void
+test_aio_shmem_startup(void)
+{
+ bool found;
+
+ if (prev_shmem_startup_hook)
+ prev_shmem_startup_hook();
+
+ /* Create or attach to the shared memory state */
+ LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
+
+ inj_io_error_state = ShmemInitStruct("injection_points",
+ sizeof(InjIoErrorState),
+ &found);
+
+ if (!found)
+ {
+ /*
+ * First time through, so initialize. This is shared with the dynamic
+ * initialization using a DSM.
+ */
+ inj_io_error_state->enabled_short_read = false;
+ inj_io_error_state->enabled_reopen = false;
+
+#ifdef USE_INJECTION_POINTS
+ InjectionPointAttach("AIO_PROCESS_COMPLETION_BEFORE_SHARED",
+ "test_aio",
+ "inj_io_short_read",
+ NULL,
+ 0);
+ InjectionPointLoad("AIO_PROCESS_COMPLETION_BEFORE_SHARED");
+
+ InjectionPointAttach("AIO_WORKER_AFTER_REOPEN",
+ "test_aio",
+ "inj_io_reopen",
+ NULL,
+ 0);
+ InjectionPointLoad("AIO_WORKER_AFTER_REOPEN");
+
+#endif
+ }
+ else
+ {
+#ifdef USE_INJECTION_POINTS
+ InjectionPointLoad("AIO_PROCESS_COMPLETION_BEFORE_SHARED");
+ InjectionPointLoad("AIO_WORKER_AFTER_REOPEN");
+ elog(LOG, "injection point loaded");
+#endif
+ }
+
+ LWLockRelease(AddinShmemInitLock);
+}
+
+void
+_PG_init(void)
+{
+ if (!process_shared_preload_libraries_in_progress)
+ return;
+
+ /* Shared memory initialization */
+ prev_shmem_request_hook = shmem_request_hook;
+ shmem_request_hook = test_aio_shmem_request;
+ prev_shmem_startup_hook = shmem_startup_hook;
+ shmem_startup_hook = test_aio_shmem_startup;
+}
+
+
+PG_FUNCTION_INFO_V1(errno_from_string);
+Datum
+errno_from_string(PG_FUNCTION_ARGS)
+{
+ const char *sym = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+ if (strcmp(sym, "EIO") == 0)
+ PG_RETURN_INT32(EIO);
+ else if (strcmp(sym, "EAGAIN") == 0)
+ PG_RETURN_INT32(EAGAIN);
+ else if (strcmp(sym, "EINTR") == 0)
+ PG_RETURN_INT32(EINTR);
+ else if (strcmp(sym, "ENOSPC") == 0)
+ PG_RETURN_INT32(ENOSPC);
+ else if (strcmp(sym, "EROFS") == 0)
+ PG_RETURN_INT32(EROFS);
+
+ ereport(ERROR,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg_internal("%s is not a supported errno value", sym));
+ PG_RETURN_INT32(0);
+}
+
+
+PG_FUNCTION_INFO_V1(grow_rel);
+Datum
+grow_rel(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+ uint32 nblocks = PG_GETARG_UINT32(1);
+ Relation rel;
+#define MAX_BUFFERS_TO_EXTEND_BY 64
+ Buffer victim_buffers[MAX_BUFFERS_TO_EXTEND_BY];
+
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ while (nblocks > 0)
+ {
+ uint32 extend_by_pages;
+
+ extend_by_pages = Min(nblocks, MAX_BUFFERS_TO_EXTEND_BY);
+
+ ExtendBufferedRelBy(BMR_REL(rel),
+ MAIN_FORKNUM,
+ NULL,
+ 0,
+ extend_by_pages,
+ victim_buffers,
+ &extend_by_pages);
+
+ nblocks -= extend_by_pages;
+
+ for (uint32 i = 0; i < extend_by_pages; i++)
+ {
+ ReleaseBuffer(victim_buffers[i]);
+ }
+ }
+
+ relation_close(rel, NoLock);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(corrupt_rel_block);
+Datum
+corrupt_rel_block(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+ BlockNumber blkno = PG_GETARG_UINT32(1);
+ Relation rel;
+ Buffer buf;
+ Page page;
+ PageHeader ph;
+
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ buf = ReadBuffer(rel, blkno);
+ page = BufferGetPage(buf);
+
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+
+ MarkBufferDirty(buf);
+
+ PageInit(page, BufferGetPageSize(buf), 0);
+
+ ph = (PageHeader) page;
+ ph->pd_special = BLCKSZ + 1;
+
+ FlushOneBuffer(buf);
+
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+
+ ReleaseBuffer(buf);
+
+ EvictUnpinnedBuffer(buf);
+
+ relation_close(rel, NoLock);
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * Ensures a buffer for rel & blkno is in shared buffers, without actually
+ * caring about the buffer contents. Used to set up test scenarios.
+ */
+static Buffer
+create_toy_buffer(Relation rel, BlockNumber blkno)
+{
+ Buffer buf;
+ BufferDesc *buf_hdr;
+ uint32 buf_state;
+ bool was_pinned = false;
+
+ /* read buffer without erroring out */
+ buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_ON_ERROR, NULL);
+
+ buf_hdr = GetBufferDescriptor(buf - 1);
+
+ buf_state = LockBufHdr(buf_hdr);
+
+ /*
+ * We should be the only backend accessing this buffer. This is just a
+ * small bit of belt-and-suspenders defense, none of this code should ever
+ * run in a cluster with real data.
+ */
+ if (BUF_STATE_GET_REFCOUNT(buf_state) > 1)
+ was_pinned = true;
+ else
+ buf_state &= ~(BM_VALID | BM_DIRTY);
+
+ UnlockBufHdr(buf_hdr, buf_state);
+
+ if (was_pinned)
+ elog(ERROR, "toy buffer %d was already pinned",
+ buf);
+
+ return buf;
+}
+
+PG_FUNCTION_INFO_V1(read_rel_block_ll);
+Datum
+read_rel_block_ll(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+ BlockNumber blkno = PG_GETARG_UINT32(1);
+ bool wait_complete = PG_GETARG_BOOL(2);
+ Relation rel;
+ Buffer buf;
+ Page pages[1];
+ PgAioReturn ior;
+ PgAioHandle *ioh;
+ PgAioWaitRef iow;
+ SMgrRelation smgr;
+
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ buf = create_toy_buffer(rel, blkno);
+
+ pages[0] = BufferGetBlock(buf);
+
+ ioh = pgaio_io_acquire(CurrentResourceOwner, &ior);
+ pgaio_io_get_wref(ioh, &iow);
+
+ smgr = RelationGetSmgr(rel);
+
+ StartBufferIO(GetBufferDescriptor(buf - 1), true, false);
+
+ pgaio_io_set_handle_data_32(ioh, (uint32 *) &buf, 1);
+ pgaio_io_register_callbacks(ioh, PGAIO_HCB_SHARED_BUFFER_READV, 0);
+
+ elog(LOG, "about to smgrstartreadv");
+ smgrstartreadv(ioh, smgr, MAIN_FORKNUM, blkno,
+ (void *) pages, 1);
+
+ ReleaseBuffer(buf);
+
+ if (wait_complete)
+ {
+ pgaio_wref_wait(&iow);
+
+ if (ior.result.status != ARS_OK)
+ pgaio_result_report(ior.result, &ior.target_data,
+ ior.result.status == ARS_PARTIAL ? WARNING : ERROR);
+ }
+
+ relation_close(rel, NoLock);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(invalidate_rel_block);
+Datum
+invalidate_rel_block(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+ BlockNumber blkno = PG_GETARG_UINT32(1);
+ Relation rel;
+ PrefetchBufferResult pr;
+ Buffer buf;
+
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ /*
+ * This is a gross hack, but there's no other API exposed that allows to
+ * get a buffer ID without actually reading the block in.
+ */
+ pr = PrefetchBuffer(rel, MAIN_FORKNUM, blkno);
+ buf = pr.recent_buffer;
+
+ if (BufferIsValid(buf))
+ {
+ /* if the buffer contents aren't valid, this'll return false */
+ if (ReadRecentBuffer(rel->rd_locator, MAIN_FORKNUM, blkno, buf))
+ {
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ FlushOneBuffer(buf);
+ LockBuffer(buf, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buf);
+
+ if (!EvictUnpinnedBuffer(buf))
+ elog(ERROR, "couldn't evict");
+ }
+ }
+
+ relation_close(rel, AccessExclusiveLock);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(buffer_create_toy);
+Datum
+buffer_create_toy(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+ BlockNumber blkno = PG_GETARG_UINT32(1);
+ Relation rel;
+ Buffer buf;
+
+ rel = relation_open(relid, AccessExclusiveLock);
+
+ buf = create_toy_buffer(rel, blkno);
+ ReleaseBuffer(buf);
+
+ relation_close(rel, NoLock);
+
+ PG_RETURN_INT32(buf);
+}
+
+PG_FUNCTION_INFO_V1(buffer_call_start_io);
+Datum
+buffer_call_start_io(PG_FUNCTION_ARGS)
+{
+ Buffer buf = PG_GETARG_INT32(0);
+ bool for_input = PG_GETARG_BOOL(1);
+ bool nowait = PG_GETARG_BOOL(2);
+ bool can_start;
+
+ can_start = StartBufferIO(GetBufferDescriptor(buf - 1), for_input, nowait);
+
+ /*
+ * Tor tests we don't want the resowner release preventing us from
+ * orchestrating odd scenarios.
+ */
+ if (can_start)
+ ResourceOwnerForgetBufferIO(CurrentResourceOwner,
+ buf);
+
+ ereport(LOG,
+ errmsg("buffer %d after StartBufferIO: %s",
+ buf, DebugPrintBufferRefcount(buf)),
+ errhidestmt(true), errhidecontext(true));
+
+ PG_RETURN_BOOL(can_start);
+}
+
+/*
+CREATE FUNCTION buffer_call_terminate_io(buffer int, for_input bool, failed bool, syncio bool)
+RETURNS pg_catalog.void STRICT
+AS 'MODULE_PATHNAME' LANGUAGE C;
+*/
+PG_FUNCTION_INFO_V1(buffer_call_terminate_io);
+Datum
+buffer_call_terminate_io(PG_FUNCTION_ARGS)
+{
+ Buffer buf = PG_GETARG_INT32(0);
+ bool for_input = PG_GETARG_BOOL(1);
+ bool succeed = PG_GETARG_BOOL(2);
+ bool io_error = PG_GETARG_BOOL(3);
+ bool syncio = PG_GETARG_BOOL(4);
+ bool clear_dirty = false;
+ uint32 set_flag_bits = 0;
+
+ if (io_error)
+ set_flag_bits |= BM_IO_ERROR;
+
+ if (for_input)
+ {
+ clear_dirty = false;
+
+ if (succeed)
+ set_flag_bits |= BM_VALID;
+ }
+ else
+ {
+ if (succeed)
+ clear_dirty = true;
+ }
+
+ ereport(LOG,
+ errmsg("buffer %d before TerminateBufferIO: %s",
+ buf, DebugPrintBufferRefcount(buf)),
+ errhidestmt(true), errhidecontext(true));
+
+ TerminateBufferIO(GetBufferDescriptor(buf - 1), clear_dirty, set_flag_bits,
+ false, syncio);
+
+ ereport(LOG,
+ errmsg("buffer %d after TerminateBufferIO: %s",
+ buf, DebugPrintBufferRefcount(buf)),
+ errhidestmt(true), errhidecontext(true));
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(handle_get);
+Datum
+handle_get(PG_FUNCTION_ARGS)
+{
+ last_handle = pgaio_io_acquire(CurrentResourceOwner, NULL);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(handle_release_last);
+Datum
+handle_release_last(PG_FUNCTION_ARGS)
+{
+ if (!last_handle)
+ elog(ERROR, "no handle");
+
+ pgaio_io_release(last_handle);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(handle_get_and_error);
+Datum
+handle_get_and_error(PG_FUNCTION_ARGS)
+{
+ pgaio_io_acquire(CurrentResourceOwner, NULL);
+
+ elog(ERROR, "as you command");
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(handle_get_twice);
+Datum
+handle_get_twice(PG_FUNCTION_ARGS)
+{
+ pgaio_io_acquire(CurrentResourceOwner, NULL);
+ pgaio_io_acquire(CurrentResourceOwner, NULL);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(handle_get_release);
+Datum
+handle_get_release(PG_FUNCTION_ARGS)
+{
+ PgAioHandle *handle;
+
+ handle = pgaio_io_acquire(CurrentResourceOwner, NULL);
+ pgaio_io_release(handle);
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(batch_start);
+Datum
+batch_start(PG_FUNCTION_ARGS)
+{
+ pgaio_enter_batchmode();
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(batch_end);
+Datum
+batch_end(PG_FUNCTION_ARGS)
+{
+ pgaio_exit_batchmode();
+ PG_RETURN_VOID();
+}
+
+#ifdef USE_INJECTION_POINTS
+extern PGDLLEXPORT void inj_io_short_read(const char *name, const void *private_data);
+extern PGDLLEXPORT void inj_io_reopen(const char *name, const void *private_data);
+
+void
+inj_io_short_read(const char *name, const void *private_data)
+{
+ PgAioHandle *ioh;
+
+ elog(LOG, "short read called: %d", inj_io_error_state->enabled_short_read);
+
+ if (inj_io_error_state->enabled_short_read)
+ {
+ ioh = pgaio_inj_io_get();
+
+ if (inj_io_error_state->short_read_result_set)
+ {
+ elog(LOG, "short read, changing result from %d to %d",
+ ioh->result, inj_io_error_state->short_read_result);
+
+ ioh->result = inj_io_error_state->short_read_result;
+ }
+ }
+}
+
+void
+inj_io_reopen(const char *name, const void *private_data)
+{
+ elog(LOG, "reopen called: %d", inj_io_error_state->enabled_reopen);
+
+ if (inj_io_error_state->enabled_reopen)
+ {
+ elog(ERROR, "injection point triggering failure to reopen ");
+ }
+}
+#endif
+
+PG_FUNCTION_INFO_V1(inj_io_short_read_attach);
+Datum
+inj_io_short_read_attach(PG_FUNCTION_ARGS)
+{
+#ifdef USE_INJECTION_POINTS
+ inj_io_error_state->enabled_short_read = true;
+ inj_io_error_state->short_read_result_set = !PG_ARGISNULL(0);
+ if (inj_io_error_state->short_read_result_set)
+ inj_io_error_state->short_read_result = PG_GETARG_INT32(0);
+#else
+ elog(ERROR, "injection points not supported");
+#endif
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(inj_io_short_read_detach);
+Datum
+inj_io_short_read_detach(PG_FUNCTION_ARGS)
+{
+#ifdef USE_INJECTION_POINTS
+ inj_io_error_state->enabled_short_read = false;
+#else
+ elog(ERROR, "injection points not supported");
+#endif
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(inj_io_reopen_attach);
+Datum
+inj_io_reopen_attach(PG_FUNCTION_ARGS)
+{
+#ifdef USE_INJECTION_POINTS
+ inj_io_error_state->enabled_reopen = true;
+#else
+ elog(ERROR, "injection points not supported");
+#endif
+
+ PG_RETURN_VOID();
+}
+
+PG_FUNCTION_INFO_V1(inj_io_reopen_detach);
+Datum
+inj_io_reopen_detach(PG_FUNCTION_ARGS)
+{
+#ifdef USE_INJECTION_POINTS
+ inj_io_error_state->enabled_reopen = false;
+#else
+ elog(ERROR, "injection points not supported");
+#endif
+ PG_RETURN_VOID();
+}
diff --git a/src/test/modules/test_aio/test_aio.control b/src/test/modules/test_aio/test_aio.control
new file mode 100644
index 00000000000..cd91c3ed16b
--- /dev/null
+++ b/src/test/modules/test_aio/test_aio.control
@@ -0,0 +1,3 @@
+comment = 'Test code for AIO'
+default_version = '1.0'
+module_pathname = '$libdir/test_aio'
--
2.48.1.76.g4e746b1a31.dirty
--ow5flh3n247znjrs
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2.8-0029-aio-Implement-smgr-md-fd-write-support.patch"
view thread (18+ messages) latest in thread
Message-ID: <no-message-id-886010@localhost>
Permalink: ../../no-message-id-886010@localhost/
Also on: postgresql.org/message-id/no-message-id-886010@localhost
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: andres@anarazel.de
Subject: Re: [PATCH v2.8 28/38] aio: Add test_aio module
In-Reply-To: <no-message-id-886010@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox