Skip to content

Commit 98fd1a5

Browse files
jasondellalucepoiana
authored andcommitted
fix(pkg/cgo): solve data races when assigning/deleting and accessing handles
Signed-off-by: Jason Dellaluce <[email protected]>
1 parent 2eb4a31 commit 98fd1a5

File tree

2 files changed

+47
-17
lines changed

2 files changed

+47
-17
lines changed

pkg/cgo/handle.go

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ limitations under the License.
1717

1818
package cgo
1919

20+
import (
21+
"fmt"
22+
"sync/atomic"
23+
"unsafe"
24+
)
25+
2026
// Handle is an alternative implementation of cgo.Handle introduced by
2127
// Go 1.17, see https://pkg.go.dev/runtime/cgo. This implementation
2228
// optimizes performance in use cases related to plugins. It is intended
@@ -43,12 +49,18 @@ package cgo
4349
// The usage in other contexts is discuraged.
4450
type Handle uintptr
4551

46-
// MaxHandle is the largest value that an Handle can hold
47-
const MaxHandle = 256 - 1
52+
const (
53+
// MaxHandle is the largest value that an Handle can hold
54+
MaxHandle = 256 - 1
55+
56+
// max number of times we're willing to iterate over the vector of reusable
57+
// handles to do compare-and-swap before giving up
58+
maxNewHandleRounds = 20
59+
)
4860

4961
var (
50-
handles [MaxHandle + 1]interface{} // [int]interface{}
51-
noHandle int = 0
62+
handles [MaxHandle + 1]unsafe.Pointer // [int]*interface{}
63+
noHandle unsafe.Pointer = nil
5264
)
5365

5466
func init() {
@@ -72,24 +84,41 @@ func init() {
7284
//
7385
// This function is not thread-safe.
7486
func NewHandle(v interface{}) Handle {
75-
for i := 1; i <= MaxHandle; i++ {
76-
if handles[i] == &noHandle {
77-
handles[i] = v
78-
return Handle(i)
87+
rounds := 0
88+
for h := uintptr(1); ; h++ {
89+
// we acquired ownership of an handle, return it
90+
// note: we attempt accessing slots 1..MaxHandle (included)
91+
if atomic.CompareAndSwapPointer(&handles[h], noHandle, (unsafe.Pointer)(&v)) {
92+
return Handle(h)
7993
}
94+
95+
// we haven't acquired a handle, but we can try with the next one
96+
if h < MaxHandle {
97+
continue
98+
}
99+
100+
// we iterated over the whole vector of handles, so we get back to start
101+
// and try again with another round. Once we do this too many times,
102+
// we have no choice if not panic-ing
103+
h = uintptr(0) // note: will be incremented when continuing
104+
if rounds < maxNewHandleRounds {
105+
rounds++
106+
continue
107+
}
108+
109+
panic(fmt.Sprintf("plugin-sdk-go/cgo: could not obtain a new handle after round #%d", rounds))
80110
}
81-
panic("plugin-sdk-go/cgo: ran out of handle space")
82111
}
83112

84113
// Value returns the associated Go value for a valid handle.
85114
//
86115
// The method panics if the handle is invalid.
87116
// This function is not thread-safe.
88117
func (h Handle) Value() interface{} {
89-
if h > MaxHandle || handles[h] == &noHandle {
90-
panic("plugin-sdk-go/cgo: misuse of an invalid Handle")
118+
if h > MaxHandle || atomic.LoadPointer(&handles[h]) == noHandle {
119+
panic(fmt.Sprintf("plugin-sdk-go/cgo: misuse (value) of an invalid Handle %d", h))
91120
}
92-
return handles[h]
121+
return *(*interface{})(atomic.LoadPointer(&handles[h]))
93122
}
94123

95124
// Delete invalidates a handle. This method should only be called once
@@ -99,14 +128,14 @@ func (h Handle) Value() interface{} {
99128
// The method panics if the handle is invalid.
100129
// This function is not thread-safe.
101130
func (h Handle) Delete() {
102-
if h > MaxHandle || handles[h] == &noHandle {
103-
panic("plugin-sdk-go/cgo: misuse of an invalid Handle")
131+
if h > MaxHandle || atomic.LoadPointer(&handles[h]) == noHandle {
132+
panic(fmt.Sprintf("plugin-sdk-go/cgo: misuse (delete) of an invalid Handle %d", h))
104133
}
105-
handles[h] = &noHandle
134+
atomic.StorePointer(&handles[h], noHandle)
106135
}
107136

108137
func resetHandles() {
109138
for i := 0; i <= MaxHandle; i++ {
110-
handles[i] = &noHandle
139+
atomic.StorePointer(&handles[i], noHandle)
111140
}
112141
}

pkg/cgo/handle_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package cgo
1919

2020
import (
2121
"reflect"
22+
"sync/atomic"
2223
"testing"
2324
)
2425

@@ -61,7 +62,7 @@ func TestHandle(t *testing.T) {
6162

6263
siz := 0
6364
for i := 0; i < MaxHandle; i++ {
64-
if handles[i] != &noHandle {
65+
if atomic.LoadPointer(&handles[i]) != noHandle {
6566
siz++
6667
}
6768
}

0 commit comments

Comments
 (0)