Skip to content

Commit 13e9425

Browse files
amitabnmariz
authored andcommitted
BUG22880163: Fix memory leak on using Named Tuple Cursors
Currently separate namedtuple class is created for each single row retrieved from DB. That is each single row has **its own** namedtuple class. Recreating of separate namedtuple classes not only reduces performance, but also results in higher memory usage and memory leaks. This patch fixes the issue for both Pure python and C Extension.
1 parent d6eaa8b commit 13e9425

File tree

3 files changed

+19
-5
lines changed

3 files changed

+19
-5
lines changed

lib/mysql/connector/abstracts.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
from abc import ABCMeta, abstractmethod, abstractproperty
2727
import re
2828
import time
29+
import weakref
2930

3031
from .catch23 import make_abc, BYTE_TYPES
3132
from .conversion import MySQLConverterBase
3233
from .constants import ClientFlag, CharacterSet, DEFAULT_CONFIGURATION
3334
from .optionfiles import MySQLOptionsParser
3435
from . import errors
3536

37+
NAMED_TUPLE_CACHE = weakref.WeakValueDictionary()
38+
3639
@make_abc(ABCMeta)
3740
class MySQLConnectionAbstract(object):
3841

lib/mysql/connector/cursor.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import weakref
3030

3131
from . import errors
32-
from .abstracts import MySQLCursorAbstract
32+
from .abstracts import MySQLCursorAbstract, NAMED_TUPLE_CACHE
3333
from .catch23 import PY2
3434

3535
SQL_COMMENT = r"\/\*.*?\*\/"
@@ -1293,9 +1293,14 @@ def _row_to_python(self, rowdata, desc=None):
12931293

12941294
if row:
12951295
# pylint: disable=W0201
1296-
self.named_tuple = namedtuple('Row', self.column_names)
1296+
columns = tuple(self.column_names)
1297+
try:
1298+
named_tuple = NAMED_TUPLE_CACHE[columns]
1299+
except KeyError:
1300+
named_tuple = namedtuple('Row', columns)
1301+
NAMED_TUPLE_CACHE[columns] = named_tuple
12971302
# pylint: enable=W0201
1298-
return self.named_tuple(*row)
1303+
return named_tuple(*row)
12991304

13001305
def fetchone(self):
13011306
"""Returns next row of a query result set

lib/mysql/connector/cursor_cext.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030

3131
from _mysql_connector import MySQLInterfaceError # pylint: disable=F0401,E0611
3232

33-
from .abstracts import MySQLConnectionAbstract, MySQLCursorAbstract
33+
from .abstracts import (MySQLConnectionAbstract, MySQLCursorAbstract,
34+
NAMED_TUPLE_CACHE)
3435
from .catch23 import PY2, isunicode
3536
from . import errors
3637
from .errorcode import CR_NO_RESULT_SET
@@ -757,7 +758,12 @@ def _handle_resultset(self):
757758
"""Handle a result set"""
758759
super(CMySQLCursorNamedTuple, self)._handle_resultset()
759760
# pylint: disable=W0201
760-
self.named_tuple = namedtuple('Row', self.column_names)
761+
columns = tuple(self.column_names)
762+
try:
763+
self.named_tuple = NAMED_TUPLE_CACHE[columns]
764+
except KeyError:
765+
self.named_tuple = namedtuple('Row', columns)
766+
NAMED_TUPLE_CACHE[columns] = self.named_tuple
761767
# pylint: enable=W0201
762768

763769
def fetchone(self):

0 commit comments

Comments
 (0)