Skip to content

Commit f2a9ad1

Browse files
committed
un-skip TestBindCgoPackage & expand CI matrix
* fix GIL ordering in generated CGo wrappers: restore GIL before go2py converters, use add_checked_string_function for C.CString return paths, and fix off-by-one in cgo.go malloc call * remove skip for TestBindSimple & TestBindCgoPackage * update Go version matrix in CI matrix
1 parent 462a29d commit f2a9ad1

7 files changed

Lines changed: 40 additions & 15 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
strategy:
2323
fail-fast: false
2424
matrix:
25-
go-version: [1.25.x, 1.24.x, 1.22.x, 1.21.x]
25+
go-version: [1.25.x, 1.24.x, 1.23.x, 1.22.x, 1.21.x]
2626
platform: [ubuntu-latest, windows-latest]
2727
#platform: [ubuntu-latest, macos-latest, windows-latest]
2828
runs-on: ${{ matrix.platform }}

_examples/cgo/cgo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package cgo
99
//#include <string.h>
1010
//#include <stdlib.h>
1111
//const char* cpkg_sprintf(const char *str) {
12-
// char *o = (char*)malloc(strlen(str));
12+
// char *o = (char*)malloc(strlen(str) + 1);
1313
// sprintf(o, "%s", str);
1414
// return o;
1515
//}

bind/gen_func.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,18 @@ func (g *pyGen) genFuncBody(sym *symbol, fsym *Func) {
263263

264264
// release GIL
265265
g.gofile.Printf("_saved_thread := C.PyEval_SaveThread()\n")
266-
if !rvIsErr && nres != 2 {
266+
// Use defer only when there is no go2py return conversion that calls
267+
// Python C API (which requires the GIL to already be reacquired).
268+
// For go2py returns (excluding handle types) and error/multi-return cases,
269+
// we restore explicitly. Handle types use handleFromPtr which is pure Go
270+
// and doesn't need the GIL, so they can keep using defer.
271+
nresForDefer := nres
272+
// hasRetCvt fires when go2py != "" and NOT (hasHandle && !isPtrOrIface).
273+
// Suppress defer in exactly those cases so the explicit restore below is the only one.
274+
if nres > 0 && res[0].sym.go2py != "" && !(res[0].sym.hasHandle() && !res[0].sym.isPtrOrIface()) {
275+
nresForDefer = 2 // treat like nres==2: no defer, explicit restore
276+
}
277+
if !rvIsErr && nresForDefer != 2 {
267278
// reacquire GIL after return
268279
g.gofile.Printf("defer C.PyEval_RestoreThread(_saved_thread)\n")
269280
}
@@ -357,8 +368,8 @@ if __err != nil {
357368
g.pywrap.Printf("_%s.%s(", pkgname, mnm)
358369
}
359370

360-
hasRetCvt := false
361371
hasAddrOfTmp := false
372+
hasRetCvt := false
362373
if nres > 0 {
363374
ret := res[0]
364375
switch {
@@ -370,8 +381,10 @@ if __err != nil {
370381
hasAddrOfTmp = true
371382
g.gofile.Printf("cret := ")
372383
case ret.sym.go2py != "":
384+
// Assign to cret first; we'll restore the GIL before calling go2py
385+
// so that Python C API functions inside go2py run with the GIL held.
373386
hasRetCvt = true
374-
g.gofile.Printf("return %s(", ret.sym.go2py)
387+
g.gofile.Printf("cret := ")
375388
default:
376389
g.gofile.Printf("return ")
377390
}
@@ -394,11 +407,6 @@ if __err != nil {
394407
} else {
395408
funCall = fmt.Sprintf("%s(%s)", fsym.GoFmt(), strings.Join(callArgs, ", "))
396409
}
397-
if hasRetCvt {
398-
ret := res[0]
399-
funCall += fmt.Sprintf(")%s", ret.sym.go2pyParenEx)
400-
}
401-
402410
if nres == 0 {
403411
g.gofile.Printf("if boolPyToGo(goRun) {\n")
404412
g.gofile.Indent()
@@ -413,6 +421,13 @@ if __err != nil {
413421
g.gofile.Printf("%s\n", funCall)
414422
}
415423

424+
if hasRetCvt {
425+
// Restore GIL before calling go2py, which may call Python C API.
426+
ret := res[0]
427+
g.gofile.Printf("C.PyEval_RestoreThread(_saved_thread)\n")
428+
g.gofile.Printf("return %s(cret)%s\n", ret.sym.go2py, ret.sym.go2pyParenEx)
429+
}
430+
416431
if rvIsErr || nres == 2 {
417432
g.gofile.Printf("\n")
418433
// reacquire GIL

bind/gen_map.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,11 @@ otherwise parameter is a python list that we copy from
317317
g.gofile.Outdent()
318318
g.gofile.Printf("}\n\n")
319319

320-
g.pybuild.Printf("mod.add_function('%s_elem', retval('%s'), [param('%s', 'handle'), param('%s', '_ky')])\n", slNm, esym.cpyname, PyHandle, ksym.cpyname)
320+
if esym.go2py == "C.CString" {
321+
g.pybuild.Printf("add_checked_string_function(mod, '%s_elem', retval('%s'), [param('%s', 'handle'), param('%s', '_ky')])\n", slNm, esym.cpyname, PyHandle, ksym.cpyname)
322+
} else {
323+
g.pybuild.Printf("mod.add_function('%s_elem', retval('%s'), [param('%s', 'handle'), param('%s', '_ky')])\n", slNm, esym.cpyname, PyHandle, ksym.cpyname)
324+
}
321325

322326
// contains
323327
g.gofile.Printf("//export %s_contains\n", slNm)

bind/gen_slice.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,11 @@ otherwise parameter is a python list that we copy from
345345
caller_owns_ret = ", caller_owns_return=True"
346346
transfer_ownership = ", transfer_ownership=False"
347347
}
348-
g.pybuild.Printf("mod.add_function('%s_elem', retval('%s'%s), [param('%s', 'handle'), param('int', 'idx')])\n", slNm, esym.cpyname, caller_owns_ret, PyHandle)
348+
if esym.go2py == "C.CString" {
349+
g.pybuild.Printf("add_checked_string_function(mod, '%s_elem', retval('%s'), [param('%s', 'handle'), param('int', 'idx')])\n", slNm, esym.cpyname, PyHandle)
350+
} else {
351+
g.pybuild.Printf("mod.add_function('%s_elem', retval('%s'%s), [param('%s', 'handle'), param('int', 'idx')])\n", slNm, esym.cpyname, caller_owns_ret, PyHandle)
352+
}
349353

350354
if slc.isSlice() {
351355
g.gofile.Printf("//export %s_subslice\n", slNm)

bind/gen_struct.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,11 @@ func (g *pyGen) genStructMemberGetter(s *Struct, i int, f types.Object) {
227227
g.gofile.Outdent()
228228
g.gofile.Printf("}\n\n")
229229

230-
g.pybuild.Printf("mod.add_function('%s', retval('%s'), [param('%s', 'handle')])\n", cgoFn, ret.cpyname, PyHandle)
230+
if ret.go2py == "C.CString" {
231+
g.pybuild.Printf("add_checked_string_function(mod, '%s', retval('%s'), [param('%s', 'handle')])\n", cgoFn, ret.cpyname, PyHandle)
232+
} else {
233+
g.pybuild.Printf("mod.add_function('%s', retval('%s'), [param('%s', 'handle')])\n", cgoFn, ret.cpyname, PyHandle)
234+
}
231235
}
232236

233237
func (g *pyGen) genStructMemberSetter(s *Struct, i int, f types.Object) {

main_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,6 @@ OK
316316
}
317317

318318
func TestBindSimple(t *testing.T) {
319-
t.Skip("Skipping due to Go 1.21+ CGO issue (see https://github.com/go-python/gopy/issues/370)")
320319
// t.Parallel()
321320
path := "_examples/simple"
322321
testPkg(t, pkg{
@@ -546,7 +545,6 @@ OK
546545
}
547546

548547
func TestBindCgoPackage(t *testing.T) {
549-
t.Skip("Skipping due to Go 1.21+ CGO issue (see https://github.com/go-python/gopy/issues/370)")
550548
// t.Parallel()
551549
path := "_examples/cgo"
552550
testPkg(t, pkg{

0 commit comments

Comments
 (0)