From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mx0a-00069f02.pphosted.com (mx0a-00069f02.pphosted.com [205.220.165.32]) (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 C16783E3D9D for ; Thu, 23 Apr 2026 09:19:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.165.32 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776935977; cv=none; b=kYRVTgAFOmSdRFGFZfrHTEFbtZDd8oGndMAfzY4TXpMQfHiPL+AXYE09vgPJeqZyfesaSPyedzjVeyyodkFEpUjMdeKQman0WSac1pW+L2IYtBko0NzQ8FT9qPLJ65srS/9QCr/a/qeu67TAg3p0OKnxOTXxdBhZaJ0QQS7IX7g= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776935977; c=relaxed/simple; bh=wXZsdZqqf2KkuxK81fZWaCDj76g36XMwVLfHgmtUVJw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rc63Mo/wDTSB+YP1kRlfRfUUr3gO8y6t+G0PGc58iYQ5AgjJcf+TT+P/S9PcKCJ48gtpvdtS4OyN4VfEmf/G2fWNhUOhifMxTokK9kVIpLo5G5gsm/Q8K+oHyAbNgn3htVmlRVxv69K/zrFgCwz1LAAuhqEbjU4W2jYCvrAFuOY= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oracle.com; spf=pass smtp.mailfrom=oracle.com; dkim=pass (2048-bit key) header.d=oracle.com header.i=@oracle.com header.b=YsAqUWSM; arc=none smtp.client-ip=205.220.165.32 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oracle.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oracle.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=oracle.com header.i=@oracle.com header.b="YsAqUWSM" Received: from pps.filterd (m0246617.ppops.net [127.0.0.1]) by mx0b-00069f02.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 63N5HrI52336472; Thu, 23 Apr 2026 09:19:18 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oracle.com; h=cc :content-transfer-encoding:date:from:in-reply-to:message-id :mime-version:references:subject:to; s=corp-2025-04-25; bh=0UzzB WjERvLog2B9W77GjKBma10PfCYQ4QqnHhjFrFw=; b=YsAqUWSM5V5ZWoWc6uxpa vJkJ6W+4VbmITqP2zeuL0iPbgDX475YHbVax4oVLKRefXC1HCFeipekLSgDCF4+l jz0/tpBjkPv5ROGs6EDl7Ru3CAIsQAK5QXOpZLUfhdq0hTZBIGS/iLaZqDJTQEp4 e80GYhQHLNJvYLkIdCO6up5nBDRaaMXovvEwmpxJgPnDYQpYzr5ukzpBWzenDrWp qiIoWKyNhs9aL+EVMFE6VT/TGkpeNgQYEEd07MTu52Df31UOfOaAZavRqAmUQwE2 K4mV3BBevcRXo+wkjzhBtjLZgZLk839lEYrMGNk51QS/qVfzNPDJE88BeSDG+ND6 A== Received: from iadpaimrmta01.imrmtpd1.prodappiadaev1.oraclevcn.com (iadpaimrmta01.appoci.oracle.com [130.35.100.223]) by mx0b-00069f02.pphosted.com (PPS) with ESMTPS id 4dpenmu5a7-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Thu, 23 Apr 2026 09:19:18 +0000 (GMT) Received: from pps.filterd (iadpaimrmta01.imrmtpd1.prodappiadaev1.oraclevcn.com [127.0.0.1]) by iadpaimrmta01.imrmtpd1.prodappiadaev1.oraclevcn.com (8.18.1.7/8.18.1.7) with ESMTP id 63N9GNIb012969; Thu, 23 Apr 2026 09:19:17 GMT Received: from pps.reinject (localhost [127.0.0.1]) by iadpaimrmta01.imrmtpd1.prodappiadaev1.oraclevcn.com (PPS) with ESMTPS id 4dpjjrd0pn-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Thu, 23 Apr 2026 09:19:17 +0000 (GMT) Received: from iadpaimrmta01.imrmtpd1.prodappiadaev1.oraclevcn.com (iadpaimrmta01.imrmtpd1.prodappiadaev1.oraclevcn.com [127.0.0.1]) by pps.reinject (8.18.1.12/8.18.1.12) with ESMTP id 63N9JF03021237; Thu, 23 Apr 2026 09:19:16 GMT Received: from pssatapa-ol8-test.osdevelopmeniad.oraclevcn.com (pssatapa-ol8-test.allregionaliads.osdevelopmeniad.oraclevcn.com [100.100.253.175]) by iadpaimrmta01.imrmtpd1.prodappiadaev1.oraclevcn.com (PPS) with ESMTP id 4dpjjrd0n7-3; Thu, 23 Apr 2026 09:19:16 +0000 (GMT) From: Partha Satapathy To: partha.satapathy@oracle.com, anna-maria@linutronix.de, frederic@kernel.org, tglx@kernel.org, linux-kernel@vger.kernel.org, tj@kernel.org, jiangshanlai@gmail.com Cc: notify@kernel.org Subject: [PATCH 2/2] workqueue: add queue_delayed_work_active_cpu() Date: Thu, 23 Apr 2026 09:19:07 +0000 Message-ID: <20260423091914.63645-3-partha.satapathy@oracle.com> X-Mailer: git-send-email 2.43.7 In-Reply-To: <20260423091914.63645-1-partha.satapathy@oracle.com> References: <20260423091914.63645-1-partha.satapathy@oracle.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 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1143,Hydra:6.1.51,FMLib:17.12.100.49 definitions=2026-04-23_02,2026-04-21_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=notspam policy=default score=0 bulkscore=0 spamscore=0 suspectscore=0 lowpriorityscore=0 adultscore=0 malwarescore=0 mlxscore=0 mlxlogscore=999 phishscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2604200000 definitions=main-2604230090 X-Proofpoint-GUID: ES9JA4dA254qlUS8DNgU6GPRFB92RRE4 X-Authority-Analysis: v=2.4 cv=Z6/c2nRA c=1 sm=1 tr=0 ts=69e9e416 b=1 cx=c_pps a=zPCbziy225d3KhSqZt3L1A==:117 a=zPCbziy225d3KhSqZt3L1A==:17 a=A5OVakUREuEA:10 a=VkNPw1HP01LnGYTKEx00:22 a=jiCTI4zE5U7BLdzWsZGv:22 a=7Gl3-_t3PgB9XO-mQDs3:22 a=yPCof4ZbAAAA:8 a=uWG3VGRt3ocMjy4b0FoA:9 cc=ntf awl=host:12291 X-Proofpoint-ORIG-GUID: ES9JA4dA254qlUS8DNgU6GPRFB92RRE4 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNDIzMDA5MCBTYWx0ZWRfXyU5tAYFrhs5Z nl/Jxvgngu/I8eCdehZ0Z2ocVckTNxFtgTLd1YOOX7hYY2arkf5zjk/0+I2BKnoyjaJB/hIBBvE ewsqeRTvp+YNiAO5Euy6NC00ZbgS6MFsmEjXoS2HF++qgJI51pRZYJKjGy9tur+act7tSfK7ZQF 2i5Jszgd3SurZIMFzB/wZG9Fb6usDXLoXlPXy27nJx6AIkaHYyDhnEsSJIXR8lI6z+kPDnTMZ+Y 36pmKk5LjktPhLJmt9AP1uypXApDavEonNv2o+8vhtrFITkRu/dwBQQg9LX7nJLI3k+9YmXJBi0 XIxH3RyjpgvYpep1fR5ZSo4V9BN4CU+e8y3Z2Y/0FxUVIIshEN32LsVAbcSL4bba8zfD+WdUoso RuCcw7kER+HKUAOcgZ2hwyjuPG/karN91oi/0uqgXXYigejsWsVHeaRcejUNY/sQ70SyzwhAdDP /pHdwXa4NvYFOyKfFG7QEy95mWiVo66BIExRquoU= From: Partha Sarathi Satapathy Delayed work queued with queue_delayed_work_on() inherits the same CPU hotplug race as add_timer_on(): the backing timer can be enqueued on a CPU that goes offline before the queueing completes, leaving the timer unserviced until that CPU comes back online. As a result, the delayed work item is never queued for execution. Add queue_delayed_work_active_cpu() for callers that want delayed work to target a specific CPU when that CPU is active, but fall back to an active CPU otherwise. For the delayed timer path, use add_timer_active_cpu() instead of add_timer_on(). After enqueueing the timer, update dwork->cpu to match the CPU recorded in the timer so that the work item is queued on the CPU actually selected for the timer. Leave queue_delayed_work_on() unchanged for callers that require the existing strict behavior. Signed-off-by: Partha Sarathi Satapathy --- include/linux/workqueue.h | 3 ++ kernel/workqueue.c | 96 +++++++++++++++++++++++++++++++++------ 2 files changed, 86 insertions(+), 13 deletions(-) diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h index dabc351cc127..9799e44bca2b 100644 --- a/include/linux/workqueue.h +++ b/include/linux/workqueue.h @@ -596,6 +596,9 @@ extern bool queue_work_node(int node, struct workqueue_struct *wq, struct work_struct *work); extern bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay); +extern bool queue_delayed_work_active_cpu(int cpu, struct workqueue_struct *wq, + struct delayed_work *work, + unsigned long delay); extern bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay); extern bool queue_rcu_work(struct workqueue_struct *wq, struct rcu_work *rwork); diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 253311af47c6..80f5d162624d 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -2483,7 +2483,8 @@ void delayed_work_timer_fn(struct timer_list *t) EXPORT_SYMBOL(delayed_work_timer_fn); static void __queue_delayed_work(int cpu, struct workqueue_struct *wq, - struct delayed_work *dwork, unsigned long delay) + struct delayed_work *dwork, unsigned long delay, + bool dwork_active_cpu) { struct timer_list *timer = &dwork->timer; struct work_struct *work = &dwork->work; @@ -2504,7 +2505,8 @@ static void __queue_delayed_work(int cpu, struct workqueue_struct *wq, return; } - WARN_ON_ONCE(cpu != WORK_CPU_UNBOUND && !cpu_online(cpu)); + WARN_ON_ONCE(!dwork_active_cpu && cpu != WORK_CPU_UNBOUND && + !cpu_online(cpu)); dwork->wq = wq; dwork->cpu = cpu; timer->expires = jiffies + delay; @@ -2516,10 +2518,17 @@ static void __queue_delayed_work(int cpu, struct workqueue_struct *wq, cpu = housekeeping_any_cpu(HK_TYPE_TIMER); add_timer_on(timer, cpu); } else { - if (likely(cpu == WORK_CPU_UNBOUND)) + if (likely(cpu == WORK_CPU_UNBOUND)) { add_timer_global(timer); - else + } else if (dwork_active_cpu) { + add_timer_active_cpu(timer, cpu); + /* The target CPU may change if it is offline. */ + /* Avoid selecting the same offline CPU. */ + dwork->cpu = READ_ONCE(timer->flags) & + TIMER_CPUMASK; + } else { add_timer_on(timer, cpu); + } } } @@ -2530,18 +2539,23 @@ static void __queue_delayed_work(int cpu, struct workqueue_struct *wq, * @dwork: work to queue * @delay: number of jiffies to wait before queueing * - * We queue the delayed_work to a specific CPU, for non-zero delays the - * caller must ensure it is online and can't go away. Callers that fail - * to ensure this, may get @dwork->timer queued to an offlined CPU and - * this will prevent queueing of @dwork->work unless the offlined CPU - * becomes online again. + * We queue the delayed_work to a specific CPU, for non-zero delays and + * dwork_active_cpu is not set, caller must ensure it is online and can't + * go away. Callers that fail to ensure this, may get @dwork->timer + * queued to an offlined CPU and this will prevent queueing of + * @dwork->work unless the offlined CPU becomes online again. + * + * If dwork_active_cpu is set, timer queued to an offlined CPU will be + * queued to the current cpu. * * Return: %false if @work was already on a queue, %true otherwise. If * @delay is zero and @dwork is idle, it will be scheduled for immediate * execution. */ -bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq, - struct delayed_work *dwork, unsigned long delay) +static inline bool +__queue_delayed_work_on(int cpu, struct workqueue_struct *wq, + struct delayed_work *dwork, unsigned long delay, + bool dwork_active_cpu) { struct work_struct *work = &dwork->work; bool ret = false; @@ -2552,15 +2566,70 @@ bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq, if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work)) && !clear_pending_if_disabled(work)) { - __queue_delayed_work(cpu, wq, dwork, delay); + __queue_delayed_work(cpu, wq, dwork, delay, dwork_active_cpu); ret = true; } local_irq_restore(irq_flags); return ret; } + +/** + * queue_delayed_work_on - queue work on specific CPU after delay + * @cpu: CPU number to execute work on + * @wq: workqueue to use + * @dwork: work to queue + * @delay: number of jiffies to wait before queueing + * + * We queue the delayed_work to a specific CPU, for non-zero delays the + * caller must ensure it is online and can't go away. Callers that fail + * to ensure this, may get @dwork->timer queued to an offlined CPU and + * this will prevent queueing of @dwork->work unless the offlined CPU + * becomes online again. + * + * Return: %false if @work was already on a queue, %true otherwise. If + * @delay is zero and @dwork is idle, it will be scheduled for immediate + * execution. + */ +bool queue_delayed_work_on(int cpu, struct workqueue_struct *wq, + struct delayed_work *dwork, unsigned long delay) +{ + bool ret = false; + bool dwork_active_cpu = false; + + ret = __queue_delayed_work_on(cpu, wq, dwork, delay, dwork_active_cpu); + return ret; +} EXPORT_SYMBOL(queue_delayed_work_on); +/** + * queue_delayed_work_active_cpu - queue delayed work for an active CPU + * @cpu: CPU number to execute work on + * @wq: workqueue to use + * @dwork: work to queue + * @delay: number of jiffies to wait before queueing + * + * This is like queue_delayed_work_on(), except that for delayed work + * the timer is queued on @cpu only if that CPU is online or is the + * current CPU. + * + * Return: %false if @work was already on a queue, %true otherwise. If + * @delay is zero and @dwork is idle, it will be scheduled for immediate + * execution. + * + */ +bool queue_delayed_work_active_cpu(int cpu, struct workqueue_struct *wq, + struct delayed_work *dwork, + unsigned long delay) +{ + bool ret = false; + bool dwork_active_cpu = true; + + ret = __queue_delayed_work_on(cpu, wq, dwork, delay, dwork_active_cpu); + return ret; +} +EXPORT_SYMBOL(queue_delayed_work_active_cpu); + /** * mod_delayed_work_on - modify delay of or queue a delayed work on specific CPU * @cpu: CPU number to execute work on @@ -2584,11 +2653,12 @@ bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq, { unsigned long irq_flags; bool ret; + bool dwork_active_cpu = false; ret = work_grab_pending(&dwork->work, WORK_CANCEL_DELAYED, &irq_flags); if (!clear_pending_if_disabled(&dwork->work)) - __queue_delayed_work(cpu, wq, dwork, delay); + __queue_delayed_work(cpu, wq, dwork, delay, dwork_active_cpu); local_irq_restore(irq_flags); return ret; -- 2.43.7