@@ -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+
556577def 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