mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-03 13:14:27 +03:00
Merge pull request #2634 from wiredfool/issue_2629
Fix for memory leaks in font handling
This commit is contained in:
commit
e71757aa6f
|
@ -170,6 +170,41 @@ class PillowTestCase(unittest.TestCase):
|
|||
return Image.open(outfile)
|
||||
raise IOError()
|
||||
|
||||
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
|
||||
class PillowLeakTestCase(PillowTestCase):
|
||||
# requires unix/osx
|
||||
iterations = 100 # count
|
||||
mem_limit = 512 # k
|
||||
|
||||
def _get_mem_usage(self):
|
||||
"""
|
||||
Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
|
||||
between OSX and Linux rss reporting
|
||||
|
||||
:returns; memory usage in kilobytes
|
||||
"""
|
||||
|
||||
from resource import getpagesize, getrusage, RUSAGE_SELF
|
||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||
if sys.platform == 'darwin':
|
||||
# man 2 getrusage:
|
||||
# ru_maxrss the maximum resident set size utilized (in bytes).
|
||||
return mem / 1024 # Kb
|
||||
else:
|
||||
# linux
|
||||
# man 2 getrusage
|
||||
# ru_maxrss (since Linux 2.6.32)
|
||||
# This is the maximum resident set size used (in kilobytes).
|
||||
return mem # Kb
|
||||
|
||||
def _test_leak(self, core):
|
||||
start_mem = self._get_mem_usage()
|
||||
for cycle in range(self.iterations):
|
||||
core()
|
||||
mem = (self._get_mem_usage() - start_mem)
|
||||
self.assertLess(mem, self.mem_limit,
|
||||
msg='memory usage limit exceeded in iteration %d' % cycle)
|
||||
|
||||
|
||||
# helpers
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from helper import unittest, PillowTestCase, hopper
|
||||
from helper import unittest, PillowTestCase, PillowLeakTestCase, hopper
|
||||
from PIL import Image, ImageFile, PngImagePlugin
|
||||
|
||||
from io import BytesIO
|
||||
|
@ -7,9 +7,6 @@ import sys
|
|||
|
||||
codecs = dir(Image.core)
|
||||
|
||||
# For Truncated phng memory leak
|
||||
MEM_LIMIT = 2 # max increase in MB
|
||||
ITERATIONS = 100 # Leak is 56k/iteration, this will leak 5.6megs
|
||||
|
||||
# sample png stream
|
||||
|
||||
|
@ -539,26 +536,14 @@ class TestFilePng(PillowTestCase):
|
|||
|
||||
|
||||
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
|
||||
class TestTruncatedPngPLeaks(PillowTestCase):
|
||||
class TestTruncatedPngPLeaks(PillowLeakTestCase):
|
||||
mem_limit = 2*1024 # max increase in K
|
||||
iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs
|
||||
|
||||
def setUp(self):
|
||||
if "zip_encoder" not in codecs or "zip_decoder" not in codecs:
|
||||
self.skipTest("zip/deflate support not available")
|
||||
|
||||
def _get_mem_usage(self):
|
||||
from resource import getpagesize, getrusage, RUSAGE_SELF
|
||||
mem = getrusage(RUSAGE_SELF).ru_maxrss
|
||||
if sys.platform == 'darwin':
|
||||
# man 2 getrusage:
|
||||
# ru_maxrss the maximum resident set size utilized (in bytes).
|
||||
return mem / 1024 / 1024 # megs
|
||||
else:
|
||||
# linux
|
||||
# man 2 getrusage
|
||||
# ru_maxrss (since Linux 2.6.32)
|
||||
# This is the maximum resident set size used (in kilobytes).
|
||||
return mem / 1024 # megs
|
||||
|
||||
def test_leak_load(self):
|
||||
with open('Tests/images/hopper.png', 'rb') as f:
|
||||
DATA = BytesIO(f.read(16 * 1024))
|
||||
|
@ -566,13 +551,13 @@ class TestTruncatedPngPLeaks(PillowTestCase):
|
|||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
with Image.open(DATA) as im:
|
||||
im.load()
|
||||
start_mem = self._get_mem_usage()
|
||||
|
||||
def core():
|
||||
with Image.open(DATA) as im:
|
||||
im.load()
|
||||
|
||||
try:
|
||||
for _ in range(ITERATIONS):
|
||||
with Image.open(DATA) as im:
|
||||
im.load()
|
||||
mem = (self._get_mem_usage() - start_mem)
|
||||
self.assertLess(mem, MEM_LIMIT, msg='memory usage limit exceeded')
|
||||
self._test_leak(core)
|
||||
finally:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||
|
||||
|
|
34
Tests/test_font_leaks.py
Normal file
34
Tests/test_font_leaks.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from __future__ import division
|
||||
from helper import unittest, PillowLeakTestCase
|
||||
import sys
|
||||
from PIL import Image, features, ImageDraw, ImageFont
|
||||
|
||||
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
|
||||
class TestTTypeFontLeak(PillowLeakTestCase):
|
||||
# fails at iteration 3 in master
|
||||
iterations = 10
|
||||
mem_limit = 4096 #k
|
||||
|
||||
def _test_font(self, font):
|
||||
im = Image.new('RGB', (255,255), 'white')
|
||||
draw = ImageDraw.ImageDraw(im)
|
||||
self._test_leak(lambda: draw.text((0, 0), "some text "*1024, #~10k
|
||||
font=font, fill="black"))
|
||||
|
||||
@unittest.skipIf(not features.check('freetype2'), "Test requires freetype2")
|
||||
def test_leak(self):
|
||||
ttype = ImageFont.truetype('Tests/fonts/FreeMono.ttf', 20)
|
||||
self._test_font(ttype)
|
||||
|
||||
class TestDefaultFontLeak(TestTTypeFontLeak):
|
||||
# fails at iteration 37 in master
|
||||
iterations = 100
|
||||
mem_limit = 1024 #k
|
||||
|
||||
def test_leak(self):
|
||||
default_font = ImageFont.load_default()
|
||||
self._test_font(default_font)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
35
_imaging.c
35
_imaging.c
|
@ -2198,26 +2198,45 @@ textwidth(ImagingFontObject* self, const unsigned char* text)
|
|||
}
|
||||
|
||||
void _font_text_asBytes(PyObject* encoded_string, unsigned char** text){
|
||||
/* Allocates *text, returns a 'new reference'. Caller is required to free */
|
||||
|
||||
PyObject* bytes = NULL;
|
||||
Py_ssize_t len = 0;
|
||||
char *buffer;
|
||||
|
||||
*text = NULL;
|
||||
|
||||
if (PyUnicode_CheckExact(encoded_string)){
|
||||
bytes = PyUnicode_AsLatin1String(encoded_string);
|
||||
PyBytes_AsStringAndSize(bytes, &buffer, &len);
|
||||
} else if (PyBytes_Check(encoded_string)) {
|
||||
bytes = encoded_string;
|
||||
PyBytes_AsStringAndSize(encoded_string, &buffer, &len);
|
||||
}
|
||||
if (bytes) {
|
||||
*text = (unsigned char*)PyBytes_AsString(bytes);
|
||||
|
||||
if (len) {
|
||||
*text = calloc(len,1);
|
||||
if (*text) {
|
||||
memcpy(*text, buffer, len);
|
||||
}
|
||||
if(bytes) {
|
||||
Py_DECREF(bytes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#if PY_VERSION_HEX < 0x03000000
|
||||
/* likely case here is py2.x with an ordinary string.
|
||||
but this isn't defined in Py3.x */
|
||||
if (PyString_Check(encoded_string)) {
|
||||
*text = (unsigned char *)PyString_AsString(encoded_string);
|
||||
PyString_AsStringAndSize(encoded_string, &buffer, &len);
|
||||
*text = calloc(len,1);
|
||||
if (*text) {
|
||||
memcpy(*text, buffer, len);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -2248,6 +2267,7 @@ _font_getmask(ImagingFontObject* self, PyObject* args)
|
|||
|
||||
im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize);
|
||||
if (!im) {
|
||||
free(text);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -2273,9 +2293,11 @@ _font_getmask(ImagingFontObject* self, PyObject* args)
|
|||
x = x + glyph->dx;
|
||||
b = b + glyph->dy;
|
||||
}
|
||||
free(text);
|
||||
return PyImagingNew(im);
|
||||
|
||||
failed:
|
||||
free(text);
|
||||
ImagingDelete(im);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -2285,6 +2307,7 @@ _font_getsize(ImagingFontObject* self, PyObject* args)
|
|||
{
|
||||
unsigned char* text;
|
||||
PyObject* encoded_string;
|
||||
PyObject* val;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O:getsize", &encoded_string))
|
||||
return NULL;
|
||||
|
@ -2294,7 +2317,9 @@ _font_getsize(ImagingFontObject* self, PyObject* args)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
return Py_BuildValue("ii", textwidth(self, text), self->ysize);
|
||||
val = Py_BuildValue("ii", textwidth(self, text), self->ysize);
|
||||
free(text);
|
||||
return val;
|
||||
}
|
||||
|
||||
static struct PyMethodDef _font_methods[] = {
|
||||
|
|
|
@ -497,6 +497,11 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
FT_Done_Glyph(glyph);
|
||||
}
|
||||
|
||||
if (glyph_info) {
|
||||
PyMem_Free(glyph_info);
|
||||
glyph_info = NULL;
|
||||
}
|
||||
|
||||
if (face) {
|
||||
|
||||
/* left bearing */
|
||||
|
|
Loading…
Reference in New Issue
Block a user