mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Bus and StateMachine are now thread-safe
This commit is contained in:
parent
ac0ca5d001
commit
e7f5953362
@ -158,85 +158,90 @@ class Bus(object):
|
|||||||
self._event_listeners = {}
|
self._event_listeners = {}
|
||||||
self._services = {}
|
self._services = {}
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.event_lock = threading.Lock()
|
||||||
|
self.service_lock = threading.Lock()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def services(self):
|
def services(self):
|
||||||
""" Dict with per domain a list of available services. """
|
""" Dict with per domain a list of available services. """
|
||||||
return {domain: self._services[domain].keys()
|
with self.service_lock:
|
||||||
for domain in self._services}
|
return {domain: self._services[domain].keys()
|
||||||
|
for domain in self._services}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def event_listeners(self):
|
def event_listeners(self):
|
||||||
""" Dict with events that is being listened for and the number
|
""" Dict with events that is being listened for and the number
|
||||||
of listeners.
|
of listeners.
|
||||||
"""
|
"""
|
||||||
return {key: len(self._event_listeners[key])
|
with self.event_lock:
|
||||||
for key in self._event_listeners}
|
return {key: len(self._event_listeners[key])
|
||||||
|
for key in self._event_listeners}
|
||||||
|
|
||||||
def call_service(self, domain, service, service_data=None):
|
def call_service(self, domain, service, service_data=None):
|
||||||
""" Calls a service. """
|
""" Calls a service. """
|
||||||
|
with self.service_lock:
|
||||||
try:
|
|
||||||
self._services[domain][service]
|
|
||||||
except KeyError:
|
|
||||||
# Domain or Service does not exist
|
|
||||||
raise ServiceDoesNotExistException(
|
|
||||||
"Service does not exist: {}/{}".format(domain, service))
|
|
||||||
|
|
||||||
if not service_data:
|
|
||||||
service_data = {}
|
|
||||||
|
|
||||||
def run():
|
|
||||||
""" Executes a service. """
|
|
||||||
service_call = ServiceCall(self, domain, service, service_data)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._services[domain][service](service_call)
|
self._services[domain][service]
|
||||||
except Exception: # pylint: disable=broad-except
|
except KeyError:
|
||||||
self.logger.exception("Bus:Exception in service {}/{}".format(
|
# Domain or Service does not exist
|
||||||
domain, service))
|
raise ServiceDoesNotExistException(
|
||||||
|
"Service does not exist: {}/{}".format(domain, service))
|
||||||
|
|
||||||
# We dont want the eventbus to be blocking - run in a thread.
|
service_data = service_data or {}
|
||||||
threading.Thread(target=run).start()
|
|
||||||
|
def run():
|
||||||
|
""" Executes a service. """
|
||||||
|
service_call = ServiceCall(self, domain, service, service_data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._services[domain][service](service_call)
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
self.logger.exception(
|
||||||
|
"Bus:Exception in service {}/{}".format(
|
||||||
|
domain, service))
|
||||||
|
|
||||||
|
# We dont want the bus to be blocking - run in a thread.
|
||||||
|
threading.Thread(target=run).start()
|
||||||
|
|
||||||
def register_service(self, domain, service, service_callback):
|
def register_service(self, domain, service, service_callback):
|
||||||
""" Register a service. """
|
""" Register a service. """
|
||||||
try:
|
with self.service_lock:
|
||||||
self._services[domain][service] = service_callback
|
try:
|
||||||
except KeyError:
|
self._services[domain][service] = service_callback
|
||||||
# Domain does not exist yet
|
except KeyError:
|
||||||
self._services[domain] = {service: service_callback}
|
# Domain does not exist yet
|
||||||
|
self._services[domain] = {service: service_callback}
|
||||||
|
|
||||||
def fire_event(self, event_type, event_data=None):
|
def fire_event(self, event_type, event_data=None):
|
||||||
""" Fire an event. """
|
""" Fire an event. """
|
||||||
|
with self.event_lock:
|
||||||
|
# Copy the list of the current listeners because some listeners
|
||||||
|
# choose to remove themselves as a listener while being executed
|
||||||
|
# which causes the iterator to be confused.
|
||||||
|
get = self._event_listeners.get
|
||||||
|
listeners = get(MATCH_ALL, []) + get(event_type, [])
|
||||||
|
|
||||||
# Copy the list of the current listeners because some listeners
|
if not listeners:
|
||||||
# choose to remove themselves as a listener while being executed
|
return
|
||||||
# which causes the iterator to be confused.
|
|
||||||
get = self._event_listeners.get
|
|
||||||
listeners = get(MATCH_ALL, []) + get(event_type, [])
|
|
||||||
|
|
||||||
if not listeners:
|
event_data = event_data or {}
|
||||||
return
|
|
||||||
|
|
||||||
event_data = event_data or {}
|
self.logger.info("Bus:Event {}: {}".format(
|
||||||
|
event_type, event_data))
|
||||||
|
|
||||||
self.logger.info("Bus:Event {}: {}".format(
|
def run():
|
||||||
event_type, event_data))
|
""" Fire listeners for event. """
|
||||||
|
event = Event(self, event_type, event_data)
|
||||||
|
|
||||||
def run():
|
for listener in listeners:
|
||||||
""" Fire listeners for event. """
|
try:
|
||||||
event = Event(self, event_type, event_data)
|
listener(event)
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
self.logger.exception(
|
||||||
|
"Bus:Exception in event listener")
|
||||||
|
|
||||||
for listener in listeners:
|
# We dont want the bus to be blocking - run in a thread.
|
||||||
try:
|
threading.Thread(target=run).start()
|
||||||
listener(event)
|
|
||||||
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
self.logger.exception("Bus:Exception in event listener")
|
|
||||||
|
|
||||||
# We dont want the bus to be blocking - run in a thread.
|
|
||||||
threading.Thread(target=run).start()
|
|
||||||
|
|
||||||
def listen_event(self, event_type, listener):
|
def listen_event(self, event_type, listener):
|
||||||
""" Listen for all events or events of a specific type.
|
""" Listen for all events or events of a specific type.
|
||||||
@ -244,10 +249,11 @@ class Bus(object):
|
|||||||
To listen to all events specify the constant ``MATCH_ALL``
|
To listen to all events specify the constant ``MATCH_ALL``
|
||||||
as event_type.
|
as event_type.
|
||||||
"""
|
"""
|
||||||
try:
|
with self.event_lock:
|
||||||
self._event_listeners[event_type].append(listener)
|
try:
|
||||||
except KeyError: # event_type did not exist
|
self._event_listeners[event_type].append(listener)
|
||||||
self._event_listeners[event_type] = [listener]
|
except KeyError: # event_type did not exist
|
||||||
|
self._event_listeners[event_type] = [listener]
|
||||||
|
|
||||||
def listen_once_event(self, event_type, listener):
|
def listen_once_event(self, event_type, listener):
|
||||||
""" Listen once for event of a specific type.
|
""" Listen once for event of a specific type.
|
||||||
@ -257,7 +263,6 @@ class Bus(object):
|
|||||||
|
|
||||||
Note: at the moment it is impossible to remove a one time listener.
|
Note: at the moment it is impossible to remove a one time listener.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def onetime_listener(event):
|
def onetime_listener(event):
|
||||||
""" Removes listener from eventbus and then fires listener. """
|
""" Removes listener from eventbus and then fires listener. """
|
||||||
self.remove_event_listener(event_type, onetime_listener)
|
self.remove_event_listener(event_type, onetime_listener)
|
||||||
@ -268,14 +273,18 @@ class Bus(object):
|
|||||||
|
|
||||||
def remove_event_listener(self, event_type, listener):
|
def remove_event_listener(self, event_type, listener):
|
||||||
""" Removes a listener of a specific event_type. """
|
""" Removes a listener of a specific event_type. """
|
||||||
try:
|
with self.event_lock:
|
||||||
self._event_listeners[event_type].remove(listener)
|
try:
|
||||||
|
self._event_listeners[event_type].remove(listener)
|
||||||
|
|
||||||
if len(self._event_listeners[event_type]) == 0:
|
# delete event_type list if empty
|
||||||
del self._event_listeners[event_type]
|
if not self._event_listeners[event_type]:
|
||||||
|
del self._event_listeners[event_type]
|
||||||
|
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
pass
|
# KeyError is key event_type did not exist
|
||||||
|
# ValueError if the list [event_type] did not contain listener
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class State(object):
|
class State(object):
|
||||||
@ -346,20 +355,22 @@ class StateMachine(object):
|
|||||||
@property
|
@property
|
||||||
def categories(self):
|
def categories(self):
|
||||||
""" List of categories which states are being tracked. """
|
""" List of categories which states are being tracked. """
|
||||||
return self.states.keys()
|
with self.lock:
|
||||||
|
return self.states.keys()
|
||||||
|
|
||||||
def remove_category(self, category):
|
def remove_category(self, category):
|
||||||
""" Removes a category from the state machine.
|
""" Removes a category from the state machine.
|
||||||
|
|
||||||
Returns boolean to indicate if a category was removed. """
|
Returns boolean to indicate if a category was removed. """
|
||||||
try:
|
with self.lock:
|
||||||
del self.states[category]
|
try:
|
||||||
|
del self.states[category]
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# if category does not exist
|
# if category does not exist
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def set_state(self, category, new_state, attributes=None):
|
def set_state(self, category, new_state, attributes=None):
|
||||||
""" Set the state of a category, add category if it does not exist.
|
""" Set the state of a category, add category if it does not exist.
|
||||||
@ -368,44 +379,44 @@ class StateMachine(object):
|
|||||||
|
|
||||||
attributes = attributes or {}
|
attributes = attributes or {}
|
||||||
|
|
||||||
self.lock.acquire()
|
with self.lock:
|
||||||
|
# Add category if it does not exist
|
||||||
# Add category if it does not exist
|
if category not in self.states:
|
||||||
if category not in self.states:
|
|
||||||
self.states[category] = State(new_state, attributes)
|
|
||||||
|
|
||||||
# Change state and fire listeners
|
|
||||||
else:
|
|
||||||
old_state = self.states[category]
|
|
||||||
|
|
||||||
if old_state.state != new_state or \
|
|
||||||
old_state.attributes != attributes:
|
|
||||||
|
|
||||||
self.states[category] = State(new_state, attributes)
|
self.states[category] = State(new_state, attributes)
|
||||||
|
|
||||||
self.bus.fire_event(EVENT_STATE_CHANGED,
|
# Change state and fire listeners
|
||||||
{'category': category,
|
else:
|
||||||
'old_state': old_state,
|
old_state = self.states[category]
|
||||||
'new_state': self.states[category]})
|
|
||||||
|
|
||||||
self.lock.release()
|
if old_state.state != new_state or \
|
||||||
|
old_state.attributes != attributes:
|
||||||
|
|
||||||
|
self.states[category] = State(new_state, attributes)
|
||||||
|
|
||||||
|
self.bus.fire_event(EVENT_STATE_CHANGED,
|
||||||
|
{'category': category,
|
||||||
|
'old_state': old_state,
|
||||||
|
'new_state': self.states[category]})
|
||||||
|
|
||||||
def get_state(self, category):
|
def get_state(self, category):
|
||||||
""" Returns a dict (state, last_changed, attributes) describing
|
""" Returns a dict (state, last_changed, attributes) describing
|
||||||
the state of the specified category. """
|
the state of the specified category. """
|
||||||
try:
|
with self.lock:
|
||||||
# Make a copy so people won't mutate the state
|
try:
|
||||||
return self.states[category].copy()
|
# Make a copy so people won't mutate the state
|
||||||
|
return self.states[category].copy()
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# If category does not exist
|
# If category does not exist
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def is_state(self, category, state):
|
def is_state(self, category, state):
|
||||||
""" Returns True if category exists and is specified state. """
|
""" Returns True if category exists and is specified state. """
|
||||||
cur_state = self.get_state(category)
|
try:
|
||||||
|
return self.get_state(category).state == state
|
||||||
return cur_state and cur_state.state == state
|
except AttributeError:
|
||||||
|
# get_state returned None
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Timer(threading.Thread):
|
class Timer(threading.Thread):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user