Skip to content

Commit f1f2ad0

Browse files
committed
add regression tests for GIL ordering bug (issue #370)
Adds two reproducers that exercise the go2py/C.CString-without-GIL crash: 1. A 5000-iteration stress loop in the cgo example (Hi/Hello string returns). 2. A new gilstring example covering struct string fields, slice elements, and map values under repeated calls.
1 parent 4848127 commit f1f2ad0

5 files changed

Lines changed: 77 additions & 0 deletions

File tree

SUPPORT_MATRIX.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ _examples/consts | yes
1111
_examples/cstrings | yes
1212
_examples/empty | yes
1313
_examples/funcs | yes
14+
_examples/gilstring | yes
1415
_examples/gobytes | yes
1516
_examples/gopygc | yes
1617
_examples/gostrings | yes

_examples/cgo/test.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,12 @@
1111
print("cgo.Hi()= %s" % repr(cgo.Hi()).lstrip('u'))
1212
print("cgo.Hello(you)= %s" % repr(cgo.Hello("you")).lstrip('u'))
1313

14+
# Regression test for GIL ordering bug (issue #370 / PR #386):
15+
# go functions returning string (go2py=C.CString) previously called C.CString
16+
# without holding the GIL, causing crashes under repeated calls.
17+
for _ in range(5000):
18+
assert cgo.Hi() == 'hi from go\n'
19+
assert cgo.Hello("world") == 'hello world from go\n'
20+
print("stress OK")
21+
1422
print("OK")

_examples/gilstring/gilstring.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2026 The go-python Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package gilstring is a regression test for the GIL ordering bug (issue #370).
6+
// It exercises go2py string conversion through functions, struct fields,
7+
// slice elements, and map values — all of which previously ran C.CString
8+
// without holding the GIL, causing crashes under repeated calls.
9+
package gilstring
10+
11+
// Item is a struct with a string field to exercise struct member string getters.
12+
type Item struct {
13+
Label string
14+
Count int
15+
}
16+
17+
// MakeItem returns an Item with the given label.
18+
func MakeItem(s string) Item { return Item{Label: s, Count: len(s)} }
19+
20+
// GetLabel returns the Label field of an Item.
21+
func GetLabel(i Item) string { return i.Label }
22+
23+
// StringSlice returns a slice of strings.
24+
func StringSlice() []string { return []string{"alpha", "beta", "gamma"} }
25+
26+
// StringMap returns a map with string values.
27+
func StringMap() map[string]string { return map[string]string{"key": "value"} }

_examples/gilstring/test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2026 The go-python Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
## py2/py3 compat
6+
from __future__ import print_function
7+
8+
import gilstring
9+
10+
# Regression test for GIL ordering bug (issue #370 / PR #386).
11+
# Struct string fields, slice elements, and map values all previously used
12+
# C.CString (go2py) without the GIL held, causing crashes under repeated calls.
13+
N = 5000
14+
15+
for _ in range(N):
16+
item = gilstring.MakeItem("hello")
17+
assert item.Label == "hello", item.Label
18+
19+
for _ in range(N):
20+
s = gilstring.StringSlice()
21+
assert s[0] == "alpha", s[0]
22+
23+
for _ in range(N):
24+
m = gilstring.StringMap()
25+
assert m["key"] == "value", m["key"]
26+
27+
print("OK")

main_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ var (
4949
"_examples/cstrings": []string{"py3"},
5050
"_examples/pkgconflict": []string{"py3"},
5151
"_examples/variadic": []string{"py3"},
52+
"_examples/gilstring": []string{"py3"},
5253
}
5354

5455
testEnvironment = os.Environ()
@@ -555,6 +556,7 @@ func TestBindCgoPackage(t *testing.T) {
555556
want: []byte(`cgo.doc: '\nPackage cgo tests bindings of CGo-based packages.\n\n'
556557
cgo.Hi()= 'hi from go\n'
557558
cgo.Hello(you)= 'hello you from go\n'
559+
stress OK
558560
OK
559561
`),
560562
})
@@ -783,6 +785,18 @@ OK
783785
})
784786
}
785787

788+
func TestGilString(t *testing.T) {
789+
// t.Parallel()
790+
path := "_examples/gilstring"
791+
testPkg(t, pkg{
792+
path: path,
793+
lang: features[path],
794+
cmd: "build",
795+
extras: nil,
796+
want: []byte("OK\n"),
797+
})
798+
}
799+
786800
func TestPackagePrefix(t *testing.T) {
787801
// t.Parallel()
788802
path := "_examples/package/mypkg"

0 commit comments

Comments
 (0)