-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
tests/run-tests.py: Auto detect whether the target has threading and the GIL or not. #17655
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #17655 +/- ##
==========================================
- Coverage 98.44% 98.41% -0.04%
==========================================
Files 171 171
Lines 22208 22208
==========================================
- Hits 21863 21855 -8
- Misses 345 353 +8 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code size report:
|
d63cbda
to
b90d11e
Compare
tests/thread/stress_aes.py
Outdated
try: | ||
|
||
def stackless(): | ||
pass | ||
|
||
micropython.heap_lock() | ||
stackless() | ||
micropython.heap_unlock() | ||
except RuntimeError: | ||
micropython.heap_unlock() | ||
is_stackless = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try: | |
def stackless(): | |
pass | |
micropython.heap_lock() | |
stackless() | |
micropython.heap_unlock() | |
except RuntimeError: | |
micropython.heap_unlock() | |
is_stackless = True | |
def stackless(): | |
pass | |
micropython.heap_lock() | |
try: | |
stackless() | |
except RuntimeError: | |
is_stackless = True | |
finally: | |
micropython.heap_unlock() |
does it work to make the try/except a bit more focused on where the actual exception is expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, it's probably better to be more focused like that. I will change it.
(I was just copying existing code for this from eg tests/micropython/heapalloc.py
.)
ports/unix/mpthreadport.c
Outdated
@@ -56,7 +56,7 @@ | |||
#endif | |||
|
|||
// This value seems to be about right for both 32-bit and 64-bit builds. | |||
#define THREAD_STACK_OVERFLOW_MARGIN (8192) | |||
#define THREAD_STACK_OVERFLOW_MARGIN (4 * 8192) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#define THREAD_STACK_OVERFLOW_MARGIN (4 * 8192) | |
#define THREAD_STACK_OVERFLOW_MARGIN (16 * 1024) |
Also, the commit message could use an explanation of why this is changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm currently trying to work out if this change is even needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is not needed, it doesn't fix the issue I thought it would fix. So I took it out.
tests/feature_check/thread_gil.py
Outdated
thread_done = True | ||
|
||
|
||
global thread_done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we actually need global
statement at the top level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, now fixed.
tests/feature_check/thread_gil.py
Outdated
busy() | ||
while not thread_done: | ||
time.sleep(0) | ||
dt = time.ticks_diff(time.ticks_ms(), t0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on time measurements seems like it could be flaky, especially on desktop systems. I'm not awake enough yet though to try to come up with an alternative that doesn't involve creating a deadlock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it could be flaky. But from testing on bare-metal and PC, it looks reasonable. The timing is usually either very close to T or 2*T (depending if GIL is disable or enabled).
If it fails due to timing errors (running for too long), it will incorrectly think the GIL is enabled when it is not. Then it will run the mutate tests and they'll probably all fail. In such a case we can just rerun CI...
If it gets to be a real issue, could increase the timing here, eg double it (I just don't want the auto detection to take too long, because it's run at the start of each run of run-tests.py
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some similar concerns, and generally prefer the approach of explicitly adding this information in sys.implementation
or similar. Suggest we keep this approach now, but keep the explicit option in mind if it turns out to be flaky.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having some information in sys.implementation
could actually be useful for users, instead of them having to run a tricky detection test like this (if they ever need to know the threading implementation...).
And I guess if you have threading enabled you have some amount of flash to spare (although not necessarily on cc3200 which does enable threading...).
So maybe it's worth adding sys.implementation._thread
after all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be useful to add. Especially if we don't add any _thread
property on builds without threading support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've now changed this GIL detection to use sys.implementation._thread
. It's a lot simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And retested on PICO_W, ESP32_GENERIC and unix port. They detect the GIL correctly and the relevant tests run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much neater, I agree worth the extra size & complexity!
Thanks @dlech for the review here. After I initially submitted this PR and all the CI failed on things I couldn't test locally, the scope of the PR increased quite a bit... so there was a lot of back and forth getting it to work. |
bf0c6cb
to
1266375
Compare
05a9ceb
to
28535b0
Compare
After a lot of back-and-forth with the CI, this PR is now in its final state. I updated the comment at the top with a better description of what this PR is aiming for. |
28535b0
to
3c1c9ab
Compare
docs/library/sys.rst
Outdated
The *_thread* entry was added in version 1.26.0 and if it exists then the | ||
target has the ``_thread`` module. The target enables the GIL (global | ||
interpreter lock) if this attribute equals ``"GIL"``, otherwise the attribute | ||
is ``"no-GIL"`` and the target does not enable the GIL. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think MicroPython will ever have a variant that is non-GIL but thread safe (ala CPython --disable-gil
?)
If yes then maybe we could make the attribute values GIL
and unsafe
(or similar), keeping the door open for a safe
value in some future release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Would mean tests which test for no-GIL will need to test _thread != "GIL"
, I guess
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think MicroPython will ever have a variant that is non-GIL but thread safe
Yes, that's a possibility. But, I would suspect that we gradually evolve the current no-GIL mode to a more safer behaviour, rather than have separate options for GIL/unsafe-no-GIL/safe-no-GIL.
That said, I'm happy to change "no-GIL" to "unsafe". Although that could scare users, but OTOH it might push them to read the docs to understand what "unsafe" actually means.
Is there another word that could better describe the situation, eg "racey" or "raceful"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's OK if this scares users! It's quite difficult to write threaded Python correctly with a thread unsafe runtime.
But, I would suspect that we gradually evolve the current no-GIL mode to a more safer behaviour, rather than have separate options for GIL/unsafe-no-GIL/safe-no-GIL.
Agree this would be the best way to evolve it. From perspective of someone targeting MicroPython it may still be useful to programmatically tell if the current runtime is thread safe or not (i.e. even if no MicroPython release supports all three values, there might still be code that checks for all three.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I've now renamed "no-GIL" to "unsafe", and updated the docs.
Also tested again on esp32, rp2 and unix.
This is useful to distinguish between GIL and non-GIL builds. Signed-off-by: Damien George <damien@micropython.org>
This is a workaround for the case where threading is enabled without a GIL. In such a configuration, creating a new global variable is not atomic and threads have race conditions resizing/accessing the global dict. Signed-off-by: Damien George <damien@micropython.org>
Builds with stackless enabled are slower than non-stackless, and this test takes around 2 minutes on the unix port of MicroPython with the standard unix parameters. So detect stackless mode and reduce the time of the test. Signed-off-by: Damien George <damien@micropython.org>
When detecting the target platform, also check if it has threading and whether the GIL is enabled or not (using the new attribute `sys.implementation._thread`). If threading is available, add the thread tests to the set of tests to run (unless the set of tests is explicitly given). With this change, the unix port no longer needs to explicitly run the set of thread tests, so that line has been removed from the Makefile. This change will make sure thread tests are run with other testing combinations. In particular, thread tests are now run: - on the unix port with the native emitter - on macOS builds - on unix qemu, the architectures MIPS, ARM and RISCV-64 Signed-off-by: Damien George <damien@micropython.org>
The qemu emulation introduces enough overhead that the `tests/thread/stress_aes.py` test overruns the default timeout. So increase it to allow this test to pass. Signed-off-by: Damien George <damien@micropython.org>
This test passes sometimes and fails other times. Eventually that should be fixed, but for now just skip this test. Signed-off-by: Damien George <damien@micropython.org>
This test passes sometimes and fails other times. Eventually that should be fixed, but for now just skip this test. Signed-off-by: Damien George <damien@micropython.org>
3c1c9ab
to
9caf2ce
Compare
Summary
This PR makes the following changes to the test runner and CI:
_thread
module and, if so, add the threading tests to the set of tests to runtests/extmod/select_poll_eintr.py
testtests/thread/stress_aes.py
test do less work on stackless threading builds, so it finishes before the test timeout limitCurrently the information whether a port has the GIL is hard-coded: unix/PC targets and the rp2 port are considered to be GIL-less, and all other targets have the GIL enabled (if they have threading enabled). With the change here, some code is run to detect the GIL. That uses the fact that native code won't bounce the GIL and can hog it, and times how long code runs.
Overall the changes here mean that the threading tests are run in many more configurations:
Testing
Tested locally on the unix port, esp32 and rp2. Let's also see how the CI works.
Trade-offs and Alternatives
This uses a tricky technique to determine GIL/non-GIL. An alternative would be to add some indication in a function whether the GIL is enabled, eg
sys.implementation._thread_info
. But that will increase code size.