mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-09 16:10:48 +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)
|
return Image.open(outfile)
|
||||||
raise IOError()
|
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
|
# 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 PIL import Image, ImageFile, PngImagePlugin
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
@ -7,9 +7,6 @@ import sys
|
||||||
|
|
||||||
codecs = dir(Image.core)
|
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
|
# sample png stream
|
||||||
|
|
||||||
|
@ -539,26 +536,14 @@ class TestFilePng(PillowTestCase):
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or MacOS")
|
@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):
|
def setUp(self):
|
||||||
if "zip_encoder" not in codecs or "zip_decoder" not in codecs:
|
if "zip_encoder" not in codecs or "zip_decoder" not in codecs:
|
||||||
self.skipTest("zip/deflate support not available")
|
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):
|
def test_leak_load(self):
|
||||||
with open('Tests/images/hopper.png', 'rb') as f:
|
with open('Tests/images/hopper.png', 'rb') as f:
|
||||||
DATA = BytesIO(f.read(16 * 1024))
|
DATA = BytesIO(f.read(16 * 1024))
|
||||||
|
@ -566,13 +551,13 @@ class TestTruncatedPngPLeaks(PillowTestCase):
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
with Image.open(DATA) as im:
|
with Image.open(DATA) as im:
|
||||||
im.load()
|
im.load()
|
||||||
start_mem = self._get_mem_usage()
|
|
||||||
|
def core():
|
||||||
|
with Image.open(DATA) as im:
|
||||||
|
im.load()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for _ in range(ITERATIONS):
|
self._test_leak(core)
|
||||||
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')
|
|
||||||
finally:
|
finally:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
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){
|
void _font_text_asBytes(PyObject* encoded_string, unsigned char** text){
|
||||||
|
/* Allocates *text, returns a 'new reference'. Caller is required to free */
|
||||||
|
|
||||||
PyObject* bytes = NULL;
|
PyObject* bytes = NULL;
|
||||||
|
Py_ssize_t len = 0;
|
||||||
|
char *buffer;
|
||||||
|
|
||||||
*text = NULL;
|
*text = NULL;
|
||||||
|
|
||||||
if (PyUnicode_CheckExact(encoded_string)){
|
if (PyUnicode_CheckExact(encoded_string)){
|
||||||
bytes = PyUnicode_AsLatin1String(encoded_string);
|
bytes = PyUnicode_AsLatin1String(encoded_string);
|
||||||
|
PyBytes_AsStringAndSize(bytes, &buffer, &len);
|
||||||
} else if (PyBytes_Check(encoded_string)) {
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if PY_VERSION_HEX < 0x03000000
|
#if PY_VERSION_HEX < 0x03000000
|
||||||
/* likely case here is py2.x with an ordinary string.
|
/* likely case here is py2.x with an ordinary string.
|
||||||
but this isn't defined in Py3.x */
|
but this isn't defined in Py3.x */
|
||||||
if (PyString_Check(encoded_string)) {
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2248,6 +2267,7 @@ _font_getmask(ImagingFontObject* self, PyObject* args)
|
||||||
|
|
||||||
im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize);
|
im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize);
|
||||||
if (!im) {
|
if (!im) {
|
||||||
|
free(text);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2273,9 +2293,11 @@ _font_getmask(ImagingFontObject* self, PyObject* args)
|
||||||
x = x + glyph->dx;
|
x = x + glyph->dx;
|
||||||
b = b + glyph->dy;
|
b = b + glyph->dy;
|
||||||
}
|
}
|
||||||
|
free(text);
|
||||||
return PyImagingNew(im);
|
return PyImagingNew(im);
|
||||||
|
|
||||||
failed:
|
failed:
|
||||||
|
free(text);
|
||||||
ImagingDelete(im);
|
ImagingDelete(im);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -2285,6 +2307,7 @@ _font_getsize(ImagingFontObject* self, PyObject* args)
|
||||||
{
|
{
|
||||||
unsigned char* text;
|
unsigned char* text;
|
||||||
PyObject* encoded_string;
|
PyObject* encoded_string;
|
||||||
|
PyObject* val;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O:getsize", &encoded_string))
|
if (!PyArg_ParseTuple(args, "O:getsize", &encoded_string))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -2294,7 +2317,9 @@ _font_getsize(ImagingFontObject* self, PyObject* args)
|
||||||
return NULL;
|
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[] = {
|
static struct PyMethodDef _font_methods[] = {
|
||||||
|
|
|
@ -497,6 +497,11 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
FT_Done_Glyph(glyph);
|
FT_Done_Glyph(glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (glyph_info) {
|
||||||
|
PyMem_Free(glyph_info);
|
||||||
|
glyph_info = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (face) {
|
if (face) {
|
||||||
|
|
||||||
/* left bearing */
|
/* left bearing */
|
||||||
|
|
Loading…
Reference in New Issue
Block a user