Skip to content

Commit bb36f34

Browse files
authored
Merge pull request #2765 from rtfd/fix-symlink
Fix symlinking race condition
2 parents e9bec3e + a395814 commit bb36f34

File tree

2 files changed

+25
-8
lines changed

2 files changed

+25
-8
lines changed

readthedocs/core/symlink.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262

6363
from readthedocs.builds.models import Version
6464
from readthedocs.core.utils.extend import SettingsOverrideObject
65+
from readthedocs.core.utils import safe_makedirs
6566
from readthedocs.projects import constants
6667
from readthedocs.projects.models import Domain
6768
from readthedocs.projects.utils import run
@@ -100,19 +101,19 @@ def sanity_check(self):
100101
if os.path.islink(self.project_root) and not self.project.single_version:
101102
self._log("Removing single version symlink")
102103
os.unlink(self.project_root)
103-
os.makedirs(self.project_root)
104+
safe_makedirs(self.project_root)
104105
elif (self.project.single_version and
105106
not os.path.islink(self.project_root) and
106107
os.path.exists(self.project_root)):
107108
shutil.rmtree(self.project_root)
108109
elif not os.path.lexists(self.project_root):
109-
os.makedirs(self.project_root)
110+
safe_makedirs(self.project_root)
110111

111112
# CNAME root directories
112113
if not os.path.lexists(self.CNAME_ROOT):
113-
os.makedirs(self.CNAME_ROOT)
114+
safe_makedirs(self.CNAME_ROOT)
114115
if not os.path.lexists(self.PROJECT_CNAME_ROOT):
115-
os.makedirs(self.PROJECT_CNAME_ROOT)
116+
safe_makedirs(self.PROJECT_CNAME_ROOT)
116117

117118
def run(self):
118119
"""
@@ -177,7 +178,7 @@ def symlink_subprojects(self):
177178
if rels.count():
178179
# Don't creat the `projects/` directory unless subprojects exist.
179180
if not os.path.exists(self.subproject_root):
180-
os.makedirs(self.subproject_root)
181+
safe_makedirs(self.subproject_root)
181182
for rel in rels:
182183
# A mapping of slugs for the subproject URL to the actual built
183184
# documentation
@@ -194,7 +195,7 @@ def symlink_subprojects(self):
194195
)
195196
symlink_dir = os.sep.join(symlink.split(os.path.sep)[:-1])
196197
if not os.path.lexists(symlink_dir):
197-
os.makedirs(symlink_dir)
198+
safe_makedirs(symlink_dir)
198199
run('ln -nsf %s %s' % (docs_dir, symlink))
199200

200201
# Remove old symlinks
@@ -219,7 +220,7 @@ def symlink_translations(self):
219220
if os.path.islink(language_dir):
220221
os.unlink(language_dir)
221222
if not os.path.lexists(language_dir):
222-
os.makedirs(language_dir)
223+
safe_makedirs(language_dir)
223224

224225
for (language, slug) in translations.items():
225226
self._log(u"Symlinking translation: {0}->{1}".format(language, slug))
@@ -271,7 +272,7 @@ def symlink_versions(self):
271272
version_queryset = self.get_version_queryset()
272273
if version_queryset.count():
273274
if not os.path.exists(version_dir):
274-
os.makedirs(version_dir)
275+
safe_makedirs(version_dir)
275276
for version in version_queryset:
276277
self._log(u"Symlinking Version: %s" % version)
277278
symlink = os.path.join(version_dir, version.slug)

readthedocs/core/utils/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import errno
12
import getpass
23
import logging
34
import os
@@ -153,3 +154,18 @@ def slugify(value, *args, **kwargs):
153154

154155

155156
slugify = allow_lazy(slugify, six.text_type, SafeText)
157+
158+
159+
def safe_makedirs(directory_name):
160+
"""
161+
Makedirs has an issue where it has a race condition around
162+
checking for a directory and then creating it.
163+
This catches the exception in the case where the dir already exists.
164+
"""
165+
166+
try:
167+
os.makedirs(directory_name)
168+
except OSError as e:
169+
if e.errno == errno.EEXIST:
170+
pass
171+
raise

0 commit comments

Comments
 (0)