END_STREAM_REASON_TIMEOUT blurs together two very different error cases
Here's one case where END_STREAM_REASON_TIMEOUT is used (in connection_ap_expire_beginning()):
if (seconds_since_born >= options->SocksTimeout) {
log_fn(severity, LD_APP,
"Tried for %d seconds to get a connection to %s:%d. "
"Giving up. (%s)",
seconds_since_born,
safe_str_client(entry_conn->socks_request->address),
entry_conn->socks_request->port,
conn_state_to_string(CONN_TYPE_AP, base_conn->state));
connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TIMEOUT);
}
And here's a second case where it is used, in errno_to_stream_end_reason():
S_CASE(ETIMEDOUT):
return END_STREAM_REASON_TIMEOUT;
That first case is a client-side timeout -- e.g. we didn't get a useful circuit. That second case is an exit-side timeout -- we got a circuit, sent the begin cell, and the exit got a tcp timeout failure making the connection to the destination.
We should be helping the controller distinguish between these two cases.