From 2265d70add3ecfaf2ffbfb8393ab29a887d8d7fd Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 5 Jan 2024 09:53:05 +1100 Subject: [PATCH] tests/thread: Adjust thread tests so most are able to run on rp2 port. The aim of this commit is to make it so that the existing thread tests can be used to test the _thread module on the rp2 port. The rp2 port only allows up to one thread to be created at a time, and does not have the GIL enabled. The following changes have been made: - run-tests.py skips mutation tests on rp2, because there's no GIL. - run-tests.py skips other tests on rp2 that require more than one thread. - The tests stop trying to start a new thread after there is an OSError, which indicates that the system cannot create more threads. - Some of these tests also now run the test function on the main thread, not just the spawned threads. - In some tests the output printing is adjusted so it's the same regardless of how many threads were spawned. - Some time.sleep(1) are replaced with time.sleep(0) to make the tests run a little faster (finish sooner when the work is done). For the most part the tests are unchanged for existing platforms like esp32 and unix. Signed-off-by: Damien George --- tests/run-tests.py | 14 ++++++++++---- tests/thread/stress_schedule.py | 13 +++++++++++-- tests/thread/thread_gc1.py | 25 +++++++++++++++++++------ tests/thread/thread_ident1.py | 6 +++++- tests/thread/thread_lock4.py | 8 ++++++-- tests/thread/thread_qstr1.py | 18 ++++++++++++++---- tests/thread/thread_shared1.py | 16 +++++++++++++--- tests/thread/thread_sleep1.py | 19 +++++++++++++++---- 8 files changed, 93 insertions(+), 26 deletions(-) diff --git a/tests/run-tests.py b/tests/run-tests.py index e5d7123036..bbc98cad96 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -562,13 +562,19 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1): skip_tests.add("cmdline/repl_sys_ps1_ps2.py") skip_tests.add("extmod/ssl_poll.py") - # Some tests shouldn't be run on a PC - if args.target == "unix": - # unix build does not have the GIL so can't run thread mutation tests + # Skip thread mutation tests on targets that don't have the GIL. + if args.target in ("rp2", "unix"): for t in tests: if t.startswith("thread/mutate_"): skip_tests.add(t) + # Skip thread tests that require many threads on targets that don't support multiple threads. + if args.target == "rp2": + skip_tests.add("thread/stress_heap.py") + skip_tests.add("thread/thread_lock2.py") + skip_tests.add("thread/thread_lock3.py") + skip_tests.add("thread/thread_shared2.py") + # Some tests shouldn't be run on pyboard if args.target != "unix": skip_tests.add("basics/exception_chain.py") # warning is not printed @@ -987,7 +993,7 @@ the last matching regex is used: elif args.target in ("renesas-ra"): test_dirs += ("float", "inlineasm", "renesas-ra") elif args.target == "rp2": - test_dirs += ("float", "stress", "inlineasm") + test_dirs += ("float", "stress", "inlineasm", "thread") elif args.target in ("esp8266", "esp32", "minimal", "nrf"): test_dirs += ("float",) elif args.target == "wipy": diff --git a/tests/thread/stress_schedule.py b/tests/thread/stress_schedule.py index 40191e6620..a5d7dc824e 100644 --- a/tests/thread/stress_schedule.py +++ b/tests/thread/stress_schedule.py @@ -19,6 +19,7 @@ _TIMEOUT_MS = 10000 n = 0 # How many times the task successfully ran. t = None # Start time of test, assigned here to preallocate entry in globals dict. +thread_run = True # If the thread should continue running. def task(x): @@ -27,7 +28,7 @@ def task(x): def thread(): - while True: + while thread_run: try: micropython.schedule(task, None) except RuntimeError: @@ -36,13 +37,21 @@ def thread(): for i in range(8): - _thread.start_new_thread(thread, ()) + try: + _thread.start_new_thread(thread, ()) + except OSError: + # System cannot create a new thead, so stop trying to create them. + break # Wait up to 10 seconds for 10000 tasks to be scheduled. t = time.ticks_ms() while n < _NUM_TASKS and time.ticks_diff(time.ticks_ms(), t) < _TIMEOUT_MS: pass +# Stop all threads. +thread_run = False +time.sleep_ms(20) + if n < _NUM_TASKS: # Not all the tasks were scheduled, likely the scheduler stopped working. print(n) diff --git a/tests/thread/thread_gc1.py b/tests/thread/thread_gc1.py index dd1e64d894..b36ea9d4c8 100644 --- a/tests/thread/thread_gc1.py +++ b/tests/thread/thread_gc1.py @@ -16,21 +16,34 @@ def thread_entry(n): data[i] = data[i] gc.collect() - # print whether the data remains intact and indicate we are finished + # check whether the data remains intact and indicate we are finished with lock: - print(list(data) == list(range(256))) - global n_finished + global n_correct, n_finished + n_correct += list(data) == list(range(256)) n_finished += 1 lock = _thread.allocate_lock() -n_thread = 4 +n_thread = 0 +n_thread_max = 4 +n_correct = 0 n_finished = 0 # spawn threads -for i in range(n_thread): - _thread.start_new_thread(thread_entry, (10,)) +for _ in range(n_thread_max): + try: + _thread.start_new_thread(thread_entry, (10,)) + n_thread += 1 + except OSError: + # System cannot create a new thead, so stop trying to create them. + break + +# also run the function on this main thread +thread_entry(10) +n_thread += 1 # busy wait for threads to finish while n_finished < n_thread: pass + +print(n_correct == n_finished) diff --git a/tests/thread/thread_ident1.py b/tests/thread/thread_ident1.py index 8e106cd317..2a3732eff5 100644 --- a/tests/thread/thread_ident1.py +++ b/tests/thread/thread_ident1.py @@ -5,7 +5,12 @@ import _thread +# Initialise variables (also preallocate their spot in the globals dict so the +# globals dict is not resized while threads are running). tid = None +tid_main = None +new_tid = None +finished = False def thread_entry(): @@ -19,7 +24,6 @@ def thread_entry(): tid_main = _thread.get_ident() print("main", type(tid_main) == int, tid_main != 0) -finished = False new_tid = _thread.start_new_thread(thread_entry, ()) while not finished: diff --git a/tests/thread/thread_lock4.py b/tests/thread/thread_lock4.py index 97c3dc5380..b424ee3d02 100644 --- a/tests/thread/thread_lock4.py +++ b/tests/thread/thread_lock4.py @@ -36,14 +36,18 @@ output_lock = _thread.allocate_lock() # spawn threads to do the jobs for i in range(4): - _thread.start_new_thread(thread_entry, ()) + try: + _thread.start_new_thread(thread_entry, ()) + except OSError: + # System cannot create a new thead, so stop trying to create them. + break # wait for the jobs to complete while True: with jobs_lock: if len(output) == n_jobs: break - time.sleep(1) + time.sleep(0) # sort and print the results output.sort(key=lambda x: x[0]) diff --git a/tests/thread/thread_qstr1.py b/tests/thread/thread_qstr1.py index f184d2a58e..96f12c5d8c 100644 --- a/tests/thread/thread_qstr1.py +++ b/tests/thread/thread_qstr1.py @@ -24,16 +24,26 @@ def th(base, n): lock = _thread.allocate_lock() -n_thread = 4 +n_thread = 0 +n_thread_max = 4 n_finished = 0 n_qstr_per_thread = 100 # make 1000 for a more stressful test (uses more heap) # spawn threads -for i in range(n_thread): - _thread.start_new_thread(th, (i * n_qstr_per_thread, n_qstr_per_thread)) +for _ in range(n_thread_max): + try: + _thread.start_new_thread(th, (n_thread * n_qstr_per_thread, n_qstr_per_thread)) + n_thread += 1 + except OSError: + # System cannot create a new thead, so stop trying to create them. + break + +# also run the function on this main thread +th(n_thread * n_qstr_per_thread, n_qstr_per_thread) +n_thread += 1 # wait for threads to finish while n_finished < n_thread: - time.sleep(1) + time.sleep(0) print("pass") diff --git a/tests/thread/thread_shared1.py b/tests/thread/thread_shared1.py index 582b01fc34..251e26fae6 100644 --- a/tests/thread/thread_shared1.py +++ b/tests/thread/thread_shared1.py @@ -18,15 +18,25 @@ def thread_entry(n, tup): lock = _thread.allocate_lock() -n_thread = 2 +n_thread = 0 +n_thread_max = 2 n_finished = 0 # the shared data structure tup = (1, 2, 3, 4) # spawn threads -for i in range(n_thread): - _thread.start_new_thread(thread_entry, (100, tup)) +for _ in range(n_thread_max): + try: + _thread.start_new_thread(thread_entry, (100, tup)) + n_thread += 1 + except OSError: + # System cannot create a new thead, so stop trying to create them. + break + +# also run the function on this main thread +thread_entry(100, tup) +n_thread += 1 # busy wait for threads to finish while n_finished < n_thread: diff --git a/tests/thread/thread_sleep1.py b/tests/thread/thread_sleep1.py index add9b02f15..36f775d1f6 100644 --- a/tests/thread/thread_sleep1.py +++ b/tests/thread/thread_sleep1.py @@ -12,7 +12,8 @@ else: import _thread lock = _thread.allocate_lock() -n_thread = 4 +n_thread = 0 +n_thread_max = 4 n_finished = 0 @@ -24,10 +25,20 @@ def thread_entry(t): n_finished += 1 -for i in range(n_thread): - _thread.start_new_thread(thread_entry, (10 * i,)) +# spawn threads +for _ in range(n_thread_max): + try: + _thread.start_new_thread(thread_entry, (10 * n_thread,)) + n_thread += 1 + except OSError: + # System cannot create a new thead, so stop trying to create them. + break + +# also run the function on this main thread +thread_entry(10 * n_thread) +n_thread += 1 # wait for threads to finish while n_finished < n_thread: sleep_ms(100) -print("done", n_thread) +print("done")