Skip to content

Commit

Permalink
Optimize critical path in smtp_DATA
Browse files Browse the repository at this point in the history
These are all faster, tested with lots of timeit's.

In relation to #149 ... well, there's no relation, actually. `data` is
an internal temporary structure and after removal of "transparent dots"
gets flattened into bytes anyways. The visible external structs are
`original_content` and `content`, and their data types remain the same.
  • Loading branch information
pepoluan committed Dec 2, 2020
1 parent 4eb3cae commit 2dcefcd
Showing 1 changed file with 16 additions and 12 deletions.
28 changes: 16 additions & 12 deletions aiosmtpd/smtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -904,11 +904,11 @@ async def smtp_DATA(self, arg: str) -> None:
return

await self.push('354 End data with <CR><LF>.<CR><LF>')
data: List[bytes] = []
data: List[bytearray] = []

num_bytes: int = 0
limit: Optional[int] = self.data_size_limit
partial_line: List[bytes] = []
line_fragments: List[bytes] = []
state: _DataState = _DataState.NOMINAL
while self.transport is not None: # pragma: nobranch
# Since eof_received cancels this coroutine,
Expand All @@ -934,7 +934,7 @@ async def smtp_DATA(self, arg: str) -> None:
line = await self._reader.read(e.consumed)
assert not line.endswith(b'\n')
# A lone dot in a line signals the end of DATA.
if not partial_line and line == b'.\r\n':
if not line_fragments and line == b'.\r\n':
break
num_bytes += len(line)
if state == _DataState.NOMINAL and limit and num_bytes > limit:
Expand All @@ -943,13 +943,12 @@ async def smtp_DATA(self, arg: str) -> None:
state = _DataState.TOO_MUCH
# Discard data immediately to prevent memory pressure
data *= 0
line_fragments.append(line)
if line.endswith(b'\n'):
# Stop recording data if state is not "NOMINAL"
# Record data only if state is "NOMINAL"
if state == _DataState.NOMINAL:
data.append(EMPTYBYTES.join(partial_line) + line)
partial_line *= 0
else:
partial_line.append(line)
data.append(bytearray(EMPTYBYTES.join(line_fragments)))
line_fragments *= 0

# Day of reckoning! Let's take care of those out-of-nominal situations
if state != _DataState.NOMINAL:
Expand All @@ -961,14 +960,16 @@ async def smtp_DATA(self, arg: str) -> None:
return

# If unfinished_line is non-empty, then the connection was closed.
assert not partial_line
assert not line_fragments

# Remove extraneous carriage returns and de-transparency
# according to RFC 5321, Section 4.5.2.
for i, text in enumerate(data):
for text in data:
if text.startswith(b'.'):
data[i] = text[1:]
content = original_content = EMPTYBYTES.join(data)
del text[0]
original_content: bytes = EMPTYBYTES.join(data)
# Discard data immediately to prevent memory pressure
data *= 0

if self._decode_data:
if self.enable_SMTPUTF8:
Expand All @@ -982,8 +983,11 @@ async def smtp_DATA(self, arg: str) -> None:
# but the client ignores that and sends non-ascii anyway.
await self.push('500 Error: strict ASCII mode')
return
else:
content = original_content
self.envelope.content = content
self.envelope.original_content = original_content

# Call the new API first if it's implemented.
if "DATA" in self._handle_hooks:
status = await self._call_handler_hook('DATA')
Expand Down

0 comments on commit 2dcefcd

Please sign in to comment.