Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4fd5a06

Browse files
committed
SF bug #509805 tempfile.gettempdir not threadsafe
This is an ancient race when multiple threads call gettempdir() (or anything relying on it) for the first time. Fixed x-platform via the Big Hammer of rearranging the code to serialize the first calls. Subsequent calls are as fast as before. Note that the Python test suite can't provoke this bug: it requires setting up multiple threads making the very first calls into tempfile, but the test suite uses tempfile several times before getting to test_threadedtempfile. Bugfix candidate.
1 parent fea1553 commit 4fd5a06

1 file changed

Lines changed: 28 additions & 2 deletions

File tree

Lib/tempfile.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,30 @@
1313
template = None
1414

1515
def gettempdir():
16+
"""Function to calculate the directory to use."""
17+
global tempdir
18+
if tempdir is not None:
19+
return tempdir
20+
21+
# _gettempdir_inner deduces whether a candidate temp dir is usable by
22+
# trying to create a file in it, and write to it. If that succeeds,
23+
# great, it closes the file and unlinks it. There's a race, though:
24+
# the *name* of the test file it tries is the same across all threads
25+
# under most OSes (Linux is an exception), and letting multiple threads
26+
# all try to open, write to, close, and unlink a single file can cause
27+
# a variety of bogus errors (e.g., you cannot unlink a file under
28+
# Windows if anyone has it open, and two threads cannot create the
29+
# same file in O_EXCL mode under Unix). The simplest cure is to serialize
30+
# calls to _gettempdir_inner. This isn't a real expense, because the
31+
# first thread to succeed sets the global tempdir, and all subsequent
32+
# calls to gettempdir() reuse that without trying _gettempdir_inner.
33+
_tempdir_lock.acquire()
34+
try:
35+
return _gettempdir_inner()
36+
finally:
37+
_tempdir_lock.release()
38+
39+
def _gettempdir_inner():
1640
"""Function to calculate the directory to use."""
1741
global tempdir
1842
if tempdir is not None:
@@ -179,8 +203,8 @@ def TemporaryFile(mode='w+b', bufsize=-1, suffix=""):
179203
# multiple threads will never see the same integer). The integer will
180204
# usually be a Python int, but if _counter.get_next() is called often
181205
# enough, it will become a Python long.
182-
# Note that the only name that survives this next block of code
183-
# is "_counter".
206+
# Note that the only names that survive this next block of code
207+
# are "_counter" and "_tempdir_lock".
184208

185209
class _ThreadSafeCounter:
186210
def __init__(self, mutex, initialvalue=0):
@@ -209,10 +233,12 @@ def acquire(self):
209233
release = acquire
210234

211235
_counter = _ThreadSafeCounter(_DummyMutex())
236+
_tempdir_lock = _DummyMutes()
212237
del _DummyMutex
213238

214239
else:
215240
_counter = _ThreadSafeCounter(thread.allocate_lock())
241+
_tempdir_lock = thread.allocate_lock()
216242
del thread
217243

218244
del _ThreadSafeCounter

0 commit comments

Comments
 (0)