Skip to content

Commit

Permalink
Call on_error if net.TLSConnection actor writes to closed socket
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mzagozen committed Nov 17, 2023
1 parent 08bd695 commit 76acc0d
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 7 deletions.
29 changes: 25 additions & 4 deletions base/src/http.act
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ actor Client(cap: net.TCPConnectCap, scheme: str, address: str, port: ?int, tls_
var close_connection: bool = True
var tcp_conn: ?net.TCPConnection = None
var tls_conn: ?net.TLSConnection = None
var reconnecting: bool = False

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

def _on_conn_connect():
# If there are outstanding requests, it probably means we were
# If there are outstanding requests, it probably means we were disconnected
for r in _on_response:
_log.trace("Sending outstanding request", {"request": r.0})
_conn_write(r.0)
await async on_connect(self)
if reconnecting:
reconnecting = False
else:
await async on_connect(self)

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

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

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

def _on_con_error(error: str) -> None:
on_error(self, error)
Expand Down
2 changes: 1 addition & 1 deletion base/src/net.act
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,6 @@ actor TLSConnection(cap: TCPConnectCap, address: str, port: int, on_connect: act
proc def _connect_tls():
NotImplemented

proc def _connect(c):
def _connect(c):
_connect_tls()
_connect(self)
15 changes: 13 additions & 2 deletions base/src/net.ext.c
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,14 @@ void tls_write_cb(uv_write_t *wreq, int status) {
$R netQ_TLSConnectionD_closeG_local (netQ_TLSConnection self, $Cont c$cont, $action on_close) {
uv_stream_t *stream = (uv_stream_t *)from$int(self->_stream);
// fd == -1 means invalid FD and can happen after __resume__
if (stream == -1)
if (stream == -1) {
// The TLS connection may have been closed already when the server
// closed the socket. In that case just call the on_close callback.
char errmsg[] = "TLS TCP socket already closed?";
log_debug(errmsg);
on_close->$class->__asyn__(on_close, self);
return $R_CONT(c$cont, B_None);
}

self->_on_close = on_close;

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

uv_write_t *wreq = (uv_write_t *)malloc(sizeof(uv_write_t));
wreq->data = self;
Expand Down

0 comments on commit 76acc0d

Please sign in to comment.