Skip to content

feat: ability to override the default allocator#508

Open
russkel wants to merge 1 commit intoros2:rollingfrom
Greenroom-Robotics:feature/override-allocator
Open

feat: ability to override the default allocator#508
russkel wants to merge 1 commit intoros2:rollingfrom
Greenroom-Robotics:feature/override-allocator

Conversation

@russkel
Copy link

@russkel russkel commented Jul 23, 2025

Description

Added a function to set the default allocator, and modified the rcutils_get_default_allocator function 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.

Copilot AI review requested due to automatic review settings July 23, 2025 01:49
@russkel russkel force-pushed the feature/override-allocator branch from 614de94 to 2772941 Compare July 23, 2025 01:50
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@russkel russkel force-pushed the feature/override-allocator branch from 2772941 to b6e86f1 Compare July 23, 2025 02:07
Signed-off-by: Russ Webber <russ.webber@greenroomrobotics.com>
@russkel russkel force-pushed the feature/override-allocator branch from b6e86f1 to 499575e Compare July 23, 2025 02:19
Copy link
Collaborator

@fujitatomoya fujitatomoya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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?

@russkel
Copy link
Author

russkel commented Jul 24, 2025

Hi @fujitatomoya,

could you tell a bit more how this API is used by the application?

Code here https://github.com/Greenroom-Robotics/rclUE/blob/greenroom/Source/rclUE/Private/ROS2Subsystem.cpp#L63

In this use case, I set the allocator after rclc_support_init call (this maybe should be before now that I am looking at it again). This is so all the libs allocate using the Unreal Engine memory allocator to resolve double free errors I was getting. In reality I probably only want to set rmw allocator so all the messages from the middleware are freeable by Unreal garbage collector. I will freely admit I am not familiar with the specifics of Unreal/rmw allocation/deallocation/garbage collection, but this did seem to fix those issues.

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.

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.

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?

Happy to follow your lead with what you think would be a better solution. Maintaining patches on top of a custom rcutils isn't what I want to be doing.

@russkel
Copy link
Author

russkel commented Jul 24, 2025

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 rmw to use a different allocator because it doesn't seem to use the allocator passed in in the options struct.

@wjwwood wjwwood self-assigned this Jul 31, 2025
@russkel
Copy link
Author

russkel commented Nov 12, 2025

Is there any actions for me on this? Or is this a bigger issue?

@russkel
Copy link
Author

russkel commented Jan 9, 2026

Friendly ping.

Copy link
Member

@wjwwood wjwwood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 address 0x01...), 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() on 0x01... and .deallocate() on 0x02...

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.

Comment on lines +116 to +118
* Thread-Safe | No
* Uses Atomics | No
* Lock-Free | Yes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)) {
Copy link
Member

@wjwwood wjwwood Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment on lines +85 to +87
if (rcutils_allocator_is_valid(&rcutils_override_default_allocator)) {
return rcutils_override_default_allocator;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_expect or 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).

@wjwwood wjwwood removed their assignment Feb 25, 2026
@russkel
Copy link
Author

russkel commented Feb 26, 2026

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).

This is a great hint. Thanks for that.

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.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants