unix: Improve support for FreeBSD.#18979
Conversation
This commit makes two failing FFI tests pass on FreeBSD 15.0-RELEASE. Those tests explicitly looked for the system's libc and libm libraries by versioned file names, both of which do not have matches on current FreeBSD installations. The new versioned file names have been added to make the tests pass. Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
This commit fixes the default system libraries path on FreeBSD, as its filesystem conventions differ from regular Linux (which seems to be the default chosen here). On FreeBSD "/usr/lib" is used exclusively by base system packages, and anything else is supposed to either use "/usr/local/lib" or something else under "/opt". Now builds for FreeBSD will use a sensible default system path, which should also easen the burden of whoever will update the FreeBSD ports' version of MicroPython. This in theory should work on other BSD systems as well but that assumption was not tested. Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
This commit lets the "sys" module report the correct value of "sys.platform" on FreeBSD systems. The only two platforms known to the module were Darwin and Linux, with the latter used as a fallback. Now FreeBSD is also recognised as such, with the appropriate string being set. Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
This commit lets the platform module recognise FreeBSD as its own thing. A new entry in the libc is added, simply called "libc" (I didn't find any more descriptive name out there), and the platform is reported as "FreeBSD" when applicable. Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
|
Code size report: |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #18979 +/- ##
=======================================
Coverage 98.46% 98.47%
=======================================
Files 176 176
Lines 22784 22845 +61
=======================================
+ Hits 22435 22497 +62
+ Misses 349 348 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
dpgeorge
left a comment
There was a problem hiding this comment.
Thanks for this, it's a nice improvement.
Just a few comments.
| } | ||
| #if defined(__FreeBSD__) | ||
| else if (ret == EDEADLK) { | ||
| return 0; |
There was a problem hiding this comment.
Maybe this case should be an error (and hence raise an exception on the Python side)? Because if you ask to wait for the lock and don't get it... that's unexpected.
There was a problem hiding this comment.
As far as I know, this occurs whenever a thread attempts to wait on a lock that it already owns: https://github.com/freebsd/freebsd-src/blob/62c1865c9aaef436498c444b460e6ec2fbcaf44d/sys/kern/kern_lock.c#L614 (and https://github.com/freebsd/freebsd-src/blob/62c1865c9aaef436498c444b460e6ec2fbcaf44d/sys/kern/kern_lockf.c#L893 although there are more instances in that file).
However I see that mutexes are initialised with PTHREAD_MUTEX_RECURSIVE so, assuming this flag is actually taken into account, my only guess is that the way threads are handled in MicroPython triggers a potential cycle in FreeBSD's lock graph. One may assume that it's a transient situation, hence the aliasing that to Linux's EBUSY case (or possibly treat it as if it returned EAGAIN).
Removing this particular bit may also require more changes since EDEADLK isn't part of py/mperrno.h and has to be added there to handle the situation cleanly in the affected tests. My main concern with this is that EDEADLK on Linux comes from pthreads, so in theory it may not be there. Also, I'm not entirely sure if aliasing EDEADLK to EAGAIN at the errno level is the correct thing to do w.r.t. threads since on non-Linux EAGAIN may mean something slightly different in other cases.
There was a problem hiding this comment.
I just installed FreeBSD 15.0-RELEASE-p9 in a VM, and built the unix port (standard variant) from master (ie not this PR) with clang (I disabled BTREE, FFI, SSL options). It runs the tests/threads/thread_coop.py test just fine, whereas the commit message here says the EDEADLK code is needed to pass that test.
All the thread tests pass for me on master, except the two you noted: thread_heap_lock.py and thread_lock5.py. And they fail exactly due to raising EDEADLK! Those tests do test the main thread re-acquiring a lock, but a separate thread is supposed to release it. Maybe FreeBSD doesn't support this use case?? Although that would be very surprising.
There's something here that needs fixing. The thread_heap_lock.py and thread_lock5.py tests are giving a big clue, and ideally we would get them passing.
There was a problem hiding this comment.
OK, so mutexes associated with _thread.allocate_lock() are not initialize recursively. So that's why FreeBSD is returning EDEADLK, because the thread tries to acquire it a second time (in those two tests that fail).
This works OK on Linux (at least) but obviously not FreeBSD's implementation of pthreads.
Making it recursive won't work, it's supposed to block the main thread ("deadlock" it actually) until another thread releases the lock.
Maybe we need to use semaphores instead to implement thread.allocate_lock.
There was a problem hiding this comment.
I'd be surprised too if releasing a mutex from a different thread isn't possible on FreeBSD.
I wish I remember which patch level I used when drafting this PR, but I'm quite sure thread_coop.py failed on me because EDEADLK was returned, I seem to recall it happened when the two threads' execution models were swapped. It could have been that it took longer than expected to shutdown the thread on FreeBSD, but given the situation a retry condition made sense.
Still, yes, there's more to look at! I'll re-create the VM and look at this too. Thanks for trying this out on your end as well.
There was a problem hiding this comment.
OK, mystery solved! Here's the relevant part of CPython's _thread implementation (Python/thread_pthread.h):
/* A pthread mutex isn't sufficient to model the Python lock type
* because, according to Draft 5 of the docs (P1003.4a/D5), both of the
* following are undefined:
* -> a thread tries to lock a mutex it already has locked
* -> a thread tries to unlock a mutex locked by a different thread
* pthread mutexes are designed for serializing threads over short pieces
* of code anyway, so wouldn't be an appropriate implementation of
* Python's locks regardless.
*
* The pthread_lock struct implements a Python lock as a "locked?" bit
* and a <condition, mutex> pair. In general, if the bit can be acquired
* instantly, it is, else the pair is used to block the thread until the
* bit is cleared. 9 May 1994 tim@ksr.com
*/
typedef struct {
char locked; /* 0=unlocked, 1=locked */
/* a <cond, mutex> pair to handle an acquire of a locked lock */
pthread_cond_t lock_released;
pthread_mutex_t mut;
} pthread_lock;There was a problem hiding this comment.
Interesting. Looks like the situation is specified to be ambiguous too! From https://pubs.opengroup.org/onlinepubs/007908799/xsh/pthread_mutex_lock.html:
If the mutex type is PTHREAD_MUTEX_RECURSIVE, then the mutex maintains the concept of a lock count. When a thread successfully acquires a mutex for the first time, the lock count is set to one. Every time a thread relocks this mutex, the lock count is incremented by one. Each time the thread unlocks the mutex, the lock count is decremented by one. When the lock count reaches zero, the mutex becomes available for other threads to acquire. If a thread attempts to unlock a mutex that it has not locked or a mutex which is unlocked, an error will be returned.
[...]
The pthread_mutex_lock() function may fail if:[EDEADLK]
The current thread already owns the mutex.
[...]
The pthread_mutex_unlock() function may fail if:[EPERM]
The current thread does not own the mutex.
So, recursive mutexes are specified but may not work, and threads may or may not unlock other threads' mutexes...
There was a problem hiding this comment.
It's quite a bit of refactoring to fix this properly.
For this PR I suggest just undo the changes to this file and accept the thread test failures under FreeBSD for now.
There was a problem hiding this comment.
Done. I've updated tests/extmod/select_poll_fd.py as well.
| raise SystemExit | ||
| except (ImportError, AttributeError): | ||
| print("SKIP") | ||
| raise SystemExit |
There was a problem hiding this comment.
Having the sys module is a requirement for a port to be able to run the tests, but sys.platform can be missing.
If a port has the select and errno modules then it's highly likely it has sys.platform. So I suggest putting the check after the import of select/errno, and just access sys.platform without any try/except, like this:
if sys.platform == "freebsd":
print("SKIP")
raise SystemExitThere was a problem hiding this comment.
Turns out sys may not be there, after all. This test is also checked against CPython, which fails with an import error (eg. https://github.com/micropython/micropython/actions/runs/27251624169/job/80477170312?pr=18979).
There was a problem hiding this comment.
Ah, sorry, I meant that a port is required to implement the sys module (I didn't mean that sys will already be imported).
Anyway, what you have here is good!
This commit makes the `extmod/select_poll_fd` test get skipped on
FreeBSD.
The test depends on the maximum number of available file descriptors
being less than 6000, which is not the case on FreeBSD (it's ~120k).
Right now there's no way to query the value directly or an easy way to
get the output of `os.system("ulimit -n")`, so the test is skipped on
FreeBSD.
Signed-off-by: Alessandro Gatti <a.gatti@frob.it>
Summary
This PR addresses a few minor issues related to MicroPython on the FreeBSD platform:
libc.so.6is no longer there, now it's calledlibc.so.7)sys.pathfor the standard library pointed to/usr/lib/micropyhon, but on FreeBSD anything outside the base system files has to reside elsewhere; the usual root under/usrfor third party packages is/usr/localsys.platformnow reports it's running on FreeBSD instead of Linuxplatform.platformnow reports FreeBSD as the operating systemextmod/select_poll_fdtest is skipped on FreeBSDThis is technically the continuation of micropython/micropython-lib#1099, as it fixes FFI support on recent FreeBSD versions in
micropython/micropython-lib.Testing
The test suite was executed in a FreeBSD 15.0-RELEASE x64 virtual machine, with no FFI-related test failures.
thread/thread_heap_lock, andthread/thread_lock5were failing before these changes as well. I haven't yet looked into those two tests.Trade-offs and Alternatives
The changes in this PR should affect MicroPython's behaviour exclusively when built on FreeBSD. FFI and
sys.pathchanges might also apply to OpenBSD, NetBSD, and their derivatives, but they weren't tested on those OSes.Generative AI
I did not use generative AI tools when creating this PR.