1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
use std::fmt::{self, Debug};
use crate::internal_prelude::*;
/// Any HDF5 object that can be referenced through an identifier.
#[repr(transparent)]
#[derive(Clone)]
pub struct Object(Handle);
impl ObjectClass for Object {
const NAME: &'static str = "object";
const VALID_TYPES: &'static [H5I_type_t] = &[];
fn from_handle(handle: Handle) -> Self {
Self(handle)
}
fn handle(&self) -> &Handle {
&self.0
}
// TODO: short_repr()
}
impl Debug for Object {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.debug_fmt(f)
}
}
impl Object {
/// Returns the object's identifier.
pub fn id(&self) -> hid_t {
self.0.id()
}
/// Returns reference count if the handle is valid and 0 otherwise.
pub fn refcount(&self) -> u32 {
self.handle().refcount()
}
/// Returns `true` if the object has a valid unlocked identifier (`false` for pre-defined
/// locked identifiers like property list classes).
pub fn is_valid(&self) -> bool {
self.handle().is_valid_user_id()
}
/// Returns type of the object.
pub fn id_type(&self) -> H5I_type_t {
self.handle().id_type()
}
pub(crate) fn try_borrow(&self) -> Result<Handle> {
Handle::try_borrow(self.id())
}
}
macro_rules! impl_downcast {
($func:ident, $tp:ty) => {
impl Object {
#[doc = concat!("Downcast the object into `", stringify!($tp), "` if possible.")]
pub fn $func(&self) -> Result<$tp> {
self.clone().cast()
}
}
};
}
impl_downcast!(as_file, File);
impl_downcast!(as_group, Group);
impl_downcast!(as_dataset, Dataset);
impl_downcast!(as_location, Location);
impl_downcast!(as_attr, Attribute);
impl_downcast!(as_container, Container);
impl_downcast!(as_datatype, Datatype);
impl_downcast!(as_dataspace, Dataspace);
impl_downcast!(as_plist, PropertyList);
#[cfg(test)]
pub mod tests {
use std::ops::Deref;
use hdf5_sys::{h5i::H5I_type_t, h5p::H5Pcreate};
use crate::globals::H5P_FILE_ACCESS;
use crate::internal_prelude::*;
pub struct TestObject(Handle);
impl ObjectClass for TestObject {
const NAME: &'static str = "test object";
const VALID_TYPES: &'static [H5I_type_t] = &[];
fn from_handle(handle: Handle) -> Self {
Self(handle)
}
fn handle(&self) -> &Handle {
&self.0
}
}
impl Deref for TestObject {
type Target = Object;
fn deref(&self) -> &Object {
unsafe { self.transmute() }
}
}
impl TestObject {
fn incref(&self) {
self.0.incref()
}
fn decref(&self) {
self.0.decref()
}
}
#[test]
pub fn test_not_a_valid_user_id() {
assert_err!(TestObject::from_id(H5I_INVALID_HID), "Invalid handle id");
assert_err!(TestObject::from_id(H5P_DEFAULT), "Invalid handle id");
}
#[test]
pub fn test_new_user_id() {
let obj = TestObject::from_id(h5call!(H5Pcreate(*H5P_FILE_ACCESS)).unwrap()).unwrap();
assert!(obj.id() > 0);
assert!(obj.is_valid());
assert!(obj.handle().is_valid_id());
assert_eq!(obj.id_type(), H5I_type_t::H5I_GENPROP_LST);
assert_eq!(obj.refcount(), 1);
obj.incref();
assert_eq!(obj.refcount(), 2);
obj.decref();
assert_eq!(obj.refcount(), 1);
h5lock!({
// Hold the lock while the refcount reaches zero. libhdf5 recycles freed
// identifiers, so without the lock another test thread could reuse this
// id before the second decref and the drop below, which would then close
// an unrelated object.
obj.decref();
assert_eq!(obj.refcount(), 0);
assert!(!obj.is_valid());
assert!(!obj.handle().is_valid_id());
// Decref and drop on an invalid handle are no-ops
obj.decref();
drop(obj);
});
}
#[test]
pub fn test_incref_decref_drop() {
use std::mem::ManuallyDrop;
// Hold the lock across both allocations and the comparison. libhdf5 hands
// out identifiers from a single global pool shared with the other test
// threads, and it recycles freed ones. On the old non-thread-safe 1.8
// builds, allocations and frees interleaved from other threads can make
// these two H5Pcreate calls return the same identifier, which flakily
// trips the assert_ne. Serialising the sequence keeps the ids distinct.
let obj = h5lock!({
let mut obj =
TestObject::from_id(h5call!(H5Pcreate(*H5P_FILE_ACCESS)).unwrap()).unwrap();
let obj_id = obj.id();
obj = TestObject::from_id(h5call!(H5Pcreate(*H5P_FILE_ACCESS)).unwrap()).unwrap();
assert_ne!(obj_id, obj.id());
obj
});
assert!(obj.id() > 0);
assert!(obj.is_valid());
assert!(obj.handle().is_valid_id());
assert_eq!(obj.refcount(), 1);
let obj2 = TestObject::from_id(obj.id()).unwrap();
obj2.incref();
assert_eq!(obj.refcount(), 2);
assert_eq!(obj2.refcount(), 2);
drop(obj2);
assert!(obj.is_valid());
assert_eq!(obj.refcount(), 1);
// obj is already owned, we must ensure we do not call drop on this without
// an incref
let mut obj2 = ManuallyDrop::new(TestObject::from_id(obj.id()).unwrap());
assert_eq!(obj.refcount(), 1);
obj2.incref();
// We can now take, as we have exactly two handles
let obj2 = unsafe { ManuallyDrop::take(&mut obj2) };
h5lock!({
// We must hold a lock here to prevent another thread creating an object
// with the same identifier as the one we just owned. Failing to do this
// might lead to the wrong object being dropped.
obj.decref();
obj.decref();
// We here have to dangling identifiers stored in obj and obj2. As this part
// is locked we know some other object is not going to created with these
// identifiers
assert!(!obj.is_valid());
assert!(!obj2.is_valid());
// By manually dropping we don't close some other unrelated objects.
// Dropping/closing an invalid object is allowed
drop(obj);
drop(obj2);
});
}
}