Skip to content
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

Uses a Semaphore to limit API calls to one at a time to avoid KLF issues #353

Merged
merged 4 commits into from
Nov 27, 2023

Conversation

palfrey
Copy link
Collaborator

@palfrey palfrey commented Nov 25, 2023

Fixes #331. I've tested this a few times with a set of 6 blinds, and if I do node.close() for all of them in parallel without this, I get roughly half closing (sometimes 2, sometimes 4-5). With this change they appear pretty stable all 6 happening. They don't quite run all at the same time, there's a bit of a gap between them (1s maybe?), but that's a lot better than them not closing.

@palfrey palfrey marked this pull request as draft November 25, 2023 23:17
@codecov-commenter
Copy link

codecov-commenter commented Nov 25, 2023

Codecov Report

Attention: 9 lines in your changes are missing coverage. Please review.

Comparison is base (936e23b) 81.96% compared to head (d25c6de) 81.90%.

Files Patch % Lines
pyvlx/api/api_event.py 0.00% 8 Missing ⚠️
pyvlx/pyvlx.py 75.00% 1 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #353      +/-   ##
==========================================
- Coverage   81.96%   81.90%   -0.07%     
==========================================
  Files          77       77              
  Lines        3388     3393       +5     
==========================================
+ Hits         2777     2779       +2     
- Misses        611      614       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@palfrey palfrey marked this pull request as ready for review November 25, 2023 23:26
@pawlizio
Copy link
Collaborator

This looks different to what I'm trying to merge with #350. My fork already has the Semaphore feature. I meantioned that here: #331 (comment).

@Julius2342
Copy link
Owner

We should find the best approach :)

both introduce an asyncio.Semaphore member within PyVLX . I'am not 100% convinced about the name parallel_commands - bc, yes, this is the intention, but it does not actually express what it is doing. I would have named the member semaphore though.

The other question is, where where we should make use of the Semaphore: CommandSend.send() (function for sending frames to the bus) or ApiEvent.do_api_call() (Baseclass of all frames).

I think it is more explicit to do this within the sender (so do_api_call) and less hidden, but im open to other opinions?

@Julius2342
Copy link
Owner

And while reading my old own code: You can clearly see a C++ guy doing Python things ...

@palfrey
Copy link
Collaborator Author

palfrey commented Nov 26, 2023

This looks different to what I'm trying to merge with #350. My fork already has the Semaphore feature. I meantioned that here: #331 (comment).

I'll note that #350 also has a lot of other stuff there, which will make review of that one hard. This PR even if it duplicates work might be a good one to merge first just to reduce the volume there.

@palfrey
Copy link
Collaborator Author

palfrey commented Nov 26, 2023

both introduce an asyncio.Semaphore member within PyVLX . I'am not 100% convinced about the name parallel_commands - bc, yes, this is the intention, but it does not actually express what it is doing. I would have named the member semaphore though.

I've renamed it to api_call_semaphore as I think that gives the clearest indication of what it does to my mind.

The other question is, where where we should make use of the Semaphore: CommandSend.send() (function for sending frames to the bus) or ApiEvent.do_api_call() (Baseclass of all frames).

I think it is more explicit to do this within the sender (so do_api_call) and less hidden, but im open to other opinions?

do_api_call also nicely wraps both send and receive. Wrapping CommandSend doesn't capture all the sends e.g. GetNodeInformation, ActivateScene, etc, etc

# We check for connection before entering the semaphore section
# because otherwise we might try to connect, which calls this, and we get stuck on
# the semaphore.
await self.pyvlx.check_connected()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you exclude check_connected() from Semaphore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less a matter of excluding, more a matter of "do before". So, inside check_connected is sometimes (if not connected yet) a call to connect, which calls self.klf200.password_enter which calls do_api_call...

Without checking for connection before, the first call to do_api_call (a GetAllNodesInformation typically) gets stuck because the semaphore has been activated, but it can't connect.

The magic that makes this not loop entirely is that the first thing connect does is call await self.connection.connect() so when check_connected gets called as part of the password_enter do_api_call, it doesn't call connect.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, you'right, I don't mean exclude, but before Semaphore. Especially, why not checking connection "inside" Semaphore?

What happen if you receive a burst of commands from HA while the pyvlx is not connected? Don't you trigger the connect function several times? Why not making this check within the Semaphore?

Copy link
Collaborator

@pawlizio pawlizio Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a while but now I got it... If the check_connected would be inside Semaphore (i.e. during command send) and in case it needs to connect first, the do_api_call from await self.connection.connect() would be locked by Semaphore as the current command send is not returned.

...let's merge this PR.

@pawlizio
Copy link
Collaborator

This looks different to what I'm trying to merge with #350. My fork already has the Semaphore feature. I meantioned that here: #331 (comment).

I'll note that #350 also has a lot of other stuff there, which will make review of that one hard. This PR even if it duplicates work might be a good one to merge first just to reduce the volume there.

On the other hand, I just try to merge my fork as it is used by my velux custom_component and over the time lots of users contributed with several PRs there.
Finally I would appreciate if we could merge them soon so that I could switch my custom_component back to official pyvlx.
Next step would be then, bringing my custom_component into HA default velux integration as it provides much more features.

@Julius2342
Copy link
Owner

| Wrapping CommandSend doesn't capture all the sends e.g. GetNodeInformation, ActivateScene, etc, etc

convinces me. @pawlizio : fine for you too?

@pawlizio
Copy link
Collaborator

Regarding Semaphore within do_api_call yes, I considered this also already in my PR.

However I'm not yet convinced on the comment for check_connected() before Semaphore.

@Julius2342 Julius2342 mentioned this pull request Nov 26, 2023
@pawlizio pawlizio merged commit 0a28367 into Julius2342:master Nov 27, 2023
5 checks passed
@Julius2342
Copy link
Owner

ty!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"Unable to send command" debug
4 participants