-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
Serialize amcrest snapshot commands and bump PyPI package to 1.2.4 #21664
Conversation
Attempting to send a snapshot command when a previous one hasn't finished will result in warnings and/or errors. This can happen when the camera picture is clicked on in the frontend, resulting in the thread that updates the thumbnail in the background every 10 seconds to sometimes collide with the thread that updates the large picture in the foreground quickly. An automation that calls the camera.snapshot service in yet another thread can make the situation worse. Fix by adding a thread lock to serialize snapshot commands. Also bump the amcrest package to 1.2.4 which fixes error handling in the command method and improves performance by reusing requests sessions.
You deleted the part of the checklist that relates to the action that you forgot ... Change looks good 👍 |
What part/action was that? I deleted everything I though wasn't applicable. |
There is (was) a whole section on new dependencies. You have to update |
Ah, ok. Sorry, I missed that. Should be good now. Thx! |
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.
Looks good 👍
try: | ||
# Send the request to snap a picture and return raw jpg data | ||
return self._camera.snapshot(channel=self._resolution).data | ||
except (RequestException, ReadTimeoutError, ValueError) as error: |
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.
It's a shame the library does not handle these exceptions and raises its own instead.
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.
Good idea. I'm working on the library, too, so I can look at doing that. Thanks!
# Send the request to snap a picture and return raw jpg data | ||
response = self._camera.snapshot(channel=self._resolution) | ||
return response.data | ||
with self._snapshot_lock: |
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.
We should avoid waiting on locks in our thread pool. We can switch to the async api and use an asyncio.Lock
instead.
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.
Oh. I agree that would be better but are you implying that we actually have a rule against locks?
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.
Yeah, it's basically the same rule as for sleeping in the thread pool.
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.
Hm. I think that dilutes the value of the thread pool. What if the lock was in the third party library? Maybe we should disallow I/O as well since it will sleep?
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.
@pnbruckner I think this is the rewrite that Martin suggests:
async def async_camera_image(self):
"""Return a still image response from the camera."""
with self._snapshot_lock:
try:
# Send the request to snap a picture and return raw jpg data
response = await hass.async_add_executor_job(
self._camera.snapshot, self._resolution)
return response.data
except (RequestException, ReadTimeoutError, ValueError) as error:
_LOGGER.error(
'Could not get camera image due to error %s', error)
return None
(and then _snapshot_lock
should be an asyncio.Lock()
)
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.
Ok. So how do I go about this? Do I restore my branch and check in more changes? Do I open a new PR? Something else?
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.
That should be a new PR.
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 need some help on this. This is what I came up with based on the suggested code above:
self._snapshot_lock = asyncio.Lock()
async def async_camera_image(self):
"""Return a still image response from the camera."""
with await self._snapshot_lock:
try:
# Send the request to snap a picture and return raw jpg data
_LOGGER.critical('async_camera_image start')
response = await self.hass.async_add_executor_job(
self._camera.snapshot, self._resolution)
_LOGGER.critical('async_camera_image end: %i', len(response.data))
return response.data
except (RequestException, ReadTimeoutError, ValueError) as error:
_LOGGER.error(
'Could not get camera image due to error %s', error)
return None
But it doesn't seem to be working. I added the logging temporarily to try and see what's going on. I also added logging to the self._camera.snapshot method. Here is what I'm seeing:
2019-03-05 17:53:52 CRITICAL (MainThread) [homeassistant.components.amcrest.camera] async_camera_image start
2019-03-05 17:53:52 CRITICAL (SyncWorker_1) [amcrest.snapshot] snapshot start
2019-03-05 17:53:53 CRITICAL (SyncWorker_1) [amcrest.snapshot] snapshot end
2019-03-05 17:54:00 CRITICAL (MainThread) [homeassistant.components.amcrest.camera] async_camera_image end: 158840
2019-03-05 17:54:00 CRITICAL (MainThread) [homeassistant.components.amcrest.camera] async_camera_image start
2019-03-05 17:54:00 CRITICAL (SyncWorker_7) [amcrest.snapshot] snapshot start
2019-03-05 17:54:01 CRITICAL (SyncWorker_7) [amcrest.snapshot] snapshot end
2019-03-05 17:54:09 CRITICAL (MainThread) [homeassistant.components.amcrest.camera] async_camera_image end: 159495
2019-03-05 17:54:09 CRITICAL (MainThread) [homeassistant.components.amcrest.camera] async_camera_image start
2019-03-05 17:54:09 CRITICAL (SyncWorker_2) [amcrest.snapshot] snapshot start
2019-03-05 17:54:09 CRITICAL (MainThread) [homeassistant.components.amcrest.camera] async_camera_image start
2019-03-05 17:54:09 CRITICAL (SyncWorker_8) [amcrest.snapshot] snapshot start
2019-03-05 17:54:09 WARNING (SyncWorker_2) [urllib3.connectionpool] Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProtocolError('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))': /cgi-bin/snapshot.cgi?channel=0
2019-03-05 17:54:09 WARNING (SyncWorker_2) [amcrest.http] Trying again due to error 401 Client Error: Unauthorized for url: http://192.168.1.164:10007/cgi-bin/snapshot.cgi?channel=0
This shows two successful snapshots, but even so, there is a long delay from the snapshot method finishing to async_camera_image finishing. I don't understand that. Then you can see async_camera_image starts twice, which results in two calls to snapshot starting twice, which leads to the errors that the locking is supposed to be avoiding.
Why does the locking seem like it's not working?
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.
Why does the locking seem like it's not working?
Sorry, try this:
async with self._snapshot_lock:
... but please do open a new PR (you can tag me).
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.
Thank you. That fixed it! (And now I see why. Still learning asyncio!)
I'll be submitting a new PR with the change soon.
Hi, BR |
Yes, I know. It was a misunderstanding between me and the maintainer.
Working on a new PR now to change to 1.2.5.
…On Wed, Mar 6, 2019 at 3:42 PM Markus Jankowski ***@***.***> wrote:
Hi,
there is no 1.2.4 in pypi. Just 1.2.3 and 1.2.5.
BR
Markus
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#21664 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AiWM2XAK93IxvzN9h7Q_naPD6d-B1uClks5vUDY1gaJpZM4bdRoo>
.
|
Description:
Attempting to send a snapshot command when a previous one hasn't finished will result in warnings and/or errors. This can happen when the camera picture is clicked on in the frontend, resulting in the thread that updates the thumbnail in the background every 10 seconds to sometimes collide with the thread that updates the large picture in the foreground quickly. An automation that calls the camera.snapshot service in yet another thread can make the situation worse. Fix by adding a thread lock to serialize snapshot commands. Also bump the amcrest package to 1.2.4 which fixes error handling in the command method and improves performance by reusing requests sessions.
Related issue (if applicable): None
Pull request in home-assistant.io with documentation (if applicable): None
Example entry for
configuration.yaml
(if applicable):No change to configuration.
Checklist:
tox
. Your PR cannot be merged unless tests passIf the code communicates with devices, web services, or third-party tools:
requirements_all.txt
by runningscript/gen_requirements_all.py
.