Skip to content

Commit 972e883

Browse files
changkunianlancetaylor
authored andcommitted
runtime/cgo: add Handle for managing (c)go pointers
A non-trivial Cgo program may need to use callbacks and interact with go objects per goroutine. Because of the rules for passing pointers between Go and C, such a program needs to store handles to associated Go values. This often causes much extra effort to figure out a way to correctly deal with: 1) map collision; 2) identifying leaks and 3) concurrency. This CL implements a Handle representation in runtime/cgo package, and related methods such as Value, Delete, etc. which allows Go users can use a standard way to handle the above difficulties. In addition, the CL allows a Go value to have multiple handles, and the NewHandle always returns a different handle compare to the previously returned handles. In comparison, CL 294670 implements a different behavior of NewHandle that returns a unique handle when the Go value is referring to the same object. Benchmark: name time/op Handle/non-concurrent-16 487ns ± 1% Handle/concurrent-16 674ns ± 1% Fixes #37033 Change-Id: I0eadb9d44332fffef8fb567c745246a49dd6d4c1 Reviewed-on: https://go-review.googlesource.com/c/go/+/295369 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Trust: Cherry Zhang <[email protected]>
1 parent b084073 commit 972e883

File tree

8 files changed

+259
-1
lines changed

8 files changed

+259
-1
lines changed

doc/go1.17.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ <h3 id="crypto/tls"><a href="/pkg/crypto/tls">crypto/tls</a></h3>
120120
has no effect.
121121
</p>
122122

123+
<h3 id="runtime/cgo"><a href="/pkg/runtime/cgo">Cgo</a></h3>
124+
125+
<p>
126+
The <a href="/pkg/runtime/cgo">runtime/cgo</a> package now provides a
127+
new facility that allows to turn any Go values to a safe representation
128+
that can be used to pass values between C and Go safely. See
129+
<a href="/pkg/runtime/cgo#Handle">runtime/cgo.Handle</a> for more information.
130+
</p>
131+
123132
<h3 id="minor_library_changes">Minor changes to the library</h3>
124133

125134
<p>

misc/cgo/test/cgo_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func TestNamedEnum(t *testing.T) { testNamedEnum(t) }
8080
func TestCastToEnum(t *testing.T) { testCastToEnum(t) }
8181
func TestErrno(t *testing.T) { testErrno(t) }
8282
func TestFpVar(t *testing.T) { testFpVar(t) }
83+
func TestHandle(t *testing.T) { testHandle(t) }
8384
func TestHelpers(t *testing.T) { testHelpers(t) }
8485
func TestLibgcc(t *testing.T) { testLibgcc(t) }
8586
func TestMultipleAssign(t *testing.T) { testMultipleAssign(t) }

misc/cgo/test/test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,10 @@ static uint16_t issue31093F(uint16_t v) { return v; }
899899
// issue 32579
900900
typedef struct S32579 { unsigned char data[1]; } S32579;
901901
902+
// issue 37033, cgo.Handle
903+
extern void GoFunc37033(uintptr_t handle);
904+
void cFunc37033(uintptr_t handle) { GoFunc37033(handle); }
905+
902906
// issue 38649
903907
// Test that #define'd type aliases work.
904908
#define netbsd_gid unsigned int
@@ -920,6 +924,7 @@ import (
920924
"os/signal"
921925
"reflect"
922926
"runtime"
927+
"runtime/cgo"
923928
"sync"
924929
"syscall"
925930
"testing"
@@ -2230,6 +2235,23 @@ func test32579(t *testing.T) {
22302235
}
22312236
}
22322237

2238+
// issue 37033, check if cgo.Handle works properly
2239+
2240+
func testHandle(t *testing.T) {
2241+
ch := make(chan int)
2242+
2243+
for i := 0; i < 42; i++ {
2244+
h := cgo.NewHandle(ch)
2245+
go func() {
2246+
C.cFunc37033(C.uintptr_t(h))
2247+
}()
2248+
if v := <-ch; issue37033 != v {
2249+
t.Fatalf("unexpected receiving value: got %d, want %d", v, issue37033)
2250+
}
2251+
h.Delete()
2252+
}
2253+
}
2254+
22332255
// issue 38649
22342256

22352257
var issue38649 C.netbsd_gid = 42

misc/cgo/test/testx.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package cgotest
1212

1313
import (
1414
"runtime"
15+
"runtime/cgo"
1516
"runtime/debug"
1617
"strings"
1718
"sync"
@@ -558,6 +559,17 @@ func test31891(t *testing.T) {
558559
C.callIssue31891()
559560
}
560561

562+
// issue 37033, check if cgo.Handle works properly
563+
564+
var issue37033 = 42
565+
566+
//export GoFunc37033
567+
func GoFunc37033(handle C.uintptr_t) {
568+
h := cgo.Handle(handle)
569+
ch := h.Value().(chan int)
570+
ch <- issue37033
571+
}
572+
561573
// issue 38408
562574
// A typedef pointer can be used as the element type.
563575
// No runtime test; just make sure it compiles.

src/cmd/cgo/doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,9 @@ and of course there is nothing stopping the C code from doing anything
387387
it likes. However, programs that break these rules are likely to fail
388388
in unexpected and unpredictable ways.
389389
390+
The runtime/cgo.Handle type can be used to safely pass Go values
391+
between Go and C. See the runtime/cgo package documentation for details.
392+
390393
Note: the current implementation has a bug. While Go code is permitted
391394
to write nil or a C pointer (but not a Go pointer) to C memory, the
392395
current implementation may sometimes cause a runtime error if the

src/cmd/link/internal/ld/lib.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,12 @@ func (ctxt *Link) loadlib() {
556556
if ctxt.BuildMode == BuildModeShared || ctxt.linkShared {
557557
Exitf("cannot implicitly include runtime/cgo in a shared library")
558558
}
559-
loadobjfile(ctxt, lib)
559+
for ; i < len(ctxt.Library); i++ {
560+
lib := ctxt.Library[i]
561+
if lib.Shlib == "" {
562+
loadobjfile(ctxt, lib)
563+
}
564+
}
560565
}
561566
}
562567

src/runtime/cgo/handle.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cgo
6+
7+
import (
8+
"sync"
9+
"sync/atomic"
10+
)
11+
12+
// Handle provides a safe representation of Go values to pass between
13+
// Go and C. The zero value of a handle is not a valid handle, and thus
14+
// is safe to use as a sentinel in C APIs.
15+
//
16+
// The underlying type of Handle is guaranteed to fit in an integer type
17+
// that is large enough to hold the bit pattern of any pointer.
18+
// For instance, on the Go side:
19+
//
20+
// package main
21+
//
22+
// /*
23+
// #include <stdint.h> // for uintptr_t
24+
//
25+
// extern void MyGoPrint(uintptr_t handle);
26+
// void myprint(uintptr_t handle);
27+
// */
28+
// import "C"
29+
// import "runtime/cgo"
30+
//
31+
// //export MyGoPrint
32+
// func MyGoPrint(handle C.uintptr_t) {
33+
// h := cgo.Handle(handle)
34+
// val := h.Value().(int)
35+
// println(val)
36+
// h.Delete()
37+
// }
38+
//
39+
// func main() {
40+
// val := 42
41+
// C.myprint(C.uintptr_t(cgo.NewHandle(val)))
42+
// // Output: 42
43+
// }
44+
//
45+
// and on the C side:
46+
//
47+
// #include <stdint.h> // for uintptr_t
48+
//
49+
// // A Go function
50+
// extern void MyGoPrint(uintptr_t handle);
51+
//
52+
// // A C function
53+
// void myprint(uintptr_t handle) {
54+
// MyGoPrint(handle);
55+
// }
56+
type Handle uintptr
57+
58+
// NewHandle returns a handle for a given value.
59+
//
60+
// The handle is valid until the program calls Delete on it. The handle
61+
// uses resources, and this package assumes that C code may hold on to
62+
// the handle, so a program must explicitly call Delete when the handle
63+
// is no longer needed.
64+
//
65+
// The intended use is to pass the returned handle to C code, which
66+
// passes it back to Go, which calls Value.
67+
func NewHandle(v interface{}) Handle {
68+
h := atomic.AddUintptr(&handleIdx, 1)
69+
if h == 0 {
70+
panic("runtime/cgo: ran out of handle space")
71+
}
72+
73+
handles.Store(h, v)
74+
return Handle(h)
75+
}
76+
77+
// Value returns the associated Go value for a valid handle.
78+
//
79+
// The method panics if the handle is invalid.
80+
func (h Handle) Value() interface{} {
81+
v, ok := handles.Load(uintptr(h))
82+
if !ok {
83+
panic("runtime/cgo: misuse of an invalid Handle")
84+
}
85+
return v
86+
}
87+
88+
// Delete invalidates a handle. This method should only be called once
89+
// the program no longer needs to pass the handle to C and the C code
90+
// no longer has a copy of the handle value.
91+
//
92+
// The method panics if the handle is invalid.
93+
func (h Handle) Delete() {
94+
_, ok := handles.LoadAndDelete(uintptr(h))
95+
if !ok {
96+
panic("runtime/cgo: misuse of an invalid Handle")
97+
}
98+
}
99+
100+
var (
101+
handles = sync.Map{} // map[Handle]interface{}
102+
handleIdx uintptr // atomic
103+
)

src/runtime/cgo/handle_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cgo
6+
7+
import (
8+
"reflect"
9+
"testing"
10+
)
11+
12+
func TestHandle(t *testing.T) {
13+
v := 42
14+
15+
tests := []struct {
16+
v1 interface{}
17+
v2 interface{}
18+
}{
19+
{v1: v, v2: v},
20+
{v1: &v, v2: &v},
21+
{v1: nil, v2: nil},
22+
}
23+
24+
for _, tt := range tests {
25+
h1 := NewHandle(tt.v1)
26+
h2 := NewHandle(tt.v2)
27+
28+
if uintptr(h1) == 0 || uintptr(h2) == 0 {
29+
t.Fatalf("NewHandle returns zero")
30+
}
31+
32+
if uintptr(h1) == uintptr(h2) {
33+
t.Fatalf("Duplicated Go values should have different handles, but got equal")
34+
}
35+
36+
h1v := h1.Value()
37+
h2v := h2.Value()
38+
if !reflect.DeepEqual(h1v, h2v) || !reflect.DeepEqual(h1v, tt.v1) {
39+
t.Fatalf("Value of a Handle got wrong, got %+v %+v, want %+v", h1v, h2v, tt.v1)
40+
}
41+
42+
h1.Delete()
43+
h2.Delete()
44+
}
45+
46+
siz := 0
47+
handles.Range(func(k, v interface{}) bool {
48+
siz++
49+
return true
50+
})
51+
if siz != 0 {
52+
t.Fatalf("handles are not cleared, got %d, want %d", siz, 0)
53+
}
54+
}
55+
56+
func TestInvalidHandle(t *testing.T) {
57+
t.Run("zero", func(t *testing.T) {
58+
h := Handle(0)
59+
60+
defer func() {
61+
if r := recover(); r != nil {
62+
return
63+
}
64+
t.Fatalf("Delete of zero handle did not trigger a panic")
65+
}()
66+
67+
h.Delete()
68+
})
69+
70+
t.Run("invalid", func(t *testing.T) {
71+
h := NewHandle(42)
72+
73+
defer func() {
74+
if r := recover(); r != nil {
75+
h.Delete()
76+
return
77+
}
78+
t.Fatalf("Invalid handle did not trigger a panic")
79+
}()
80+
81+
Handle(h + 1).Delete()
82+
})
83+
}
84+
85+
func BenchmarkHandle(b *testing.B) {
86+
b.Run("non-concurrent", func(b *testing.B) {
87+
for i := 0; i < b.N; i++ {
88+
h := NewHandle(i)
89+
_ = h.Value()
90+
h.Delete()
91+
}
92+
})
93+
b.Run("concurrent", func(b *testing.B) {
94+
b.RunParallel(func(pb *testing.PB) {
95+
var v int
96+
for pb.Next() {
97+
h := NewHandle(v)
98+
_ = h.Value()
99+
h.Delete()
100+
}
101+
})
102+
})
103+
}

0 commit comments

Comments
 (0)