add support for linux dynamic dll loading - #583
Conversation
|
Looks like something is going wrong in the thread shutdown. I'll look into it tomorrow. |
There was a problem hiding this comment.
Running unit tests is not really part of initialization. I moved the call to line 567.
|
Module-related code is beyond me; cc @dawgfoto |
|
Among other things we cannot initialize a shared library for another thread but the loading one. This means we must not use one globally shared data structure ( |
|
While handling rt.dmain2's constructor specially works, we could simply run rt_init before the first module constructor (here) and rt_term after the last one. I think that would be a cleaner solution. |
There was a problem hiding this comment.
Will this really work the way we want? Since there's no dependency tree here, it seems like this static dtor will be run in a random order with the others, and so some static dtors will be run after the GC and runtime are completely shut down.
There was a problem hiding this comment.
In minfo.d, I added code specifically to run this ctor first, and this dtor last.
There was a problem hiding this comment.
Just call rt_init within the first call to _d_dso_registry and rt_term when the last library unregisters. This is dead simple and you can scratch all the code to treat rt.dmain2 specially.
I know _static_dsos[] needs to be protected with a mutex. Other than that, I believe the design is sound. |
You may be right. But I'd like to get everything working first, and then we can look at it again and perhaps refactor it. |
|
This whole area is in need of a comprehensive set of tests. Have you been building them or sticking to ad-hoc tests? |
|
A great deal of it is tested already with the existing suite. For the more specific tests for loading/unloading, yes I have some, and I've been working up in parallel a guide to writing such DLLs. But I was going to ask for your help in integrating them into the auto tester. |
|
Hopefully got the std.concurrency bug fixed. Turns out that atexit() runs before the .ctors, which caused the mutexes used by the GC to be prematurely destroyed. |
This is a bigger issue than it might initially appear. Say that thread A loads a shared library libfoo.so which contains module |
|
I don't see a fundamental reason why a dll cannot be initialized for all threads. |
|
I added a mutex around access to _static_dsos[]. |
Because native TLS access can't be redirected, you'd need to alter the GS register to do so. |
|
Suppose Thread 1 loads DLL A. Then Thread 2 loads DLL A. It isn't going to be loaded or initialized again. Also, there's nothing stopping Thread 3 from calling functions in A. |
|
@dawgfoto Ah, I see what you mean. Thread 1 cannot initialize TLS in Thread 2, i.e. cannot call the module tls ctors for Thread 2. In general it may be a problem we're stuck with. Dynamically loading/unloading DLLs does have some safety issues that I don't see a reasonable way around, such as unloading a DLL while still having function pointers into the DLL. But in the meantime, this pull does not make things worse, nor does it preclude other designs. |
We can perform initialization for Thread 2 in Runtime.loadLibrary. That would need a mapping from the dlopened handle to the DSO struct and knowing library dependencies. It's something to do after we get a single threaded version to run.
That's true and as you said certain programming errors remain possible. |
There was a problem hiding this comment.
since there are comments for what functions get used, at best the : notation should be used
|
@braddr @dawgfoto OK to pull this in the interest of time? If it doesn't lock us out of good designs etc. it would be great. Please advise, thanks. |
|
The corresponding documentation: |
There was a problem hiding this comment.
You don't actually need a mutex because calls to dlopen/dlclose are already globally serialized by the runtime linker, i.e. there is always only one thread calling _d_dso_registry.
There was a problem hiding this comment.
But there are references to _static_dsos[] outside of _d_dso_registry which do need to be synchronized with the ones inside _d_dso_registry. Otherwise the code may wind up pointing to memory that has been free()'d.
I'd rather spend two more days to sort out the remaining issues with this pull. |
I had that as a separate pull already, |
Which wasn't immediately merged because it introduced two unrelated behavioral changes. |
There was a problem hiding this comment.
This call chain comes to early gc_init=>thread_init=>thread_attachThis=>rt.sections_linux.initTLSRanges, because it makes the implicit assumption that all libraries are already loaded. It will miss the TLS ranges from anything but libphobos2.so.
|
I tried to make an alternative pull for the auto initialization during loading (https://github.com/dawgfoto/druntime/tree/autoInit) and it turns out, that this requires more work because druntime's initialization is pretty messy and contains a lot of implicit assumptions which break when we perform the initialization earlier. |
|
Got it to work #590. |
They were related because the initialization in rt_init and otherwise was different. They needed to be the same. It would not pass the test suite otherwise. |
|
And dynamic loading #593. |
|
This is superceded by #593 |
First stab at supporting dynamically loadable shared libraries under Linux. It's magical, because (thanks to @dawgfoto's ideas) you don't have to do anything other than use the Linux API to load/unload DLLs. They initialize themselves!
(Currently won't work if you try to load DLLs at the same time from different threads, needs some synchronization.)
Also needs some thought towards what should happen if an exception is thrown during module construction/destruction.
Once we get thoroughly satisfied that this is right, we can extend it to the other platforms.