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

Add RwLockReadGuard::map for transforming guards to contain sub-borrows. #30834

Merged
merged 3 commits into from
Feb 3, 2016

Conversation

reem
Copy link
Contributor

@reem reem commented Jan 12, 2016

This is very useful when the RwLock is synchronizing access to a data
structure and you would like to return or store guards which contain
references to data inside the data structure instead of the data structure
itself.

@rust-highfive
Copy link
Collaborator

r? @alexcrichton

(rust_highfive has picked a reviewer for you, use r? to override)

@reem
Copy link
Contributor Author

reem commented Jan 12, 2016

I've opened this without an accompanying RFC since it's a simple, unstable API. Let me know if adding this as unstable would require an RFC and I can write one up.

@reem
Copy link
Contributor Author

reem commented Jan 12, 2016

For reference, my current use case is a type which contains a RwLock<HashMap<K, V>> and I have an API where I would like to return RwLockReadGuard<V>, which is currently impossible. Without this API, I would have to define a new type and look up the value on every dereference of that type.

@reem reem force-pushed the rwlock-read-guard-map branch 2 times, most recently from 14039a1 to d18ccce Compare January 12, 2016 04:05
@huonw
Copy link
Member

huonw commented Jan 12, 2016

I would think these APIs should match the similar APIs on the RefCell guards, e.g. http://doc.rust-lang.org/std/cell/struct.Ref.html has map and filter_map.

NB. those have the functionality as associated functions, not methods: we try to avoid methods on generic smart-pointers/Deref types because they interfere with method calls to the internal one. The method form would actually be a breaking change, since, for instance, if x: RwLock<Option<...>> then let y = x.read(); let z: Option<()> = y.map(|_| ()) compiles, but this PR would make z try to be assigned an RwLockReadGuard<()>.

Also, MutexGuard and RwLockWriteGuard could probably have them.

(I'm in favour of this.)

@reem
Copy link
Contributor Author

reem commented Jan 12, 2016

Note: I have prepped a temporary fork of RwLock to get this API on stable for my personal use which I'll keep around until these changes become stabilized. (We have a rust in prod usecase that depends on this feature and we would like to compile on stable)

Agreed with all of @huonw's comments, will update.

@sfackler
Copy link
Member

If we're going to add this (which I have no real problem with), we should also stick this on MutexGuard and RwLockWriteGuard in this PR.

@reem
Copy link
Contributor Author

reem commented Jan 12, 2016

Can do.

@alexcrichton alexcrichton added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Jan 12, 2016
@reem
Copy link
Contributor Author

reem commented Jan 12, 2016

Hmm, I am looking at this code again and I think there is a subtle bug - the original guard should be mem::forgeted so as to avoid prematurely releasing the read lock.

@rphmeier
Copy link
Contributor

I think this is a definite improvement to the RwLock API, having found myself wanting something like this in my own code and being forced to approximate with unsafe code. I also agree with @sfackler that this should be added to the guards of various synchronization primitives, although the situation gets slightly hairier with RwLockWriteGuard and MutexGuard, since you have transformations from both &mut T -> &mut U as well as &mut T -> &U.

In particular, I wonder whether it is possible or makes sense to actually downgrade an RwLockWriteGuard to an RwLockReadGuard in the &mut T -> &U case, since it seems like overkill to continue to hold exclusive access if no further mutation is going to occur for the duration of the borrow.

@sfackler
Copy link
Member

@rphmeier pthread rwlocks don't support a downgrade operation, unfortunately, so we wouldn't be able to support a &mut T -> &U method on unix-y platforms.

@rphmeier
Copy link
Contributor

@sfackler Ah, that's unfortunate. At the very least, &mut T -> &U transformations can still be supported, they'll just have to continue holding exclusive access via a RwLockWriteGuard.

@arthurprs
Copy link
Contributor

This is very cool, I wanted something similar while writing floki.

@sfackler
Copy link
Member

I believe we're good to go with this if the same method gets added to MutexGuard, right @alexcrichton?

@alexcrichton
Copy link
Member

Ah yes sorry I meant to get to this sooner. We discussed this in the libs meeting and decided to move forward. We'd like to add this to the following types, though:

  • MutexGuard
  • RwLockReadGuard
  • RwLockWriteGuard

Also feel free to hook this up to this tracking issue: #27746

@alexcrichton alexcrichton removed the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Jan 29, 2016
@reem reem force-pushed the rwlock-read-guard-map branch 2 times, most recently from 876f311 to 4385db1 Compare January 30, 2016 00:15
@reem
Copy link
Contributor Author

reem commented Jan 30, 2016

Updated the PR to include the method as an associated function for RwLockReadGuard, RwLockWriteGuard and MutexGuard, also added specific tests of poisoning behavior for RwLockWriteGuard and MutexGuard, where they are relevant.

@reem
Copy link
Contributor Author

reem commented Jan 30, 2016

I currently added the methods as unstable, but could change the PR to land them as insta-stable if that is desirable.

@sfackler
Copy link
Member

I think we'll land them as unstable but stabilize at the same time as RefCellGuard::map.

@reem
Copy link
Contributor Author

reem commented Jan 30, 2016

Ok, I changed them to use a common feature guard_map.

reason = "recently added, needs RFC for stabilization",
issue = "0")]
pub fn map<U: ?Sized, F>(this: Self, cb: F) -> RwLockReadGuard<'rwlock, U>
where F: FnOnce(&'rwlock T) -> &'rwlock U {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stylistically where clauses like this in the standard library tend to be formatted like:

pub fn map<F>(...) -> T
    where F: ...
{
    // ...
}

This is very useful when the lock is synchronizing access to a data
structure and you would like to return or store guards which contain
references to data inside the data structure instead of the data structure
itself.
where F: FnOnce(&'rwlock mut T) -> &'rwlock mut U {
let new_data = unsafe {
let data: &'rwlock mut T = &mut *this.__data.get();
mem::transmute::<&'rwlock mut U, &'rwlock UnsafeCell<U>>(cb(data))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The strategy above of changing the stored type to just T seemed to work out well, could that be done here to avoid the transmute?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change both RwLockWriteGuard and MutexGuard to store &mut Ts.

@reem
Copy link
Contributor Author

reem commented Jan 30, 2016

Ok, I think this should be ready to land now.

/// ```
#[unstable(feature = "guard_map",
reason = "recently added, needs RFC for stabilization",
issue = "0")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An issue should be opened and these numbers updated before this lands.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're going to fold this into #27746 so that's the issue number to use here.

@reem
Copy link
Contributor Author

reem commented Jan 31, 2016

Added the issue number and fixed the failing doctests, hopefully passes this time.

@@ -231,7 +231,7 @@ impl<T: ?Sized> Mutex<T> {
#[stable(feature = "rust1", since = "1.0.0")]
pub fn try_lock(&self) -> TryLockResult<MutexGuard<T>> {
if unsafe { self.inner.lock.try_lock() } {
Ok(try!(MutexGuard::new(&*self.inner, &self.data)))
Ok(try!(unsafe { MutexGuard::new(&*self.inner, &self.data) }))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this new proliferation of unsafe blocks, can blocks like this just be merged with the one above? (e.g. have one surrounding block)

@reem
Copy link
Contributor Author

reem commented Feb 2, 2016

@alexcrichton updated for style changes both in mutex and rwlock

@@ -178,7 +178,7 @@ impl<T: ?Sized> RwLock<T> {
#[stable(feature = "rust1", since = "1.0.0")]
pub fn read(&self) -> LockResult<RwLockReadGuard<T>> {
unsafe { self.inner.lock.read() }
RwLockReadGuard::new(&*self.inner, &self.data)
unsafe { RwLockReadGuard::new(&*self.inner, &self.data) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah looks like the rwlock here needs similar treatment as a mutex

@reem
Copy link
Contributor Author

reem commented Feb 3, 2016

How would we feel about also adding Ref::filter_map equivalents?

EDIT: Also updated for last nits.

@alexcrichton
Copy link
Member

In theory we're slating Ref::filter_map for deprecation so it's fine to not add those as part of this PR.

@bors: r+ fc875b0

Thanks!

bors added a commit that referenced this pull request Feb 3, 2016
This is very useful when the RwLock is synchronizing access to a data
structure and you would like to return or store guards which contain
references to data inside the data structure instead of the data structure
itself.
@bors
Copy link
Contributor

bors commented Feb 3, 2016

⌛ Testing commit fc875b0 with merge 18c1781...

@bors bors merged commit fc875b0 into rust-lang:master Feb 3, 2016
@reem reem deleted the rwlock-read-guard-map branch February 5, 2016 10:16
@reem
Copy link
Contributor Author

reem commented Feb 5, 2016

@alexcrichton damn, I've discovered a safety hole in MutexGuard::map - it is not safe in combination with Condvar, since the "inner" borrow can be invalidated by another thread taking the lock during a Condvar::wait see: http://is.gd/RM038X.

@reem
Copy link
Contributor Author

reem commented Feb 5, 2016

This is not a problem with the RwLock methods, since the RwLock guards cannot be used with a Condvar.

@reem
Copy link
Contributor Author

reem commented Feb 5, 2016

It would be possible to provide a similar, but safe, API by defining a separate type for "mapped" mutex guards that can't be used with a Condvar.

@alexcrichton
Copy link
Member

Nice catch @reem! Certainly an interesting interaction between the two APIs...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants