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 <damien@micropython.org>
This commit is contained in:
Damien George 2024-01-05 09:53:05 +11:00
parent 231fc20ce0
commit 2265d70add
8 changed files with 93 additions and 26 deletions

View File

@ -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("cmdline/repl_sys_ps1_ps2.py")
skip_tests.add("extmod/ssl_poll.py") skip_tests.add("extmod/ssl_poll.py")
# Some tests shouldn't be run on a PC # Skip thread mutation tests on targets that don't have the GIL.
if args.target == "unix": if args.target in ("rp2", "unix"):
# unix build does not have the GIL so can't run thread mutation tests
for t in tests: for t in tests:
if t.startswith("thread/mutate_"): if t.startswith("thread/mutate_"):
skip_tests.add(t) 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 # Some tests shouldn't be run on pyboard
if args.target != "unix": if args.target != "unix":
skip_tests.add("basics/exception_chain.py") # warning is not printed 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"): elif args.target in ("renesas-ra"):
test_dirs += ("float", "inlineasm", "renesas-ra") test_dirs += ("float", "inlineasm", "renesas-ra")
elif args.target == "rp2": elif args.target == "rp2":
test_dirs += ("float", "stress", "inlineasm") test_dirs += ("float", "stress", "inlineasm", "thread")
elif args.target in ("esp8266", "esp32", "minimal", "nrf"): elif args.target in ("esp8266", "esp32", "minimal", "nrf"):
test_dirs += ("float",) test_dirs += ("float",)
elif args.target == "wipy": elif args.target == "wipy":

View File

@ -19,6 +19,7 @@ _TIMEOUT_MS = 10000
n = 0 # How many times the task successfully ran. n = 0 # How many times the task successfully ran.
t = None # Start time of test, assigned here to preallocate entry in globals dict. 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): def task(x):
@ -27,7 +28,7 @@ def task(x):
def thread(): def thread():
while True: while thread_run:
try: try:
micropython.schedule(task, None) micropython.schedule(task, None)
except RuntimeError: except RuntimeError:
@ -36,13 +37,21 @@ def thread():
for i in range(8): 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. # Wait up to 10 seconds for 10000 tasks to be scheduled.
t = time.ticks_ms() t = time.ticks_ms()
while n < _NUM_TASKS and time.ticks_diff(time.ticks_ms(), t) < _TIMEOUT_MS: while n < _NUM_TASKS and time.ticks_diff(time.ticks_ms(), t) < _TIMEOUT_MS:
pass pass
# Stop all threads.
thread_run = False
time.sleep_ms(20)
if n < _NUM_TASKS: if n < _NUM_TASKS:
# Not all the tasks were scheduled, likely the scheduler stopped working. # Not all the tasks were scheduled, likely the scheduler stopped working.
print(n) print(n)

View File

@ -16,21 +16,34 @@ def thread_entry(n):
data[i] = data[i] data[i] = data[i]
gc.collect() 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: with lock:
print(list(data) == list(range(256))) global n_correct, n_finished
global n_finished n_correct += list(data) == list(range(256))
n_finished += 1 n_finished += 1
lock = _thread.allocate_lock() lock = _thread.allocate_lock()
n_thread = 4 n_thread = 0
n_thread_max = 4
n_correct = 0
n_finished = 0 n_finished = 0
# spawn threads # spawn threads
for i in range(n_thread): for _ in range(n_thread_max):
_thread.start_new_thread(thread_entry, (10,)) 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 # busy wait for threads to finish
while n_finished < n_thread: while n_finished < n_thread:
pass pass
print(n_correct == n_finished)

View File

@ -5,7 +5,12 @@
import _thread 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 = None
tid_main = None
new_tid = None
finished = False
def thread_entry(): def thread_entry():
@ -19,7 +24,6 @@ def thread_entry():
tid_main = _thread.get_ident() tid_main = _thread.get_ident()
print("main", type(tid_main) == int, tid_main != 0) print("main", type(tid_main) == int, tid_main != 0)
finished = False
new_tid = _thread.start_new_thread(thread_entry, ()) new_tid = _thread.start_new_thread(thread_entry, ())
while not finished: while not finished:

View File

@ -36,14 +36,18 @@ output_lock = _thread.allocate_lock()
# spawn threads to do the jobs # spawn threads to do the jobs
for i in range(4): 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 # wait for the jobs to complete
while True: while True:
with jobs_lock: with jobs_lock:
if len(output) == n_jobs: if len(output) == n_jobs:
break break
time.sleep(1) time.sleep(0)
# sort and print the results # sort and print the results
output.sort(key=lambda x: x[0]) output.sort(key=lambda x: x[0])

View File

@ -24,16 +24,26 @@ def th(base, n):
lock = _thread.allocate_lock() lock = _thread.allocate_lock()
n_thread = 4 n_thread = 0
n_thread_max = 4
n_finished = 0 n_finished = 0
n_qstr_per_thread = 100 # make 1000 for a more stressful test (uses more heap) n_qstr_per_thread = 100 # make 1000 for a more stressful test (uses more heap)
# spawn threads # spawn threads
for i in range(n_thread): for _ in range(n_thread_max):
_thread.start_new_thread(th, (i * n_qstr_per_thread, n_qstr_per_thread)) 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 # wait for threads to finish
while n_finished < n_thread: while n_finished < n_thread:
time.sleep(1) time.sleep(0)
print("pass") print("pass")

View File

@ -18,15 +18,25 @@ def thread_entry(n, tup):
lock = _thread.allocate_lock() lock = _thread.allocate_lock()
n_thread = 2 n_thread = 0
n_thread_max = 2
n_finished = 0 n_finished = 0
# the shared data structure # the shared data structure
tup = (1, 2, 3, 4) tup = (1, 2, 3, 4)
# spawn threads # spawn threads
for i in range(n_thread): for _ in range(n_thread_max):
_thread.start_new_thread(thread_entry, (100, tup)) 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 # busy wait for threads to finish
while n_finished < n_thread: while n_finished < n_thread:

View File

@ -12,7 +12,8 @@ else:
import _thread import _thread
lock = _thread.allocate_lock() lock = _thread.allocate_lock()
n_thread = 4 n_thread = 0
n_thread_max = 4
n_finished = 0 n_finished = 0
@ -24,10 +25,20 @@ def thread_entry(t):
n_finished += 1 n_finished += 1
for i in range(n_thread): # spawn threads
_thread.start_new_thread(thread_entry, (10 * i,)) 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 # wait for threads to finish
while n_finished < n_thread: while n_finished < n_thread:
sleep_ms(100) sleep_ms(100)
print("done", n_thread) print("done")