* [PATCH v4 0/3] rust: workqueue: add safe cancellation and status methods
@ 2026-04-07 10:37 Aakash Bollineni via B4 Relay
2026-04-07 10:37 ` [PATCH v4 1/3] rust: helpers: add workqueue helpers Aakash Bollineni via B4 Relay
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Aakash Bollineni via B4 Relay @ 2026-04-07 10:37 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Wedson Almeida Filho, Gary Guo,
Björn Roy Baron, Alice Ryhl, Tejun Heo, Lai Jiangshan,
Boqun Feng, Benno Lossin, Andreas Hindborg
Cc: rust-for-linux, linux-kernel, Aakash Bollineni, kernel test robot
This is the fourth version of the patch series for modernizing the Linux
kernel Rust workqueue infrastructure.
Changes in v4:
- Fixed rustfmt and formatting errors in samples/rust/rust_workqueue_test.rs
reported by the kernel test robot.
- Added Reported-by and Closes tags to the third patch for robot tracking.
- Improved the commit structure to cleanly separate API changes from tests.
- Link to v3: https://lore.kernel.org/r/20260403-workqueue-v3-final-v3-0-6ffc3950d804@multicorewareinc.com
Changes in v3:
- Fixed a critical initialization bug in the DelayedWork C-helper where
the timer function was not correctly registered, causing KUnit crashes.
- Consolidated Arc refcount inspection into a shared `arc_count` helper
in `rust/kernel/workqueue.rs` (gated by CONFIG_KUNIT).
- Updated internal KUnit tests and sample module to use the shared helper.
- Split the sample test's `TestItem` into `TestWorkItem` and
`TestDelayedWorkItem` for clearer trait dispatch and safety.
- Integrated the sample stress test into the kernel build system via
the SAMPLES_RUST Kconfig.
- Improved documentation and safety comments based on feedback from
Onur and Miguel Ojeda.
Aakash Bollineni (3):
rust: helpers: add workqueue helpers
rust: workqueue: add safe cancellation and status methods
rust: workqueue: add KUnit and sample stress tests
To: Miguel Ojeda <ojeda@kernel.org>
To: Alex Gaynor <alex.gaynor@gmail.com>
To: Wedson Almeida Filho <wedsonaf@gmail.com>
To: Boqun Feng <boqun.feng@gmail.com>
To: Gary Guo <gary@garyguo.net>
To: Björn Roy Baron <bjorn.roy.baron@gmail.com>
To: Benno Lossin <benno.lossin@proton.me>
To: Andreas Hindborg <a.hindborg@samsung.com>
To: Alice Ryhl <aliceryhl@google.com>
To: Tejun Heo <tj@kernel.org>
To: Lai Jiangshan <jiangshanlai@gmail.com>
Cc: rust-for-linux@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
---
Aakash Bollineni (3):
rust: helpers: add workqueue helpers
rust: workqueue: add safe cancellation and status methods
rust: workqueue: add KUnit and sample stress tests
rust/helpers/workqueue.c | 32 +++
rust/kernel/workqueue.rs | 463 +++++++++++++++++++++++++++++++++---
samples/rust/Kconfig | 10 +
samples/rust/Makefile | 2 +
samples/rust/rust_workqueue_test.rs | 192 +++++++++++++++
5 files changed, 664 insertions(+), 35 deletions(-)
---
base-commit: bf074eb6891be799174ff42e0051492681fdc045
change-id: 20260403-workqueue-v3-final-fa406defb67c
Best regards,
--
Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v4 1/3] rust: helpers: add workqueue helpers
2026-04-07 10:37 [PATCH v4 0/3] rust: workqueue: add safe cancellation and status methods Aakash Bollineni via B4 Relay
@ 2026-04-07 10:37 ` Aakash Bollineni via B4 Relay
2026-04-07 12:48 ` Gary Guo
2026-04-07 10:37 ` [PATCH v4 2/3] rust: workqueue: add safe cancellation and status methods Aakash Bollineni via B4 Relay
2026-04-07 10:37 ` [PATCH v4 3/3] rust: workqueue: add KUnit and sample stress tests Aakash Bollineni via B4 Relay
2 siblings, 1 reply; 6+ messages in thread
From: Aakash Bollineni via B4 Relay @ 2026-04-07 10:37 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Wedson Almeida Filho, Gary Guo,
Björn Roy Baron, Alice Ryhl, Tejun Heo, Lai Jiangshan,
Boqun Feng, Benno Lossin, Andreas Hindborg
Cc: rust-for-linux, linux-kernel, Aakash Bollineni
From: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
Add C-helpers to bridge the Rust workqueue abstraction with the
kernel's C workqueue macros. These helpers wrap core workqueue
functions that are either inline or macros in C, making them
accessible to Rust FFI.
New wrappers:
- rust_helper_work_pending(): Wraps work_pending().
- rust_helper_cancel_work_sync(): Wraps cancel_work_sync().
- rust_helper_cancel_delayed_work_sync(): Wraps cancel_delayed_work_sync().
- rust_helper_init_delayed_work(): Performs robust initialization of
a delayed_work structure, ensuring the correct timer function
(delayed_work_timer_fn) and lockdep maps are initialized using
standard kernel macros.
These helpers are essential for supporting safe cancellation and
correct DelayedWork lifecycle management in the Rust workqueue API.
Signed-off-by: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
---
rust/helpers/workqueue.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/rust/helpers/workqueue.c b/rust/helpers/workqueue.c
index ce1c3a5b2150..85a6c0b9e4d5 100644
--- a/rust/helpers/workqueue.c
+++ b/rust/helpers/workqueue.c
@@ -14,3 +14,35 @@ __rust_helper void rust_helper_init_work_with_key(struct work_struct *work,
INIT_LIST_HEAD(&work->entry);
work->func = func;
}
+
+__rust_helper bool rust_helper_work_pending(struct work_struct *work)
+{
+ return work_pending(work);
+}
+
+__rust_helper bool rust_helper_cancel_work_sync(struct work_struct *work)
+{
+ return cancel_work_sync(work);
+}
+
+__rust_helper bool rust_helper_cancel_delayed_work_sync(struct delayed_work *dwork)
+{
+ return cancel_delayed_work_sync(dwork);
+}
+
+__rust_helper void rust_helper_init_delayed_work(struct delayed_work *dwork,
+ work_func_t func,
+ const char *name,
+ struct lock_class_key *key,
+ const char *tname,
+ struct lock_class_key *tkey)
+{
+ INIT_DELAYED_WORK(dwork, func);
+ lockdep_init_map(&dwork->work.lockdep_map, name, key, 0);
+ timer_init_key(&dwork->timer, dwork->timer.function, TIMER_IRQSAFE, tname, tkey);
+}
+
+__rust_helper void *rust_helper_get_dwork_timer_fn(void)
+{
+ return (void *)delayed_work_timer_fn;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 2/3] rust: workqueue: add safe cancellation and status methods
2026-04-07 10:37 [PATCH v4 0/3] rust: workqueue: add safe cancellation and status methods Aakash Bollineni via B4 Relay
2026-04-07 10:37 ` [PATCH v4 1/3] rust: helpers: add workqueue helpers Aakash Bollineni via B4 Relay
@ 2026-04-07 10:37 ` Aakash Bollineni via B4 Relay
2026-04-07 11:33 ` Onur Özkan
2026-04-07 10:37 ` [PATCH v4 3/3] rust: workqueue: add KUnit and sample stress tests Aakash Bollineni via B4 Relay
2 siblings, 1 reply; 6+ messages in thread
From: Aakash Bollineni via B4 Relay @ 2026-04-07 10:37 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Wedson Almeida Filho, Gary Guo,
Björn Roy Baron, Alice Ryhl, Tejun Heo, Lai Jiangshan,
Boqun Feng, Benno Lossin, Andreas Hindborg
Cc: rust-for-linux, linux-kernel, Aakash Bollineni
From: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
Modernize the Rust workqueue by adding methods for status checking
and cancellation of work and delayed work items.
Specifically, this patch adds:
- is_pending(): Returns true if the work item is currently enqueued.
- cancel(): Attempts to cancel the work item before it runs.
- cancel_sync(): Synchronously cancels the work item, waiting for it
to finish if it's already running.
Reclaims ownership if the work was pending.
Signed-off-by: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
---
rust/kernel/workqueue.rs | 333 ++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 298 insertions(+), 35 deletions(-)
diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs
index 706e833e9702..94a52c278776 100644
--- a/rust/kernel/workqueue.rs
+++ b/rust/kernel/workqueue.rs
@@ -448,8 +448,26 @@ pub unsafe trait WorkItemPointer<const ID: u64>: RawWorkItem<ID> {
/// The provided `work_struct` pointer must originate from a previous call to [`__enqueue`]
/// where the `queue_work_on` closure returned true, and the pointer must still be valid.
///
+ /// The implementation must ensure that the pointer is reclaimed (e.g., via `from_raw`)
+ /// before calling the `run` method of the underlying [`WorkItem`].
+ ///
/// [`__enqueue`]: RawWorkItem::__enqueue
unsafe extern "C" fn run(ptr: *mut bindings::work_struct);
+
+ /// Reclaims ownership of the pointer from the work item.
+ ///
+ /// This is called when a work item is successfully cancelled, allowing the caller
+ /// to recover the original pointer (e.g., `Arc` or `KBox`) that was "leaked"
+ /// during enqueuing.
+ ///
+ /// # Safety
+ ///
+ /// The provided `work_struct` pointer must originate from a previous call to [`__enqueue`]
+ /// where the `queue_work_on` closure returned true, and the work item must have been
+ /// successfully cancelled (i.e., `cancel_work` returned true).
+ ///
+ /// [`__enqueue`]: RawWorkItem::__enqueue
+ unsafe fn reclaim(ptr: *mut bindings::work_struct) -> Self;
}
/// Defines the method that should be called when this work item is executed.
@@ -516,6 +534,156 @@ pub fn new(name: &'static CStr, key: Pin<&'static LockClassKey>) -> impl PinInit
})
}
+ /// Returns whether the work item is currently pending.
+ ///
+ /// # Warning
+ ///
+ /// This method is inherently racy. A work item can be enqueued or start running
+ /// immediately after this check returns. Do not rely on this for correctness
+ /// logic (e.g., as a guard for unsafe operations); use it only for debugging or
+ /// non-critical status reporting.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork};
+ /// # use kernel::impl_has_work;
+ /// # use kernel::sync::Arc;
+ /// # #[pin_data]
+ /// # struct MyStruct { #[pin] work: Work<MyStruct> }
+ /// # impl_has_work! { impl HasWork<Self> for MyStruct { self.work } }
+ /// # impl WorkItem for MyStruct {
+ /// # type Pointer = Arc<MyStruct>;
+ /// # fn run(_this: Arc<MyStruct>) {}
+ /// # }
+ /// let my_struct = Arc::pin_init(pin_init!(MyStruct {
+ /// work <- new_work!("MyStruct::work"),
+ /// }), kernel::alloc::flags::GFP_KERNEL).unwrap();
+ /// assert!(!my_struct.work.is_pending());
+ /// workqueue::system().enqueue(my_struct.clone());
+ /// assert!(my_struct.work.is_pending());
+ /// # let _ = my_struct.work.cancel();
+ /// ```
+ #[inline]
+ pub fn is_pending(&self) -> bool {
+ // SAFETY: `self.work` is a valid pointer to a `work_struct`.
+ unsafe { bindings::work_pending(self.work.get()) }
+ }
+
+ /// Cancels the work item.
+ ///
+ /// If the work item was successfully cancelled (i.e., it was pending and had not yet
+ /// started running), the original pointer is reclaimed and returned.
+ ///
+ /// # Guarantees
+ ///
+ /// This method is non-blocking and may return while the work item is still running
+ /// on another CPU. If it returns `None`, the work item might be about to start,
+ /// is currently running, or has already finished.
+ ///
+ /// # Safety
+ ///
+ /// This is safe because ownership is only reclaimed if the kernel confirms (via
+ /// `cancel_work` returning true) that the work item is no longer in any queue and
+ /// will not be executed by the workqueue for this specific enqueue event.
+ ///
+ /// [`cancel_sync`]: Work::cancel_sync
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork};
+ /// # use kernel::impl_has_work;
+ /// # use kernel::sync::Arc;
+ /// # #[pin_data]
+ /// # struct MyStruct { #[pin] work: Work<MyStruct> }
+ /// # impl_has_work! { impl HasWork<Self> for MyStruct { self.work } }
+ /// # impl WorkItem for MyStruct {
+ /// # type Pointer = Arc<MyStruct>;
+ /// # fn run(_this: Arc<MyStruct>) {}
+ /// # }
+ /// let my_struct = Arc::pin_init(pin_init!(MyStruct {
+ /// work <- new_work!("MyStruct::work"),
+ /// }), kernel::alloc::flags::GFP_KERNEL).unwrap();
+ /// workqueue::system().enqueue(my_struct.clone());
+ /// assert!(my_struct.work.is_pending());
+ /// let reclaimed = my_struct.work.cancel();
+ /// assert!(reclaimed.is_some());
+ /// assert!(!my_struct.work.is_pending());
+ /// ```
+ pub fn cancel(&self) -> Option<T::Pointer>
+ where
+ T: WorkItem<ID>,
+ {
+ let work_ptr = self.work.get();
+ // SAFETY: `work_ptr` is a valid pointer to a `work_struct`.
+ if unsafe { bindings::cancel_work(work_ptr) } {
+ // SAFETY: The work item was successfully cancelled and is guaranteed not to run,
+ // so we can safely reclaim the ownership leaked during `enqueue`.
+ Some(unsafe { T::Pointer::reclaim(work_ptr) })
+ } else {
+ None
+ }
+ }
+
+ /// Synchronously cancels the work item.
+ ///
+ /// This method waits for the work item to finish if it is currently running.
+ /// If the work item was successfully cancelled from the queue, the pointer is
+ /// reclaimed and returned.
+ ///
+ /// # Guarantees
+ ///
+ /// After this method returns, the work item is guaranteed to be:
+ /// - Not pending in any queue.
+ /// - Not running on any CPU.
+ ///
+ /// This makes it safe to use during teardown (e.g., driver `remove` or object `drop`)
+ /// to ensure no background tasks are accessing resources that are about to be freed.
+ ///
+ /// # Safety
+ ///
+ /// Same as [`cancel`], it only reclaims ownership if the kernel confirms the work
+ /// was still pending and is now removed.
+ ///
+ /// [`cancel`]: Work::cancel
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork};
+ /// # use kernel::impl_has_work;
+ /// # use kernel::sync::Arc;
+ /// # #[pin_data]
+ /// # struct MyStruct { #[pin] work: Work<MyStruct> }
+ /// # impl_has_work! { impl HasWork<Self> for MyStruct { self.work } }
+ /// # impl WorkItem for MyStruct {
+ /// # type Pointer = Arc<MyStruct>;
+ /// # fn run(_this: Arc<MyStruct>) {}
+ /// # }
+ /// let my_struct = Arc::pin_init(pin_init!(MyStruct {
+ /// work <- new_work!("MyStruct::work"),
+ /// }), kernel::alloc::flags::GFP_KERNEL).unwrap();
+ /// workqueue::system().enqueue(my_struct.clone());
+ /// let reclaimed = my_struct.work.cancel_sync();
+ /// assert!(reclaimed.is_some());
+ /// ```
+ pub fn cancel_sync(&self) -> Option<T::Pointer>
+ where
+ T: WorkItem<ID>,
+ {
+ let work_ptr = self.work.get();
+ // SAFETY: `work_ptr` is a valid pointer to a `work_struct`.
+ if unsafe { bindings::cancel_work_sync(work_ptr) } {
+ // SAFETY: The work item was successfully cancelled/waited for, and is guaranteed
+ // not to run again unless re-enqueued. We reclaim the ownership leaked during
+ // `enqueue`.
+ Some(unsafe { T::Pointer::reclaim(work_ptr) })
+ } else {
+ None
+ }
+ }
+
/// Get a pointer to the inner `work_struct`.
///
/// # Safety
@@ -674,25 +842,14 @@ pub fn new(
pin_init!(Self {
dwork <- Opaque::ffi_init(|slot: *mut bindings::delayed_work| {
// SAFETY: The `WorkItemPointer` implementation promises that `run` can be used as
- // the work item function.
+ // the work item function. We use the C-helper to ensure the timer function
+ // and data are initialized correctly according to kernel macros.
unsafe {
- bindings::init_work_with_key(
- core::ptr::addr_of_mut!((*slot).work),
+ bindings::init_delayed_work(
+ slot,
Some(T::Pointer::run),
- false,
work_name.as_char_ptr(),
work_key.as_ptr(),
- )
- }
-
- // SAFETY: The `delayed_work_timer_fn` function pointer can be used here because
- // the timer is embedded in a `struct delayed_work`, and only ever scheduled via
- // the core workqueue code, and configured to run in irqsafe context.
- unsafe {
- bindings::timer_init_key(
- core::ptr::addr_of_mut!((*slot).timer),
- Some(bindings::delayed_work_timer_fn),
- bindings::TIMER_IRQSAFE,
timer_name.as_char_ptr(),
timer_key.as_ptr(),
)
@@ -702,6 +859,89 @@ pub fn new(
})
}
+ /// Returns whether the work item is currently pending.
+ ///
+ /// # Warning
+ ///
+ /// This method is inherently racy. See [`Work::is_pending`].
+ ///
+ /// # Examples
+ ///
+ /// See [`Work::is_pending`].
+ ///
+ /// [`Work::is_pending`]: Work::is_pending
+ #[inline]
+ pub fn is_pending(&self) -> bool {
+ // SAFETY: `self.dwork` is reaching into a valid Opaque<bindings::delayed_work>.
+ unsafe {
+ let ptr: *mut bindings::delayed_work = self.dwork.get();
+ bindings::work_pending(core::ptr::addr_of_mut!((*ptr).work))
+ }
+ }
+
+ /// Cancels the delayed work item.
+ ///
+ /// If the work item was successfully cancelled (i.e., it was pending and had not yet
+ /// started running), the original pointer is reclaimed and returned.
+ ///
+ /// # Guarantees
+ ///
+ /// See [`Work::cancel`].
+ ///
+ /// # Safety
+ ///
+ /// Same as [`Work::cancel`].
+ ///
+ /// [`cancel_sync`]: DelayedWork::cancel_sync
+ /// [`Work::cancel`]: Work::cancel
+ ///
+ /// # Examples
+ ///
+ /// See [`Work::cancel`].
+ pub fn cancel(&self) -> Option<T::Pointer>
+ where
+ T: WorkItem<ID>,
+ {
+ let dwork_ptr = self.dwork.get();
+ // SAFETY: `dwork_ptr` is a valid pointer to a `delayed_work`.
+ if unsafe { bindings::cancel_delayed_work(dwork_ptr) } {
+ // SAFETY: The work item was successfully cancelled and is guaranteed not to run,
+ // so we can safely reclaim the ownership leaked during `enqueue`.
+ Some(unsafe { T::Pointer::reclaim(core::ptr::addr_of_mut!((*dwork_ptr).work)) })
+ } else {
+ None
+ }
+ }
+
+ /// Synchronously cancels the delayed work item.
+ ///
+ /// This method waits for the work item to finish if it is currently running.
+ /// If the work item was successfully cancelled from the queue, the pointer is
+ /// reclaimed and returned.
+ ///
+ /// # Guarantees
+ ///
+ /// See [`Work::cancel_sync`].
+ ///
+ /// # Safety
+ ///
+ /// Same as [`Work::cancel_sync`].
+ pub fn cancel_sync(&self) -> Option<T::Pointer>
+ where
+ T: WorkItem<ID>,
+ {
+ let dwork_ptr = self.dwork.get();
+ // SAFETY: `dwork_ptr` is a valid pointer to a `delayed_work`.
+ if unsafe { bindings::cancel_delayed_work_sync(dwork_ptr) } {
+ // SAFETY: The work item was successfully cancelled/waited for, and is guaranteed
+ // not to run again unless re-enqueued. We reclaim the ownership leaked during
+ // `enqueue`.
+ Some(unsafe { T::Pointer::reclaim(core::ptr::addr_of_mut!((*dwork_ptr).work)) })
+ } else {
+ None
+ }
+ }
+
/// Get a pointer to the inner `delayed_work`.
///
/// # Safety
@@ -781,22 +1021,11 @@ unsafe fn raw_get_work(
unsafe fn work_container_of(
ptr: *mut $crate::workqueue::Work<$work_type $(, $id)?>,
) -> *mut Self {
- // SAFETY: The caller promises that the pointer points at a field of the right type
- // in the right kind of struct.
- let ptr = unsafe { $crate::workqueue::Work::raw_get(ptr) };
-
- // SAFETY: The caller promises that the pointer points at a field of the right type
- // in the right kind of struct.
- let delayed_work = unsafe {
- $crate::container_of!(ptr, $crate::bindings::delayed_work, work)
- };
-
- let delayed_work: *mut $crate::workqueue::DelayedWork<$work_type $(, $id)?> =
- delayed_work.cast();
-
- // SAFETY: The caller promises that the pointer points at a field of the right type
- // in the right kind of struct.
- unsafe { $crate::container_of!(delayed_work, Self, $field) }
+ // SAFETY: The caller promises that the pointer points at the `work` field
+ // of a `delayed_work` struct, which is itself the `dwork` field of a
+ // `DelayedWork` wrapper, which is the `$field` field of a `Self` struct.
+ let ptr = ptr.cast::<$crate::workqueue::DelayedWork<$work_type $(, $id)?>>();
+ unsafe { $crate::container_of!(ptr, Self, $field) }
}
}
)*};
@@ -827,6 +1056,15 @@ unsafe impl<T, const ID: u64> WorkItemPointer<ID> for Arc<T>
T::run(arc)
}
+
+ unsafe fn reclaim(ptr: *mut bindings::work_struct) -> Self {
+ // The `__enqueue` method always uses a `work_struct` stored in a `Work<T, ID>`.
+ let ptr = ptr.cast::<Work<T, ID>>();
+ // SAFETY: This computes the pointer that `__enqueue` got from `Arc::into_raw`.
+ let ptr = unsafe { T::work_container_of(ptr) };
+ // SAFETY: This pointer comes from `Arc::into_raw` and we've been given back ownership.
+ unsafe { Arc::from_raw(ptr) }
+ }
}
// SAFETY: The `work_struct` raw pointer is guaranteed to be valid for the duration of the call to
@@ -874,7 +1112,8 @@ unsafe impl<T, const ID: u64> RawDelayedWorkItem<ID> for Arc<T>
{
}
-// SAFETY: TODO.
+// SAFETY: The `WorkItemPointer` implementation for `Pin<KBox<T>>` is safe because `KBox::from_raw`
+// correctly reconstructs the box that was leaked during `enqueue` (via `KBox::into_raw`).
unsafe impl<T, const ID: u64> WorkItemPointer<ID> for Pin<KBox<T>>
where
T: WorkItem<ID, Pointer = Self>,
@@ -883,18 +1122,35 @@ unsafe impl<T, const ID: u64> WorkItemPointer<ID> for Pin<KBox<T>>
unsafe extern "C" fn run(ptr: *mut bindings::work_struct) {
// The `__enqueue` method always uses a `work_struct` stored in a `Work<T, ID>`.
let ptr = ptr.cast::<Work<T, ID>>();
- // SAFETY: This computes the pointer that `__enqueue` got from `Arc::into_raw`.
+ // SAFETY: This computes the pointer that `__enqueue` got from `KBox::into_raw`.
let ptr = unsafe { T::work_container_of(ptr) };
- // SAFETY: This pointer comes from `Arc::into_raw` and we've been given back ownership.
+ // SAFETY: This pointer comes from `KBox::into_raw` and we've been given back ownership.
let boxed = unsafe { KBox::from_raw(ptr) };
// SAFETY: The box was already pinned when it was enqueued.
let pinned = unsafe { Pin::new_unchecked(boxed) };
T::run(pinned)
}
+
+ unsafe fn reclaim(ptr: *mut bindings::work_struct) -> Self {
+ // The `__enqueue` method always uses a `work_struct` stored in a `Work<T, ID>`.
+ let ptr = ptr.cast::<Work<T, ID>>();
+ // SAFETY: This computes the pointer that `__enqueue` got from `KBox::into_raw`.
+ let ptr = unsafe { T::work_container_of(ptr) };
+ // SAFETY: This pointer comes from `KBox::into_raw` and we've been given back ownership.
+ let boxed = unsafe { KBox::from_raw(ptr) };
+ // SAFETY: The box was already pinned when it was enqueued.
+ unsafe { Pin::new_unchecked(boxed) }
+ }
}
-// SAFETY: TODO.
+// SAFETY: The `work_struct` raw pointer is guaranteed to be valid for the duration of the call to
+// the closure because we have exclusive ownership of the `KBox`, and we don't drop it ourselves.
+// If `queue_work_on` returns true, it is further guaranteed to be valid until a call to the
+// function pointer in `work_struct` because we leak the memory it points to, and only reclaim it
+// if the closure returns false (not reachable for KBox as it must succeed) or in
+// `WorkItemPointer::run`, which is what the function pointer in the `work_struct` must be
+// pointing to.
unsafe impl<T, const ID: u64> RawWorkItem<ID> for Pin<KBox<T>>
where
T: WorkItem<ID, Pointer = Self>,
@@ -1022,3 +1278,10 @@ pub fn system_bh_highpri() -> &'static Queue {
// SAFETY: `system_bh_highpri_wq` is a C global, always available.
unsafe { Queue::from_raw(bindings::system_bh_highpri_wq) }
}
+
+/// Returns the strong count of an [`Arc`] for testing purposes.
+///
+/// # Safety
+///
+/// This is intended for use in KUnit tests and sample modules ONLY.
+#[cfg(CONFIG_KUNIT)]
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v4 3/3] rust: workqueue: add KUnit and sample stress tests
2026-04-07 10:37 [PATCH v4 0/3] rust: workqueue: add safe cancellation and status methods Aakash Bollineni via B4 Relay
2026-04-07 10:37 ` [PATCH v4 1/3] rust: helpers: add workqueue helpers Aakash Bollineni via B4 Relay
2026-04-07 10:37 ` [PATCH v4 2/3] rust: workqueue: add safe cancellation and status methods Aakash Bollineni via B4 Relay
@ 2026-04-07 10:37 ` Aakash Bollineni via B4 Relay
2 siblings, 0 replies; 6+ messages in thread
From: Aakash Bollineni via B4 Relay @ 2026-04-07 10:37 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Wedson Almeida Filho, Gary Guo,
Björn Roy Baron, Alice Ryhl, Tejun Heo, Lai Jiangshan,
Boqun Feng, Benno Lossin, Andreas Hindborg
Cc: rust-for-linux, linux-kernel, Aakash Bollineni, kernel test robot
From: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
To ensure the safety and correctness of the improved workqueue API,
this patch adds comprehensive testing infrastructure:
1. KUnit Tests: Adds an internal 'rust_kernel_workqueue' test suite
to rust/kernel/workqueue.rs.
2. Sample Module: Updates samples/rust/rust_workqueue_test.rs for
stress verification.
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202604071427.209bk.JHO@intel.com/
v4: Fixed rustfmt and build errors in samples/rust/rust_workqueue_test.rs.
Signed-off-by: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
---
rust/kernel/workqueue.rs | 130 ++++++++++++++++++++++++
samples/rust/Kconfig | 10 ++
samples/rust/Makefile | 2 +
samples/rust/rust_workqueue_test.rs | 192 ++++++++++++++++++++++++++++++++++++
4 files changed, 334 insertions(+)
diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs
index 94a52c278776..d25e0fa9d44c 100644
--- a/rust/kernel/workqueue.rs
+++ b/rust/kernel/workqueue.rs
@@ -1285,3 +1285,133 @@ pub fn system_bh_highpri() -> &'static Queue {
///
/// This is intended for use in KUnit tests and sample modules ONLY.
#[cfg(CONFIG_KUNIT)]
+
+#[macros::kunit_tests(rust_kernel_workqueue)]
+mod tests {
+ use super::*;
+ use crate::sync::Arc;
+
+ #[pin_data]
+ struct TestWorkItem {
+ #[pin]
+ work: Work<TestWorkItem>,
+ value: i32,
+ }
+
+ impl_has_work! {
+ impl HasWork<Self> for TestWorkItem { self.work }
+ }
+
+ impl WorkItem for TestWorkItem {
+ type Pointer = Arc<Self>;
+ fn run(_this: Arc<Self>) {}
+ }
+
+ #[pin_data]
+ struct TestDelayedWorkItem {
+ #[pin]
+ delayed_work: DelayedWork<TestDelayedWorkItem>,
+ value: i32,
+ }
+
+ impl_has_delayed_work! {
+ impl HasDelayedWork<Self> for TestDelayedWorkItem { self.delayed_work }
+ }
+
+ impl WorkItem for TestDelayedWorkItem {
+ type Pointer = Arc<Self>;
+ fn run(_this: Arc<Self>) {}
+ }
+
+ #[test]
+ fn test_work_cancel_reclaim() {
+ let item = Arc::pin_init(
+ pin_init!(TestWorkItem {
+ work <- new_work!("TestWorkItem::work"),
+ value: 42,
+ }),
+ GFP_KERNEL,
+ )
+ .expect("Failed to allocate TestWorkItem");
+
+ let initial_count = arc_count(&item);
+
+ // Enqueue
+ let _ = system().enqueue(item.clone());
+
+ // Cancel and Reclaim (if it was pending)
+ if let Some(reclaimed) = item.work.cancel() {
+ assert!(arc_count(&item) == initial_count + 1);
+ drop(reclaimed);
+ assert!(arc_count(&item) == initial_count);
+ }
+ }
+
+ #[test]
+ fn test_work_cancel_sync_reclaim() {
+ let item = Arc::pin_init(
+ pin_init!(TestWorkItem {
+ work <- new_work!("TestWorkItem::work"),
+ value: 42,
+ }),
+ GFP_KERNEL,
+ )
+ .expect("Failed to allocate TestWorkItem");
+
+ let initial_count = arc_count(&item);
+
+ // Enqueue
+ let _ = system().enqueue(item.clone());
+
+ // Cancel Sync and Reclaim
+ if let Some(reclaimed) = item.work.cancel_sync() {
+ assert!(arc_count(&item) == initial_count + 1);
+ drop(reclaimed);
+ assert!(arc_count(&item) == initial_count);
+ }
+ }
+
+ #[test]
+ fn test_work_stress_enqueue_cancel() {
+ let item = Arc::pin_init(
+ pin_init!(TestWorkItem {
+ work <- new_work!("TestWorkItem::work"),
+ value: 42,
+ }),
+ GFP_KERNEL,
+ )
+ .expect("Failed to allocate TestWorkItem");
+
+ for _ in 0..100 {
+ if let Ok(_) = system().enqueue(item.clone()) {
+ let _ = item.work.cancel_sync();
+ }
+ }
+
+ assert_eq!(arc_count(&item), 1);
+ }
+
+ #[test]
+ fn test_delayed_work_cancel_reclaim() {
+ let item = Arc::pin_init(
+ pin_init!(TestDelayedWorkItem {
+ delayed_work <- new_delayed_work!("TestDelayedWorkItem::delayed_work"),
+ value: 42,
+ }),
+ GFP_KERNEL,
+ )
+ .expect("Failed to allocate TestDelayedWorkItem");
+
+ let initial_count = arc_count(&item);
+
+ // Enqueue delayed (use a longer delay to ensure it stays pending)
+ let _ = system().enqueue_delayed(item.clone(), 1000);
+
+ // Cancel and Reclaim
+ if let Some(reclaimed) = item.delayed_work.cancel() {
+ assert!(arc_count(&item) == initial_count + 1);
+ drop(reclaimed);
+ assert!(arc_count(&item) == initial_count);
+ }
+ }
+}
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index c49ab9106345..b3f078f77ca2 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -172,6 +172,16 @@ config SAMPLE_RUST_SOC
If unsure, say N.
+config SAMPLE_RUST_WORKQUEUE
+ tristate "Workqueue"
+ help
+ This option builds the Rust workqueue robust stress test sample.
+
+ To compile this as a module, choose M here:
+ the module will be called rust_workqueue_test.
+
+ If unsure, say N.
+
config SAMPLE_RUST_HOSTPROGS
bool "Host programs"
help
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 6c0aaa58cccc..261aa67b6502 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -20,3 +20,5 @@ obj-$(CONFIG_SAMPLE_RUST_SOC) += rust_soc.o
rust_print-y := rust_print_main.o rust_print_events.o
subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs
+
+obj-$(CONFIG_SAMPLE_RUST_WORKQUEUE) += rust_workqueue_test.o
diff --git a/samples/rust/rust_workqueue_test.rs b/samples/rust/rust_workqueue_test.rs
new file mode 100644
index 000000000000..e21296fdb34d
--- /dev/null
+++ b/samples/rust/rust_workqueue_test.rs
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Robust stress test for Rust workqueue API.
+
+use kernel::prelude::*;
+use kernel::sync::Arc;
+use kernel::time::msecs_to_jiffies;
+use kernel::workqueue::{self, new_work, Work, WorkItem};
+
+#[pin_data]
+struct TestWorkItem {
+ #[pin]
+ work: Work<TestWorkItem>,
+ value: i32,
+}
+
+kernel::impl_has_work! {
+ impl HasWork<Self> for TestWorkItem { self.work }
+}
+
+impl WorkItem for TestWorkItem {
+ type Pointer = Arc<TestWorkItem>;
+
+ fn run(this: Arc<TestWorkItem>) {
+ pr_info!(
+ "Rust workqueue test: Work item running (value: {})\n",
+ this.value
+ );
+ }
+}
+
+#[pin_data]
+struct TestDelayedWorkItem {
+ #[pin]
+ delayed_work: workqueue::DelayedWork<TestDelayedWorkItem>,
+ value: i32,
+}
+
+// SAFETY: The `delayed_work` field is at a fixed offset and is valid for the lifetime of
+// `TestDelayedWorkItem`.
+unsafe impl workqueue::HasDelayedWork<Self> for TestDelayedWorkItem {}
+
+impl WorkItem for TestDelayedWorkItem {
+ type Pointer = Arc<TestDelayedWorkItem>;
+
+ fn run(this: Arc<TestDelayedWorkItem>) {
+ pr_info!(
+ "Rust workqueue test: Delayed work item running (value: {})\n",
+ this.value
+ );
+ }
+}
+
+struct RustWorkqueueTest;
+
+impl kernel::Module for RustWorkqueueTest {
+ fn init(_module: &'static ThisModule) -> Result<Self> {
+ pr_info!("Rust workqueue test: starting robust verification (v4)\n");
+
+ // 1. Basic Lifecycle with Refcount Validation (Standard Work)
+ {
+ let work_item = Arc::pin_init(
+ pin_init!(TestWorkItem {
+ work <- new_work!("TestWorkItem::work"),
+ value: 42,
+ }),
+ GFP_KERNEL,
+ )?;
+
+ let initial_count = workqueue::arc_count(&work_item);
+ pr_info!("Initial Arc strong count: {}\n", initial_count);
+
+ // Enqueue
+ let enqueued_item = work_item.clone();
+
+ if let Err(returned_item) = workqueue::system().enqueue(enqueued_item) {
+ pr_warn!("Work already pending, unexpected!\n");
+ let _ = returned_item;
+ } else {
+ pr_info!(
+ "Work enqueued successfully. Strong count: {}\n",
+ workqueue::arc_count(&work_item)
+ );
+ }
+
+ // Cancel immediately
+ if let Some(reclaimed) = work_item.work.cancel() {
+ let count_after_cancel = workqueue::arc_count(&work_item);
+ pr_info!(
+ "Success: Work cancelled and Arc reclaimed. Strong count: {}\n",
+ count_after_cancel
+ );
+
+ if count_after_cancel != initial_count + 1 {
+ pr_err!(
+ "ERROR: Refcount mismatch after cancel! Expected {}, got {}\n",
+ initial_count + 1,
+ count_after_cancel
+ );
+ return Err(ENXIO);
+ }
+ drop(reclaimed);
+ if workqueue::arc_count(&work_item) != initial_count {
+ pr_err!("ERROR: Refcount mismatch after drop!\n");
+ return Err(ENXIO);
+ }
+ } else {
+ pr_info!("Work already running or finished.\n");
+ }
+ }
+
+ // 2. Stress Testing: Enqueue/Cancel Sync Loop
+ {
+ pr_info!("Starting stress test (1000 iterations)...\n");
+ let work_item = Arc::pin_init(
+ pin_init!(TestWorkItem {
+ work <- new_work!("TestWorkItem::work"),
+ value: 99,
+ }),
+ GFP_KERNEL,
+ )?;
+
+ for i in 0..1000 {
+ let _ = workqueue::system().enqueue(work_item.clone());
+ let _ = work_item.work.cancel_sync();
+ if i % 250 == 0 {
+ pr_info!("Stress test progress: {}/1000\n", i);
+ }
+ }
+
+ if workqueue::arc_count(&work_item) != 1 {
+ pr_err!("ERROR: Refcount leak detected after stress test!\n");
+ return Err(ENXIO);
+ } else {
+ pr_info!("Stress test completed successfully.\n");
+ }
+ }
+
+ // 3. Delayed Work Cancellation Test
+ {
+ let delayed_item = Arc::pin_init(
+ pin_init!(TestDelayedWorkItem {
+ delayed_work <-
+ workqueue::new_delayed_work!("TestDelayedWorkItem::delayed_work"),
+ value: 7,
+ }),
+ GFP_KERNEL,
+ )?;
+
+ let initial_count = workqueue::arc_count(&delayed_item);
+
+ // Schedule with a long delay (5 seconds)
+ if let Err(returned) =
+ workqueue::system().enqueue_delayed(delayed_item.clone(), msecs_to_jiffies(5000))
+ {
+ drop(returned);
+ } else {
+ pr_info!(
+ "Delayed work enqueued. count: {}\n",
+ workqueue::arc_count(&delayed_item)
+ );
+ }
+
+ if let Some(reclaimed) = delayed_item.delayed_work.cancel() {
+ pr_info!("Success: Delayed work reclaimed. No leak.\n");
+ drop(reclaimed);
+ }
+
+ if workqueue::arc_count(&delayed_item) != initial_count {
+ pr_err!("ERROR: Refcount leak after delayed cancel!\n");
+ return Err(ENXIO);
+ }
+ }
+
+ pr_info!("Rust workqueue test: all robust checks passed\n");
+ Ok(RustWorkqueueTest)
+ }
+}
+
+impl Drop for RustWorkqueueTest {
+ fn drop(&mut self) {
+ pr_info!("Rust workqueue test: exit\n");
+ }
+}
+
+module! {
+ type: RustWorkqueueTest,
+ name: "rust_workqueue_test",
+ authors: ["Aakash Bollineni"],
+ description: "Robust stress test for Rust workqueue API",
+ license: "GPL",
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v4 2/3] rust: workqueue: add safe cancellation and status methods
2026-04-07 10:37 ` [PATCH v4 2/3] rust: workqueue: add safe cancellation and status methods Aakash Bollineni via B4 Relay
@ 2026-04-07 11:33 ` Onur Özkan
0 siblings, 0 replies; 6+ messages in thread
From: Onur Özkan @ 2026-04-07 11:33 UTC (permalink / raw)
To: Aakash Bollineni via B4 Relay
Cc: Miguel Ojeda, Alex Gaynor, Wedson Almeida Filho, Gary Guo,
Björn Roy Baron, Alice Ryhl, Tejun Heo, Lai Jiangshan,
Boqun Feng, Benno Lossin, Andreas Hindborg, rust-for-linux,
linux-kernel, Aakash Bollineni
> From: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
>
> Modernize the Rust workqueue by adding methods for status checking
> and cancellation of work and delayed work items.
>
> Specifically, this patch adds:
> - is_pending(): Returns true if the work item is currently enqueued.
> - cancel(): Attempts to cancel the work item before it runs.
> - cancel_sync(): Synchronously cancels the work item, waiting for it
> to finish if it's already running.
> Reclaims ownership if the work was pending.
>
> Signed-off-by: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
> ---
> rust/kernel/workqueue.rs | 333 ++++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 298 insertions(+), 35 deletions(-)
>
> diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs
> index 706e833e9702..94a52c278776 100644
> --- a/rust/kernel/workqueue.rs
> +++ b/rust/kernel/workqueue.rs
> @@ -448,8 +448,26 @@ pub unsafe trait WorkItemPointer<const ID: u64>: RawWorkItem<ID> {
> /// The provided `work_struct` pointer must originate from a previous call to [`__enqueue`]
> /// where the `queue_work_on` closure returned true, and the pointer must still be valid.
> ///
> + /// The implementation must ensure that the pointer is reclaimed (e.g., via `from_raw`)
> + /// before calling the `run` method of the underlying [`WorkItem`].
> + ///
> /// [`__enqueue`]: RawWorkItem::__enqueue
> unsafe extern "C" fn run(ptr: *mut bindings::work_struct);
> +
> + /// Reclaims ownership of the pointer from the work item.
> + ///
> + /// This is called when a work item is successfully cancelled, allowing the caller
> + /// to recover the original pointer (e.g., `Arc` or `KBox`) that was "leaked"
> + /// during enqueuing.
> + ///
> + /// # Safety
> + ///
> + /// The provided `work_struct` pointer must originate from a previous call to [`__enqueue`]
> + /// where the `queue_work_on` closure returned true, and the work item must have been
> + /// successfully cancelled (i.e., `cancel_work` returned true).
> + ///
> + /// [`__enqueue`]: RawWorkItem::__enqueue
> + unsafe fn reclaim(ptr: *mut bindings::work_struct) -> Self;
> }
>
> /// Defines the method that should be called when this work item is executed.
> @@ -516,6 +534,156 @@ pub fn new(name: &'static CStr, key: Pin<&'static LockClassKey>) -> impl PinInit
> })
> }
>
> + /// Returns whether the work item is currently pending.
> + ///
> + /// # Warning
> + ///
> + /// This method is inherently racy. A work item can be enqueued or start running
> + /// immediately after this check returns. Do not rely on this for correctness
> + /// logic (e.g., as a guard for unsafe operations); use it only for debugging or
> + /// non-critical status reporting.
> + ///
> + /// # Examples
> + ///
> + /// ```
> + /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork};
> + /// # use kernel::impl_has_work;
> + /// # use kernel::sync::Arc;
> + /// # #[pin_data]
> + /// # struct MyStruct { #[pin] work: Work<MyStruct> }
> + /// # impl_has_work! { impl HasWork<Self> for MyStruct { self.work } }
> + /// # impl WorkItem for MyStruct {
> + /// # type Pointer = Arc<MyStruct>;
> + /// # fn run(_this: Arc<MyStruct>) {}
> + /// # }
> + /// let my_struct = Arc::pin_init(pin_init!(MyStruct {
> + /// work <- new_work!("MyStruct::work"),
> + /// }), kernel::alloc::flags::GFP_KERNEL).unwrap();
> + /// assert!(!my_struct.work.is_pending());
> + /// workqueue::system().enqueue(my_struct.clone());
> + /// assert!(my_struct.work.is_pending());
> + /// # let _ = my_struct.work.cancel();
> + /// ```
Can you add some spaces between certain lines? The bulk style makes it quite
hard to follow in my opinion. This applies to other examples too.
> + #[inline]
> + pub fn is_pending(&self) -> bool {
> + // SAFETY: `self.work` is a valid pointer to a `work_struct`.
> + unsafe { bindings::work_pending(self.work.get()) }
> + }
> +
> + /// Cancels the work item.
> + ///
> + /// If the work item was successfully cancelled (i.e., it was pending and had not yet
> + /// started running), the original pointer is reclaimed and returned.
> + ///
> + /// # Guarantees
> + ///
> + /// This method is non-blocking and may return while the work item is still running
> + /// on another CPU. If it returns `None`, the work item might be about to start,
> + /// is currently running, or has already finished.
> + ///
> + /// # Safety
> + ///
> + /// This is safe because ownership is only reclaimed if the kernel confirms (via
> + /// `cancel_work` returning true) that the work item is no longer in any queue and
> + /// will not be executed by the workqueue for this specific enqueue event.
> + ///
> + /// [`cancel_sync`]: Work::cancel_sync
Why not using [`Work::cancel_sync`] directly?
> + ///
> + /// # Examples
> + ///
> + /// ```
> + /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork};
> + /// # use kernel::impl_has_work;
> + /// # use kernel::sync::Arc;
> + /// # #[pin_data]
> + /// # struct MyStruct { #[pin] work: Work<MyStruct> }
> + /// # impl_has_work! { impl HasWork<Self> for MyStruct { self.work } }
> + /// # impl WorkItem for MyStruct {
> + /// # type Pointer = Arc<MyStruct>;
> + /// # fn run(_this: Arc<MyStruct>) {}
> + /// # }
> + /// let my_struct = Arc::pin_init(pin_init!(MyStruct {
> + /// work <- new_work!("MyStruct::work"),
> + /// }), kernel::alloc::flags::GFP_KERNEL).unwrap();
> + /// workqueue::system().enqueue(my_struct.clone());
> + /// assert!(my_struct.work.is_pending());
> + /// let reclaimed = my_struct.work.cancel();
> + /// assert!(reclaimed.is_some());
> + /// assert!(!my_struct.work.is_pending());
> + /// ```
> + pub fn cancel(&self) -> Option<T::Pointer>
> + where
> + T: WorkItem<ID>,
> + {
> + let work_ptr = self.work.get();
> + // SAFETY: `work_ptr` is a valid pointer to a `work_struct`.
> + if unsafe { bindings::cancel_work(work_ptr) } {
> + // SAFETY: The work item was successfully cancelled and is guaranteed not to run,
> + // so we can safely reclaim the ownership leaked during `enqueue`.
> + Some(unsafe { T::Pointer::reclaim(work_ptr) })
> + } else {
> + None
> + }
> + }
> +
> + /// Synchronously cancels the work item.
> + ///
> + /// This method waits for the work item to finish if it is currently running.
> + /// If the work item was successfully cancelled from the queue, the pointer is
> + /// reclaimed and returned.
> + ///
> + /// # Guarantees
> + ///
> + /// After this method returns, the work item is guaranteed to be:
> + /// - Not pending in any queue.
> + /// - Not running on any CPU.
> + ///
> + /// This makes it safe to use during teardown (e.g., driver `remove` or object `drop`)
> + /// to ensure no background tasks are accessing resources that are about to be freed.
> + ///
> + /// # Safety
> + ///
> + /// Same as [`cancel`], it only reclaims ownership if the kernel confirms the work
> + /// was still pending and is now removed.
> + ///
> + /// [`cancel`]: Work::cancel
> + ///
> + /// # Examples
> + ///
> + /// ```
> + /// # use kernel::workqueue::{self, new_work, Work, WorkItem, HasWork};
> + /// # use kernel::impl_has_work;
> + /// # use kernel::sync::Arc;
> + /// # #[pin_data]
> + /// # struct MyStruct { #[pin] work: Work<MyStruct> }
> + /// # impl_has_work! { impl HasWork<Self> for MyStruct { self.work } }
> + /// # impl WorkItem for MyStruct {
> + /// # type Pointer = Arc<MyStruct>;
> + /// # fn run(_this: Arc<MyStruct>) {}
> + /// # }
> + /// let my_struct = Arc::pin_init(pin_init!(MyStruct {
> + /// work <- new_work!("MyStruct::work"),
> + /// }), kernel::alloc::flags::GFP_KERNEL).unwrap();
> + /// workqueue::system().enqueue(my_struct.clone());
> + /// let reclaimed = my_struct.work.cancel_sync();
> + /// assert!(reclaimed.is_some());
> + /// ```
> + pub fn cancel_sync(&self) -> Option<T::Pointer>
> + where
> + T: WorkItem<ID>,
> + {
> + let work_ptr = self.work.get();
> + // SAFETY: `work_ptr` is a valid pointer to a `work_struct`.
> + if unsafe { bindings::cancel_work_sync(work_ptr) } {
> + // SAFETY: The work item was successfully cancelled/waited for, and is guaranteed
> + // not to run again unless re-enqueued. We reclaim the ownership leaked during
> + // `enqueue`.
> + Some(unsafe { T::Pointer::reclaim(work_ptr) })
> + } else {
> + None
> + }
> + }
> +
> /// Get a pointer to the inner `work_struct`.
> ///
> /// # Safety
> @@ -674,25 +842,14 @@ pub fn new(
> pin_init!(Self {
> dwork <- Opaque::ffi_init(|slot: *mut bindings::delayed_work| {
> // SAFETY: The `WorkItemPointer` implementation promises that `run` can be used as
> - // the work item function.
> + // the work item function. We use the C-helper to ensure the timer function
> + // and data are initialized correctly according to kernel macros.
> unsafe {
> - bindings::init_work_with_key(
> - core::ptr::addr_of_mut!((*slot).work),
> + bindings::init_delayed_work(
> + slot,
> Some(T::Pointer::run),
> - false,
> work_name.as_char_ptr(),
> work_key.as_ptr(),
> - )
> - }
> -
> - // SAFETY: The `delayed_work_timer_fn` function pointer can be used here because
> - // the timer is embedded in a `struct delayed_work`, and only ever scheduled via
> - // the core workqueue code, and configured to run in irqsafe context.
> - unsafe {
> - bindings::timer_init_key(
> - core::ptr::addr_of_mut!((*slot).timer),
> - Some(bindings::delayed_work_timer_fn),
> - bindings::TIMER_IRQSAFE,
> timer_name.as_char_ptr(),
> timer_key.as_ptr(),
> )
> @@ -702,6 +859,89 @@ pub fn new(
> })
> }
>
> + /// Returns whether the work item is currently pending.
> + ///
> + /// # Warning
> + ///
> + /// This method is inherently racy. See [`Work::is_pending`].
> + ///
> + /// # Examples
> + ///
> + /// See [`Work::is_pending`].
> + ///
> + /// [`Work::is_pending`]: Work::is_pending
I guess this is a leftover or something? It doesn't seem to make any sense.
> + #[inline]
> + pub fn is_pending(&self) -> bool {
> + // SAFETY: `self.dwork` is reaching into a valid Opaque<bindings::delayed_work>.
> + unsafe {
> + let ptr: *mut bindings::delayed_work = self.dwork.get();
> + bindings::work_pending(core::ptr::addr_of_mut!((*ptr).work))
> + }
> + }
> +
> + /// Cancels the delayed work item.
> + ///
> + /// If the work item was successfully cancelled (i.e., it was pending and had not yet
> + /// started running), the original pointer is reclaimed and returned.
> + ///
> + /// # Guarantees
> + ///
> + /// See [`Work::cancel`].
> + ///
> + /// # Safety
> + ///
> + /// Same as [`Work::cancel`].
> + ///
> + /// [`cancel_sync`]: DelayedWork::cancel_sync
> + /// [`Work::cancel`]: Work::cancel
1- Same as above, these don't make any sense.
2- cancel_sync is not used anywhere. I assume the refs are wrong?
> + ///
> + /// # Examples
> + ///
> + /// See [`Work::cancel`].
> + pub fn cancel(&self) -> Option<T::Pointer>
> + where
> + T: WorkItem<ID>,
> + {
> + let dwork_ptr = self.dwork.get();
> + // SAFETY: `dwork_ptr` is a valid pointer to a `delayed_work`.
> + if unsafe { bindings::cancel_delayed_work(dwork_ptr) } {
> + // SAFETY: The work item was successfully cancelled and is guaranteed not to run,
> + // so we can safely reclaim the ownership leaked during `enqueue`.
> + Some(unsafe { T::Pointer::reclaim(core::ptr::addr_of_mut!((*dwork_ptr).work)) })
> + } else {
> + None
> + }
> + }
> +
> + /// Synchronously cancels the delayed work item.
> + ///
> + /// This method waits for the work item to finish if it is currently running.
> + /// If the work item was successfully cancelled from the queue, the pointer is
> + /// reclaimed and returned.
> + ///
> + /// # Guarantees
> + ///
> + /// See [`Work::cancel_sync`].
> + ///
> + /// # Safety
> + ///
> + /// Same as [`Work::cancel_sync`].
> + pub fn cancel_sync(&self) -> Option<T::Pointer>
> + where
> + T: WorkItem<ID>,
> + {
> + let dwork_ptr = self.dwork.get();
> + // SAFETY: `dwork_ptr` is a valid pointer to a `delayed_work`.
> + if unsafe { bindings::cancel_delayed_work_sync(dwork_ptr) } {
> + // SAFETY: The work item was successfully cancelled/waited for, and is guaranteed
> + // not to run again unless re-enqueued. We reclaim the ownership leaked during
> + // `enqueue`.
> + Some(unsafe { T::Pointer::reclaim(core::ptr::addr_of_mut!((*dwork_ptr).work)) })
> + } else {
> + None
> + }
> + }
> +
> /// Get a pointer to the inner `delayed_work`.
> ///
> /// # Safety
> @@ -781,22 +1021,11 @@ unsafe fn raw_get_work(
> unsafe fn work_container_of(
> ptr: *mut $crate::workqueue::Work<$work_type $(, $id)?>,
> ) -> *mut Self {
> - // SAFETY: The caller promises that the pointer points at a field of the right type
> - // in the right kind of struct.
> - let ptr = unsafe { $crate::workqueue::Work::raw_get(ptr) };
> -
> - // SAFETY: The caller promises that the pointer points at a field of the right type
> - // in the right kind of struct.
> - let delayed_work = unsafe {
> - $crate::container_of!(ptr, $crate::bindings::delayed_work, work)
> - };
> -
> - let delayed_work: *mut $crate::workqueue::DelayedWork<$work_type $(, $id)?> =
> - delayed_work.cast();
> -
> - // SAFETY: The caller promises that the pointer points at a field of the right type
> - // in the right kind of struct.
> - unsafe { $crate::container_of!(delayed_work, Self, $field) }
> + // SAFETY: The caller promises that the pointer points at the `work` field
> + // of a `delayed_work` struct, which is itself the `dwork` field of a
> + // `DelayedWork` wrapper, which is the `$field` field of a `Self` struct.
> + let ptr = ptr.cast::<$crate::workqueue::DelayedWork<$work_type $(, $id)?>>();
> + unsafe { $crate::container_of!(ptr, Self, $field) }
> }
> }
> )*};
> @@ -827,6 +1056,15 @@ unsafe impl<T, const ID: u64> WorkItemPointer<ID> for Arc<T>
>
> T::run(arc)
> }
> +
> + unsafe fn reclaim(ptr: *mut bindings::work_struct) -> Self {
Missing # SAFETY contract.
> + // The `__enqueue` method always uses a `work_struct` stored in a `Work<T, ID>`.
> + let ptr = ptr.cast::<Work<T, ID>>();
> + // SAFETY: This computes the pointer that `__enqueue` got from `Arc::into_raw`.
> + let ptr = unsafe { T::work_container_of(ptr) };
> + // SAFETY: This pointer comes from `Arc::into_raw` and we've been given back ownership.
> + unsafe { Arc::from_raw(ptr) }
> + }
> }
>
> // SAFETY: The `work_struct` raw pointer is guaranteed to be valid for the duration of the call to
> @@ -874,7 +1112,8 @@ unsafe impl<T, const ID: u64> RawDelayedWorkItem<ID> for Arc<T>
> {
> }
>
> -// SAFETY: TODO.
> +// SAFETY: The `WorkItemPointer` implementation for `Pin<KBox<T>>` is safe because `KBox::from_raw`
> +// correctly reconstructs the box that was leaked during `enqueue` (via `KBox::into_raw`).
> unsafe impl<T, const ID: u64> WorkItemPointer<ID> for Pin<KBox<T>>
> where
> T: WorkItem<ID, Pointer = Self>,
> @@ -883,18 +1122,35 @@ unsafe impl<T, const ID: u64> WorkItemPointer<ID> for Pin<KBox<T>>
> unsafe extern "C" fn run(ptr: *mut bindings::work_struct) {
> // The `__enqueue` method always uses a `work_struct` stored in a `Work<T, ID>`.
> let ptr = ptr.cast::<Work<T, ID>>();
> - // SAFETY: This computes the pointer that `__enqueue` got from `Arc::into_raw`.
> + // SAFETY: This computes the pointer that `__enqueue` got from `KBox::into_raw`.
> let ptr = unsafe { T::work_container_of(ptr) };
> - // SAFETY: This pointer comes from `Arc::into_raw` and we've been given back ownership.
> + // SAFETY: This pointer comes from `KBox::into_raw` and we've been given back ownership.
> let boxed = unsafe { KBox::from_raw(ptr) };
> // SAFETY: The box was already pinned when it was enqueued.
> let pinned = unsafe { Pin::new_unchecked(boxed) };
>
> T::run(pinned)
> }
> +
> + unsafe fn reclaim(ptr: *mut bindings::work_struct) -> Self {
Same here, missing # SAFETY contract.
> + // The `__enqueue` method always uses a `work_struct` stored in a `Work<T, ID>`.
> + let ptr = ptr.cast::<Work<T, ID>>();
> + // SAFETY: This computes the pointer that `__enqueue` got from `KBox::into_raw`.
> + let ptr = unsafe { T::work_container_of(ptr) };
> + // SAFETY: This pointer comes from `KBox::into_raw` and we've been given back ownership.
> + let boxed = unsafe { KBox::from_raw(ptr) };
> + // SAFETY: The box was already pinned when it was enqueued.
> + unsafe { Pin::new_unchecked(boxed) }
> + }
> }
>
> -// SAFETY: TODO.
> +// SAFETY: The `work_struct` raw pointer is guaranteed to be valid for the duration of the call to
> +// the closure because we have exclusive ownership of the `KBox`, and we don't drop it ourselves.
> +// If `queue_work_on` returns true, it is further guaranteed to be valid until a call to the
> +// function pointer in `work_struct` because we leak the memory it points to, and only reclaim it
> +// if the closure returns false (not reachable for KBox as it must succeed) or in
> +// `WorkItemPointer::run`, which is what the function pointer in the `work_struct` must be
> +// pointing to.
> unsafe impl<T, const ID: u64> RawWorkItem<ID> for Pin<KBox<T>>
> where
> T: WorkItem<ID, Pointer = Self>,
> @@ -1022,3 +1278,10 @@ pub fn system_bh_highpri() -> &'static Queue {
> // SAFETY: `system_bh_highpri_wq` is a C global, always available.
> unsafe { Queue::from_raw(bindings::system_bh_highpri_wq) }
> }
> +
> +/// Returns the strong count of an [`Arc`] for testing purposes.
> +///
> +/// # Safety
> +///
> +/// This is intended for use in KUnit tests and sample modules ONLY.
> +#[cfg(CONFIG_KUNIT)]
>
Seems like this part failed on the patch separation.
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v4 1/3] rust: helpers: add workqueue helpers
2026-04-07 10:37 ` [PATCH v4 1/3] rust: helpers: add workqueue helpers Aakash Bollineni via B4 Relay
@ 2026-04-07 12:48 ` Gary Guo
0 siblings, 0 replies; 6+ messages in thread
From: Gary Guo @ 2026-04-07 12:48 UTC (permalink / raw)
To: aakash.bollineni, Miguel Ojeda, Alex Gaynor, Wedson Almeida Filho,
Gary Guo, Björn Roy Baron, Alice Ryhl, Tejun Heo,
Lai Jiangshan, Boqun Feng, Benno Lossin, Andreas Hindborg
Cc: rust-for-linux, linux-kernel
On Tue Apr 7, 2026 at 11:37 AM BST, Aakash Bollineni via B4 Relay wrote:
> From: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
>
> Add C-helpers to bridge the Rust workqueue abstraction with the
> kernel's C workqueue macros. These helpers wrap core workqueue
> functions that are either inline or macros in C, making them
> accessible to Rust FFI.
> New wrappers:
> - rust_helper_work_pending(): Wraps work_pending().
> - rust_helper_cancel_work_sync(): Wraps cancel_work_sync().
> - rust_helper_cancel_delayed_work_sync(): Wraps cancel_delayed_work_sync().
These lines are of very low information density. Just state what's wrapped.
> - rust_helper_init_delayed_work(): Performs robust initialization of
> a delayed_work structure, ensuring the correct timer function
> (delayed_work_timer_fn) and lockdep maps are initialized using
> standard kernel macros.
> These helpers are essential for supporting safe cancellation and
> correct DelayedWork lifecycle management in the Rust workqueue API.
>
> Signed-off-by: Aakash Bollineni <aakash.bollineni@multicorewareinc.com>
> ---
> rust/helpers/workqueue.c | 32 ++++++++++++++++++++++++++++++++
> 1 file changed, 32 insertions(+)
>
> diff --git a/rust/helpers/workqueue.c b/rust/helpers/workqueue.c
> index ce1c3a5b2150..85a6c0b9e4d5 100644
> --- a/rust/helpers/workqueue.c
> +++ b/rust/helpers/workqueue.c
> @@ -14,3 +14,35 @@ __rust_helper void rust_helper_init_work_with_key(struct work_struct *work,
> INIT_LIST_HEAD(&work->entry);
> work->func = func;
> }
> +
> +__rust_helper bool rust_helper_work_pending(struct work_struct *work)
> +{
> + return work_pending(work);
> +}
> +
> +__rust_helper bool rust_helper_cancel_work_sync(struct work_struct *work)
> +{
> + return cancel_work_sync(work);
> +}
> +
> +__rust_helper bool rust_helper_cancel_delayed_work_sync(struct delayed_work *dwork)
> +{
> + return cancel_delayed_work_sync(dwork);
> +}
> +
> +__rust_helper void rust_helper_init_delayed_work(struct delayed_work *dwork,
> + work_func_t func,
> + const char *name,
> + struct lock_class_key *key,
> + const char *tname,
> + struct lock_class_key *tkey)
> +{
> + INIT_DELAYED_WORK(dwork, func);
This is just broken. You're initializing things multiple times.
> + lockdep_init_map(&dwork->work.lockdep_map, name, key, 0);
The two lines above can just be
rust_helper_init_work(&dwork->work, func, 0, name, key);
> + timer_init_key(&dwork->timer, dwork->timer.function, TIMER_IRQSAFE, tname, tkey);
The existing impl of INIT_DELAYED_WORK uses `delayed_work_timer_fn`.
> +}
> +
> +__rust_helper void *rust_helper_get_dwork_timer_fn(void)
> +{
> + return (void *)delayed_work_timer_fn;
Why?
> +}
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-04-07 12:48 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-07 10:37 [PATCH v4 0/3] rust: workqueue: add safe cancellation and status methods Aakash Bollineni via B4 Relay
2026-04-07 10:37 ` [PATCH v4 1/3] rust: helpers: add workqueue helpers Aakash Bollineni via B4 Relay
2026-04-07 12:48 ` Gary Guo
2026-04-07 10:37 ` [PATCH v4 2/3] rust: workqueue: add safe cancellation and status methods Aakash Bollineni via B4 Relay
2026-04-07 11:33 ` Onur Özkan
2026-04-07 10:37 ` [PATCH v4 3/3] rust: workqueue: add KUnit and sample stress tests Aakash Bollineni via B4 Relay
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox