-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Memory leak in ParserBuffer #579
Comments
It's less elegant but maybe it's an option to not subclass class ParserBuffer:
"""ParserBuffer is NOT a bytearray extension anymore.
ParserBuffer provides helper methods for parsers.
"""
__slots__ = ('_exception', '_writer', '_data')
def __init__(self, *args):
self._data = bytearray(*args)
self._exception = None
self._writer = self._feed_data(self._data)
next(self._writer)
def exception(self):
return self._exception
def set_exception(self, exc):
self._exception = exc
@staticmethod
def _feed_data(data):
while True:
chunk = yield
if chunk:
data.extend(chunk)
def _check_exception(self):
if self._exception:
self._writer = self._feed_data(self._data)
next(self._writer)
raise self._exception
def feed_data(self, data):
self._writer.send(data)
self._check_exception()
def read(self, size):
"""read() reads specified amount of bytes."""
while True:
if len(self._data) >= size:
data = self._data[:size]
del self._data[:size]
return data
self._writer.send((yield))
self._check_exception()
def readsome(self, size=None):
"""reads size of less amount of bytes."""
while True:
length = len(self._data)
if length > 0:
if size is None or length < size:
size = length
data = self._data[:size]
del self._data[:size]
return data
self._writer.send((yield))
self._check_exception()
def readuntil(self, stop, limit=None):
assert isinstance(stop, bytes) and stop, \
'bytes is required: {!r}'.format(stop)
stop_len = len(stop)
while True:
pos = self._data.find(stop)
if pos >= 0:
end = pos + stop_len
size = end
if limit is not None and size > limit:
raise errors.LineLimitExceededParserError(
'Line is too long.', limit)
data = self._data[:size]
del self._data[:size]
return data
else:
if limit is not None and len(self._data) > limit:
raise errors.LineLimitExceededParserError(
'Line is too long.', limit)
self._writer.send((yield))
self._check_exception()
def wait(self, size):
"""wait() waits for specified amount of bytes
then returns data without changing internal buffer."""
while True:
if len(self._data) >= size:
return self._data[:size]
self._writer.send((yield))
self._check_exception()
def waituntil(self, stop, limit=None):
"""waituntil() reads until `stop` bytes sequence."""
assert isinstance(stop, bytes) and stop, \
'bytes is required: {!r}'.format(stop)
stop_len = len(stop)
while True:
pos = self._data.find(stop)
if pos >= 0:
size = pos + stop_len
if limit is not None and size > limit:
raise errors.LineLimitExceededParserError(
'Line is too long. %s' % bytes(self._data), limit)
return self._data[:size]
else:
if limit is not None and len(self._data) > limit:
raise errors.LineLimitExceededParserError(
'Line is too long. %s' % bytes(self._data), limit)
self._writer.send((yield))
self._check_exception()
def skip(self, size):
"""skip() skips specified amount of bytes."""
while len(self._data) < size:
self._writer.send((yield))
self._check_exception()
del self._data[:size]
def skipuntil(self, stop):
"""skipuntil() reads until `stop` bytes sequence."""
assert isinstance(stop, bytes) and stop, \
'bytes is required: {!r}'.format(stop)
stop_len = len(stop)
while True:
stop_line = self._data.find(stop)
if stop_line >= 0:
size = stop_line + stop_len
del self._data[:size]
return
self._writer.send((yield))
self._check_exception() |
Did you run tests? Sent from my iPhone
|
I did, the memory leak does not occur using this code. But maybe you have a better solution for the 'check_exception' part. I had to remove this from the |
Could you create pull request for this change.
|
Thanks! i added you to contributors list. |
It looks like there is a memory leak in the ParserBuffer, caused by the
_writer
generator. This simplification shows the issue:The generator is not closed and holds a reference to
self
and therefore creates a reference cycle. Is it really necessary to use a generator? (Or can we somehow make sure the generator is closed usingself._writer.close()
?)The text was updated successfully, but these errors were encountered: