-
Notifications
You must be signed in to change notification settings - Fork 2
[DRAFT] Dynamic Libraries #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
interacsion
wants to merge
2
commits into
ekala-project:master
Choose a base branch
from
interacsion:dynamic-libraries
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| --- | ||
| Title: Dynamic Libraries | ||
| Author: interacsion | ||
| Status: Draft | ||
| Type: Informational | ||
| Created: 2025-05-11 | ||
| --- | ||
|
|
||
| # Preface | ||
|
|
||
| *Dynamic Libraries* (also referred to as *Shared Libraries*, *Shared Objects* or just *Libraries*) are files containing executable code (*Executables*) that can be loaded into a process at runtime. This is generally done in one of 2 ways: | ||
|
|
||
| ### 1. *Dynamic Linking* | ||
|
|
||
| An *Executable* (either a *Binary*, which contains an entry point, or a *Library*) can be compiled against a Dynamic Library. When such Binary is run it will first invoke the *Dynamic Linker* (also referred to as *Dynamic Loader* or *ELF Interpreter*) which will in turn recursively load all required Dynamic Libraries into process memory. | ||
|
|
||
| ### 2. *Dynamic Loading* | ||
|
|
||
| A Dynamic Library can also be loaded into process memory by calling `dlopen` (or `dlmopen`, more on that later). The Executable does not need to be compiled against the Library, and can conditionally load different Libraries. | ||
|
|
||
| ## Dynamic Libraries in Nix | ||
|
|
||
| On traditional Linux distributions, Dynamic Libraries are usually linked to by their *`SONAME`* (The filename if the Shared Library). The Dynamic Linker then searches a predefined set of system directories to find it. | ||
|
|
||
| Nix, on the other hand, takes a different approach; since it doesn't install packages into standard directories, it links Dynamic Libraries by their full filepath, and this is where the problem starts. | ||
|
|
||
| # Problem Statement | ||
|
|
||
| Most Dynamic Libraries assume that the Library and the Executable that uses it have access to the same instance of a Dynamic Library they both depend on. | ||
|
|
||
| The most obvious example is a Library which allocates memory and expects the User to free it. This requires that both the Library and the User share the same instance of, usually, glibc. | ||
|
|
||
| The way the Dynamic linker accommodates this is as following: Before a Library is loaded it checks if a Library with the same `SONAME` is already loaded into the process, disregarding the full filepath, if specified. if so, it substitutes it for the one that is already loaded. | ||
|
|
||
| This works fine on traditional Linux distributions, but **actively undermines** the purity guarantees Nix tries to make. For example, if a Binary is linked to a certain version of glibc and to a Library that's linked to a different version of glibc, the first one that gets loaded will trump the other. | ||
|
|
||
| Nixpkgs manages to mostly avoid this problem due to its monorepo nature; there's a single attribute set containing all the packages and as long as a Binary only links to dependencies from a single Nixpkgs revision, no conflict can occur. | ||
|
|
||
| Ekapkgs, on the other hand, would have to manage this more carefully due to its polyrepo structure. For example, a binary from `pkgs` could depend on one `corepkgs` revision and on a library from `ocaml-pkgs` which depends on a different `corepkgs` revision, which would attempt to load two different glibc versions. | ||
|
|
||
| ## System Libraries | ||
|
|
||
| A small subset of Dynamic Libraries must be installed as part of the system; most notable are graphics drivers (which are loaded using `libvulkan`, `libglvnd` or `libgbm`), but GPGPU and sound systems probably fall into this category as well. | ||
|
|
||
| Since these Libraries are installed separately than the applications using them, they cannot be expected to link to the same version of glibc as the application. This is a problem that actually manifests in NixOS (Citation Needed). | ||
|
|
||
| # Non-Solutions | ||
|
|
||
| ### 1. Patching the Dynamic Linker to load multiple versions of the same Library | ||
|
|
||
| This is not a solution since *Most Dynamic Libraries assume that the Library and the Executable that uses it have access to the same instance of a Dynamic Library they both depend on.* | ||
|
|
||
| # Proposed Solution | ||
|
|
||
| The following is a concept of a 3-part system which should ultimately resolve all the aforementioned problems. | ||
|
|
||
| ### 1. Dependency tree analysis | ||
|
|
||
| At either package evaluation or build time, we would analyze the tree of *Linked* Dynamic Libraries and ensure only one version of each Library is present. | ||
|
|
||
| ### 2. Runtime Loading validation | ||
|
|
||
| We would have to patch glibc and make `dlopen` error out when a different version of a Library is already loaded, to prevent accidental impure behavior. | ||
|
|
||
| ### 3. System Library isolation | ||
|
|
||
| System Libraries would have to run in a separate *link-map list* than the rest of the process, which would allow them to link to a different glibc version. This can be done in 2 ways: | ||
|
|
||
| 1. We can patch Libraries like `libvulkan` to use `dlmopen` instead of `dlopen` when loading drivers. | ||
|
|
||
| 2. We can write shims for Libraries like `libvulkan` that would load the real Library using `dlmopen`. | ||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The composition of the different repositories will occur through overlays, from the nix perspective, the instantiated package set should be very similar to nixpkgs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I didn't consider that. I guess this paragraph is void then
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still a valid concern with "flakes" (not specifically, just that they make it easy), where you can reference very divergent DAGs for the same "package". E.g. two versions of openssl
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonringer Doesn't this idea pose a conflict with the idea of atoms? Something like
corepkgswill inevitably change over time. And an overlay that depends on corepkgs cannot always magically assume that what it needs is available. I think you would need some kind of compatibility-checking either way.I've been working on a prototype that allows for different corepkgs revisions to be used at the same time, this is good for easy predictability but would hit the problem described here. Maybe if there's a versioning system and good public-facing documentation of the packages in corepkgs (and available attributes) in place both issues could be addressed? Version-checking the different repos guarantees predictability but also makes sure there's no conflicting dependencies. Sound right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I raised this point briefly in the discord and I tend to agree. We would potentially have to implement some sort of dependency resolver for Atoms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to flakes, I will probably provide multiple entrypoints. But at the end of the day, corepkgs will just be a "nix package set"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can be open to including metadata required for Atoms to work, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm fine with non-intrusive inclusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take this into consideration for my prototype. How will all the overlays be collected together?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My original plan was to not invade Jon's design space really at all, and instead just build up something that would naturally compliment it as it develops.
That's the idea, yes. For simplicities sake, and for simple projects, a "shallow" algorithm which simply locks direct dependencies based on a version request should suffice. For large derivations, a deep algo which descends the entire dependency tree might come in handy.
Either way, the point of atoms is the keep the dependency tree well abstracted and explicit. There is a lot of incidental complexity involved with all the overlaying and overriding going on everywhere, and the tight coupling between a specific checkout of nixpkgs and virtually everything else sort of makes this feel unavoidable.
atoms should alleviate this, and make updating the package repository more of a granular step by step process, instead of an all or nothing slog. At least, that's what I'm betting on, and as the project grows in complexity, and if I suceed, hopefully that becomes clear all on its own.