gopy/gopyh/handle.go

211 lines
5.0 KiB
Go

// Copyright 2019 The go-python 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 gopyh provides the variable handle manager for gopy.
// The handles map can NOT be globally shared in C because it
// must use interface{} values that can change location via GC.
// In effect, each gopy package must be thought of as a completely
// separate Go instance, and there can be NO sharing of anything
// between them, because they fundamentally live in different .so
// libraries.
// Thus, we must ensure that all handles used within a package
// are registered within that same package -- this means python
// users typically will import a single package, which exports
// all the relevant functionality as needed. Any further packages
// cannot share anything other than basic types (int, string etc).
package gopyh
import (
"fmt"
"os"
"reflect"
"strconv"
"sync"
)
// GoHandle is the type for the handle
type GoHandle int64
type CGoHandle int64
// --- variable handles: all pointers managed via handles ---
var (
mu sync.RWMutex
ctr int64
handles map[GoHandle]interface{}
counts map[GoHandle]int64
)
// IfaceIsNil returns true if interface or value represented by interface is nil
func IfaceIsNil(it interface{}) bool {
if it == nil {
return true
}
v := reflect.ValueOf(it)
vk := v.Kind()
if vk == reflect.Ptr || vk == reflect.Interface || vk == reflect.Map || vk == reflect.Slice || vk == reflect.Func || vk == reflect.Chan {
return v.IsNil()
}
return false
}
// NonPtrValue returns the non-pointer underlying value
func NonPtrValue(v reflect.Value) reflect.Value {
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
return v
}
// PtrValue returns the pointer version (Addr()) of the underlying value if
// the value is not already a Ptr
func PtrValue(v reflect.Value) reflect.Value {
if v.CanAddr() && v.Kind() != reflect.Ptr {
v = v.Addr()
}
return v
}
// Embed returns the embedded struct (in first field only) of given type within given struct
func Embed(stru interface{}, embed reflect.Type) interface{} {
if IfaceIsNil(stru) {
return nil
}
v := NonPtrValue(reflect.ValueOf(stru))
typ := v.Type()
if typ == embed {
return PtrValue(v).Interface()
}
if typ.NumField() == 0 {
return nil
}
f := typ.Field(0)
if f.Type.Kind() == reflect.Struct && f.Anonymous { // anon only avail on StructField fm typ
vf := v.Field(0)
vfpi := PtrValue(vf).Interface()
if f.Type == embed {
return vfpi
}
rv := Embed(vfpi, embed)
if rv != nil {
return rv
}
}
return nil
}
var (
trace = false
)
func init() {
if len(os.Getenv("GOPY_HANDLE_TRACE")) > 0 {
trace = true
}
}
// Register registers a new variable instance.
func Register(typnm string, ifc interface{}) CGoHandle {
if IfaceIsNil(ifc) {
return -1
}
mu.Lock()
defer mu.Unlock()
if handles == nil {
handles = make(map[GoHandle]interface{})
counts = make(map[GoHandle]int64)
}
ctr++
hc := ctr
ghc := GoHandle(hc)
handles[ghc] = ifc
counts[ghc] = 0
if trace {
fmt.Printf("gopy Registered: %s %v %d\n", typnm, ifc, hc)
}
return CGoHandle(hc)
}
// DecRef decrements the reference count for the specified handle
// and removes it if the reference count goes to zero.
func DecRef(handle CGoHandle) {
if handle < 1 {
return
}
mu.Lock()
defer mu.Unlock()
if handles == nil {
return
}
ghc := GoHandle(handle)
if _, exists := handles[ghc]; !exists {
return
}
counts[ghc]--
switch cnt := counts[ghc]; {
case cnt == 0:
delete(counts, ghc)
delete(handles, ghc)
if trace {
fmt.Printf("gopy DecRef: %d\n", handle)
}
case cnt < 0:
panic(fmt.Sprintf("gopy DecRef ref count %v for handle: %v, ifc %v", cnt, ghc, handles[ghc]))
default:
if trace {
fmt.Printf("gopy DecRef: %d: %d\n", handle, cnt)
}
}
}
// IncRef increments the reference count for the specified handle.
func IncRef(handle CGoHandle) {
if handle < 1 {
return
}
mu.Lock()
defer mu.Unlock()
ghc := GoHandle(handle)
if _, exists := counts[ghc]; exists {
counts[ghc]++
if trace {
fmt.Printf("gopy IncRef: %d: %d\n", handle, counts[ghc])
}
}
}
// VarFromHandle gets variable from handle string.
// Reports error to python but does not return it,
// for use in inline calls
func VarFromHandle(h CGoHandle, typnm string) interface{} {
v, _ := VarFromHandleTry(h, typnm)
return v
}
// VarFromHandleTry version returns the error explicitly,
// for use when error can be processed
func VarFromHandleTry(h CGoHandle, typnm string) (interface{}, error) {
if h < 1 {
return nil, fmt.Errorf("gopy: nil handle")
}
mu.RLock()
defer mu.RUnlock()
v, has := handles[GoHandle(h)]
if !has {
err := fmt.Errorf("gopy: variable handle not registered: " + strconv.FormatInt(int64(h), 10))
// TODO: need to get access to this:
// C.PyErr_SetString(C.PyExc_TypeError, C.CString(err.Error()))
return nil, err
}
return v, nil
}
// NumHandles returns the number of handles in use.
func NumHandles() int {
mu.RLock()
defer mu.RUnlock()
return len(handles)
}