@@ -1027,6 +1027,46 @@ def inner(frame):
10271027 # The next line crashes on 3.12 if we haven't exposed the frames.
10281028 self .assertIsNone (frame .f_back )
10291029
1030+ def test_switching_holding_critical_section_no_crash (self ):
1031+ # https://github.com/python-greenlet/greenlet/issues/513
1032+ # In no-GIL builds, we were failing to restore the
1033+ # critical_section pointer, leading to
1034+ # ``Fatal Python error: PyMutex_Unlock: unlocking mutex that is not locked``
1035+ # when two greenlets both held a lock on an object, and then
1036+ # the GIL was released, which, according to
1037+ # ``Include/cpython/critical_section.h`` causes the critical
1038+ # section to get released.
1039+ # The field is present on Python 3.13+, and only used
1040+ # in no-GIL builds.
1041+ # Initial version of this test case provided in the
1042+ # github issue by ddorian
1043+ def k1 (x ):
1044+ g2 .switch () # into G2 while holding G1's sort critical section
1045+ return x
1046+
1047+
1048+ def k2 (x ):
1049+ g1 .switch () # back into G1 while holding G2's sort critical section
1050+ return x
1051+
1052+
1053+ def g1_body ():
1054+ sorted ([0 ], key = k1 )
1055+ g2 .switch ()
1056+
1057+
1058+ def g2_body ():
1059+ sorted ([0 ], key = k2 )
1060+ # Do a blocking I/O operation which would cause the
1061+ # critical sections to get suspended
1062+ with open (__file__ , "rt" , encoding = 'utf-8' ) as f :
1063+ f .read ()
1064+
1065+
1066+ g1 = greenlet .greenlet (g1_body )
1067+ g2 = greenlet .greenlet (g2_body )
1068+ g1 .switch ()
1069+
10301070
10311071class TestGreenletSetParentErrors (TestCase ):
10321072 def test_threaded_reparent (self ):
0 commit comments