Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking Issue for rwlock_downgrade #128203

Open
6 of 9 tasks
connortsui20 opened this issue Jul 25, 2024 · 5 comments
Open
6 of 9 tasks

Tracking Issue for rwlock_downgrade #128203

connortsui20 opened this issue Jul 25, 2024 · 5 comments
Labels
C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@connortsui20
Copy link

connortsui20 commented Jul 25, 2024

Feature gate: #![feature(rwlock_downgrade)]

This is a tracking issue for a downgrade method for RwLock as discussed in rust-lang/libs-team#392.

The downgrade method on RwLockWriteGuard will transform a write-locked RwLock into a read-locked one.

Public API

impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> {
    pub fn downgrade(s: Self) -> RwLockReadGuard<'a, T> {}
}

Steps / History

Unresolved Questions

  • It is likely that the reader protocol for the futex implementation will need to change. See below.
  • How to go about implementing downgrade for the queue.rs implementation?
  • Does the solid_asp3 platform already have a downgrade method on their RwLock implementation?
  • Does lock poisoning play into this at all?

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html

@connortsui20 connortsui20 added C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Jul 25, 2024
@connortsui20
Copy link
Author

For the futex.rs implementation, it is likely that the protocol for waiting readers will have to change slightly. You can read it in the code changes, but I'll give a quick summary here.

When a call to downgrade occurs on the inner RwLock, it is arguably incorrect for just the single thread that initially held the write-lock to be the only one who is allowed to hold the read-lock. With no changes to the current implementation, this is what would happen, as we don't let readers (either waiting or very recently entered into the protocol) take the read lock if there are any readers or writers waiting (see the is_read_lockable function).

So a solution that is arguably less wrong would be to allow readers who have just been waken up after a call to futex_wait(&self.state) to take the read lock, regardless of if there are other readers or writers waiting. This means that the futex.rs implementation no longer always prioritizes writers over readers, and I'm not sure if this is acceptable.

It is hard to say if this is a good solution, if anyone would like to give their input that would be much appreciated!

@connortsui20
Copy link
Author

@joboet Hi, I saw that you wrote the queue.rs implementation. Do you have an idea on how to go about implementing downgrade for the queue version? I believe it should be something along the lines of finding the tail of the queue and then changing the reader count from 0 to 1, but I am very unfamiliar with the code and I am probably missing something. I also assume that add_backlinks_and_find_tail is probably going to be necessary here?

@joboet
Copy link
Member

joboet commented Jul 26, 2024

Sure! There are two cases to consider:

  1. no threads are waiting (fast path)
  2. threads are waiting

The fast path is pretty easy, you just need to CAS the write-locked state (LOCKED) with the read-locked state for one thread (LOCKED + SINGLE).

For the contended case, I'd first of all change this line:

// If there are threads queued, set the `next` field to a
// pointer to the next node in the queue. Otherwise set it to
// the lock count if the state is read-locked or to zero if it
// is write-locked.
node.next.0 = AtomicPtr::new(state.mask(MASK).cast());

so that the count is one if the lock is write-locked (try cmp::maxing the address). I'll get back to this later.

If the fast path above fails, you need to try to acquire the queue lock so that you can make modifications to the queue (try a CAS that sets the QUEUE_LOCKED bit, fail if the bit is already set). If this is unsuccessful, then return, as there is no way to wake up new threads. Since the last node contains a lock-count of one (with the change above), the read_unlock call will still work correctly.

If you acquired the QUEUE_LOCKED bit, then you need to wake up new threads. I'd modify unlock_queue so that it takes a downgrade argument, if that is true then skip the LOCKED check, do not remove the last node if it is a writer (this and this line need to be skipped) and use LOCKED + SINGLE as unlock value if the last node is a reader.

@connortsui20
Copy link
Author

connortsui20 commented Jul 26, 2024

I think I understand your algorithm, though I was wondering if there might be another way to do it. This algorithm basically is an atomic unlock and place a reader node at the end of the queue, right? Do you think it is possible to implement downgrade without adding a node to the queue at all, and instead just mutating the current state so that we go from 0b0001 to 0b1001 for the fast path and for the waiting path simply incrementing the last queue node from a reader count of 0 to 1?

I'm not sure this is possible, but if it is, it would allow for more opportunities to potentially wake up readers as well.

@connortsui20
Copy link
Author

connortsui20 commented Jul 28, 2024

I see the problem now after playing around with the code and trying to implement what I said above. In the queue implementation, if there are any waiters in the queue (writer or readers), we do not allow readers to take the read lock. So my idea of waking up readers after a call to downgrade is not exactly compatible with the current implementation.

However, that doesn't imply that this is completely impossible with the current implementation. I think what might work is forcing the writer who just downgraded to wake up the next readers in the queue AND remove them from the queue. Maybe the former writer thread can set a bit somewhere (probably stored in the Node struct) to let the awakened readers know that they have been awaken by a call to downgrade, and then there can be an extra case in lock_contended to handle readers who just woke up and observed that bit. However, there might be something I am missing that makes this not able to work.

@joboet Do you have any thoughts about this? (Sorry for all of the pings!)

(EDIT: resolved! see PR)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

2 participants