Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upWeak pointers #14
Conversation
kvark
self-requested a review
May 8, 2017
vitvakatu
force-pushed the
vitvakatu:weak-pointers
branch
from
2c33185
to
1abf891
May 8, 2017
kvark
requested changes
May 8, 2017
|
Unfortunately, I don't think this is going to work just yet. Supposing you have a weak pointer. It's counter is in Here are 2 suggestions to make it great:
|
|
|
||
| struct Base { | ||
| value: i32, | ||
| childs: Vec<Pointer<Child>>, |
This comment has been minimized.
This comment has been minimized.
| }; | ||
| println!("{} has parent with value {}", child.value, parent_value); | ||
| } | ||
| } |
This comment has been minimized.
This comment has been minimized.
|
|
||
| base.childs.push(child1.clone()); | ||
| base.childs.push(child2.clone()); | ||
| let base_storage = Storage::new(); |
This comment has been minimized.
This comment has been minimized.
kvark
May 8, 2017
Owner
it's great to have cross-storage support, but maybe we should make the example really simple and use a single storage instead? just for clarity - people will use it for learning purposes, not just feature demonstration
| for index in pending.sub_ref_strong.drain(..) { | ||
| s.meta[index].strong -= 1; | ||
| if s.meta[index].strong == 0 { | ||
| s.meta[index].weak -= 1; |
This comment has been minimized.
This comment has been minimized.
kvark
reviewed
May 8, 2017
| sub_ref: Vec::new(), | ||
| add_ref_strong: Vec::new(), | ||
| sub_ref_strong: Vec::new(), | ||
| add_ref_weak: Vec::new(), |
This comment has been minimized.
This comment has been minimized.
kvark
May 8, 2017
Owner
I wonder why do we even need to store the weak counters? They aren't holding anything alive anyway, so we might as well not bother.
kvark
reviewed
May 8, 2017
| sub_ref: Vec<usize>, | ||
| add_ref_strong: Vec<usize>, | ||
| sub_ref_strong: Vec<usize>, | ||
| add_ref_weak: Vec<usize>, |
This comment has been minimized.
This comment has been minimized.
kvark
May 8, 2017
Owner
Do we actually need to store the weak pointer count in the meta? It's not obvious to me, why we'd do that.
vitvakatu
force-pushed the
vitvakatu:weak-pointers
branch
from
1abf891
to
853a4b5
May 8, 2017
This comment has been minimized.
This comment has been minimized.
|
Well, it was a big mistake. I misunderstood the implementation of Weak from standard library. |
kvark
reviewed
May 9, 2017
|
I think we are almost there. A few more notes. |
| impl<T> Pointer<T> { | ||
| /// Creates a new `WeakPointer` to this component. | ||
| pub fn downgrade(&self) -> WeakPointer<T> { | ||
| let epoch = self.target.read().unwrap().epoch[self.index]; |
This comment has been minimized.
This comment has been minimized.
kvark
May 9, 2017
Owner
I'd like to never ever lock the target of a pointer inside the pointer methods. Imagine locking a storage for writing, then trying to downgrade one of its pointers. BAM, deadlock!
Instead, every strong pointer should carry the epoch internally, passed at creation, since the one it target.epoch is guaranteed to be the same for the lifetime of any such pointer.
| match self.target.upgrade().and(self.pending.upgrade()) { | ||
| None => None, | ||
| Some(arc) => { | ||
| let target = self.target.upgrade().unwrap(); |
This comment has been minimized.
This comment has been minimized.
kvark
May 9, 2017
Owner
calling self.target.upgrade() twice - let's just bail out early:
let target = match self.target.upgrade() {
Some(target) => target,
None => return None,
};side note: I wonder if/when the ? syntax would start working with options, it would help here
| None => None, | ||
| Some(arc) => { | ||
| let target = self.target.upgrade().unwrap(); | ||
| if target.read().unwrap().meta[self.index] == 0 || |
This comment has been minimized.
This comment has been minimized.
kvark
May 9, 2017
Owner
Again, I think locking the target here is just asking for a deadlock.
Perhaps, we could have upgrade as a method of Storage instead? (or ReadLock/WriteLock - needs investigation)
This comment has been minimized.
This comment has been minimized.
vitvakatu
May 9, 2017
Author
Collaborator
Well, interesting proposal.
I think upgrade as Storage method makes no sense - you still need lock.
As the most often use case will be accessing Pointer soon after upgradeing it, we can add new methods for Locks: access_weak and access_weak_mut which will return Result<Pointer<T>>. But we still need regular upgrade method.
This comment has been minimized.
This comment has been minimized.
kvark
May 9, 2017
Owner
If possible, I'd like the pointer to not even be able to lock the storage. The fact it's currently possible is purely accidental.
access_weak and access_weak_mut which will return Result<Pointer>.
that would be inconsistent with our currentaccesssemantics
But we still need regular upgrade method.
Do we? That use-case is not obvious to me.
This comment has been minimized.
This comment has been minimized.
vitvakatu
May 9, 2017
Author
Collaborator
Oh, I made a mistake. Not Result<Pointer<T>>, but Result<&T> and Result<&mut T>.
As for upgrade method - I'm not sure it's useful, but in some situations user can need store Pointer produced by Weak
| let target = self.target.upgrade().unwrap(); | ||
| if target.read().unwrap().meta[self.index] == 0 || | ||
| target.read().unwrap().epoch[self.index] != self.epoch { | ||
| return None; |
This comment has been minimized.
This comment has been minimized.
kvark
May 9, 2017
Owner
I think we can introduce richer semantic here at no cost. Make it return a Result, where the error may be either the storage itself is dead, or the element is dead, or the epoch is wrong.
| impl<T> PartialEq for WeakPointer<T> { | ||
| fn eq(&self, other: &WeakPointer<T>) -> bool { | ||
| match self.upgrade().and(other.upgrade()) { | ||
| Some(other_arc) => self.upgrade().unwrap() == other_arc, |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
vitvakatu
force-pushed the
vitvakatu:weak-pointers
branch
from
d3b6dac
to
225b49f
May 10, 2017
vitvakatu
added some commits
May 8, 2017
vitvakatu
force-pushed the
vitvakatu:weak-pointers
branch
from
225b49f
to
cbc2238
May 10, 2017
kvark
requested changes
May 10, 2017
| target: self.storage.clone(), | ||
| pending: self.pending.clone(), | ||
| } | ||
| } | ||
|
|
||
| /// Upgrades the `WeakPointer` to an `Pointer`, if possible. |
This comment has been minimized.
This comment has been minimized.
| /// TODO: control by a cargo feature | ||
| type Epoch = u16; | ||
|
|
||
| pub enum UpgradeErr { |
This comment has been minimized.
This comment has been minimized.
| /// Shared pointer to the pending updates. | ||
| type PendingRef = Arc<Mutex<Pending>>; | ||
| type WeakPendingRef = Weak<Mutex<Pending>>; |
This comment has been minimized.
This comment has been minimized.
kvark
May 10, 2017
Owner
this, as well as WeakStorageRef, are only used once. Perhaps, these typedefs are not really useful here
| index: usize, | ||
| epoch: Epoch, | ||
| target: WeakStorageRef<T>, | ||
| pending: WeakPendingRef, |
This comment has been minimized.
This comment has been minimized.
kvark
May 10, 2017
Owner
I don't think we need to fiddle with the weak pending reference, let's just use PendingRef here
| target: self.storage.clone(), | ||
| pending: self.pending.clone(), | ||
| } | ||
| } | ||
|
|
||
| /// Upgrades the `WeakPointer` to an `Pointer`, if possible. |
This comment has been minimized.
This comment has been minimized.
| target: self.storage.clone(), | ||
| pending: self.pending.clone(), | ||
| } | ||
| } | ||
|
|
||
| /// Upgrades the `WeakPointer` to an `Pointer`, if possible. | ||
| /// Returns `Err` if the strong count has reached zero and the inner value was destroyed. |
This comment has been minimized.
This comment has been minimized.
|
|
||
| /// Upgrades the `WeakPointer` to an `Pointer`, if possible. | ||
| /// Returns `Err` if the strong count has reached zero and the inner value was destroyed. | ||
| pub fn upgrade(&self, ptr: &WeakPointer<T>) -> Result<Pointer<T>, UpgradeErr> { |
This comment has been minimized.
This comment has been minimized.
kvark
May 10, 2017
Owner
we'll need to seriously make this thing nice
first off, when epoch is bumped on deletion, this code no longer needs to check for the refcount == 0
secondly, now that we don't need guard, we don't actually need &self to be ReadLock/WriteLock either!
so we can make upgrade just a method of WeakPointer, as you initially wanted, but without the danger of dead-locking the Storage.
| if pending.epoch[ptr.index] != ptr.epoch { | ||
| return Err(UpgradeErr::WrongEpoch); | ||
| } | ||
| else { |
This comment has been minimized.
This comment has been minimized.
| } | ||
| else { | ||
| pending.add_ref.push(ptr.index); | ||
| return Ok(Pointer { |
This comment has been minimized.
This comment has been minimized.
vitvakatu
added some commits
May 10, 2017
| } | ||
|
|
||
| impl Pending { | ||
| fn prepare_sub_drain(&mut self) -> (&mut Vec<usize>, &Vec<Epoch>) { |
This comment has been minimized.
This comment has been minimized.
kvark
May 10, 2017
Owner
let's return (Drain<usize>, &[Epoch]) pair here and rename the method to drain_sub
| @@ -213,6 +289,14 @@ impl<'a, T> ReadLock<'a, T> { | |||
| &self.guard.data[ptr.index] | |||
| } | |||
|
|
|||
| /// Borrow a weak pointed component for reading. | |||
| pub fn access_weak(&self, pointer: &WeakPointer<T>) -> Result<&T, UpgradeErr> { | |||
This comment has been minimized.
This comment has been minimized.
kvark
May 10, 2017
Owner
I don't think access_weak brings anything on the table, really. It may seem useful, but you can do the same from user space and that would be as efficient, so I'd be in favour of reducing the API surface here
| s.meta[index] -= 1; | ||
| if s.meta[index] == 0 { | ||
| s.free_list.push(index); | ||
| s.free_list.push((index, epoch[index] + 1)); |
This comment has been minimized.
This comment has been minimized.
kvark
May 10, 2017
Owner
I think we actually need to bump the epoch here, as in epoch[index] += 1, unless I'm missing the place where you are already doing that.
vitvakatu
added some commits
May 10, 2017
kvark
approved these changes
May 11, 2017
| storage.read() | ||
| .access_weak(next) | ||
| .ok().map_or("None".into(), |item| item.value.clone()) | ||
| let value = node.next.as_ref().map_or("None".into(), |ref next| { |
This comment has been minimized.
This comment has been minimized.
kvark
May 11, 2017
Owner
this seems quite complicated, I'm going to think on reducing it, but it's not essential to the PR
| @@ -55,8 +55,8 @@ struct Pending { | |||
| } | |||
|
|
|||
| impl Pending { | |||
| fn prepare_sub_drain(&mut self) -> (&mut Vec<usize>, &Vec<Epoch>) { | |||
| (&mut self.sub_ref, &self.epoch) | |||
| fn drain_sub(&mut self) -> (std::vec::Drain<usize>, &mut [Epoch]) { | |||
This comment has been minimized.
This comment has been minimized.
kvark
May 11, 2017
Owner
niiiice. Just one nit: make the use of Drain consistent with the rest of the code, so have use std::vec::Drain at the top (right after use std::sync... line).
kvark
reviewed
May 11, 2017
|
|
||
| for node in storage.read().iter() { | ||
| let value = node.next.as_ref().map_or("None".into(), |ref next| { | ||
| next.upgrade().ok().map_or("None".into(), |ref ptr| { |
This comment has been minimized.
This comment has been minimized.
kvark
May 11, 2017
Owner
can't we just unwrap here, considering that we know the storage and target elements are alive?
This comment has been minimized.
This comment has been minimized.
vitvakatu
May 11, 2017
Author
Collaborator
We can, of course. I'll do it to simplify example, but I think we need to reduce it somehow.
kvark
changed the title
[WIP] Weak pointers
Weak pointers
May 11, 2017
kvark
merged commit 64fa5b2
into
kvark:master
May 11, 2017
1 check passed
This comment has been minimized.
This comment has been minimized.
|
IT HAPPENED! |
vitvakatu commentedMay 8, 2017
•
edited
Weak pointers are used to avoid memory leaking when trying to
dropstructures with cycled linking (e.g.Basehas pointers toChilds and everyChildhas pointer to `Base)Implementation is very rough atm, there're some places where we need to optimize performance or to provide better API.
I'm looking for your feedback.
Fixes #9