From: Trevor Gross <tmgross@umich.edu>
To: "Maíra Canal" <mcanal@igalia.com>
Cc: "Asahi Lina" <lina@asahilina.net>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Wedson Almeida Filho" <wedsonaf@gmail.com>,
"Boqun Feng" <boqun.feng@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <benno.lossin@proton.me>,
"Andreas Hindborg" <a.hindborg@samsung.com>,
"Alice Ryhl" <aliceryhl@google.com>,
"Matthew Wilcox" <willy@infradead.org>,
rust-for-linux@vger.kernel.org, kernel-dev@igalia.com
Subject: Re: [PATCH v5] rust: xarray: Add an abstraction for XArray
Date: Fri, 8 Dec 2023 00:21:53 -0500 [thread overview]
Message-ID: <CALNs47svFebQKvBf6fOajGBY-u6wYqiPbS9uRHG9ahKh9wKsFQ@mail.gmail.com> (raw)
In-Reply-To: <20231201195300.1329092-1-mcanal@igalia.com>
On Fri, Dec 1, 2023 at 2:54 PM Maíra Canal <mcanal@igalia.com> wrote:
> +impl<'a, T: ForeignOwnable> Guard<'a, T> {
> + /// Borrow the underlying value wrapped by the `Guard`.
> + ///
> + /// Returns a `T::Borrowed` type for the owned `ForeignOwnable` type.
> + pub fn borrow(&self) -> T::Borrowed<'_> {
> + // SAFETY: The value is owned by the `XArray`, the lifetime it is borrowed for must not
> + // outlive the `XArray` itself, nor the Guard that holds the lock ensuring the value
> + // remains in the `XArray`.
> + unsafe { T::borrow(self.0.as_ptr() as _) }
> + }
> +}
Prefer `.cast::<...>()` or at least `.cast()` wherever possible since
you can't accidentally change mutability (the others mentioned this
area too)
> +// Convenience impl for `ForeignOwnable` types whose `Borrowed`
> +// form implements Deref.
> +impl<'a, T: ForeignOwnable> Deref for Guard<'a, T>
> +where
> + T::Borrowed<'a>: Deref,
> + for<'b> T::Borrowed<'b>: Into<&'b <T::Borrowed<'a> as Deref>::Target>,
You can make these doc comments if there is any value to a user seeing them
> +/// Represents a reserved slot in an `XArray`, which does not yet have a value but has an assigned
> +/// index and may not be allocated by any other user. If the Reservation is dropped without
> +/// being filled, the entry is marked as available again.
> +///
> +/// Users must ensure that reserved slots are not filled by other mechanisms, or otherwise their
> +/// contents may be dropped and replaced (which will print a warning).
> +pub struct Reservation<'a, T: ForeignOwnable>(Pin<&'a XArray<T>>, usize, PhantomData<T>);
Can you clarify who users are here, and give an example of what other
mechanisms would be?
Docs nits:
- s/Represents a/A/ (noun form for object docs)
- Split "If the Reservation..." to a new paragraph so there isn't a long summary
- s/or otherwise/otherwise/
> +impl<'a, T: ForeignOwnable> Reservation<'a, T> {
> + /// Stores a value into the reserved slot.
> + pub fn store(self, value: T) -> Result<usize> {
Add to the docs what this return value is
> + if self.0.replace(self.1, value)?.is_some() {
> + crate::pr_err!("XArray: Reservation stored but the entry already had data!\n");
> + // Consider it a success anyway, not much we can do
> + }
> + let index = self.1;
> + // The reservation is now fulfilled, so do not run our destructor.
> + core::mem::forget(self);
Add a note about when the destructor does get run
> + Ok(index)
> + }
> + /// Replaces an entry with a new value, returning the old value (if any).
> + pub fn replace(self: Pin<&Self>, index: usize, value: T) -> Result<Option<T>> {
> + let new = value.into_foreign();
> + // SAFETY: `new` just came from into_foreign(), and we dismiss this guard if
> + // the xa_store operation succeeds and takes ownership of the pointer.
> + let guard = ScopeGuard::new(|| unsafe {
> + T::from_foreign(new);
> + });
> +
> + // SAFETY: `self.xa` is always valid by the type invariant, and we are storing
> + // a `T::into_foreign()` result which upholds the later invariants.
What type invariant, I don't see one on the type itself :)
> + let guard = ScopeGuard::new(|| unsafe {
> + T::from_foreign(new);
> + });
> +
> + // SAFETY: `self.xa` is always valid by the type invariant, and we are storing
> + // a `T::into_foreign()` result which upholds the later invariants.
> + let old = unsafe {
> + bindings::xa_store(
> + self.xa.get(),
> + index.try_into()?,
> + new as *mut _,
> + bindings::GFP_KERNEL,
> + )
> + };
`as` -> `.cast::<...>()`
> + Ok(if old.is_null() {
> + None
> + } else {
> + // SAFETY: The old value must have been stored by either this function or
> + // `alloc_limits_opt`, both of which ensure non-NULL entries are valid
> + // ForeignOwnable pointers.
> + Some(unsafe { T::from_foreign(old) })
It's obviously done right there, but it doesn't hurt to clarify in the
comment that you checked for null (part of the `from_foreign`
invariant). There are a few cases like this.
Nit: it looks cleaner to `let ret = if ...` .. `Ok(ret)` than it does
to wrap a larger `if` block in `Ok`.
> + /// Looks up and returns a reference to an entry in the array, returning a `Guard` if it
> + /// exists.
> + ///
> + /// This guard blocks all other actions on the `XArray`. Callers are expected to drop the
> + /// `Guard` eagerly to avoid blocking other users, such as by taking a clone of the value.
> + pub fn get(self: Pin<&Self>, index: usize) -> Option<Guard<'_, T>> {
> + // SAFETY: `self.xa` is always valid by the type invariant.
> + unsafe { bindings::xa_lock(self.xa.get()) };
> +
> + // SAFETY: `self.xa` is always valid by the type invariant.
> + let guard = ScopeGuard::new(|| unsafe { bindings::xa_unlock(self.xa.get()) });
> +
> + // SAFETY: `self.xa` is always valid by the type invariant.
> + let p = unsafe { bindings::xa_load(self.xa.get(), index.try_into().ok()?) };
Is it worth any sort of
> + NonNull::new(p as *mut T).map(|p| {
> + guard.dismiss();
> + Guard(p, self)
> + })
> + }
`as` -> `.cast::<...>()`
> + /// Removes and returns an entry, returning it if it existed.
> + pub fn remove(self: Pin<&Self>, index: usize) -> Option<T> {
Docs wording is redundant
> + /// Allocates a new index in the array, optionally storing a new value into it, with
> + /// configurable bounds for the index range to allocate from.
> + ///
> + /// If `value` is `None`, then the index is reserved from further allocation but remains
> + /// free for storing a value into it.
> + fn alloc_limits_opt(self: Pin<&Self>, value: Option<T>, min: u32, max: u32) -> Result<usize> {
Should min and max be usize to be cohesive with indices? And then
return an error if they don't fit. Same questions for below.
> + let new = value.map_or(core::ptr::null(), |a| a.into_foreign());
> + let mut id: u32 = 0;
> +
> + let guard = ScopeGuard::new(|| {
> + if !new.is_null() {
> + // SAFETY: If `new` is not NULL, it came from the `ForeignOwnable` we got
> + // from the caller.
> + unsafe { T::from_foreign(new) };
> + }
> + });
> +
> + // SAFETY: `self.xa` is always valid by the type invariant. If this succeeds, it
> + // takes ownership of the passed `T` (if any). If it fails, we must drop the
> + // `T` again.
> + let ret = unsafe {
> + bindings::xa_alloc(
> + self.xa.get(),
> + &mut id,
> + new as *mut _,
> + bindings::xa_limit { min, max },
> + bindings::GFP_KERNEL,
> + )
> + };
`as` -> `.cast::<...>()`
> + if ret < 0 {
> + Err(Error::from_errno(ret))
> + } else {
> + guard.dismiss();
> + Ok(id as usize)
> + }
> + }
> +impl<T: ForeignOwnable> Drop for XArray<T> {
> + fn drop(&mut self) {
> + let mut index: core::ffi::c_ulong = 0;
> +
> + // SAFETY: `self.xa` is valid by the type invariant, and as we have
> + // the only reference to the `XArray` we can safely iterate its contents
> + // and drop everything.
> + unsafe {
> + let mut entry = bindings::xa_find(
> + self.xa.get(),
> + &mut index,
> + core::ffi::c_ulong::MAX,
> + bindings::BINDINGS_XA_PRESENT,
> + );
> +
> + while !entry.is_null() {
> + T::from_foreign(entry);
> + entry = bindings::xa_find_after(
> + self.xa.get(),
> + &mut index,
> + core::ffi::c_ulong::MAX,
> + bindings::BINDINGS_XA_PRESENT,
> + );
> + }
> + }
This may be better split into two safety blocks (as Benno mentioned)
> + // SAFETY: Locked locks are not safe to drop. Normally we would want to
> + // try_lock()/unlock() here for safety or something similar, but in this
> + // case xa_destroy() is guaranteed to acquire the lock anyway. This will
> + // deadlock if a lock guard was improperly dropped, but that is not UB,
> + // so it's sufficient for soundness purposes.
> + unsafe {
> + bindings::xa_destroy(self.xa.get());
> + }
> + }
> +}
Nit: put the semicolon outside the {...} so it goes on one line.
Looks great so far!
- Trevor
prev parent reply other threads:[~2023-12-08 5:22 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-12-01 19:50 [PATCH v5] rust: xarray: Add an abstraction for XArray Maíra Canal
2023-12-06 11:31 ` Benno Lossin
[not found] ` <CGME20231206141047eucas1p1b6c2b022717a6b3a2e24e37b6a6d5d23@eucas1p1.samsung.com>
2023-12-06 14:10 ` Andreas Hindborg
2023-12-07 19:05 ` Benno Lossin
2023-12-08 5:21 ` Trevor Gross [this message]
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=CALNs47svFebQKvBf6fOajGBY-u6wYqiPbS9uRHG9ahKh9wKsFQ@mail.gmail.com \
--to=tmgross@umich.edu \
--cc=a.hindborg@samsung.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=benno.lossin@proton.me \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=gary@garyguo.net \
--cc=kernel-dev@igalia.com \
--cc=lina@asahilina.net \
--cc=mcanal@igalia.com \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=wedsonaf@gmail.com \
--cc=willy@infradead.org \
/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;
as well as URLs for NNTP newsgroup(s).