Fix bug allowing io_combine_limit > io_max_combine_combine limit
authorAndres Freund <andres@anarazel.de>
Fri, 25 Apr 2025 16:18:27 +0000 (12:18 -0400)
committerAndres Freund <andres@anarazel.de>
Fri, 25 Apr 2025 17:31:24 +0000 (13:31 -0400)
10f66468475 intended to limit the value of io_combine_limit to the minimum of
io_combine_limit and io_max_combine_limit. To avoid issues with interdependent
GUCs, it introduced io_combine_limit_guc and set io_combine_limit in assign
hooks. That plan was thwarted by guc_tables.c accidentally still referencing
io_combine_limit, instead of io_combine_limit_guc.  That lead to the GUC
machinery overriding the work done in the assign hooks, potentially leaving
io_combine_limit with a too high value.

The consequence of this bug was that when running with io_combine_limit >
io_combine_limit_guc the AIO machinery would not have reserved large enough
iovec and IO data arrays, with one IO's arrays overlapping with another IO's,
leading to total confusion.

To make such a problem easier to detect in the future, add assertions to
pgaio_io_set_handle_data_* checking the length is smaller than
io_max_combine_limit (not just PG_IOV_MAX).

It'd be nice to have a few tests for this, but it's not entirely obvious how
to do so portably.

As remarked upon by Tom, the GUC assignment hooks really shouldn't set the
underlying variable, that's the job of the GUC machinery. Change that as well.

Discussion: https://postgr.es/m/c5jyqnuwrpigd35qe7xdypxsisdjrdba5iw63mhcse4mzjogxo@qdjpv22z763f

src/backend/commands/variable.c
src/backend/storage/aio/aio_callback.c
src/backend/utils/misc/guc_tables.c

index a9f2a3a30624580626b3518dd7c655e88358008d..608f10d9412dac90fc6a1d970c46c6191bb87bff 100644 (file)
@@ -1163,14 +1163,12 @@ assign_maintenance_io_concurrency(int newval, void *extra)
 void
 assign_io_max_combine_limit(int newval, void *extra)
 {
-   io_max_combine_limit = newval;
-   io_combine_limit = Min(io_max_combine_limit, io_combine_limit_guc);
+   io_combine_limit = Min(newval, io_combine_limit_guc);
 }
 void
 assign_io_combine_limit(int newval, void *extra)
 {
-   io_combine_limit_guc = newval;
-   io_combine_limit = Min(io_max_combine_limit, io_combine_limit_guc);
+   io_combine_limit = Min(io_max_combine_limit, newval);
 }
 
 /*
index bf42778a48c70ffd1599c8ee54b47c2a20b76819..0ad9795bb7e0c1c5778a5944d28edfa5edffa361 100644 (file)
@@ -124,6 +124,7 @@ pgaio_io_set_handle_data_64(PgAioHandle *ioh, uint64 *data, uint8 len)
    Assert(ioh->state == PGAIO_HS_HANDED_OUT);
    Assert(ioh->handle_data_len == 0);
    Assert(len <= PG_IOV_MAX);
+   Assert(len <= io_max_combine_limit);
 
    for (int i = 0; i < len; i++)
        pgaio_ctl->handle_data[ioh->iovec_off + i] = data[i];
@@ -141,6 +142,7 @@ pgaio_io_set_handle_data_32(PgAioHandle *ioh, uint32 *data, uint8 len)
    Assert(ioh->state == PGAIO_HS_HANDED_OUT);
    Assert(ioh->handle_data_len == 0);
    Assert(len <= PG_IOV_MAX);
+   Assert(len <= io_max_combine_limit);
 
    for (int i = 0; i < len; i++)
        pgaio_ctl->handle_data[ioh->iovec_off + i] = data[i];
index 60b12446a1c98e252e090d291070b872c7695b84..2f8cbd8675998a9a8e25bc6953933ad807d52b42 100644 (file)
@@ -3287,7 +3287,7 @@ struct config_int ConfigureNamesInt[] =
            NULL,
            GUC_UNIT_BLOCKS
        },
-       &io_combine_limit,
+       &io_combine_limit_guc,
        DEFAULT_IO_COMBINE_LIMIT,
        1, MAX_IO_COMBINE_LIMIT,
        NULL, assign_io_combine_limit, NULL