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

Skip to content

Commit e0283a9

Browse files
committed
Switch to per-file locking.
Replace the Locked contextmanager (which locks a directory, preventing other (cooperative) processes to access it) by a private _lock_path contextmanager, which locks a single file (or directory). - The finer grained lock avoids locking out the entire tex cache when handling usetex, which is useful when running multiple processes at once. - Python3 implements the `"x"` ("exclusive") mode to open, which we can use instead of relying on `makedirs` to achieve a race-free operation on the filesystem. - The previous implementation allowed multiple threads of a single process to acquire the same lock, but (for the use cases here, e.g. running a tex subprocess) this is actually undesirable. Removing this behavior also simplifies the implementation. - As far as I can tell, the previous implementation was actually racy: in retries = 50 sleeptime = 0.1 while retries: files = glob.glob(self.pattern) if files and not files[0].endswith(self.end): time.sleep(sleeptime) retries -= 1 else: break else: err_str = _lockstr.format(self.pattern) raise self.TimeoutError(err_str) # <----- HERE if not files: try: os.makedirs(self.lock_path) except OSError: pass else: # PID lock already here --- someone else will remove it. self.remove = False multiple processes can reach "HERE" at the same time and each successfully create their own lock.
1 parent e54939a commit e0283a9

File tree

4 files changed

+46
-13
lines changed

4 files changed

+46
-13
lines changed

doc/api/next_api_changes/2018-02-15-AL-deprecations.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The following functions and classes are deprecated:
1010

1111
- ``cbook.GetRealpathAndStat`` (which is only a helper for
1212
``get_realpath_and_stat``),
13+
- ``cbook.Locked``,
1314
- ``cbook.is_numlike`` (use ``isinstance(..., numbers.Number)`` instead),
1415
- ``mathtext.unichr_safe`` (use ``chr`` instead),
1516
- ``texmanager.dvipng_hack_alpha``,

lib/matplotlib/cbook/__init__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import numbers
2525
import operator
2626
import os
27+
from pathlib import Path
2728
import re
2829
import sys
2930
import time
@@ -2486,6 +2487,7 @@ def get_label(y, default_name):
24862487
"""
24872488

24882489

2490+
@deprecated("3.0")
24892491
class Locked(object):
24902492
"""
24912493
Context manager to handle locks.
@@ -2541,6 +2543,40 @@ def __exit__(self, exc_type, exc_value, traceback):
25412543
pass
25422544

25432545

2546+
@contextlib.contextmanager
2547+
def _lock_path(path):
2548+
"""
2549+
Context manager for locking a path.
2550+
2551+
Usage::
2552+
2553+
with _lock_path(path):
2554+
...
2555+
2556+
Another thread or process that attempts to lock the same path will wait
2557+
until this context manager is exited.
2558+
2559+
The lock is implemented by creating a temporary file in the parent
2560+
directory, so that directory must exist and be writable.
2561+
"""
2562+
path = Path(path)
2563+
lock_path = path.with_name(path.name + ".matplotlib-lock")
2564+
retries = 50
2565+
sleeptime = 0.1
2566+
for _ in range(retries):
2567+
try:
2568+
with lock_path.open("xb"):
2569+
break
2570+
except FileExistsError:
2571+
time.sleep(sleeptime)
2572+
else:
2573+
raise TimeoutError(_lockstr.format(lock_path))
2574+
try:
2575+
yield
2576+
finally:
2577+
lock_path.unlink()
2578+
2579+
25442580
def _topmost_artist(
25452581
artists,
25462582
_cached_max=functools.partial(max, key=operator.attrgetter("zorder"))):

lib/matplotlib/font_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,7 @@ def _rebuild():
14461446
fontManager = FontManager()
14471447

14481448
if _fmcache:
1449-
with cbook.Locked(cachedir):
1449+
with cbook._lock_path(_fmcache):
14501450
json_dump(fontManager, _fmcache)
14511451
_log.info("generated new fontManager")
14521452

@@ -1459,9 +1459,9 @@ def _rebuild():
14591459
else:
14601460
fontManager.default_size = None
14611461
_log.debug("Using fontManager instance from %s", _fmcache)
1462-
except cbook.Locked.TimeoutError:
1462+
except TimeoutError:
14631463
raise
1464-
except:
1464+
except Exception:
14651465
_rebuild()
14661466
else:
14671467
_rebuild()

lib/matplotlib/texmanager.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,25 @@
3333
3434
"""
3535

36-
from __future__ import absolute_import, division, print_function
37-
3836
import six
3937

4038
import copy
39+
import distutils.version
4140
import glob
4241
import hashlib
4342
import logging
4443
import os
4544
from pathlib import Path
45+
import re
4646
import shutil
4747
import subprocess
4848
import sys
4949
import warnings
5050

51-
import distutils.version
5251
import numpy as np
52+
5353
import matplotlib as mpl
54-
from matplotlib import rcParams
55-
from matplotlib._png import read_png
56-
from matplotlib.cbook import Locked
57-
import matplotlib.dviread as dviread
58-
import re
54+
from matplotlib import _png, cbook, dviread, rcParams
5955

6056
_log = logging.getLogger(__name__)
6157

@@ -340,7 +336,7 @@ def make_dvi(self, tex, fontsize):
340336
dvifile = '%s.dvi' % basefile
341337
if not os.path.exists(dvifile):
342338
texfile = self.make_tex(tex, fontsize)
343-
with Locked(self.texcache):
339+
with cbook._lock_path(texfile):
344340
self._run_checked_subprocess(
345341
["latex", "-interaction=nonstopmode", "--halt-on-error",
346342
texfile], tex)
@@ -436,7 +432,7 @@ def get_grey(self, tex, fontsize=None, dpi=None):
436432
alpha = self.grey_arrayd.get(key)
437433
if alpha is None:
438434
pngfile = self.make_png(tex, fontsize, dpi)
439-
X = read_png(os.path.join(self.texcache, pngfile))
435+
X = _png.read_png(os.path.join(self.texcache, pngfile))
440436
self.grey_arrayd[key] = alpha = X[:, :, -1]
441437
return alpha
442438

0 commit comments

Comments
 (0)