diff --git a/src/network/connection.c b/src/network/connection.c index 08f067a17..8e4627d7f 100644 --- a/src/network/connection.c +++ b/src/network/connection.c @@ -118,13 +118,19 @@ get_connections_transfering_count(void) return i; } +/** Check whether the pointer @a conn still points to a connection + * with the given @a id. If the struct connection has already been + * freed, this returns 0. By comparing connection.id, this function + * can usually detect even the case where a different connection has + * been created at the same address. For that to work, the caller + * must save the connection.id before the connection can be deleted. */ static inline int -connection_disappeared(struct connection *conn) +connection_disappeared(struct connection *conn, unsigned int id) { struct connection *c; foreach (c, connection_queue) - if (conn == c && conn->id == c->id) + if (conn == c && id == c->id) return 0; return 1; @@ -361,17 +367,19 @@ set_connection_state(struct connection *conn, struct connection_state state) conn->state = state; if (is_in_state(conn->state, S_TRANS)) { + const unsigned int id = conn->id; + if (upload_progress && upload_progress->timer == TIMER_ID_UNDEF) { start_update_progress(upload_progress, (void (*)(void *)) upload_stat_timer, conn); upload_stat_timer(conn); - if (connection_disappeared(conn)) + if (connection_disappeared(conn, id)) return; } if (progress->timer == TIMER_ID_UNDEF) { start_update_progress(progress, (void (*)(void *)) stat_timer, conn); update_connection_progress(conn); - if (connection_disappeared(conn)) + if (connection_disappeared(conn, id)) return; } @@ -450,13 +458,15 @@ static void notify_connection_callbacks(struct connection *conn) { struct connection_state state = conn->state; + unsigned int id = conn->id; struct download *download, *next; foreachsafe (download, next, conn->downloads) { download->cached = conn->cached; if (download->callback) download->callback(download, download->data); - if (is_in_progress_state(state) && connection_disappeared(conn)) + if (is_in_progress_state(state) + && connection_disappeared(conn, id)) return; } } diff --git a/src/network/socket.c b/src/network/socket.c index 29487addd..23a042340 100644 --- a/src/network/socket.c +++ b/src/network/socket.c @@ -69,6 +69,16 @@ struct connect_info { struct uri *uri; /* For updating the blacklist. */ }; +/** For detecting whether a struct socket has been deleted while a + * function was using it. */ +struct socket_weak_ref { + LIST_HEAD(struct socket_weak_ref); + + /** done_socket() resets this to NULL. */ + struct socket *socket; +}; + +static INIT_LIST_OF(struct socket_weak_ref, socket_weak_refs); /* To enable logging of tranfers, for debugging purposes. */ #if 0 @@ -143,6 +153,8 @@ init_socket(void *conn, struct socket_operations *ops) void done_socket(struct socket *socket) { + struct socket_weak_ref *ref; + close_socket(socket); if (socket->connect_info) @@ -150,6 +162,11 @@ done_socket(struct socket *socket) mem_free_set(&socket->read_buffer, NULL); mem_free_set(&socket->write_buffer, NULL); + + foreach(ref, socket_weak_refs) { + if (ref->socket == socket) + ref->socket = NULL; + } } void @@ -935,13 +952,26 @@ void read_from_socket(struct socket *socket, struct read_buffer *buffer, struct connection_state state, socket_read_T done) { + const int is_buffer_new = (buffer != socket->read_buffer); + struct socket_weak_ref ref; select_handler_T write_handler; + ref.socket = socket; + add_to_list(socket_weak_refs, &ref); + buffer->done = done; socket->ops->set_timeout(socket, connection_state(0)); socket->ops->set_state(socket, state); + del_from_list(&ref); + if (ref.socket == NULL) { + /* socket->ops->set_state deleted the socket. */ + if (is_buffer_new) + mem_free(buffer); + return; + } + if (socket->read_buffer && buffer != socket->read_buffer) mem_free(socket->read_buffer); socket->read_buffer = buffer;