Skip to content

Commit acf6c33

Browse files
authored
Merge pull request #139 from mulimoen/bugfix/handles
Revamp object identifier ownership
2 parents b762e22 + 061848c commit acf6c33

File tree

9 files changed

+241
-137
lines changed

9 files changed

+241
-137
lines changed

.github/workflows/ci.yml

+12
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,15 @@ jobs:
232232
run: sudo apt-get install wine64 mingw-w64
233233
- name: Build and test
234234
run: env CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUNNER=wine64 cargo test --features hdf5-sys/static --target x86_64-pc-windows-gnu -- --skip test_compile_fail
235+
addr_san:
236+
name: Address sanitizer
237+
runs-on: ubuntu-latest
238+
steps:
239+
- name: Checkout repository
240+
uses: actions/checkout@v2
241+
with: {submodules: true}
242+
- name: Install Rust
243+
uses: actions-rs/toolchain@v1
244+
with: {toolchain: nightly, profile: minimal, override: true}
245+
- name: Run test with sanitizer
246+
run: env RUSTFLAGS="-Z sanitizer=address" cargo test --features hdf5-sys/static --target x86_64-unknown-linux-gnu --workspace --exclude hdf5-derive

CHANGELOG.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@
5151
- Added support for creating external links on a `Group` with `link_external`.
5252
- Added `Location` methods: `get_info`, `get_info_by_name`, `loc_type`, and `open_by_token`.
5353
- Added `Group` methods: `iter_visit`, `iter_visit_default`, `get_all_of_type`, `datasets`, `groups`, and `named_datatypes`.
54-
54+
- Added `Debug` for `Handle`.
55+
- Added method `try_borrow` on `Handle` for not taking ownership of an HDF5 object.
56+
5557
### Changed
5658

5759
- Required Rust compiler version is now `1.51`.
@@ -78,6 +80,12 @@
7880
- `silence_errors` now work globally and will not be reset on dropping `SilenceErrors`.
7981
- Errors are not expanded when encountered, but only when being used for printing or by
8082
the library user.
83+
- Handles to `hdf5` identifiers are no longer tracked via a registry and is instead handled by stricter semantics of ownership.
84+
- The handle to a `File` will not close all objects in a `File` when dropped, but instead uses a weak file close degree. For the old behaviour see `FileCloseDegree::Strong`.
85+
86+
### Fixed
87+
88+
- A memory leak of handles has been identified and fixed.
8189

8290
## 0.7.1
8391

hdf5-types/src/dyn_value.rs

+34-3
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ impl Display for DynValue<'_> {
702702

703703
pub struct OwnedDynValue {
704704
tp: TypeDescriptor,
705-
buf: Vec<u8>,
705+
buf: Box<[u8]>,
706706
}
707707

708708
impl OwnedDynValue {
@@ -711,7 +711,7 @@ impl OwnedDynValue {
711711
let len = mem::size_of_val(&value);
712712
let buf = unsafe { std::slice::from_raw_parts(ptr, len) };
713713
mem::forget(value);
714-
Self { tp: T::type_descriptor(), buf: buf.to_owned() }
714+
Self { tp: T::type_descriptor(), buf: buf.to_owned().into_boxed_slice() }
715715
}
716716

717717
pub fn get(&self) -> DynValue {
@@ -728,9 +728,40 @@ impl OwnedDynValue {
728728
}
729729

730730
#[doc(hidden)]
731-
pub unsafe fn from_raw(tp: TypeDescriptor, buf: Vec<u8>) -> Self {
731+
pub unsafe fn from_raw(tp: TypeDescriptor, buf: Box<[u8]>) -> Self {
732732
Self { tp, buf }
733733
}
734+
735+
/// Cast to the concrete type
736+
///
737+
/// Will fail if the type-descriptors are not equal
738+
pub fn cast<T: H5Type>(mut self) -> Result<T, Self> {
739+
use mem::MaybeUninit;
740+
if self.tp != T::type_descriptor() {
741+
return Err(self);
742+
}
743+
debug_assert_eq!(self.tp.size(), self.buf.len());
744+
let mut out = MaybeUninit::<T>::uninit();
745+
unsafe {
746+
ptr::copy_nonoverlapping(
747+
self.buf.as_ptr(),
748+
out.as_mut_ptr().cast::<u8>(),
749+
self.buf.len(),
750+
);
751+
}
752+
// For safety we must ensure any nested structures are not live at the same time,
753+
// as this could cause a double free in `dyn_drop`.
754+
// We must deallocate only the top level of Self
755+
756+
// The zero-sized array has a special case to not drop ptr if len is zero,
757+
// so `dyn_drop` of `DynArray` is a nop
758+
self.tp = <[u8; 0]>::type_descriptor();
759+
// We must also swap out the buffer to ensure we can create the `DynValue`
760+
let mut b: Box<[u8]> = Box::new([]);
761+
mem::swap(&mut self.buf, &mut b);
762+
763+
Ok(unsafe { out.assume_init() })
764+
}
734765
}
735766

736767
impl<T: H5Type> From<T> for OwnedDynValue {

src/handle.rs

+33-56
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
use std::collections::HashMap;
2-
use std::sync::Arc;
3-
4-
use lazy_static::lazy_static;
5-
use parking_lot::{Mutex, RwLock};
6-
7-
use hdf5_sys::h5i::{H5I_type_t, H5Idec_ref, H5Iget_type, H5Iinc_ref, H5Iis_valid};
1+
use hdf5_sys::h5i::{H5I_type_t, H5Idec_ref, H5Iget_ref, H5Iget_type, H5Iinc_ref, H5Iis_valid};
82

93
use crate::internal_prelude::*;
104

@@ -20,6 +14,10 @@ pub fn get_id_type(id: hid_t) -> H5I_type_t {
2014
})
2115
}
2216

17+
pub(crate) fn refcount(id: hid_t) -> Result<hsize_t> {
18+
h5call!(H5Iget_ref(id)).map(|x| x as _)
19+
}
20+
2321
pub fn is_valid_id(id: hid_t) -> bool {
2422
h5lock!({
2523
let tp = get_id_type(id);
@@ -31,77 +29,60 @@ pub fn is_valid_user_id(id: hid_t) -> bool {
3129
h5lock!({ H5Iis_valid(id) == 1 })
3230
}
3331

34-
struct Registry {
35-
registry: Mutex<HashMap<hid_t, Arc<RwLock<hid_t>>>>,
36-
}
37-
38-
impl Default for Registry {
39-
fn default() -> Self {
40-
Self::new()
41-
}
42-
}
43-
44-
impl Registry {
45-
pub fn new() -> Self {
46-
Self { registry: Mutex::new(HashMap::new()) }
47-
}
48-
49-
pub fn new_handle(&self, id: hid_t) -> Arc<RwLock<hid_t>> {
50-
let mut registry = self.registry.lock();
51-
let handle = registry.entry(id).or_insert_with(|| Arc::new(RwLock::new(id)));
52-
if *handle.read() != id {
53-
// an id may be left dangling by previous invalidation of a linked handle
54-
*handle = Arc::new(RwLock::new(id));
55-
}
56-
handle.clone()
57-
}
58-
}
59-
32+
/// A handle to an HDF5 object
33+
#[derive(Debug)]
6034
pub struct Handle {
61-
id: Arc<RwLock<hid_t>>,
35+
id: hid_t,
6236
}
6337

6438
impl Handle {
39+
/// Create a handle from object ID, taking ownership of it
6540
pub fn try_new(id: hid_t) -> Result<Self> {
66-
lazy_static! {
67-
static ref REGISTRY: Registry = Registry::new();
68-
}
6941
h5lock!({
7042
if is_valid_user_id(id) {
71-
Ok(Self { id: REGISTRY.new_handle(id) })
43+
Ok(Self { id })
7244
} else {
7345
Err(From::from(format!("Invalid handle id: {}", id)))
7446
}
7547
})
7648
}
7749

78-
pub fn invalid() -> Self {
79-
Self { id: Arc::new(RwLock::new(H5I_INVALID_HID)) }
50+
/// Create a handle from object ID by cloning it
51+
pub fn try_borrow(id: hid_t) -> Result<Self> {
52+
h5lock!({
53+
if is_valid_user_id(id) {
54+
h5call!(H5Iinc_ref(id))?;
55+
Ok(Self { id })
56+
} else {
57+
Err(From::from(format!("Invalid handle id: {}", id)))
58+
}
59+
})
8060
}
8161

82-
pub fn id(&self) -> hid_t {
83-
*self.id.read()
62+
pub const fn invalid() -> Self {
63+
Self { id: H5I_INVALID_HID }
8464
}
8565

86-
pub fn invalidate(&self) {
87-
*self.id.write() = H5I_INVALID_HID;
66+
pub const fn id(&self) -> hid_t {
67+
self.id
8868
}
8969

70+
/// Increment the reference count of the handle
9071
pub fn incref(&self) {
9172
if is_valid_user_id(self.id()) {
9273
h5lock!(H5Iinc_ref(self.id()));
9374
}
9475
}
9576

77+
/// Decrease the reference count of the handle
78+
///
79+
/// Note: This function should only be used if `incref` has been
80+
/// previously called.
9681
pub fn decref(&self) {
9782
h5lock!({
9883
if self.is_valid_id() {
9984
H5Idec_ref(self.id());
10085
}
101-
// must invalidate all linked IDs because the library reuses them internally
102-
if !self.is_valid_user_id() && !self.is_valid_id() {
103-
self.invalidate();
104-
}
10586
});
10687
}
10788

@@ -115,19 +96,15 @@ impl Handle {
11596
is_valid_id(self.id())
11697
}
11798

118-
pub fn decref_full(&self) {
119-
while self.is_valid_user_id() {
120-
self.decref();
121-
}
99+
/// Return the reference count of the object
100+
pub fn refcount(&self) -> u32 {
101+
refcount(self.id).unwrap_or(0) as _
122102
}
123103
}
124104

125105
impl Clone for Handle {
126106
fn clone(&self) -> Self {
127-
h5lock!({
128-
self.incref();
129-
Self::try_new(self.id()).unwrap_or_else(|_| Self::invalid())
130-
})
107+
Self::try_borrow(self.id()).unwrap_or_else(|_| Self::invalid())
131108
}
132109
}
133110

0 commit comments

Comments
 (0)