4
4
5
5
from .base_protocol import BaseProtocol
6
6
from .client_exceptions import (
7
+ ClientConnectionError ,
7
8
ClientOSError ,
8
9
ClientPayloadError ,
9
10
ServerDisconnectedError ,
10
11
SocketTimeoutError ,
11
12
)
12
13
from .helpers import (
13
14
BaseTimerContext ,
15
+ ErrorableMixin ,
14
16
set_exception ,
15
17
set_result ,
16
18
status_code_must_be_empty_body ,
@@ -80,41 +82,71 @@ def is_connected(self) -> bool:
80
82
def connection_lost (self , exc : Optional [BaseException ]) -> None :
81
83
self ._drop_timeout ()
82
84
83
- if exc is not None :
84
- set_exception (self .closed , exc )
85
- else :
85
+ original_connection_error = exc
86
+ reraised_exc = original_connection_error
87
+
88
+ connection_closed_cleanly = original_connection_error is None
89
+ """Whether the connection got a clean EOF."""
90
+
91
+ if connection_closed_cleanly :
86
92
set_result (self .closed , None )
93
+ else :
94
+ set_exception (
95
+ self .closed ,
96
+ ClientConnectionError (
97
+ f"Connection lost: { original_connection_error !s} " ,
98
+ ),
99
+ original_connection_error ,
100
+ )
87
101
88
102
if self ._payload_parser is not None :
89
- with suppress (Exception ):
103
+ with suppress (Exception ): # FIXME: log this somehow?
90
104
self ._payload_parser .feed_eof ()
91
105
92
106
uncompleted = None
93
107
if self ._parser is not None :
94
108
try :
95
109
uncompleted = self ._parser .feed_eof ()
96
- except Exception as e :
110
+ except Exception as underlying_exc :
97
111
if self ._payload is not None :
98
- exc = ClientPayloadError ("Response payload is not completed" )
99
- exc .__cause__ = e
100
- self ._payload .set_exception (exc )
112
+ client_payload_exc_msg = (
113
+ f"Response payload is not completed: { underlying_exc !r} "
114
+ )
115
+ if not connection_closed_cleanly :
116
+ client_payload_exc_msg = (
117
+ f"{ client_payload_exc_msg !s} . "
118
+ f"{ original_connection_error !r} "
119
+ )
120
+ set_exception (
121
+ self ._payload ,
122
+ ClientPayloadError (client_payload_exc_msg ),
123
+ underlying_exc ,
124
+ )
101
125
102
126
if not self .is_eof ():
103
- if isinstance (exc , OSError ):
104
- exc = ClientOSError (* exc .args )
105
- if exc is None :
106
- exc = ServerDisconnectedError (uncompleted )
127
+ if isinstance (original_connection_error , OSError ):
128
+ reraised_exc = ClientOSError (* original_connection_error .args )
129
+ if connection_closed_cleanly :
130
+ reraised_exc = ServerDisconnectedError (uncompleted )
107
131
# assigns self._should_close to True as side effect,
108
132
# we do it anyway below
109
- self .set_exception (exc )
133
+ set_exc_kwargs = (
134
+ {}
135
+ if connection_closed_cleanly
136
+ else {
137
+ "exc_cause" : original_connection_error ,
138
+ }
139
+ )
140
+ assert reraised_exc is not None
141
+ self .set_exception (reraised_exc , ** set_exc_kwargs )
110
142
111
143
self ._should_close = True
112
144
self ._parser = None
113
145
self ._payload = None
114
146
self ._payload_parser = None
115
147
self ._reading_paused = False
116
148
117
- super ().connection_lost (exc )
149
+ super ().connection_lost (reraised_exc )
118
150
119
151
def eof_received (self ) -> None :
120
152
# should call parser.feed_eof() most likely
@@ -128,10 +160,14 @@ def resume_reading(self) -> None:
128
160
super ().resume_reading ()
129
161
self ._reschedule_timeout ()
130
162
131
- def set_exception (self , exc : BaseException ) -> None :
163
+ def set_exception (
164
+ self ,
165
+ exc : BaseException ,
166
+ exc_cause : BaseException = ErrorableMixin ._EXC_SENTINEL ,
167
+ ) -> None :
132
168
self ._should_close = True
133
169
self ._drop_timeout ()
134
- super ().set_exception (exc )
170
+ super ().set_exception (exc , exc_cause )
135
171
136
172
def set_parser (self , parser : Any , payload : Any ) -> None :
137
173
# TODO: actual types are:
@@ -208,7 +244,7 @@ def _on_read_timeout(self) -> None:
208
244
exc = SocketTimeoutError ("Timeout on reading data from socket" )
209
245
self .set_exception (exc )
210
246
if self ._payload is not None :
211
- self ._payload . set_exception ( exc )
247
+ set_exception ( self ._payload , exc )
212
248
213
249
def data_received (self , data : bytes ) -> None :
214
250
self ._reschedule_timeout ()
@@ -234,14 +270,19 @@ def data_received(self, data: bytes) -> None:
234
270
# parse http messages
235
271
try :
236
272
messages , upgraded , tail = self ._parser .feed_data (data )
237
- except BaseException as exc :
273
+ except BaseException as underlying_exc :
238
274
if self .transport is not None :
239
275
# connection.release() could be called BEFORE
240
276
# data_received(), the transport is already
241
277
# closed in this case
242
278
self .transport .close ()
243
279
# should_close is True after the call
244
- self .set_exception (exc )
280
+ self .set_exception (
281
+ ClientPayloadError (
282
+ f"Unable to parse response payload: { underlying_exc !r} "
283
+ ),
284
+ underlying_exc ,
285
+ )
245
286
return
246
287
247
288
self ._upgraded = upgraded
0 commit comments