2020-12-17 22:15:18 +00:00
|
|
|
package cutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math/rand"
|
2021-03-04 21:02:12 +00:00
|
|
|
"runtime"
|
2020-12-17 22:15:18 +00:00
|
|
|
"testing"
|
2021-08-10 16:27:37 +00:00
|
|
|
"time"
|
2020-12-17 22:15:18 +00:00
|
|
|
"unsafe"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestPtrGuard(t *testing.T) {
|
|
|
|
t.Run("storeAndRelease", func(t *testing.T) {
|
|
|
|
s := "string"
|
|
|
|
goPtr := (unsafe.Pointer)(&s)
|
|
|
|
cPtr := Malloc(PtrSize)
|
|
|
|
defer Free(cPtr)
|
|
|
|
pg := NewPtrGuard(cPtr, goPtr)
|
|
|
|
assert.Equal(t, *(*unsafe.Pointer)(cPtr), goPtr)
|
|
|
|
pg.Release()
|
|
|
|
assert.Zero(t, *(*unsafe.Pointer)(cPtr))
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("multiRelease", func(t *testing.T) {
|
|
|
|
s := "string"
|
|
|
|
goPtr := (unsafe.Pointer)(&s)
|
|
|
|
cPtr := Malloc(PtrSize)
|
|
|
|
defer Free(cPtr)
|
|
|
|
pg := NewPtrGuard(cPtr, goPtr)
|
|
|
|
assert.Equal(t, *(*unsafe.Pointer)(cPtr), goPtr)
|
|
|
|
pg.Release()
|
|
|
|
pg.Release()
|
|
|
|
pg.Release()
|
|
|
|
pg.Release()
|
|
|
|
assert.Zero(t, *(*unsafe.Pointer)(cPtr))
|
|
|
|
})
|
|
|
|
|
2021-03-04 21:02:12 +00:00
|
|
|
t.Run("uintptrescapesTest", func(t *testing.T) {
|
|
|
|
// This test assures that the special //go:uintptrescapes comment before
|
|
|
|
// the storeUntilRelease() function works as intended, that is the
|
|
|
|
// garbage collector doesn't touch the object referenced by the uintptr
|
|
|
|
// until the function returns after Release() is called. The test will
|
|
|
|
// fail if the //go:uintptrescapes comment is disabled (removed) or
|
|
|
|
// stops working in future versions of go.
|
2021-08-10 16:27:37 +00:00
|
|
|
var pgDone, uDone bool
|
2021-03-04 21:02:12 +00:00
|
|
|
var goPtr = func(b *bool) unsafe.Pointer {
|
|
|
|
s := "ok"
|
|
|
|
runtime.SetFinalizer(&s, func(p *string) { *b = true })
|
|
|
|
return unsafe.Pointer(&s)
|
|
|
|
}
|
|
|
|
cPtr := Malloc(PtrSize)
|
|
|
|
defer Free(cPtr)
|
2021-08-10 16:27:37 +00:00
|
|
|
pg := NewPtrGuard(cPtr, goPtr(&pgDone))
|
|
|
|
u := uintptr(goPtr(&uDone))
|
2021-03-04 21:02:12 +00:00
|
|
|
runtime.GC()
|
2021-08-10 16:27:37 +00:00
|
|
|
assert.Eventually(t, func() bool { return uDone },
|
|
|
|
time.Second, 10*time.Millisecond)
|
|
|
|
assert.False(t, pgDone)
|
2021-03-04 21:02:12 +00:00
|
|
|
pg.Release()
|
|
|
|
runtime.GC()
|
2021-08-10 16:27:37 +00:00
|
|
|
assert.Eventually(t, func() bool { return pgDone },
|
|
|
|
time.Second, 10*time.Millisecond)
|
2021-03-04 21:02:12 +00:00
|
|
|
assert.NotZero(t, u) // avoid "unused" error
|
|
|
|
})
|
|
|
|
|
2020-12-17 22:15:18 +00:00
|
|
|
t.Run("stressTest", func(t *testing.T) {
|
|
|
|
// Because the default thread limit of the Go runtime is 10000, creating
|
|
|
|
// 20000 parallel PtrGuards asserts, that Go routines of PtrGuards don't
|
|
|
|
// create threads.
|
|
|
|
const N = 20000 // Number of parallel PtrGuards
|
|
|
|
const M = 100000 // Number of loops
|
|
|
|
var ptrGuards [N]*PtrGuard
|
|
|
|
cPtrArr := (*[N]CPtr)(unsafe.Pointer(Malloc(N * PtrSize)))
|
|
|
|
defer Free(CPtr(&cPtrArr[0]))
|
|
|
|
toggle := func(i int) {
|
|
|
|
if ptrGuards[i] == nil {
|
|
|
|
goPtr := unsafe.Pointer(&(struct{ byte }{42}))
|
|
|
|
cPtrPtr := CPtr(&cPtrArr[i])
|
|
|
|
ptrGuards[i] = NewPtrGuard(cPtrPtr, goPtr)
|
|
|
|
assert.Equal(t, (unsafe.Pointer)(cPtrArr[i]), goPtr)
|
|
|
|
} else {
|
|
|
|
ptrGuards[i].Release()
|
|
|
|
ptrGuards[i] = nil
|
|
|
|
assert.Zero(t, cPtrArr[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := range ptrGuards {
|
|
|
|
toggle(i)
|
|
|
|
}
|
|
|
|
for n := 0; n < M; n++ {
|
|
|
|
i := rand.Intn(N)
|
|
|
|
toggle(i)
|
|
|
|
}
|
|
|
|
for i := range ptrGuards {
|
|
|
|
if ptrGuards[i] != nil {
|
|
|
|
ptrGuards[i].Release()
|
|
|
|
ptrGuards[i] = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := uintptr(0); i < N; i++ {
|
|
|
|
assert.Zero(t, cPtrArr[i])
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|