Fix potential deadlock in platform setup that waits for MQTT

This commit is contained in:
jbouwh 2025-04-04 18:11:31 +00:00
parent 3c60bff7dc
commit 451ca92df9
7 changed files with 98 additions and 75 deletions

View File

@ -120,10 +120,15 @@ async def async_setup_platform(
) -> None:
"""Set up the ARWN platform."""
# Make sure MQTT integration is enabled and the client is available
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.error("MQTT integration is not available")
return
async def _async_setup_mqtt() -> None:
# Make sure MQTT integration is enabled and the client is available
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.error("MQTT integration is not available")
return
await mqtt.async_subscribe(hass, TOPIC, async_sensor_event_received, 0)
hass.create_task(_async_setup_mqtt(), "arwn setup")
@callback
def async_sensor_event_received(msg: mqtt.ReceiveMessage) -> None:
@ -167,8 +172,6 @@ async def async_setup_platform(
)
store[sensor.name].set_event(event)
await mqtt.async_subscribe(hass, TOPIC, async_sensor_event_received, 0)
class ArwnSensor(SensorEntity):
"""Representation of an ARWN sensor."""

View File

@ -199,34 +199,38 @@ async def async_setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the manual MQTT alarm platform."""
# Make sure MQTT integration is enabled and the client is available
# We cannot count on dependencies as the alarm_control_panel platform setup
# also will be triggered when mqtt is loading the `alarm_control_panel` platform
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.error("MQTT integration is not available")
return
add_entities(
[
ManualMQTTAlarm(
hass,
config[CONF_NAME],
config.get(CONF_CODE),
config.get(CONF_CODE_TEMPLATE),
config.get(CONF_DISARM_AFTER_TRIGGER, DEFAULT_DISARM_AFTER_TRIGGER),
config.get(mqtt.CONF_STATE_TOPIC),
config.get(mqtt.CONF_COMMAND_TOPIC),
config.get(mqtt.CONF_QOS),
config.get(CONF_CODE_ARM_REQUIRED),
config.get(CONF_PAYLOAD_DISARM),
config.get(CONF_PAYLOAD_ARM_HOME),
config.get(CONF_PAYLOAD_ARM_AWAY),
config.get(CONF_PAYLOAD_ARM_NIGHT),
config.get(CONF_PAYLOAD_ARM_VACATION),
config.get(CONF_PAYLOAD_ARM_CUSTOM_BYPASS),
config,
)
]
)
async def _async_setup_entities() -> None:
# Make sure MQTT integration is enabled and the client is available
# We cannot count on dependencies as the alarm_control_panel platform setup
# also will be triggered when mqtt is loading the `alarm_control_panel` platform
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.error("MQTT integration is not available")
return
add_entities(
[
ManualMQTTAlarm(
hass,
config[CONF_NAME],
config.get(CONF_CODE),
config.get(CONF_CODE_TEMPLATE),
config.get(CONF_DISARM_AFTER_TRIGGER, DEFAULT_DISARM_AFTER_TRIGGER),
config.get(mqtt.CONF_STATE_TOPIC),
config.get(mqtt.CONF_COMMAND_TOPIC),
config.get(mqtt.CONF_QOS),
config.get(CONF_CODE_ARM_REQUIRED),
config.get(CONF_PAYLOAD_DISARM),
config.get(CONF_PAYLOAD_ARM_HOME),
config.get(CONF_PAYLOAD_ARM_AWAY),
config.get(CONF_PAYLOAD_ARM_NIGHT),
config.get(CONF_PAYLOAD_ARM_VACATION),
config.get(CONF_PAYLOAD_ARM_CUSTOM_BYPASS),
config,
)
]
)
hass.create_task(_async_setup_entities(), "manual_mqtt setup")
class ManualMQTTAlarm(AlarmControlPanelEntity):

View File

@ -48,13 +48,27 @@ async def async_setup_scanner(
discovery_info: DiscoveryInfoType | None = None,
) -> bool:
"""Set up the MQTT JSON tracker."""
# Make sure MQTT integration is enabled and the client is available
# We cannot count on dependencies as the device_tracker platform setup
# also will be triggered when mqtt is loading the `device_tracker` platform
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.error("MQTT integration is not available")
return False
async def _async_wait_for_mqtt_and_set_up() -> None:
# Make sure MQTT integration is enabled and the client is available
# We cannot count on dependencies as the device_tracker platform setup
# also will be triggered when mqtt is loading the `device_tracker` platform
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.error("MQTT integration is not available")
return
await _async_setup_scanner(hass, config, async_see)
hass.create_task(_async_wait_for_mqtt_and_set_up(), "mqtt_json setup")
return True
async def _async_setup_scanner(
hass: HomeAssistant,
config: ConfigType,
async_see: AsyncSeeCallback,
) -> None:
"""Set up MQTT JSON tracker."""
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
@ -83,8 +97,6 @@ async def async_setup_scanner(
await mqtt.async_subscribe(hass, topic, async_message_received, qos)
return True
def _parse_see_args(dev_id, data):
"""Parse the payload location parameters, into the format see expects."""

View File

@ -81,24 +81,28 @@ async def async_setup_platform(
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up MQTT room Sensor."""
# Make sure MQTT integration is enabled and the client is available
# We cannot count on dependencies as the sensor platform setup
# also will be triggered when mqtt is loading the `sensor` platform
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.error("MQTT integration is not available")
return
async_add_entities(
[
MQTTRoomSensor(
config.get(CONF_NAME),
config[CONF_STATE_TOPIC],
config[CONF_DEVICE_ID],
config[CONF_TIMEOUT],
config[CONF_AWAY_TIMEOUT],
config.get(CONF_UNIQUE_ID),
)
]
)
async def _async_setup_entities() -> None:
# Make sure MQTT integration is enabled and the client is available
# We cannot count on dependencies as the sensor platform setup
# also will be triggered when mqtt is loading the `sensor` platform
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.error("MQTT integration is not available")
return
async_add_entities(
[
MQTTRoomSensor(
config.get(CONF_NAME),
config[CONF_STATE_TOPIC],
config[CONF_DEVICE_ID],
config[CONF_TIMEOUT],
config[CONF_AWAY_TIMEOUT],
config.get(CONF_UNIQUE_ID),
)
]
)
hass.create_task(_async_setup_entities(), "mqtt_room setup")
class MQTTRoomSensor(SensorEntity):

View File

@ -103,7 +103,7 @@ async def test_no_pending(
}
},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_id = "alarm_control_panel.test"
@ -155,7 +155,7 @@ async def test_no_pending_when_code_not_req(
}
},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_id = "alarm_control_panel.test"
@ -206,7 +206,7 @@ async def test_with_pending(
}
},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
entity_id = "alarm_control_panel.test"

View File

@ -65,7 +65,7 @@ async def test_setup_fails_without_mqtt_being_setup(
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert "MQTT integration is not available" in caplog.text
@ -95,7 +95,7 @@ async def test_ensure_device_tracker_platform_validation(hass: HomeAssistant) ->
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_sp.call_count == 1
@ -110,7 +110,7 @@ async def test_json_message(hass: HomeAssistant) -> None:
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
async_fire_mqtt_message(hass, topic, location)
await hass.async_block_till_done()
state = hass.states.get("device_tracker.zanzito")
@ -131,7 +131,7 @@ async def test_non_json_message(
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
caplog.set_level(logging.ERROR)
caplog.clear()
@ -153,7 +153,7 @@ async def test_incomplete_message(
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: topic}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
caplog.set_level(logging.ERROR)
caplog.clear()
@ -177,7 +177,7 @@ async def test_single_level_wildcard_topic(hass: HomeAssistant) -> None:
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: subscription}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
async_fire_mqtt_message(hass, topic, location)
await hass.async_block_till_done()
@ -198,7 +198,7 @@ async def test_multi_level_wildcard_topic(hass: HomeAssistant) -> None:
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: subscription}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
async_fire_mqtt_message(hass, topic, location)
await hass.async_block_till_done()
@ -220,7 +220,7 @@ async def test_single_level_wildcard_topic_not_matching(hass: HomeAssistant) ->
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: subscription}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
async_fire_mqtt_message(hass, topic, location)
await hass.async_block_till_done()
@ -240,7 +240,7 @@ async def test_multi_level_wildcard_topic_not_matching(hass: HomeAssistant) -> N
DT_DOMAIN,
{DT_DOMAIN: {CONF_PLATFORM: "mqtt_json", "devices": {dev_id: subscription}}},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
async_fire_mqtt_message(hass, topic, location)
await hass.async_block_till_done()

View File

@ -78,7 +78,7 @@ async def test_no_mqtt(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) ->
}
},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(SENSOR_STATE)
assert state is None
assert "MQTT integration is not available" in caplog.text
@ -100,7 +100,7 @@ async def test_room_update(hass: HomeAssistant, mqtt_mock: MqttMockHAClient) ->
}
},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
await send_message(hass, BEDROOM_TOPIC, FAR_MESSAGE)
await assert_state(hass, BEDROOM)
@ -141,7 +141,7 @@ async def test_unique_id_is_set(
}
},
)
await hass.async_block_till_done()
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get(SENSOR_STATE)
assert state.state is not None