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

Rework storage for Accessors #678

Merged
merged 1 commit into from
Apr 8, 2024
Merged

Conversation

pfmooney
Copy link
Collaborator

@pfmooney pfmooney commented Apr 4, 2024

The initial storage (trees/nodes) and lifecycle model for Accessors left open a race between node self-cleanup (removing the associated entry from a tree on drop) and racing node re-parenting (as part of node adoption or orphaning).

This changes how nodes are cleaned up from their containing tree, and simplifies indexing of nodes in said tree to use (opaque) pointers, rather than arbitrary numeric IDs.

Fixes #675

@hawkw hawkw self-requested a review April 4, 2024 20:15
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.

I'm not very familiar with the context around this tree structure and how it's used, so I've tried to focus on the implementation of this change. Overall, this seems good, and most of my comments were style nits. I think it might be worth using NonNull rather than *const for the key type, so that the null case can be represented using an Option.

Regarding the poisoning operation on trees, what does this actually represent and when do we do it? A comment on the method might be nice, IMO, although I understand that there wasn't one previously, so it doesn't necessarily have to be added in this PR...I'll look at the rest of the codebase and try to give a more thorough review

Comment on lines 134 to 137
let mut ent = node.0.lock().unwrap();
ent.tree = Arc::clone(&tree_ref);
ent.resource = self.resource_root.clone();
drop(ent);
Copy link
Member

Choose a reason for hiding this comment

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

silly style nit, feel free to disagree: in general, I prefer to use scopes with curly-braces for stuff like this, rather than explicit drops, it makes the portion of code for which the lock is held a little more visually obvious to readers.

also, i notice that we do it that way later in the file, so the consistency might be nice?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This should be cleaned up, as well as the other instances I found. (I didn't bother going through the tests, though)

Comment on lines 24 to 27
pub struct NodeKey(*const Node<c_void>);
impl NodeKey {
const NULL: Self = Self(std::ptr::null());
}
Copy link
Member

Choose a reason for hiding this comment

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

IMO, it might be nicer to represent this as NonNull<Node<c_void>>, allowing us to use Option<NodeKey> rather than treating NodeKey as nullable (and using NonNull means that Option<NodeKey> should be represented the same as NodeKey(ptr::null()) due to niche optimization).

This way, code that touches a nullable NodeKey (the parent ID) is forced to handle the null case, while the compiler ensures that other NodeKeys that are not the parent key are never null.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I waffled a bit on this while writing it. It makes some accesses a bit more annoying, and further suggests to one examining NodeKey that the pointer it holds might actually be useful for something (beyond a unique ID). The help from the compiler is probably worth it, though.

@pfmooney
Copy link
Collaborator Author

pfmooney commented Apr 5, 2024

Thanks for taking a look.

Regarding the poisoning operation on trees, what does this actually represent and when do we do it?

It's there mainly for the top of the accessor hierarchy to quickly withdraw access to the underlying resource in as prompt a manner as possible. Consumers may be in the middle of an access (with a guard held) which would delay the process somewhat, but absent that, it should allow the resource to be freed quickly. I've added some detail to the Accessor::poison() method.

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.

Looks good to me, thanks for adding the comment on poison!

The initial storage (trees/nodes) and lifecycle model for Accessors left
open a race between node self-cleanup (removing the associated entry
from a tree on drop) and racing node re-parenting (as part of node
adoption or orphaning).

This changes how nodes are cleaned up from their containing tree, and
simplifies indexing of nodes in said tree to use (opaque) pointers,
rather than arbitrary numeric IDs.

Fixes oxidecomputer#675
@pfmooney pfmooney merged commit 10e973e into oxidecomputer:master Apr 8, 2024
10 checks passed
@pfmooney pfmooney deleted the bug-675 branch April 8, 2024 14:29
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.

accessor Node::poison deadlocks with racing drop
2 participants