Skip to content

Commit

Permalink
Avoid parallel execution of a specific schenario (#2638)
Browse files Browse the repository at this point in the history
Adds a lock and waits for it in order to avoid running in parallel
using the same ephemeral directory.
  • Loading branch information
ssbarnea committed Sep 7, 2020
1 parent 0172637 commit 65ccd6b
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 17 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ repos:
- id: pylint
additional_dependencies:
- ansible-base
- testinfra
5 changes: 5 additions & 0 deletions molecule/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Constants used by molecule."""


RC_SUCCESS = 0
RC_TIMEOUT = 3
60 changes: 43 additions & 17 deletions molecule/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
# DEALINGS IN THE SOFTWARE.
"""Molecule Scenario Module."""

import fcntl
import fnmatch
import os
import shutil
from pathlib import Path
from time import sleep

from molecule import logger, scenarios, util
from molecule.constants import RC_TIMEOUT

LOG = logger.get_logger(__name__)

Expand Down Expand Up @@ -91,6 +94,7 @@ def __init__(self, config):
:param config: An instance of a Molecule config.
:return: None
"""
self._lock = None
self.config = config
self._setup()

Expand Down Expand Up @@ -143,22 +147,42 @@ def directory(self):

@property
def ephemeral_directory(self):
_ephemeral_directory = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY")
if _ephemeral_directory:
return _ephemeral_directory

project_directory = os.path.basename(self.config.project_directory)

if self.config.is_parallel:
project_directory = "{}-{}".format(project_directory, self.config._run_uuid)

project_scenario_directory = os.path.join(
self.config.cache_directory, project_directory, self.name
)

path = ephemeral_directory(project_scenario_directory)

return ephemeral_directory(path)
path = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY", None)
if not path:

project_directory = os.path.basename(self.config.project_directory)

if self.config.is_parallel:
project_directory = "{}-{}".format(
project_directory, self.config._run_uuid
)

project_scenario_directory = os.path.join(
self.config.cache_directory, project_directory, self.name
)

path = ephemeral_directory(project_scenario_directory)

# TODO(ssbarnea): Tune or make the retry logic configurable once we have enough data
if not self._lock:
self._lock = open(os.path.join(path, ".lock"), "w")
for i in range(1, 5):
try:
fcntl.lockf(self._lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except OSError:
delay = 30 * i
LOG.warning(
"Retrying to acquire lock on %s, waiting for %s seconds",
path,
delay,
)
sleep(delay)
else:
LOG.warning("Timedout trying to acquire lock on %s", path)
raise SystemExit(RC_TIMEOUT)

return path

@property
def inventory_directory(self):
Expand Down Expand Up @@ -246,7 +270,7 @@ def _setup(self):
os.makedirs(self.inventory_directory)


def ephemeral_directory(path=None):
def ephemeral_directory(path: str = None) -> str:
"""
Return temporary directory to be used by molecule.
Expand All @@ -256,6 +280,8 @@ def ephemeral_directory(path=None):
d = os.getenv("MOLECULE_EPHEMERAL_DIRECTORY")
if not d:
d = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
if not d:
raise RuntimeError("Unable to determine ephemeral directory to use.")
d = os.path.abspath(os.path.join(d, path if path else "molecule"))

if not os.path.isdir(d):
Expand Down

0 comments on commit 65ccd6b

Please sign in to comment.