Skip to content

Commit 76acc0d

Browse files
committed
Call on_error if net.TLSConnection actor writes to closed socket
If the (HTTPS) server closes the socket on us after a response, the stream becomes invalid. Let's communicate that to the user by calling on_error. If the user is http.Client it can automatically reconnect and resend the failed request without any intervention.
1 parent 08bd695 commit 76acc0d

3 files changed

Lines changed: 39 additions & 7 deletions

File tree

base/src/http.act

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ actor Client(cap: net.TCPConnectCap, scheme: str, address: str, port: ?int, tls_
460460
var close_connection: bool = True
461461
var tcp_conn: ?net.TCPConnection = None
462462
var tls_conn: ?net.TLSConnection = None
463+
var reconnecting: bool = False
463464

464465
def _connect():
465466
if scheme == "http":
@@ -474,10 +475,14 @@ actor Client(cap: net.TCPConnectCap, scheme: str, address: str, port: ?int, tls_
474475
raise ValueError("Only http and https schemes are supported. Unsupported scheme: %s" % scheme)
475476

476477
def _on_conn_connect():
477-
# If there are outstanding requests, it probably means we were
478+
# If there are outstanding requests, it probably means we were disconnected
478479
for r in _on_response:
480+
_log.trace("Sending outstanding request", {"request": r.0})
479481
_conn_write(r.0)
480-
await async on_connect(self)
482+
if reconnecting:
483+
reconnecting = False
484+
else:
485+
await async on_connect(self)
481486

482487
def _on_tcp_connect(conn: net.TCPConnection) -> None:
483488
_on_conn_connect()
@@ -516,10 +521,26 @@ actor Client(cap: net.TCPConnectCap, scheme: str, address: str, port: ?int, tls_
516521
break
517522

518523
def _on_tcp_error(conn: net.TCPConnection, error: str) -> None:
519-
_on_con_error(error)
524+
# TODO: What if we're reconnecting right now and get another error
525+
# because the user made another request? The request ends up in the
526+
# _on_response buffer, so I guess we do not propagate the error?
527+
if "bad file descriptor" in error:
528+
_log.debug("TCP connection closed, reconnecting", {"error": error})
529+
reconnecting = True
530+
conn.reconnect()
531+
else:
532+
_on_con_error(error)
520533

521534
def _on_tls_error(conn: net.TLSConnection, error: str) -> None:
522-
_on_con_error(error)
535+
# TODO: What if we're reconnecting right now and get another error
536+
# because the user made another request? The request ends up in the
537+
# _on_response buffer, so I guess we do not propagate the error?
538+
if "bad stream" in error:
539+
_log.debug("TLS connection closed, reconnecting", {"error": error})
540+
reconnecting = True
541+
conn.reconnect()
542+
else:
543+
_on_con_error(error)
523544

524545
def _on_con_error(error: str) -> None:
525546
on_error(self, error)

base/src/net.act

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,6 @@ actor TLSConnection(cap: TCPConnectCap, address: str, port: int, on_connect: act
337337
proc def _connect_tls():
338338
NotImplemented
339339

340-
proc def _connect(c):
340+
def _connect(c):
341341
_connect_tls()
342342
_connect(self)

base/src/net.ext.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,8 +630,14 @@ void tls_write_cb(uv_write_t *wreq, int status) {
630630
$R netQ_TLSConnectionD_closeG_local (netQ_TLSConnection self, $Cont c$cont, $action on_close) {
631631
uv_stream_t *stream = (uv_stream_t *)from$int(self->_stream);
632632
// fd == -1 means invalid FD and can happen after __resume__
633-
if (stream == -1)
633+
if (stream == -1) {
634+
// The TLS connection may have been closed already when the server
635+
// closed the socket. In that case just call the on_close callback.
636+
char errmsg[] = "TLS TCP socket already closed?";
637+
log_debug(errmsg);
638+
on_close->$class->__asyn__(on_close, self);
634639
return $R_CONT(c$cont, B_None);
640+
}
635641

636642
self->_on_close = on_close;
637643

@@ -646,8 +652,13 @@ void tls_write_cb(uv_write_t *wreq, int status) {
646652
$R netQ_TLSConnectionD_writeG_local (netQ_TLSConnection self, $Cont c$cont, B_bytes data) {
647653
uv_stream_t *stream = (uv_stream_t *)from$int(self->_stream);
648654
// fd == -1 means invalid FD and can happen after __resume__
649-
if (stream == -1)
655+
if (stream == -1) {
656+
char errmsg[] = "Failed to write to TLS TCP socket: bad stream";
657+
log_debug(errmsg);
658+
$action2 f = ($action2)self->on_error;
659+
f->$class->__asyn__(f, self, to$str(errmsg));
650660
return $R_CONT(c$cont, B_None);
661+
}
651662

652663
uv_write_t *wreq = (uv_write_t *)malloc(sizeof(uv_write_t));
653664
wreq->data = self;

0 commit comments

Comments
 (0)