public inbox for rust-for-linux@vger.kernel.org
 help / color / mirror / Atom feed
From: "Onur Özkan" <work@onurozkan.dev>
To: Aakash Bollineni via B4 Relay
	<devnull+aakash.bollineni.multicorewareinc.com@kernel.org>
Cc: "Miguel Ojeda" <ojeda@kernel.org>,
	"Alex Gaynor" <alex.gaynor@gmail.com>,
	"Wedson Almeida Filho" <wedsonaf@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn.roy.baron@gmail.com>,
	"Alice Ryhl" <aliceryhl@google.com>, "Tejun Heo" <tj@kernel.org>,
	"Lai Jiangshan" <jiangshanlai@gmail.com>,
	"Boqun Feng" <boqun@kernel.org>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
	"Aakash Bollineni" <aakash.bollineni@multicorewareinc.com>
Subject: Re: [PATCH v4 2/3] rust: workqueue: add safe cancellation and status methods
Date: Tue,  7 Apr 2026 14:33:44 +0300	[thread overview]
Message-ID: <20260407113348.119125-1-work@onurozkan.dev> (raw)
In-Reply-To: <20260407-workqueue-v3-final-v4-2-c27da7e5f175@multicorewareinc.com>

> 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
> 
> 

  reply	other threads:[~2026-04-07 11:34 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
2026-04-07 10:37 ` [PATCH v4 3/3] rust: workqueue: add KUnit and sample stress tests Aakash Bollineni via B4 Relay

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260407113348.119125-1-work@onurozkan.dev \
    --to=work@onurozkan.dev \
    --cc=a.hindborg@kernel.org \
    --cc=aakash.bollineni@multicorewareinc.com \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn.roy.baron@gmail.com \
    --cc=boqun@kernel.org \
    --cc=devnull+aakash.bollineni.multicorewareinc.com@kernel.org \
    --cc=gary@garyguo.net \
    --cc=jiangshanlai@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tj@kernel.org \
    --cc=wedsonaf@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox