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 <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Trust: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
Changkun Ou 2021-02-23 09:58:14 +01:00 committed by Ian Lance Taylor
parent b084073b53
commit 972e883925
8 changed files with 259 additions and 1 deletions

View file

@ -120,6 +120,15 @@ Do not send CLs removing the interior tags from such phrases.
has no effect. has no effect.
</p> </p>
<h3 id="runtime/cgo"><a href="/pkg/runtime/cgo">Cgo</a></h3>
<p>
The <a href="/pkg/runtime/cgo">runtime/cgo</a> package now provides a
new facility that allows to turn any Go values to a safe representation
that can be used to pass values between C and Go safely. See
<a href="/pkg/runtime/cgo#Handle">runtime/cgo.Handle</a> for more information.
</p>
<h3 id="minor_library_changes">Minor changes to the library</h3> <h3 id="minor_library_changes">Minor changes to the library</h3>
<p> <p>

View file

@ -80,6 +80,7 @@ func TestNamedEnum(t *testing.T) { testNamedEnum(t) }
func TestCastToEnum(t *testing.T) { testCastToEnum(t) } func TestCastToEnum(t *testing.T) { testCastToEnum(t) }
func TestErrno(t *testing.T) { testErrno(t) } func TestErrno(t *testing.T) { testErrno(t) }
func TestFpVar(t *testing.T) { testFpVar(t) } func TestFpVar(t *testing.T) { testFpVar(t) }
func TestHandle(t *testing.T) { testHandle(t) }
func TestHelpers(t *testing.T) { testHelpers(t) } func TestHelpers(t *testing.T) { testHelpers(t) }
func TestLibgcc(t *testing.T) { testLibgcc(t) } func TestLibgcc(t *testing.T) { testLibgcc(t) }
func TestMultipleAssign(t *testing.T) { testMultipleAssign(t) } func TestMultipleAssign(t *testing.T) { testMultipleAssign(t) }

View file

@ -899,6 +899,10 @@ static uint16_t issue31093F(uint16_t v) { return v; }
// issue 32579 // issue 32579
typedef struct S32579 { unsigned char data[1]; } S32579; typedef struct S32579 { unsigned char data[1]; } S32579;
// issue 37033, cgo.Handle
extern void GoFunc37033(uintptr_t handle);
void cFunc37033(uintptr_t handle) { GoFunc37033(handle); }
// issue 38649 // issue 38649
// Test that #define'd type aliases work. // Test that #define'd type aliases work.
#define netbsd_gid unsigned int #define netbsd_gid unsigned int
@ -920,6 +924,7 @@ import (
"os/signal" "os/signal"
"reflect" "reflect"
"runtime" "runtime"
"runtime/cgo"
"sync" "sync"
"syscall" "syscall"
"testing" "testing"
@ -2230,6 +2235,23 @@ func test32579(t *testing.T) {
} }
} }
// issue 37033, check if cgo.Handle works properly
func testHandle(t *testing.T) {
ch := make(chan int)
for i := 0; i < 42; i++ {
h := cgo.NewHandle(ch)
go func() {
C.cFunc37033(C.uintptr_t(h))
}()
if v := <-ch; issue37033 != v {
t.Fatalf("unexpected receiving value: got %d, want %d", v, issue37033)
}
h.Delete()
}
}
// issue 38649 // issue 38649
var issue38649 C.netbsd_gid = 42 var issue38649 C.netbsd_gid = 42

View file

@ -12,6 +12,7 @@ package cgotest
import ( import (
"runtime" "runtime"
"runtime/cgo"
"runtime/debug" "runtime/debug"
"strings" "strings"
"sync" "sync"
@ -558,6 +559,17 @@ func test31891(t *testing.T) {
C.callIssue31891() C.callIssue31891()
} }
// issue 37033, check if cgo.Handle works properly
var issue37033 = 42
//export GoFunc37033
func GoFunc37033(handle C.uintptr_t) {
h := cgo.Handle(handle)
ch := h.Value().(chan int)
ch <- issue37033
}
// issue 38408 // issue 38408
// A typedef pointer can be used as the element type. // A typedef pointer can be used as the element type.
// No runtime test; just make sure it compiles. // No runtime test; just make sure it compiles.

View file

@ -387,6 +387,9 @@ and of course there is nothing stopping the C code from doing anything
it likes. However, programs that break these rules are likely to fail it likes. However, programs that break these rules are likely to fail
in unexpected and unpredictable ways. in unexpected and unpredictable ways.
The runtime/cgo.Handle type can be used to safely pass Go values
between Go and C. See the runtime/cgo package documentation for details.
Note: the current implementation has a bug. While Go code is permitted Note: the current implementation has a bug. While Go code is permitted
to write nil or a C pointer (but not a Go pointer) to C memory, the to write nil or a C pointer (but not a Go pointer) to C memory, the
current implementation may sometimes cause a runtime error if the current implementation may sometimes cause a runtime error if the

View file

@ -556,7 +556,12 @@ func (ctxt *Link) loadlib() {
if ctxt.BuildMode == BuildModeShared || ctxt.linkShared { if ctxt.BuildMode == BuildModeShared || ctxt.linkShared {
Exitf("cannot implicitly include runtime/cgo in a shared library") Exitf("cannot implicitly include runtime/cgo in a shared library")
} }
loadobjfile(ctxt, lib) for ; i < len(ctxt.Library); i++ {
lib := ctxt.Library[i]
if lib.Shlib == "" {
loadobjfile(ctxt, lib)
}
}
} }
} }

103
src/runtime/cgo/handle.go Normal file
View file

@ -0,0 +1,103 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cgo
import (
"sync"
"sync/atomic"
)
// Handle provides a safe representation of Go values to pass between
// Go and C. The zero value of a handle is not a valid handle, and thus
// is safe to use as a sentinel in C APIs.
//
// The underlying type of Handle is guaranteed to fit in an integer type
// that is large enough to hold the bit pattern of any pointer.
// For instance, on the Go side:
//
// package main
//
// /*
// #include <stdint.h> // for uintptr_t
//
// extern void MyGoPrint(uintptr_t handle);
// void myprint(uintptr_t handle);
// */
// import "C"
// import "runtime/cgo"
//
// //export MyGoPrint
// func MyGoPrint(handle C.uintptr_t) {
// h := cgo.Handle(handle)
// val := h.Value().(int)
// println(val)
// h.Delete()
// }
//
// func main() {
// val := 42
// C.myprint(C.uintptr_t(cgo.NewHandle(val)))
// // Output: 42
// }
//
// and on the C side:
//
// #include <stdint.h> // for uintptr_t
//
// // A Go function
// extern void MyGoPrint(uintptr_t handle);
//
// // A C function
// void myprint(uintptr_t handle) {
// MyGoPrint(handle);
// }
type Handle uintptr
// NewHandle returns a handle for a given value.
//
// The handle is valid until the program calls Delete on it. The handle
// uses resources, and this package assumes that C code may hold on to
// the handle, so a program must explicitly call Delete when the handle
// is no longer needed.
//
// The intended use is to pass the returned handle to C code, which
// passes it back to Go, which calls Value.
func NewHandle(v interface{}) Handle {
h := atomic.AddUintptr(&handleIdx, 1)
if h == 0 {
panic("runtime/cgo: ran out of handle space")
}
handles.Store(h, v)
return Handle(h)
}
// Value returns the associated Go value for a valid handle.
//
// The method panics if the handle is invalid.
func (h Handle) Value() interface{} {
v, ok := handles.Load(uintptr(h))
if !ok {
panic("runtime/cgo: misuse of an invalid Handle")
}
return v
}
// Delete invalidates a handle. This method should only be called once
// the program no longer needs to pass the handle to C and the C code
// no longer has a copy of the handle value.
//
// The method panics if the handle is invalid.
func (h Handle) Delete() {
_, ok := handles.LoadAndDelete(uintptr(h))
if !ok {
panic("runtime/cgo: misuse of an invalid Handle")
}
}
var (
handles = sync.Map{} // map[Handle]interface{}
handleIdx uintptr // atomic
)

View file

@ -0,0 +1,103 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cgo
import (
"reflect"
"testing"
)
func TestHandle(t *testing.T) {
v := 42
tests := []struct {
v1 interface{}
v2 interface{}
}{
{v1: v, v2: v},
{v1: &v, v2: &v},
{v1: nil, v2: nil},
}
for _, tt := range tests {
h1 := NewHandle(tt.v1)
h2 := NewHandle(tt.v2)
if uintptr(h1) == 0 || uintptr(h2) == 0 {
t.Fatalf("NewHandle returns zero")
}
if uintptr(h1) == uintptr(h2) {
t.Fatalf("Duplicated Go values should have different handles, but got equal")
}
h1v := h1.Value()
h2v := h2.Value()
if !reflect.DeepEqual(h1v, h2v) || !reflect.DeepEqual(h1v, tt.v1) {
t.Fatalf("Value of a Handle got wrong, got %+v %+v, want %+v", h1v, h2v, tt.v1)
}
h1.Delete()
h2.Delete()
}
siz := 0
handles.Range(func(k, v interface{}) bool {
siz++
return true
})
if siz != 0 {
t.Fatalf("handles are not cleared, got %d, want %d", siz, 0)
}
}
func TestInvalidHandle(t *testing.T) {
t.Run("zero", func(t *testing.T) {
h := Handle(0)
defer func() {
if r := recover(); r != nil {
return
}
t.Fatalf("Delete of zero handle did not trigger a panic")
}()
h.Delete()
})
t.Run("invalid", func(t *testing.T) {
h := NewHandle(42)
defer func() {
if r := recover(); r != nil {
h.Delete()
return
}
t.Fatalf("Invalid handle did not trigger a panic")
}()
Handle(h + 1).Delete()
})
}
func BenchmarkHandle(b *testing.B) {
b.Run("non-concurrent", func(b *testing.B) {
for i := 0; i < b.N; i++ {
h := NewHandle(i)
_ = h.Value()
h.Delete()
}
})
b.Run("concurrent", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
var v int
for pb.Next() {
h := NewHandle(v)
_ = h.Value()
h.Delete()
}
})
})
}