We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
virtualenv parallel run silently breaks
When running virtualenv creation in parallel, the app-data image builder is not synchronized and inconsistent states might break the virtual environment creation. Furthermore, in this case no error is raised in case of the failed commands.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/virtualenv/seed/embed/via_app_data/via_app_data.py`
Content:
```
1 """Bootstrap"""
2 from __future__ import absolute_import, unicode_literals
3 
4 import logging
5 from contextlib import contextmanager
6 from subprocess import CalledProcessError
7 from threading import Lock, Thread
8 
9 import six
10 
11 from virtualenv.info import fs_supports_symlink
12 from virtualenv.seed.embed.base_embed import BaseEmbed
13 from virtualenv.seed.wheels import get_wheel
14 from virtualenv.util.path import Path
15 
16 from .pip_install.copy import CopyPipInstall
17 from .pip_install.symlink import SymlinkPipInstall
18 
19 
20 class FromAppData(BaseEmbed):
21     def __init__(self, options):
22         super(FromAppData, self).__init__(options)
23         self.symlinks = options.symlink_app_data
24 
25     @classmethod
26     def add_parser_arguments(cls, parser, interpreter, app_data):
27         super(FromAppData, cls).add_parser_arguments(parser, interpreter, app_data)
28         can_symlink = app_data.transient is False and fs_supports_symlink()
29         parser.add_argument(
30             "--symlink-app-data",
31             dest="symlink_app_data",
32             action="store_true" if can_symlink else "store_false",
33             help="{} symlink the python packages from the app-data folder (requires seed pip>=19.3)".format(
34                 "" if can_symlink else "not supported - ",
35             ),
36             default=False,
37         )
38 
39     def run(self, creator):
40         if not self.enabled:
41             return
42         with self._get_seed_wheels(creator) as name_to_whl:
43             pip_version = name_to_whl["pip"].version_tuple if "pip" in name_to_whl else None
44             installer_class = self.installer_class(pip_version)
45 
46             def _install(name, wheel):
47                 logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__)
48                 key = Path(installer_class.__name__) / wheel.path.stem
49                 wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)
50                 installer = installer_class(wheel.path, creator, wheel_img)
51                 if not installer.has_image():
52                     installer.build_image()
53                 installer.install(creator.interpreter.version_info)
54 
55             threads = list(Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items())
56             for thread in threads:
57                 thread.start()
58             for thread in threads:
59                 thread.join()
60 
61     @contextmanager
62     def _get_seed_wheels(self, creator):
63         name_to_whl, lock, fail = {}, Lock(), {}
64 
65         def _get(distribution, version):
66             for_py_version = creator.interpreter.version_release_str
67             failure, result = None, None
68             # fallback to download in case the exact version is not available
69             for download in [True] if self.download else [False, True]:
70                 failure = None
71                 try:
72                     result = get_wheel(
73                         distribution=distribution,
74                         version=version,
75                         for_py_version=for_py_version,
76                         search_dirs=self.extra_search_dir,
77                         download=download,
78                         app_data=self.app_data,
79                         do_periodic_update=self.periodic_update,
80                     )
81                     if result is not None:
82                         break
83                 except Exception as exception:  # noqa
84                     logging.exception("fail")
85                     failure = exception
86             if failure:
87                 if isinstance(failure, CalledProcessError):
88                     msg = "failed to download {}".format(distribution)
89                     if version is not None:
90                         msg += " version {}".format(version)
91                     msg += ", pip download exit code {}".format(failure.returncode)
92                     output = failure.output if six.PY2 else (failure.output + failure.stderr)
93                     if output:
94                         msg += "\n"
95                         msg += output
96                 else:
97                     msg = repr(failure)
98                 logging.error(msg)
99                 with lock:
100                     fail[distribution] = version
101             else:
102                 with lock:
103                     name_to_whl[distribution] = result
104 
105         threads = list(
106             Thread(target=_get, args=(distribution, version))
107             for distribution, version in self.distribution_to_versions().items()
108         )
109         for thread in threads:
110             thread.start()
111         for thread in threads:
112             thread.join()
113         if fail:
114             raise RuntimeError("seed failed due to failing to download wheels {}".format(", ".join(fail.keys())))
115         yield name_to_whl
116 
117     def installer_class(self, pip_version_tuple):
118         if self.symlinks and pip_version_tuple:
119             # symlink support requires pip 19.3+
120             if pip_version_tuple >= (19, 3):
121                 return SymlinkPipInstall
122         return CopyPipInstall
123 
124     def __unicode__(self):
125         base = super(FromAppData, self).__unicode__()
126         msg = ", via={}, app_data_dir={}".format("symlink" if self.symlinks else "copy", self.app_data)
127         return base[:-1] + msg + base[-1]
128
```

Path: `src/virtualenv/util/lock.py`
Content:
```
1 """holds locking functionality that works across processes"""
2 from __future__ import absolute_import, unicode_literals
3 
4 import logging
5 import os
6 from contextlib import contextmanager
7 from threading import Lock, RLock
8 
9 from filelock import FileLock, Timeout
10 
11 from virtualenv.util.path import Path
12 
13 
14 class _CountedFileLock(FileLock):
15     def __init__(self, lock_file):
16         super(_CountedFileLock, self).__init__(lock_file)
17         self.count = 0
18         self.thread_safe = RLock()
19 
20     def acquire(self, timeout=None, poll_intervall=0.05):
21         with self.thread_safe:
22             if self.count == 0:
23                 super(_CountedFileLock, self).acquire(timeout=timeout, poll_intervall=poll_intervall)
24             self.count += 1
25 
26     def release(self, force=False):
27         with self.thread_safe:
28             if self.count == 1:
29                 super(_CountedFileLock, self).release()
30             self.count = max(self.count - 1, 0)
31 
32 
33 _lock_store = {}
34 _store_lock = Lock()
35 
36 
37 class ReentrantFileLock(object):
38     def __init__(self, folder):
39         self._lock = None
40         path = Path(folder)
41         self.path = path.resolve() if path.exists() else path
42 
43     def __repr__(self):
44         return "{}({})".format(self.__class__.__name__, self.path)
45 
46     def __div__(self, other):
47         return ReentrantFileLock(self.path / other)
48 
49     def __truediv__(self, other):
50         return self.__div__(other)
51 
52     def _create_lock(self, name=""):
53         lock_file = str(self.path / "{}.lock".format(name))
54         with _store_lock:
55             if lock_file not in _lock_store:
56                 _lock_store[lock_file] = _CountedFileLock(lock_file)
57             return _lock_store[lock_file]
58 
59     @staticmethod
60     def _del_lock(lock):
61         with _store_lock:
62             if lock is not None:
63                 with lock.thread_safe:
64                     if lock.count == 0:
65                         _lock_store.pop(lock.lock_file, None)
66 
67     def __del__(self):
68         self._del_lock(self._lock)
69 
70     def __enter__(self):
71         self._lock = self._create_lock()
72         self._lock_file(self._lock)
73 
74     def __exit__(self, exc_type, exc_val, exc_tb):
75         self._release(self._lock)
76 
77     def _lock_file(self, lock, no_block=False):
78         # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without
79         # a lock, but that lock might then become expensive, and it's not clear where that lock should live.
80         # Instead here we just ignore if we fail to create the directory.
81         try:
82             os.makedirs(str(self.path))
83         except OSError:
84             pass
85         try:
86             lock.acquire(0.0001)
87         except Timeout:
88             if no_block:
89                 raise
90             logging.debug("lock file %s present, will block until released", lock.lock_file)
91             lock.release()  # release the acquire try from above
92             lock.acquire()
93 
94     @staticmethod
95     def _release(lock):
96         lock.release()
97 
98     @contextmanager
99     def lock_for_key(self, name, no_block=False):
100         lock = self._create_lock(name)
101         try:
102             try:
103                 self._lock_file(lock, no_block)
104                 yield
105             finally:
106                 self._release(lock)
107         finally:
108             self._del_lock(lock)
109 
110 
111 __all__ = (
112     "Timeout",
113     "ReentrantFileLock",
114 )
115
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/virtualenv/seed/embed/via_app_data/via_app_data.py b/src/virtualenv/seed/embed/via_app_data/via_app_data.py
--- a/src/virtualenv/seed/embed/via_app_data/via_app_data.py
+++ b/src/virtualenv/seed/embed/via_app_data/via_app_data.py
@@ -2,6 +2,8 @@
 from __future__ import absolute_import, unicode_literals
 
 import logging
+import sys
+import traceback
 from contextlib import contextmanager
 from subprocess import CalledProcessError
 from threading import Lock, Thread
@@ -11,7 +13,9 @@
 from virtualenv.info import fs_supports_symlink
 from virtualenv.seed.embed.base_embed import BaseEmbed
 from virtualenv.seed.wheels import get_wheel
+from virtualenv.util.lock import _CountedFileLock
 from virtualenv.util.path import Path
+from virtualenv.util.six import ensure_text
 
 from .pip_install.copy import CopyPipInstall
 from .pip_install.symlink import SymlinkPipInstall
@@ -42,21 +46,32 @@
         with self._get_seed_wheels(creator) as name_to_whl:
             pip_version = name_to_whl["pip"].version_tuple if "pip" in name_to_whl else None
             installer_class = self.installer_class(pip_version)
+            exceptions = {}
 
             def _install(name, wheel):
-                logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__)
-                key = Path(installer_class.__name__) / wheel.path.stem
-                wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)
-                installer = installer_class(wheel.path, creator, wheel_img)
-                if not installer.has_image():
-                    installer.build_image()
-                installer.install(creator.interpreter.version_info)
+                try:
+                    logging.debug("install %s from wheel %s via %s", name, wheel, installer_class.__name__)
+                    key = Path(installer_class.__name__) / wheel.path.stem
+                    wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)
+                    installer = installer_class(wheel.path, creator, wheel_img)
+                    with _CountedFileLock(ensure_text(str(wheel_img.parent / "{}.lock".format(wheel_img.name)))):
+                        if not installer.has_image():
+                            installer.build_image()
+                    installer.install(creator.interpreter.version_info)
+                except Exception:  # noqa
+                    exceptions[name] = sys.exc_info()
 
             threads = list(Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items())
             for thread in threads:
                 thread.start()
             for thread in threads:
                 thread.join()
+            if exceptions:
+                messages = ["failed to build image {} because:".format(", ".join(exceptions.keys()))]
+                for value in exceptions.values():
+                    exc_type, exc_value, exc_traceback = value
+                    messages.append("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
+                raise RuntimeError("\n".join(messages))
 
     @contextmanager
     def _get_seed_wheels(self, creator):
diff --git a/src/virtualenv/util/lock.py b/src/virtualenv/util/lock.py
--- a/src/virtualenv/util/lock.py
+++ b/src/virtualenv/util/lock.py
@@ -13,6 +13,12 @@
 
 class _CountedFileLock(FileLock):
     def __init__(self, lock_file):
+        parent = os.path.dirname(lock_file)
+        if not os.path.exists(parent):
+            try:
+                os.makedirs(parent)
+            except OSError:
+                pass
         super(_CountedFileLock, self).__init__(lock_file)
         self.count = 0
         self.thread_safe = RLock()

{"golden_diff": "diff --git a/src/virtualenv/seed/embed/via_app_data/via_app_data.py b/src/virtualenv/seed/embed/via_app_data/via_app_data.py\n--- a/src/virtualenv/seed/embed/via_app_data/via_app_data.py\n+++ b/src/virtualenv/seed/embed/via_app_data/via_app_data.py\n@@ -2,6 +2,8 @@\n from __future__ import absolute_import, unicode_literals\n \n import logging\n+import sys\n+import traceback\n from contextlib import contextmanager\n from subprocess import CalledProcessError\n from threading import Lock, Thread\n@@ -11,7 +13,9 @@\n from virtualenv.info import fs_supports_symlink\n from virtualenv.seed.embed.base_embed import BaseEmbed\n from virtualenv.seed.wheels import get_wheel\n+from virtualenv.util.lock import _CountedFileLock\n from virtualenv.util.path import Path\n+from virtualenv.util.six import ensure_text\n \n from .pip_install.copy import CopyPipInstall\n from .pip_install.symlink import SymlinkPipInstall\n@@ -42,21 +46,32 @@\n         with self._get_seed_wheels(creator) as name_to_whl:\n             pip_version = name_to_whl[\"pip\"].version_tuple if \"pip\" in name_to_whl else None\n             installer_class = self.installer_class(pip_version)\n+            exceptions = {}\n \n             def _install(name, wheel):\n-                logging.debug(\"install %s from wheel %s via %s\", name, wheel, installer_class.__name__)\n-                key = Path(installer_class.__name__) / wheel.path.stem\n-                wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)\n-                installer = installer_class(wheel.path, creator, wheel_img)\n-                if not installer.has_image():\n-                    installer.build_image()\n-                installer.install(creator.interpreter.version_info)\n+                try:\n+                    logging.debug(\"install %s from wheel %s via %s\", name, wheel, installer_class.__name__)\n+                    key = Path(installer_class.__name__) / wheel.path.stem\n+                    wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)\n+                    installer = installer_class(wheel.path, creator, wheel_img)\n+                    with _CountedFileLock(ensure_text(str(wheel_img.parent / \"{}.lock\".format(wheel_img.name)))):\n+                        if not installer.has_image():\n+                            installer.build_image()\n+                    installer.install(creator.interpreter.version_info)\n+                except Exception:  # noqa\n+                    exceptions[name] = sys.exc_info()\n \n             threads = list(Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items())\n             for thread in threads:\n                 thread.start()\n             for thread in threads:\n                 thread.join()\n+            if exceptions:\n+                messages = [\"failed to build image {} because:\".format(\", \".join(exceptions.keys()))]\n+                for value in exceptions.values():\n+                    exc_type, exc_value, exc_traceback = value\n+                    messages.append(\"\".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))\n+                raise RuntimeError(\"\\n\".join(messages))\n \n     @contextmanager\n     def _get_seed_wheels(self, creator):\ndiff --git a/src/virtualenv/util/lock.py b/src/virtualenv/util/lock.py\n--- a/src/virtualenv/util/lock.py\n+++ b/src/virtualenv/util/lock.py\n@@ -13,6 +13,12 @@\n \n class _CountedFileLock(FileLock):\n     def __init__(self, lock_file):\n+        parent = os.path.dirname(lock_file)\n+        if not os.path.exists(parent):\n+            try:\n+                os.makedirs(parent)\n+            except OSError:\n+                pass\n         super(_CountedFileLock, self).__init__(lock_file)\n         self.count = 0\n         self.thread_safe = RLock()\n", "issue": "virtualenv parallel run silently breaks\nWhen running virtualenv creation in parallel, the app-data image builder is not synchronized and inconsistent states might break the virtual environment creation. Furthermore, in this case no error is raised in case of the failed commands.\n", "before_files": [{"content": "\"\"\"Bootstrap\"\"\"\nfrom __future__ import absolute_import, unicode_literals\n\nimport logging\nfrom contextlib import contextmanager\nfrom subprocess import CalledProcessError\nfrom threading import Lock, Thread\n\nimport six\n\nfrom virtualenv.info import fs_supports_symlink\nfrom virtualenv.seed.embed.base_embed import BaseEmbed\nfrom virtualenv.seed.wheels import get_wheel\nfrom virtualenv.util.path import Path\n\nfrom .pip_install.copy import CopyPipInstall\nfrom .pip_install.symlink import SymlinkPipInstall\n\n\nclass FromAppData(BaseEmbed):\n    def __init__(self, options):\n        super(FromAppData, self).__init__(options)\n        self.symlinks = options.symlink_app_data\n\n    @classmethod\n    def add_parser_arguments(cls, parser, interpreter, app_data):\n        super(FromAppData, cls).add_parser_arguments(parser, interpreter, app_data)\n        can_symlink = app_data.transient is False and fs_supports_symlink()\n        parser.add_argument(\n            \"--symlink-app-data\",\n            dest=\"symlink_app_data\",\n            action=\"store_true\" if can_symlink else \"store_false\",\n            help=\"{} symlink the python packages from the app-data folder (requires seed pip>=19.3)\".format(\n                \"\" if can_symlink else \"not supported - \",\n            ),\n            default=False,\n        )\n\n    def run(self, creator):\n        if not self.enabled:\n            return\n        with self._get_seed_wheels(creator) as name_to_whl:\n            pip_version = name_to_whl[\"pip\"].version_tuple if \"pip\" in name_to_whl else None\n            installer_class = self.installer_class(pip_version)\n\n            def _install(name, wheel):\n                logging.debug(\"install %s from wheel %s via %s\", name, wheel, installer_class.__name__)\n                key = Path(installer_class.__name__) / wheel.path.stem\n                wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)\n                installer = installer_class(wheel.path, creator, wheel_img)\n                if not installer.has_image():\n                    installer.build_image()\n                installer.install(creator.interpreter.version_info)\n\n            threads = list(Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items())\n            for thread in threads:\n                thread.start()\n            for thread in threads:\n                thread.join()\n\n    @contextmanager\n    def _get_seed_wheels(self, creator):\n        name_to_whl, lock, fail = {}, Lock(), {}\n\n        def _get(distribution, version):\n            for_py_version = creator.interpreter.version_release_str\n            failure, result = None, None\n            # fallback to download in case the exact version is not available\n            for download in [True] if self.download else [False, True]:\n                failure = None\n                try:\n                    result = get_wheel(\n                        distribution=distribution,\n                        version=version,\n                        for_py_version=for_py_version,\n                        search_dirs=self.extra_search_dir,\n                        download=download,\n                        app_data=self.app_data,\n                        do_periodic_update=self.periodic_update,\n                    )\n                    if result is not None:\n                        break\n                except Exception as exception:  # noqa\n                    logging.exception(\"fail\")\n                    failure = exception\n            if failure:\n                if isinstance(failure, CalledProcessError):\n                    msg = \"failed to download {}\".format(distribution)\n                    if version is not None:\n                        msg += \" version {}\".format(version)\n                    msg += \", pip download exit code {}\".format(failure.returncode)\n                    output = failure.output if six.PY2 else (failure.output + failure.stderr)\n                    if output:\n                        msg += \"\\n\"\n                        msg += output\n                else:\n                    msg = repr(failure)\n                logging.error(msg)\n                with lock:\n                    fail[distribution] = version\n            else:\n                with lock:\n                    name_to_whl[distribution] = result\n\n        threads = list(\n            Thread(target=_get, args=(distribution, version))\n            for distribution, version in self.distribution_to_versions().items()\n        )\n        for thread in threads:\n            thread.start()\n        for thread in threads:\n            thread.join()\n        if fail:\n            raise RuntimeError(\"seed failed due to failing to download wheels {}\".format(\", \".join(fail.keys())))\n        yield name_to_whl\n\n    def installer_class(self, pip_version_tuple):\n        if self.symlinks and pip_version_tuple:\n            # symlink support requires pip 19.3+\n            if pip_version_tuple >= (19, 3):\n                return SymlinkPipInstall\n        return CopyPipInstall\n\n    def __unicode__(self):\n        base = super(FromAppData, self).__unicode__()\n        msg = \", via={}, app_data_dir={}\".format(\"symlink\" if self.symlinks else \"copy\", self.app_data)\n        return base[:-1] + msg + base[-1]\n", "path": "src/virtualenv/seed/embed/via_app_data/via_app_data.py"}, {"content": "\"\"\"holds locking functionality that works across processes\"\"\"\nfrom __future__ import absolute_import, unicode_literals\n\nimport logging\nimport os\nfrom contextlib import contextmanager\nfrom threading import Lock, RLock\n\nfrom filelock import FileLock, Timeout\n\nfrom virtualenv.util.path import Path\n\n\nclass _CountedFileLock(FileLock):\n    def __init__(self, lock_file):\n        super(_CountedFileLock, self).__init__(lock_file)\n        self.count = 0\n        self.thread_safe = RLock()\n\n    def acquire(self, timeout=None, poll_intervall=0.05):\n        with self.thread_safe:\n            if self.count == 0:\n                super(_CountedFileLock, self).acquire(timeout=timeout, poll_intervall=poll_intervall)\n            self.count += 1\n\n    def release(self, force=False):\n        with self.thread_safe:\n            if self.count == 1:\n                super(_CountedFileLock, self).release()\n            self.count = max(self.count - 1, 0)\n\n\n_lock_store = {}\n_store_lock = Lock()\n\n\nclass ReentrantFileLock(object):\n    def __init__(self, folder):\n        self._lock = None\n        path = Path(folder)\n        self.path = path.resolve() if path.exists() else path\n\n    def __repr__(self):\n        return \"{}({})\".format(self.__class__.__name__, self.path)\n\n    def __div__(self, other):\n        return ReentrantFileLock(self.path / other)\n\n    def __truediv__(self, other):\n        return self.__div__(other)\n\n    def _create_lock(self, name=\"\"):\n        lock_file = str(self.path / \"{}.lock\".format(name))\n        with _store_lock:\n            if lock_file not in _lock_store:\n                _lock_store[lock_file] = _CountedFileLock(lock_file)\n            return _lock_store[lock_file]\n\n    @staticmethod\n    def _del_lock(lock):\n        with _store_lock:\n            if lock is not None:\n                with lock.thread_safe:\n                    if lock.count == 0:\n                        _lock_store.pop(lock.lock_file, None)\n\n    def __del__(self):\n        self._del_lock(self._lock)\n\n    def __enter__(self):\n        self._lock = self._create_lock()\n        self._lock_file(self._lock)\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self._release(self._lock)\n\n    def _lock_file(self, lock, no_block=False):\n        # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without\n        # a lock, but that lock might then become expensive, and it's not clear where that lock should live.\n        # Instead here we just ignore if we fail to create the directory.\n        try:\n            os.makedirs(str(self.path))\n        except OSError:\n            pass\n        try:\n            lock.acquire(0.0001)\n        except Timeout:\n            if no_block:\n                raise\n            logging.debug(\"lock file %s present, will block until released\", lock.lock_file)\n            lock.release()  # release the acquire try from above\n            lock.acquire()\n\n    @staticmethod\n    def _release(lock):\n        lock.release()\n\n    @contextmanager\n    def lock_for_key(self, name, no_block=False):\n        lock = self._create_lock(name)\n        try:\n            try:\n                self._lock_file(lock, no_block)\n                yield\n            finally:\n                self._release(lock)\n        finally:\n            self._del_lock(lock)\n\n\n__all__ = (\n    \"Timeout\",\n    \"ReentrantFileLock\",\n)\n", "path": "src/virtualenv/util/lock.py"}], "after_files": [{"content": "\"\"\"Bootstrap\"\"\"\nfrom __future__ import absolute_import, unicode_literals\n\nimport logging\nimport sys\nimport traceback\nfrom contextlib import contextmanager\nfrom subprocess import CalledProcessError\nfrom threading import Lock, Thread\n\nimport six\n\nfrom virtualenv.info import fs_supports_symlink\nfrom virtualenv.seed.embed.base_embed import BaseEmbed\nfrom virtualenv.seed.wheels import get_wheel\nfrom virtualenv.util.lock import _CountedFileLock\nfrom virtualenv.util.path import Path\nfrom virtualenv.util.six import ensure_text\n\nfrom .pip_install.copy import CopyPipInstall\nfrom .pip_install.symlink import SymlinkPipInstall\n\n\nclass FromAppData(BaseEmbed):\n    def __init__(self, options):\n        super(FromAppData, self).__init__(options)\n        self.symlinks = options.symlink_app_data\n\n    @classmethod\n    def add_parser_arguments(cls, parser, interpreter, app_data):\n        super(FromAppData, cls).add_parser_arguments(parser, interpreter, app_data)\n        can_symlink = app_data.transient is False and fs_supports_symlink()\n        parser.add_argument(\n            \"--symlink-app-data\",\n            dest=\"symlink_app_data\",\n            action=\"store_true\" if can_symlink else \"store_false\",\n            help=\"{} symlink the python packages from the app-data folder (requires seed pip>=19.3)\".format(\n                \"\" if can_symlink else \"not supported - \",\n            ),\n            default=False,\n        )\n\n    def run(self, creator):\n        if not self.enabled:\n            return\n        with self._get_seed_wheels(creator) as name_to_whl:\n            pip_version = name_to_whl[\"pip\"].version_tuple if \"pip\" in name_to_whl else None\n            installer_class = self.installer_class(pip_version)\n            exceptions = {}\n\n            def _install(name, wheel):\n                try:\n                    logging.debug(\"install %s from wheel %s via %s\", name, wheel, installer_class.__name__)\n                    key = Path(installer_class.__name__) / wheel.path.stem\n                    wheel_img = self.app_data.wheel_image(creator.interpreter.version_release_str, key)\n                    installer = installer_class(wheel.path, creator, wheel_img)\n                    with _CountedFileLock(ensure_text(str(wheel_img.parent / \"{}.lock\".format(wheel_img.name)))):\n                        if not installer.has_image():\n                            installer.build_image()\n                    installer.install(creator.interpreter.version_info)\n                except Exception:  # noqa\n                    exceptions[name] = sys.exc_info()\n\n            threads = list(Thread(target=_install, args=(n, w)) for n, w in name_to_whl.items())\n            for thread in threads:\n                thread.start()\n            for thread in threads:\n                thread.join()\n            if exceptions:\n                messages = [\"failed to build image {} because:\".format(\", \".join(exceptions.keys()))]\n                for value in exceptions.values():\n                    exc_type, exc_value, exc_traceback = value\n                    messages.append(\"\".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))\n                raise RuntimeError(\"\\n\".join(messages))\n\n    @contextmanager\n    def _get_seed_wheels(self, creator):\n        name_to_whl, lock, fail = {}, Lock(), {}\n\n        def _get(distribution, version):\n            for_py_version = creator.interpreter.version_release_str\n            failure, result = None, None\n            # fallback to download in case the exact version is not available\n            for download in [True] if self.download else [False, True]:\n                failure = None\n                try:\n                    result = get_wheel(\n                        distribution=distribution,\n                        version=version,\n                        for_py_version=for_py_version,\n                        search_dirs=self.extra_search_dir,\n                        download=download,\n                        app_data=self.app_data,\n                        do_periodic_update=self.periodic_update,\n                    )\n                    if result is not None:\n                        break\n                except Exception as exception:  # noqa\n                    logging.exception(\"fail\")\n                    failure = exception\n            if failure:\n                if isinstance(failure, CalledProcessError):\n                    msg = \"failed to download {}\".format(distribution)\n                    if version is not None:\n                        msg += \" version {}\".format(version)\n                    msg += \", pip download exit code {}\".format(failure.returncode)\n                    output = failure.output if six.PY2 else (failure.output + failure.stderr)\n                    if output:\n                        msg += \"\\n\"\n                        msg += output\n                else:\n                    msg = repr(failure)\n                logging.error(msg)\n                with lock:\n                    fail[distribution] = version\n            else:\n                with lock:\n                    name_to_whl[distribution] = result\n\n        threads = list(\n            Thread(target=_get, args=(distribution, version))\n            for distribution, version in self.distribution_to_versions().items()\n        )\n        for thread in threads:\n            thread.start()\n        for thread in threads:\n            thread.join()\n        if fail:\n            raise RuntimeError(\"seed failed due to failing to download wheels {}\".format(\", \".join(fail.keys())))\n        yield name_to_whl\n\n    def installer_class(self, pip_version_tuple):\n        if self.symlinks and pip_version_tuple:\n            # symlink support requires pip 19.3+\n            if pip_version_tuple >= (19, 3):\n                return SymlinkPipInstall\n        return CopyPipInstall\n\n    def __unicode__(self):\n        base = super(FromAppData, self).__unicode__()\n        msg = \", via={}, app_data_dir={}\".format(\"symlink\" if self.symlinks else \"copy\", self.app_data)\n        return base[:-1] + msg + base[-1]\n", "path": "src/virtualenv/seed/embed/via_app_data/via_app_data.py"}, {"content": "\"\"\"holds locking functionality that works across processes\"\"\"\nfrom __future__ import absolute_import, unicode_literals\n\nimport logging\nimport os\nfrom contextlib import contextmanager\nfrom threading import Lock, RLock\n\nfrom filelock import FileLock, Timeout\n\nfrom virtualenv.util.path import Path\n\n\nclass _CountedFileLock(FileLock):\n    def __init__(self, lock_file):\n        parent = os.path.dirname(lock_file)\n        if not os.path.exists(parent):\n            try:\n                os.makedirs(parent)\n            except OSError:\n                pass\n        super(_CountedFileLock, self).__init__(lock_file)\n        self.count = 0\n        self.thread_safe = RLock()\n\n    def acquire(self, timeout=None, poll_intervall=0.05):\n        with self.thread_safe:\n            if self.count == 0:\n                super(_CountedFileLock, self).acquire(timeout=timeout, poll_intervall=poll_intervall)\n            self.count += 1\n\n    def release(self, force=False):\n        with self.thread_safe:\n            if self.count == 1:\n                super(_CountedFileLock, self).release()\n            self.count = max(self.count - 1, 0)\n\n\n_lock_store = {}\n_store_lock = Lock()\n\n\nclass ReentrantFileLock(object):\n    def __init__(self, folder):\n        self._lock = None\n        path = Path(folder)\n        self.path = path.resolve() if path.exists() else path\n\n    def __repr__(self):\n        return \"{}({})\".format(self.__class__.__name__, self.path)\n\n    def __div__(self, other):\n        return ReentrantFileLock(self.path / other)\n\n    def __truediv__(self, other):\n        return self.__div__(other)\n\n    def _create_lock(self, name=\"\"):\n        lock_file = str(self.path / \"{}.lock\".format(name))\n        with _store_lock:\n            if lock_file not in _lock_store:\n                _lock_store[lock_file] = _CountedFileLock(lock_file)\n            return _lock_store[lock_file]\n\n    @staticmethod\n    def _del_lock(lock):\n        with _store_lock:\n            if lock is not None:\n                with lock.thread_safe:\n                    if lock.count == 0:\n                        _lock_store.pop(lock.lock_file, None)\n\n    def __del__(self):\n        self._del_lock(self._lock)\n\n    def __enter__(self):\n        self._lock = self._create_lock()\n        self._lock_file(self._lock)\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self._release(self._lock)\n\n    def _lock_file(self, lock, no_block=False):\n        # multiple processes might be trying to get a first lock... so we cannot check if this directory exist without\n        # a lock, but that lock might then become expensive, and it's not clear where that lock should live.\n        # Instead here we just ignore if we fail to create the directory.\n        try:\n            os.makedirs(str(self.path))\n        except OSError:\n            pass\n        try:\n            lock.acquire(0.0001)\n        except Timeout:\n            if no_block:\n                raise\n            logging.debug(\"lock file %s present, will block until released\", lock.lock_file)\n            lock.release()  # release the acquire try from above\n            lock.acquire()\n\n    @staticmethod\n    def _release(lock):\n        lock.release()\n\n    @contextmanager\n    def lock_for_key(self, name, no_block=False):\n        lock = self._create_lock(name)\n        try:\n            try:\n                self._lock_file(lock, no_block)\n                yield\n            finally:\n                self._release(lock)\n        finally:\n            self._del_lock(lock)\n\n\n__all__ = (\n    \"Timeout\",\n    \"ReentrantFileLock\",\n)\n", "path": "src/virtualenv/util/lock.py"}]}