55from test .support import threading_helper
66import _thread as thread
77import time
8+ import warnings
89import weakref
910
1011from test import lock_tests
1112
13+ threading_helper .requires_working_threading (module = True )
14+
1215NUMTASKS = 10
1316NUMTRIPS = 3
14- POLL_SLEEP = 0.010 # seconds = 10 ms
1517
1618_print_mutex = thread .allocate_lock ()
1719
@@ -74,6 +76,14 @@ def test_stack_size(self):
7476 thread .stack_size (0 )
7577 self .assertEqual (thread .stack_size (), 0 , "stack_size not reset to default" )
7678
79+ with self .assertRaises (ValueError ):
80+ # 123 bytes is too small
81+ thread .stack_size (123 )
82+
83+ with self .assertRaises (ValueError ):
84+ # size must be positive
85+ thread .stack_size (- 4096 )
86+
7787 @unittest .skipIf (os .name not in ("nt" , "posix" ), 'test meant for nt and posix' )
7888 def test_nt_and_posix_stack_size (self ):
7989 try :
@@ -119,22 +129,28 @@ def task():
119129
120130 with threading_helper .wait_threads_exit ():
121131 thread .start_new_thread (task , ())
122- while not started :
123- time .sleep (POLL_SLEEP )
132+ for _ in support .sleeping_retry (support .LONG_TIMEOUT ):
133+ if started :
134+ break
124135 self .assertEqual (thread ._count (), orig + 1 )
136+
125137 # Allow the task to finish.
126138 mut .release ()
139+
127140 # The only reliable way to be sure that the thread ended from the
128- # interpreter's point of view is to wait for the function object to be
129- # destroyed.
141+ # interpreter's point of view is to wait for the function object to
142+ # be destroyed.
130143 done = []
131144 wr = weakref .ref (task , lambda _ : done .append (None ))
132145 del task
133- while not done :
134- time .sleep (POLL_SLEEP )
146+
147+ for _ in support .sleeping_retry (support .LONG_TIMEOUT ):
148+ if done :
149+ break
135150 support .gc_collect () # For PyPy or other GCs.
136151 self .assertEqual (thread ._count (), orig )
137152
153+ @unittest .expectedFailure # TODO: RUSTPYTHON
138154 def test_unraisable_exception (self ):
139155 def task ():
140156 started .release ()
@@ -148,11 +164,190 @@ def task():
148164 started .acquire ()
149165
150166 self .assertEqual (str (cm .unraisable .exc_value ), "task failed" )
151- self .assertIs (cm .unraisable .object , task )
167+ self .assertIsNone (cm .unraisable .object )
152168 self .assertEqual (cm .unraisable .err_msg ,
153- "Exception ignored in thread started by" )
169+ f "Exception ignored in thread started by { task !r } " )
154170 self .assertIsNotNone (cm .unraisable .exc_traceback )
155171
172+ def test_join_thread (self ):
173+ finished = []
174+
175+ def task ():
176+ time .sleep (0.05 )
177+ finished .append (thread .get_ident ())
178+
179+ with threading_helper .wait_threads_exit ():
180+ handle = thread .start_joinable_thread (task )
181+ handle .join ()
182+ self .assertEqual (len (finished ), 1 )
183+ self .assertEqual (handle .ident , finished [0 ])
184+
185+ def test_join_thread_already_exited (self ):
186+ def task ():
187+ pass
188+
189+ with threading_helper .wait_threads_exit ():
190+ handle = thread .start_joinable_thread (task )
191+ time .sleep (0.05 )
192+ handle .join ()
193+
194+ def test_join_several_times (self ):
195+ def task ():
196+ pass
197+
198+ with threading_helper .wait_threads_exit ():
199+ handle = thread .start_joinable_thread (task )
200+ handle .join ()
201+ # Subsequent join() calls should succeed
202+ handle .join ()
203+
204+ def test_joinable_not_joined (self ):
205+ handle_destroyed = thread .allocate_lock ()
206+ handle_destroyed .acquire ()
207+
208+ def task ():
209+ handle_destroyed .acquire ()
210+
211+ with threading_helper .wait_threads_exit ():
212+ handle = thread .start_joinable_thread (task )
213+ del handle
214+ handle_destroyed .release ()
215+
216+ def test_join_from_self (self ):
217+ errors = []
218+ handles = []
219+ start_joinable_thread_returned = thread .allocate_lock ()
220+ start_joinable_thread_returned .acquire ()
221+ task_tried_to_join = thread .allocate_lock ()
222+ task_tried_to_join .acquire ()
223+
224+ def task ():
225+ start_joinable_thread_returned .acquire ()
226+ try :
227+ handles [0 ].join ()
228+ except Exception as e :
229+ errors .append (e )
230+ finally :
231+ task_tried_to_join .release ()
232+
233+ with threading_helper .wait_threads_exit ():
234+ handle = thread .start_joinable_thread (task )
235+ handles .append (handle )
236+ start_joinable_thread_returned .release ()
237+ # Can still join after joining failed in other thread
238+ task_tried_to_join .acquire ()
239+ handle .join ()
240+
241+ assert len (errors ) == 1
242+ with self .assertRaisesRegex (RuntimeError , "Cannot join current thread" ):
243+ raise errors [0 ]
244+
245+ def test_join_then_self_join (self ):
246+ # make sure we can't deadlock in the following scenario with
247+ # threads t0 and t1 (see comment in `ThreadHandle_join()` for more
248+ # details):
249+ #
250+ # - t0 joins t1
251+ # - t1 self joins
252+ def make_lock ():
253+ lock = thread .allocate_lock ()
254+ lock .acquire ()
255+ return lock
256+
257+ error = None
258+ self_joiner_handle = None
259+ self_joiner_started = make_lock ()
260+ self_joiner_barrier = make_lock ()
261+ def self_joiner ():
262+ nonlocal error
263+
264+ self_joiner_started .release ()
265+ self_joiner_barrier .acquire ()
266+
267+ try :
268+ self_joiner_handle .join ()
269+ except Exception as e :
270+ error = e
271+
272+ joiner_started = make_lock ()
273+ def joiner ():
274+ joiner_started .release ()
275+ self_joiner_handle .join ()
276+
277+ with threading_helper .wait_threads_exit ():
278+ self_joiner_handle = thread .start_joinable_thread (self_joiner )
279+ # Wait for the self-joining thread to start
280+ self_joiner_started .acquire ()
281+
282+ # Start the thread that joins the self-joiner
283+ joiner_handle = thread .start_joinable_thread (joiner )
284+
285+ # Wait for the joiner to start
286+ joiner_started .acquire ()
287+
288+ # Not great, but I don't think there's a deterministic way to make
289+ # sure that the self-joining thread has been joined.
290+ time .sleep (0.1 )
291+
292+ # Unblock the self-joiner
293+ self_joiner_barrier .release ()
294+
295+ self_joiner_handle .join ()
296+ joiner_handle .join ()
297+
298+ with self .assertRaisesRegex (RuntimeError , "Cannot join current thread" ):
299+ raise error
300+
301+ def test_join_with_timeout (self ):
302+ lock = thread .allocate_lock ()
303+ lock .acquire ()
304+
305+ def thr ():
306+ lock .acquire ()
307+
308+ with threading_helper .wait_threads_exit ():
309+ handle = thread .start_joinable_thread (thr )
310+ handle .join (0.1 )
311+ self .assertFalse (handle .is_done ())
312+ lock .release ()
313+ handle .join ()
314+ self .assertTrue (handle .is_done ())
315+
316+ def test_join_unstarted (self ):
317+ handle = thread ._ThreadHandle ()
318+ with self .assertRaisesRegex (RuntimeError , "thread not started" ):
319+ handle .join ()
320+
321+ def test_set_done_unstarted (self ):
322+ handle = thread ._ThreadHandle ()
323+ with self .assertRaisesRegex (RuntimeError , "thread not started" ):
324+ handle ._set_done ()
325+
326+ @unittest .skipIf (__import__ ("sys" ).platform == "linux" , "TODO: RUSTPYTHON; panic" )
327+ def test_start_duplicate_handle (self ):
328+ lock = thread .allocate_lock ()
329+ lock .acquire ()
330+
331+ def func ():
332+ lock .acquire ()
333+
334+ handle = thread ._ThreadHandle ()
335+ with threading_helper .wait_threads_exit ():
336+ thread .start_joinable_thread (func , handle = handle )
337+ with self .assertRaisesRegex (RuntimeError , "thread already started" ):
338+ thread .start_joinable_thread (func , handle = handle )
339+ lock .release ()
340+ handle .join ()
341+
342+ @unittest .skipIf (__import__ ("sys" ).platform == "linux" , "TODO: RUSTPYTHON; panic" )
343+ def test_start_with_none_handle (self ):
344+ def func ():
345+ pass
346+
347+ with threading_helper .wait_threads_exit ():
348+ handle = thread .start_joinable_thread (func , handle = None )
349+ handle .join ()
350+
156351
157352class Barrier :
158353 def __init__ (self , num_threads ):
@@ -224,19 +419,21 @@ class TestForkInThread(unittest.TestCase):
224419 def setUp (self ):
225420 self .read_fd , self .write_fd = os .pipe ()
226421
227- @unittest . skipUnless ( hasattr ( os , 'fork' ), 'need os.fork' )
422+ @support . requires_fork ( )
228423 @threading_helper .reap_threads
229424 def test_forkinthread (self ):
230425 pid = None
231426
232427 def fork_thread (read_fd , write_fd ):
233428 nonlocal pid
234429
235- # fork in a thread
236- pid = os .fork ()
237- if pid :
238- # parent process
239- return
430+ # Ignore the warning about fork with threads.
431+ with warnings .catch_warnings (category = DeprecationWarning ,
432+ action = "ignore" ):
433+ # fork in a thread (DANGER, undefined per POSIX)
434+ if (pid := os .fork ()):
435+ # parent process
436+ return
240437
241438 # child process
242439 try :
0 commit comments