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

Added map associated function to MutexGuard that uses new MappedMutexGuard type #2472

Merged
merged 6 commits into from Apr 23, 2021

Conversation

sunjay
Copy link
Contributor

@sunjay sunjay commented May 1, 2020

Part of #2471, extends the work of #2445. Both this PR and #2445 together close #2471.

This PR introduces the MappedMutexGuard type and adds a map associated function to MutexGuard. The work here is largely based on #2445, so thanks @udoprog for the great PR. This was very easy to implement using your work as a reference.

MappedMutexGuard works almost exactly the same as parking_lot::MappedMutexGuard, but adapted to the internals of tokio::sync::Mutex. The MappedMutexGuard type stores a reference to the semaphore from the original Mutex as well as a raw pointer *mut T that is the result of calling the function passed to map. The Mutex does not hold a mutable reference to its data (it uses an UnsafeCell), so I do not believe that it is possible to accidentally run into any aliasing issues.

I added documentation based on the work in #2445 and the documentation in parking_lot/lock_api. There are doctests in both map and try_map that are almost exactly the same as the ones in #2445 for RwLockWriteGuard. I generated the docs with cargo doc and everything looks great. 🎉

@Darksonn Darksonn added A-tokio Area: The main tokio crate C-enhancement Category: A PR with an enhancement or bugfix. M-sync Module: tokio/sync labels May 1, 2020
@Darksonn
Copy link
Contributor

What is the status on this?

@sunjay
Copy link
Contributor Author

sunjay commented May 12, 2020

Thanks @carllerche! I have addressed @hawkw's comment and merged MappedMutexGuard into MutexGuard. Note that this required me to add a manual unsafe impl Send for MutexGuard. I believe that is correct (and #2445 does the same), but someone with a better understanding than me should also verify that.

This is ready for review again. 🎉

@udoprog
Copy link
Contributor

udoprog commented May 12, 2020

Heyo. So in case we want to support a similar API as parking_lot, there is a distinction between the mapped and the regular guard.

For a quick comparison:

It would be nice to make sure that if we want to bring these methods into Tokio's lock API, we don't encounter similar issues, which is a consideration I've raised now in #2445 as well.

@carllerche
Copy link
Member

@udoprog could you elaborate on why the APIs differ between the two?

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

This looks correct to me, and not having a separate type for MappedMutexGuard looks nice. However, if @udoprog has additional context around the rationale behind having a separate type in parking_lot, I'd like to wait to hear about that before we merge this PR in its current state.

tokio/src/sync/mutex.rs Outdated Show resolved Hide resolved
tokio/tests/sync_mutex.rs Outdated Show resolved Hide resolved
tokio/src/sync/mutex.rs Outdated Show resolved Hide resolved
@hawkw
Copy link
Member

hawkw commented May 12, 2020

CI failures appear to be network timeouts while updating dependencies, not test failures. I restarted them.

@udoprog
Copy link
Contributor

udoprog commented May 12, 2020

@udoprog could you elaborate on why the APIs differ between the two?

Yeah. I just haven't had time to do it yet. If someone else beats me to reviewing this part of parking_lot, feel free to beat me to it!

@udoprog
Copy link
Contributor

udoprog commented May 13, 2020

Well. So far the issue seems to be that map provides the ability to project a subfield of MutexGuard<T> (as MutexGuard<U>), and for some reason &mut MutexGuard<U> doesn't seem to provide the necessary guarantee (on a type level?) that the original MutexGuard<T> is uniquely held by the current thread and can be soundly unlocked and mutated by another thread. But this is frankly over my head.

Maybe @Amanieu could provide some insight into what the potential soundness issue is if they have the time?

@Amanieu
Copy link

Amanieu commented May 13, 2020

The main soundness issue is that a MappedMutexGuard can't temporarily give up the lock and the re-acquire it later while still pointing to the mapped value. This is because another thread can acquire the lock in the meantime and modify the locked data such that the map operation is now invalid. For example, if you mapped to a field of an enum, another thread could change that enum to a different variant which would make the original map invalid.

This can happen in several cases, which parking_lot supports only for MutexGuard and not MappedMutexGuard:

  • With Condvar.
  • With MutexGuard::unlocked.
  • With MutexGuard::bump.
  • For RwLocks, upgrading/downgrading a lock.

@udoprog
Copy link
Contributor

udoprog commented May 13, 2020

@Amanieu That makes sense. Thank you!

With that in mind, I think it would be prudent to keep the separate guards so that we can mimic those APIs in the future without a major break.

@carllerche What do you say?

@hawkw
Copy link
Member

hawkw commented May 13, 2020

We should also consider whether or not it's possible and makes sense for Tokio's async mutex to provide all the same methods as parking_lot's mutex.

@carllerche
Copy link
Member

What are the use cases for mapping a mutex guard? I don't think I've ever used it.

@sunjay
Copy link
Contributor Author

sunjay commented May 13, 2020

What are the use cases for mapping a mutex guard? I don't think I've ever used it.

The main use case is for getting a subfield of a value guarded by a mutex. See the documentation of map in the PR for some code that demonstrates that.

@carllerche
Copy link
Member

@sunjay i understand this, but when would one want it guarded by the mutex vs. let v = &mut my_guard.field;.

@Amanieu
Copy link

Amanieu commented May 13, 2020

Mapped guards are generally used when you are returning a guard as part of your public API, but you only want to expose a subset of your internal state to the user.

@hawkw
Copy link
Member

hawkw commented May 13, 2020

Or, conversely, I imagine, when a function takes a MutexGuard<T> but you have a Mutex<StructThatHasATField>?

@Darksonn
Copy link
Contributor

Is this waiting for #2445 to merge, or what is the status?

@sunjay
Copy link
Contributor Author

sunjay commented Jul 27, 2020

This PR doesn't depend on any of the changes from #2445, so it can be merged as soon as a decision is made about whether we want a separate type for the mapped mutex guard. I'm guessing that we will choose not to have one based on #2445 (comment), but I defer that to the team members who know more about all of this than I do. :)

I'll rebase the PR shortly.

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

Overall, this looks good to me, modulo a couple of very minor nits.

I still want to get @carllerche's final opinions on the overall direction of MappedXGuard changes.

tokio/src/sync/mutex.rs Outdated Show resolved Hide resolved
tokio/src/sync/mutex.rs Outdated Show resolved Hide resolved
Copy link
Contributor Author

@sunjay sunjay left a comment

Choose a reason for hiding this comment

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

Apologies for the spam. I'm away from my computer so I'm fixing the build via GitHub suggestions from my phone.

tokio/src/sync/mutex.rs Outdated Show resolved Hide resolved
tokio/src/sync/mutex.rs Outdated Show resolved Hide resolved
tokio/src/sync/mutex.rs Show resolved Hide resolved
tokio/src/sync/mutex.rs Show resolved Hide resolved
@Darksonn
Copy link
Contributor

I would like to remind people that this exists.

@Darksonn
Copy link
Contributor

@sunjay Are you interested in getting this PR finished up?

@sunjay
Copy link
Contributor Author

sunjay commented Mar 20, 2021

Sure, though I believe it is still waiting on an opinion from @carllerche

@Darksonn
Copy link
Contributor

Darksonn commented Mar 20, 2021

I will do a full review later today, and try to get Carl to look as well. (but that probably wont happen until monday I'm guessing)

Copy link
Contributor

@Darksonn Darksonn left a comment

Choose a reason for hiding this comment

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

This seems ok to me. We already have these methods for RwLock, and it follows the same design here.

@Darksonn
Copy link
Contributor

There's a rustfmt failure, and you should probably merge in master to get the up-to-date CI config.

@Darksonn
Copy link
Contributor

Looking more closely at this, I think we should have separate non-mapped and mapped guards, since if we don't, we can never implement an async Condvar due to the soundness issues that @Amanieu mentioned.

@carllerche
Copy link
Member

I am deferring to @Darksonn and @hawkw

@sunjay
Copy link
Contributor Author

sunjay commented Mar 25, 2021

Looking more closely at this, I think we should have separate non-mapped and mapped guards, since if we don't, we can never implement an async Condvar due to the soundness issues that @Amanieu mentioned.

The first commit on my PR has separate mapped guards. Should I go back to that version? Happy to do whatever you think is correct here.

@Darksonn
Copy link
Contributor

Yeah, I think that having separate guard types is safer in the light of being able to add new features in the future.

@Darksonn
Copy link
Contributor

Were you going to go back to the other version?

@sunjay
Copy link
Contributor Author

sunjay commented Apr 12, 2021

Yes, sorry. Haven't had a chance to do so yet.

@sunjay
Copy link
Contributor Author

sunjay commented Apr 19, 2021

@Darksonn sorry for the delay. I've gone back to the version with a separate MappedMutexGuard type. I've also rebased from the master branch and made some minor tweaks based on the feedback I got on that initial commit. This should be ready to review/merge now. Thanks for all your patience and for all the discussion! 😊

tokio/src/sync/mutex.rs Outdated Show resolved Hide resolved
@Darksonn
Copy link
Contributor

The history seems like you added back the commit that merged them, but the diff doesn't have it..? I'm very confused. Force pushes always make the history so confusing.

@sunjay
Copy link
Contributor Author

sunjay commented Apr 22, 2021

Instead of reverting back to the first commit and losing the history, I just added a commit on top of everything else with the separate mapped muted guard type. So all the commits there right now are correct.

Copy link
Contributor

@Darksonn Darksonn left a comment

Choose a reason for hiding this comment

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

Thanks.

@Darksonn Darksonn merged commit 0ba1e3c into tokio-rs:master Apr 23, 2021
@Darksonn Darksonn mentioned this pull request May 14, 2021
Dessix added a commit to microsoft/snocat that referenced this pull request Jul 13, 2021
…exGuard

MappedOwnedMutexGuard had an (as of yet unused) undefined behaviour case
in its implementation of map_mut. This has been superceded by tokio's
tokio-rs/tokio#2472 which used a better
implementation without undefined behaviour (using UnsafeCell instead).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate C-enhancement Category: A PR with an enhancement or bugfix. M-sync Module: tokio/sync
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add map method to MutexGuard, RwLockReadGuard, RwLockWriteGuard
6 participants