get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.2/patches/2220017/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2220017,
    "url": "http://patchwork.ozlabs.org/api/1.2/patches/2220017/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260405200735.3075407-3-jhkim@linux.ibm.com/",
    "project": {
        "id": 14,
        "url": "http://patchwork.ozlabs.org/api/1.2/projects/14/?format=api",
        "name": "QEMU Development",
        "link_name": "qemu-devel",
        "list_id": "qemu-devel.nongnu.org",
        "list_email": "qemu-devel@nongnu.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": "",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<20260405200735.3075407-3-jhkim@linux.ibm.com>",
    "list_archive_url": null,
    "date": "2026-04-05T20:07:33",
    "name": "[v3,2/3] aio-poll: refine iothread polling using weighted handler intervals",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "863f1bddfd5aabdab1e9efb6205a8992e3a350b1",
    "submitter": {
        "id": 91012,
        "url": "http://patchwork.ozlabs.org/api/1.2/people/91012/?format=api",
        "name": "Jaehoon Kim",
        "email": "jhkim@linux.ibm.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260405200735.3075407-3-jhkim@linux.ibm.com/mbox/",
    "series": [
        {
            "id": 498805,
            "url": "http://patchwork.ozlabs.org/api/1.2/series/498805/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=498805",
            "date": "2026-04-05T20:07:32",
            "name": "improve aio-polling efficiency",
            "version": 3,
            "mbox": "http://patchwork.ozlabs.org/series/498805/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2220017/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2220017/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>",
        "X-Original-To": "incoming@patchwork.ozlabs.org",
        "Delivered-To": "patchwork-incoming@legolas.ozlabs.org",
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=ibm.com header.i=@ibm.com header.a=rsa-sha256\n header.s=pp1 header.b=rXBwZeQw;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)"
        ],
        "Received": [
            "from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fpk7C2PXJz1yG0\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 06 Apr 2026 06:08:53 +1000 (AEST)",
            "from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1w9TlA-0005fq-OC; Sun, 05 Apr 2026 16:07:52 -0400",
            "from eggs.gnu.org ([2001:470:142:3::10])\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <jhkim@linux.ibm.com>)\n id 1w9Tl7-0005e2-Li; Sun, 05 Apr 2026 16:07:49 -0400",
            "from mx0a-001b2d01.pphosted.com ([148.163.156.1])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <jhkim@linux.ibm.com>)\n id 1w9Tl4-00036h-O5; Sun, 05 Apr 2026 16:07:49 -0400",
            "from pps.filterd (m0356517.ppops.net [127.0.0.1])\n by mx0a-001b2d01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id\n 635GKlp83045555; Sun, 5 Apr 2026 20:07:43 GMT",
            "from ppma11.dal12v.mail.ibm.com\n (db.9e.1632.ip4.static.sl-reverse.com [50.22.158.219])\n by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4datc2m7pb-1\n (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT);\n Sun, 05 Apr 2026 20:07:43 +0000 (GMT)",
            "from pps.filterd (ppma11.dal12v.mail.ibm.com [127.0.0.1])\n by ppma11.dal12v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id\n 635Hb5Hk006367;\n Sun, 5 Apr 2026 20:07:42 GMT",
            "from smtprelay05.dal12v.mail.ibm.com ([172.16.1.7])\n by ppma11.dal12v.mail.ibm.com (PPS) with ESMTPS id 4dbfp1j2cd-1\n (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT);\n Sun, 05 Apr 2026 20:07:42 +0000",
            "from smtpav02.dal12v.mail.ibm.com (smtpav02.dal12v.mail.ibm.com\n [10.241.53.101])\n by smtprelay05.dal12v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id\n 635K7f2q26083950\n (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK);\n Sun, 5 Apr 2026 20:07:41 GMT",
            "from smtpav02.dal12v.mail.ibm.com (unknown [127.0.0.1])\n by IMSVA (Postfix) with ESMTP id 6C8B358051;\n Sun,  5 Apr 2026 20:07:41 +0000 (GMT)",
            "from smtpav02.dal12v.mail.ibm.com (unknown [127.0.0.1])\n by IMSVA (Postfix) with ESMTP id 2280B5805A;\n Sun,  5 Apr 2026 20:07:41 +0000 (GMT)",
            "from IBM-GLTZVH3.ibm.com (unknown [9.61.243.136])\n by smtpav02.dal12v.mail.ibm.com (Postfix) with ESMTP;\n Sun,  5 Apr 2026 20:07:41 +0000 (GMT)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc\n :content-transfer-encoding:date:from:in-reply-to:message-id\n :mime-version:references:subject:to; s=pp1; bh=rjNuhebYUg6vqJ5le\n MWXD+6D1lC7nXdGyxXk4HOp6WI=; b=rXBwZeQw8TRGwN0FRhssmRVQIm8H/KNfF\n MYnnYdOTF0hXfjqeuLJQlpka3QgrPLqgfKmZ++BiGfRIjSsW/zYN+mldNWYPdIVj\n reSAyBkrGJ9jCg6kq9AJfkXuv3f8Jop6RVA/igsU4x0K1UYs3youYYRx5kOXpVq6\n uenO3EwGZX0nb0cZa+/ad80Za0aGmYO7f5YM5UNsJsRxSjkY6yWfV/iNCcBS0j7N\n 9UbliX4jMRevqqSnEiV6cVvhRHQkv+mSY/alwxadgo896qRrptIpHyIZN4UOfItf\n gfQuoyqD4muu7Fe33GMi2YvMXz9YdvbUM5w7zdtf/nsBVFhEY/pqQ==",
        "From": "Jaehoon Kim <jhkim@linux.ibm.com>",
        "To": "qemu-devel@nongnu.org, qemu-block@nongnu.org",
        "Cc": "pbonzini@redhat.com, stefanha@redhat.com, fam@euphon.net,\n armbru@redhat.com, eblake@redhat.com, berrange@redhat.com,\n eduardo@habkost.net, dave@treblig.org, sw@weilnetz.de,\n mjrosato@linux.ibm.com, farman@linux.ibm.com,\n Jaehoon Kim <jhkim@linux.ibm.com>",
        "Subject": "[PATCH v3 2/3] aio-poll: refine iothread polling using weighted\n handler intervals",
        "Date": "Sun,  5 Apr 2026 15:07:33 -0500",
        "Message-ID": "<20260405200735.3075407-3-jhkim@linux.ibm.com>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<20260405200735.3075407-1-jhkim@linux.ibm.com>",
        "References": "<20260405200735.3075407-1-jhkim@linux.ibm.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-TM-AS-GCONF": "00",
        "X-Proofpoint-Spam-Details-Enc": "AW1haW4tMjYwNDA1MDIwNiBTYWx0ZWRfX+3sK9ZjCUAk8\n y9K39AXd0FjiYM9Kx5PBIloon6Z+EC6pQOaR6ouuB5UCpc7HfOk9dG3iqnUAfNT+Lgfm5SBpflH\n pVAD6hWvHLhBwZ+BUujWCe7q1Q5o6Y3x6VYaczRSJ1n3VEsFPnK1ErwrvdXdRJzhImEorAnN4k0\n YP/eNHXBvAS3BGn59rEhfqwlCvcXdtO2ivVsnfacQ5wZ8RqSLg0gWGlZyI6w8XaMfJ8fXoP5TDZ\n GURl5YKhjOe2iDCrjVztxhzaO1AeTTegeQsPkX4Eo7XnAqAiGMAy9JZDkBQu6WPu1H6/IgcyyDm\n kOg9FjlEjXg5mYcYpIC8lmrzGhfhxgOfsjfKA6SbW2iLIhE1oTMLeTb4mqB4UQHH0PX+rSAgm10\n kV1ucsvTQS2uwOhgDLhO+HrlE7+WFX0W7YNiNZ2R5I4AAYjRSWYQHewMklqoNKxDwc4ThQKnGSU\n gIdziT24YwV/OrmhI2g==",
        "X-Proofpoint-GUID": "_K0uGTQqG-ZBIioVqDva0qT18g8OXMfq",
        "X-Proofpoint-ORIG-GUID": "_K0uGTQqG-ZBIioVqDva0qT18g8OXMfq",
        "X-Authority-Analysis": "v=2.4 cv=HJvO14tv c=1 sm=1 tr=0 ts=69d2c10f cx=c_pps\n a=aDMHemPKRhS1OARIsFnwRA==:117 a=aDMHemPKRhS1OARIsFnwRA==:17\n a=A5OVakUREuEA:10 a=VkNPw1HP01LnGYTKEx00:22 a=RnoormkPH1_aCDwRdu11:22\n a=U7nrCbtTmkRpXpFmAIza:22 a=VnNF1IyMAAAA:8 a=7aeLzZfa0VvpqtFDgoYA:9",
        "X-Proofpoint-Virus-Version": "vendor=baseguard\n engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.51,FMLib:17.12.100.49\n definitions=2026-04-05_06,2026-04-03_01,2025-10-01_01",
        "X-Proofpoint-Spam-Details": "rule=outbound_notspam policy=outbound score=0\n impostorscore=0 suspectscore=0 clxscore=1015 lowpriorityscore=0 adultscore=0\n malwarescore=0 spamscore=0 phishscore=0 priorityscore=1501 bulkscore=0\n classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0\n reason=mlx scancount=1 engine=8.22.0-2603050001 definitions=main-2604050206",
        "Received-SPF": "pass client-ip=148.163.156.1; envelope-from=jhkim@linux.ibm.com;\n helo=mx0a-001b2d01.pphosted.com",
        "X-Spam_score_int": "-26",
        "X-Spam_score": "-2.7",
        "X-Spam_bar": "--",
        "X-Spam_report": "(-2.7 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7,\n RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001,\n RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001,\n SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no",
        "X-Spam_action": "no action",
        "X-BeenThere": "qemu-devel@nongnu.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "qemu development <qemu-devel.nongnu.org>",
        "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>",
        "List-Post": "<mailto:qemu-devel@nongnu.org>",
        "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>",
        "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>",
        "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org",
        "Sender": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org"
    },
    "content": "Improve adaptive polling by updating each AioHandler's poll.ns\nevery loop iteration using weighted averages. This reduces CPU\nconsumption while minimizing performance impact.\n\nBackground:\nStarting from QEMU 10.0, poll.ns was introduced per event handler\nto mitigate excessive fluctuations in IOThread polling times\nobserved in earlier versions (QEMU 9.x). However, the current\ndesign has limitations:\n\n1. poll.ns is updated only when an event occurs, making it\n   difficult to treat block_ns as a reliable event interval.\n2. The IOThread's next polling time is determined by the maximum\n   poll.ns among all AioHandlers, meaning idle AioHandlers with\n   high poll.ns can have an outsized impact on polling duration.\n3. For io_uring, idle AioHandlers are cleared after\n   POLL_IDLE_INTERVAL_NS (7s), but for ppoll/epoll there is no\n   such mechanism, leading to increased CPU consumption from idle\n   nodes.\n\nImplementation:\nThis patch treats block_ns as an event interval and updates each\nAioHandler's poll.ns in every loop iteration:\n\n- Active handlers (with events): poll.ns is updated using a\n  weighted average of the current block_ns and previous poll.ns,\n  smoothing out adjustments and preventing excessive fluctuations.\n- Inactive handlers (no events): poll.ns accumulates block_ns\n  without weighting, allowing rapid isolation of idle nodes. When\n  poll.ns exceeds poll_max_ns, it resets to 0, preventing\n  sporadically active handlers from unnecessarily prolonging\n  iothread polling.\n- The iothread polling duration is set based on the largest poll.ns\n  among active handlers. The shrink divider defaults to 2, matching\n  the grow rate, to reduce frequent poll_ns resets for slow devices.\n\nThe implementation renames poll_idle_timeout to last_dispatch_timestamp\nfor use as an active handler identifier.\n\nTesting:\nPOLL_WEIGHT_SHIFT=3 (12.5% weight) was selected based on testing\ncomparing baseline vs weight=2/3 across various workloads:\n\nThe table below shows a comparison between:\n-Host: RHEL 10.1 GA + qemu-10.0.0-14.el10_1, Guest: RHEL 9.6GA vs.\n-Host: RHEL 10.1 GA + qemu-10.0.0-14.el10_1 (w=2/w=3), Guest: RHEL 9.6GA\nfor FIO FCP and FICON with 1 iothread and 8 iothreads.\nThe values shown are the averages for numjobs 1, 4, and 8.\n\nSummary of results (% change vs baseline):\n\n                    | poll-weight=2      | poll-weight=3\n--------------------|--------------------|-----------------\nThroughput avg      | -2.4% (all tests)  | -2.2% (all tests)\nCPU consumption avg | -10.9% (all tests) | -9.4% (all tests)\n\nBoth configurations achieve ~10% CPU reduction with minimal\nthroughput impact (~2%), addressing the QEMU 10.0.0 CPU regression.\nWeight=3 is chosen as default for its slightly better throughput\nwhile maintaining substantial CPU savings.\n\nSigned-off-by: Jaehoon Kim <jhkim@linux.ibm.com>\n---\n include/qemu/aio.h |   3 +-\n util/aio-posix.c   | 130 ++++++++++++++++++++++++++++++---------------\n util/aio-posix.h   |   2 +-\n util/async.c       |   1 +\n 4 files changed, 90 insertions(+), 46 deletions(-)",
    "diff": "diff --git a/include/qemu/aio.h b/include/qemu/aio.h\nindex 8cca2360d1..6c22064a28 100644\n--- a/include/qemu/aio.h\n+++ b/include/qemu/aio.h\n@@ -195,7 +195,7 @@ struct BHListSlice {\n typedef QSLIST_HEAD(, AioHandler) AioHandlerSList;\n \n typedef struct AioPolledEvent {\n-    int64_t ns;        /* current polling time in nanoseconds */\n+    int64_t ns;     /* estimated block time in nanoseconds */\n } AioPolledEvent;\n \n struct AioContext {\n@@ -306,6 +306,7 @@ struct AioContext {\n     int poll_disable_cnt;\n \n     /* Polling mode parameters */\n+    int64_t poll_ns;        /* current polling time in nanoseconds */\n     int64_t poll_max_ns;    /* maximum polling time in nanoseconds */\n     int64_t poll_grow;      /* polling time growth factor */\n     int64_t poll_shrink;    /* polling time shrink factor */\ndiff --git a/util/aio-posix.c b/util/aio-posix.c\nindex 351847c6fb..8e9e9e5d8f 100644\n--- a/util/aio-posix.c\n+++ b/util/aio-posix.c\n@@ -29,9 +29,11 @@\n \n /* Stop userspace polling on a handler if it isn't active for some time */\n #define POLL_IDLE_INTERVAL_NS (7 * NANOSECONDS_PER_SECOND)\n+#define POLL_WEIGHT_SHIFT   (3)\n \n-static void adjust_polling_time(AioContext *ctx, AioPolledEvent *poll,\n-                                int64_t block_ns);\n+static void update_handler_poll_times(AioContext *ctx, int64_t block_ns,\n+                                      int64_t dispatch_time);\n+static void adjust_polling_time(AioContext *ctx, int64_t block_ns);\n \n bool aio_poll_disabled(AioContext *ctx)\n {\n@@ -359,7 +361,7 @@ static bool aio_dispatch_handler(AioContext *ctx, AioHandler *node)\n \n static bool aio_dispatch_ready_handlers(AioContext *ctx,\n                                         AioHandlerList *ready_list,\n-                                        int64_t block_ns)\n+                                        int64_t dispatch_time)\n {\n     bool progress = false;\n     AioHandler *node;\n@@ -369,11 +371,11 @@ static bool aio_dispatch_ready_handlers(AioContext *ctx,\n         progress = aio_dispatch_handler(ctx, node) || progress;\n \n         /*\n-         * Adjust polling time only after aio_dispatch_handler(), which can\n-         * add the handler to ctx->poll_aio_handlers.\n+         * Update last_dispatch_timestamp to mark this as an active\n+         * handler for polling time adjustment and prevent idle removal.\n          */\n         if (ctx->poll_max_ns && QLIST_IS_INSERTED(node, node_poll)) {\n-            adjust_polling_time(ctx, &node->poll, block_ns);\n+            node->last_dispatch_timestamp = dispatch_time;\n         }\n     }\n \n@@ -394,7 +396,7 @@ void aio_dispatch(AioContext *ctx)\n         ctx->fdmon_ops->dispatch(ctx);\n     }\n \n-    /* block_ns is 0 because polling is disabled in the glib event loop */\n+    /* Set now to 0 as polling is disabled in the glib event loop */\n     aio_dispatch_ready_handlers(ctx, &ready_list, 0);\n \n     aio_free_deleted_handlers(ctx);\n@@ -415,9 +417,6 @@ static bool run_poll_handlers_once(AioContext *ctx,\n     QLIST_FOREACH_SAFE(node, &ctx->poll_aio_handlers, node_poll, tmp) {\n         if (node->io_poll(node->opaque)) {\n             aio_add_poll_ready_handler(ready_list, node);\n-\n-            node->poll_idle_timeout = now + POLL_IDLE_INTERVAL_NS;\n-\n             /*\n              * Polling was successful, exit try_poll_mode immediately\n              * to adjust the next polling time.\n@@ -458,11 +457,10 @@ static bool remove_idle_poll_handlers(AioContext *ctx,\n     }\n \n     QLIST_FOREACH_SAFE(node, &ctx->poll_aio_handlers, node_poll, tmp) {\n-        if (node->poll_idle_timeout == 0LL) {\n-            node->poll_idle_timeout = now + POLL_IDLE_INTERVAL_NS;\n-        } else if (now >= node->poll_idle_timeout) {\n+        if (node->poll_ready == false &&\n+            now >= node->last_dispatch_timestamp + POLL_IDLE_INTERVAL_NS) {\n             trace_poll_remove(ctx, node, node->pfd.fd);\n-            node->poll_idle_timeout = 0LL;\n+            node->last_dispatch_timestamp = 0LL;\n             QLIST_SAFE_REMOVE(node, node_poll);\n             if (ctx->poll_started && node->io_poll_end) {\n                 node->io_poll_end(node->opaque);\n@@ -560,18 +558,13 @@ static bool run_poll_handlers(AioContext *ctx, AioHandlerList *ready_list,\n static bool try_poll_mode(AioContext *ctx, AioHandlerList *ready_list,\n                           int64_t *timeout)\n {\n-    AioHandler *node;\n     int64_t max_ns;\n \n     if (QLIST_EMPTY_RCU(&ctx->poll_aio_handlers)) {\n         return false;\n     }\n \n-    max_ns = 0;\n-    QLIST_FOREACH(node, &ctx->poll_aio_handlers, node_poll) {\n-        max_ns = MAX(max_ns, node->poll.ns);\n-    }\n-    max_ns = qemu_soonest_timeout(*timeout, max_ns);\n+    max_ns = qemu_soonest_timeout(*timeout, ctx->poll_ns);\n \n     if (max_ns && !ctx->fdmon_ops->need_wait(ctx)) {\n         /*\n@@ -587,43 +580,85 @@ static bool try_poll_mode(AioContext *ctx, AioHandlerList *ready_list,\n     return false;\n }\n \n-static void adjust_polling_time(AioContext *ctx, AioPolledEvent *poll,\n-                                int64_t block_ns)\n+static void adjust_polling_time(AioContext *ctx, int64_t block_ns)\n {\n-    if (block_ns <= poll->ns) {\n-        /* This is the sweet spot, no adjustment needed */\n-    } else if (block_ns > ctx->poll_max_ns) {\n-        /* We'd have to poll for too long, poll less */\n-        int64_t old = poll->ns;\n-\n-        if (ctx->poll_shrink) {\n-            poll->ns /= ctx->poll_shrink;\n-        } else {\n-            poll->ns = 0;\n+    if (block_ns < ctx->poll_ns) {\n+        int64_t old = ctx->poll_ns;\n+        int64_t shrink = ctx->poll_shrink;\n+\n+        if (shrink == 0) {\n+            shrink = 2;\n+        }\n+\n+        if (block_ns < (ctx->poll_ns / shrink)) {\n+            ctx->poll_ns /= shrink;\n         }\n \n-        trace_poll_shrink(ctx, old, poll->ns);\n-    } else if (poll->ns < ctx->poll_max_ns &&\n-               block_ns < ctx->poll_max_ns) {\n+        trace_poll_shrink(ctx, old, ctx->poll_ns);\n+    } else if (block_ns > ctx->poll_ns) {\n         /* There is room to grow, poll longer */\n-        int64_t old = poll->ns;\n+        int64_t old = ctx->poll_ns;\n         int64_t grow = ctx->poll_grow;\n \n         if (grow == 0) {\n             grow = 2;\n         }\n \n-        if (poll->ns) {\n-            poll->ns *= grow;\n+        if (block_ns > ctx->poll_ns * grow) {\n+            ctx->poll_ns = block_ns;\n         } else {\n-            poll->ns = 4000; /* start polling at 4 microseconds */\n+            ctx->poll_ns *= grow;\n         }\n \n-        if (poll->ns > ctx->poll_max_ns) {\n-            poll->ns = ctx->poll_max_ns;\n+        if (ctx->poll_ns > ctx->poll_max_ns) {\n+            ctx->poll_ns = ctx->poll_max_ns;\n         }\n \n-        trace_poll_grow(ctx, old, poll->ns);\n+        trace_poll_grow(ctx, old, ctx->poll_ns);\n+    }\n+}\n+\n+static void update_handler_poll_times(AioContext *ctx, int64_t block_ns,\n+                                      int64_t dispatch_time)\n+{\n+    AioHandler *node;\n+    int64_t max_poll_ns = -1;\n+\n+    QLIST_FOREACH(node, &ctx->poll_aio_handlers, node_poll) {\n+        if (node->last_dispatch_timestamp == dispatch_time) {\n+            /*\n+             * Active handler: had an event in this aio_poll() call.\n+             * Update poll.ns using a weighted average of the current\n+             * block_ns and previous poll.ns to smooth adjustments.\n+             */\n+            node->poll.ns = node->poll.ns\n+                ? (node->poll.ns - (node->poll.ns >> POLL_WEIGHT_SHIFT))\n+                + (block_ns >> POLL_WEIGHT_SHIFT) : block_ns;\n+\n+            if (node->poll.ns > ctx->poll_max_ns) {\n+                node->poll.ns = 0;\n+            }\n+            /*\n+             * Track the maximum poll.ns among active handlers to\n+             * calculate the next polling time.\n+             */\n+            max_poll_ns = MAX(max_poll_ns, node->poll.ns);\n+        } else {\n+            /*\n+             * Inactive handler: no event in this aio_poll() call but\n+             * was active before. Increase poll.ns by block_ns. If it\n+             * exceeds poll_max_ns, reset to 0 until next event.\n+             */\n+            if (node->poll.ns != 0) {\n+                node->poll.ns += block_ns;\n+                if (node->poll.ns > ctx->poll_max_ns) {\n+                    node->poll.ns = 0;\n+                }\n+            }\n+        }\n+    }\n+    if (max_poll_ns >= 0) {\n+        adjust_polling_time(ctx, max_poll_ns);\n     }\n }\n \n@@ -635,6 +670,7 @@ bool aio_poll(AioContext *ctx, bool blocking)\n     int64_t timeout;\n     int64_t start = 0;\n     int64_t block_ns = 0;\n+    int64_t dispatch_ns = 0;\n \n     /*\n      * There cannot be two concurrent aio_poll calls for the same AioContext (or\n@@ -711,7 +747,8 @@ bool aio_poll(AioContext *ctx, bool blocking)\n \n     /* Calculate blocked time for adaptive polling */\n     if (ctx->poll_max_ns) {\n-        block_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) - start;\n+        dispatch_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);\n+        block_ns = dispatch_ns - start;\n     }\n \n     if (ctx->fdmon_ops->dispatch) {\n@@ -719,10 +756,14 @@ bool aio_poll(AioContext *ctx, bool blocking)\n     }\n \n     progress |= aio_bh_poll(ctx);\n-    progress |= aio_dispatch_ready_handlers(ctx, &ready_list, block_ns);\n+    progress |= aio_dispatch_ready_handlers(ctx, &ready_list, dispatch_ns);\n \n     aio_free_deleted_handlers(ctx);\n \n+    if (ctx->poll_max_ns) {\n+        update_handler_poll_times(ctx, block_ns, dispatch_ns);\n+    }\n+\n     qemu_lockcnt_dec(&ctx->list_lock);\n \n     progress |= timerlistgroup_run_timers(&ctx->tlg);\n@@ -794,6 +835,7 @@ void aio_context_set_poll_params(AioContext *ctx, int64_t max_ns,\n     ctx->poll_max_ns = max_ns;\n     ctx->poll_grow = grow;\n     ctx->poll_shrink = shrink;\n+    ctx->poll_ns = 0;\n \n     aio_notify(ctx);\n }\ndiff --git a/util/aio-posix.h b/util/aio-posix.h\nindex ab894a3c0f..cd459bbbae 100644\n--- a/util/aio-posix.h\n+++ b/util/aio-posix.h\n@@ -38,7 +38,7 @@ struct AioHandler {\n     unsigned flags; /* see fdmon-io_uring.c */\n     CqeHandler internal_cqe_handler; /* used for POLL_ADD/POLL_REMOVE */\n #endif\n-    int64_t poll_idle_timeout; /* when to stop userspace polling */\n+    int64_t last_dispatch_timestamp; /* when last handler was dispatched */\n     bool poll_ready; /* has polling detected an event? */\n     AioPolledEvent poll;\n };\ndiff --git a/util/async.c b/util/async.c\nindex 80d6b01a8a..9d3627566f 100644\n--- a/util/async.c\n+++ b/util/async.c\n@@ -606,6 +606,7 @@ AioContext *aio_context_new(Error **errp)\n     timerlistgroup_init(&ctx->tlg, aio_timerlist_notify, ctx);\n \n     ctx->poll_max_ns = 0;\n+    ctx->poll_ns = 0;\n     ctx->poll_grow = 0;\n     ctx->poll_shrink = 0;\n \n",
    "prefixes": [
        "v3",
        "2/3"
    ]
}