Add opt-in monolithic shared library for ODR-safe plugin co-loading#170
Open
blasscoc wants to merge 1 commit into
Open
Add opt-in monolithic shared library for ODR-safe plugin co-loading#170blasscoc wants to merge 1 commit into
blasscoc wants to merge 1 commit into
Conversation
MDIO is consumed header-only, so every translation unit that touches it
statically links its own copy of tensorstore and its vendored Abseil.
That is harmless for a single executable, but a process that dlopen()s
more than one such plugin ends up with several copies of Abseil's global
state and aborts at runtime with the infamous "ODR violation in Cord".
This adds an opt-in MDIO_BUILD_MONOLITHIC_SHARED (default OFF, so static
consumers are untouched) that emits one libmdio_monolith.so, exposed via
the mdio::monolith alias. Plugins link that single object instead of the
tensorstore::* static deps, so the dynamic linker maps tensorstore and
Abseil exactly once per process and the globals are singletons again.
Getting a self-contained shared object right took two non-obvious steps:
- WHOLE_ARCHIVE on the tensorstore::* targets is a no-op because they
are INTERFACE aggregators. A small recursive collector walks the link
closure down to the concrete STATIC_LIBRARY targets (unwrapping the
$<LINK_LIBRARY:WHOLE_ARCHIVE,...> genexes tensorstore uses for its
alwayslink driver libs) and whole-archives those, so the explicit
template instantiations (Spec / Zarr metadata JSON binders) and the
driver self-registration objects are all force-included.
- The interface deps are re-exposed to consumers as $<COMPILE_ONLY:...>
usage requirements, propagating the transitive tensorstore/Abseil/
nlohmann include dirs and defines without dragging the static archives
back into the consumer (which would re-duplicate Abseil and defeat the
whole exercise).
Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MDIO is consumed header-only, so every translation unit that touches it statically links its own copy of tensorstore and its vendored Abseil. That is harmless for a single executable, but a process that dlopen()s more than one such plugin ends up with several copies of Abseil's global state and aborts at runtime with the infamous "ODR violation in Cord".
This adds an opt-in MDIO_BUILD_MONOLITHIC_SHARED (default OFF, so static consumers are untouched) that emits one libmdio_monolith.so, exposed via the mdio::monolith alias. Plugins link that single object instead of the tensorstore::* static deps, so the dynamic linker maps tensorstore and Abseil exactly once per process and the globals are singletons again.
Getting a self-contained shared object right took two non-obvious steps:
WHOLE_ARCHIVE on the tensorstore::* targets is a no-op because they are INTERFACE aggregators. A small recursive collector walks the link closure down to the concrete STATIC_LIBRARY targets (unwrapping the $<LINK_LIBRARY:WHOLE_ARCHIVE,...> genexes tensorstore uses for its alwayslink driver libs) and whole-archives those, so the explicit template instantiations (Spec / Zarr metadata JSON binders) and the driver self-registration objects are all force-included.
The interface deps are re-exposed to consumers as $<COMPILE_ONLY:...> usage requirements, propagating the transitive tensorstore/Abseil/ nlohmann include dirs and defines without dragging the static archives back into the consumer (which would re-duplicate Abseil and defeat the whole exercise).