* [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items
@ 2026-06-15 11:56 Onur Özkan
2026-06-15 15:18 ` Alice Ryhl
2026-06-17 13:10 ` Danilo Krummrich
0 siblings, 2 replies; 6+ messages in thread
From: Onur Özkan @ 2026-06-15 11:56 UTC (permalink / raw)
To: linux-kernel, rust-for-linux
Cc: tj, jiangshanlai, ojeda, boqun, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, dakr, Onur Özkan
Add a workqueue wrapper for work items that are not 'static.
Tyr reset work is queued from a handle that owns a Controller<'bound>
where the work item holds references tied to the lifetime of the bound
device and its mapped IO state. The existing API only accepts 'static
work items which cannot express that relationship.
Introduce ScopedQueue for this case. It owns the underlying workqueue
and ties enqueued work to the queue lifetime so borrowed state cannot
outlive the queue that may still run it.
Construction is unsafe because the queue must not be leaked.
`compile_fail` doc-tests are ignored for now as KUnit doesn't support
that. Enabling those tests as regular code block would raise this error:
ERROR:root:error[E0597]: `data` does not live long enough
--> rust/doctests_kernel_generated.rs:22029:44
|
22027 | let data = ();
| ---- binding `data` declared here
22028 | // SAFETY: Queue is not leaked.
22029 | queue = unsafe { new_queue_with_lt(&data)? };
| ^^^^^ borrowed value does not live long enough
22030 | }
| - `data` dropped here while still borrowed
...
22034 | }
| - borrow might be used here, when `queue` is dropped and runs the `Drop` code for type `ScopedQueue`
|
= note: values in a scope are dropped in the opposite order they are defined
which is exactly the constraint ScopedQueue is meant to enforce.
This series is based on Alice's "Creation of workqueues in Rust" [1]
series.
Link: https://lore.kernel.org/all/20260312-create-workqueue-v4-0-ea39c351c38f@google.com [1]
Signed-off-by: Onur Özkan <work@onurozkan.dev>
---
rust/kernel/workqueue/mod.rs | 3 +
rust/kernel/workqueue/scoped_queue.rs | 179 ++++++++++++++++++++++++++
2 files changed, 182 insertions(+)
create mode 100644 rust/kernel/workqueue/scoped_queue.rs
diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
index e30c21214a81..5e2baa84c143 100644
--- a/rust/kernel/workqueue/mod.rs
+++ b/rust/kernel/workqueue/mod.rs
@@ -212,6 +212,9 @@
mod builder;
pub use self::builder::Builder;
+mod scoped_queue;
+pub use self::scoped_queue::ScopedQueue;
+
/// Creates a [`Work`] initialiser with the given name and a newly-created lock class.
#[macro_export]
macro_rules! new_work {
diff --git a/rust/kernel/workqueue/scoped_queue.rs b/rust/kernel/workqueue/scoped_queue.rs
new file mode 100644
index 000000000000..e0457b521a19
--- /dev/null
+++ b/rust/kernel/workqueue/scoped_queue.rs
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Lifetime-scoped workqueues.
+//!
+//! Provides [`ScopedQueue`] for work items that may borrow data with some
+//! non-`'static` lifetime.
+//!
+//! Unlike [`Queue`] which only accepts `'static` work items, [`ScopedQueue`]
+//! owns its underlying queue and relies on that queue being dropped to drain
+//! pending and running work before borrowed data can go out of scope.
+//!
+//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
+//! ```compile_fail,ignore
+//! use kernel::prelude::*;
+//! use kernel::workqueue::ScopedQueue;
+//!
+//! /// # Safety
+//! ///
+//! /// Returned queue must not be leaked.
+//! unsafe fn new_queue_with_lt<'bound>(_: &'bound ()) -> Result<ScopedQueue<'bound>> {
+//! // SAFETY: Caller guarantees that the returned queue is not leaked.
+//! unsafe { ScopedQueue::new_ordered_with_lt(c"scoped_queue") }
+//! }
+//!
+//! fn queue_outlives_borrowed_data() -> Result {
+//! let queue;
+//!
+//! {
+//! let data = ();
+//! // SAFETY: Queue is not leaked.
+//! queue = unsafe { new_queue_with_lt(&data)? };
+//! }
+//! // Here the `compile_fail` is fulfilled as `queue` would be dropped
+//! // after `data`.
+//! Ok(())
+//! }
+//! ```
+//!
+//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
+//! ```compile_fail,ignore
+//! use kernel::prelude::*;
+//! use kernel::sync::Arc;
+//! use kernel::workqueue::{
+//! impl_has_work,
+//! new_work,
+//! ScopedQueue,
+//! Work,
+//! WorkItem,
+//! };
+//!
+//! #[pin_data]
+//! struct BorrowedWork<'bound> {
+//! data: &'bound (),
+//! #[pin]
+//! work: Work<BorrowedWork<'bound>>,
+//! }
+//!
+//! impl_has_work! {
+//! impl{'bound} HasWork<BorrowedWork<'bound>> for BorrowedWork<'bound> { self.work }
+//! }
+//!
+//! impl<'bound> WorkItem for BorrowedWork<'bound> {
+//! type Pointer = Arc<Self>;
+//!
+//! fn run(_this: Arc<Self>) {}
+//! }
+//!
+//! impl<'bound> BorrowedWork<'bound> {
+//! fn new(data: &'bound ()) -> Result<Arc<Self>> {
+//! Arc::pin_init(
+//! pin_init!(Self {
+//! data,
+//! work <- new_work!("BorrowedWork::work"),
+//! }),
+//! GFP_KERNEL,
+//! )
+//! }
+//! }
+//!
+//! struct Handle<'bound> {
+//! work: Arc<BorrowedWork<'bound>>,
+//! wq: ScopedQueue<'bound>,
+//! }
+//!
+//! impl<'bound> Handle<'bound> {
+//! /// # Safety
+//! ///
+//! /// Returned handle must not be leaked.
+//! unsafe fn new_with_lt(data: &'bound ()) -> Result<Self> {
+//! Ok(Self {
+//! work: BorrowedWork::new(data)?,
+//! // SAFETY: Caller guarantees that the returned handle is not leaked.
+//! wq: unsafe { ScopedQueue::new_ordered_with_lt(c"handle_wq")? },
+//! })
+//! }
+//! }
+//!
+//! fn handle_outlives_borrowed_data() -> Result {
+//! let handle;
+//!
+//! {
+//! let data = ();
+//! // SAFETY: Handle is not leaked.
+//! handle = unsafe { Handle::new_with_lt(&data)? };
+//!
+//! let _ = handle.wq.enqueue(handle.work.clone());
+//! }
+//! // Here the `compile_fail` is fulfilled as `handle` would be dropped
+//! // after `data`.
+//! Ok(())
+//! }
+//! ```
+
+use super::{
+ OwnedQueue,
+ Queue,
+ RawWorkItem, //
+};
+
+use crate::{
+ bindings,
+ ffi,
+ prelude::*, //
+};
+
+use core::marker::PhantomData;
+
+/// An owned workqueue that can enqueue work items borrowing from `'scope`.
+///
+/// A `ScopedQueue` must not outlive data borrowed by its work items.
+pub struct ScopedQueue<'scope> {
+ inner: OwnedQueue,
+ _scope: PhantomData<&'scope mut &'scope ()>,
+}
+
+impl<'scope> ScopedQueue<'scope> {
+ /// Creates an ordered scoped workqueue.
+ ///
+ /// # Safety
+ ///
+ /// The caller must not leak the returned queue or otherwise prevent its
+ /// [`Drop`] implementation from running since dropping the queue drains
+ /// pending and running work that may borrow from `'scope`.
+ pub unsafe fn new_ordered_with_lt(name: &'static CStr) -> Result<Self> {
+ Ok(Self {
+ inner: Queue::new_ordered().build(name)?,
+ _scope: PhantomData,
+ })
+ }
+
+ /// Enqueues a work item on this scoped queue.
+ pub fn enqueue<W, const ID: u64>(&self, work: W) -> W::EnqueueOutput
+ where
+ W: RawWorkItem<ID> + Send + 'scope,
+ {
+ let queue_ptr = self.inner.0.get();
+
+ // SAFETY: `W: 'scope` and dropck keep borrowed data alive until this
+ // queue is dropped. The constructor requires that the queue is not
+ // leaked and dropping `inner` drains pending and running work.
+ unsafe {
+ work.__enqueue(move |work_ptr| {
+ bindings::queue_work_on(
+ bindings::wq_misc_consts_WORK_CPU_UNBOUND as ffi::c_int,
+ queue_ptr,
+ work_ptr,
+ )
+ })
+ }
+ }
+}
+
+impl Drop for ScopedQueue<'_> {
+ fn drop(&mut self) {
+ // This impl makes dropck require `'scope` to outlive `OwnedQueue`.
+ // See: https://doc.rust-lang.org/nomicon/phantom-data.html#generic-parameters-and-drop-checking
+ let _ = &self._scope;
+ }
+}
--
2.51.2
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items
2026-06-15 11:56 [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items Onur Özkan
@ 2026-06-15 15:18 ` Alice Ryhl
2026-06-17 12:49 ` Onur Özkan
2026-06-17 14:08 ` Gary Guo
2026-06-17 13:10 ` Danilo Krummrich
1 sibling, 2 replies; 6+ messages in thread
From: Alice Ryhl @ 2026-06-15 15:18 UTC (permalink / raw)
To: Onur Özkan
Cc: linux-kernel, rust-for-linux, tj, jiangshanlai, ojeda, boqun,
gary, bjorn3_gh, lossin, a.hindborg, tmgross, dakr
On Mon, Jun 15, 2026 at 1:56 PM Onur Özkan <work@onurozkan.dev> wrote:
>
> Add a workqueue wrapper for work items that are not 'static.
>
> Tyr reset work is queued from a handle that owns a Controller<'bound>
> where the work item holds references tied to the lifetime of the bound
> device and its mapped IO state. The existing API only accepts 'static
> work items which cannot express that relationship.
>
> Introduce ScopedQueue for this case. It owns the underlying workqueue
> and ties enqueued work to the queue lifetime so borrowed state cannot
> outlive the queue that may still run it.
>
> Construction is unsafe because the queue must not be leaked.
>
> `compile_fail` doc-tests are ignored for now as KUnit doesn't support
> that. Enabling those tests as regular code block would raise this error:
>
> ERROR:root:error[E0597]: `data` does not live long enough
> --> rust/doctests_kernel_generated.rs:22029:44
> |
> 22027 | let data = ();
> | ---- binding `data` declared here
> 22028 | // SAFETY: Queue is not leaked.
> 22029 | queue = unsafe { new_queue_with_lt(&data)? };
> | ^^^^^ borrowed value does not live long enough
> 22030 | }
> | - `data` dropped here while still borrowed
> ...
> 22034 | }
> | - borrow might be used here, when `queue` is dropped and runs the `Drop` code for type `ScopedQueue`
> |
> = note: values in a scope are dropped in the opposite order they are defined
>
> which is exactly the constraint ScopedQueue is meant to enforce.
>
> This series is based on Alice's "Creation of workqueues in Rust" [1]
> series.
>
> Link: https://lore.kernel.org/all/20260312-create-workqueue-v4-0-ea39c351c38f@google.com [1]
> Signed-off-by: Onur Özkan <work@onurozkan.dev>
> ---
> rust/kernel/workqueue/mod.rs | 3 +
> rust/kernel/workqueue/scoped_queue.rs | 179 ++++++++++++++++++++++++++
> 2 files changed, 182 insertions(+)
> create mode 100644 rust/kernel/workqueue/scoped_queue.rs
>
> diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
> index e30c21214a81..5e2baa84c143 100644
> --- a/rust/kernel/workqueue/mod.rs
> +++ b/rust/kernel/workqueue/mod.rs
> @@ -212,6 +212,9 @@
> mod builder;
> pub use self::builder::Builder;
>
> +mod scoped_queue;
> +pub use self::scoped_queue::ScopedQueue;
> +
> /// Creates a [`Work`] initialiser with the given name and a newly-created lock class.
> #[macro_export]
> macro_rules! new_work {
> diff --git a/rust/kernel/workqueue/scoped_queue.rs b/rust/kernel/workqueue/scoped_queue.rs
> new file mode 100644
> index 000000000000..e0457b521a19
> --- /dev/null
> +++ b/rust/kernel/workqueue/scoped_queue.rs
> @@ -0,0 +1,179 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! Lifetime-scoped workqueues.
> +//!
> +//! Provides [`ScopedQueue`] for work items that may borrow data with some
> +//! non-`'static` lifetime.
> +//!
> +//! Unlike [`Queue`] which only accepts `'static` work items, [`ScopedQueue`]
> +//! owns its underlying queue and relies on that queue being dropped to drain
> +//! pending and running work before borrowed data can go out of scope.
> +//!
> +//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
> +//! ```compile_fail,ignore
> +//! use kernel::prelude::*;
> +//! use kernel::workqueue::ScopedQueue;
> +//!
> +//! /// # Safety
> +//! ///
> +//! /// Returned queue must not be leaked.
> +//! unsafe fn new_queue_with_lt<'bound>(_: &'bound ()) -> Result<ScopedQueue<'bound>> {
> +//! // SAFETY: Caller guarantees that the returned queue is not leaked.
> +//! unsafe { ScopedQueue::new_ordered_with_lt(c"scoped_queue") }
> +//! }
> +//!
> +//! fn queue_outlives_borrowed_data() -> Result {
> +//! let queue;
> +//!
> +//! {
> +//! let data = ();
> +//! // SAFETY: Queue is not leaked.
> +//! queue = unsafe { new_queue_with_lt(&data)? };
> +//! }
> +//! // Here the `compile_fail` is fulfilled as `queue` would be dropped
> +//! // after `data`.
> +//! Ok(())
> +//! }
> +//! ```
> +//!
> +//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
> +//! ```compile_fail,ignore
> +//! use kernel::prelude::*;
> +//! use kernel::sync::Arc;
> +//! use kernel::workqueue::{
> +//! impl_has_work,
> +//! new_work,
> +//! ScopedQueue,
> +//! Work,
> +//! WorkItem,
> +//! };
> +//!
> +//! #[pin_data]
> +//! struct BorrowedWork<'bound> {
> +//! data: &'bound (),
> +//! #[pin]
> +//! work: Work<BorrowedWork<'bound>>,
> +//! }
> +//!
> +//! impl_has_work! {
> +//! impl{'bound} HasWork<BorrowedWork<'bound>> for BorrowedWork<'bound> { self.work }
> +//! }
> +//!
> +//! impl<'bound> WorkItem for BorrowedWork<'bound> {
> +//! type Pointer = Arc<Self>;
> +//!
> +//! fn run(_this: Arc<Self>) {}
> +//! }
> +//!
> +//! impl<'bound> BorrowedWork<'bound> {
> +//! fn new(data: &'bound ()) -> Result<Arc<Self>> {
> +//! Arc::pin_init(
> +//! pin_init!(Self {
> +//! data,
> +//! work <- new_work!("BorrowedWork::work"),
> +//! }),
> +//! GFP_KERNEL,
> +//! )
> +//! }
> +//! }
> +//!
> +//! struct Handle<'bound> {
> +//! work: Arc<BorrowedWork<'bound>>,
> +//! wq: ScopedQueue<'bound>,
> +//! }
> +//!
> +//! impl<'bound> Handle<'bound> {
> +//! /// # Safety
> +//! ///
> +//! /// Returned handle must not be leaked.
> +//! unsafe fn new_with_lt(data: &'bound ()) -> Result<Self> {
> +//! Ok(Self {
> +//! work: BorrowedWork::new(data)?,
> +//! // SAFETY: Caller guarantees that the returned handle is not leaked.
> +//! wq: unsafe { ScopedQueue::new_ordered_with_lt(c"handle_wq")? },
> +//! })
> +//! }
> +//! }
> +//!
> +//! fn handle_outlives_borrowed_data() -> Result {
> +//! let handle;
> +//!
> +//! {
> +//! let data = ();
> +//! // SAFETY: Handle is not leaked.
> +//! handle = unsafe { Handle::new_with_lt(&data)? };
> +//!
> +//! let _ = handle.wq.enqueue(handle.work.clone());
> +//! }
> +//! // Here the `compile_fail` is fulfilled as `handle` would be dropped
> +//! // after `data`.
> +//! Ok(())
> +//! }
> +//! ```
> +
> +use super::{
> + OwnedQueue,
> + Queue,
> + RawWorkItem, //
> +};
> +
> +use crate::{
> + bindings,
> + ffi,
> + prelude::*, //
> +};
> +
> +use core::marker::PhantomData;
> +
> +/// An owned workqueue that can enqueue work items borrowing from `'scope`.
> +///
> +/// A `ScopedQueue` must not outlive data borrowed by its work items.
> +pub struct ScopedQueue<'scope> {
> + inner: OwnedQueue,
> + _scope: PhantomData<&'scope mut &'scope ()>,
I think this can be contra-variant:
_scope: PhantomData<fn(&'scope ())>,
Alice
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items
2026-06-15 15:18 ` Alice Ryhl
@ 2026-06-17 12:49 ` Onur Özkan
2026-06-17 14:08 ` Gary Guo
1 sibling, 0 replies; 6+ messages in thread
From: Onur Özkan @ 2026-06-17 12:49 UTC (permalink / raw)
To: Alice Ryhl
Cc: linux-kernel, rust-for-linux, tj, jiangshanlai, ojeda, boqun,
gary, bjorn3_gh, lossin, a.hindborg, tmgross, dakr
On Mon, 15 Jun 2026 17:18:02 +0200
Alice Ryhl <aliceryhl@google.com> wrote:
> On Mon, Jun 15, 2026 at 1:56 PM Onur Özkan <work@onurozkan.dev> wrote:
> >
> > Add a workqueue wrapper for work items that are not 'static.
> >
> > Tyr reset work is queued from a handle that owns a Controller<'bound>
> > where the work item holds references tied to the lifetime of the bound
> > device and its mapped IO state. The existing API only accepts 'static
> > work items which cannot express that relationship.
> >
> > Introduce ScopedQueue for this case. It owns the underlying workqueue
> > and ties enqueued work to the queue lifetime so borrowed state cannot
> > outlive the queue that may still run it.
> >
> > Construction is unsafe because the queue must not be leaked.
> >
> > `compile_fail` doc-tests are ignored for now as KUnit doesn't support
> > that. Enabling those tests as regular code block would raise this error:
> >
> > ERROR:root:error[E0597]: `data` does not live long enough
> > --> rust/doctests_kernel_generated.rs:22029:44
> > |
> > 22027 | let data = ();
> > | ---- binding `data` declared here
> > 22028 | // SAFETY: Queue is not leaked.
> > 22029 | queue = unsafe { new_queue_with_lt(&data)? };
> > | ^^^^^ borrowed value does not live long enough
> > 22030 | }
> > | - `data` dropped here while still borrowed
> > ...
> > 22034 | }
> > | - borrow might be used here, when `queue` is dropped and runs the `Drop` code for type `ScopedQueue`
> > |
> > = note: values in a scope are dropped in the opposite order they are defined
> >
> > which is exactly the constraint ScopedQueue is meant to enforce.
> >
> > This series is based on Alice's "Creation of workqueues in Rust" [1]
> > series.
> >
> > Link: https://lore.kernel.org/all/20260312-create-workqueue-v4-0-ea39c351c38f@google.com [1]
> > Signed-off-by: Onur Özkan <work@onurozkan.dev>
> > ---
> > rust/kernel/workqueue/mod.rs | 3 +
> > rust/kernel/workqueue/scoped_queue.rs | 179 ++++++++++++++++++++++++++
> > 2 files changed, 182 insertions(+)
> > create mode 100644 rust/kernel/workqueue/scoped_queue.rs
> >
> > diff --git a/rust/kernel/workqueue/mod.rs b/rust/kernel/workqueue/mod.rs
> > index e30c21214a81..5e2baa84c143 100644
> > --- a/rust/kernel/workqueue/mod.rs
> > +++ b/rust/kernel/workqueue/mod.rs
> > @@ -212,6 +212,9 @@
> > mod builder;
> > pub use self::builder::Builder;
> >
> > +mod scoped_queue;
> > +pub use self::scoped_queue::ScopedQueue;
> > +
> > /// Creates a [`Work`] initialiser with the given name and a newly-created lock class.
> > #[macro_export]
> > macro_rules! new_work {
> > diff --git a/rust/kernel/workqueue/scoped_queue.rs b/rust/kernel/workqueue/scoped_queue.rs
> > new file mode 100644
> > index 000000000000..e0457b521a19
> > --- /dev/null
> > +++ b/rust/kernel/workqueue/scoped_queue.rs
> > @@ -0,0 +1,179 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +
> > +//! Lifetime-scoped workqueues.
> > +//!
> > +//! Provides [`ScopedQueue`] for work items that may borrow data with some
> > +//! non-`'static` lifetime.
> > +//!
> > +//! Unlike [`Queue`] which only accepts `'static` work items, [`ScopedQueue`]
> > +//! owns its underlying queue and relies on that queue being dropped to drain
> > +//! pending and running work before borrowed data can go out of scope.
> > +//!
> > +//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
> > +//! ```compile_fail,ignore
> > +//! use kernel::prelude::*;
> > +//! use kernel::workqueue::ScopedQueue;
> > +//!
> > +//! /// # Safety
> > +//! ///
> > +//! /// Returned queue must not be leaked.
> > +//! unsafe fn new_queue_with_lt<'bound>(_: &'bound ()) -> Result<ScopedQueue<'bound>> {
> > +//! // SAFETY: Caller guarantees that the returned queue is not leaked.
> > +//! unsafe { ScopedQueue::new_ordered_with_lt(c"scoped_queue") }
> > +//! }
> > +//!
> > +//! fn queue_outlives_borrowed_data() -> Result {
> > +//! let queue;
> > +//!
> > +//! {
> > +//! let data = ();
> > +//! // SAFETY: Queue is not leaked.
> > +//! queue = unsafe { new_queue_with_lt(&data)? };
> > +//! }
> > +//! // Here the `compile_fail` is fulfilled as `queue` would be dropped
> > +//! // after `data`.
> > +//! Ok(())
> > +//! }
> > +//! ```
> > +//!
> > +//! TODO: Remove `ignore` once KUnit supports `compile_fail` on doc-tests.
> > +//! ```compile_fail,ignore
> > +//! use kernel::prelude::*;
> > +//! use kernel::sync::Arc;
> > +//! use kernel::workqueue::{
> > +//! impl_has_work,
> > +//! new_work,
> > +//! ScopedQueue,
> > +//! Work,
> > +//! WorkItem,
> > +//! };
> > +//!
> > +//! #[pin_data]
> > +//! struct BorrowedWork<'bound> {
> > +//! data: &'bound (),
> > +//! #[pin]
> > +//! work: Work<BorrowedWork<'bound>>,
> > +//! }
> > +//!
> > +//! impl_has_work! {
> > +//! impl{'bound} HasWork<BorrowedWork<'bound>> for BorrowedWork<'bound> { self.work }
> > +//! }
> > +//!
> > +//! impl<'bound> WorkItem for BorrowedWork<'bound> {
> > +//! type Pointer = Arc<Self>;
> > +//!
> > +//! fn run(_this: Arc<Self>) {}
> > +//! }
> > +//!
> > +//! impl<'bound> BorrowedWork<'bound> {
> > +//! fn new(data: &'bound ()) -> Result<Arc<Self>> {
> > +//! Arc::pin_init(
> > +//! pin_init!(Self {
> > +//! data,
> > +//! work <- new_work!("BorrowedWork::work"),
> > +//! }),
> > +//! GFP_KERNEL,
> > +//! )
> > +//! }
> > +//! }
> > +//!
> > +//! struct Handle<'bound> {
> > +//! work: Arc<BorrowedWork<'bound>>,
> > +//! wq: ScopedQueue<'bound>,
> > +//! }
> > +//!
> > +//! impl<'bound> Handle<'bound> {
> > +//! /// # Safety
> > +//! ///
> > +//! /// Returned handle must not be leaked.
> > +//! unsafe fn new_with_lt(data: &'bound ()) -> Result<Self> {
> > +//! Ok(Self {
> > +//! work: BorrowedWork::new(data)?,
> > +//! // SAFETY: Caller guarantees that the returned handle is not leaked.
> > +//! wq: unsafe { ScopedQueue::new_ordered_with_lt(c"handle_wq")? },
> > +//! })
> > +//! }
> > +//! }
> > +//!
> > +//! fn handle_outlives_borrowed_data() -> Result {
> > +//! let handle;
> > +//!
> > +//! {
> > +//! let data = ();
> > +//! // SAFETY: Handle is not leaked.
> > +//! handle = unsafe { Handle::new_with_lt(&data)? };
> > +//!
> > +//! let _ = handle.wq.enqueue(handle.work.clone());
> > +//! }
> > +//! // Here the `compile_fail` is fulfilled as `handle` would be dropped
> > +//! // after `data`.
> > +//! Ok(())
> > +//! }
> > +//! ```
> > +
> > +use super::{
> > + OwnedQueue,
> > + Queue,
> > + RawWorkItem, //
> > +};
> > +
> > +use crate::{
> > + bindings,
> > + ffi,
> > + prelude::*, //
> > +};
> > +
> > +use core::marker::PhantomData;
> > +
> > +/// An owned workqueue that can enqueue work items borrowing from `'scope`.
> > +///
> > +/// A `ScopedQueue` must not outlive data borrowed by its work items.
> > +pub struct ScopedQueue<'scope> {
> > + inner: OwnedQueue,
> > + _scope: PhantomData<&'scope mut &'scope ()>,
>
> I think this can be contra-variant:
>
> _scope: PhantomData<fn(&'scope ())>,
With that, lifetime of the queue is not restricted enough i.e. can live longer
than expected.
For instance, module doc-test should fail to compile when removing
`compile_fail,ignore` but it doesn't with your suggestion.
Thanks,
Onur
>
> Alice
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items
2026-06-15 11:56 [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items Onur Özkan
2026-06-15 15:18 ` Alice Ryhl
@ 2026-06-17 13:10 ` Danilo Krummrich
2026-06-17 13:21 ` Onur Özkan
1 sibling, 1 reply; 6+ messages in thread
From: Danilo Krummrich @ 2026-06-17 13:10 UTC (permalink / raw)
To: Onur Özkan
Cc: linux-kernel, rust-for-linux, tj, jiangshanlai, ojeda, boqun,
gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross
On Mon Jun 15, 2026 at 1:56 PM CEST, Onur Özkan wrote:
> Add a workqueue wrapper for work items that are not 'static.
>
> Tyr reset work is queued from a handle that owns a Controller<'bound>
> where the work item holds references tied to the lifetime of the bound
> device and its mapped IO state. The existing API only accepts 'static
> work items which cannot express that relationship.
>
> Introduce ScopedQueue for this case. It owns the underlying workqueue
> and ties enqueued work to the queue lifetime so borrowed state cannot
> outlive the queue that may still run it.
>
> Construction is unsafe because the queue must not be leaked.
>
> `compile_fail` doc-tests are ignored for now as KUnit doesn't support
> that. Enabling those tests as regular code block would raise this error:
>
> ERROR:root:error[E0597]: `data` does not live long enough
> --> rust/doctests_kernel_generated.rs:22029:44
> |
> 22027 | let data = ();
> | ---- binding `data` declared here
> 22028 | // SAFETY: Queue is not leaked.
> 22029 | queue = unsafe { new_queue_with_lt(&data)? };
> | ^^^^^ borrowed value does not live long enough
> 22030 | }
> | - `data` dropped here while still borrowed
> ...
> 22034 | }
> | - borrow might be used here, when `queue` is dropped and runs the `Drop` code for type `ScopedQueue`
> |
> = note: values in a scope are dropped in the opposite order they are defined
>
> which is exactly the constraint ScopedQueue is meant to enforce.
>
> This series is based on Alice's "Creation of workqueues in Rust" [1]
> series.
>
> Link: https://lore.kernel.org/all/20260312-create-workqueue-v4-0-ea39c351c38f@google.com [1]
> Signed-off-by: Onur Özkan <work@onurozkan.dev>
Suggested-by: Danilo Krummrich <dakr@kernel.org>
> +/// An owned workqueue that can enqueue work items borrowing from `'scope`.
> +///
> +/// A `ScopedQueue` must not outlive data borrowed by its work items.
> +pub struct ScopedQueue<'scope> {
> + inner: OwnedQueue,
> + _scope: PhantomData<&'scope mut &'scope ()>,
> +}
> +
> +impl<'scope> ScopedQueue<'scope> {
> + /// Creates an ordered scoped workqueue.
> + ///
> + /// # Safety
> + ///
> + /// The caller must not leak the returned queue or otherwise prevent its
> + /// [`Drop`] implementation from running since dropping the queue drains
> + /// pending and running work that may borrow from `'scope`.
> + pub unsafe fn new_ordered_with_lt(name: &'static CStr) -> Result<Self> {
I think the with_lt naming is a bit redundant; the ScopedQueue name and it's
lifetime already implies that, so I think ScopedQueue::new_ordered() is
sufficient.
Also note that we only use the with_lt suffix when there's also a 'static
version called new().
> + Ok(Self {
> + inner: Queue::new_ordered().build(name)?,
> + _scope: PhantomData,
> + })
> + }
> +
> + /// Enqueues a work item on this scoped queue.
> + pub fn enqueue<W, const ID: u64>(&self, work: W) -> W::EnqueueOutput
> + where
> + W: RawWorkItem<ID> + Send + 'scope,
> + {
> + let queue_ptr = self.inner.0.get();
> +
> + // SAFETY: `W: 'scope` and dropck keep borrowed data alive until this
> + // queue is dropped. The constructor requires that the queue is not
> + // leaked and dropping `inner` drains pending and running work.
I think it is covered implicitly, but maybe we can spell out more explicitly how
this requirement is justified:
/// If the work item type is annotated with any lifetimes, then you must not call the function
/// pointer after any such lifetime expires. (Never calling the function pointer is okay.)
Also, I think __enqueue() has three distinct safety requirements, so I think it
would be good to address them with separate bullet points.
> + unsafe {
> + work.__enqueue(move |work_ptr| {
> + bindings::queue_work_on(
> + bindings::wq_misc_consts_WORK_CPU_UNBOUND as ffi::c_int,
> + queue_ptr,
> + work_ptr,
> + )
> + })
> + }
> + }
> +}
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items
2026-06-17 13:10 ` Danilo Krummrich
@ 2026-06-17 13:21 ` Onur Özkan
0 siblings, 0 replies; 6+ messages in thread
From: Onur Özkan @ 2026-06-17 13:21 UTC (permalink / raw)
To: Danilo Krummrich
Cc: linux-kernel, rust-for-linux, tj, jiangshanlai, ojeda, boqun,
gary, bjorn3_gh, lossin, a.hindborg, aliceryhl, tmgross
On Wed, 17 Jun 2026 15:10:30 +0200
Danilo Krummrich <dakr@kernel.org> wrote:
> On Mon Jun 15, 2026 at 1:56 PM CEST, Onur Özkan wrote:
> > Add a workqueue wrapper for work items that are not 'static.
> >
> > Tyr reset work is queued from a handle that owns a Controller<'bound>
> > where the work item holds references tied to the lifetime of the bound
> > device and its mapped IO state. The existing API only accepts 'static
> > work items which cannot express that relationship.
> >
> > Introduce ScopedQueue for this case. It owns the underlying workqueue
> > and ties enqueued work to the queue lifetime so borrowed state cannot
> > outlive the queue that may still run it.
> >
> > Construction is unsafe because the queue must not be leaked.
> >
> > `compile_fail` doc-tests are ignored for now as KUnit doesn't support
> > that. Enabling those tests as regular code block would raise this error:
> >
> > ERROR:root:error[E0597]: `data` does not live long enough
> > --> rust/doctests_kernel_generated.rs:22029:44
> > |
> > 22027 | let data = ();
> > | ---- binding `data` declared here
> > 22028 | // SAFETY: Queue is not leaked.
> > 22029 | queue = unsafe { new_queue_with_lt(&data)? };
> > | ^^^^^ borrowed value does not live long enough
> > 22030 | }
> > | - `data` dropped here while still borrowed
> > ...
> > 22034 | }
> > | - borrow might be used here, when `queue` is dropped and runs the `Drop` code for type `ScopedQueue`
> > |
> > = note: values in a scope are dropped in the opposite order they are defined
> >
> > which is exactly the constraint ScopedQueue is meant to enforce.
> >
> > This series is based on Alice's "Creation of workqueues in Rust" [1]
> > series.
> >
> > Link: https://lore.kernel.org/all/20260312-create-workqueue-v4-0-ea39c351c38f@google.com [1]
> > Signed-off-by: Onur Özkan <work@onurozkan.dev>
>
> Suggested-by: Danilo Krummrich <dakr@kernel.org>
>
> > +/// An owned workqueue that can enqueue work items borrowing from `'scope`.
> > +///
> > +/// A `ScopedQueue` must not outlive data borrowed by its work items.
> > +pub struct ScopedQueue<'scope> {
> > + inner: OwnedQueue,
> > + _scope: PhantomData<&'scope mut &'scope ()>,
> > +}
> > +
> > +impl<'scope> ScopedQueue<'scope> {
> > + /// Creates an ordered scoped workqueue.
> > + ///
> > + /// # Safety
> > + ///
> > + /// The caller must not leak the returned queue or otherwise prevent its
> > + /// [`Drop`] implementation from running since dropping the queue drains
> > + /// pending and running work that may borrow from `'scope`.
> > + pub unsafe fn new_ordered_with_lt(name: &'static CStr) -> Result<Self> {
>
> I think the with_lt naming is a bit redundant; the ScopedQueue name and it's
> lifetime already implies that, so I think ScopedQueue::new_ordered() is
> sufficient.
>
> Also note that we only use the with_lt suffix when there's also a 'static
> version called new().
Perhaps new() alone would be better? ScopedQueue can only work with ordered queue
anyway.
Onur
>
> > + Ok(Self {
> > + inner: Queue::new_ordered().build(name)?,
> > + _scope: PhantomData,
> > + })
> > + }
> > +
> > + /// Enqueues a work item on this scoped queue.
> > + pub fn enqueue<W, const ID: u64>(&self, work: W) -> W::EnqueueOutput
> > + where
> > + W: RawWorkItem<ID> + Send + 'scope,
> > + {
> > + let queue_ptr = self.inner.0.get();
> > +
> > + // SAFETY: `W: 'scope` and dropck keep borrowed data alive until this
> > + // queue is dropped. The constructor requires that the queue is not
> > + // leaked and dropping `inner` drains pending and running work.
>
> I think it is covered implicitly, but maybe we can spell out more explicitly how
> this requirement is justified:
>
> /// If the work item type is annotated with any lifetimes, then you must not call the function
> /// pointer after any such lifetime expires. (Never calling the function pointer is okay.)
>
> Also, I think __enqueue() has three distinct safety requirements, so I think it
> would be good to address them with separate bullet points.
>
> > + unsafe {
> > + work.__enqueue(move |work_ptr| {
> > + bindings::queue_work_on(
> > + bindings::wq_misc_consts_WORK_CPU_UNBOUND as ffi::c_int,
> > + queue_ptr,
> > + work_ptr,
> > + )
> > + })
> > + }
> > + }
> > +}
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items
2026-06-15 15:18 ` Alice Ryhl
2026-06-17 12:49 ` Onur Özkan
@ 2026-06-17 14:08 ` Gary Guo
1 sibling, 0 replies; 6+ messages in thread
From: Gary Guo @ 2026-06-17 14:08 UTC (permalink / raw)
To: Alice Ryhl, Onur Özkan
Cc: linux-kernel, rust-for-linux, tj, jiangshanlai, ojeda, boqun,
gary, bjorn3_gh, lossin, a.hindborg, tmgross, dakr
On Mon Jun 15, 2026 at 4:18 PM BST, Alice Ryhl wrote:
>> +/// An owned workqueue that can enqueue work items borrowing from `'scope`.
>> +///
>> +/// A `ScopedQueue` must not outlive data borrowed by its work items.
>> +pub struct ScopedQueue<'scope> {
>> + inner: OwnedQueue,
>> + _scope: PhantomData<&'scope mut &'scope ()>,
>
> I think this can be contra-variant:
>
> _scope: PhantomData<fn(&'scope ())>,
The function signature is contravariant, but the captured data is covariant, so
they combine to invariance.
Best,
Gary
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-17 14:08 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-15 11:56 [PATCH v1] rust: workqueue: add ScopedQueue for lifetime bound items Onur Özkan
2026-06-15 15:18 ` Alice Ryhl
2026-06-17 12:49 ` Onur Özkan
2026-06-17 14:08 ` Gary Guo
2026-06-17 13:10 ` Danilo Krummrich
2026-06-17 13:21 ` Onur Özkan
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.