Skip to content

Commit c4ea32c

Browse files
authored
Merge pull request #22 from cpunion/gc
Gc
2 parents c7ae0eb + e090081 commit c4ea32c

File tree

10 files changed

+98
-113
lines changed

10 files changed

+98
-113
lines changed

convert.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ func ToValue(from Object, to reflect.Value) bool {
149149
t := to.Type()
150150
to.Set(reflect.MakeMap(t))
151151
dict := cast[Dict](from)
152-
for key, value := range dict.Items() {
152+
iter := dict.Iter()
153+
for iter.HasNext() {
154+
key, value := iter.Next()
153155
vk := reflect.New(t.Key()).Elem()
154156
vv := reflect.New(t.Elem()).Elem()
155157
if !ToValue(key, vk) || !ToValue(value, vv) {

dict.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -75,26 +75,26 @@ func (d Dict) Del(key Objecter) {
7575
C.PyDict_DelItem(d.obj, key.Obj())
7676
}
7777

78-
func (d Dict) Items() func(fn func(key, value Object) bool) {
79-
return func(fn func(key, value Object) bool) {
80-
items := C.PyDict_Items(d.obj)
81-
check(items != nil, "failed to get items of dict")
82-
defer C.Py_DecRef(items)
83-
iter := C.PyObject_GetIter(items)
84-
for {
85-
item := C.PyIter_Next(iter)
86-
if item == nil {
87-
break
88-
}
89-
C.Py_IncRef(item)
90-
key := C.PyTuple_GetItem(item, 0)
91-
value := C.PyTuple_GetItem(item, 1)
92-
C.Py_IncRef(key)
93-
C.Py_IncRef(value)
94-
C.Py_DecRef(item)
95-
if !fn(newObject(key), newObject(value)) {
96-
break
97-
}
98-
}
78+
func (d Dict) Iter() *DictIter {
79+
return &DictIter{dict: d, pos: 0}
80+
}
81+
82+
type DictIter struct {
83+
dict Dict
84+
pos C.long
85+
}
86+
87+
func (d *DictIter) HasNext() bool {
88+
pos := d.pos
89+
return C.PyDict_Next(d.dict.obj, &pos, nil, nil) != 0
90+
}
91+
92+
func (d *DictIter) Next() (Object, Object) {
93+
var key, value *C.PyObject
94+
if C.PyDict_Next(d.dict.obj, &d.pos, &key, &value) == 0 {
95+
return Nil(), Nil()
9996
}
97+
C.Py_IncRef(key)
98+
C.Py_IncRef(value)
99+
return newObject(key), newObject(value)
100100
}

dict_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ func TestDictForEach(t *testing.T) {
155155
"key3": "value3",
156156
}
157157

158-
for key, value := range dict.Items() {
158+
iter := dict.Iter()
159+
for iter.HasNext() {
160+
key, value := iter.Next()
159161
count++
160162
k := key.String()
161163
v := value.String()

extension.go

Lines changed: 33 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type slotMeta struct {
5050
hasRecv bool // whether it has a receiver
5151
index int // used for member type
5252
typ reflect.Type // member/method type
53+
def *C.PyMethodDef
5354
}
5455

5556
type typeMeta struct {
@@ -60,9 +61,7 @@ type typeMeta struct {
6061

6162
func allocWrapper(typ *C.PyTypeObject, obj any) *wrapperType {
6263
self := C.PyType_GenericAlloc(typ, 0)
63-
if self == nil {
64-
return nil
65-
}
64+
check(self != nil, "failed to allocate wrapper")
6665
wrapper := (*wrapperType)(unsafe.Pointer(self))
6766
holder := new(objectHolder)
6867
holder.obj = obj
@@ -83,9 +82,7 @@ func wrapperAlloc(typ *C.PyTypeObject, size C.Py_ssize_t) *C.PyObject {
8382
maps := getGlobalData()
8483
meta := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(typ))]
8584
wrapper := allocWrapper(typ, reflect.New(meta.typ).Interface())
86-
if wrapper == nil {
87-
return nil
88-
}
85+
check(wrapper != nil, "failed to allocate wrapper")
8986
return (*C.PyObject)(unsafe.Pointer(wrapper))
9087
}
9188

@@ -101,9 +98,8 @@ func wrapperInit(self, args *C.PyObject) C.int {
10198
typ := (*C.PyObject)(self).ob_type
10299
maps := getGlobalData()
103100
typeMeta := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(typ))]
104-
if typeMeta.init == nil {
105-
return 0
106-
}
101+
check(typeMeta != nil, "type not registered")
102+
check(typeMeta.init != nil, "init method not found")
107103
if wrapperMethod_(typeMeta, typeMeta.init, self, args, 0) == nil {
108104
return -1
109105
}
@@ -114,15 +110,9 @@ func wrapperInit(self, args *C.PyObject) C.int {
114110
func getterMethod(self *C.PyObject, _closure unsafe.Pointer, methodId C.int) *C.PyObject {
115111
maps := getGlobalData()
116112
typeMeta := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(self.ob_type))]
117-
if typeMeta == nil {
118-
SetError(fmt.Errorf("type %v not registered", FromPy(self)))
119-
return nil
120-
}
113+
check(typeMeta != nil, fmt.Sprintf("type %v not registered", FromPy(self)))
121114
methodMeta := typeMeta.methods[uint(methodId)]
122-
if methodMeta == nil {
123-
SetError(fmt.Errorf("getter method %d not found", methodId))
124-
return nil
125-
}
115+
check(methodMeta != nil, fmt.Sprintf("getter method %d not found", methodId))
126116

127117
wrapper := (*wrapperType)(unsafe.Pointer(self))
128118
goPtr := reflect.ValueOf(wrapper.goObj)
@@ -136,10 +126,7 @@ func getterMethod(self *C.PyObject, _closure unsafe.Pointer, methodId C.int) *C.
136126
}
137127
if pyType, ok := maps.pyTypes[fieldType.Elem()]; ok {
138128
newWrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(pyType)), field.Interface())
139-
if newWrapper == nil {
140-
SetError(fmt.Errorf("failed to allocate wrapper for nested struct pointer"))
141-
return nil
142-
}
129+
check(newWrapper != nil, "failed to allocate wrapper for nested struct pointer")
143130
return (*C.PyObject)(unsafe.Pointer(newWrapper))
144131
}
145132
} else if field.Kind() == reflect.Struct {
@@ -148,10 +135,7 @@ func getterMethod(self *C.PyObject, _closure unsafe.Pointer, methodId C.int) *C.
148135
fieldAddr := unsafe.Add(baseAddr, typeMeta.typ.Field(methodMeta.index).Offset)
149136
fieldPtr := reflect.NewAt(fieldType, fieldAddr).Interface()
150137
newWrapper := allocWrapper((*C.PyTypeObject)(unsafe.Pointer(pyType)), fieldPtr)
151-
if newWrapper == nil {
152-
SetError(fmt.Errorf("failed to allocate wrapper for nested struct"))
153-
return nil
154-
}
138+
check(newWrapper != nil, "failed to allocate wrapper for nested struct")
155139
return (*C.PyObject)(unsafe.Pointer(newWrapper))
156140
}
157141
}
@@ -162,29 +146,23 @@ func getterMethod(self *C.PyObject, _closure unsafe.Pointer, methodId C.int) *C.
162146
func setterMethod(self, value *C.PyObject, _closure unsafe.Pointer, methodId C.int) C.int {
163147
maps := getGlobalData()
164148
typeMeta := maps.typeMetas[(*C.PyObject)(unsafe.Pointer(self.ob_type))]
165-
if typeMeta == nil {
166-
SetError(fmt.Errorf("type %v not registered", FromPy(self)))
167-
return -1
168-
}
149+
check(typeMeta != nil, fmt.Sprintf("type %v not registered", FromPy(self)))
169150
methodMeta := typeMeta.methods[uint(methodId)]
170-
if methodMeta == nil {
171-
SetError(fmt.Errorf("setter method %d not found", methodId))
172-
return -1
173-
}
151+
check(methodMeta != nil, fmt.Sprintf("setter method %d not found", methodId))
174152

175153
wrapper := (*wrapperType)(unsafe.Pointer(self))
176154
goPtr := reflect.ValueOf(wrapper.goObj)
177155
goValue := goPtr.Elem()
178156

179157
structValue := goValue
180158
if !structValue.CanSet() {
181-
SetError(fmt.Errorf("struct value cannot be set"))
159+
SetTypeError(fmt.Errorf("struct value cannot be set"))
182160
return -1
183161
}
184162

185163
field := structValue.Field(methodMeta.index)
186164
if !field.CanSet() {
187-
SetError(fmt.Errorf("field %s cannot be set", methodMeta.name))
165+
SetTypeError(fmt.Errorf("field %s cannot be set", methodMeta.name))
188166
return -1
189167
}
190168

@@ -210,7 +188,7 @@ func setterMethod(self, value *C.PyObject, _closure unsafe.Pointer, methodId C.i
210188
}
211189
valueWrapper := (*wrapperType)(unsafe.Pointer(value))
212190
if valueWrapper == nil {
213-
SetError(fmt.Errorf("invalid value for struct pointer field"))
191+
SetTypeError(fmt.Errorf("invalid value for struct pointer field"))
214192
return -1
215193
}
216194
field.Set(reflect.ValueOf(valueWrapper.goObj))
@@ -229,10 +207,6 @@ func setterMethod(self, value *C.PyObject, _closure unsafe.Pointer, methodId C.i
229207
return -1
230208
}
231209
valueWrapper := (*wrapperType)(unsafe.Pointer(value))
232-
if valueWrapper == nil {
233-
SetError(fmt.Errorf("invalid value for struct field"))
234-
return -1
235-
}
236210
baseAddr := goPtr.UnsafePointer()
237211
fieldAddr := unsafe.Add(baseAddr, typeMeta.typ.Field(methodMeta.index).Offset)
238212
fieldPtr := reflect.NewAt(fieldType, fieldAddr)
@@ -257,21 +231,13 @@ func wrapperMethod(self, args *C.PyObject, methodId C.int) *C.PyObject {
257231

258232
maps := getGlobalData()
259233
typeMeta, ok := maps.typeMetas[key]
260-
if !ok {
261-
SetError(fmt.Errorf("type %v not registered", FromPy(key)))
262-
return nil
263-
}
234+
check(ok, fmt.Sprintf("type %v not registered", FromPy(key)))
264235

265236
methodMeta := typeMeta.methods[uint(methodId)]
266237
return wrapperMethod_(typeMeta, methodMeta, self, args, methodId)
267238
}
268239

269240
func wrapperMethod_(typeMeta *typeMeta, methodMeta *slotMeta, self, args *C.PyObject, methodId C.int) *C.PyObject {
270-
if methodMeta == nil {
271-
SetError(fmt.Errorf("method %d not found", methodId))
272-
return nil
273-
}
274-
275241
methodType := methodMeta.typ
276242
argc := C.PyTuple_Size(args)
277243
expectedArgs := methodType.NumIn()
@@ -550,9 +516,11 @@ func (m Module) AddType(obj, init any, name, doc string) Object {
550516
*currentSlot = slot
551517
}
552518

519+
typeName := fmt.Sprintf("%s.%s", m.Name(), name)
520+
553521
totalSize := unsafe.Sizeof(wrapperType{})
554522
spec := &C.PyType_Spec{
555-
name: C.CString(name),
523+
name: C.CString(typeName),
556524
basicsize: C.int(totalSize),
557525
flags: C.Py_TPFLAGS_DEFAULT,
558526
slots: slotsPtr,
@@ -627,30 +595,30 @@ func (m Module) AddMethod(name string, fn any, doc string) Func {
627595
}
628596

629597
methodId := uint(len(meta.methods))
630-
meta.methods[methodId] = &slotMeta{
598+
599+
methodPtr := C.wrapperMethods[methodId]
600+
cName := C.CString(name)
601+
cDoc := C.CString(doc)
602+
603+
def := (*C.PyMethodDef)(C.malloc(C.size_t(unsafe.Sizeof(C.PyMethodDef{}))))
604+
def.ml_name = cName
605+
def.ml_meth = C.PyCFunction(methodPtr)
606+
def.ml_flags = C.METH_VARARGS
607+
def.ml_doc = cDoc
608+
609+
methodMeta := &slotMeta{
631610
name: name,
632611
methodName: name,
633612
fn: fn,
634613
typ: t,
635614
doc: doc,
636615
hasRecv: false,
616+
def: def,
637617
}
638-
639-
methodPtr := C.wrapperMethods[methodId]
640-
cName := C.CString(name)
641-
cDoc := C.CString(doc)
642-
643-
def := &C.PyMethodDef{
644-
ml_name: cName,
645-
ml_meth: C.PyCFunction(methodPtr),
646-
ml_flags: C.METH_VARARGS,
647-
ml_doc: cDoc,
648-
}
618+
meta.methods[methodId] = methodMeta
649619

650620
pyFunc := C.PyCFunction_NewEx(def, m.obj, m.obj)
651-
if pyFunc == nil {
652-
panic(fmt.Sprintf("Failed to create function %s", name))
653-
}
621+
check(pyFunc != nil, fmt.Sprintf("Failed to create function %s", name))
654622

655623
if C.PyModule_AddObjectRef(m.obj, cName, pyFunc) < 0 {
656624
C.Py_DecRef(pyFunc)
@@ -660,12 +628,6 @@ func (m Module) AddMethod(name string, fn any, doc string) Func {
660628
return newFunc(pyFunc)
661629
}
662630

663-
func SetError(err error) {
664-
errStr := C.CString(err.Error())
665-
C.PyErr_SetString(C.PyExc_RuntimeError, errStr)
666-
C.free(unsafe.Pointer(errStr))
667-
}
668-
669631
func SetTypeError(err error) {
670632
errStr := C.CString(err.Error())
671633
C.PyErr_SetString(C.PyExc_TypeError, errStr)

global_data.go

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"reflect"
1010
"sync"
1111
"sync/atomic"
12+
"unsafe"
1213
)
1314

1415
// ----------------------------------------------------------------------------
@@ -51,11 +52,15 @@ func (l *decRefList) add(obj *C.PyObject) {
5152
l.mu.Unlock()
5253
}
5354

54-
func (l *decRefList) decRefAll() {
55-
var list []*C.PyObject
55+
func (l *decRefList) len() int {
56+
l.mu.Lock()
57+
defer l.mu.Unlock()
58+
return len(l.objects)
59+
}
5660

61+
func (l *decRefList) decRefAll() {
5762
l.mu.Lock()
58-
list = l.objects
63+
list := l.objects
5964
l.objects = make([]*C.PyObject, 0, maxPyObjects*2)
6065
l.mu.Unlock()
6166

@@ -67,12 +72,12 @@ func (l *decRefList) decRefAll() {
6772
// ----------------------------------------------------------------------------
6873

6974
type globalData struct {
70-
typeMetas map[*C.PyObject]*typeMeta
71-
pyTypes map[reflect.Type]*C.PyObject
72-
holders holderList
73-
decRefList decRefList
74-
disableDecRef bool
75-
finished int32
75+
typeMetas map[*C.PyObject]*typeMeta
76+
pyTypes map[reflect.Type]*C.PyObject
77+
holders holderList
78+
decRefList decRefList
79+
finished int32
80+
alwaysDecRef bool
7681
}
7782

7883
var (
@@ -84,17 +89,16 @@ func getGlobalData() *globalData {
8489
}
8590

8691
func (gd *globalData) addDecRef(obj *C.PyObject) {
87-
if gd.disableDecRef {
88-
return
89-
}
9092
if atomic.LoadInt32(&gd.finished) != 0 {
9193
return
9294
}
9395
gd.decRefList.add(obj)
9496
}
9597

9698
func (gd *globalData) decRefObjectsIfNeeded() {
97-
gd.decRefList.decRefAll()
99+
if gd.alwaysDecRef || gd.decRefList.len() >= maxPyObjects {
100+
gd.decRefList.decRefAll()
101+
}
98102
}
99103

100104
// ----------------------------------------------------------------------------
@@ -111,5 +115,15 @@ func markFinished() {
111115
}
112116

113117
func cleanupGlobal() {
118+
for _, meta := range global.typeMetas {
119+
for _, method := range meta.methods {
120+
def := method.def
121+
if def != nil {
122+
C.free(unsafe.Pointer(def.ml_name))
123+
C.free(unsafe.Pointer(def.ml_doc))
124+
C.free(unsafe.Pointer(def))
125+
}
126+
}
127+
}
114128
global = nil
115129
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/cpunion/go-python
22

3-
go 1.23
3+
go 1.20

0 commit comments

Comments
 (0)