diff --git a/internal/dlopen/dlopen.go b/internal/dlopen/dlopen.go index 23774f6..770d28d 100644 --- a/internal/dlopen/dlopen.go +++ b/internal/dlopen/dlopen.go @@ -23,6 +23,7 @@ import "C" import ( "errors" "fmt" + "runtime" "unsafe" ) @@ -56,6 +57,10 @@ func GetHandle(libs []string) (*LibHandle, error) { // GetSymbolPointer takes a symbol name and returns a pointer to the symbol. func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) { + // Locking the thread is critical here as the dlerror() is thread local so + // go should not reschedule this onto another thread. + runtime.LockOSThread() + defer runtime.UnlockOSThread() sym := C.CString(symbol) defer C.free(unsafe.Pointer(sym)) @@ -71,6 +76,10 @@ func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) { // Close closes a LibHandle. func (l *LibHandle) Close() error { + // Locking the thread is critical here as the dlerror() is thread local so + // go should not reschedule this onto another thread. + runtime.LockOSThread() + defer runtime.UnlockOSThread() C.dlerror() C.dlclose(l.Handle) e := C.dlerror() diff --git a/internal/dlopen/dlopen_test.go b/internal/dlopen/dlopen_test.go index b9017b7..613cbe7 100644 --- a/internal/dlopen/dlopen_test.go +++ b/internal/dlopen/dlopen_test.go @@ -16,6 +16,7 @@ package dlopen import ( "fmt" + "sync" "testing" ) @@ -62,3 +63,42 @@ func TestDlopen(t *testing.T) { } } } + +// Note this is not a reliable reproducer for the problem. +// It depends on the fact the it first generates some dlerror() errors +// by using non existent libraries. +func TestDlopenThreadSafety(t *testing.T) { + libs := []string{ + "libstrange1.so", + "libstrange2.so", + "libstrange3.so", + "libstrange4.so", + "libstrange5.so", + "libstrange6.so", + "libstrange7.so", + "libstrange8.so", + "libc.so.6", + "libc.so", + } + + // 10000 is the default golang thread limit, so adding more will likely fail + // but this number is enough to reproduce the issue most of the time for me. + count := 10000 + wg := sync.WaitGroup{} + wg.Add(count) + + for i := 0; i < count; i++ { + go func() { + defer wg.Done() + lib, err := GetHandle(libs) + if err != nil { + t.Errorf("GetHandle failed unexpectedly: %v", err) + } + _, err = lib.GetSymbolPointer("strlen") + if err != nil { + t.Errorf("GetSymbolPointer strlen failed unexpectedly: %v", err) + } + }() + } + wg.Wait() +}