-
-
Notifications
You must be signed in to change notification settings - Fork 111
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
Increased thread-safety by using Lock context manager syntax #192
Conversation
This is a safer and more readable way to prevent accidental missed release() calls on logical branching of code/execution flow. Functions that may not be readily converted to this format have been tagged as TODOs for potential future investigation after refactoring.
Accomplished by refactoring released lock with sleep and recursive call out of the middle of the function and instead raising an exception to the main calling register() function. This should be functionally equivalent whilst allowing for the context manager syntax, which is safer against missing release() calls.
Accomplished by refactoring released lock with sleep and recursive call out of the middle of the function and instead raising an exception to the main calling deregister() function. This should be functionally equivalent whilst allowing for the context manager syntax, which is safer against missing release() calls.
This is just cleaner code without changing much. __(de)register() no longer safely locks on its, but only their public equivalents call them anyway. We can hoist the context manager wrapping there and prevent two functions from being entirely indented by a `with ...:` statement.
…blocking() Like the other preceding commits, this should eliminate the risk of missing release() and corresponding setblocking(True) calls for this function.
…cking Since recv_loop pairs acquiring a lock and setting a socket as non-blocking together, as well as their corresponding release/blocking, combining them into a single context manager function makes it safer.
Thank you for this PR @f3ndot It is not only a major improvement, but it is well written and I can tell you read the Thank you again! |
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.
All of these changes look great, thank you for your thorough edits.
Tested "sleep 5 seconds, try (de)registering again" logic
with the same script I used to create the feature. The feature still works as intended.
Thank you again!
The current code base makes liberal use of
Lock.acquire()
andLock.release()
throughout, however that syntax can suffer from missing release calls in branched logic flows. Indeed, #125 was opened due to a missing release.Thankfully Python's threading module supports the context manager protocol for Lock which bakes in a guaranteed release after the inner code runs or excepts at any time.
This PR refactors almost all existing acquired/released locks to use the context manager syntax instead, removing the risk of a missing release. A small amount of refactoring was required to deal with cases where some functions intentionally release their lock in the middle of their code to sleep and recursively call themselves.
One utility function was added to simplify where the main receive loop function paired a Lock's locking/releasing action to a corresponding socket's nonblocking/blocking state. This also guarantees both are "toggled" in all possible branched flows too.
This PR passes
pytest --check-functionality
with the test Docker image running, but I've not tried it in a project yet. I suggest @tayler6000 gives it a good once-over and testing in a real application context before considering it for merging. I'm most interested in testing the "sleep 5 seconds, try (de)registering again" logic to see if that's preserved.