-
Notifications
You must be signed in to change notification settings - Fork 27
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
Add thread locking to TEDAPI #148
base: main
Are you sure you want to change the base?
Conversation
24dd7f5
to
f925a6c
Compare
Thanks @Nexarian - I'll start testing. Out of curiosity, did you see any thread locking issues as a result of the current design? It would be interesting to correlate this to issues people see (if they do). |
The origin of this fix was the race condition issues we were seeing with the animation. That is now very clearly in the realm of something to do with JavaScript contention and browsers, but as part of debugging it I tried this. Dictionaries are not thread-safe, and any thread safety you see is incidental from implementation and Python version, so this is a correct fix, though not the only fix. You could argue that exponential backoff and jitter isn't necessary here, and my use of a contextmanager that forces a with-indent might be over-engineered. I'll let you all think about it. But as best I can tell, this works with no ill side effects and makes the code cleaner. The magic of the with block makes it so the lock is always released and we don't have to manually think about it anymore. |
I just realized this has a problem: I said |
This is relevant to my interests. I was having difficulty with the animation JS absolutely hammering the endpoint because it runs the HTTP request every X seconds regardless of whether it has one running at the time or not, but with the minified code it was tedious to edit. I've started rebuilding my own version of it without the dependency on the unminified existing code here: https://github.com/mccahan/power-flow . Using TanStack Query to make sure we're only running one HTTP request to pypowerwall at a time and being a bit more tolerant with some error situations. |
@mccahan 😮 That's awesome! Did you run the code through some sort of de-minifier or are you rebuilding from scratch? |
Started from scratch, it seems like a whole technician setup app and everything so it didn't seem worth sorting through. I'm torn on which way to go with it API-wise, with the issues I've had with timeouts and stalls I have a local copy of pypowerwall modified to add a websocket endpoint so it can broadcast whenever it gets new load/battery numbers to try and get around some things. This PR might change that plan a bit, though. |
@mccahan Feel free to test it and let me know! I still need to fix the dictionary thing, but overall it should be an improvement in locking capability. |
Ok, no rush. Let me know when it is ready to test. 🙏 |
@jasonacox Should be ready now. There's perhaps more cleanup/hygiene to do here, but the functional aspect should be fine. I was able to add a "thread safe" dictionary with minimal additional refactoring. |
- Using a dictionary with an api lock is not thread safe. Given that this is frequently called from the proxy, a more thread safe option is appropriate, especially because I am in the process of modifying the proxy to call multiple gateways. - Use a dictionary (ApiLockPool) that IS thread safe. - Update some typing - Switch to enumerate instead of manually incrementing indicies.
f43a5d7
to
c1f639a
Compare
@Nexarian I haven't tested the version from 2 hours ago, but the previous version I was still having an issue with request spamming. I didn't do a lot of investigation, though I do wonder if having separate locks for every kind of request is an issue. This is a work-in-progress, but I do have part of my suggestion on the matter here: https://github.com/jasonacox/pypowerwall/compare/main...mccahan:pypowerwall:feature/queue-tedapi-requests?expand=1
|
@mccahan The scope of this PR is very narrow: The way API locks were implemented was not correct or thread safe. That's all I intend to fix here. I had, in passing, mentioned that I found this as a result of investigating the behavior of the Tesla animation, but this wasn't directly intended to address anything there. I don't fully understand what I'm also not entirely convinced that making sure pypowerwall/proxies query the inverter serially is strictly necessary. There are provisions in the code to already handle backoff if we're getting timeouts from the gateways, and without more concrete data about how they scale, I believe this to be premature optimization. |
Rather than store the lock in a dictionary, which at best uses the Global Interpreter Lock to get a value from a dictionary (and dictionary reads are not guaranteed thread safe), store the lock as an attribute on the function, which should never have access issues. It also removes a "global" parameter from the object.
@jasonacox I updated the way the threading.Lock is stored. I think this is better... It uses a decorator on the functions that need api locking. I originally wanted to do an array, but this is more "automatic." After a lot of thought, dictionaries aren't great for storing these, they consume the Global Interpreter Lock for reads and are not guaranteed thread safe anyway. In this way, the lock is defined once, has thread-safe access, is defined in a generic way, and is relatively easy to access. |
Summary
Testing