Skip to content

Commit 692dd88

Browse files
committed
WIP
1 parent d395851 commit 692dd88

File tree

4 files changed

+86
-54
lines changed

4 files changed

+86
-54
lines changed

tests/test_tcp.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2682,9 +2682,9 @@ def eof_server(sock):
26822682
# send EOF
26832683
sock.shutdown(socket.SHUT_WR)
26842684

2685-
# should receive all data
2686-
data = sock.recv_all(CHUNK * SIZE)
2687-
self.assertEqual(len(data), CHUNK * SIZE)
2685+
# should not receive any data
2686+
data = sock.recv(CHUNK * SIZE)
2687+
self.assertEqual(data, b'')
26882688

26892689
sock.close()
26902690

@@ -2842,7 +2842,7 @@ def server(sock):
28422842
sock.shutdown(socket.SHUT_WR)
28432843
loop.call_soon_threadsafe(eof.set)
28442844
# make sure we have enough time to reproduce the issue
2845-
assert sock.recv(1024) == b''
2845+
self.assertEqual(sock.recv(1024), b'')
28462846
sock.close()
28472847

28482848
class Protocol(asyncio.Protocol):
@@ -2875,13 +2875,18 @@ async def client(addr):
28752875
tr.resume_reading()
28762876
await pr.fut
28772877
tr.close()
2878-
# extra data received after transport.close() is ignored
2879-
self.assertIsNone(extra)
2878+
if self.implementation != 'asyncio':
2879+
# extra data received after transport.close() should be
2880+
# ignored - this is likely a bug in asyncio
2881+
self.assertIsNone(extra)
28802882

28812883
with self.tcp_server(server) as srv:
28822884
loop.run_until_complete(client(srv.addr))
28832885

28842886
def test_shutdown_while_pause_reading(self):
2887+
if self.implementation == 'asyncio':
2888+
raise unittest.SkipTest()
2889+
28852890
loop = self.loop
28862891
conn_made = loop.create_future()
28872892
eof_recvd = loop.create_future()
@@ -2898,14 +2903,14 @@ def server(sock):
28982903
while True:
28992904
try:
29002905
sslobj.do_handshake()
2906+
sslobj.write(b'trailing data')
2907+
break
29012908
except ssl.SSLWantReadError:
29022909
if outgoing.pending:
29032910
sock.send(outgoing.read())
29042911
incoming.write(sock.recv(16384))
2905-
else:
2906-
if outgoing.pending:
2907-
sock.send(outgoing.read())
2908-
break
2912+
if outgoing.pending:
2913+
sock.send(outgoing.read())
29092914

29102915
while True:
29112916
try:

uvloop/includes/stdlib.pxi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ cdef ssl_MemoryBIO = ssl.MemoryBIO
129129
cdef ssl_create_default_context = ssl.create_default_context
130130
cdef ssl_SSLError = ssl.SSLError
131131
cdef ssl_SSLAgainErrors = (ssl.SSLWantReadError, ssl.SSLSyscallError)
132+
cdef ssl_SSLZeroReturnError = ssl.SSLZeroReturnError
132133
cdef ssl_CertificateError = ssl.CertificateError
133134
cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
134135
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE

uvloop/sslproto.pxd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ cdef class SSLProtocol:
6565

6666
bint _ssl_writing_paused
6767
bint _app_reading_paused
68-
bint _eof_received
6968

7069
size_t _incoming_high_water
7170
size_t _incoming_low_water
@@ -100,6 +99,7 @@ cdef class SSLProtocol:
10099

101100
cdef _start_shutdown(self)
102101
cdef _check_shutdown_timeout(self)
102+
cdef _do_read_into_void(self)
103103
cdef _do_flush(self)
104104
cdef _do_shutdown(self)
105105
cdef _on_shutdown_complete(self, shutdown_exc)

uvloop/sslproto.pyx

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,6 @@ cdef class SSLProtocol:
278278
self._incoming_high_water = 0
279279
self._incoming_low_water = 0
280280
self._set_read_buffer_limits()
281-
self._eof_received = False
282281

283282
self._app_writing_paused = False
284283
self._outgoing_high_water = 0
@@ -392,27 +391,24 @@ cdef class SSLProtocol:
392391
will close itself. If it returns a true value, closing the
393392
transport is up to the protocol.
394393
"""
395-
self._eof_received = True
396394
try:
397395
if self._loop.get_debug():
398396
aio_logger.debug("%r received EOF", self)
399397

400398
if self._state == DO_HANDSHAKE:
401399
self._on_handshake_complete(ConnectionResetError)
402400

403-
elif self._state == WRAPPED:
404-
self._set_state(FLUSHING)
405-
self._do_write()
401+
elif self._state == WRAPPED or self._state == FLUSHING:
402+
# we treat low-level EOF as a critical situation similar as a
403+
# broken connection - just send whatever in the buffer and
404+
# close up. No application level eof_received() is called -
405+
# because we don't want the user to think that this is a
406+
# graceful shutdown triggered by SSL "close_notify".
406407
self._set_state(SHUTDOWN)
407-
self._do_shutdown()
408-
409-
elif self._state == FLUSHING:
410-
self._do_write()
411-
self._set_state(SHUTDOWN)
412-
self._do_shutdown()
408+
self._on_shutdown_complete(None)
413409

414410
elif self._state == SHUTDOWN:
415-
self._do_shutdown()
411+
self._on_shutdown_complete(None)
416412

417413
except Exception:
418414
self._transport.close()
@@ -443,6 +439,9 @@ cdef class SSLProtocol:
443439
elif self._state == WRAPPED and new_state == FLUSHING:
444440
allowed = True
445441

442+
elif self._state == WRAPPED and new_state == SHUTDOWN:
443+
allowed = True
444+
446445
elif self._state == FLUSHING and new_state == SHUTDOWN:
447446
allowed = True
448447

@@ -560,54 +559,70 @@ cdef class SSLProtocol:
560559
self._transport._force_close(
561560
aio_TimeoutError('SSL shutdown timed out'))
562561

563-
cdef _do_flush(self):
562+
cdef _do_read_into_void(self):
563+
"""Consume and discard incoming application data.
564+
565+
If close_notify is received for the first time, call eof_received.
566+
"""
564567
cdef:
565568
bint close_notify = False
569+
try:
570+
while True:
571+
if not self._sslobj_read(SSL_READ_MAX_SIZE):
572+
close_notify = True
573+
break
574+
except ssl_SSLAgainErrors as exc:
575+
pass
576+
except ssl_SSLZeroReturnError:
577+
close_notify = True
578+
if close_notify:
579+
self._call_eof_received()
566580

581+
cdef _do_flush(self):
582+
"""Flush the write backlog, discarding new data received.
583+
584+
We don't send close_notify in FLUSHING because we still want to send
585+
the remaining data over SSL, even if we received a close_notify. Also,
586+
no application-level resume_writing() or pause_writing() will be called
587+
in FLUSHING, as we could fully manage the flow control internally.
588+
"""
567589
try:
568-
try:
569-
while True:
570-
if not self._sslobj_read(SSL_READ_MAX_SIZE):
571-
close_notify = True
572-
break
573-
except ssl_SSLAgainErrors as exc:
574-
pass
575-
if close_notify:
576-
self._call_eof_received()
577-
if self._write_backlog:
578-
self._do_write()
579-
else:
580-
self._process_outgoing()
590+
self._do_read_into_void()
591+
self._do_write()
592+
self._process_outgoing()
581593
self._control_ssl_reading()
582594
except Exception as ex:
583-
self._fatal_error(ex, 'Fatal error on SSL protocol')
595+
self._on_shutdown_complete(ex)
584596
else:
585597
if not self._get_write_buffer_size():
586598
self._set_state(SHUTDOWN)
587599
self._do_shutdown()
588600

589601
cdef _do_shutdown(self):
602+
"""Send close_notify and wait for the same from the peer."""
590603
try:
591-
if not self._eof_received:
604+
# we must skip all application data (if any) before unwrap
605+
self._do_read_into_void()
606+
try:
592607
self._sslobj.unwrap()
593-
except ssl_SSLAgainErrors as exc:
594-
self._process_outgoing()
595-
except ssl_SSLError as exc:
596-
self._on_shutdown_complete(exc)
597-
else:
598-
self._process_outgoing()
599-
self._call_eof_received()
600-
self._on_shutdown_complete(None)
608+
except ssl_SSLAgainErrors as exc:
609+
self._process_outgoing()
610+
else:
611+
self._process_outgoing()
612+
if not self._get_write_buffer_size():
613+
self._on_shutdown_complete(None)
614+
except Exception as ex:
615+
self._on_shutdown_complete(ex)
601616

602617
cdef _on_shutdown_complete(self, shutdown_exc):
603618
if self._shutdown_timeout_handle is not None:
604619
self._shutdown_timeout_handle.cancel()
605620
self._shutdown_timeout_handle = None
606621

607622
if shutdown_exc:
608-
self._fatal_error(shutdown_exc)
623+
self._fatal_error(shutdown_exc, 'Error occurred during shutdown')
609624
else:
610-
self._loop.call_soon(self._transport.close)
625+
self._transport.close()
611626

612627
cdef _abort(self, exc):
613628
self._set_state(UNWRAPPED)
@@ -630,11 +645,14 @@ cdef class SSLProtocol:
630645
try:
631646
if self._state == WRAPPED:
632647
self._do_write()
648+
self._process_outgoing()
649+
self._control_app_writing()
633650

634651
except Exception as ex:
635652
self._fatal_error(ex, 'Fatal error on SSL protocol')
636653

637654
cdef _do_write(self):
655+
"""Do SSL write, consumes write backlog and fills outgoing BIO."""
638656
cdef size_t data_len, count
639657
try:
640658
while self._write_backlog:
@@ -651,14 +669,13 @@ cdef class SSLProtocol:
651669
self._write_buffer_size -= data_len
652670
except ssl_SSLAgainErrors as exc:
653671
pass
654-
self._process_outgoing()
655672

656673
cdef _process_outgoing(self):
674+
"""Send bytes from the outgoing BIO."""
657675
if not self._ssl_writing_paused:
658676
data = self._outgoing_read()
659677
if len(data):
660678
self._transport.write(data)
661-
self._control_app_writing()
662679

663680
# Incoming flow
664681

@@ -673,8 +690,8 @@ cdef class SSLProtocol:
673690
self._do_read__copied()
674691
if self._write_backlog:
675692
self._do_write()
676-
else:
677-
self._process_outgoing()
693+
self._process_outgoing()
694+
self._control_app_writing()
678695
self._control_ssl_reading()
679696
except Exception as ex:
680697
self._fatal_error(ex, 'Fatal error on SSL protocol')
@@ -860,7 +877,16 @@ cdef class SSLProtocol:
860877
"""
861878
assert self._ssl_writing_paused
862879
self._ssl_writing_paused = False
863-
self._process_outgoing()
880+
881+
if self._state == WRAPPED:
882+
self._process_outgoing()
883+
self._control_app_writing()
884+
885+
elif self._state == FLUSHING:
886+
self._do_flush()
887+
888+
elif self._state == SHUTDOWN:
889+
self._do_shutdown()
864890

865891
cdef _fatal_error(self, exc, message='Fatal error on transport'):
866892
if self._transport:

0 commit comments

Comments
 (0)