mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-28 07:10:15 +08:00
fix: close image file handle after loading in LoadImage nodes
Fixes #3477 The LoadImage and LoadImageMask nodes opened image files but did not close them after processing. This kept file handles open, preventing other processes from accessing the files. Added try/finally blocks to ensure img.close() is called after processing completes.
This commit is contained in:
parent
3a5f239cb6
commit
d14fe06352
6
nodes.py
6
nodes.py
@ -1665,6 +1665,7 @@ class LoadImage:
|
|||||||
|
|
||||||
excluded_formats = ['MPO']
|
excluded_formats = ['MPO']
|
||||||
|
|
||||||
|
try:
|
||||||
for i in ImageSequence.Iterator(img):
|
for i in ImageSequence.Iterator(img):
|
||||||
i = node_helpers.pillow(ImageOps.exif_transpose, i)
|
i = node_helpers.pillow(ImageOps.exif_transpose, i)
|
||||||
|
|
||||||
@ -1698,6 +1699,8 @@ class LoadImage:
|
|||||||
else:
|
else:
|
||||||
output_image = output_images[0]
|
output_image = output_images[0]
|
||||||
output_mask = output_masks[0]
|
output_mask = output_masks[0]
|
||||||
|
finally:
|
||||||
|
img.close()
|
||||||
|
|
||||||
return (output_image, output_mask)
|
return (output_image, output_mask)
|
||||||
|
|
||||||
@ -1734,6 +1737,7 @@ class LoadImageMask:
|
|||||||
def load_image(self, image, channel):
|
def load_image(self, image, channel):
|
||||||
image_path = folder_paths.get_annotated_filepath(image)
|
image_path = folder_paths.get_annotated_filepath(image)
|
||||||
i = node_helpers.pillow(Image.open, image_path)
|
i = node_helpers.pillow(Image.open, image_path)
|
||||||
|
try:
|
||||||
i = node_helpers.pillow(ImageOps.exif_transpose, i)
|
i = node_helpers.pillow(ImageOps.exif_transpose, i)
|
||||||
if i.getbands() != ("R", "G", "B", "A"):
|
if i.getbands() != ("R", "G", "B", "A"):
|
||||||
if i.mode == 'I':
|
if i.mode == 'I':
|
||||||
@ -1748,6 +1752,8 @@ class LoadImageMask:
|
|||||||
mask = 1. - mask
|
mask = 1. - mask
|
||||||
else:
|
else:
|
||||||
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
|
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
|
||||||
|
finally:
|
||||||
|
i.close()
|
||||||
return (mask.unsqueeze(0),)
|
return (mask.unsqueeze(0),)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
91
tests-unit/test_load_image_file_handle.py
Normal file
91
tests-unit/test_load_image_file_handle.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
"""Tests for LoadImage and LoadImageMask file handle management.
|
||||||
|
|
||||||
|
Relates to issue #3477: close image file after loading
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
class TestImageFileHandleRelease:
|
||||||
|
"""Test that image files are properly closed after loading."""
|
||||||
|
|
||||||
|
def test_file_handle_released_after_close(self):
|
||||||
|
"""Verify file handle is released after calling close()."""
|
||||||
|
# Create a temporary test image
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
|
||||||
|
temp_path = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a test image
|
||||||
|
img = Image.new('RGB', (64, 64), color='red')
|
||||||
|
img.save(temp_path)
|
||||||
|
|
||||||
|
# Open and close the image
|
||||||
|
loaded_img = Image.open(temp_path)
|
||||||
|
loaded_img.load() # Force load the image data
|
||||||
|
loaded_img.close()
|
||||||
|
|
||||||
|
# Try to delete the file - should succeed if handle is released
|
||||||
|
os.unlink(temp_path)
|
||||||
|
assert not os.path.exists(temp_path), "File should be deleted"
|
||||||
|
except Exception:
|
||||||
|
# Cleanup in case of failure
|
||||||
|
if os.path.exists(temp_path):
|
||||||
|
os.unlink(temp_path)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def test_try_finally_pattern_releases_handle(self):
|
||||||
|
"""Verify try/finally pattern properly releases file handle."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
|
||||||
|
temp_path = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a test image
|
||||||
|
img = Image.new('RGBA', (64, 64), color='blue')
|
||||||
|
img.save(temp_path)
|
||||||
|
|
||||||
|
# Simulate the pattern used in LoadImage
|
||||||
|
loaded_img = Image.open(temp_path)
|
||||||
|
try:
|
||||||
|
# Process the image
|
||||||
|
_ = loaded_img.convert("RGB")
|
||||||
|
finally:
|
||||||
|
loaded_img.close()
|
||||||
|
|
||||||
|
# Verify file can be accessed/deleted
|
||||||
|
os.unlink(temp_path)
|
||||||
|
assert not os.path.exists(temp_path)
|
||||||
|
except Exception:
|
||||||
|
if os.path.exists(temp_path):
|
||||||
|
os.unlink(temp_path)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def test_image_data_preserved_after_close(self):
|
||||||
|
"""Verify image data is preserved after closing the file."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
|
||||||
|
temp_path = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a test image with specific size
|
||||||
|
original_size = (128, 64)
|
||||||
|
img = Image.new('RGB', original_size, color='green')
|
||||||
|
img.save(temp_path)
|
||||||
|
|
||||||
|
# Load and process
|
||||||
|
loaded_img = Image.open(temp_path)
|
||||||
|
try:
|
||||||
|
loaded_img.load()
|
||||||
|
size = loaded_img.size
|
||||||
|
mode = loaded_img.mode
|
||||||
|
finally:
|
||||||
|
loaded_img.close()
|
||||||
|
|
||||||
|
# Data should still be valid after close
|
||||||
|
assert size == original_size
|
||||||
|
assert mode == 'RGB'
|
||||||
|
finally:
|
||||||
|
if os.path.exists(temp_path):
|
||||||
|
os.unlink(temp_path)
|
||||||
Loading…
Reference in New Issue
Block a user