Skip to content

Commit 0f55f24

Browse files
mini-1235claude
andauthored
Speed up shared library path lookup on macOS (ros2#609)
rcutils_load_shared_library() finds the full path of a library it just opened by calling dlopen(RTLD_NOLOAD)/dlclose() on every loaded image until one returns the same handle. Each dlclose() makes dyld walk all loaded images, so a single lookup is quadratic in the number of images. In a Nav2 component container (~700 images) this makes bringup take minutes, since every type support library lookup goes through here. Check only images whose name matches the request first: the file name when a bare name was requested (as rosidl_typesupport does), otherwise the requested or realpath()-resolved path. If none matches, fall back to checking all images as before, so libraries dyld knows under another name are still found. Signed-off-by: Maurice <mauricepurnawan@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent e86e907 commit 0f55f24

1 file changed

Lines changed: 67 additions & 18 deletions

File tree

‎src/shared_library.c‎

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
extern "C"
1717
{
1818
#endif
19+
#include <stdbool.h>
1920
#include <stdio.h>
21+
#include <limits.h>
2022
#include <stdlib.h>
23+
#include <string.h>
2124

2225
#ifndef _WIN32
2326
#if defined(__APPLE__)
@@ -60,6 +63,57 @@ rcutils_get_zero_initialized_shared_library(void)
6063
return zero_initialized_shared_library;
6164
}
6265

66+
#if defined(__APPLE__)
67+
/// Whether a loaded image's name could be the library that dlopen loaded for `library_path`.
68+
/**
69+
* `resolved_path` is the realpath() of `library_path`, or NULL if it has none.
70+
*/
71+
static bool
72+
image_name_matches(const char * image_name, const char * library_path, const char * resolved_path)
73+
{
74+
if (NULL == strchr(library_path, '/')) {
75+
// dlopen searched for a bare file name, so the image can be in any directory.
76+
const char * slash = strrchr(image_name, '/');
77+
return 0 == strcmp(NULL == slash ? image_name : slash + 1, library_path);
78+
}
79+
return 0 == strcmp(image_name, library_path) ||
80+
(NULL != resolved_path && 0 == strcmp(image_name, resolved_path));
81+
}
82+
83+
/// Find the name of the loaded image whose handle is `lib_pointer`; NULL if none.
84+
/**
85+
* With `only_matching_names`, images rejected by image_name_matches() are skipped.
86+
*/
87+
static rcutils_ret_t
88+
find_image_name(
89+
void * lib_pointer, const char * library_path, const char * resolved_path,
90+
bool only_matching_names, const char ** image_name)
91+
{
92+
*image_name = NULL;
93+
uint32_t image_count = _dyld_image_count();
94+
for (uint32_t i = 0; NULL == *image_name && i < image_count; ++i) {
95+
// Iterate in reverse as the library is likely near the end of the list.
96+
const char * candidate_name = _dyld_get_image_name(image_count - i - 1);
97+
if (NULL == candidate_name) {
98+
RCUTILS_SET_ERROR_MSG("dyld image index out of range");
99+
return RCUTILS_RET_ERROR;
100+
}
101+
if (only_matching_names && !image_name_matches(candidate_name, library_path, resolved_path)) {
102+
continue;
103+
}
104+
void * handle = dlopen(candidate_name, RTLD_LAZY | RTLD_NOLOAD);
105+
if (handle == lib_pointer) {
106+
*image_name = candidate_name;
107+
}
108+
if (dlclose(handle) != 0) {
109+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("dlclose error: %s", dlerror());
110+
return RCUTILS_RET_ERROR;
111+
}
112+
}
113+
return RCUTILS_RET_OK;
114+
}
115+
#endif // defined(__APPLE__)
116+
63117
rcutils_ret_t
64118
rcutils_load_shared_library(
65119
rcutils_shared_library_t * lib,
@@ -98,25 +152,20 @@ rcutils_load_shared_library(
98152
}
99153

100154
#if defined(__APPLE__)
155+
// Find the loaded image's name to get its full path. Each candidate costs a dlopen/dlclose,
156+
// and every dlclose makes dyld walk all loaded images, so checking every image is quadratic.
157+
// Check images whose name matches the request first, then fall back to all images in case
158+
// dyld knows the library under another name (e.g. a versioned symlink).
159+
char resolved_path[PATH_MAX];
160+
const char * resolved =
161+
NULL == strchr(library_path, '/') ? NULL : realpath(library_path, resolved_path);
101162
const char * image_name = NULL;
102-
uint32_t image_count = _dyld_image_count();
103-
for (uint32_t i = 0; NULL == image_name && i < image_count; ++i) {
104-
// Iterate in reverse as the library is likely near the end of the list.
105-
const char * candidate_name = _dyld_get_image_name(image_count - i - 1);
106-
if (NULL == candidate_name) {
107-
RCUTILS_SET_ERROR_MSG("dyld image index out of range");
108-
ret = RCUTILS_RET_ERROR;
109-
goto fail;
110-
}
111-
void * handle = dlopen(candidate_name, RTLD_LAZY | RTLD_NOLOAD);
112-
if (handle == lib->lib_pointer) {
113-
image_name = candidate_name;
114-
}
115-
if (dlclose(handle) != 0) {
116-
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("dlclose error: %s", dlerror());
117-
ret = RCUTILS_RET_ERROR;
118-
goto fail;
119-
}
163+
ret = find_image_name(lib->lib_pointer, library_path, resolved, true, &image_name);
164+
if (RCUTILS_RET_OK == ret && NULL == image_name) {
165+
ret = find_image_name(lib->lib_pointer, library_path, resolved, false, &image_name);
166+
}
167+
if (RCUTILS_RET_OK != ret) {
168+
goto fail;
120169
}
121170
if (NULL == image_name) {
122171
RCUTILS_SET_ERROR_MSG("dyld image name could not be found");

0 commit comments

Comments
 (0)