From eb197d7469c6865bc22a00f0661b879fde2712e5 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Thu, 14 Nov 2024 15:28:10 -0600 Subject: [PATCH 1/3] prefer wheel-provided libucxx.so in load_library, use RTLD_LOCAL --- python/libucxx/libucxx/load.py | 67 ++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/python/libucxx/libucxx/load.py b/python/libucxx/libucxx/load.py index 4d49d2cd..a25bb7eb 100644 --- a/python/libucxx/libucxx/load.py +++ b/python/libucxx/libucxx/load.py @@ -15,8 +15,35 @@ import ctypes import os +# Loading with RTLD_LOCAL adds the library itself to the loader's +# loaded library cache without loading any symbols into the global +# namespace. This allows libraries that express a dependency on +# this library to be loaded later and successfully satisfy this dependency +# without polluting the global symbol table with symbols from +# libucxx that could conflict with symbols from other DSOs. +PREFERRED_LOAD_FLAG = ctypes.RTLD_LOCAL + + +def _load_system_installation(soname: str): + """Try to dlopen() the library indicated by ``soname`` + + Raises ``OSError`` if library cannot be loaded. + """ + return ctypes.CDLL(soname, PREFERRED_LOAD_FLAG) + + +def _load_wheel_installation(soname: str): + """Try to dlopen() the library indicated by ``soname`` + + Returns ``None`` if the library cannot be loaded. + """ + if os.path.isfile(lib := os.path.join(os.path.dirname(__file__), "lib64", soname)): + return ctypes.CDLL(lib, PREFERRED_LOAD_FLAG) + return None + def load_library(): + """Dynamically load libucxx.so and its dependencies""" # If libucx was installed as a wheel, we must request it to load the library # symbols. Otherwise, we assume that the library was installed in a system path # that ld can find. @@ -28,27 +55,29 @@ def load_library(): libucx.load_library() del libucx - # Dynamically load libucxx.so. Prefer a system library if one is present to - # avoid clobbering symbols that other packages might expect, but if no - # other library is present use the one in the wheel. + prefer_system_installation = ( + os.getenv("RAPIDS_LIBUCXX_PREFER_SYSTEM_LIBRARY", "false").lower() != "false" + ) + + soname = "libucxx.so" libucxx_lib = None - try: - libucxx_lib = ctypes.CDLL("libucxx.so", ctypes.RTLD_GLOBAL) - except OSError: - # If neither of these directories contain the library, we assume we are in an - # environment where the C++ library is already installed somewhere else and the - # CMake build of the libucxx Python package was a no-op. Note that this approach - # won't work for real editable installs of the libucxx package, but that's not a - # use case I think we need to support. scikit-build-core has limited support for - # importlib.resources so there isn't a clean way to support that case yet. - for lib_dir in ("lib", "lib64"): - if os.path.isfile( - lib := os.path.join(os.path.dirname(__file__), lib_dir, "libucxx.so") - ): - libucxx_lib = ctypes.CDLL(lib, ctypes.RTLD_GLOBAL) - break + if prefer_system_installation: + # Prefer a system library if one is present to + # avoid clobbering symbols that other packages might expect, but if no + # other library is present use the one in the wheel. + try: + libucxx_lib = _load_system_installation(soname) + except OSError: + libucxx_lib = _load_wheel_installation(soname) + else: + # Prefer the libraries bundled in this package. If they aren't found + # (which might be the case in builds where the library was prebuilt + # before packaging the wheel), look for a system installation. + libucxx_lib = _load_wheel_installation(soname) + if libucxx_lib is None: + libucxx_lib = _load_system_installation(soname) # The caller almost never needs to do anything with this library, but no # harm in offering the option since this object at least provides a handle - # to inspect where libucxx was loaded from. + # to inspect where the libucxx was loaded from. return libucxx_lib From c69bf83bf8fb55f25da5850c0618ee29ed710b68 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Fri, 15 Nov 2024 11:52:54 -0600 Subject: [PATCH 2/3] try-catch default load path --- python/libucxx/libucxx/load.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/python/libucxx/libucxx/load.py b/python/libucxx/libucxx/load.py index a25bb7eb..ad3783a5 100644 --- a/python/libucxx/libucxx/load.py +++ b/python/libucxx/libucxx/load.py @@ -73,9 +73,15 @@ def load_library(): # Prefer the libraries bundled in this package. If they aren't found # (which might be the case in builds where the library was prebuilt # before packaging the wheel), look for a system installation. - libucxx_lib = _load_wheel_installation(soname) - if libucxx_lib is None: - libucxx_lib = _load_system_installation(soname) + try: + libucxx_lib = _load_wheel_installation(soname) + if libucxx_lib is None: + libucxx_lib = _load_system_installation(soname) + except OSError: + # If none of the searches above succeed, just silently return None + # and rely on other mechanisms (like RPATHs on other DSOs) to + # help the loader find the library. + pass # The caller almost never needs to do anything with this library, but no # harm in offering the option since this object at least provides a handle From 29282423bf30a9780cb4f4a3ea70085cb0bd1cd8 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Fri, 15 Nov 2024 11:54:02 -0600 Subject: [PATCH 3/3] Update python/libucxx/libucxx/load.py --- python/libucxx/libucxx/load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/libucxx/libucxx/load.py b/python/libucxx/libucxx/load.py index ad3783a5..be514139 100644 --- a/python/libucxx/libucxx/load.py +++ b/python/libucxx/libucxx/load.py @@ -85,5 +85,5 @@ def load_library(): # The caller almost never needs to do anything with this library, but no # harm in offering the option since this object at least provides a handle - # to inspect where the libucxx was loaded from. + # to inspect where libucxx was loaded from. return libucxx_lib