Skip to content

Commit

Permalink
Merge pull request #1500 from girder/vips-new-from-file-lock
Browse files Browse the repository at this point in the history
Guard a race condition in vips new_from_file
  • Loading branch information
manthey authored Apr 10, 2024
2 parents 37521b0 + 0929d7e commit 74839f1
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
### Changes
- Improve algorithm sweep example options ([#1498](../../pull/1498))

### Bug Fixes
- Guard a race condition in vips new_from_file ([#1500](../../pull/1500))

## 1.28.0

### Features
Expand Down
6 changes: 6 additions & 0 deletions large_image/tilesource/utilities.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import io
import math
import os
import threading
import types
import xml.etree.ElementTree
from collections import defaultdict
Expand All @@ -27,6 +28,11 @@
# Turn off decompression warning check
PIL.Image.MAX_IMAGE_PIXELS = None

# This is used by any submodule that uses vips to avoid a race condition in
# new_from_file. Since vips is technically optional and the various modules
# might pull it in independently, it is located here to make is shareable.
_newFromFileLock = threading.RLock()

# Extend colors so G and GREEN map to expected values. CSS green is #0080ff,
# which is unfortunate.
colormap = {
Expand Down
11 changes: 7 additions & 4 deletions sources/vips/large_image_source_vips/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
dtypeToGValue)
from large_image.exceptions import TileSourceError, TileSourceFileNotFoundError
from large_image.tilesource import FileTileSource
from large_image.tilesource.utilities import _imageToNumpy
from large_image.tilesource.utilities import _imageToNumpy, _newFromFileLock

logging.getLogger('pyvips').setLevel(logging.ERROR)

Expand Down Expand Up @@ -72,7 +72,8 @@ def __init__(self, path, **kwargs):

config._ignoreSourceNames('vips', self._largeImagePath)
try:
self._image = pyvips.Image.new_from_file(self._largeImagePath)
with _newFromFileLock:
self._image = pyvips.Image.new_from_file(self._largeImagePath)
except pyvips.error.Error:
if not os.path.isfile(self._largeImagePath):
raise TileSourceFileNotFoundError(self._largeImagePath) from None
Expand All @@ -87,7 +88,8 @@ def __init__(self, path, **kwargs):
self._frames = [0]
for page in range(1, pages):
subInputPath = self._largeImagePath + '[page=%d]' % page
subImage = pyvips.Image.new_from_file(subInputPath)
with _newFromFileLock:
subImage = pyvips.Image.new_from_file(subInputPath)
if subImage.width == self.sizeX and subImage.height == self.sizeY:
self._frames.append(page)
continue
Expand Down Expand Up @@ -187,7 +189,8 @@ def _getFrameImage(self, frame=0):
with self._frameLock:
if frame not in self._recentFrames:
subpath = self._largeImagePath + '[page=%d]' % self._frames[frame]
img = pyvips.Image.new_from_file(subpath)
with _newFromFileLock:
img = pyvips.Image.new_from_file(subpath)
self._recentFrames[frame] = img
else:
img = self._recentFrames[frame]
Expand Down
21 changes: 14 additions & 7 deletions utilities/converter/large_image_converter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import tifftools

import large_image
from large_image.tilesource.utilities import _gdalParameters, _vipsCast, _vipsParameters
from large_image.tilesource.utilities import (_gdalParameters,
_newFromFileLock, _vipsCast,
_vipsParameters)

from . import format_aperio

Expand Down Expand Up @@ -77,7 +79,7 @@ def _data_from_large_image(path, outputPath, **kwargs):
_import_pyvips()
if not path.startswith('large_image://test'):
try:
ts = large_image.open(path)
ts = large_image.open(path, noCache=True)
except Exception:
return
else:
Expand Down Expand Up @@ -164,7 +166,8 @@ def _generate_multiframe_tiff(inputPath, outputPath, tempPath, lidata, **kwargs)
"""
_import_pyvips()

image = pyvips.Image.new_from_file(inputPath)
with _newFromFileLock:
image = pyvips.Image.new_from_file(inputPath)
width = image.width
height = image.height
pages = 1
Expand All @@ -180,7 +183,8 @@ def _generate_multiframe_tiff(inputPath, outputPath, tempPath, lidata, **kwargs)
# Process each image separately to pyramidize it
for page in range(pages):
subInputPath = inputPath + '[page=%d]' % page
subImage = pyvips.Image.new_from_file(subInputPath)
with _newFromFileLock:
subImage = pyvips.Image.new_from_file(subInputPath)
imageSizes.append((subImage.width, subImage.height, subInputPath, page))
if subImage.width != width or subImage.height != height:
if subImage.width * subImage.height <= width * height:
Expand Down Expand Up @@ -274,7 +278,8 @@ def _convert_via_vips(inputPathOrBuffer, outputPath, tempPath, forTiled=True,
image = pyvips.Image.new_from_buffer(inputPathOrBuffer, '')
else:
source = inputPathOrBuffer
image = pyvips.Image.new_from_file(inputPathOrBuffer)
with _newFromFileLock:
image = pyvips.Image.new_from_file(inputPathOrBuffer)
logger.info('Input: %s, Output: %s, Options: %r%s',
source, outputPath, convertParams, status)
image = image.autorot()
Expand Down Expand Up @@ -736,7 +741,8 @@ def _is_multiframe(path):
"""
_import_pyvips()
try:
image = pyvips.Image.new_from_file(path)
with _newFromFileLock:
image = pyvips.Image.new_from_file(path)
except Exception:
try:
open(path, 'rb').read(1)
Expand Down Expand Up @@ -971,7 +977,8 @@ def is_vips(path):
"""
_import_pyvips()
try:
image = pyvips.Image.new_from_file(path)
with _newFromFileLock:
image = pyvips.Image.new_from_file(path)
# image(0, 0) will throw if vips can't decode the image
if not image.width or not image.height or image(0, 0) is None:
return False
Expand Down

0 comments on commit 74839f1

Please sign in to comment.