mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-24 05:40:23 -07:00
Fix handlers on strategy free (#33011)
Add _flushed_hosts dict to store when handlers are flushed and prevent them to be executed too early using _filter_notified_hosts. Add _wait_on_handler_results to wait only for handlers to be completed. Remove only hosts that have been flushed from handler notified list. Fix #31504, #23970
This commit is contained in:
parent
ec2aa12581
commit
5f953d1129
2 changed files with 66 additions and 8 deletions
|
@ -197,6 +197,10 @@ class StrategyBase:
|
||||||
# outstanding tasks still in queue
|
# outstanding tasks still in queue
|
||||||
self._blocked_hosts = dict()
|
self._blocked_hosts = dict()
|
||||||
|
|
||||||
|
# this dictionary is used to keep track of hosts that have
|
||||||
|
# flushed handlers
|
||||||
|
self._flushed_hosts = dict()
|
||||||
|
|
||||||
self._results = deque()
|
self._results = deque()
|
||||||
self._results_lock = threading.Condition(threading.Lock())
|
self._results_lock = threading.Condition(threading.Lock())
|
||||||
|
|
||||||
|
@ -657,6 +661,35 @@ class StrategyBase:
|
||||||
|
|
||||||
return ret_results
|
return ret_results
|
||||||
|
|
||||||
|
def _wait_on_handler_results(self, iterator, handler, notified_hosts):
|
||||||
|
'''
|
||||||
|
Wait for the handler tasks to complete, using a short sleep
|
||||||
|
between checks to ensure we don't spin lock
|
||||||
|
'''
|
||||||
|
|
||||||
|
ret_results = []
|
||||||
|
handler_results = 0
|
||||||
|
|
||||||
|
display.debug("waiting for handler results...")
|
||||||
|
while (self._pending_results > 0 and
|
||||||
|
handler_results < len(notified_hosts) and
|
||||||
|
not self._tqm._terminated):
|
||||||
|
|
||||||
|
if self._tqm.has_dead_workers():
|
||||||
|
raise AnsibleError("A worker was found in a dead state")
|
||||||
|
|
||||||
|
results = self._process_pending_results(iterator)
|
||||||
|
ret_results.extend(results)
|
||||||
|
handler_results += len([
|
||||||
|
r._host for r in results if r._host in notified_hosts and
|
||||||
|
r.task_name == handler.name])
|
||||||
|
if self._pending_results > 0:
|
||||||
|
time.sleep(C.DEFAULT_INTERNAL_POLL_INTERVAL)
|
||||||
|
|
||||||
|
display.debug("no more pending handlers, returning what we have")
|
||||||
|
|
||||||
|
return ret_results
|
||||||
|
|
||||||
def _wait_on_pending_results(self, iterator):
|
def _wait_on_pending_results(self, iterator):
|
||||||
'''
|
'''
|
||||||
Wait for the shared counter to drop to zero, using a short sleep
|
Wait for the shared counter to drop to zero, using a short sleep
|
||||||
|
@ -855,14 +888,17 @@ class StrategyBase:
|
||||||
# self._tqm.send_callback('v2_playbook_on_no_hosts_remaining')
|
# self._tqm.send_callback('v2_playbook_on_no_hosts_remaining')
|
||||||
# result = False
|
# result = False
|
||||||
# break
|
# break
|
||||||
|
if notified_hosts is None:
|
||||||
|
notified_hosts = self._notified_handlers[handler._uuid]
|
||||||
|
|
||||||
|
notified_hosts = self._filter_notified_hosts(notified_hosts)
|
||||||
|
|
||||||
|
if len(notified_hosts) > 0:
|
||||||
saved_name = handler.name
|
saved_name = handler.name
|
||||||
handler.name = handler_name
|
handler.name = handler_name
|
||||||
self._tqm.send_callback('v2_playbook_on_handler_task_start', handler)
|
self._tqm.send_callback('v2_playbook_on_handler_task_start', handler)
|
||||||
handler.name = saved_name
|
handler.name = saved_name
|
||||||
|
|
||||||
if notified_hosts is None:
|
|
||||||
notified_hosts = self._notified_handlers[handler._uuid]
|
|
||||||
|
|
||||||
run_once = False
|
run_once = False
|
||||||
try:
|
try:
|
||||||
action = action_loader.get(handler.action, class_only=True)
|
action = action_loader.get(handler.action, class_only=True)
|
||||||
|
@ -883,7 +919,7 @@ class StrategyBase:
|
||||||
break
|
break
|
||||||
|
|
||||||
# collect the results from the handler run
|
# collect the results from the handler run
|
||||||
host_results = self._wait_on_pending_results(iterator)
|
host_results = self._wait_on_handler_results(iterator, handler, notified_hosts)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
included_files = IncludedFile.process_include_results(
|
included_files = IncludedFile.process_include_results(
|
||||||
|
@ -922,11 +958,22 @@ class StrategyBase:
|
||||||
display.warning(str(e))
|
display.warning(str(e))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# wipe the notification list
|
# remove hosts from notification list
|
||||||
self._notified_handlers[handler._uuid] = []
|
self._notified_handlers[handler._uuid] = [
|
||||||
|
h for h in self._notified_handlers[handler._uuid]
|
||||||
|
if h not in notified_hosts]
|
||||||
display.debug("done running handlers, result is: %s" % result)
|
display.debug("done running handlers, result is: %s" % result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _filter_notified_hosts(self, notified_hosts):
|
||||||
|
'''
|
||||||
|
Filter notified hosts accordingly to strategy
|
||||||
|
'''
|
||||||
|
|
||||||
|
# As main strategy is linear, we do not filter hosts
|
||||||
|
# We return a copy to avoid race conditions
|
||||||
|
return notified_hosts[:]
|
||||||
|
|
||||||
def _take_step(self, task, host=None):
|
def _take_step(self, task, host=None):
|
||||||
|
|
||||||
ret = False
|
ret = False
|
||||||
|
@ -974,7 +1021,9 @@ class StrategyBase:
|
||||||
elif meta_action == 'flush_handlers':
|
elif meta_action == 'flush_handlers':
|
||||||
if task.when:
|
if task.when:
|
||||||
self._cond_not_supported_warn(meta_action)
|
self._cond_not_supported_warn(meta_action)
|
||||||
|
self._flushed_hosts[target_host] = True
|
||||||
self.run_handlers(iterator, play_context)
|
self.run_handlers(iterator, play_context)
|
||||||
|
self._flushed_hosts[target_host] = False
|
||||||
msg = "ran handlers"
|
msg = "ran handlers"
|
||||||
elif meta_action == 'refresh_inventory' or self.flush_cache:
|
elif meta_action == 'refresh_inventory' or self.flush_cache:
|
||||||
if task.when:
|
if task.when:
|
||||||
|
|
|
@ -49,6 +49,15 @@ except ImportError:
|
||||||
|
|
||||||
class StrategyModule(StrategyBase):
|
class StrategyModule(StrategyBase):
|
||||||
|
|
||||||
|
def _filter_notified_hosts(self, notified_hosts):
|
||||||
|
'''
|
||||||
|
Filter notified hosts accordingly to strategy
|
||||||
|
'''
|
||||||
|
|
||||||
|
# We act only on hosts that are ready to flush handlers
|
||||||
|
return [host for host in notified_hosts
|
||||||
|
if host in self._flushed_hosts and self._flushed_hosts[host]]
|
||||||
|
|
||||||
def run(self, iterator, play_context):
|
def run(self, iterator, play_context):
|
||||||
'''
|
'''
|
||||||
The "free" strategy is a bit more complex, in that it allows tasks to
|
The "free" strategy is a bit more complex, in that it allows tasks to
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue