diff --git a/com/win32com/client/gencache.py b/com/win32com/client/gencache.py index fed4cc52c..0e903077e 100644 --- a/com/win32com/client/gencache.py +++ b/com/win32com/client/gencache.py @@ -23,6 +23,7 @@ from __future__ import annotations +import contextlib import glob import os import sys @@ -34,6 +35,7 @@ import pywintypes import win32com import win32com.client +import win32event from . import CLSIDToClass @@ -133,6 +135,22 @@ def _LoadDicts(): f.close() +@contextlib.contextmanager +def ModuleMutex(module_name): + """Given the output of GetGeneratedFilename, acquire a named mutex for that module + + This is required so that writes (generation) don't interfere with each other and with reads (import) + """ + mutex = win32event.CreateMutex(None, False, module_name) + with contextlib.closing(mutex): + # acquire mutex + win32event.WaitForSingleObject(mutex, win32event.INFINITE) + try: + yield + finally: + win32event.ReleaseMutex(mutex) + + def GetGeneratedFileName(clsid, lcid, major, minor): """Given the clsid, lcid, major and minor for a type lib, return the file name (no extension) providing this support. @@ -258,7 +276,8 @@ class which wraps the COM object. if sub_mod is not None: sub_mod_name = mod.__name__ + "." + sub_mod try: - __import__(sub_mod_name) + with ModuleMutex(mod.__name__.split(".")[-1]): + __import__(sub_mod_name) except ImportError: info = typelibCLSID, lcid, major, minor # Force the generation. If this typelibrary has explicitly been added, @@ -730,7 +749,8 @@ def GetGeneratedInfos(): def _GetModule(fname): """Given the name of a module in the gen_py directory, import and return it.""" mod_name = "win32com.gen_py.%s" % fname - mod = __import__(mod_name) + with ModuleMutex(fname): + __import__(mod_name) return sys.modules[mod_name] diff --git a/com/win32com/client/genpy.py b/com/win32com/client/genpy.py index a45791ae3..697d8eccf 100644 --- a/com/win32com/client/genpy.py +++ b/com/win32com/client/genpy.py @@ -18,9 +18,8 @@ import time import pythoncom -import win32com -from . import build +from . import build, gencache error = "makepy.error" makepy_version = "0.5.01" # Written to generated file. @@ -1037,35 +1036,15 @@ def open_writer(self, filename, encoding="utf-8"): def finish_writer(self, filename, f, worked): f.close() - try: - os.unlink(filename) - except OSError: - pass temp_filename = self.get_temp_filename(filename) if worked: + os.replace(temp_filename, filename) + else: try: - os.rename(temp_filename, filename) + os.unlink(filename) + os.unlink(temp_filename) except OSError: - # If we are really unlucky, another process may have written the - # file in between our calls to os.unlink and os.rename. So try - # again, but only once. - # There are still some race conditions, but they seem difficult to - # fix, and they probably occur much less frequently: - # * The os.rename failure could occur more than once if more than - # two processes are involved. - # * In between os.unlink and os.rename, another process could try - # to import the module, having seen that it already exists. - # * If another process starts a COM server while we are still - # generating __init__.py, that process sees that the folder - # already exists and assumes that __init__.py is already there - # as well. - try: - os.unlink(filename) - except OSError: - pass - os.rename(temp_filename, filename) - else: - os.unlink(temp_filename) + pass def get_temp_filename(self, filename): return "%s.%d.temp" % (filename, os.getpid()) @@ -1356,7 +1335,8 @@ def generate_child(self, child, dir): self.progress.Tick() worked = True finally: - self.finish_writer(out_name, self.file, worked) + with gencache.ModuleMutex(self.base_mod_name.split(".")[-1]): + self.finish_writer(out_name, self.file, worked) self.file = None finally: self.progress.Finished() diff --git a/com/win32com/client/makepy.py b/com/win32com/client/makepy.py index 53ff7e1c7..f7d601ecc 100644 --- a/com/win32com/client/makepy.py +++ b/com/win32com/client/makepy.py @@ -292,10 +292,11 @@ def GenerateFromTypeLibSpec( for typelib, info in typelibs: gen = genpy.Generator(typelib, info.dll, progress, bBuildHidden=bBuildHidden) + this_name = gencache.GetGeneratedFileName( + info.clsid, info.lcid, info.major, info.minor + ) + if file is None: - this_name = gencache.GetGeneratedFileName( - info.clsid, info.lcid, info.major, info.minor - ) full_name = os.path.join(gencache.GetGeneratePath(), this_name) if bForDemand: try: @@ -326,7 +327,8 @@ def GenerateFromTypeLibSpec( worked = True finally: if file is None: - gen.finish_writer(outputName, fileUse, worked) + with gencache.ModuleMutex(this_name): + gen.finish_writer(outputName, fileUse, worked) importlib.invalidate_caches() if bToGenDir: progress.SetDescription("Importing module") @@ -371,7 +373,8 @@ def GenerateChildFromTypeLibSpec( gen.generate_child(child, dir_path_name) progress.SetDescription("Importing module") importlib.invalidate_caches() - __import__("win32com.gen_py." + dir_name + "." + child) + with gencache.ModuleMutex(dir_name): + __import__("win32com.gen_py." + dir_name + "." + child) progress.Close()