6
6
//! The central type of this module is [`DynamicMessage`].
7
7
8
8
use std:: {
9
+ collections:: HashMap ,
9
10
fmt:: { self , Display } ,
10
11
ops:: Deref ,
11
12
path:: PathBuf ,
12
- sync:: Arc ,
13
+ sync:: { Arc , LazyLock , Mutex } ,
13
14
} ;
14
15
15
16
use rosidl_runtime_rs:: RmwMessage ;
@@ -29,25 +30,50 @@ pub use error::*;
29
30
pub use field_access:: * ;
30
31
pub use message_structure:: * ;
31
32
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
+ }
49
67
}
50
68
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
+
51
77
/// A parsed/validated message type name of the form `<package_name>/msg/<type_name>`.
52
78
#[ derive( Clone , Debug , PartialEq , Eq ) ]
53
79
pub struct MessageTypeName {
@@ -90,8 +116,6 @@ pub struct DynamicMessage {
90
116
needs_fini : bool ,
91
117
}
92
118
93
- // ========================= impl for DynamicMessagePackage =========================
94
-
95
119
/// This is an analogue of rclcpp::get_typesupport_library.
96
120
fn get_type_support_library (
97
121
package_name : & str ,
@@ -160,62 +184,6 @@ unsafe fn get_type_support_handle(
160
184
161
185
const INTROSPECTION_TYPE_SUPPORT_IDENTIFIER : & str = "rosidl_typesupport_introspection_c" ;
162
186
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
-
219
187
// ========================= impl for MessageTypeName =========================
220
188
221
189
impl TryFrom < & str > for MessageTypeName {
@@ -280,8 +248,38 @@ impl DynamicMessageMetadata {
280
248
///
281
249
/// See [`DynamicMessage::new()`] for the expected format of the `full_message_type`.
282
250
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)
285
283
}
286
284
287
285
/// Instantiates a new message.
@@ -565,4 +563,23 @@ mod tests {
565
563
assert_eq ! ( * value, 42 ) ;
566
564
}
567
565
}
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
+ }
568
585
}
0 commit comments