-
Notifications
You must be signed in to change notification settings - Fork 121
Support free-threaded Python #469
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
|
@bastibe gentle ping on this. If you'd like any help on my end with other maintenance tasks on the repo I'm also happy to do that, especially if that makes you more likely to help me ship this! |
|
I'm sorry for not being able to work on this at the moment. Fall is a very busy time for me. I hope I'll be able to have a look on the weekend. |
|
Sorry for not responding for so long. It was indeed an incredibly busy time for me. At any rate, I've had a look now. This pull request adds a lock to each soundfile object, thereby preventing any concurrent use. This prevents crashes if you write code that accesses the same soundfile object from multiple threads. However, I am a bit hesitant about this, as it doesn't fix concurrency, but merely prevents it. It's certainly a better user experience to throw an exception instead of crashing with a segfault, but I'm not sure if this is actually helping. I've not followed the multi-threaded python development particularly closely. What is the common pattern for dealing with non-thread-safe code? Do we expect all non-thread-safe code to secure itself? |
Not necessarily - another option is to document the thread safety issues and leave it up to users to handle it, including any crashes that might happen. We've written some documentation on multithreaded Python programming patterns here: https://py-free-threading.github.io/porting/#multithreaded-python-programming There's also some documentation here on similar patterns for more low-level code: https://py-free-threading.github.io/porting-extensions/#dealing-with-thread-unsafe-native-libraries Soundfile uses cffi, so it's a combination of the two patterns: interacting with a low-level library directly from Python. Another way to handle this would be to lock the mutex using a blocking lock and allow concurrent uses of a shared SoundFile. For that case - do you have a workflow in mind where threads would concurrently work with the same SoundFile object? I could then write a test that models this workflow. To assuage concerns about the approach used in this PR: it's also always fine to change an exception into something else in the future, so if you are able to figure out a better concurrency story, you could always remove the locking and exceptions. This approach also doesn't change the status quo in that anyone trying to use python-soundfile in a multithreaded program (free-threaded build or no) will see crashes that this PR turns into runtime errors. So anyone using this library with threads is already dealing with this somehow, e.g. by not sharing the SoundFIile objects between threads or with some other external synchronization approach. |
| with: | ||
| python-version: ${{ matrix.python-version }} | ||
| architecture: ${{ matrix.architecture }} | ||
| allow-prereleases: 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.
This isn't necessary now that Python3.14 final is out.
| allow-prereleases: true |
| "cffi>=2.0.0b1; python_version >= '3.14'", | ||
| 'numpy', | ||
| 'typing-extensions' | ||
| ], |
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 I can revert all this now that there's a final CFFI release out. Python 3.14 can only install CFFI 2.0 or newer.
Closes #467
This adds support for Python 3.14 and 3.14t as well as supporting free-threaded Python.
I've opted to add a
threading.RLockmember to theSoundFileobject. I also made it so all the public methods ofSoundFilethat access the_filehandle need to acquire the lock via a newwith_lockdecorator.I'm using an
RLockto avoid the need to refactor to avoid possibly reentrancy. I can probably use athreading.Lockinstead but it will be trickier to get right.Also adds tests for supported workflows (each thread has its own
SoundFile) as well as unsupported workflows.You could also support the free-threaded build without adding the locking. IMO the locking is a nicer user experience. With this approach if you try to use a
SoundFileobject in more than one thread simultaneously, you will see a RuntimeError if there is any concurrent use. Since I'm using the non-blocking variant ofRLock.acquire(), it's impossible for this to introduce new scaling issues. Acquiring an uncontended lock is very low-overhead so I don't expect this to add much overhead.I ran the tests under TSan and didn't see any issues besides a known race in
BytesIOin 3.14 that should be fixed in 3.14.1. I can add TSan CI as a followup if you'd like.