Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions internal/dlopen/dlopen.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import "C"
import (
"errors"
"fmt"
"runtime"
"unsafe"
)

Expand Down Expand Up @@ -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))

Expand All @@ -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()
Expand Down
40 changes: 40 additions & 0 deletions internal/dlopen/dlopen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package dlopen

import (
"fmt"
"sync"
"testing"
)

Expand Down Expand Up @@ -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()
}