@@ -1733,6 +1733,84 @@ for _ in range(40):
17331733 worker. join ( ) . expect ( "worker panicked" ) ;
17341734 }
17351735
1736+ /// `STOP_BIT` is process-wide and `start_the_world` in any interpreter
1737+ /// clears it. A worker must still park from its own `stop_requested`.
1738+ #[ cfg( all( feature = "threading" , feature = "rustpython-compiler" ) ) ]
1739+ #[ test]
1740+ fn stop_the_world_parks_when_global_stop_bit_is_cleared ( ) {
1741+ use crate :: compiler:: Mode ;
1742+ use alloc:: sync:: Arc ;
1743+ use core:: {
1744+ sync:: atomic:: { AtomicBool , AtomicU64 , Ordering } ,
1745+ time:: Duration ,
1746+ } ;
1747+
1748+ let main = Interpreter :: without_stdlib ( Default :: default ( ) ) ;
1749+ let sub = main. create_subinterpreter ( ) ;
1750+ let sub_state = sub. enter ( |vm| vm. state . clone ( ) ) ;
1751+
1752+ let progress = Arc :: new ( AtomicU64 :: new ( 0 ) ) ;
1753+ let stop = Arc :: new ( AtomicBool :: new ( false ) ) ;
1754+ let flip = Arc :: new ( AtomicBool :: new ( true ) ) ;
1755+
1756+ let progress_worker = Arc :: clone ( & progress) ;
1757+ let stop_worker = Arc :: clone ( & stop) ;
1758+ let worker = sub. enter ( |vm| {
1759+ let thread_vm = vm. new_thread ( ) ;
1760+ std:: thread:: spawn ( move || {
1761+ thread_vm. run ( |vm| {
1762+ let source = "x = 1 + 1\n " ;
1763+ let code = vm
1764+ . compile ( source, Mode :: Exec , "<spin>" )
1765+ . map_err ( |err| err. into_pyexception ( vm, Some ( source) ) )
1766+ . unwrap ( ) ;
1767+ while !stop_worker. load ( Ordering :: Acquire ) {
1768+ let scope = vm. new_scope_with_builtins ( ) ;
1769+ vm. run_code_obj ( code. clone ( ) , scope) . unwrap ( ) ;
1770+ progress_worker. fetch_add ( 1 , Ordering :: Release ) ;
1771+ }
1772+ } ) ;
1773+ } )
1774+ } ) ;
1775+
1776+ while progress. load ( Ordering :: Acquire ) == 0 {
1777+ std:: thread:: yield_now ( ) ;
1778+ }
1779+
1780+ let flip_flag = Arc :: clone ( & flip) ;
1781+ let flicker = std:: thread:: spawn ( move || {
1782+ while flip_flag. load ( Ordering :: Acquire ) {
1783+ crate :: signal:: set_stop_bit ( ) ;
1784+ crate :: signal:: clear_stop_bit ( ) ;
1785+ }
1786+ } ) ;
1787+
1788+ main. enter ( |_vm| {
1789+ sub_state. stop_the_world . stop_the_world ( & sub_state) ;
1790+
1791+ let parked_at = progress. load ( Ordering :: Acquire ) ;
1792+ std:: thread:: sleep ( Duration :: from_millis ( 50 ) ) ;
1793+ assert_eq ! (
1794+ progress. load( Ordering :: Acquire ) ,
1795+ parked_at,
1796+ "subinterpreter thread kept running after STOP_BIT was cleared"
1797+ ) ;
1798+
1799+ sub_state. stop_the_world . start_the_world ( & sub_state) ;
1800+ } ) ;
1801+
1802+ flip. store ( false , Ordering :: Release ) ;
1803+ flicker. join ( ) . expect ( "stop-bit flicker panicked" ) ;
1804+
1805+ let resumed_from = progress. load ( Ordering :: Acquire ) ;
1806+ while progress. load ( Ordering :: Acquire ) == resumed_from {
1807+ std:: thread:: yield_now ( ) ;
1808+ }
1809+
1810+ stop. store ( true , Ordering :: Release ) ;
1811+ worker. join ( ) . expect ( "worker panicked" ) ;
1812+ }
1813+
17361814 /// Entering a subinterpreter from inside the parent's `enter` must attach
17371815 /// the subinterpreter's thread slot (and detach the parent's). Otherwise the
17381816 /// thread runs the sub's bytecode with a DETACHED slot, and a collector
0 commit comments