-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
runtime: support dlclose with -buildmode=c-shared #11100
Comments
I don't understand. What do you expect otherwise? In general, it's impossible to dlclose a Go shared |
eventhough close(c) and wait exiting goroutine, it reproduce. On 6/7/15, Minux Ma [email protected] wrote:
|
You can't stop the runtime, which uses its own
OS threads.
|
I think this is a legitimate feature request. Although we currently do not support calling dlclose on a Go shared library, and it would be difficult to make it work, it is not fundamentally impossible. |
It's possible if the user can be certain it doesn't hold onto
any Go objects and all resources allocated by Go code
has been freed (esp. no background goroutines).
However, as there is no way to kill a goroutine, I think all
sufficiently sophisticated Go shared library will not be unloadable.
For example, the os/signal package contains a background
goroutine to check for newly arrived signals. Any use of
os/signal.Handle will leave the Go shared library un-unloadable.
I'm sure there are other cases.
What's more, the runtime can't reliably detect whether it's
safe to unload the Go shared library, so rather than make
dlclose potentially trigger segmentation fault later, I'd rather
make dlclose always fail and document that.
|
This also reproduce. package main
import (
"C"
"fmt"
)
var (
c chan string
q chan struct{}
)
func init() {
c = make(chan string)
q = make(chan struct{})
go func() {
defer func() {
recover()
q <- struct{}{}
}()
n := 1
for {
switch {
case n%15 == 0:
c <- "FizzBuzz"
case n%3 == 0:
c <- "Fizz"
case n%5 == 0:
c <- "Buzz"
default:
c <- fmt.Sprint(n)
println("stop")
}
n++
}
}()
}
//export fizzbuzz
func fizzbuzz() *C.char {
return C.CString(<-c)
}
//export finish
func finish() {
close(c) // occur panic of sending closed channel in above
<-q // wait goroutine
}
func main() {
} from ctypes import *
import _ctypes
lib = CDLL("./libfizzbuzz.so")
lib.fizzbuzz.restype = c_char_p
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
lib.finish()
_ctypes.dlclose(lib._handle) |
i added |
I have a need for this functionality too. My goal is to be able to make changes to a running Go game engine without needing to completely reload the game. The implementation idea is to have 2 layers, the platform layer and the game layer. The platform layer is GOOS/GOARCH-specific and is written in C. It is responsible for the game loop wherein it gathers controller input and manages image and sound buffers that it provides to the game layer for writing. It uses OS-provided functionality to output graphics and sound. The game layer is platform agnostic, contains game logic only, and is written in Go. It is called by the platform layer once per iteration of the game loop. The game layer takes the controller input and buffers provided by the platform layer, updates the game state, and writes to the buffers. The game layer is a shared library, built using See https://gist.github.com/gcatlin/e09359f6e53f37e74a82 I'm trying this on darwin/amd64 and getting the following error when calling
Is there a different way to achieve this with Go? |
Go shared libraries do not support dlclose, and there is no likelihood that they will suppose dlclose in the future. Set the DF_1_NODELETE flag to tell the dynamic linker to not attempt to remove them from memory. This makes the shared library act as though every call to dlopen passed the RTLD_NODELETE flag. Fixes #12582. Update #11100. Update #12873. Change-Id: Id4b6e90a1b54e2e6fc8355b5fb22c5978fc762b4 Reviewed-on: https://go-review.googlesource.com/15605 Reviewed-by: Michael Hudson-Doyle <[email protected]>
Minux said:
Hi, so how does go know it is safe for the exe/elf to close if a user kills the application or hits the close button on a win32 gui window... There must be some way of finding out if it is safe? or go program just exits badly with hanging data around? The go runtime does not keep a reference count of the number of goroutines currently open? This should not be a performance hit to keep track of N number of goroutines open? Each goroutine opened increments a ref count by 1. But maybe there is already something implemented like this, that could be tapped into and used for dll's to know if all goroutines are finished. Minux says:
Can you increment a reference counter and unload the library later once all signals are finished (goroutine ref count hit zero)? How does the exe/elf normally exit safely and kill the program if goroutines could be still running? Simply make the DLL unload function act like how a normal go exe program safely exits? easier said than done, likely :-) |
Minux wrote,
This is the root of the problem, @z505. I don't believe there is any graceful shutdown of the runtime threads under normal process exit/termination. Hence they may still have references to DLL memory. So currently there's no safety problem with process termination; just doing an exit() call, it simply unceremoniously stops the process, and doesn't need to do any graceful cleanup.
Yep. |
I'm using Go c-shared library in my Unity3D game. In case of using Go c-shared library which creates at least one goroutine Unity will wait forever and will not be able to exit. This will not happen if library does not utilize goroutines. Here is sample library code to reproduce:
I'm compiling that library on Linux that way: Here is c# class that utilizes this library:
Unity is besed on Mono, that's why c#. There is a very-very dirty trick that fixes my problem and allows to exit my game without wating forever - I have implemented function in my Go library that causes panic and library crashes when I need to stop everything and exit game. Actually nobody should do that, but it works.
Wortst of it is that I don't know what exactly crashes after calling Panic() from C# - Unity(Mono) or Go library, but I suspect Unity. Another workaround is to compile Go code as executable and run it in separate process, but this is not sutible for iOS version of my game because iOS does not let executing separate processes (even included in the same application boundle) without using private APIs |
I'll attempt this. Background from https://groups.google.com/forum/#!topic/golang-nuts/L-tby34r5Gs
|
After some thinking, I realized that the main use case is really during development: to unload a Go DLL, and then re-load a modified version of that DLL; this being done during coding and evolving the DLL. We don't really want to stop the runtime, because we'll just then need to restart it upon the re-load of the newer version of the DLL. So I propose the following, more general, approach to supporting dlclose with -buildmode=c-shared: a) On windows, additionally during build of Go from source, build the runtime as a distinct DLL. Once the runtime DLL is loaded, it will never be unloaded, even if client Go DLLs that depend on the runtime are unloaded. This solves the tricky part of trying to halt the runtime cleanly, because we don't need to do that after all. b) On windows, build Go DLLs as libraries that are clients of the Go runtime DLL. Each client Go DLL will dynamically load the Go runtime DLL if it is not already loaded, taking care prevent race on load by some means when there are two or more Go DLLs loaded during process start (I expect this to be a common race; perhaps the first to claim a pre-agreed upon localhost port wins the race and gets to load the runtime DLL from some well known location in GOROOT). Each Go DLL will increment the reference count on the runtime twice, so that Windows never unloads the runtime DLL, even if the client Go DLL is unloaded by a dlclose() call. A distinct new buildmode may be indicated, to distinguish it from c-shared which currently bundles another copy of the runtime into every DLL, and probably needs to continue to do so for backwards compatibility. Suggestion: c) the extra benefit: now multiple DLLs when loaded all share the same runtime, and so the possibility of communicating via channels between DLLs becomes viable. @ianlancetaylor @alexbrainman @minux and anyone else with wisdom to contribute: Feedback welcome. |
@glycerine that may be the main use case for some, but others are writing shared libraries in Go to plug into other programs (e.g. see @brutestack's case above, or from my personal experience and @mattn's description for this issue, a library that is used from other programming languages) and run them in production, where they don't control how the library is used. I think your proposal, while useful for some cases, doesn't solve the issue. |
@dchest: This issue is about dlclose() support. The plan is perfectly compatible with plugging into non Go host programs. If you don’t understand why, ask about the specific point where you are confused, so that we can clarify. Also divide and conquer wise, we can’t do everything at once, so I’m okay with no runtime shutdown just prior to program termination. Especially as a workaround is already posted above. |
Looks like go/src/cmd/link/internal/ld/lib.go Lines 1346 to 1348 in 70b1a45
It sounds like from this thread, an approximate solution would be to:
Does that sound right? |
@tmm1 Not really. Consider #11100 (comment). Consider what should happen for a goroutine that is currently sitting in C code; if we remove the Go shared library then when the C code returns it will crash or (if some other shared library has been opened) behave unpredictably. Note that there is no separate GC thread; in the current Go runtime GC is handled by ordinary goroutines. |
I understand there are a lot of complexities and edge cases. In my case I am working in a plugin environment where I have a lot of control and can ensure that no goroutines will be busy calling into C code or other shared libraries. I have the ability to make sure all my code is done before calling dlclose, so I'm wondering what's required/possible in that more limited scenario. I suppose parts of the runtime internally could be calling into libc even if my user code is not. What I observe currently is that many of the OS threads related to the runtime start busy looping after I dlclose. I'll try to make a list of them and kill them to see if that helps. |
After looking more into this today, it seems there is no way to kill a thread. Even if you use pthread_kill (i.e tgkill), the signal will be delivered to that particular thread but the process as a whole will be affected by it if its a stop/terminate/kill signal.
I observed this behavior on macOS, where It turns out I found instead that using On Windows, a similar My solution for now is to simply allow old copies of the runtime and my code to stay resident in memory. When my plugin updates, I load a new dylib/so with a fresh copy of the runtime and my code. These copies of the golang runtime seem to be happy residing side by side. The old runtimes are basically doing nothing anyway. Note for anyone else attempting something similar: on macOS, be sure to pass |
This issue is about to get unloadable modules written in golang for some app that supports loadable modules. During the development process, it's good do have this, in production, this could be useful if someone requires hot upgrades, but since that go runtime (threads) does not shutdown in dlclose, we don't have this. I'm doing a hello world module example for freeswitch and I have this issue. The only thing I can do for now (golang <1.17) is to mark the module as unloadable. Module: https://github.com/iuridiniz/freeswitch_module_golang_sample |
@ianlancetaylor surely that's a problem for the c developer? it would be incumbent on them to stop the goroutine prior to closing the library; failing to do so would leave their application in an indeterminate state, as it would whatever resource they were trying to use after closing it? |
The problem is that there is nothing that the C developer can do to fix the problem. The Go runtime starts goroutines itself. Those goroutines can in some cases call into C code. There is no way for the program to stop those goroutines, or even to know whether they are running. |
@danieldonoghue This simple shared library starts 6 threads in my machine (go 1.18) and I don't know to shutdown them correctly. package main
import (
"C"
)
func init() {
// empty
}
//export answer
func answer() int8 {
return 42
}
func main() {
} calling it from python import ctypes
import _ctypes
import time
import os
def get_answer_from_golang():
lib = ctypes.CDLL("./libgoanswer.so")
answer = lib.answer
answer.restype = ctypes.c_int8
print("Answer is", answer())
_ctypes.dlclose(lib._handle)
def get_answer_from_c():
lib = ctypes.CDLL("./libcanswer.so")
answer = lib.answer
answer.restype = ctypes.c_int8
print("Answer is", answer())
_ctypes.dlclose(lib._handle)
def get_number_of_threads():
proc_status = "/proc/%d/status" % os.getpid()
with open(proc_status) as f:
v = f.read()
return int(v.split("Threads:")[1].split()[0])
print("Number of threads is:", get_number_of_threads())
# Try commenting out the next line
get_answer_from_golang()
# get_answer_from_c()
while True:
print("Number of threads is:", get_number_of_threads())
time.sleep(1) Results:
if I make the same library in C, there's no runtime after dlclose (what I think is the expecte behavior) C version: signed char answer()
{
return 42;
} Results:
|
I'm not an expert on how dynamic link works |
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as duplicate.
This comment was marked as duplicate.
…ending on what happens with the following issue golang/go#11100
It doesn't, maybe in the future depending on what happens with the following issue golang/go#11100. This commit is just to save the workarounds I was trying so that maybe I can come back to it in the future, the problem is that go spawns some threads that don't die after dlclose so segmentation fault happens and I could not find any way to kill these threads
It doesn't, maybe in the future depending on what happens with the following issue golang/go#11100. This commit is just to save the workarounds I was trying so that maybe I can come back to it in the future, the problem is that go spawns some threads that don't die after dlclose so segmentation fault happens and I could not find any way to kill these threads. To test run: `cargo run` `curl https://127.0.0.1:7878/api -k -H "Host: jequi.com"` `make reload`
It doesn't, maybe in the future depending on what happens with the following issue golang/go#11100. This commit is just to save the workarounds I was trying so that maybe I can come back to it in the future, the problem is that go spawns some threads that don't die after dlclose so segmentation fault happens and I could not find any way to kill these threads. To test run: `cargo run` `curl https://127.0.0.1:7878/api -k -H "Host: jequi.com"` `make reload`
It doesn't, maybe in the future depending on what happens with the following issue golang/go#11100. This commit is just to save the workarounds I was trying so that maybe I can come back to it in the future, the problem is that go spawns some threads that don't die after dlclose so segmentation fault happens and I could not find any way to kill these threads. To test run: `cargo run` `curl https://127.0.0.1:7878/api -k -H "Host: jequi.com"` `make reload`
It doesn't, maybe in the future depending on what happens with the following issue golang/go#11100. This commit is just to save the workarounds I was trying so that maybe I can come back to it in the future, the problem is that go spawns some threads that don't die after dlclose so segmentation fault happens and I could not find any way to kill these threads. To test run: `cargo run` `curl https://127.0.0.1:7878/api -k -H "Host: jequi.com"` `make reload`
Change https://go.dev/cl/582975 mentions this issue: |
This is the gccgo version of CL 15605. For https://gcc.gnu.org/PR114699 For #11100 For #12582 For #12873 Change-Id: I30e23130737022d772971f0bd629b57269174886 Reviewed-on: https://go-review.googlesource.com/c/go/+/582975 Reviewed-by: Than McIntosh <[email protected]> Commit-Queue: Ian Lance Taylor <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]>
build this with
then go
The text was updated successfully, but these errors were encountered: