rust-for-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: "Onur Özkan" <work@onurozkan.dev>
To: Lyude Paul <lyude@redhat.com>
Cc: rust-for-linux@vger.kernel.org, lossin@kernel.org,
	ojeda@kernel.org, alex.gaynor@gmail.com, boqun.feng@gmail.com,
	gary@garyguo.net, a.hindborg@kernel.org, aliceryhl@google.com,
	tmgross@umich.edu, dakr@kernel.org, peterz@infradead.org,
	mingo@redhat.com, will@kernel.org, longman@redhat.com,
	felipe_life@live.com, daniel@sedlak.dev,
	bjorn3_gh@protonmail.com, daniel.almeida@collabora.com,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH v7 5/6] rust: ww_mutex: implement LockSet
Date: Thu, 27 Nov 2025 13:16:06 +0300	[thread overview]
Message-ID: <20251127131606.58d86291@nimda.home> (raw)
In-Reply-To: <6372d3fa58962bcad9de902ae9184200d2edcb9b.camel@redhat.com>

On Tue, 25 Nov 2025 16:35:21 -0500
Lyude Paul <lyude@redhat.com> wrote:

> On Mon, 2025-11-24 at 18:49 +0300, Onur Özkan wrote:
> > > 
> > > I wonder if there's some way we can get rid of the safety contract
> > > here and verify this at compile time, it would be a shame if every
> > > single lock invocation needed to be unsafe.
> > > 
> > 
> > Yeah :(. We could get rid of them easily by keeping the class that
> > was passed to the constructor functions but that becomes a problem
> > for the from_raw implementations.
> > 
> > I think the best solution would be to expose ww_class type from
> > ww_acquire_ctx and ww_mutex unconditionally (right now it depends on
> > DEBUG_WW_MUTEXES). That way we can just access the class and verify
> > that the mutex and acquire_ctx classes match.
> > 
> > What do you think? I can submit a patch for the C-side
> > implementation. It should be straightforward and shouldn't have any
> > runtime impact.
> 
> I would be fine with this, and think this is definitely the right way
> to go
> 

It would be great to reach a consensus on this (whether we should send a
patch to the C side or instead pass the Class to the from_raw functions
without modifying the C code).

-Onur 

> > 
> > > > +    ///
> > > > +    ///         Ok(())
> > > > +    ///     },
> > > > +    ///     // `on_all_locks_taken` closure
> > > > +    ///     |lock_set| {
> > > > +    ///         // Safely mutate both values while holding the
> > > > locks.
> > > > +    ///         lock_set.with_locked(&mutex1, |v| *v += 1)?;
> > > > +    ///         lock_set.with_locked(&mutex2, |v| *v += 1)?;
> > > > +    ///
> > > > +    ///         Ok(())
> > > > +    ///     },
> > > 
> > > I'm still pretty confident we don't need or want both closures and
> > > can combine them into a single closure. And I am still pretty sure
> > > the only thing that needs to be tracked here is which lock we
> > > failed to acquire in the event of a deadlock.
> > > 
> > > Let me see if I can do a better job of explaining why. Or, if I'm
> > > actually wrong about this - maybe this will help you correct me
> > > and see where I've misunderstood something :).
> > > 
> > > First, let's pretend we've made a couple of changes here:
> > > 
> > >   * We remove `taken: KVec<RawGuard>` and replace it with
> > > `failed: *mut Mutex<…>`
> > >   * lock_set.lock():
> > >      - Now returns a `Guard` that executes `ww_mutex_unlock` in
> > > its destructor
> > >      - If `ww_mutex_lock` fails due to -EDEADLK, this function
> > > stores a pointer to the respective mutex in `lock_set.failed`.
> > >      - Before acquiring a lock, we now check:
> > >         + if lock_set.failed == lock
> > >            * Return a Guard for lock without calling
> > > ww_mutex_lock()
> > >            * lock_set.failed = null_mut();
> > >   * We remove `on_all_locks_taken()`, and rename
> > > `locking_algorithm` to `ww_cb`.
> > >   * If `ww_cb()` returns Err(EDEADLK):
> > >      - if !lock_set.failed.is_null()
> > >         + ww_mutex_lock(lock_set.failed) // Don't store a guard
> > >   * If `ww_cb()` returns Ok(…):
> > >      - if !lock_set.failed.is_null()
> > >        // This could only happen if we hit -EDEADLK but then
> > > `ww_cb` did not // re-acquire `lock_set.failed` on the next
> > > attempt
> > >         + ww_mutex_unlock(lock_set.failed)
> > > 
> > > With all of those changes, we can rewrite `ww_cb` to look like
> > > this:
> > > 
> > > > lock_set| {
> > > // SAFETY: Both `lock_set` and `mutex1` uses the same class.
> > > let g1 = unsafe { lock_set.lock(&mutex1)? };
> > > 
> > > // SAFETY: Both `lock_set` and `mutex2` uses the same class.
> > > let g2 = unsafe { lock_set.lock(&mutex2)? };
> > > 
> > > *g1 += 1;
> > > *g2 += 2;
> > > 
> > > Ok(())
> > > }
> > > 
> > > If we hit -EDEADLK when trying to acquire g2, this is more or less
> > > what would happen:
> > > 
> > >   * let res = ww_cb():
> > >      - let g1 = …; // (we acquire g1 successfully)
> > >      - let g2 = …; // (enter .lock())
> > >         + res = ww_mutex_lock(mutex2);
> > >         + if (res) == EDEADLK
> > >            * lock_set.failed = mutex2;
> > >         + return Err(EDEADLK);
> > >      - return Err(-EDEADLK);
> > >        // Exiting ww_cb(), so rust will drop all variables in this
> > > scope:
> > >         + ww_mutex_unlock(mutex1) // g1's Drop
> > > 
> > >   * // (res == Err(EDEADLK))
> > >     // All locks have been released at this point
> > > 
> > >   * if !lock_set.failed.is_null()
> > >      - ww_mutex_lock(lock_set.failed) // Don't create a guard
> > >     // We've now re-acquired the lock we dead-locked on
> > > 
> > >   * let res = ww_cb():
> > >      - let g1 = …; // (we acquire g1 successfully)
> > >      - let g2 = …; // (enter .lock())
> > >         + if lock_set.failed == lock
> > >            * lock_set.failed = null_mut();
> > >            * return Guard(…); // but don't call ww_mutex_lock(),
> > > it's already locked
> > >      - // We acquired g2 successfully!
> > >      - *g1 += 1;
> > >      - *g2 += 2;
> > > 
> > >   * etc…
> > > 
> > > The only challenge with this is that users need to write their
> > > ww_cb() implementations to be idempotent (so that calling it
> > > multiple times isn't unexpected). But that's already what we do
> > > on the C side, and is kind of what I expected we would want to do
> > > in rust anyhow.
> > > 
> > > Does this make sense, or was there something I made a mistake with
> > > here?
> > 
> > Thanks a lot for analyzing and providing an alternative on this!
> > 
> > However, collapsing everything into a single callback would require
> > the caller to self-police various disciplines like "don't touch gN
> > until gN+1 succeeded", which is exactly the foot-gun we are trying
> > avoid with 2 closures.
> > 
> > Separating acquire and use logics not just simpler API to implement
> > (and provide), but also more effective compare to your example
> > here. With single closure we basically move API responsibility to
> > the users (e.g., do not run this part of the code in the loop, do
> > not access to any data behind any guard if all the locks aren't
> > taken yet, etc.), which is not a good thing to do, especially from
> > the high-level API.
> 
> !!!!!
> OK - now I finally understand what I was missing, it totally slipped
> my mind that we would have this requirement. One thing I'm not sure
> this takes into account though: what about a situation where you
> can't actually know you need to acquire gN+1 until you've acquired gN
> and looked at it? This is at least a fairly common pattern with KMS,
> I'm not sure if it comes up with other parts of the kernel using ww
> mutexes.
> 


  parent reply	other threads:[~2025-11-27 10:23 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-11-01 16:10 [PATCH v7 0/6] rust: add `ww_mutex` support Onur Özkan
2025-11-01 16:10 ` [PATCH v7 1/6] rust: add C wrappers for ww_mutex inline functions Onur Özkan
2025-11-21 19:08   ` Lyude Paul
2025-11-25 15:53   ` Daniel Almeida
2025-11-01 16:10 ` [PATCH v7 2/6] rust: implement `Class` for ww_class support Onur Özkan
2025-11-21 19:15   ` Lyude Paul
2025-11-27  8:57     ` Onur Özkan
2025-11-25 16:12   ` Daniel Almeida
2025-11-01 16:10 ` [PATCH v7 3/6] rust: error: add EDEADLK Onur Özkan
2025-11-21 19:49   ` Lyude Paul
2025-11-25 16:13   ` Daniel Almeida
2025-11-01 16:10 ` [PATCH v7 4/6] rust: ww_mutex: add Mutex, AcquireCtx and MutexGuard Onur Özkan
2025-11-21 21:00   ` Lyude Paul
2025-11-27  9:24     ` Onur Özkan
2025-11-28 11:37     ` Onur Özkan
2025-11-25 18:32   ` Daniel Almeida
2025-11-25 18:59     ` Onur Özkan
2025-11-01 16:10 ` [PATCH v7 5/6] rust: ww_mutex: implement LockSet Onur Özkan
2025-11-21 22:34   ` Lyude Paul
2025-11-24 15:49     ` Onur Özkan
2025-11-25 19:01       ` Daniel Almeida
2025-11-25 20:08         ` Onur Özkan
2025-11-25 21:35       ` Lyude Paul
2025-11-25 21:47         ` Daniel Almeida
2025-11-25 22:14           ` Lyude Paul
2025-11-27 10:16         ` Onur Özkan [this message]
2025-11-27 13:46           ` Daniel Almeida
2025-11-01 16:10 ` [PATCH v7 6/6] rust: add test coverage for ww_mutex implementation Onur Özkan
2025-11-10  5:28 ` [PATCH v7 0/6] rust: add `ww_mutex` support Onur Özkan

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=20251127131606.58d86291@nimda.home \
    --to=work@onurozkan.dev \
    --cc=a.hindborg@kernel.org \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=daniel@sedlak.dev \
    --cc=felipe_life@live.com \
    --cc=gary@garyguo.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=longman@redhat.com \
    --cc=lossin@kernel.org \
    --cc=lyude@redhat.com \
    --cc=mingo@redhat.com \
    --cc=ojeda@kernel.org \
    --cc=peterz@infradead.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tmgross@umich.edu \
    --cc=will@kernel.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).