Skip to content

Commit 034969e

Browse files
authored
Prevent race condition in genpy via named mutex (#2118)
1 parent cf8d985 commit 034969e

File tree

3 files changed

+38
-35
lines changed

3 files changed

+38
-35
lines changed

com/win32com/client/gencache.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
from __future__ import annotations
2525

26+
import contextlib
2627
import glob
2728
import os
2829
import sys
@@ -34,6 +35,7 @@
3435
import pywintypes
3536
import win32com
3637
import win32com.client
38+
import win32event
3739

3840
from . import CLSIDToClass
3941

@@ -133,6 +135,22 @@ def _LoadDicts():
133135
f.close()
134136

135137

138+
@contextlib.contextmanager
139+
def ModuleMutex(module_name):
140+
"""Given the output of GetGeneratedFilename, acquire a named mutex for that module
141+
142+
This is required so that writes (generation) don't interfere with each other and with reads (import)
143+
"""
144+
mutex = win32event.CreateMutex(None, False, module_name)
145+
with contextlib.closing(mutex):
146+
# acquire mutex
147+
win32event.WaitForSingleObject(mutex, win32event.INFINITE)
148+
try:
149+
yield
150+
finally:
151+
win32event.ReleaseMutex(mutex)
152+
153+
136154
def GetGeneratedFileName(clsid, lcid, major, minor):
137155
"""Given the clsid, lcid, major and minor for a type lib, return
138156
the file name (no extension) providing this support.
@@ -258,7 +276,8 @@ class which wraps the COM object.
258276
if sub_mod is not None:
259277
sub_mod_name = mod.__name__ + "." + sub_mod
260278
try:
261-
__import__(sub_mod_name)
279+
with ModuleMutex(mod.__name__.split(".")[-1]):
280+
__import__(sub_mod_name)
262281
except ImportError:
263282
info = typelibCLSID, lcid, major, minor
264283
# Force the generation. If this typelibrary has explicitly been added,
@@ -730,7 +749,8 @@ def GetGeneratedInfos():
730749
def _GetModule(fname):
731750
"""Given the name of a module in the gen_py directory, import and return it."""
732751
mod_name = "win32com.gen_py.%s" % fname
733-
mod = __import__(mod_name)
752+
with ModuleMutex(fname):
753+
__import__(mod_name)
734754
return sys.modules[mod_name]
735755

736756

com/win32com/client/genpy.py

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
from itertools import chain
2020

2121
import pythoncom
22-
import win32com
2322

24-
from . import build
23+
from . import build, gencache
2524

2625
makepy_version = "0.5.01" # Written to generated file.
2726

@@ -1017,35 +1016,15 @@ def open_writer(self, filename, encoding="utf-8"):
10171016

10181017
def finish_writer(self, filename, f, worked):
10191018
f.close()
1020-
try:
1021-
os.unlink(filename)
1022-
except OSError:
1023-
pass
10241019
temp_filename = self.get_temp_filename(filename)
10251020
if worked:
1021+
os.replace(temp_filename, filename)
1022+
else:
10261023
try:
1027-
os.rename(temp_filename, filename)
1024+
os.unlink(filename)
1025+
os.unlink(temp_filename)
10281026
except OSError:
1029-
# If we are really unlucky, another process may have written the
1030-
# file in between our calls to os.unlink and os.rename. So try
1031-
# again, but only once.
1032-
# There are still some race conditions, but they seem difficult to
1033-
# fix, and they probably occur much less frequently:
1034-
# * The os.rename failure could occur more than once if more than
1035-
# two processes are involved.
1036-
# * In between os.unlink and os.rename, another process could try
1037-
# to import the module, having seen that it already exists.
1038-
# * If another process starts a COM server while we are still
1039-
# generating __init__.py, that process sees that the folder
1040-
# already exists and assumes that __init__.py is already there
1041-
# as well.
1042-
try:
1043-
os.unlink(filename)
1044-
except OSError:
1045-
pass
1046-
os.rename(temp_filename, filename)
1047-
else:
1048-
os.unlink(temp_filename)
1027+
pass
10491028

10501029
def get_temp_filename(self, filename):
10511030
return "%s.%d.temp" % (filename, os.getpid())
@@ -1327,7 +1306,8 @@ def generate_child(self, child, dir):
13271306
self.progress.Tick()
13281307
worked = True
13291308
finally:
1330-
self.finish_writer(out_name, self.file, worked)
1309+
with gencache.ModuleMutex(self.base_mod_name.split(".")[-1]):
1310+
self.finish_writer(out_name, self.file, worked)
13311311
self.file = None
13321312
finally:
13331313
self.progress.Finished()

com/win32com/client/makepy.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,11 @@ def GenerateFromTypeLibSpec(
293293
for typelib, info in typelibs:
294294
gen = genpy.Generator(typelib, info.dll, progress, bBuildHidden=bBuildHidden)
295295

296+
this_name = gencache.GetGeneratedFileName(
297+
info.clsid, info.lcid, info.major, info.minor
298+
)
299+
296300
if file is None:
297-
this_name = gencache.GetGeneratedFileName(
298-
info.clsid, info.lcid, info.major, info.minor
299-
)
300301
full_name = os.path.join(gencache.GetGeneratePath(), this_name)
301302
if bForDemand:
302303
try:
@@ -327,7 +328,8 @@ def GenerateFromTypeLibSpec(
327328
worked = True
328329
finally:
329330
if file is None:
330-
gen.finish_writer(outputName, fileUse, worked)
331+
with gencache.ModuleMutex(this_name):
332+
gen.finish_writer(outputName, fileUse, worked)
331333
importlib.invalidate_caches()
332334
if bToGenDir:
333335
progress.SetDescription("Importing module")
@@ -372,7 +374,8 @@ def GenerateChildFromTypeLibSpec(
372374
gen.generate_child(child, dir_path_name)
373375
progress.SetDescription("Importing module")
374376
importlib.invalidate_caches()
375-
__import__("win32com.gen_py." + dir_name + "." + child)
377+
with gencache.ModuleMutex(dir_name):
378+
__import__("win32com.gen_py." + dir_name + "." + child)
376379
progress.Close()
377380

378381

0 commit comments

Comments
 (0)