Skip to content

Commit f4ae75d

Browse files
committed
disable library cache
Signed-off-by: Esteve Fernandez <[email protected]>
1 parent 3779e79 commit f4ae75d

File tree

1 file changed

+78
-95
lines changed

1 file changed

+78
-95
lines changed

rclrs/src/dynamic_message.rs

Lines changed: 78 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
//! The central type of this module is [`DynamicMessage`].
77
88
use std::{
9-
collections::HashMap,
109
fmt::{self, Display},
1110
ops::Deref,
1211
path::PathBuf,
13-
sync::{Arc, LazyLock, Mutex},
12+
sync::Arc,
1413
};
1514

1615
use rosidl_runtime_rs::RmwMessage;
@@ -30,49 +29,24 @@ pub use error::*;
3029
pub use field_access::*;
3130
pub use message_structure::*;
3231

33-
/// A struct to cache loaded shared libraries for dynamic messages, indexing them by name.
34-
#[derive(Default)]
35-
pub struct DynamicMessageLibraryCache(HashMap<String, Arc<libloading::Library>>);
36-
37-
impl DynamicMessageLibraryCache {
38-
/// Get a reference to the library for the specific `package_name`. Attempt to load and store
39-
/// it in the cache if it is not currently loaded.
40-
pub fn get_or_load(
41-
&mut self,
42-
package_name: &str,
43-
) -> Result<Arc<libloading::Library>, DynamicMessageError> {
44-
use std::collections::hash_map::Entry;
45-
let lib = match self.0.entry(package_name.into()) {
46-
Entry::Occupied(entry) => entry.get().clone(),
47-
Entry::Vacant(entry) => entry
48-
.insert(get_type_support_library(
49-
package_name,
50-
INTROSPECTION_TYPE_SUPPORT_IDENTIFIER,
51-
)?)
52-
.clone(),
53-
};
54-
Ok(lib)
55-
}
56-
57-
/// Remove a package_name from the cache. Return `true` if it was removed, `false` otherwise
58-
///
59-
/// This function can be used to reduce memory footprint if the message library is not used
60-
/// anymore.
61-
/// Note that since shared libraries are wrapped by an `Arc` this does _not_ unload the library
62-
/// until all other structures that reference it ([`DynamicMessage`] or
63-
/// [`DynamicMessageMetadata`]) are also dropped.
64-
pub fn unload(&mut self, package_name: &str) -> bool {
65-
self.0.remove(package_name).is_some()
66-
}
67-
}
68-
69-
/// A global cache for loaded message packages.
32+
/// Factory for constructing messages in a certain package dynamically.
7033
///
71-
/// Since creating a new dynamic message requires loading a shared library from the file system, by
72-
/// caching loaded libraries we can reduce the overhead for preloaded libraries to
73-
/// just a [`Arc::clone`].
74-
pub static DYNAMIC_MESSAGE_PACKAGE_CACHE: LazyLock<Mutex<DynamicMessageLibraryCache>> =
75-
LazyLock::new(|| Default::default());
34+
/// This is the result of loading the introspection type support library (which is a per-package
35+
/// operation), whereas [`DynamicMessageMetadata`] is the result of loading the data related to
36+
/// the message from the library.
37+
//
38+
// Theoretically it could be beneficial to make this struct public so users can "cache"
39+
// the library loading, but unless a compelling use case comes up, I don't think it's
40+
// worth the complexity.
41+
//
42+
// Under the hood, this is an `Arc<libloading::Library>`, so if this struct and the
43+
// [`DynamicMessageMetadata`] and [`DynamicMessage`] structs created from it are dropped,
44+
// the library will be unloaded. This shared ownership ensures that the type_support_ptr
45+
// is always valid.
46+
struct DynamicMessagePackage {
47+
introspection_type_support_library: Arc<libloading::Library>,
48+
package: String,
49+
}
7650

7751
/// A parsed/validated message type name of the form `<package_name>/msg/<type_name>`.
7852
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -116,6 +90,8 @@ pub struct DynamicMessage {
11690
needs_fini: bool,
11791
}
11892

93+
// ========================= impl for DynamicMessagePackage =========================
94+
11995
/// This is an analogue of rclcpp::get_typesupport_library.
12096
fn get_type_support_library(
12197
package_name: &str,
@@ -184,6 +160,62 @@ unsafe fn get_type_support_handle(
184160

185161
const INTROSPECTION_TYPE_SUPPORT_IDENTIFIER: &str = "rosidl_typesupport_introspection_c";
186162

163+
impl DynamicMessagePackage {
164+
/// Creates a new `DynamicMessagePackage`.
165+
///
166+
/// This dynamically loads a type support library for the specified package.
167+
pub fn new(package_name: impl Into<String>) -> Result<Self, DynamicMessageError> {
168+
let package_name = package_name.into();
169+
Ok(Self {
170+
introspection_type_support_library: get_type_support_library(
171+
&package_name,
172+
INTROSPECTION_TYPE_SUPPORT_IDENTIFIER,
173+
)?,
174+
package: package_name,
175+
})
176+
}
177+
178+
pub(crate) fn message_metadata(
179+
&self,
180+
type_name: impl Into<String>,
181+
) -> Result<DynamicMessageMetadata, DynamicMessageError> {
182+
let message_type = MessageTypeName {
183+
package_name: self.package.clone(),
184+
type_name: type_name.into(),
185+
};
186+
// SAFETY: The symbol type of the type support getter function can be trusted
187+
// assuming the install dir hasn't been tampered with.
188+
// The pointer returned by this function is kept valid by keeping the library loaded.
189+
let type_support_ptr = unsafe {
190+
get_type_support_handle(
191+
self.introspection_type_support_library.as_ref(),
192+
INTROSPECTION_TYPE_SUPPORT_IDENTIFIER,
193+
&message_type,
194+
)?
195+
};
196+
// SAFETY: The pointer returned by get_type_support_handle() is always valid.
197+
let type_support = unsafe { &*type_support_ptr };
198+
debug_assert!(!type_support.data.is_null());
199+
let message_members: &rosidl_message_members_t =
200+
// SAFETY: The data pointer is supposed to be always valid.
201+
unsafe { &*(type_support.data as *const rosidl_message_members_t) };
202+
// SAFETY: The message members coming from a type support library will always be valid.
203+
let structure = unsafe { MessageStructure::from_rosidl_message_members(message_members) };
204+
// The fini function will always exist.
205+
let fini_function = message_members.fini_function.unwrap();
206+
let metadata = DynamicMessageMetadata {
207+
message_type,
208+
introspection_type_support_library: Arc::clone(
209+
&self.introspection_type_support_library,
210+
),
211+
type_support_ptr,
212+
structure,
213+
fini_function,
214+
};
215+
Ok(metadata)
216+
}
217+
}
218+
187219
// ========================= impl for MessageTypeName =========================
188220

189221
impl TryFrom<&str> for MessageTypeName {
@@ -248,38 +280,8 @@ impl DynamicMessageMetadata {
248280
///
249281
/// See [`DynamicMessage::new()`] for the expected format of the `full_message_type`.
250282
pub fn new(message_type: MessageTypeName) -> Result<Self, DynamicMessageError> {
251-
// SAFETY: The symbol type of the type support getter function can be trusted
252-
// assuming the install dir hasn't been tampered with.
253-
// The pointer returned by this function is kept valid by keeping the library loaded.
254-
let library = DYNAMIC_MESSAGE_PACKAGE_CACHE
255-
.lock()
256-
.unwrap()
257-
.get_or_load(&message_type.package_name)?;
258-
let type_support_ptr = unsafe {
259-
get_type_support_handle(
260-
&*library,
261-
INTROSPECTION_TYPE_SUPPORT_IDENTIFIER,
262-
&message_type,
263-
)?
264-
};
265-
// SAFETY: The pointer returned by get_type_support_handle() is always valid.
266-
let type_support = unsafe { &*type_support_ptr };
267-
debug_assert!(!type_support.data.is_null());
268-
let message_members: &rosidl_message_members_t =
269-
// SAFETY: The data pointer is supposed to be always valid.
270-
unsafe { &*(type_support.data as *const rosidl_message_members_t) };
271-
// SAFETY: The message members coming from a type support library will always be valid.
272-
let structure = unsafe { MessageStructure::from_rosidl_message_members(message_members) };
273-
// The fini function will always exist.
274-
let fini_function = message_members.fini_function.unwrap();
275-
let metadata = DynamicMessageMetadata {
276-
message_type,
277-
introspection_type_support_library: library,
278-
type_support_ptr,
279-
structure,
280-
fini_function,
281-
};
282-
Ok(metadata)
283+
let pkg = DynamicMessagePackage::new(&message_type.package_name)?;
284+
pkg.message_metadata(&message_type.type_name)
283285
}
284286

285287
/// Instantiates a new message.
@@ -563,23 +565,4 @@ mod tests {
563565
assert_eq!(*value, 42);
564566
}
565567
}
566-
567-
#[test]
568-
fn message_package_cache() {
569-
let package_name = "test_msgs";
570-
571-
// Create a weak reference to avoid increasing reference count
572-
let mut cache = DynamicMessageLibraryCache::default();
573-
let lib = Arc::downgrade(&cache.get_or_load(package_name).unwrap());
574-
{
575-
// Mock a user of the library (i.e. message)
576-
let _mock = lib.upgrade().unwrap();
577-
assert!(cache.unload(package_name));
578-
assert!(!cache.unload("non_existing_package"));
579-
// The library should _still_ be loaded since the message holds a reference
580-
assert!(lib.upgrade().is_some());
581-
}
582-
// Now the library should be unloaded and the reference should not be upgradeable
583-
assert!(lib.upgrade().is_none());
584-
}
585568
}

0 commit comments

Comments
 (0)