feat: ability to override the default allocator#508
feat: ability to override the default allocator#508russkel wants to merge 1 commit intoros2:rollingfrom
Conversation
614de94 to
2772941
Compare
There was a problem hiding this comment.
Pull Request Overview
This PR adds functionality to override the default allocator in rcutils, enabling custom memory management integration (specifically for Unreal Engine's allocator API). The changes introduce a new public API function and modify the existing default allocator retrieval behavior.
- Adds
rcutils_set_default_allocator()function to allow setting a custom default allocator - Modifies
rcutils_get_default_allocator()to return the override allocator when set and valid - Introduces static storage for the override allocator with validation checks
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/allocator.c | Implements override allocator storage and setter function, updates default allocator logic |
| include/rcutils/allocator.h | Adds public API declaration for the new setter function |
2772941 to
b6e86f1
Compare
Signed-off-by: Russ Webber <russ.webber@greenroomrobotics.com>
b6e86f1 to
499575e
Compare
fujitatomoya
left a comment
There was a problem hiding this comment.
@russkel thanks for creating PR.
could you tell a bit more how this API is used by the application?
when this API is expected to be called? there are many packages depend on rcutils to get the default allocator such as rclcpp, rclpy, rmw and idl libraries. if this is up to the user application to set the default allocator, some uses default allocator (because they already use the default allocator before this API is called) and some uses use specified default allocator. i am not sure if that is what we want to do here, can you explain the user application behavior and expectation with this API?
i think that would be more useful to allow the user application to pass the allocator argument to be used by each API instead of changing the default allocator for everyone? and if not specified, it falls back to the default allocator as currently some APIs are implemented so?
|
Hi @fujitatomoya,
In this use case, I set the allocator after
I wrote this some years ago - but it is intended to set the allocator for all the libraries before they are initialised. If I remember correctly, I was unable to individually set them.
Happy to follow your lead with what you think would be a better solution. Maintaining patches on top of a custom |
|
I dug through the code again, and I realise this is probably why I did it this way: https://github.com/ros2/rmw/blob/e6addf2411b8ee8a2ac43d691533b8c05ae8f1b6/rmw/src/allocators.c#L28 I was never able to get |
|
Is there any actions for me on this? Or is this a bigger issue? |
|
Friendly ping. |
There was a problem hiding this comment.
Sorry for taking a very long time to get back to you on this.
I left some technical comments that would need to be addressed, but taking a step back, I actually think you'd be better off overriding the default allocator functions using a mechanism like LD_PRELOAD or a similar mechanism, which would set the global allocator very early, and due to its nature, could do so without having to change the existing implementation. This is the approach that allocators like jemalloc recommend (https://github.com/jemalloc/jemalloc/wiki/getting-started).
Setting the allocator so early (dynamic linking step), and never unsetting it, also helps you avoid a potential problem with having the allocators mismatch. For example, consider:
- a routine uses
rcutils_get_default_allocator()very early in the program (let's say it returns an address0x01...), and calls.allocate() - you set the default allocator, which is stored in a different address, like
0x02... - the program uses the default allocator (now
0x02...) to allocate and deallocate memory freely - the very early routine finally gets cleaned up by calling
rcutils_get_default_allocator()again and then.deallocate()on it - 🌩️ you run into a problem because that early routine called
.allocate()on0x01...and.deallocate()on0x02...
It will be very hard to avoid this situation in practice.
The allocator interface here was meant more for use with arena allocators and other special use cases, in which cases you'd create an allocator and then pass it in every where you use it, rather that changing the default allocator.
As you've discovered, it isn't passed all the way down the interface in every situation, due to a variety of technical reasons and in some cases oversights in the code.
For all of these reasons, I'm not convinced this is a good API to add, due to the numerous pitfalls you can run into with it.
| * Thread-Safe | No | ||
| * Uses Atomics | No | ||
| * Lock-Free | Yes |
There was a problem hiding this comment.
I'm a bit concerned about having this be non-thread-safe, because it's unlikely that calling code will have the ability to synchronize with other calling threads and/or with other threads reading the value. My guess is that for this to be useful, it needs to be thread-safe and that to do so, using atomics and no locks (assuming the atomics are lock-free) would be ideal.
| void | ||
| rcutils_set_default_allocator(rcutils_allocator_t override_allocator) | ||
| { | ||
| if (rcutils_allocator_is_valid(&override_allocator)) { |
There was a problem hiding this comment.
With this check on the setter, is it impossible to reset the default allocator to be the "real default"? I would imagine, you could do something like rcutils_set_default_allocator({0});, but not with this check. Maybe it should just assign the allocator value no matter what?
| if (rcutils_allocator_is_valid(&rcutils_override_default_allocator)) { | ||
| return rcutils_override_default_allocator; | ||
| } |
There was a problem hiding this comment.
Three concerns here:
- Thread-safety: what happens if some other thread calls
rcutils_set_default_allocator()between lines 85 and 85? Not to mention line 86 being run in the middle of the assignment that happens on line 78. - Performance: this check will be called potentially many, many times, even in critical code sections, and having a branch here might be expensive. It would be great to either eliminate the check, or use some kind of branch prediction hints, like
__builtin_expector something similar. I believe we use this kind of thing in the console logging code in this repository. - Is this redundant? calling code is likely to check that the allocator is valid and right now the setter for this first checks that the allocator is valid, so this check is probably redundant (unless we remove the check in the setter).
This is a great hint. Thanks for that.
Okay, I will not pursue this as it sounds like it wont work nicely and probably will not be merged. I hope the LD_PRELOAD does the trick instead, which is quite a bit simpler. |
Description
Added a function to set the default allocator, and modified the
rcutils_get_default_allocatorfunction to return it if it is set and valid.I needed this for use with Unreal Engine in order to expose UE's allocator API to ROS 2 so that the UE garbage cleaner could free ROS 2 objects successfully.
I have also seen references to a similar function in another PR (https://github.com/ros2/rcutils/pull/458/files#diff-8352803836a54313eb08fa96e6b6590eae0cfad976ae3187ae45d322d6f50551R101), so I assume I am not the only one to have added this functionality.
Is this user-facing behavior change?
Yes, an additional function in the rcutils allocator API.
Did you use Generative AI?
No.