66//! The central type of this module is [`DynamicMessage`].
77
88use std:: {
9+ collections:: HashMap ,
910 fmt:: { self , Display } ,
1011 ops:: Deref ,
1112 path:: PathBuf ,
12- sync:: Arc ,
13+ sync:: { Arc , LazyLock , Mutex } ,
1314} ;
1415
1516use rosidl_runtime_rs:: RmwMessage ;
@@ -29,25 +30,50 @@ pub use error::*;
2930pub use field_access:: * ;
3031pub use message_structure:: * ;
3132
32- /// Factory for constructing messages in a certain package dynamically.
33- ///
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 ,
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.
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+ }
4967}
5068
69+ /// A global cache for loaded message packages.
70+ ///
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 ( ) ) ;
76+
5177/// A parsed/validated message type name of the form `<package_name>/msg/<type_name>`.
5278#[ derive( Clone , Debug , PartialEq , Eq ) ]
5379pub struct MessageTypeName {
@@ -90,8 +116,6 @@ pub struct DynamicMessage {
90116 needs_fini : bool ,
91117}
92118
93- // ========================= impl for DynamicMessagePackage =========================
94-
95119/// This is an analogue of rclcpp::get_typesupport_library.
96120fn get_type_support_library (
97121 package_name : & str ,
@@ -160,62 +184,6 @@ unsafe fn get_type_support_handle(
160184
161185const INTROSPECTION_TYPE_SUPPORT_IDENTIFIER : & str = "rosidl_typesupport_introspection_c" ;
162186
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-
219187// ========================= impl for MessageTypeName =========================
220188
221189impl TryFrom < & str > for MessageTypeName {
@@ -280,8 +248,38 @@ impl DynamicMessageMetadata {
280248 ///
281249 /// See [`DynamicMessage::new()`] for the expected format of the `full_message_type`.
282250 pub fn new ( message_type : MessageTypeName ) -> Result < Self , DynamicMessageError > {
283- let pkg = DynamicMessagePackage :: new ( & message_type. package_name ) ?;
284- pkg. message_metadata ( & message_type. type_name )
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)
285283 }
286284
287285 /// Instantiates a new message.
@@ -565,4 +563,23 @@ mod tests {
565563 assert_eq ! ( * value, 42 ) ;
566564 }
567565 }
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+ }
568585}
0 commit comments