Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
Core
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
mirrored_repos
HomeAssistant
Core
Commits
bf1b4080
Unverified
Commit
bf1b4080
authored
4 years ago
by
Phil Bruckner
Committed by
GitHub
4 years ago
Browse files
Options
Downloads
Patches
Plain Diff
Handle cancellation in ServiceRegistry.async_call (#33644)
parent
d7e99594
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
homeassistant/core.py
+46
-17
46 additions, 17 deletions
homeassistant/core.py
tests/test_core.py
+36
-0
36 additions, 0 deletions
tests/test_core.py
with
82 additions
and
17 deletions
homeassistant/core.py
+
46
−
17
View file @
bf1b4080
...
...
@@ -28,6 +28,7 @@ from typing import (
Optional
,
Set
,
TypeVar
,
Union
,
)
import
uuid
...
...
@@ -1224,29 +1225,57 @@ class ServiceRegistry:
context
=
context
,
)
coro
=
self
.
_execute_service
(
handler
,
service_call
)
if
not
blocking
:
self
.
_
hass
.
async_create_task
(
self
.
_safe_execute
(
handler
,
service_call
)
)
self
.
_
run_service_in_background
(
coro
,
service_call
)
return
None
task
=
self
.
_hass
.
async_create_task
(
coro
)
try
:
async
with
timeout
(
limit
):
await
asyncio
.
shield
(
self
.
_execute_service
(
handler
,
service_call
))
await
asyncio
.
wait
({
task
},
timeout
=
limit
)
except
asyncio
.
CancelledError
:
# Task calling us was cancelled, so cancel service call task, and wait for
# it to be cancelled, within reason, before leaving.
_LOGGER
.
debug
(
"
Service call was cancelled: %s
"
,
service_call
)
task
.
cancel
()
await
asyncio
.
wait
({
task
},
timeout
=
SERVICE_CALL_LIMIT
)
raise
if
task
.
cancelled
():
# Service call task was cancelled some other way, such as during shutdown.
_LOGGER
.
debug
(
"
Service was cancelled: %s
"
,
service_call
)
raise
asyncio
.
CancelledError
if
task
.
done
():
# Propagate any exceptions that might have happened during service call.
task
.
result
()
# Service call completed successfully!
return
True
except
asyncio
.
TimeoutError
:
return
False
# Service call task did not complete before timeout expired.
# Let it keep running in background.
self
.
_run_service_in_background
(
task
,
service_call
)
_LOGGER
.
debug
(
"
Service did not complete before timeout: %s
"
,
service_call
)
return
False
async
def
_safe_execute
(
self
,
handler
:
Service
,
service_call
:
ServiceCall
)
->
None
:
"""
Execute a service and catch exceptions.
"""
try
:
await
self
.
_execute_service
(
handler
,
service_call
)
except
Unauthorized
:
_LOGGER
.
warning
(
"
Unauthorized service called %s/%s
"
,
service_call
.
domain
,
service_call
.
service
,
)
except
Exception
:
# pylint: disable=broad-except
_LOGGER
.
exception
(
"
Error executing service %s
"
,
service_call
)
def
_run_service_in_background
(
self
,
coro_or_task
:
Union
[
Coroutine
,
asyncio
.
Task
],
service_call
:
ServiceCall
)
->
None
:
"""
Run service call in background, catching and logging any exceptions.
"""
async
def
catch_exceptions
()
->
None
:
try
:
await
coro_or_task
except
Unauthorized
:
_LOGGER
.
warning
(
"
Unauthorized service called %s/%s
"
,
service_call
.
domain
,
service_call
.
service
,
)
except
asyncio
.
CancelledError
:
_LOGGER
.
debug
(
"
Service was cancelled: %s
"
,
service_call
)
except
Exception
:
# pylint: disable=broad-except
_LOGGER
.
exception
(
"
Error executing service: %s
"
,
service_call
)
self
.
_hass
.
async_create_task
(
catch_exceptions
())
async
def
_execute_service
(
self
,
handler
:
Service
,
service_call
:
ServiceCall
...
...
This diff is collapsed.
Click to expand it.
tests/test_core.py
+
36
−
0
View file @
bf1b4080
...
...
@@ -1214,6 +1214,42 @@ async def test_async_functions_with_callback(hass):
assert
len
(
runs
)
==
3
@pytest.mark.parametrize
(
"
cancel_call
"
,
[
True
,
False
])
async
def
test_cancel_service_task
(
hass
,
cancel_call
):
"""
Test cancellation.
"""
service_called
=
asyncio
.
Event
()
service_cancelled
=
False
async
def
service_handler
(
call
):
nonlocal
service_cancelled
service_called
.
set
()
try
:
await
asyncio
.
sleep
(
10
)
except
asyncio
.
CancelledError
:
service_cancelled
=
True
raise
hass
.
services
.
async_register
(
"
test_domain
"
,
"
test_service
"
,
service_handler
)
call_task
=
hass
.
async_create_task
(
hass
.
services
.
async_call
(
"
test_domain
"
,
"
test_service
"
,
blocking
=
True
)
)
tasks_1
=
asyncio
.
all_tasks
()
await
asyncio
.
wait_for
(
service_called
.
wait
(),
timeout
=
1
)
tasks_2
=
asyncio
.
all_tasks
()
-
tasks_1
assert
len
(
tasks_2
)
==
1
service_task
=
tasks_2
.
pop
()
if
cancel_call
:
call_task
.
cancel
()
else
:
service_task
.
cancel
()
with
pytest
.
raises
(
asyncio
.
CancelledError
):
await
call_task
assert
service_cancelled
def
test_valid_entity_id
():
"""
Test valid entity ID.
"""
for
invalid
in
[
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment