From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from CH1PR05CU001.outbound.protection.outlook.com (mail-northcentralusazon11010001.outbound.protection.outlook.com [52.101.193.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 29D6D3B27E5 for ; Thu, 28 May 2026 09:50:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.193.1 ARC-Seal:i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779961820; cv=fail; b=iVDcAy5SpEAvMq9WksZxgczhCm2EDXh3oQ/iOqgapKyzUZ4I5LJ9ifYGOhMnt+WBgihGE81OzuNK8lfEP3D0PkTslQRB/RPVnjJCe+MJr3wZiKiqJTL1J27ev+ajYaCBFwXK/6Ee46QnnihpgIUarqjY14bmEM9c+wsSXk+jNcg= ARC-Message-Signature:i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779961820; c=relaxed/simple; bh=ASGrVMddOi9Zab/Nf1joqhCVXiHc3x64FBwGwGT8w3k=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=kJpOlUDApu1L/JqsEIwvH9b57xljGUMyoW9dYZOONjYd8wehJWKGhIdBLWs61+Ybn7X3ijNp/NYuOuEr2c3iKRMeh0CAHJO3C9BNzOoBH5E9BKs1T3aO3KTGeaZflEySBRBHlIAHx5yCThCaFCSIiQ6byjEkawFYFuo7rQgdCyQ= ARC-Authentication-Results:i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com; spf=fail smtp.mailfrom=amd.com; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b=xCQ+TXg3; arc=fail smtp.client-ip=52.101.193.1 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=amd.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b="xCQ+TXg3" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=xlGEeZIp2DG0vRSErcpMc9bEI7XVgXzeYLXe1Xk5tzQCBLH/YPakkluLS2oS05WhoFDI8famQVanHaS2Jm9537T2fJvLkqJpqnH8uPd3FSk4KuyBSHbT6aD/wcn4ShahnlSbbikg4kX3aEt+kDBwu82OtV7Yy/xe7GvFiP3JUDYDmQxq8UY3KA3C9772eJ7Tay40xpzcknI13/uvmUfRgW4of+1cj5zUwWPDo82bqFeeonbkX3H6I9jKe94KAvtCrHxyxMhAie0uV507XKGZGDReE1rHG/jeZLcJ0EDoOzDeWveGPRsU6hm9oHitStKOHjtNpNKdsvhor3lYFKzluA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=DCmHhRVn8jrkEMP4AnxO8SBf3Orlt85zVGU69kjq5ZI=; b=MYDm3pSLBHaggWdS0H7mo0PIZiSB5QlGJ1eItoAIdqWINSnsCS0GMTWyPeBz3fN6ozIujs8HaPlTB/rKPkDjM/oZW3fvi6Sxs50dyDWf2xFoVLlB0FDxg4z+ieUl40HFSqw8x4VdOH4MECvidlTWZMWuvQkHzD25VBpgeUQl5sVjLC35mEfaoFo26eUpWcqqFHoesJKVBbP3cJTgRvj9u0fSrRV+j8MSuIb8CHTjtrFDG9gWS1Fi9q6Rzp85fV7t7CNg86KDIhPRL4Y7Lvx8YClg/EDpkeOw/BzSeEe9FGSuSHKCCRhCfVX0CC+bVJsNB+0QCBtjqntvF7YWzuXs9Q== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 165.204.84.17) smtp.rcpttodomain=redhat.com smtp.mailfrom=amd.com; dmarc=pass (p=quarantine sp=quarantine pct=100) action=none header.from=amd.com; dkim=none (message not signed); arc=none (0) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amd.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=DCmHhRVn8jrkEMP4AnxO8SBf3Orlt85zVGU69kjq5ZI=; b=xCQ+TXg36aGyrt6jdlp3f67Uj1t3cEVDeUaCnVUiZchsTeGEuYeOltiJg6F313TWYT8xphFi1CnkzBuBug6/ALRsQ0/jykplvd5b7i4/tVYi5t9J2f7tXdFKcnCKL49L7hc0Z4LOvXW10sXErYtPqRhn5KELkTIsH08URPlSdvw= Received: from MN2PR08CA0016.namprd08.prod.outlook.com (2603:10b6:208:239::21) by SA1PR12MB7040.namprd12.prod.outlook.com (2603:10b6:806:24f::13) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.71.13; Thu, 28 May 2026 09:50:12 +0000 Received: from BN2PEPF00004FBD.namprd04.prod.outlook.com (2603:10b6:208:239:cafe::27) by MN2PR08CA0016.outlook.office365.com (2603:10b6:208:239::21) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.21.71.13 via Frontend Transport; Thu, 28 May 2026 09:50:11 +0000 X-MS-Exchange-Authentication-Results: spf=pass (sender IP is 165.204.84.17) smtp.mailfrom=amd.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=amd.com; Received-SPF: Pass (protection.outlook.com: domain of amd.com designates 165.204.84.17 as permitted sender) receiver=protection.outlook.com; client-ip=165.204.84.17; helo=satlexmb07.amd.com; pr=C Received: from satlexmb07.amd.com (165.204.84.17) by BN2PEPF00004FBD.mail.protection.outlook.com (10.167.243.183) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.71.7 via Frontend Transport; Thu, 28 May 2026 09:50:11 +0000 Received: from BLRKPRNAYAK.amd.com (10.180.168.240) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.41; Thu, 28 May 2026 04:50:07 -0500 From: K Prateek Nayak To: Ingo Molnar , Peter Zijlstra , Juri Lelli , Vincent Guittot CC: Dietmar Eggemann , Steven Rostedt , Ben Segall , Mel Gorman , Valentin Schneider , Aaron Lu , Josh Don , K Prateek Nayak , Subject: [PATCH 5/5] sched/fair: Unify cfs_rq throttling via account_cfs_rq_runtime() Date: Thu, 28 May 2026 09:48:30 +0000 Message-ID: <20260528094830.13291-6-kprateek.nayak@amd.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260528094830.13291-1-kprateek.nayak@amd.com> References: <20260528094830.13291-1-kprateek.nayak@amd.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Content-Type: text/plain X-ClientProxiedBy: satlexmb07.amd.com (10.181.42.216) To satlexmb07.amd.com (10.181.42.216) X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: BN2PEPF00004FBD:EE_|SA1PR12MB7040:EE_ X-MS-Office365-Filtering-Correlation-Id: 15ce3bbd-2eb9-40cc-09f5-08debc9e82c3 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|1800799024|7416014|376014|82310400026|36860700016|18002099003|22082099003|11063799006|56012099006; X-Microsoft-Antispam-Message-Info: 67lglBBs88o1gEwDaPIMtJl3nCa6RGqffNkYslCVaVoQFNCXVXf9MY7eKmz/fJyoYRVTpgJSgWgvEkdDOgFWkRsxkMJxgGioz/E5rxlsl5H3HZ+0J2TQPoE6CQvXjdOgjbrBB7HM3KVqd/HUXYC63q/ryF0+GuxjU3wTTb5Gsm6QXXGgYt6g65GoP3Df8iwktJ2jQwJ25dgm+LnXF8x7hZoM9QvZHwUNlGEdnLh9EyZxsx+1kWSI9Cq9dqO5JGEB5DQ16esQHwyAzqOi897GO5d6lvm/eJqqIR6EraLnzWLMisLtrN0zdkqsM7vkkebeEkix0MIYwLyN+8eGPqymwXsUiqKAAb+CBH6IpSvi/M54Ms8yeT/yFTY+wLYiNXrbAnn6Ih1arit9axzrC0ignN75gLqabxOQ1JDBKOOu+U0q+9PE2O4bjXKe7n+XZPUwDh6FrPirtRfwFQZm3dB17bI4FcrjrmaA+LYyOp9MzTi7hCGQ3Dw8kIV0D6wfyBzR+W8+9CzMJI2bHH2Qdpocl522J74EljfwE6EkB92aJFSucCFGwuHL390ZeXSQ5oW9820txyYkhEiLiBJXc3uxwN3ZSY2DkXuIBjSpnnME0hFT9oZAwHQ05U8anSm7TAj2YSlzFO4MvUL94yS0ETij7BfbCpnTYym3sBXUOQtxWwVgXqYO9/wqikRQ7GMnV1kplivajbkotU7FeKPN66SZswqT0lD3gX+LMziW5AhledY= X-Forefront-Antispam-Report: CIP:165.204.84.17;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:satlexmb07.amd.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(1800799024)(7416014)(376014)(82310400026)(36860700016)(18002099003)(22082099003)(11063799006)(56012099006);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: AncioAABRO5FEv+Zq9RzmSA6wxf0YZB0nN5gggZQOVQbfEqF+agj/XR2J1Ge/RWhfwn0W9EBxgqy3yiOIQQeH1AGchunHSog72wph5BCuxQny05eaJ9x9H77et8jsCGsFo8pHZPh77GXtO6snO/JtYJqhgNJvpIIXmfaKCL4eRk7la6xqhMC8IJ4eBYq+ZEakH/mFUQe/nb/fb7jXD8H3RjJrie6mMR2cQ5sHkRDkwuL2uXx2IY4Xz/GWnYQG+4gOCeSS+V+/R/8oqS+WdZpdkMtoxjHC3C8NImYAXtcZKCvUnKmskCrfQ6tAiKdK/5BtJbpKD+TIanqLG1diZsJyV+OGqQzZhEqX7NOGe+cYv22VDr+mh6L+CQRFI198YRCYr0YZd9TdQBeaUpDkdxWp1awIlz4o4nIbA9cnXtm+WIlBQGCDonHw0qLUYpV+dat X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 May 2026 09:50:11.7473 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: 15ce3bbd-2eb9-40cc-09f5-08debc9e82c3 X-MS-Exchange-CrossTenant-Id: 3dd8961f-e488-4e60-8e11-a82d994e183d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=3dd8961f-e488-4e60-8e11-a82d994e183d;Ip=[165.204.84.17];Helo=[satlexmb07.amd.com] X-MS-Exchange-CrossTenant-AuthSource: BN2PEPF00004FBD.namprd04.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA1PR12MB7040 From: Peter Zijlstra assign_cfs_rq_runtime() during update_curr() sets the resched indicator and relies on check_cfs_rq_runtime() during pick_next_task() / put_prev_entity() to throttle the hierarchy once current task is preempted / blocks. Per-task throttle, on the other hand, uses throttle_cfs_rq() to simply propagate the throttle signals, and then relies on task work to individually throttle the runnable tasks on their way out to the userspace. Remove check_cfs_rq_runtime() and unify throttling into account_cfs_rq_runtime() which only sets the cfs_rq->throttled, cfs_rq->throttle_count indicators via throttle_cfs_rq() and optionally adds the task work to the current task (donor) it is on the throttled hierarchy. throttle_cfs_rq() requests for sched_cfs_bandwidth_slice() worth of bandwidth for the current hierarchy that enable it to continue running uninterrupted when selected. For the rest, it requests a bare minimum of "1" to ensure some bandwidth is available and pass the "runtime_remaining > 0" checks once selected. For SCHED_PROXY_EXEC, a mutex holder cannot exit to userspace without dropping it first and the mutex_unlock() ensures proxy is stopped before the mutex handoff which preserves the current semantics for running a throttled task until it exits to the userspace even if it acts as a donor. [ prateek: rebased on tip, comments, commit message. ] Not-yet-signed-off-by: Peter Zijlstra Signed-off-by: K Prateek Nayak --- kernel/sched/fair.c | 110 ++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index c48eaf2d7919..a481647f0f0f 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -525,7 +525,7 @@ static int se_is_idle(struct sched_entity *se) #endif /* !CONFIG_FAIR_GROUP_SCHED */ static __always_inline -void account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec); +bool account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec); /************************************************************** * Scheduling class tree data structure manipulation methods: @@ -6359,8 +6359,6 @@ pick_next_entity(struct rq *rq, struct cfs_rq *cfs_rq, bool protect) return se; } -static bool check_cfs_rq_runtime(struct cfs_rq *cfs_rq); - static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev) { /* @@ -6370,9 +6368,6 @@ static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev) if (prev->on_rq) update_curr(cfs_rq); - /* throttle cfs_rqs exceeding runtime */ - check_cfs_rq_runtime(cfs_rq); - if (prev->on_rq) { update_stats_wait_start_fair(cfs_rq, prev); /* Put 'current' back into the tree. */ @@ -6507,41 +6502,32 @@ static int __assign_cfs_rq_runtime(struct cfs_bandwidth *cfs_b, return cfs_rq->runtime_remaining > 0; } -/* returns 0 on failure to allocate runtime */ -static int assign_cfs_rq_runtime(struct cfs_rq *cfs_rq) -{ - struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(cfs_rq->tg); - - guard(raw_spinlock)(&cfs_b->lock); +static bool throttle_cfs_rq(struct cfs_rq *cfs_rq); - return __assign_cfs_rq_runtime(cfs_b, cfs_rq, sched_cfs_bandwidth_slice()); -} - -static void __account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec) +static bool __account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec) { /* dock delta_exec before expiring quota (as it could span periods) */ cfs_rq->runtime_remaining -= delta_exec; if (likely(cfs_rq->runtime_remaining > 0)) - return; + return false; if (cfs_rq->throttled) - return; + return true; /* - * if we're unable to extend our runtime we resched so that the active - * hierarchy can be throttled + * throttle_cfs_rq() will try to extend the runtime first + * before throttling the hierarchy. */ - if (!assign_cfs_rq_runtime(cfs_rq) && likely(cfs_rq->curr)) - resched_curr(rq_of(cfs_rq)); + return throttle_cfs_rq(cfs_rq); } static __always_inline -void account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec) +bool account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec) { if (!cfs_bandwidth_used() || !cfs_rq->runtime_enabled) - return; + return false; - __account_cfs_rq_runtime(cfs_rq, delta_exec); + return __account_cfs_rq_runtime(cfs_rq, delta_exec); } static inline int cfs_rq_throttled(struct cfs_rq *cfs_rq) @@ -6829,10 +6815,24 @@ static int tg_throttle_down(struct task_group *tg, void *data) static bool throttle_cfs_rq(struct cfs_rq *cfs_rq) { - struct rq *rq = rq_of(cfs_rq); struct cfs_bandwidth *cfs_b = tg_cfs_bandwidth(cfs_rq->tg); + struct sched_entity *curr = cfs_rq->curr; + struct rq *rq = rq_of(cfs_rq); scoped_guard(raw_spinlock, &cfs_b->lock) { + u64 target_runtime = 1; + + /* + * If cfs_rq->curr is still runnable, we are here from an + * update_curr(). Request sysctl_sched_cfs_bandwidth_slice + * worth of bandwidth to continue running. + * + * If the curr is not runnable, just request enough bandwidth + * to be runnable next time the pick selects this cfs_rq. + */ + if (curr && curr->on_rq) + target_runtime = sched_cfs_bandwidth_slice(); + /* * Check if We have raced with bandwidth becoming available. If * we actually throttled the timer might not unthrottle us for @@ -6843,7 +6843,7 @@ static bool throttle_cfs_rq(struct cfs_rq *cfs_rq) * * This will start the period timer if necessary. */ - if (__assign_cfs_rq_runtime(cfs_b, cfs_rq, 1)) + if (__assign_cfs_rq_runtime(cfs_b, cfs_rq, target_runtime)) return false; /* @@ -6864,6 +6864,17 @@ static bool throttle_cfs_rq(struct cfs_rq *cfs_rq) */ cfs_rq->throttled = 1; WARN_ON_ONCE(cfs_rq->throttled_clock); + + /* + * If current hierarchy was throttled, add throttle work to the + * current donor. In case of proxy-execution, the execution + * context cannot exit to the userspace while holding a mutex + * and the rule of throttle deferral to only throttle the + * throttled context at exit to userspace is still preserved. + */ + if (curr && curr->on_rq) + task_throttle_setup_work(rq->donor); + return true; } @@ -7245,7 +7256,7 @@ static void check_enqueue_throttle(struct cfs_rq *cfs_rq) if (!cfs_bandwidth_used()) return; - /* an active group must be handled by the update_curr()->put() path */ + /* an active group must be handled by the update_curr() path */ if (!cfs_rq->runtime_enabled || cfs_rq->curr) return; @@ -7255,8 +7266,6 @@ static void check_enqueue_throttle(struct cfs_rq *cfs_rq) /* update runtime allocation */ account_cfs_rq_runtime(cfs_rq, 0); - if (cfs_rq->runtime_remaining <= 0) - throttle_cfs_rq(cfs_rq); } static void sync_throttle(struct task_group *tg, int cpu) @@ -7286,25 +7295,6 @@ static void sync_throttle(struct task_group *tg, int cpu) cfs_rq->pelt_clock_throttled = 1; } -/* conditionally throttle active cfs_rq's from put_prev_entity() */ -static bool check_cfs_rq_runtime(struct cfs_rq *cfs_rq) -{ - if (!cfs_bandwidth_used()) - return false; - - if (likely(!cfs_rq->runtime_enabled || cfs_rq->runtime_remaining > 0)) - return false; - - /* - * it's possible for a throttled entity to be forced into a running - * state (e.g. set_curr_task), in this case we're finished. - */ - if (cfs_rq_throttled(cfs_rq)) - return true; - - return throttle_cfs_rq(cfs_rq); -} - static enum hrtimer_restart sched_cfs_slack_timer(struct hrtimer *timer) { struct cfs_bandwidth *cfs_b = @@ -7559,8 +7549,7 @@ static void sched_fair_update_stop_tick(struct rq *rq, struct task_struct *p) #else /* !CONFIG_CFS_BANDWIDTH: */ -static void account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec) {} -static bool check_cfs_rq_runtime(struct cfs_rq *cfs_rq) { return false; } +static bool account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec) { return false; } static void check_enqueue_throttle(struct cfs_rq *cfs_rq) {} static inline void sync_throttle(struct task_group *tg, int cpu) {} static __always_inline void return_cfs_rq_runtime(struct cfs_rq *cfs_rq) {} @@ -9893,8 +9882,15 @@ static struct task_struct *pick_task_fair(struct rq *rq, struct rq_flags *rf) /* Might not have done put_prev_entity() */ if (cfs_rq->curr && cfs_rq->curr->on_rq) update_curr(cfs_rq); - - throttled |= check_cfs_rq_runtime(cfs_rq); + /* + * For the current hierarchy, update_curr() above would + * have set the throttle indicators if the cfs_rq has + * run out of bandwidth. For others, enqueue / last + * update_curr() for the cfs_rq would have ensured the + * throttle indicators are set if bandwidth was not + * available. + */ + throttled |= cfs_rq_throttled(cfs_rq); se = pick_next_entity(rq, cfs_rq, true); if (!se) @@ -14868,8 +14864,8 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) {} */ static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued) { - struct cfs_rq *cfs_rq; struct sched_entity *se = &curr->se; + struct cfs_rq *cfs_rq; for_each_sched_entity(se) { cfs_rq = cfs_rq_of(se); @@ -15074,15 +15070,19 @@ static void __set_next_task_fair(struct rq *rq, struct task_struct *p, bool firs static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first) { struct sched_entity *se = &p->se; + bool throttled = false; for_each_sched_entity(se) { struct cfs_rq *cfs_rq = cfs_rq_of(se); set_next_entity(cfs_rq, se, first); /* ensure bandwidth has been allocated on our new cfs_rq */ - account_cfs_rq_runtime(cfs_rq, 0); + throttled |= account_cfs_rq_runtime(cfs_rq, 0); } + if (throttled) + task_throttle_setup_work(p); + __set_next_task_fair(rq, p, first); } -- 2.43.0