Skip to content

Commit 7d375ec

Browse files
authored
Merge pull request #431 from Luap99/dlerror
dlopen: make error handling thread safe
2 parents d843340 + d1df97f commit 7d375ec

File tree

2 files changed

+49
-0
lines changed

2 files changed

+49
-0
lines changed

internal/dlopen/dlopen.go

+9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import "C"
2323
import (
2424
"errors"
2525
"fmt"
26+
"runtime"
2627
"unsafe"
2728
)
2829

@@ -56,6 +57,10 @@ func GetHandle(libs []string) (*LibHandle, error) {
5657

5758
// GetSymbolPointer takes a symbol name and returns a pointer to the symbol.
5859
func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) {
60+
// Locking the thread is critical here as the dlerror() is thread local so
61+
// go should not reschedule this onto another thread.
62+
runtime.LockOSThread()
63+
defer runtime.UnlockOSThread()
5964
sym := C.CString(symbol)
6065
defer C.free(unsafe.Pointer(sym))
6166

@@ -71,6 +76,10 @@ func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) {
7176

7277
// Close closes a LibHandle.
7378
func (l *LibHandle) Close() error {
79+
// Locking the thread is critical here as the dlerror() is thread local so
80+
// go should not reschedule this onto another thread.
81+
runtime.LockOSThread()
82+
defer runtime.UnlockOSThread()
7483
C.dlerror()
7584
C.dlclose(l.Handle)
7685
e := C.dlerror()

internal/dlopen/dlopen_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package dlopen
1616

1717
import (
1818
"fmt"
19+
"sync"
1920
"testing"
2021
)
2122

@@ -62,3 +63,42 @@ func TestDlopen(t *testing.T) {
6263
}
6364
}
6465
}
66+
67+
// Note this is not a reliable reproducer for the problem.
68+
// It depends on the fact the it first generates some dlerror() errors
69+
// by using non existent libraries.
70+
func TestDlopenThreadSafety(t *testing.T) {
71+
libs := []string{
72+
"libstrange1.so",
73+
"libstrange2.so",
74+
"libstrange3.so",
75+
"libstrange4.so",
76+
"libstrange5.so",
77+
"libstrange6.so",
78+
"libstrange7.so",
79+
"libstrange8.so",
80+
"libc.so.6",
81+
"libc.so",
82+
}
83+
84+
// 10000 is the default golang thread limit, so adding more will likely fail
85+
// but this number is enough to reproduce the issue most of the time for me.
86+
count := 10000
87+
wg := sync.WaitGroup{}
88+
wg.Add(count)
89+
90+
for i := 0; i < count; i++ {
91+
go func() {
92+
defer wg.Done()
93+
lib, err := GetHandle(libs)
94+
if err != nil {
95+
t.Errorf("GetHandle failed unexpectedly: %v", err)
96+
}
97+
_, err = lib.GetSymbolPointer("strlen")
98+
if err != nil {
99+
t.Errorf("GetSymbolPointer strlen failed unexpectedly: %v", err)
100+
}
101+
}()
102+
}
103+
wg.Wait()
104+
}

0 commit comments

Comments
 (0)