diff --git a/docs/changelog/1516.bugfix.rst b/docs/changelog/1516.bugfix.rst new file mode 100644 index 000000000..3aa93b5a3 --- /dev/null +++ b/docs/changelog/1516.bugfix.rst @@ -0,0 +1 @@ +Creating virtual environments in parallel fail with cannot acquire lock within app data - by ``gaborbernat``. diff --git a/src/virtualenv/util/lock.py b/src/virtualenv/util/lock.py index e37fd5995..78bb744d6 100644 --- a/src/virtualenv/util/lock.py +++ b/src/virtualenv/util/lock.py @@ -2,12 +2,13 @@ from __future__ import absolute_import, unicode_literals import logging +import os from contextlib import contextmanager from threading import Lock from filelock import FileLock, Timeout -from virtualenv.util.path import Path, ensure_dir +from virtualenv.util.path import Path class _CountedFileLock(FileLock): @@ -72,11 +73,17 @@ def __exit__(self, exc_type, exc_val, exc_tb): self._release(self._lock) def _lock_file(self, lock): - ensure_dir(self.path) + # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without + # a lock, but that lock might then become expensive, and it's not clear where that lock should live. + # Instead here we just ignore if we fail to create the directory. + try: + os.makedirs(str(self.path)) + except OSError: + pass try: lock.acquire(0.0001) except Timeout: - logging.debug("lock file %s present, will block until released", self._lock.lock_file) + logging.debug("lock file %s present, will block until released", lock.lock_file) lock.release() # release the acquire try from above lock.acquire() diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py index 476b7a3a6..d16d8b7a5 100644 --- a/tests/unit/create/test_creator.py +++ b/tests/unit/create/test_creator.py @@ -5,8 +5,10 @@ import logging import os import stat +import subprocess import sys from itertools import product +from threading import Thread import pytest import six @@ -275,3 +277,16 @@ def test_cross_major(cross_python, coverage_env, tmp_path, current_fastest): coverage_env() env = PythonInfo.from_exe(str(result.creator.exe)) assert env.version_info.major != CURRENT.version_info.major + + +def test_create_parallel(tmp_path, monkeypatch): + monkeypatch.setenv(str("_VIRTUALENV_OVERRIDE_APP_DATA"), str(tmp_path)) + + def create(count): + subprocess.check_call([sys.executable, "-m", "virtualenv", str(tmp_path / "venv{}".format(count))]) + + threads = [Thread(target=create, args=(i,)) for i in range(1, 4)] + for thread in threads: + thread.start() + for thread in threads: + thread.join()