Skip to content

Commit 607bfa6

Browse files
committed
safe DecRef Python objects
1 parent a5dd83c commit 607bfa6

File tree

4 files changed

+54
-7
lines changed

4 files changed

+54
-7
lines changed

function.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ func (f Func) callOneArg(arg Objecter) Object {
3636
}
3737

3838
func (f Func) CallObject(args Tuple) Object {
39+
defer getCurrentThreadData().decRefObjectsIfNeeded()
3940
return newObject(C.PyObject_CallObject(f.obj, args.obj))
4041
}
4142

4243
func (f Func) CallObjectKw(args Tuple, kw KwArgs) Object {
44+
defer getCurrentThreadData().decRefObjectsIfNeeded()
4345
// Convert keyword arguments to Python dict
4446
kwDict := MakeDict(nil)
4547
for k, v := range kw {
@@ -51,6 +53,7 @@ func (f Func) CallObjectKw(args Tuple, kw KwArgs) Object {
5153
func (f Func) Call(args ...any) Object {
5254
argsTuple, kwArgs := splitArgs(args...)
5355
if kwArgs == nil {
56+
defer getCurrentThreadData().decRefObjectsIfNeeded()
5457
switch len(args) {
5558
case 0:
5659
return f.callNoArgs()

object.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
// the Python Object's DecRef method during garbage collection.
1616
type pyObject struct {
1717
obj *C.PyObject
18+
gid int64
1819
}
1920

2021
func (obj *pyObject) Obj() *PyObject {
@@ -54,11 +55,12 @@ func newObject(obj *PyObject) Object {
5455
C.PyErr_Print()
5556
panic("nil Python object")
5657
}
57-
o := &pyObject{obj: obj}
58+
o := &pyObject{obj: obj, gid: getThreadID()}
5859
p := Object{o}
60+
5961
runtime.SetFinalizer(o, func(o *pyObject) {
60-
// TODO: need better auto-release mechanism
61-
// C.Py_DecRef(o.obj)
62+
maps := getThreadData(o.gid)
63+
maps.addPyObject(o.obj)
6264
})
6365
return p
6466
}

python.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ func Initialize() {
2121
}
2222

2323
func Finalize() {
24+
cleanupThreadLocal()
2425
r := C.Py_FinalizeEx()
2526
check(r == 0, "failed to finalize Python")
26-
cleanupThreadLocal()
2727
}
2828

2929
// ----------------------------------------------------------------------------

thread_local.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,59 @@ func (l *holderList) Remove(holder *objectHolder) {
3535
}
3636
}
3737

38+
type decRefList struct {
39+
objects []*C.PyObject
40+
mu sync.Mutex
41+
}
42+
43+
func (l *decRefList) add(obj *C.PyObject) {
44+
l.mu.Lock()
45+
l.objects = append(l.objects, obj)
46+
l.mu.Unlock()
47+
}
48+
49+
func (l *decRefList) decRefAll() {
50+
var list []*C.PyObject
51+
52+
l.mu.Lock()
53+
list = l.objects
54+
l.objects = make([]*C.PyObject, 0, maxPyObjects*2)
55+
l.mu.Unlock()
56+
57+
for _, obj := range list {
58+
C.Py_DecRef(obj)
59+
}
60+
}
61+
3862
type threadData struct {
39-
typeMetas map[*C.PyObject]*typeMeta
40-
pyTypes map[reflect.Type]*C.PyObject
41-
holders holderList
63+
typeMetas map[*C.PyObject]*typeMeta
64+
pyTypes map[reflect.Type]*C.PyObject
65+
holders holderList
66+
decRefList decRefList
67+
}
68+
69+
const maxPyObjects = 128
70+
71+
func (td *threadData) addPyObject(obj *C.PyObject) {
72+
td.decRefList.add(obj)
73+
}
74+
75+
func (td *threadData) decRefObjectsIfNeeded() {
76+
if len(td.decRefList.objects) > maxPyObjects {
77+
td.decRefList.decRefAll()
78+
}
4279
}
4380

4481
var (
4582
globalThreadData sync.Map // map[int64]*threadData
4683
)
4784

4885
func getCurrentThreadData() *threadData {
86+
id := getThreadID()
87+
return getThreadData(id)
88+
}
89+
90+
func getThreadData(gid int64) *threadData {
4991
id := getThreadID()
5092
maps, ok := globalThreadData.Load(id)
5193
if !ok {

0 commit comments

Comments
 (0)