Skip to content

Commit 7ba70b1

Browse files
Serialise publishes to avoid concurrent-rsync failures
1 parent 0fa3e4d commit 7ba70b1

2 files changed

Lines changed: 75 additions & 44 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
/build_root/
22
/logs/
33
/www/
4-
# temporary lock file created while building the docs
4+
# temporary lock files created while building the docs
55
build_docs.lock
66
build_docs_archives.lock
77
build_docs_html.lock
8+
build_docs_html_en.lock
9+
publish-*.lock
810

911

1012
# Created by https://www.gitignore.io/api/python

build_docs.py

Lines changed: 72 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,27 @@ def edit(file: Path):
553553
temporary.rename(file)
554554

555555

556+
@contextmanager
557+
def wait_for_lock(
558+
path: Path, timeout: float = 600, poll_interval: float = 10
559+
) -> Iterator[None]:
560+
"""Context manager to hold *path* as a lock file, waiting up to *timeout* seconds for it."""
561+
deadline = perf_counter() + timeout
562+
while True:
563+
try:
564+
lock = zc.lockfile.LockFile(path)
565+
break
566+
except zc.lockfile.LockError:
567+
if perf_counter() >= deadline:
568+
raise
569+
logging.info("Waiting for lock %s...", path.name)
570+
sleep(poll_interval)
571+
try:
572+
yield
573+
finally:
574+
lock.close()
575+
576+
556577
def setup_switchers(script_content: bytes, html_root: Path) -> None:
557578
"""Setup cross-links between CPython versions:
558579
- Cross-link various languages in a language switcher
@@ -826,50 +847,58 @@ def copy_build_to_webroot(self, http: urllib3.PoolManager) -> None:
826847
language_dir.chmod(0o775)
827848
target = language_dir / self.build_meta.version
828849

829-
target.mkdir(parents=True, exist_ok=True)
830-
try:
831-
target.chmod(0o775)
832-
except PermissionError as err:
833-
logging.warning("Can't change mod of %s: %s", target, str(err))
834-
chgrp(target, group=self.group, recursive=True)
835-
836-
changed = 0
837-
if self.includes_html:
838-
# Copy built HTML files to webroot (default /srv/docs.python.org)
839-
changed += changed_files(self.checkout / "Doc" / "build" / "html", target)
840-
logging.info("Copying HTML files to %s", target)
841-
chgrp(
842-
self.checkout / "Doc" / "build" / "html/",
843-
group=self.group,
844-
recursive=True,
845-
)
846-
chmod_make_readable(self.checkout / "Doc" / "build" / "html")
847-
run((
848-
"rsync",
849-
"-a",
850-
"--delete-delay",
851-
"--filter",
852-
"P archives/",
853-
str(self.checkout / "Doc" / "build" / "html") + "/",
854-
target,
855-
))
856-
857-
dist_dir = self.checkout / "Doc" / "dist"
858-
if dist_dir.is_dir():
859-
# Copy archive files to /archives/
860-
logging.debug("Copying dist files.")
861-
chgrp(dist_dir, group=self.group, recursive=True)
862-
chmod_make_readable(dist_dir)
863-
archives_dir = target / "archives"
864-
archives_dir.mkdir(parents=True, exist_ok=True)
865-
archives_dir.chmod(
866-
archives_dir.stat().st_mode | stat.S_IROTH | stat.S_IXOTH
867-
)
868-
chgrp(archives_dir, group=self.group)
869-
changed += 1
870-
for dist_file in dist_dir.iterdir():
871-
shutil.copy2(dist_file, archives_dir / dist_file.name)
850+
# Builds run concurrently but may publish the same language/version to
851+
# the same directory, so serialise publishes per target.
852+
# Contention is expected, but brief, so wait instead of dying.
853+
with wait_for_lock(
854+
HERE / f"publish-{self.build_meta.language}-{self.build_meta.version}.lock"
855+
):
856+
target.mkdir(parents=True, exist_ok=True)
857+
try:
858+
target.chmod(0o775)
859+
except PermissionError as err:
860+
logging.warning("Can't change mod of %s: %s", target, str(err))
861+
chgrp(target, group=self.group, recursive=True)
862+
863+
changed = 0
864+
if self.includes_html:
865+
# Copy built HTML files to webroot (default /srv/docs.python.org)
866+
changed += changed_files(
867+
self.checkout / "Doc" / "build" / "html", target
868+
)
869+
logging.info("Copying HTML files to %s", target)
870+
chgrp(
871+
self.checkout / "Doc" / "build" / "html/",
872+
group=self.group,
873+
recursive=True,
874+
)
875+
chmod_make_readable(self.checkout / "Doc" / "build" / "html")
876+
run((
877+
"rsync",
878+
"-a",
879+
"--delete-delay",
880+
"--filter",
881+
"P archives/",
882+
str(self.checkout / "Doc" / "build" / "html") + "/",
883+
target,
884+
))
885+
886+
dist_dir = self.checkout / "Doc" / "dist"
887+
if dist_dir.is_dir():
888+
# Copy archive files to /archives/
889+
logging.debug("Copying dist files.")
890+
chgrp(dist_dir, group=self.group, recursive=True)
891+
chmod_make_readable(dist_dir)
892+
archives_dir = target / "archives"
893+
archives_dir.mkdir(parents=True, exist_ok=True)
894+
archives_dir.chmod(
895+
archives_dir.stat().st_mode | stat.S_IROTH | stat.S_IXOTH
896+
)
897+
chgrp(archives_dir, group=self.group)
872898
changed += 1
899+
for dist_file in dist_dir.iterdir():
900+
shutil.copy2(dist_file, archives_dir / dist_file.name)
901+
changed += 1
873902

874903
logging.info("%s files changed", changed)
875904
if changed and not self.skip_cache_invalidation:

0 commit comments

Comments
 (0)