Skip to content

Revamp object identifier ownership #139

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

Merged
merged 19 commits into from
Oct 13, 2021
Merged

Conversation

mulimoen
Copy link
Collaborator

This is a new way of handling object identifiers given by us from the hdf5-c library.

The old approach was to store the handles in a registry, a hashmap that kept track of all previous identfiers. Each object addtionally kept track of whether it is invalid or not through an RwLock. This works very well for our tests, but fails when opening and closing many objects (#76, #137).

This PR instead relies on hdf5-c to keep track of the identifiers and validity. The Handle is now a type which owns the underlying object directly, and ownership is a bit stricter.

In practical terms this does not change much. Opened file objects will not be automatically closed when a file is dropped, the user must set the strong close degree on the file to get this behaviour. Setting the strong close degree is dangerous (in the sense it closes the wrong items), but not unsafe, and a warning has been added to the corresponding function.

The idea is to strengthen the relationship between an object handle on the rust side and the hdf5 side. A handle owns the object, and is cloned by incrementing the reference count and handing out a new object. The old approach was to try and close all related handles, acting as a half FileCloseDegree::Strong. The new approach requires the user to explicitely opt into this behaviour, with all the pitfalls this entails...

Soundness difficulties (but not memory safety concern) leading to double free:

  • Creating a new borrowed object from an ID without incref
  • decref without incref
  • The strong file close degree

Should we remove or change some items from our API to remove some of these? We could add a counterpart of from_id (from_borrowed_id), make decref hidden, and use unsafe to annotate these functions.

Fixes #76
Fixes #137

@mulimoen mulimoen requested a review from aldanor February 19, 2021 17:10
@mulimoen
Copy link
Collaborator Author

@aldanor It would be nice if you had a look at this proposal. This is the opposite of keeping the registry, but seems to be easier to reason about regarding ownership of objects.

@mulimoen mulimoen force-pushed the bugfix/handles branch 3 times, most recently from 8bba25d to 9c2b7c8 Compare March 19, 2021 19:44
@aldanor aldanor mentioned this pull request Apr 6, 2021
@aldanor
Copy link
Owner

aldanor commented Apr 7, 2021

This will need to be rebased as #140 is merged. @mulimoen I've read through this, and I guess my main question is: "why does it work?" :) Especially pre-1.10, for 1.8.x. (specifically, in multi-threaded environment)

Also, for the crate user who doesn't want to dabble in source code of this crate, what would be the major usage differences? (in regards to strong close etc)

@mulimoen
Copy link
Collaborator Author

mulimoen commented Apr 7, 2021

It is simply having a single owner for every ID, taking special care to incref if we share the ID, and never decref more than necessary (special care is taken in tests to avoid this). The previous code was buggy as it always decreffed all open handles on file close. There are no multi-threaded concerns as we always operate on the ID using a mutex (serial execution).

For users this means never use strong close (unless dead certain no objects are open), don't decref (Drop is ok), take care during FFI/hdf5 to never give ownership of an ID without an incref.

@mulimoen
Copy link
Collaborator Author

mulimoen commented Apr 7, 2021

Seems DynValue leaks memory, which is picked up by the address sanitizer. I'm debugging this ATM.

@mulimoen
Copy link
Collaborator Author

mulimoen commented Apr 8, 2021

6b66419 changed the inner type of OwnedDynValue to a Box. This was for making it obvious that the type is fixed-size. If rust-lang/rust#63291 is stabilized we might want to use new_uninit_slice.

Comment on lines 744 to 750
let mut out: mem::MaybeUninit<T> = mem::MaybeUninit::uninit();
let buf = value.get_buf();
ptr::copy_nonoverlapping(buf.as_ptr(), (&mut out as *mut T).cast(), buf.len());
mem::forget(value);
out
ptr::copy_nonoverlapping(buf.as_ptr(), out.as_mut_ptr().cast(), buf.len());
// Drop the Box<[u8]>, but not subfields
value.drop_nonrecursive();
// We now have exclusive access to all subfields
out.assume_init()
Copy link
Owner

Choose a reason for hiding this comment

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

Since drop_nonrecursive() is only used here, I wonder if it could be made pub(crate) and this whole block could be pulled out into a method on OwnedDynValue like this:

    #[doc(hidden)]
   pub unsafe fn cast<T: H5Type>(mut self) -> Result<T> {
        ...
   }

where it could also check that T::type_descriptor() == self.tp (or it could return -> T without Result and just panic on mismatch).

And then here it just becomes

let dtype = Datatype::from_type::<T>()?;
self.get_fill_value(&dtype.to_descriptor()?)?.map(|v| unsafe { v.cast() }).transpose()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The new code pushes all the unsafety on hdf5-types, making the code a lot clearer. As zero-sized fixed arrays are not possible to create in hdf5, I've added a small expection to dyn_drop of DynArray to support this code path.

@mulimoen mulimoen force-pushed the bugfix/handles branch 3 times, most recently from 41efb66 to 8bf60ff Compare April 13, 2021 16:16
@mulimoen mulimoen mentioned this pull request Jun 10, 2021
@mulimoen mulimoen mentioned this pull request Jul 17, 2021
@mulimoen mulimoen force-pushed the bugfix/handles branch 2 times, most recently from c040a92 to 003e561 Compare October 6, 2021 19:42
@mulimoen mulimoen force-pushed the bugfix/handles branch 2 times, most recently from f5eca62 to 8fc6995 Compare October 13, 2021 19:07
@aldanor
Copy link
Owner

aldanor commented Oct 13, 2021

Posted the last batch of minor comments ^

I was wondering, do we even really want incref() and decref() methods on Handle? Is there a use case? What if we removed both and replaced them with try_borrow()? (same as in Object - at which point the line between Handle and Object starts to become very thin...)

@mulimoen
Copy link
Collaborator Author

I suppose advanced users of hdf5 want to use underlying functions which may take ownership, requiring incref. decref may be dangerous and could invalidate a Handle, but I guess there are uses?

@aldanor
Copy link
Owner

aldanor commented Oct 13, 2021

I suppose advanced users of hdf5 want to use underlying functions which may take ownership, requiring incref. decref may be dangerous and could invalidate a Handle, but I guess there are uses?

I think the point is, if you use the high-level interface (i.e. hdf5) crate, I can't really imagine a case where you will be increfing or decrefing things (i.e., where try_new, try_borrow, clone and drop will not be sufficient for more advanced cases).

If you really need to dig into some advanced territory (i.e. using hdf5-sys directly), chances are you will probably be using straight hid_t anyway, no?

I'm just trying to think of cases and I can't really find none. You could think of hdf5 crate itself as a fairly "advanced" use of itself.

Well, given that this update is already quite big, let's leave it be, but perhaps remove it in a later update (I'll add a ticket) and maybe remove Handle altogether then which will simplify things. Now that there's no registry and it's a simple wrapper around an int, I'm not sure it's not needed at all.

@aldanor
Copy link
Owner

aldanor commented Oct 13, 2021

One more thing - could you get rid of a manual incref in Handle::clone() and use try_borrow? I think this is the only manual incref in the entire codebase now (aside from tests). And the only manual decref is in impl of Handle::drop.

@mulimoen
Copy link
Collaborator Author

I find Handle is a good abstraction, all ownership is taken care of here and it is the superclass of the ObjectClass.

@aldanor
Copy link
Owner

aldanor commented Oct 13, 2021

I find Handle is a good abstraction, all ownership is taken care of here and it is the superclass of the ObjectClass.

Well, yea, it's basically our hid_t with a few methods and an auto-drop now. Perhaps it should stay but given it's simplicity maybe a few things could be cleaned up. I'll give it a few thoughts.

@aldanor
Copy link
Owner

aldanor commented Oct 13, 2021

Ok well, let's try to merge this (hope it doesn't shoot us in the foot, in some multi-threaded cases or some other weird stuff like that) :)

@mulimoen mulimoen merged commit acf6c33 into aldanor:master Oct 13, 2021
@mulimoen mulimoen deleted the bugfix/handles branch October 13, 2021 20:32
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.

Memory leak in group creation probable memory leak in Dataset::chunk_info (possibly in HDF5)
2 participants