* [PATCH v2 0/1] rust: workqueue: add cancel_sync support @ 2026-06-12 19:45 Onur Özkan 2026-06-12 19:45 ` [PATCH v2 1/1] " Onur Özkan 0 siblings, 1 reply; 4+ messages in thread From: Onur Özkan @ 2026-06-12 19:45 UTC (permalink / raw) To: rust-for-linux, linux-kernel Cc: ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross, dakr, tamird, daniel.almeida, Onur Özkan Changes since v1: - Created SupportsCancelling trait for from_raw_work function. - Removed from_raw_work from WorkItemPointer. v1: https://lore.kernel.org/all/20260510082211.207450-1-work@onurozkan.dev Onur Özkan (1): rust: workqueue: add cancel_sync support rust/kernel/workqueue.rs | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) -- 2.51.2 ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 1/1] rust: workqueue: add cancel_sync support 2026-06-12 19:45 [PATCH v2 0/1] rust: workqueue: add cancel_sync support Onur Özkan @ 2026-06-12 19:45 ` Onur Özkan 2026-06-15 7:08 ` Alice Ryhl 0 siblings, 1 reply; 4+ messages in thread From: Onur Özkan @ 2026-06-12 19:45 UTC (permalink / raw) To: rust-for-linux, linux-kernel Cc: ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross, dakr, tamird, daniel.almeida, Onur Özkan Drivers can use this during teardown to cancel pending work and wait for running work to finish before dropping related resources. This is not implemented for Pin<KBox<T>> because queuing a boxed work item transfers ownership of the box to the workqueue. There is therefore no separate safe owner that can cancel the boxed work while it is pending. Signed-off-by: Onur Özkan <work@onurozkan.dev> --- rust/kernel/workqueue.rs | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs index 7e253b6f299c..4d61d7a10fae 100644 --- a/rust/kernel/workqueue.rs +++ b/rust/kernel/workqueue.rs @@ -471,6 +471,23 @@ pub trait WorkItem<const ID: u64 = 0> { fn run(this: Self::Pointer); } +/// Work item pointers that support cancellation. +/// +/// # Safety +/// +/// Implementers must ensure that `from_raw_work` rebuilds the exact ownership transferred +/// by a successful [`RawWorkItem::__enqueue`] call. +pub unsafe trait SupportsCancelling<const ID: u64>: WorkItemPointer<ID> + Sized { + /// Rebuild this work item's pointer from its embedded `work_struct`. + /// + /// # Safety + /// + /// The provided `work_struct` pointer must originate from a previous call to + /// [`RawWorkItem::__enqueue`] where the `queue_work_on` closure returned true + /// and the pointer must still be valid. + unsafe fn from_raw_work(ptr: *mut bindings::work_struct) -> Self; +} + /// Links for a work item. /// /// This struct contains a function pointer to the [`run`] function from the [`WorkItemPointer`] @@ -537,6 +554,32 @@ pub unsafe fn raw_get(ptr: *const Self) -> *mut bindings::work_struct { // the compiler does not complain that the `work` field is unused. unsafe { Opaque::cast_into(core::ptr::addr_of!((*ptr).work)) } } + + /// Cancels this work item if it is pending and waits for any running execution to finish. + /// + /// On return, the work item is guaranteed to not be pending or executing as long as there are + /// no racing re-enqueues. + /// + /// # Note + /// + /// Should be called from a sleepable context if the work was last queued on a non-BH + /// workqueue. + #[inline] + pub fn cancel_sync(&self) -> Option<T::Pointer> + where + T: WorkItem<ID>, + T::Pointer: SupportsCancelling<ID>, + { + let ptr = self.work.get(); + // SAFETY: `ptr` is a valid embedded `work_struct`. + if unsafe { bindings::cancel_work_sync(ptr) } { + // SAFETY: A `true` return means the work was pending and got canceled, so the queued + // ownership transfer performed by `__enqueue` is reclaimed here. + Some(unsafe { T::Pointer::from_raw_work(ptr) }) + } else { + None + } + } } /// Declares that a type contains a [`Work<T, ID>`]. @@ -725,6 +768,34 @@ pub unsafe fn raw_as_work(ptr: *const Self) -> *mut Work<T, ID> { // CAST: Work and work_struct have compatible layouts. wrk.cast() } + + /// Cancels this delayed work item if it is pending and waits for any running execution to + /// finish. + /// + /// On return, the work item is guaranteed to not be pending or executing as long as there are + /// no racing re-enqueues. + /// + /// # Note + /// + /// Should be called from a sleepable context if the work was last queued on a non-BH + /// workqueue. + #[inline] + pub fn cancel_sync(&self) -> Option<T::Pointer> + where + T: WorkItem<ID>, + T::Pointer: SupportsCancelling<ID>, + { + let ptr = self.dwork.get(); + + // SAFETY: `ptr` is a valid embedded `delayed_work`. + if unsafe { bindings::cancel_delayed_work_sync(ptr) } { + // SAFETY: A `true` return means the work was pending and got canceled, so the queued + // ownership transfer performed by `__enqueue` is reclaimed here. + Some(unsafe { T::Pointer::from_raw_work(core::ptr::addr_of_mut!((*ptr).work)) }) + } else { + None + } + } } /// Declares that a type contains a [`DelayedWork<T, ID>`]. @@ -871,6 +942,24 @@ unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput } } +// SAFETY: `from_raw_work()` reconstructs exactly the `Arc<T>` ownership previously transferred by +// `Arc::into_raw()` in `RawWorkItem::__enqueue`, using the same `Work<T, ID>` field selected by +// `HasWork<T, ID>`. +unsafe impl<T, const ID: u64> SupportsCancelling<ID> for Arc<T> +where + T: WorkItem<ID, Pointer = Self>, + T: HasWork<T, ID>, +{ + unsafe fn from_raw_work(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: By the safety requirements of `HasDelayedWork`, the `work_struct` returned by methods in // `HasWork` provides a `work_struct` that is the `work` field of a `delayed_work`, and the rest of // the `delayed_work` has the same access rules as its `work` field. @@ -1013,6 +1102,33 @@ unsafe fn __enqueue<F>(self, queue_work_on: F) -> Self::EnqueueOutput } } +// SAFETY: `from_raw_work()` reconstructs exactly the `ARef<T>` ownership previously transferred by +// `ARef::into_raw()` in `RawWorkItem::__enqueue`, using the same `Work<T, ID>` field selected by +// `HasWork<T, ID>`. +unsafe impl<T, const ID: u64> SupportsCancelling<ID> for ARef<T> +where + T: AlwaysRefCounted, + T: WorkItem<ID, Pointer = Self>, + T: HasWork<T, ID>, +{ + unsafe fn from_raw_work(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 + // `ARef::into_raw`. + let ptr = unsafe { T::work_container_of(ptr) }; + + // SAFETY: The safety contract of `work_container_of` ensures that it + // returns a valid non-null pointer. + let ptr = unsafe { NonNull::new_unchecked(ptr) }; + + // SAFETY: This pointer comes from `ARef::into_raw` and we've been given + // back ownership. + unsafe { ARef::from_raw(ptr) } + } +} + // SAFETY: By the safety requirements of `HasDelayedWork`, the `work_struct` returned by methods in // `HasWork` provides a `work_struct` that is the `work` field of a `delayed_work`, and the rest of // the `delayed_work` has the same access rules as its `work` field. -- 2.51.2 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v2 1/1] rust: workqueue: add cancel_sync support 2026-06-12 19:45 ` [PATCH v2 1/1] " Onur Özkan @ 2026-06-15 7:08 ` Alice Ryhl 2026-06-17 12:58 ` Onur Özkan 0 siblings, 1 reply; 4+ messages in thread From: Alice Ryhl @ 2026-06-15 7:08 UTC (permalink / raw) To: Onur Özkan Cc: rust-for-linux, linux-kernel, ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg, tmgross, dakr, tamird, daniel.almeida On Fri, Jun 12, 2026 at 10:45:42PM +0300, Onur Özkan wrote: > Drivers can use this during teardown to cancel pending work and wait for > running work to finish before dropping related resources. > > This is not implemented for Pin<KBox<T>> because queuing a boxed work > item transfers ownership of the box to the workqueue. There is therefore > no separate safe owner that can cancel the boxed work while it is pending. > > Signed-off-by: Onur Özkan <work@onurozkan.dev> Overall looks reasonable to me, but some comments below. > rust/kernel/workqueue.rs | 116 +++++++++++++++++++++++++++++++++++++++ > 1 file changed, 116 insertions(+) > > diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs > index 7e253b6f299c..4d61d7a10fae 100644 > --- a/rust/kernel/workqueue.rs > +++ b/rust/kernel/workqueue.rs > @@ -471,6 +471,23 @@ pub trait WorkItem<const ID: u64 = 0> { > fn run(this: Self::Pointer); > } > > +/// Work item pointers that support cancellation. > +/// > +/// # Safety > +/// > +/// Implementers must ensure that `from_raw_work` rebuilds the exact ownership transferred > +/// by a successful [`RawWorkItem::__enqueue`] call. > +pub unsafe trait SupportsCancelling<const ID: u64>: WorkItemPointer<ID> + Sized { Nit; I think it reads nicer as SupportsCancel. > + /// Rebuild this work item's pointer from its embedded `work_struct`. > + /// > + /// # Safety > + /// > + /// The provided `work_struct` pointer must originate from a previous call to > + /// [`RawWorkItem::__enqueue`] where the `queue_work_on` closure returned true > + /// and the pointer must still be valid. > + unsafe fn from_raw_work(ptr: *mut bindings::work_struct) -> Self; I think you're missing some condition here about ownership having been transferred *out* from the workqueue by cancel_sync() or similar methods. Otherwise this currently says I can call the method even though the workqueue still owns the work item. > + /// # Note > + /// > + /// Should be called from a sleepable context if the work was last queued on a non-BH > + /// workqueue. Nit: I'd either reword "Note" to something more specific to what the note is about, or remove the heading. > + #[inline] > + pub fn cancel_sync(&self) -> Option<T::Pointer> > + where > + T: WorkItem<ID>, > + T::Pointer: SupportsCancelling<ID>, > + { > + let ptr = self.dwork.get(); > + > + // SAFETY: `ptr` is a valid embedded `delayed_work`. > + if unsafe { bindings::cancel_delayed_work_sync(ptr) } { > + // SAFETY: A `true` return means the work was pending and got canceled, so the queued > + // ownership transfer performed by `__enqueue` is reclaimed here. > + Some(unsafe { T::Pointer::from_raw_work(core::ptr::addr_of_mut!((*ptr).work)) }) Nit: The addr_of_mut! macro is no longer required. You can do: &raw mut (*ptr).work Alice ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 1/1] rust: workqueue: add cancel_sync support 2026-06-15 7:08 ` Alice Ryhl @ 2026-06-17 12:58 ` Onur Özkan 0 siblings, 0 replies; 4+ messages in thread From: Onur Özkan @ 2026-06-17 12:58 UTC (permalink / raw) To: Alice Ryhl Cc: rust-for-linux, linux-kernel, ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg, tmgross, dakr, tamird, daniel.almeida On Mon, 15 Jun 2026 07:08:37 +0000 Alice Ryhl <aliceryhl@google.com> wrote: > On Fri, Jun 12, 2026 at 10:45:42PM +0300, Onur Özkan wrote: > > Drivers can use this during teardown to cancel pending work and wait for > > running work to finish before dropping related resources. > > > > This is not implemented for Pin<KBox<T>> because queuing a boxed work > > item transfers ownership of the box to the workqueue. There is therefore > > no separate safe owner that can cancel the boxed work while it is pending. > > > > Signed-off-by: Onur Özkan <work@onurozkan.dev> > > Overall looks reasonable to me, but some comments below. > > > rust/kernel/workqueue.rs | 116 +++++++++++++++++++++++++++++++++++++++ > > 1 file changed, 116 insertions(+) > > > > diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs > > index 7e253b6f299c..4d61d7a10fae 100644 > > --- a/rust/kernel/workqueue.rs > > +++ b/rust/kernel/workqueue.rs > > @@ -471,6 +471,23 @@ pub trait WorkItem<const ID: u64 = 0> { > > fn run(this: Self::Pointer); > > } > > > > +/// Work item pointers that support cancellation. > > +/// > > +/// # Safety > > +/// > > +/// Implementers must ensure that `from_raw_work` rebuilds the exact ownership transferred > > +/// by a successful [`RawWorkItem::__enqueue`] call. > > +pub unsafe trait SupportsCancelling<const ID: u64>: WorkItemPointer<ID> + Sized { > > Nit; I think it reads nicer as SupportsCancel. > > > + /// Rebuild this work item's pointer from its embedded `work_struct`. > > + /// > > + /// # Safety > > + /// > > + /// The provided `work_struct` pointer must originate from a previous call to > > + /// [`RawWorkItem::__enqueue`] where the `queue_work_on` closure returned true > > + /// and the pointer must still be valid. > > + unsafe fn from_raw_work(ptr: *mut bindings::work_struct) -> Self; > > I think you're missing some condition here about ownership having been > transferred *out* from the workqueue by cancel_sync() or similar > methods. Otherwise this currently says I can call the method even though > the workqueue still owns the work item. > Very true, good point! I will improve this doc in the next version with the nits applied. Onur > > + /// # Note > > + /// > > + /// Should be called from a sleepable context if the work was last queued on a non-BH > > + /// workqueue. > > Nit: I'd either reword "Note" to something more specific to what the > note is about, or remove the heading. > > > + #[inline] > > + pub fn cancel_sync(&self) -> Option<T::Pointer> > > + where > > + T: WorkItem<ID>, > > + T::Pointer: SupportsCancelling<ID>, > > + { > > + let ptr = self.dwork.get(); > > + > > + // SAFETY: `ptr` is a valid embedded `delayed_work`. > > + if unsafe { bindings::cancel_delayed_work_sync(ptr) } { > > + // SAFETY: A `true` return means the work was pending and got canceled, so the queued > > + // ownership transfer performed by `__enqueue` is reclaimed here. > > + Some(unsafe { T::Pointer::from_raw_work(core::ptr::addr_of_mut!((*ptr).work)) }) > > Nit: The addr_of_mut! macro is no longer required. You can do: &raw mut (*ptr).work > > Alice ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-06-17 12:59 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-12 19:45 [PATCH v2 0/1] rust: workqueue: add cancel_sync support Onur Özkan 2026-06-12 19:45 ` [PATCH v2 1/1] " Onur Özkan 2026-06-15 7:08 ` Alice Ryhl 2026-06-17 12:58 ` Onur Özkan
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox