@ -36,6 +36,7 @@ from ansible.executor.process.worker import WorkerProcess
from ansible . executor . task_result import TaskResult
from ansible . inventory . host import Host
from ansible . inventory . group import Group
from ansible . module_utils . facts import Facts
from ansible . playbook . helpers import load_list_of_blocks
from ansible . playbook . included_file import IncludedFile
from ansible . plugins import action_loader , connection_loader , filter_loader , lookup_loader , module_loader , test_loader
@ -52,8 +53,24 @@ except ImportError:
__all__ = [ ' StrategyBase ' ]
action_write_locks = defaultdict ( Lock )
if ' action_write_locks ' not in globals ( ) :
# Do not initialize this more than once because it seems to bash
# the existing one. multiprocessing must be reloading the module
# when it forks?
action_write_locks = dict ( )
# Below is a Lock for use when we weren't expecting a named module.
# It gets used when an action plugin directly invokes a module instead
# of going through the strategies. Slightly less efficient as all
# processes with unexpected module names will wait on this lock
action_write_locks [ None ] = Lock ( )
# These plugins are called directly by action plugins (not going through
# a strategy). We precreate them here as an optimization
mods = set ( p [ ' name ' ] for p in Facts . PKG_MGRS )
mods . update ( ( ' copy ' , ' file ' , ' setup ' , ' slurp ' , ' stat ' ) )
for mod_name in mods :
action_write_locks [ mod_name ] = Lock ( )
# TODO: this should probably be in the plugins/__init__.py, with
# a smarter mechanism to set all of the attributes based on
@ -144,18 +161,19 @@ class StrategyBase:
display . debug ( " entering _queue_task() for %s / %s " % ( host , task ) )
# Add a write lock for tasks.
# Maybe this should be added somewhere further up the call stack but
# this is the earliest in the code where we have task (1) extracted
# into its own variable and (2) there's only a single code path
# leading to the module being run. This is called by three
# functions: __init__.py::_do_handler_run(), linear.py::run(), and
# free.py::run() so we'd have to add to all three to do it there.
# The next common higher level is __init__.py::run() and that has
# tasks inside of play_iterator so we'd have to extract them to do it
# there.
if not action_write_locks [ task . action ] :
display . warning ( ' Python defaultdict did not create the Lock for us. Creating manually ' )
# Add a write lock for tasks.
# Maybe this should be added somewhere further up the call stack but
# this is the earliest in the code where we have task (1) extracted
# into its own variable and (2) there's only a single code path
# leading to the module being run. This is called by three
# functions: __init__.py::_do_handler_run(), linear.py::run(), and
# free.py::run() so we'd have to add to all three to do it there.
# The next common higher level is __init__.py::run() and that has
# tasks inside of play_iterator so we'd have to extract them to do it
# there.
global action_write_locks
if task . action not in action_write_locks :
display . debug ( ' Creating lock for %s ' % task . action )
action_write_locks [ task . action ] = Lock ( )
# and then queue the new task