[PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll

Jaehoon Kim posted 3 patches 1 week, 4 days ago
Maintainers: Stefan Hajnoczi <stefanha@redhat.com>, Fam Zheng <fam@euphon.net>, "Dr. David Alan Gilbert" <dave@treblig.org>, Eric Blake <eblake@redhat.com>, Markus Armbruster <armbru@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>, "Daniel P. Berrangé" <berrange@redhat.com>, Stefan Weil <sw@weilnetz.de>
[PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll
Posted by Jaehoon Kim 1 week, 4 days ago
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
Re: [PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll
Posted by Stefan Hajnoczi 1 week, 2 days ago
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().
Re: [PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll
Posted by JAEHOON KIM 1 week, 1 day ago
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
Re: [PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll
Posted by Stefan Hajnoczi 1 week, 2 days ago
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>
Re: [PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll
Posted by Markus Armbruster 1 week, 2 days ago
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);

[...]
Re: [PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll
Posted by JAEHOON KIM 1 week, 1 day ago
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);
> [...]
>


Re: [PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll
Posted by Markus Armbruster 1 week ago
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!

[...]
Re: [PATCH RFC v2 3/3] qapi/iothread: introduce poll-weight parameter for aio-poll
Posted by JAEHOON KIM 1 week ago
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.

>