Introduce a configurable poll-weight parameter for adaptive polling
in IOThread. This parameter replaces the hardcoded POLL_WEIGHT_SHIFT
constant, allowing runtime control over how much the most recent
event interval affects the next polling duration calculation.
The poll-weight parameter uses a shift value where larger values
decrease the weight of the current interval, enabling more gradual
adjustments. When set to 0, a default value of 3 is used (meaning
the current interval contributes approximately 1/8 to the weighted
average).
This patch also removes the hardcoded default values for poll-grow
and poll-shrink parameters from the grow_polling_time() and
shrink_polling_time() functions, as these defaults are now properly
initialized in iothread.c during IOThread creation.
Signed-off-by: Jaehoon Kim <jhkim@linux.ibm.com>
---
include/qemu/aio.h | 4 +++-
include/system/iothread.h | 1 +
iothread.c | 34 ++++++++++++++++++++++++++++++-
monitor/hmp-cmds.c | 1 +
qapi/misc.json | 7 +++++++
qapi/qom.json | 8 +++++++-
qemu-options.hx | 7 ++++++-
tests/unit/test-nested-aio-poll.c | 2 +-
util/aio-posix.c | 17 +++++-----------
util/aio-win32.c | 3 ++-
util/async.c | 1 +
11 files changed, 67 insertions(+), 18 deletions(-)
diff --git a/include/qemu/aio.h b/include/qemu/aio.h
index 6c77a190e9..50b8db2712 100644
--- a/include/qemu/aio.h
+++ b/include/qemu/aio.h
@@ -311,6 +311,7 @@ struct AioContext {
int64_t poll_max_ns; /* maximum polling time in nanoseconds */
int64_t poll_grow; /* polling time growth factor */
int64_t poll_shrink; /* polling time shrink factor */
+ int64_t poll_weight; /* weight of current interval in calculation */
/* AIO engine parameters */
int64_t aio_max_batch; /* maximum number of requests in a batch */
@@ -792,12 +793,13 @@ void aio_context_destroy(AioContext *ctx);
* @max_ns: how long to busy poll for, in nanoseconds
* @grow: polling time growth factor
* @shrink: polling time shrink factor
+ * @weight: weight factor applied to the current polling interval
*
* Poll mode can be disabled by setting poll_max_ns to 0.
*/
void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
int64_t grow, int64_t shrink,
- Error **errp);
+ int64_t weight, Error **errp);
/**
* aio_context_set_aio_params:
diff --git a/include/system/iothread.h b/include/system/iothread.h
index e26d13c6c7..6ea57ed126 100644
--- a/include/system/iothread.h
+++ b/include/system/iothread.h
@@ -38,6 +38,7 @@ struct IOThread {
int64_t poll_max_ns;
int64_t poll_grow;
int64_t poll_shrink;
+ int64_t poll_weight;
};
typedef struct IOThread IOThread;
diff --git a/iothread.c b/iothread.c
index caf68e0764..0389b8f7a8 100644
--- a/iothread.c
+++ b/iothread.c
@@ -32,8 +32,14 @@
* workloads.
*/
#define IOTHREAD_POLL_MAX_NS_DEFAULT 32768ULL
+#define IOTHREAD_POLL_GROW_DEFAULT 2ULL
+#define IOTHREAD_POLL_SHRINK_DEFAULT 2ULL
+#define IOTHREAD_POLL_WEIGHT_DEFAULT 3ULL
#else
#define IOTHREAD_POLL_MAX_NS_DEFAULT 0ULL
+#define IOTHREAD_POLL_GROW_DEFAULT 0ULL
+#define IOTHREAD_POLL_SHRINK_DEFAULT 0ULL
+#define IOTHREAD_POLL_WEIGHT_DEFAULT 0ULL
#endif
static void *iothread_run(void *opaque)
@@ -103,6 +109,10 @@ static void iothread_instance_init(Object *obj)
IOThread *iothread = IOTHREAD(obj);
iothread->poll_max_ns = IOTHREAD_POLL_MAX_NS_DEFAULT;
+ iothread->poll_grow = IOTHREAD_POLL_GROW_DEFAULT;
+ iothread->poll_shrink = IOTHREAD_POLL_SHRINK_DEFAULT;
+ iothread->poll_weight = IOTHREAD_POLL_WEIGHT_DEFAULT;
+
iothread->thread_id = -1;
qemu_sem_init(&iothread->init_done_sem, 0);
/* By default, we don't run gcontext */
@@ -164,6 +174,7 @@ static void iothread_set_aio_context_params(EventLoopBase *base, Error **errp)
iothread->poll_max_ns,
iothread->poll_grow,
iothread->poll_shrink,
+ iothread->poll_weight,
errp);
if (*errp) {
return;
@@ -233,6 +244,9 @@ static IOThreadParamInfo poll_grow_info = {
static IOThreadParamInfo poll_shrink_info = {
"poll-shrink", offsetof(IOThread, poll_shrink),
};
+static IOThreadParamInfo poll_weight_info = {
+ "poll-weight", offsetof(IOThread, poll_weight),
+};
static void iothread_get_param(Object *obj, Visitor *v,
const char *name, IOThreadParamInfo *info, Error **errp)
@@ -260,7 +274,19 @@ static bool iothread_set_param(Object *obj, Visitor *v,
return false;
}
- *field = value;
+ if (value == 0) {
+ if (info->offset == offsetof(IOThread, poll_grow)) {
+ *field = IOTHREAD_POLL_GROW_DEFAULT;
+ } else if (info->offset == offsetof(IOThread, poll_shrink)) {
+ *field = IOTHREAD_POLL_SHRINK_DEFAULT;
+ } else if (info->offset == offsetof(IOThread, poll_weight)) {
+ *field = IOTHREAD_POLL_WEIGHT_DEFAULT;
+ } else {
+ *field = value;
+ }
+ } else {
+ *field = value;
+ }
return true;
}
@@ -288,6 +314,7 @@ static void iothread_set_poll_param(Object *obj, Visitor *v,
iothread->poll_max_ns,
iothread->poll_grow,
iothread->poll_shrink,
+ iothread->poll_weight,
errp);
}
}
@@ -311,6 +338,10 @@ static void iothread_class_init(ObjectClass *klass, const void *class_data)
iothread_get_poll_param,
iothread_set_poll_param,
NULL, &poll_shrink_info);
+ object_class_property_add(klass, "poll-weight", "int",
+ iothread_get_poll_param,
+ iothread_set_poll_param,
+ NULL, &poll_weight_info);
}
static const TypeInfo iothread_info = {
@@ -356,6 +387,7 @@ static int query_one_iothread(Object *object, void *opaque)
info->poll_max_ns = iothread->poll_max_ns;
info->poll_grow = iothread->poll_grow;
info->poll_shrink = iothread->poll_shrink;
+ info->poll_weight = iothread->poll_weight;
info->aio_max_batch = iothread->parent_obj.aio_max_batch;
QAPI_LIST_APPEND(*tail, info);
diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
index bad034937a..75b6e7fa65 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
@@ -206,6 +206,7 @@ void hmp_info_iothreads(Monitor *mon, const QDict *qdict)
monitor_printf(mon, " poll-max-ns=%" PRId64 "\n", value->poll_max_ns);
monitor_printf(mon, " poll-grow=%" PRId64 "\n", value->poll_grow);
monitor_printf(mon, " poll-shrink=%" PRId64 "\n", value->poll_shrink);
+ monitor_printf(mon, " poll-weight=%" PRId64 "\n", value->poll_weight);
monitor_printf(mon, " aio-max-batch=%" PRId64 "\n",
value->aio_max_batch);
}
diff --git a/qapi/misc.json b/qapi/misc.json
index 28c641fe2f..39d17010bc 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
@@ -85,6 +85,12 @@
# @poll-shrink: how many ns will be removed from polling time, 0 means
# that it's not configured (since 2.9)
#
+# @poll-weight: the weight factor for adaptive polling.
+# Determines how much the current event interval contributes to
+# the next polling time calculation. Valid values are 1 or
+# greater. 0 selects the system default value which is current 3
+# (since 10.2)
+#
# @aio-max-batch: maximum number of requests in a batch for the AIO
# engine, 0 means that the engine will use its default (since 6.1)
#
@@ -96,6 +102,7 @@
'poll-max-ns': 'int',
'poll-grow': 'int',
'poll-shrink': 'int',
+ 'poll-weight': 'int',
'aio-max-batch': 'int' } }
##
diff --git a/qapi/qom.json b/qapi/qom.json
index c653248f85..feb80b6cfe 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -606,6 +606,11 @@
# algorithm detects it is spending too long polling without
# encountering events. 0 selects a default behaviour (default: 0)
#
+# @poll-weight: the weight factor for adaptive polling.
+# Determines how much the current event interval contributes to
+# the next polling time calculation. Valid values are 1 or
+# greater. If set to 0, the default value of 3 is used.
+#
# The @aio-max-batch option is available since 6.1.
#
# Since: 2.0
@@ -614,7 +619,8 @@
'base': 'EventLoopBaseProperties',
'data': { '*poll-max-ns': 'int',
'*poll-grow': 'int',
- '*poll-shrink': 'int' } }
+ '*poll-shrink': 'int',
+ '*poll-weight': 'int' } }
##
# @MainLoopProperties:
diff --git a/qemu-options.hx b/qemu-options.hx
index 69e5a874c1..8ddf6c8d36 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -6413,7 +6413,7 @@ SRST
CN=laptop.example.com,O=Example Home,L=London,ST=London,C=GB
- ``-object iothread,id=id,poll-max-ns=poll-max-ns,poll-grow=poll-grow,poll-shrink=poll-shrink,aio-max-batch=aio-max-batch``
+ ``-object iothread,id=id,poll-max-ns=poll-max-ns,poll-grow=poll-grow,poll-shrink=poll-shrink,poll-weight=poll-weight,aio-max-batch=aio-max-batch``
Creates a dedicated event loop thread that devices can be
assigned to. This is known as an IOThread. By default device
emulation happens in vCPU threads or the main event loop thread.
@@ -6449,6 +6449,11 @@ SRST
the polling time when the algorithm detects it is spending too
long polling without encountering events.
+ The ``poll-weight`` parameter is the weight factor used in the
+ adaptive polling algorithm. It determines how much the most
+ recent event interval affects the calculation of the next
+ polling duration.
+
The ``aio-max-batch`` parameter is the maximum number of requests
in a batch for the AIO engine, 0 means that the engine will use
its default.
diff --git a/tests/unit/test-nested-aio-poll.c b/tests/unit/test-nested-aio-poll.c
index 9ab1ad08a7..4c38f36fd4 100644
--- a/tests/unit/test-nested-aio-poll.c
+++ b/tests/unit/test-nested-aio-poll.c
@@ -81,7 +81,7 @@ static void test(void)
qemu_set_current_aio_context(td.ctx);
/* Enable polling */
- aio_context_set_poll_params(td.ctx, 1000000, 2, 2, &error_abort);
+ aio_context_set_poll_params(td.ctx, 1000000, 2, 2, 3, &error_abort);
/* Make the event notifier active (set) right away */
event_notifier_init(&td.poll_notifier, 1);
diff --git a/util/aio-posix.c b/util/aio-posix.c
index 2b3522f2f9..13b7f94911 100644
--- a/util/aio-posix.c
+++ b/util/aio-posix.c
@@ -29,7 +29,6 @@
/* Stop userspace polling on a handler if it isn't active for some time */
#define POLL_IDLE_INTERVAL_NS (7 * NANOSECONDS_PER_SECOND)
-#define POLL_WEIGHT_SHIFT (3)
static void adjust_block_ns(AioContext *ctx, int64_t block_ns);
static void grow_polling_time(AioContext *ctx, int64_t block_ns);
@@ -593,10 +592,6 @@ static void shrink_polling_time(AioContext *ctx, int64_t block_ns)
int64_t old = ctx->poll_ns;
int64_t shrink = ctx->poll_shrink;
- if (shrink == 0) {
- shrink = 2;
- }
-
if (block_ns < (ctx->poll_ns / shrink)) {
ctx->poll_ns /= shrink;
}
@@ -610,10 +605,6 @@ static void grow_polling_time(AioContext *ctx, int64_t block_ns)
int64_t old = ctx->poll_ns;
int64_t grow = ctx->poll_grow;
- if (grow == 0) {
- grow = 2;
- }
-
if (block_ns > ctx->poll_ns * grow) {
ctx->poll_ns = block_ns;
} else {
@@ -640,8 +631,8 @@ static void adjust_block_ns(AioContext *ctx, int64_t block_ns)
* poll.ns to smooth out polling time adjustments.
*/
node->poll.ns = node->poll.ns
- ? (node->poll.ns - (node->poll.ns >> POLL_WEIGHT_SHIFT))
- + (block_ns >> POLL_WEIGHT_SHIFT) : block_ns;
+ ? (node->poll.ns - (node->poll.ns >> ctx->poll_weight))
+ + (block_ns >> ctx->poll_weight) : block_ns;
if (node->poll.ns > ctx->poll_max_ns) {
node->poll.ns = 0;
@@ -831,7 +822,8 @@ void aio_context_destroy(AioContext *ctx)
}
void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
- int64_t grow, int64_t shrink, Error **errp)
+ int64_t grow, int64_t shrink,
+ int64_t weight, Error **errp)
{
AioHandler *node;
@@ -848,6 +840,7 @@ void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
ctx->poll_max_ns = max_ns;
ctx->poll_grow = grow;
ctx->poll_shrink = shrink;
+ ctx->poll_weight = weight;
ctx->poll_ns = 0;
aio_notify(ctx);
diff --git a/util/aio-win32.c b/util/aio-win32.c
index 6e6f699e4b..1985843233 100644
--- a/util/aio-win32.c
+++ b/util/aio-win32.c
@@ -429,7 +429,8 @@ void aio_context_destroy(AioContext *ctx)
}
void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
- int64_t grow, int64_t shrink, Error **errp)
+ int64_t grow, int64_t shrink,
+ int64_t weight, Error **errp)
{
if (max_ns) {
error_setg(errp, "AioContext polling is not implemented on Windows");
diff --git a/util/async.c b/util/async.c
index 9d3627566f..741fcfd6a7 100644
--- a/util/async.c
+++ b/util/async.c
@@ -609,6 +609,7 @@ AioContext *aio_context_new(Error **errp)
ctx->poll_ns = 0;
ctx->poll_grow = 0;
ctx->poll_shrink = 0;
+ ctx->poll_weight = 0;
ctx->aio_max_batch = 0;
--
2.50.1
On Mon, Mar 23, 2026 at 08:54:51AM -0500, Jaehoon Kim wrote:
> @@ -831,7 +822,8 @@ void aio_context_destroy(AioContext *ctx)
> }
>
> void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
> - int64_t grow, int64_t shrink, Error **errp)
> + int64_t grow, int64_t shrink,
> + int64_t weight, Error **errp)
> {
> AioHandler *node;
>
> @@ -848,6 +840,7 @@ void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
> ctx->poll_max_ns = max_ns;
> ctx->poll_grow = grow;
> ctx->poll_shrink = shrink;
> + ctx->poll_weight = weight;
> ctx->poll_ns = 0;
>
> aio_notify(ctx);
On second thought, now that the divide-by-0 protection has been removed
and these fields are assumed to hold a valid value when poll_max_ns > 0,
aio_context_set_poll_params() needs the same 0-protection as
iothread_set_param().
On 3/25/2026 11:56 AM, Stefan Hajnoczi wrote:
> On Mon, Mar 23, 2026 at 08:54:51AM -0500, Jaehoon Kim wrote:
>> @@ -831,7 +822,8 @@ void aio_context_destroy(AioContext *ctx)
>> }
>>
>> void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
>> - int64_t grow, int64_t shrink, Error **errp)
>> + int64_t grow, int64_t shrink,
>> + int64_t weight, Error **errp)
>> {
>> AioHandler *node;
>>
>> @@ -848,6 +840,7 @@ void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
>> ctx->poll_max_ns = max_ns;
>> ctx->poll_grow = grow;
>> ctx->poll_shrink = shrink;
>> + ctx->poll_weight = weight;
>> ctx->poll_ns = 0;
>>
>> aio_notify(ctx);
> On second thought, now that the divide-by-0 protection has been removed
> and these fields are assumed to hold a valid value when poll_max_ns > 0,
> aio_context_set_poll_params() needs the same 0-protection as
> iothread_set_param().
Thanks for pointing that out. I only considered the user settings and
overlooked the possibility of this being configured in test code or
other areas.
I will add divide-by-zero protection to aio_context_set_poll_params.
and update the versioning to 11.1 in the next version.
Regards,
Jaehoon
On Mon, Mar 23, 2026 at 08:54:51AM -0500, Jaehoon Kim wrote: > diff --git a/qapi/misc.json b/qapi/misc.json > index 28c641fe2f..39d17010bc 100644 > --- a/qapi/misc.json > +++ b/qapi/misc.json > @@ -85,6 +85,12 @@ > # @poll-shrink: how many ns will be removed from polling time, 0 means > # that it's not configured (since 2.9) > # > +# @poll-weight: the weight factor for adaptive polling. > +# Determines how much the current event interval contributes to > +# the next polling time calculation. Valid values are 1 or > +# greater. 0 selects the system default value which is current 3 > +# (since 10.2) QEMU 11.0 is already in hard freeze, so this patch will go into 11.1: (since 11.1) > +# > # @aio-max-batch: maximum number of requests in a batch for the AIO > # engine, 0 means that the engine will use its default (since 6.1) > # > @@ -96,6 +102,7 @@ > 'poll-max-ns': 'int', > 'poll-grow': 'int', > 'poll-shrink': 'int', > + 'poll-weight': 'int', > 'aio-max-batch': 'int' } } > > ## > diff --git a/qapi/qom.json b/qapi/qom.json > index c653248f85..feb80b6cfe 100644 > --- a/qapi/qom.json > +++ b/qapi/qom.json > @@ -606,6 +606,11 @@ > # algorithm detects it is spending too long polling without > # encountering events. 0 selects a default behaviour (default: 0) > # > +# @poll-weight: the weight factor for adaptive polling. > +# Determines how much the current event interval contributes to > +# the next polling time calculation. Valid values are 1 or > +# greater. If set to 0, the default value of 3 is used. (since 11.1) Other than that: Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Jaehoon Kim <jhkim@linux.ibm.com> writes:
> Introduce a configurable poll-weight parameter for adaptive polling
> in IOThread. This parameter replaces the hardcoded POLL_WEIGHT_SHIFT
> constant, allowing runtime control over how much the most recent
> event interval affects the next polling duration calculation.
>
> The poll-weight parameter uses a shift value where larger values
> decrease the weight of the current interval, enabling more gradual
> adjustments. When set to 0, a default value of 3 is used (meaning
> the current interval contributes approximately 1/8 to the weighted
> average).
>
> This patch also removes the hardcoded default values for poll-grow
> and poll-shrink parameters from the grow_polling_time() and
> shrink_polling_time() functions, as these defaults are now properly
> initialized in iothread.c during IOThread creation.
>
> Signed-off-by: Jaehoon Kim <jhkim@linux.ibm.com>
[...]
> diff --git a/qapi/misc.json b/qapi/misc.json
> index 28c641fe2f..39d17010bc 100644
> --- a/qapi/misc.json
> +++ b/qapi/misc.json
> @@ -85,6 +85,12 @@
Note: this is IOThreadInfo, used only as return value of
query-iothreads.
> # @poll-shrink: how many ns will be removed from polling time, 0 means
> # that it's not configured (since 2.9)
> #
> +# @poll-weight: the weight factor for adaptive polling.
> +# Determines how much the current event interval contributes to
> +# the next polling time calculation. Valid values are 1 or
> +# greater. 0 selects the system default value which is current 3
Does query-iothreads actually return 0? I'd expect it to return the
value that is actually used.
> +# (since 10.2)
11.1 most likely.
> +#
> # @aio-max-batch: maximum number of requests in a batch for the AIO
> # engine, 0 means that the engine will use its default (since 6.1)
> #
> @@ -96,6 +102,7 @@
> 'poll-max-ns': 'int',
> 'poll-grow': 'int',
> 'poll-shrink': 'int',
> + 'poll-weight': 'int',
> 'aio-max-batch': 'int' } }
>
> ##
> diff --git a/qapi/qom.json b/qapi/qom.json
> index c653248f85..feb80b6cfe 100644
> --- a/qapi/qom.json
> +++ b/qapi/qom.json
> @@ -606,6 +606,11 @@
> # algorithm detects it is spending too long polling without
> # encountering events. 0 selects a default behaviour (default: 0)
> #
> +# @poll-weight: the weight factor for adaptive polling.
> +# Determines how much the current event interval contributes to
> +# the next polling time calculation. Valid values are 1 or
> +# greater. If set to 0, the default value of 3 is used.
The commit message hints what the valid values mean, the doc comment
doesn't even that. Do users need to know?
Code [*] below uses it like time >> poll_weight, where @time is int64_t.
poll_weight > 63 is undefined behavior, which is a no-no. Please reject
such values. poll_weight == 64 results in zero. Is that useful?
Missing: (default: 0) (since 11.1)
> +#
> # The @aio-max-batch option is available since 6.1.
> #
> # Since: 2.0
> @@ -614,7 +619,8 @@
> 'base': 'EventLoopBaseProperties',
> 'data': { '*poll-max-ns': 'int',
> '*poll-grow': 'int',
> - '*poll-shrink': 'int' } }
> + '*poll-shrink': 'int',
> + '*poll-weight': 'int' } }
>
> ##
> # @MainLoopProperties:
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 69e5a874c1..8ddf6c8d36 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -6413,7 +6413,7 @@ SRST
>
> CN=laptop.example.com,O=Example Home,L=London,ST=London,C=GB
>
> - ``-object iothread,id=id,poll-max-ns=poll-max-ns,poll-grow=poll-grow,poll-shrink=poll-shrink,aio-max-batch=aio-max-batch``
> + ``-object iothread,id=id,poll-max-ns=poll-max-ns,poll-grow=poll-grow,poll-shrink=poll-shrink,poll-weight=poll-weight,aio-max-batch=aio-max-batch``
> Creates a dedicated event loop thread that devices can be
> assigned to. This is known as an IOThread. By default device
> emulation happens in vCPU threads or the main event loop thread.
> @@ -6449,6 +6449,11 @@ SRST
> the polling time when the algorithm detects it is spending too
> long polling without encountering events.
>
> + The ``poll-weight`` parameter is the weight factor used in the
> + adaptive polling algorithm. It determines how much the most
> + recent event interval affects the calculation of the next
> + polling duration.
> +
> The ``aio-max-batch`` parameter is the maximum number of requests
> in a batch for the AIO engine, 0 means that the engine will use
> its default.
> diff --git a/tests/unit/test-nested-aio-poll.c b/tests/unit/test-nested-aio-poll.c
> index 9ab1ad08a7..4c38f36fd4 100644
> --- a/tests/unit/test-nested-aio-poll.c
> +++ b/tests/unit/test-nested-aio-poll.c
> @@ -81,7 +81,7 @@ static void test(void)
> qemu_set_current_aio_context(td.ctx);
>
> /* Enable polling */
> - aio_context_set_poll_params(td.ctx, 1000000, 2, 2, &error_abort);
> + aio_context_set_poll_params(td.ctx, 1000000, 2, 2, 3, &error_abort);
>
> /* Make the event notifier active (set) right away */
> event_notifier_init(&td.poll_notifier, 1);
> diff --git a/util/aio-posix.c b/util/aio-posix.c
> index 2b3522f2f9..13b7f94911 100644
> --- a/util/aio-posix.c
> +++ b/util/aio-posix.c
> @@ -29,7 +29,6 @@
>
> /* Stop userspace polling on a handler if it isn't active for some time */
> #define POLL_IDLE_INTERVAL_NS (7 * NANOSECONDS_PER_SECOND)
> -#define POLL_WEIGHT_SHIFT (3)
>
> static void adjust_block_ns(AioContext *ctx, int64_t block_ns);
> static void grow_polling_time(AioContext *ctx, int64_t block_ns);
> @@ -593,10 +592,6 @@ static void shrink_polling_time(AioContext *ctx, int64_t block_ns)
> int64_t old = ctx->poll_ns;
> int64_t shrink = ctx->poll_shrink;
>
> - if (shrink == 0) {
> - shrink = 2;
> - }
> -
> if (block_ns < (ctx->poll_ns / shrink)) {
> ctx->poll_ns /= shrink;
> }
> @@ -610,10 +605,6 @@ static void grow_polling_time(AioContext *ctx, int64_t block_ns)
> int64_t old = ctx->poll_ns;
> int64_t grow = ctx->poll_grow;
>
> - if (grow == 0) {
> - grow = 2;
> - }
> -
> if (block_ns > ctx->poll_ns * grow) {
> ctx->poll_ns = block_ns;
> } else {
> @@ -640,8 +631,8 @@ static void adjust_block_ns(AioContext *ctx, int64_t block_ns)
> * poll.ns to smooth out polling time adjustments.
> */
> node->poll.ns = node->poll.ns
> - ? (node->poll.ns - (node->poll.ns >> POLL_WEIGHT_SHIFT))
> - + (block_ns >> POLL_WEIGHT_SHIFT) : block_ns;
> + ? (node->poll.ns - (node->poll.ns >> ctx->poll_weight))
> + + (block_ns >> ctx->poll_weight) : block_ns;
[*] This is the use of @poll-weight referred to above.
>
> if (node->poll.ns > ctx->poll_max_ns) {
> node->poll.ns = 0;
> @@ -831,7 +822,8 @@ void aio_context_destroy(AioContext *ctx)
> }
>
> void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
> - int64_t grow, int64_t shrink, Error **errp)
> + int64_t grow, int64_t shrink,
> + int64_t weight, Error **errp)
> {
> AioHandler *node;
>
> @@ -848,6 +840,7 @@ void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
> ctx->poll_max_ns = max_ns;
> ctx->poll_grow = grow;
> ctx->poll_shrink = shrink;
> + ctx->poll_weight = weight;
> ctx->poll_ns = 0;
>
> aio_notify(ctx);
[...]
On 3/25/2026 9:04 AM, Markus Armbruster wrote:
> Jaehoon Kim <jhkim@linux.ibm.com> writes:
>
>> Introduce a configurable poll-weight parameter for adaptive polling
>> in IOThread. This parameter replaces the hardcoded POLL_WEIGHT_SHIFT
>> constant, allowing runtime control over how much the most recent
>> event interval affects the next polling duration calculation.
>>
>> The poll-weight parameter uses a shift value where larger values
>> decrease the weight of the current interval, enabling more gradual
>> adjustments. When set to 0, a default value of 3 is used (meaning
>> the current interval contributes approximately 1/8 to the weighted
>> average).
>>
>> This patch also removes the hardcoded default values for poll-grow
>> and poll-shrink parameters from the grow_polling_time() and
>> shrink_polling_time() functions, as these defaults are now properly
>> initialized in iothread.c during IOThread creation.
>>
>> Signed-off-by: Jaehoon Kim <jhkim@linux.ibm.com>
> [...]
>
>> diff --git a/qapi/misc.json b/qapi/misc.json
>> index 28c641fe2f..39d17010bc 100644
>> --- a/qapi/misc.json
>> +++ b/qapi/misc.json
>> @@ -85,6 +85,12 @@
> Note: this is IOThreadInfo, used only as return value of
> query-iothreads.
>
>> # @poll-shrink: how many ns will be removed from polling time, 0 means
>> # that it's not configured (since 2.9)
>> #
>> +# @poll-weight: the weight factor for adaptive polling.
>> +# Determines how much the current event interval contributes to
>> +# the next polling time calculation. Valid values are 1 or
>> +# greater. 0 selects the system default value which is current 3
> Does query-iothreads actually return 0? I'd expect it to return the
> value that is actually used.
Thanks for your feedback.
Expose IOThread default poll parameters in iothread_instance_init() so
query shows system-initialized values for grow, shrink, and weight.
Also, I modified iothread_set_param() so that when the user input 0,
it reverts to the system default values.
>> +# (since 10.2)
> 11.1 most likely.
Thanks for noticing. I'll update the version to 11.1 in the next patch.
>> +#
>> # @aio-max-batch: maximum number of requests in a batch for the AIO
>> # engine, 0 means that the engine will use its default (since 6.1)
>> #
>> @@ -96,6 +102,7 @@
>> 'poll-max-ns': 'int',
>> 'poll-grow': 'int',
>> 'poll-shrink': 'int',
>> + 'poll-weight': 'int',
>> 'aio-max-batch': 'int' } }
>>
>> ##
>> diff --git a/qapi/qom.json b/qapi/qom.json
>> index c653248f85..feb80b6cfe 100644
>> --- a/qapi/qom.json
>> +++ b/qapi/qom.json
>> @@ -606,6 +606,11 @@
>> # algorithm detects it is spending too long polling without
>> # encountering events. 0 selects a default behaviour (default: 0)
>> #
>> +# @poll-weight: the weight factor for adaptive polling.
>> +# Determines how much the current event interval contributes to
>> +# the next polling time calculation. Valid values are 1 or
>> +# greater. If set to 0, the default value of 3 is used.
> The commit message hints what the valid values mean, the doc comment
> doesn't even that. Do users need to know?
>
> Code [*] below uses it like time >> poll_weight, where @time is int64_t.
> poll_weight > 63 is undefined behavior, which is a no-no. Please reject
> such values. poll_weight == 64 results in zero. Is that useful?
>
> Missing: (default: 0) (since 11.1)
I agree. I will update the doc comment to give users a practical hint,
for example like this:
# @poll-weight: the weight factor for adaptive polling.
# Determines how much the most recent event interval affects
# the next polling duration calculation.
# If set to 0, the system default value of 3 is used.
# Typical values: 1 (high weight on recent interval),
# 2-4 (moderate weight on recent interval).
# (default: 0) (since 11.1)
I will also a check in the code so that values exceeding the maximum
allowed will revert to the system default.
>> +#
>> # The @aio-max-batch option is available since 6.1.
>> #
>> # Since: 2.0
>> @@ -614,7 +619,8 @@
>> 'base': 'EventLoopBaseProperties',
>> 'data': { '*poll-max-ns': 'int',
>> '*poll-grow': 'int',
>> - '*poll-shrink': 'int' } }
>> + '*poll-shrink': 'int',
>> + '*poll-weight': 'int' } }
>>
>> ##
>> # @MainLoopProperties:
>> diff --git a/qemu-options.hx b/qemu-options.hx
>> index 69e5a874c1..8ddf6c8d36 100644
>> --- a/qemu-options.hx
>> +++ b/qemu-options.hx
>> @@ -6413,7 +6413,7 @@ SRST
>>
>> CN=laptop.example.com,O=Example Home,L=London,ST=London,C=GB
>>
>> - ``-object iothread,id=id,poll-max-ns=poll-max-ns,poll-grow=poll-grow,poll-shrink=poll-shrink,aio-max-batch=aio-max-batch``
>> + ``-object iothread,id=id,poll-max-ns=poll-max-ns,poll-grow=poll-grow,poll-shrink=poll-shrink,poll-weight=poll-weight,aio-max-batch=aio-max-batch``
>> Creates a dedicated event loop thread that devices can be
>> assigned to. This is known as an IOThread. By default device
>> emulation happens in vCPU threads or the main event loop thread.
>> @@ -6449,6 +6449,11 @@ SRST
>> the polling time when the algorithm detects it is spending too
>> long polling without encountering events.
>>
>> + The ``poll-weight`` parameter is the weight factor used in the
>> + adaptive polling algorithm. It determines how much the most
>> + recent event interval affects the calculation of the next
>> + polling duration.
>> +
>> The ``aio-max-batch`` parameter is the maximum number of requests
>> in a batch for the AIO engine, 0 means that the engine will use
>> its default.
>> diff --git a/tests/unit/test-nested-aio-poll.c b/tests/unit/test-nested-aio-poll.c
>> index 9ab1ad08a7..4c38f36fd4 100644
>> --- a/tests/unit/test-nested-aio-poll.c
>> +++ b/tests/unit/test-nested-aio-poll.c
>> @@ -81,7 +81,7 @@ static void test(void)
>> qemu_set_current_aio_context(td.ctx);
>>
>> /* Enable polling */
>> - aio_context_set_poll_params(td.ctx, 1000000, 2, 2, &error_abort);
>> + aio_context_set_poll_params(td.ctx, 1000000, 2, 2, 3, &error_abort);
>>
>> /* Make the event notifier active (set) right away */
>> event_notifier_init(&td.poll_notifier, 1);
>> diff --git a/util/aio-posix.c b/util/aio-posix.c
>> index 2b3522f2f9..13b7f94911 100644
>> --- a/util/aio-posix.c
>> +++ b/util/aio-posix.c
>> @@ -29,7 +29,6 @@
>>
>> /* Stop userspace polling on a handler if it isn't active for some time */
>> #define POLL_IDLE_INTERVAL_NS (7 * NANOSECONDS_PER_SECOND)
>> -#define POLL_WEIGHT_SHIFT (3)
>>
>> static void adjust_block_ns(AioContext *ctx, int64_t block_ns);
>> static void grow_polling_time(AioContext *ctx, int64_t block_ns);
>> @@ -593,10 +592,6 @@ static void shrink_polling_time(AioContext *ctx, int64_t block_ns)
>> int64_t old = ctx->poll_ns;
>> int64_t shrink = ctx->poll_shrink;
>>
>> - if (shrink == 0) {
>> - shrink = 2;
>> - }
>> -
>> if (block_ns < (ctx->poll_ns / shrink)) {
>> ctx->poll_ns /= shrink;
>> }
>> @@ -610,10 +605,6 @@ static void grow_polling_time(AioContext *ctx, int64_t block_ns)
>> int64_t old = ctx->poll_ns;
>> int64_t grow = ctx->poll_grow;
>>
>> - if (grow == 0) {
>> - grow = 2;
>> - }
>> -
>> if (block_ns > ctx->poll_ns * grow) {
>> ctx->poll_ns = block_ns;
>> } else {
>> @@ -640,8 +631,8 @@ static void adjust_block_ns(AioContext *ctx, int64_t block_ns)
>> * poll.ns to smooth out polling time adjustments.
>> */
>> node->poll.ns = node->poll.ns
>> - ? (node->poll.ns - (node->poll.ns >> POLL_WEIGHT_SHIFT))
>> - + (block_ns >> POLL_WEIGHT_SHIFT) : block_ns;
>> + ? (node->poll.ns - (node->poll.ns >> ctx->poll_weight))
>> + + (block_ns >> ctx->poll_weight) : block_ns;
> [*] This is the use of @poll-weight referred to above.
>
>>
>> if (node->poll.ns > ctx->poll_max_ns) {
>> node->poll.ns = 0;
>> @@ -831,7 +822,8 @@ void aio_context_destroy(AioContext *ctx)
>> }
>>
>> void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
>> - int64_t grow, int64_t shrink, Error **errp)
>> + int64_t grow, int64_t shrink,
>> + int64_t weight, Error **errp)
>> {
>> AioHandler *node;
>>
>> @@ -848,6 +840,7 @@ void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,
>> ctx->poll_max_ns = max_ns;
>> ctx->poll_grow = grow;
>> ctx->poll_shrink = shrink;
>> + ctx->poll_weight = weight;
>> ctx->poll_ns = 0;
>>
>> aio_notify(ctx);
> [...]
>
JAEHOON KIM <jhkim@linux.ibm.com> writes: > On 3/25/2026 9:04 AM, Markus Armbruster wrote: >> Jaehoon Kim <jhkim@linux.ibm.com> writes: >> >>> Introduce a configurable poll-weight parameter for adaptive polling >>> in IOThread. This parameter replaces the hardcoded POLL_WEIGHT_SHIFT >>> constant, allowing runtime control over how much the most recent >>> event interval affects the next polling duration calculation. >>> >>> The poll-weight parameter uses a shift value where larger values >>> decrease the weight of the current interval, enabling more gradual >>> adjustments. When set to 0, a default value of 3 is used (meaning >>> the current interval contributes approximately 1/8 to the weighted >>> average). >>> >>> This patch also removes the hardcoded default values for poll-grow >>> and poll-shrink parameters from the grow_polling_time() and >>> shrink_polling_time() functions, as these defaults are now properly >>> initialized in iothread.c during IOThread creation. >>> >>> Signed-off-by: Jaehoon Kim <jhkim@linux.ibm.com> [...] >>> diff --git a/qapi/qom.json b/qapi/qom.json >>> index c653248f85..feb80b6cfe 100644 >>> --- a/qapi/qom.json >>> +++ b/qapi/qom.json >>> @@ -606,6 +606,11 @@ >>> # algorithm detects it is spending too long polling without >>> # encountering events. 0 selects a default behaviour (default: 0) >>> # >>> +# @poll-weight: the weight factor for adaptive polling. >>> +# Determines how much the current event interval contributes to >>> +# the next polling time calculation. Valid values are 1 or >>> +# greater. If set to 0, the default value of 3 is used. >> >> The commit message hints what the valid values mean, the doc comment >> doesn't even that. Do users need to know? >> >> Code [*] below uses it like time >> poll_weight, where @time is int64_t. >> poll_weight > 63 is undefined behavior, which is a no-no. Please reject >> such values. poll_weight == 64 results in zero. Is that useful? >> >> Missing: (default: 0) (since 11.1) > > I agree. I will update the doc comment to give users a practical hint, > for example like this: > > # @poll-weight: the weight factor for adaptive polling. > # Determines how much the most recent event interval affects > # the next polling duration calculation. > # If set to 0, the system default value of 3 is used. > # Typical values: 1 (high weight on recent interval), > # 2-4 (moderate weight on recent interval). > # (default: 0) (since 11.1) Better, thanks! > I will also a check in the code so that values exceeding the maximum > allowed will revert to the system default. Don't silently "correct" invalid input, reject it! [...]
On 3/27/2026 12:49 AM, Markus Armbruster wrote: > JAEHOON KIM <jhkim@linux.ibm.com> writes: > >> On 3/25/2026 9:04 AM, Markus Armbruster wrote: >>> Jaehoon Kim <jhkim@linux.ibm.com> writes: >>> >>>> Introduce a configurable poll-weight parameter for adaptive polling >>>> in IOThread. This parameter replaces the hardcoded POLL_WEIGHT_SHIFT >>>> constant, allowing runtime control over how much the most recent >>>> event interval affects the next polling duration calculation. >>>> >>>> The poll-weight parameter uses a shift value where larger values >>>> decrease the weight of the current interval, enabling more gradual >>>> adjustments. When set to 0, a default value of 3 is used (meaning >>>> the current interval contributes approximately 1/8 to the weighted >>>> average). >>>> >>>> This patch also removes the hardcoded default values for poll-grow >>>> and poll-shrink parameters from the grow_polling_time() and >>>> shrink_polling_time() functions, as these defaults are now properly >>>> initialized in iothread.c during IOThread creation. >>>> >>>> Signed-off-by: Jaehoon Kim <jhkim@linux.ibm.com> > [...] > >>>> diff --git a/qapi/qom.json b/qapi/qom.json >>>> index c653248f85..feb80b6cfe 100644 >>>> --- a/qapi/qom.json >>>> +++ b/qapi/qom.json >>>> @@ -606,6 +606,11 @@ >>>> # algorithm detects it is spending too long polling without >>>> # encountering events. 0 selects a default behaviour (default: 0) >>>> # >>>> +# @poll-weight: the weight factor for adaptive polling. >>>> +# Determines how much the current event interval contributes to >>>> +# the next polling time calculation. Valid values are 1 or >>>> +# greater. If set to 0, the default value of 3 is used. >>> The commit message hints what the valid values mean, the doc comment >>> doesn't even that. Do users need to know? >>> >>> Code [*] below uses it like time >> poll_weight, where @time is int64_t. >>> poll_weight > 63 is undefined behavior, which is a no-no. Please reject >>> such values. poll_weight == 64 results in zero. Is that useful? >>> >>> Missing: (default: 0) (since 11.1) >> I agree. I will update the doc comment to give users a practical hint, >> for example like this: >> >> # @poll-weight: the weight factor for adaptive polling. >> # Determines how much the most recent event interval affects >> # the next polling duration calculation. >> # If set to 0, the system default value of 3 is used. >> # Typical values: 1 (high weight on recent interval), >> # 2-4 (moderate weight on recent interval). >> # (default: 0) (since 11.1) > Better, thanks! > >> I will also a check in the code so that values exceeding the maximum >> allowed will revert to the system default. > Don't silently "correct" invalid input, reject it! > > [...] Hi Markus, Thank you for the review. I will change the code to reject invalid values instead of silently correct them in the next version. Regards, Jaehoon. >
© 2016 - 2026 Red Hat, Inc.