diff --git a/content/assets/cgo-handle/cgo1/bench-2021-06-10-19:57:43.txt b/content/assets/cgo-handle/cgo1/bench-2021-06-10-19:57:43.txt new file mode 100644 index 0000000..54ea8e2 --- /dev/null +++ b/content/assets/cgo-handle/cgo1/bench-2021-06-10-19:57:43.txt @@ -0,0 +1,25 @@ +goos: darwin +goarch: arm64 +pkg: cgo-handle/cgo1 +BenchmarkHandle/non-concurrent-8 2822158 405.0 ns/op +BenchmarkHandle/non-concurrent-8 2935992 409.3 ns/op +BenchmarkHandle/non-concurrent-8 2932722 417.4 ns/op +BenchmarkHandle/non-concurrent-8 2917302 409.5 ns/op +BenchmarkHandle/non-concurrent-8 2921462 409.1 ns/op +BenchmarkHandle/non-concurrent-8 2953590 407.4 ns/op +BenchmarkHandle/non-concurrent-8 2948713 407.0 ns/op +BenchmarkHandle/non-concurrent-8 2955361 405.4 ns/op +BenchmarkHandle/non-concurrent-8 2833503 419.7 ns/op +BenchmarkHandle/non-concurrent-8 2925784 405.4 ns/op +BenchmarkHandle/concurrent-8 1562476 767.6 ns/op +BenchmarkHandle/concurrent-8 1561358 767.9 ns/op +BenchmarkHandle/concurrent-8 1560795 769.4 ns/op +BenchmarkHandle/concurrent-8 1563916 768.8 ns/op +BenchmarkHandle/concurrent-8 1518699 768.2 ns/op +BenchmarkHandle/concurrent-8 1559850 769.1 ns/op +BenchmarkHandle/concurrent-8 1557451 768.2 ns/op +BenchmarkHandle/concurrent-8 1560009 777.5 ns/op +BenchmarkHandle/concurrent-8 1510149 766.8 ns/op +BenchmarkHandle/concurrent-8 1500530 767.7 ns/op +PASS +ok cgo-handle/cgo1 36.071s diff --git a/content/assets/cgo-handle/cgo1/cgo.go b/content/assets/cgo-handle/cgo1/cgo.go new file mode 100644 index 0000000..37a67c4 --- /dev/null +++ b/content/assets/cgo-handle/cgo1/cgo.go @@ -0,0 +1,160 @@ +// Copyright 2021 The golang.design Initiative Authors. +// All rights reserved. Use of this source code is governed +// by a MIT license that can be found in the LICENSE file. +// +// Written by Changkun Ou + +// Package cgo is an implementation of golang.org/issue/37033. +// +// See golang.org/cl/294670 for code review discussion. +package cgo1 + +import ( + "reflect" + "sync" +) + +// Handle provides a way to pass values that contain Go pointers +// (pointers to memory allocated by Go) between Go and C without +// breaking the cgo pointer passing rules. A Handle is an integer +// value that can represent any Go value. A Handle can be passed +// through C and back to Go, and Go code can use the Handle to +// retrieve the original Go value. +// +// 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. The zero +// value of a Handle is not valid, and thus is safe to use as a sentinel +// in C APIs. +// +// For instance, on the Go side: +// +// package main +// +// /* +// #include // 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().(string) +// println(val) +// h.Delete() +// } +// +// func main() { +// val := "hello Go" +// C.myprint(C.uintptr_t(cgo.NewHandle(val))) +// // Output: hello Go +// } +// +// and on the C side: +// +// #include // 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 { + var k uintptr + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Ptr, reflect.UnsafePointer, reflect.Slice, + reflect.Map, reflect.Chan, reflect.Func: + if rv.IsNil() { + panic("cgo: cannot use Handle for nil value") + } + + k = rv.Pointer() + default: + // Wrap and turn a value parameter into a pointer. This enables + // us to always store the passing object as a pointer, and helps + // to identify which of whose are initially pointers or values + // when Value is called. + v = &wrap{v} + k = reflect.ValueOf(v).Pointer() + } + + // v was escaped to the heap because of reflection. As Go do not have + // a moving GC (and possibly lasts true for a long future), it is + // safe to use its pointer address as the key of the global map at + // this moment. The implementation must be reconsidered if moving GC + // is introduced internally in the runtime. + actual, loaded := m.LoadOrStore(k, v) + if !loaded { + return Handle(k) + } + + arv := reflect.ValueOf(actual) + switch arv.Kind() { + case reflect.Ptr, reflect.UnsafePointer, reflect.Slice, + reflect.Map, reflect.Chan, reflect.Func: + // The underlying object of the given Go value already have + // its existing handle. + if arv.Pointer() == k { + return Handle(k) + } + + // If the loaded pointer is inconsistent with the new pointer, + // it means the address has been used for different objects + // because of GC and its address is reused for a new Go object, + // meaning that the Handle does not call Delete explicitly when + // the old Go value is not needed. Consider this as a misuse of + // a handle, do panic. + panic("cgo: misuse of a Handle") + default: + panic("cgo: Handle implementation has an internal bug") + } +} + +// 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 := m.Load(uintptr(h)) + if !ok { + panic("cgo: misuse of an invalid Handle") + } + if wv, ok := v.(*wrap); ok { + return wv.v + } + 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 := m.LoadAndDelete(uintptr(h)) + if !ok { + panic("cgo: misuse of an invalid Handle") + } +} + +var m = &sync.Map{} // map[uintptr]interface{} + +// wrap wraps a Go value. +type wrap struct{ v interface{} } diff --git a/content/assets/cgo-handle/cgo1/cgo_test.go b/content/assets/cgo-handle/cgo1/cgo_test.go new file mode 100644 index 0000000..7829f52 --- /dev/null +++ b/content/assets/cgo-handle/cgo1/cgo_test.go @@ -0,0 +1,149 @@ +// Copyright 2021 The golang.design Initiative Authors. +// All rights reserved. Use of this source code is governed +// by a MIT license that can be found in the LICENSE file. +// +// Written by Changkun Ou + +package cgo1 + +import ( + "testing" +) + +func TestValueHandle(t *testing.T) { + v := 42 + + h1 := NewHandle(v) + h2 := NewHandle(v) + + if uintptr(h1) == uintptr(h2) { + t.Fatalf("duplicated Go values should have different handles") + } + + h1v := h1.Value().(int) + h2v := h2.Value().(int) + if h1v != h2v { + t.Fatalf("the Value of duplicated Go values are different: want %d, got %d", h1v, h2v) + } + if h1v != v { + t.Fatalf("the Value of a handle does not match origin: want %v, got %v", v, h1v) + } + + h1.Delete() + h2.Delete() + + siz := 0 + m.Range(func(k, v interface{}) bool { + siz++ + return true + }) + if siz != 0 { + t.Fatalf("handles are not deleted, want: %d, got %d", 0, siz) + } +} + +func TestPointerHandle(t *testing.T) { + v := 42 + + p1 := &v + p2 := &v + + h1 := NewHandle(p1) + h2 := NewHandle(p2) + + if uintptr(h1) != uintptr(h2) { + t.Fatalf("pointers to the same value should have same handle") + } + + h1v := h1.Value().(*int) + h2v := h2.Value().(*int) + if h1v != h2v { + t.Fatalf("the Value of a handle does not match origin: want %v, got %v", v, h1v) + } + + h1.Delete() + + siz := 0 + m.Range(func(k, v interface{}) bool { + siz++ + return true + }) + if siz != 0 { + t.Fatalf("handles are not deleted: want %d, got %d", 0, siz) + } + + defer func() { + if r := recover(); r != nil { + return + } + t.Fatalf("double Delete on a same handle did not trigger a panic") + }() + + h2.Delete() +} + +func TestNilHandle(t *testing.T) { + var v *int + + defer func() { + if r := recover(); r != nil { + return + } + t.Fatalf("nil should not be created as a handle successfully") + }() + + _ = NewHandle(v) +} + +func f1() {} +func f2() {} + +type foo struct{} + +func (f *foo) bar() {} +func (f *foo) wow() {} + +func TestFuncHandle(t *testing.T) { + h1 := NewHandle(f1) + h2 := NewHandle(f2) + h3 := NewHandle(f2) + + if h1 == h2 { + t.Fatalf("different functions should have different handles") + } + if h2 != h3 { + t.Fatalf("same functions should have same handles") + } + + f := foo{} + h4 := NewHandle(f.bar) + h5 := NewHandle(f.bar) + h6 := NewHandle(f.wow) + + if h4 != h5 { + t.Fatalf("same methods should have same handles") + } + + if h5 == h6 { + t.Fatalf("different methods should have different handles") + } +} +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() + } + }) + }) +} \ No newline at end of file diff --git a/content/assets/cgo-handle/cgo2/bench-2021-06-10-19:56:57.txt b/content/assets/cgo-handle/cgo2/bench-2021-06-10-19:56:57.txt new file mode 100644 index 0000000..7618888 --- /dev/null +++ b/content/assets/cgo-handle/cgo2/bench-2021-06-10-19:56:57.txt @@ -0,0 +1,25 @@ +goos: darwin +goarch: arm64 +pkg: cgo-handle/cgo2 +BenchmarkHandle/non-concurrent-8 2972786 400.8 ns/op +BenchmarkHandle/non-concurrent-8 3040192 390.4 ns/op +BenchmarkHandle/non-concurrent-8 3058515 391.1 ns/op +BenchmarkHandle/non-concurrent-8 3036595 391.0 ns/op +BenchmarkHandle/non-concurrent-8 3050779 392.0 ns/op +BenchmarkHandle/non-concurrent-8 3042757 389.9 ns/op +BenchmarkHandle/non-concurrent-8 3020358 404.6 ns/op +BenchmarkHandle/non-concurrent-8 3008872 396.0 ns/op +BenchmarkHandle/non-concurrent-8 3024003 392.7 ns/op +BenchmarkHandle/non-concurrent-8 3051001 392.9 ns/op +BenchmarkHandle/concurrent-8 1586451 755.9 ns/op +BenchmarkHandle/concurrent-8 1588989 754.7 ns/op +BenchmarkHandle/concurrent-8 1543646 757.2 ns/op +BenchmarkHandle/concurrent-8 1586863 755.3 ns/op +BenchmarkHandle/concurrent-8 1584315 758.5 ns/op +BenchmarkHandle/concurrent-8 1584865 759.3 ns/op +BenchmarkHandle/concurrent-8 1575646 769.2 ns/op +BenchmarkHandle/concurrent-8 1496425 760.6 ns/op +BenchmarkHandle/concurrent-8 1572453 759.0 ns/op +BenchmarkHandle/concurrent-8 1585522 779.9 ns/op +PASS +ok cgo-handle/cgo2 35.840s diff --git a/content/assets/cgo-handle/cgo2/cgo.go b/content/assets/cgo-handle/cgo2/cgo.go new file mode 100644 index 0000000..9ff0cfb --- /dev/null +++ b/content/assets/cgo-handle/cgo2/cgo.go @@ -0,0 +1,114 @@ +// Copyright 2021 The golang.design Initiative Authors. +// All rights reserved. Use of this source code is governed +// by a MIT license that can be found in the LICENSE file. +// +// Written by Changkun Ou + +// Package cgo is an implementation of golang.org/issue/37033. +// +// See golang.org/cl/295369 for code review discussion. +package cgo2 + +import ( + "sync" + "sync/atomic" +) + +// Handle provides a way to pass values that contain Go pointers +// (pointers to memory allocated by Go) between Go and C without +// breaking the cgo pointer passing rules. A Handle is an integer +// value that can represent any Go value. A Handle can be passed +// through C and back to Go, and Go code can use the Handle to +// retrieve the original Go value. +// +// 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. The zero +// value of a Handle is not valid, and thus is safe to use as a sentinel +// in C APIs. +// +// For instance, on the Go side: +// +// package main +// +// /* +// #include // 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().(string) +// println(val) +// h.Delete() +// } +// +// func main() { +// val := "hello Go" +// C.myprint(C.uintptr_t(cgo.NewHandle(val))) +// // Output: hello Go +// } +// +// and on the C side: +// +// #include // 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 +) \ No newline at end of file diff --git a/content/assets/cgo-handle/cgo2/cgo_test.go b/content/assets/cgo-handle/cgo2/cgo_test.go new file mode 100644 index 0000000..7307c33 --- /dev/null +++ b/content/assets/cgo-handle/cgo2/cgo_test.go @@ -0,0 +1,105 @@ +// Copyright 2021 The golang.design Initiative Authors. +// All rights reserved. Use of this source code is governed +// by a MIT license that can be found in the LICENSE file. +// +// Written by Changkun Ou + +package cgo2 + +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() + } + }) + }) +} diff --git a/content/assets/cgo-handle/go.mod b/content/assets/cgo-handle/go.mod new file mode 100644 index 0000000..2e39b39 --- /dev/null +++ b/content/assets/cgo-handle/go.mod @@ -0,0 +1,3 @@ +module cgo-handle + +go 1.16 diff --git a/content/assets/cgo-handle/main.c b/content/assets/cgo-handle/main.c new file mode 100644 index 0000000..ce33290 --- /dev/null +++ b/content/assets/cgo-handle/main.c @@ -0,0 +1,13 @@ +// Copyright 2021 The golang.design Initiative Authors. +// All rights reserved. Use of this source code is governed +// by a MIT license that can be found in the LICENSE file. +// +// Written by Changkun Ou + +#include + +extern void GoFunc(uintptr_t handle); + +void cFunc(uintptr_t handle) { + GoFunc(handle); +} \ No newline at end of file diff --git a/content/assets/cgo-handle/main.go b/content/assets/cgo-handle/main.go new file mode 100644 index 0000000..d3207db --- /dev/null +++ b/content/assets/cgo-handle/main.go @@ -0,0 +1,45 @@ +// Copyright 2021 The golang.design Initiative Authors. +// All rights reserved. Use of this source code is governed +// by a MIT license that can be found in the LICENSE file. +// +// Written by Changkun Ou + +package main + +/* +#include + +extern void GoFunc(uintptr_t handle); +void cFunc(uintptr_t handle); +*/ +import "C" +import ( + "cgo-handle/cgo" + "log" +) + +var meaningOfLife = 42 + +//export GoFunc +func GoFunc(handle C.uintptr_t) { + h := cgo.Handle(handle) + ch := h.Value().(chan int) + ch <- meaningOfLife +} + +func main() { + // Say we would like to pass the channel to a C function, then pass + // it back from C to Go side and send some value. + ch := make(chan int) + + h := cgo.NewHandle(ch) + go func() { + C.cFunc(C.uintptr_t(h)) + }() + + v := <-ch + if meaningOfLife != v { + log.Fatalf("unexpected receiving value: got %d, want %d", v, meaningOfLife) + } + h.Delete() +} \ No newline at end of file diff --git a/content/posts/cgo-handle.md b/content/posts/cgo-handle.md new file mode 100644 index 0000000..597070f --- /dev/null +++ b/content/posts/cgo-handle.md @@ -0,0 +1,553 @@ +--- +date: 2021-06-10T19:24:41+02:00 +toc: true +slug: /cgo-handle +tags: + - Go + - Cgo + - Handle + - Non-Moving GC + - Escaping +title: A Concurrent-safe Centralized Pointer Managing Facility +--- + +Author(s): [Changkun Ou](https://changkun.de) + +In the Go 1.17 release, we contributed a new cgo facility [runtime/cgo.Handle](https://tip.golang.org/pkg/runtime/cgo/#Handle) in order to help future cgo applications better and easier to build concurrent-safe applications while passing pointers between Go and C. This article will look at the feature by asking what the feature offers to us, why we need such a facility, and how exactly we contributed to the implementation eventually. + + + +## Starting from Cgo and X Window Clipboard + +Cgo is the de facto approach to interact with the C facility in Go. Nevertheless, how often do we need to interact with C in Go? The answer to the question depends on how much we work on the system level or have to utilize a legacy C library, such as for image processing. Whenever a Go application needs to use the legacy from C, it needs to import a sort of C dedicated package as follows: + +```go +/* +#include + +void myprint() { + printf("Hello %s", "World"); +} +*/ +import "C" +``` + +Then on the Go side, one can simply call the `myprint` function through the imported C symbol: + +```go +func main() { + C.myprint() // Hello World +} +``` + +A few months ago, while we were working on building a new package [`golang.design/x/clipboard`](https://golang.design/x/clipboard), we found out that there is a lacking of the facility in Go, although the variety of approaches in the wild is there, still suffering from soundness and performance issues. + +In the [`golang.design/x/clipboard`](https://golang.design/x/clipboard) package, we had to cooperate with cgo to access system level APIs (well, technically, it is an API from a legacy but widely used C system), but lacking the facility of knowing the execution progress on the C side. For instance, on the Go side, we have to call the C code in a goroutine, then do something else in parallel: + +```go +go func() { + C.doWork() // do stuff on C side +}() + +// .. do stuff on Go side .. +``` + +However, under certain circumstances, we need a sort of mechanism to understand the execution progress from the C side, which brings +communication and synchronization between the Go and C. For instance, if we need our Go code to wait until the C side code finishes some initialization work, then proceed, we need precisely this type of communication. + +A real example that we encountered was the need to interact with the clipboard facility. In Linux's [X window environment](https://en.wikipedia.org/wiki/X_Window_System), clipboards are decentralized and can only be owned by each application. The ones who need access to clipboard information required to create their clipboard instance. Say an application A wants to paste something into the clipboard, it has to request to the X window server, then become a clipboard owner to send the information back to other applications whenever they send a copy request. + +This design was considered natural and often required applications to cooperate: If an application B tries to request become the next owner of the clipboard, then A will lost its ownership, then the copy requests from application C, D, etc., will be forwarded to the application B. Similar to a shared region of memory being overwritten by somebody else. + +With the above context information, one can understand that before an application starts to "paste" (serve) the clipboard information, it first obtains the clipboard ownership. Until we get the ownership, the clipboard information will not be available. +In other words, if a clipboard API is designed in the following way: + +```go +clipboard.Write("some information") +``` + +We have to guarantee from its inside that when the function returns, +the information should be available to be accessed. + +Back then, our first idea to deal with the problem was to pass a channel from Go to C, then send a value through the channel from C to Go. After a quick research, we realized that it is impossible because channels cannot be passed as a value between C and Go, even there is a way to pass the entire channel value to the C, there will be no method for sending values through the channel on the C side. + +The next idea was to pass a function callback, then get it called on the C side. The function's execution will use the desired channel to send a notification back to the waiting goroutine. + +After a few attempt, we found that the only possible way is to attach a global function pointer and gets it called through a function wrapper: + + +```go +/* +int myfunc(void* go_value); +*/ +import "C" + +// This funcCallback tries to avoid a runtime panic error when directly +// pass it to Cgo because it violates the pointer passing rules: +// +// panic: runtime error: cgo argument has Go pointer to Go pointer +var ( + funcCallback func() + funcCallbackMu sync.Mutex +) + +type gocallback struct{ f func() } + +func main() { + go func() { + ret := C.myfunc(unsafe.Pointer(&gocallback{func() { + funcCallbackMu.Lock() + f := funcCallback // must use a global function variable. + funcCallbackMu.Unlock() + f() + }})) + // ... do work ... + }() + // ... do work ... +} +``` + +In above, the `gocallback` pointer on the Go side is passed through the C function `myfunc`. On the C side, there will be a call using `go_func_callback` that being called on the C, via passing the struct `gocallback` as a parameter: + +```c +// myfunc will trigger a callback, c_func, whenever it is needed and pass +// the gocallback data though the void* parameter. +void c_func(void *data) { + void *gocallback = userData; + // the gocallback is received as a pointer, we pass it as an argument + // to the go_func_callback + go_func_callback(gocallback); +} +``` + +The `go_func_callback` knows its parameter is typed as `gocallback`. Thus a type casting is safe to do the call: + +```go +//export go_func_callback +func go_func_callback(c unsafe.Pointer) { + (*gocallback)(c).call() +} + +func (c *gocallback) call() { c.f() } +``` + +The function `f` in the `gocallback` is exactly what we would like to call: + +```go +func() { + funcCallbackMu.Lock() + f := funcCallback // must use a global function variable. + funcCallbackMu.Unlock() + f() // get called +} +``` + +Note that the `funcCallback` must be a global function variable. Otherwise, it is a violation of the [cgo pointer passing rules](https://golang.org/pkg/cmd/cgo/#hdr-Passing_pointers). An immediate reaction to the above code is that it is too complicated. Moreover, the demonstrated approach can only assign one function at a time, which is also a violation of the concurrent nature. Any per-goroutine dedicated application will not benefit from this approach because they need a per-goroutine function callback instead of a single global callback. By then, we wonder if there is a better and elegant approach to deal with it. + +This need occurs quite often and also had proposed to offer such an in +[issue 37033](https://golang.org/issue/37033). But luckily, +such a facility is ready in Go 1.17 :) + +## What is [runtime/cgo.Handle](https://tip.golang.org/pkg/runtime/cgo/#Handle)? + +The new [runtime/cgo.Handle](https://tip.golang.org/pkg/runtime/cgo/#Handle) provides a way to pass values that contain Go pointers (pointers to memory allocated by Go) between Go and C without breaking the cgo pointer passing rules. A Handle is an integer value that can represent any Go value. A Handle can be passed through C and back to Go, and the Go code can use the Handle to retrieve the original Go value. The final API design looks like this: + +```go +package cgo + +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 + +// Value returns the associated Go value for a valid handle. +// +// The method panics if the handle is invalid. +func (h Handle) Value() interface{} + +// 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() +``` + +As we can see: `cgo.NewHandle` returns a handle for any given value; the method `Handle.Value` returns the corresponding value of the handle; whenever we need to delete it, one can call `Handle.Delete`. + +The most straightforward example is to pass a string between Go and C using `Handle`. On the Go side: + +```go +package main +/* +#include // for uintptr_t +extern void MyGoPrint(uintptr_t handle); +void myprint(uintptr_t handle); +*/ +import "C" +import "runtime/cgo" + +func main() { + s := "Hello golang.design Initiative" + C.myprint(C.uintptr_t(cgo.NewHandle(s))) + // Output: Hello golang.design Initiative +} +``` + +The string `s` is passed through a created handle to the C function `myprint`, and on the C side: + +```c +#include // for uintptr_t + +// A Go function +extern void MyGoPrint(uintptr_t handle); +// A C function +void myprint(uintptr_t handle) { + MyGoPrint(handle); +} +``` + +The `myprint` passes the handle back to a Go function `MyGoPrint`: + +```go +//export MyGoPrint +func MyGoPrint(handle C.uintptr_t) { + h := cgo.Handle(handle) + s := h.Value().(string) + println(s) + h.Delete() +} +``` + +The `MyGoPrint` queries the value using `Handle.Value()` and prints it out. Then deletes the value using `Handle.Delete()`. + +With this new facility, we can simplify the previously mentioned function +callback pattern much better: + +```go +/* +#include + +int myfunc(void* go_value); +*/ +import "C" + +func main() { + + ch := make(chan struct{}) + handle := cgo.NewHandle(ch) + go func() { + C.myfunc(C.uintptr_t(handle)) // myfunc will call goCallback when needed. + ... + }() + + <-ch // we got notified from the myfunc. + handle.Delete() // no need thus delete the handle. + ... +} + +//export goCallback +func goCallback(h C.uintptr_t) { + v := cgo.Handle(h).Value().(chan struct{}) + v <- struct{} +} +``` + +More importantly, the handles allocated by `cgo.NewHandle` is a concurrent-safe mechanism, which means that whenever we have the handle number, we will fetch the value (if still available) anywhere without suffering from data race. + +Next question: How to implement it? + +## First Attempt + +The first attempt was a lot complicated. Since we need a centralized way to manage all pointers in a concurrent-safe way, the quickest thing that comes to our mind was the `sync.Map` that maps an unique number to the desired value. Thus we can easily use a global `sync.Map`: + +```go +package cgo + +var m = &sync.Map{} +``` + +However, we have to think about the core challenge in the problem: +How to allocate a runtime-level unique ID? Since passing an integer between Go and C is easy, what could represent unique information for a given value? + +The first idea is the memory address. Because every pointer or value +are stored somewhere in memory, if we can have the information, it would +be very easy to use as the ID of the value. + +To complete this idea, we need to be a little bit cautious: Will the memory address of a living value is changed at some point? This leads to two more questions: + +1. What if a value is on the goroutine stack? If so, the value will be released when the goroutine is dead. +2. Go is a garbage-collected language. What if the garbage collector moves and compacts the value to a different place? Then the memory address of the value will be changed, too. + +Based on our years of [experience and understanding](https://golang.design/s/more) to the runtime, we learned that the Go's garbage collector before 1.17 is always not moving. That means, if a value is living on the heap, it will not be moved to other places. With this fact, we are good with the second question. It is a little bit tricky for the first question: a value stay on the stack can be itself was written as a local variable of the goroutine. It is likely to be a value on the stack. However, the more intractable part is that compiler optimization may move values between stacks, and runtime may move the stack when the stack ran out of its size. + +Naturally, we might ask: is it possible to make sure a value always be allocated on the heap instead of the stack? The answer is: Yes! If we turn it into an `interface{}`. Until 1.17, the Go compiler's escape analysis always marks the value that should escape to the heap if it is converted as an `interface{}`. + +With all the knowledge above, we can write the following part of the implementation that utilizes the memory address of an escaped value: + +```go +// wrap wraps a Go value. +type wrap struct{ v interface{} } + +func NewHandle(v interface{}) Handle { + var k uintptr + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Ptr, reflect.UnsafePointer, reflect.Slice, + reflect.Map, reflect.Chan, reflect.Func: + if rv.IsNil() { + panic("cgo: cannot use Handle for nil value") + } + + k = rv.Pointer() + default: + // Wrap and turn a value parameter into a pointer. This enables + // us to always store the passing object as a pointer, and helps + // to identify which of whose are initially pointers or values + // when Value is called. + v = &wrap{v} + k = reflect.ValueOf(v).Pointer() + } + + ... +} +``` + +Note that the implementation above treats the values differently: For `reflect.Ptr`, `reflect.UnsafePointer`, `reflect.Slice`, `reflect.Map`, `reflect.Chan`, `reflect.Func` types, they are already pointers escaped to the heap, we can safely get the address from them. For the other kinds, we need to turn them from a value to a pointer and also make sure they will always escape to the heap. That is the part: + +```go + // Wrap and turn a value parameter into a pointer. This enables + // us to always store the passing object as a pointer, and helps + // to identify which of whose are initially pointers or values + // when Value is called. + v = &wrap{v} + k = reflect.ValueOf(v).Pointer() +``` + +Now we have turned everything into an escaped value on the heap. The next thing we have to ask is: what if the two values are the same? That means the `v` passed to `cgo.NewHandle(v)` is the same object. Then we will get the same memory address in `k` at this point. + +The easy case is, of course, if the address is not on the global map, then we do not have to think but return the address as the handle of the value: + + +```go +func NewHandle(v interface{}) Handle { + ... + + // v was escaped to the heap because of reflection. As Go do not have + // a moving GC (and possibly lasts true for a long future), it is + // safe to use its pointer address as the key of the global map at + // this moment. The implementation must be reconsidered if moving GC + // is introduced internally in the runtime. + actual, loaded := m.LoadOrStore(k, v) + if !loaded { + return Handle(k) + } + + ... +} +``` + +Otherwise, we have to check the old value in the global map, if it is the same value, then we return the same address as expected: + +```go +func NewHandle(v interface{}) Handle { + ... + + arv := reflect.ValueOf(actual) + switch arv.Kind() { + case reflect.Ptr, reflect.UnsafePointer, reflect.Slice, + reflect.Map, reflect.Chan, reflect.Func: + // The underlying object of the given Go value already have + // its existing handle. + if arv.Pointer() == k { + return Handle(k) + } + + // If the loaded pointer is inconsistent with the new pointer, + // it means the address has been used for different objects + // because of GC and its address is reused for a new Go object, + // meaning that the Handle does not call Delete explicitly when + // the old Go value is not needed. Consider this as a misuse of + // a handle, do panic. + panic("cgo: misuse of a Handle") + default: + panic("cgo: Handle implementation has an internal bug") + } +} +``` + +If the existing value shares the same address with the newly requested +value, this must be a misuse of the Handle. + +Since we have used the `wrap` struct to turn everything into the `reflect.Ptr` type, it is impossible to have other kinds of values to fetch from the global map. If that happens, it is an internal bug in the handle implementation. + +When implementing the `Value()` method, we see why a `wrap` struct beneficial: + +```go +func (h Handle) Value() interface{} { + v, ok := m.Load(uintptr(h)) + if !ok { + panic("cgo: misuse of an invalid Handle") + } + if wv, ok := v.(*wrap); ok { + return wv.v + } + return v +} +``` + +Because we can check when the stored object is a `*wrap` pointer, which means it was a value other than pointers. We return the value instead of the stored object. + +Lastly, the `Delete` method becomes trivial: + +```go +func (h Handle) Delete() { + _, ok := m.LoadAndDelete(uintptr(h)) + if !ok { + panic("cgo: misuse of an invalid Handle") + } +} +``` + +See a full implementation in [golang.design/x/clipboard/internal/cgo](https://github.com/golang-design/clipboard/blob/main/internal/cgo/handle.go). + +## The Accepted Approach + +As one can see, the previous approach is more complicated than expected: it relies on the foundation that runtime garbage collector is not a moving garbage collector, and an argument though interfaces will escape to the heap. + +Although several other places in the internal runtime implementation rely on these facts, such as the channel implementation, it is still a little over-complicated than what we expected. + +Notably, the previous `NewHandle` actually behaves to return a unique handle when the provided Go value refers to the same object. This is the core that brings the complexity of the implementation. However, we have another possibility: `NewHandle` always returns a different handle, and a Go value can have multiple handles. + +Do we really need to Handle to be unique and keep it satisfy [idempotence](https://en.wikipedia.org/wiki/Idempotence)? After a short discussion with the Go team, we share the consensus that for the purpose of a Handle, it seems unnecessary to keep it unique for the following reasons: + +1. The semantic of `NewHandle` is to return a *new* handle, instead of a unique handle; +2. The handle is nothing more than just an integer and guarantee it to be unique may prevent misuse of the handle, but it cannot always avoid the misuse until it is too late; +3. The complexity of the implementation. + +Therefore, we need to rethink the original question: How to allocate a runtime-level unique ID? + +In reality, the approach is more manageable: we only need to increase a number and never stop. This is the most commonly used approach for unique ID generation. For instance, in database applications, the unique id of a table row is always incremental; in Unix timestamp, the time is always incremental, etc. + +If we use the same approach, what would be a possible concurrent-safe implementation? With `sync.Map` and atomic, we can produce code like this: + +```go +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) +} + +var ( + handles = sync.Map{} // map[Handle]interface{} + handleIdx uintptr // atomic +) +``` + +Whenever we want to allocate a new ID (`NewHandle`), one can increase the handle number `handleIdx` atomically, then the next allocation will always be guaranteed to have a larger number to use. With that allocated number, we can easily store it to a global map that persists all the Go values. + +The remaining work becomes trivial. When we want to use the handle to retrieve the corresponding Go value back, we access the value map via the handle number: + +```go +func (h Handle) Value() interface{} { + v, ok := handles.Load(uintptr(h)) + if !ok { + panic("runtime/cgo: misuse of an invalid Handle") + } + return v +} +``` + +Further, if we are done with the handle, one can delete it from the value map: + +```go +func (h Handle) Delete() { + _, ok := handles.LoadAndDelete(uintptr(h)) + if !ok { + panic("runtime/cgo: misuse of an invalid Handle") + } +} +``` + +In this implementation, we do not have to assume the runtime mechanism but just the language. As long as the Go 1 compatibility keeps the promise `sync.Map` to work, there will be no need to rework the whole `Handle` design. Because of its simplicity, this is the accepted approach (see [CL 295369](https://golang.org/cl/295369)) by the Go team. + +Aside from a future re-implementation of `sync.Map` that optimizes parallelism, the `Handle` will automatically benefit from it. Let us do a final benchmark that compares the previous method and the current approach: + +```go +func BenchmarkHandle(b *testing.B) { + b.Run("non-concurrent", func(b *testing.B) { + for i := 0; i < b.N; i++ { + h := cgo.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 := cgo.NewHandle(v) + _ = h.Value() + h.Delete() + } + }) + }) +} +``` + +``` +name old time/op new time/op delta +Handle/non-concurrent-8 407ns ±1% 393ns ±2% -3.51% (p=0.000 n=8+9) +Handle/concurrent-8 768ns ±0% 759ns ±1% -1.21% (p=0.003 n=9+9) +``` + +Simpler, faster, why not? + +## Conclusion + +This article discussed the newly introduced `runtime/cgo.Handle` facility coming in the Go 1.17 release that we contributed. The `Handle` facility enables us to pass Go values between Go and C back and forth without breaking the cgo pointer passing rules. After a short introduction to the usage of the feature, we first discussed a first attempt implementation based on the fact that the runtime garbage collector is not a moving GC and the escape behavior of `interface{}` arguments. +After a few discussions of the ambiguity of the Handle semantics and the drawbacks in the previous implementation, we also introduced a straightforward and better-performed approach and demonstrated its performance. + +As real-world demonstration, we have been using the mentioned two approaches +in two of our released packages for quite a long time: +[golang.design/x/clipboard](https://github.com/golang-design/clipboard) +and [golang.design/x/hotkey](https://github.com/golang-design/hotkey) +before in their `internal/cgo` package. +We are looking forward to switch to the officially released `runtime/cgo` +package in the Go 1.17 release. + +For future work, one can foresee that a possible limitation in the accepted +implementation is that the handle number may run out of the handle space +very quickly in 32-bit or lower operating systems (similar to +[Year 2038 Problem](https://en.wikipedia.org/wiki/Year_2038_problem). +When we allocate 100 handles per second, the handle space can run out in +0xFFFFFFF / (24 * 60 * 60 * 100) = 31 days). + +*_If you are interested and think this is a serious issue, feel free to +[CC us](mailto:hi[at]golang.design) when you send a CL, +it would also interesting for us to read your excellent approach._ + + +## Further Reading Suggestions + +- Alex Dubov. runtime: provide centralized facility for managing (c)go pointer handles. Feb 5, 2020. https://golang.org/issue/37033 +- Changkun Ou. runtime/cgo: add Handle for managing (c)go pointers Feb 21, 2021. https://golang.org/cl/294670 +- Changkun Ou. runtime/cgo: add Handle for managing (c)go pointers Feb 23, 2021. https://golang.org/cl/295369 +- Ian Lance Taylor. cmd/cgo: specify rules for passing pointers between Go and C. Aug 31, 2015. https://golang.org/issue/12416 +- Ian Lance Taylor. Proposal: Rules for passing pointers between Go and C. October, 2015. https://golang.org/design/12416-cgo-pointers +- Go Contributors. cgo. Mar 12, 2019. https://github.com/golang/go/wiki/cgo +- The golang.design Initiative. 📋 cross-platform clipboard package in Go. Feb 25, 2021. https://github.com/golang-design/clipboard +- The golang.design Initiative. ⌨️ cross-platform hotkey package in GO. Feb 27, 2021. https://github.com/golang-design/hotkey \ No newline at end of file