| /* |
| * libwebsockets - small server side websockets and web server implementation |
| * |
| * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com> |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to |
| * deal in the Software without restriction, including without limitation the |
| * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
| * sell copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| * IN THE SOFTWARE. |
| */ |
| |
| #include "private-lib-core.h" |
| |
| #if defined(LWS_WITH_CLIENT) |
| static int |
| lws_close_trans_q_leader(struct lws_dll2 *d, void *user) |
| { |
| struct lws *w = lws_container_of(d, struct lws, dll2_cli_txn_queue); |
| |
| __lws_close_free_wsi(w, -1, "trans q leader closing"); |
| |
| return 0; |
| } |
| #endif |
| |
| void |
| __lws_reset_wsi(struct lws *wsi) |
| { |
| if (!wsi) |
| return; |
| |
| #if defined(LWS_WITH_CLIENT) |
| |
| lws_free_set_NULL(wsi->cli_hostname_copy); |
| |
| /* |
| * if we have wsi in our transaction queue, if we are closing we |
| * must go through and close all those first |
| */ |
| if (wsi->vhost) { |
| |
| /* we are no longer an active client connection that can piggyback */ |
| lws_dll2_remove(&wsi->dll_cli_active_conns); |
| |
| lws_dll2_foreach_safe(&wsi->dll2_cli_txn_queue_owner, NULL, |
| lws_close_trans_q_leader); |
| |
| /* |
| * !!! If we are closing, but we have pending pipelined |
| * transaction results we already sent headers for, that's going |
| * to destroy sync for HTTP/1 and leave H2 stream with no live |
| * swsi.` |
| * |
| * However this is normal if we are being closed because the |
| * transaction queue leader is closing. |
| */ |
| lws_dll2_remove(&wsi->dll2_cli_txn_queue); |
| } |
| #endif |
| |
| if (wsi->vhost) |
| lws_dll2_remove(&wsi->vh_awaiting_socket); |
| |
| /* |
| * Protocol user data may be allocated either internally by lws |
| * or by specified the user. We should only free what we allocated. |
| */ |
| if (wsi->protocol && wsi->protocol->per_session_data_size && |
| wsi->user_space && !wsi->user_space_externally_allocated) |
| lws_free_set_NULL(wsi->user_space); |
| |
| lws_buflist_destroy_all_segments(&wsi->buflist); |
| lws_buflist_destroy_all_segments(&wsi->buflist_out); |
| #if defined(LWS_WITH_UDP) |
| lws_free_set_NULL(wsi->udp); |
| #endif |
| wsi->retry = 0; |
| |
| #if defined(LWS_WITH_CLIENT) |
| lws_dll2_remove(&wsi->dll2_cli_txn_queue); |
| lws_dll2_remove(&wsi->dll_cli_active_conns); |
| #endif |
| |
| #if defined(LWS_WITH_SYS_ASYNC_DNS) |
| lws_async_dns_cancel(wsi); |
| #endif |
| |
| #if defined(LWS_WITH_HTTP_PROXY) |
| if (wsi->http.buflist_post_body) |
| lws_buflist_destroy_all_segments(&wsi->http.buflist_post_body); |
| #endif |
| |
| if (wsi->vhost && wsi->vhost->lserv_wsi == wsi) |
| wsi->vhost->lserv_wsi = NULL; |
| #if defined(LWS_WITH_CLIENT) |
| if (wsi->vhost) |
| lws_dll2_remove(&wsi->dll_cli_active_conns); |
| #endif |
| wsi->context->count_wsi_allocated--; |
| |
| __lws_same_vh_protocol_remove(wsi); |
| #if defined(LWS_WITH_CLIENT) |
| lws_free_set_NULL(wsi->stash); |
| lws_free_set_NULL(wsi->cli_hostname_copy); |
| #endif |
| |
| #if defined(LWS_WITH_PEER_LIMITS) |
| lws_peer_track_wsi_close(wsi->context, wsi->peer); |
| wsi->peer = NULL; |
| #endif |
| |
| /* since we will destroy the wsi, make absolutely sure now */ |
| |
| #if defined(LWS_WITH_OPENSSL) |
| __lws_ssl_remove_wsi_from_buffered_list(wsi); |
| #endif |
| __lws_wsi_remove_from_sul(wsi); |
| |
| if (wsi->role_ops->destroy_role) |
| wsi->role_ops->destroy_role(wsi); |
| |
| #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) |
| __lws_header_table_detach(wsi, 0); |
| #endif |
| } |
| |
| void |
| __lws_free_wsi(struct lws *wsi) |
| { |
| if (!wsi) |
| return; |
| |
| __lws_reset_wsi(wsi); |
| |
| if (wsi->context->event_loop_ops->destroy_wsi) |
| wsi->context->event_loop_ops->destroy_wsi(wsi); |
| |
| lws_vhost_unbind_wsi(wsi); |
| |
| lwsl_debug("%s: %p, remaining wsi %d, tsi fds count %d\n", __func__, wsi, |
| wsi->context->count_wsi_allocated, |
| wsi->context->pt[(int)wsi->tsi].fds_count); |
| |
| lws_free(wsi); |
| } |
| |
| |
| void |
| lws_remove_child_from_any_parent(struct lws *wsi) |
| { |
| struct lws **pwsi; |
| int seen = 0; |
| |
| if (!wsi->parent) |
| return; |
| |
| /* detach ourselves from parent's child list */ |
| pwsi = &wsi->parent->child_list; |
| while (*pwsi) { |
| if (*pwsi == wsi) { |
| lwsl_info("%s: detach %p from parent %p\n", __func__, |
| wsi, wsi->parent); |
| |
| if (wsi->parent->protocol) |
| wsi->parent->protocol->callback(wsi, |
| LWS_CALLBACK_CHILD_CLOSING, |
| wsi->parent->user_space, wsi, 0); |
| |
| *pwsi = wsi->sibling_list; |
| seen = 1; |
| break; |
| } |
| pwsi = &(*pwsi)->sibling_list; |
| } |
| if (!seen) |
| lwsl_err("%s: failed to detach from parent\n", __func__); |
| |
| wsi->parent = NULL; |
| } |
| |
| #if defined(LWS_WITH_CLIENT) |
| void |
| lws_inform_client_conn_fail(struct lws *wsi, void *arg, size_t len) |
| { |
| lws_addrinfo_clean(wsi); |
| |
| if (wsi->already_did_cce) |
| return; |
| |
| wsi->already_did_cce = 1; |
| lws_stats_bump(&wsi->context->pt[(int)wsi->tsi], |
| LWSSTATS_C_CONNS_CLIENT_FAILED, 1); |
| |
| if (!wsi->protocol) |
| return; |
| |
| wsi->protocol->callback(wsi, |
| LWS_CALLBACK_CLIENT_CONNECTION_ERROR, |
| wsi->user_space, arg, len); |
| } |
| #endif |
| |
| void |
| lws_addrinfo_clean(struct lws *wsi) |
| { |
| #if defined(LWS_WITH_CLIENT) |
| if (!wsi->dns_results) |
| return; |
| |
| #if defined(LWS_WITH_SYS_ASYNC_DNS) |
| lws_async_dns_freeaddrinfo(&wsi->dns_results); |
| #else |
| freeaddrinfo((struct addrinfo *)wsi->dns_results); |
| #endif |
| wsi->dns_results = NULL; |
| #endif |
| } |
| |
| void |
| __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, |
| const char *caller) |
| { |
| struct lws_context_per_thread *pt; |
| const struct lws_protocols *pro; |
| struct lws_context *context; |
| struct lws *wsi1, *wsi2; |
| int n, ccb; |
| |
| lwsl_info("%s: %p: caller: %s\n", __func__, wsi, caller); |
| |
| if (!wsi) |
| return; |
| |
| lws_access_log(wsi); |
| |
| if (!lws_dll2_is_detached(&wsi->dll_buflist)) { |
| lwsl_info("%s: wsi %p: going down with stuff in buflist\n", |
| __func__, wsi); } |
| |
| context = wsi->context; |
| pt = &context->pt[(int)wsi->tsi]; |
| lws_stats_bump(pt, LWSSTATS_C_API_CLOSE, 1); |
| |
| #if defined(LWS_WITH_CLIENT) |
| |
| lws_free_set_NULL(wsi->cli_hostname_copy); |
| |
| lws_addrinfo_clean(wsi); |
| #endif |
| |
| #if defined(LWS_WITH_HTTP2) |
| if (wsi->mux_stream_immortal) |
| lws_http_close_immortal(wsi); |
| #endif |
| |
| /* if we have children, close them first */ |
| if (wsi->child_list) { |
| wsi2 = wsi->child_list; |
| while (wsi2) { |
| wsi1 = wsi2->sibling_list; |
| wsi2->parent = NULL; |
| /* stop it doing shutdown processing */ |
| wsi2->socket_is_permanently_unusable = 1; |
| __lws_close_free_wsi(wsi2, reason, |
| "general child recurse"); |
| wsi2 = wsi1; |
| } |
| wsi->child_list = NULL; |
| } |
| |
| #if defined(LWS_ROLE_RAW_FILE) |
| if (wsi->role_ops == &role_ops_raw_file) { |
| lws_remove_child_from_any_parent(wsi); |
| __remove_wsi_socket_from_fds(wsi); |
| if (wsi->protocol) |
| wsi->protocol->callback(wsi, wsi->role_ops->close_cb[0], |
| wsi->user_space, NULL, 0); |
| goto async_close; |
| } |
| #endif |
| |
| wsi->wsistate_pre_close = wsi->wsistate; |
| |
| #ifdef LWS_WITH_CGI |
| if (wsi->role_ops == &role_ops_cgi) { |
| |
| // lwsl_debug("%s: closing stdwsi index %d\n", __func__, (int)wsi->lsp_channel); |
| |
| /* we are not a network connection, but a handler for CGI io */ |
| if (wsi->parent && wsi->parent->http.cgi) { |
| |
| if (wsi->parent->child_list == wsi && !wsi->sibling_list) |
| lws_cgi_remove_and_kill(wsi->parent); |
| |
| /* end the binding between us and master */ |
| if (wsi->parent->http.cgi) |
| wsi->parent->http.cgi->lsp->stdwsi[(int)wsi->lsp_channel] = |
| NULL; |
| } |
| wsi->socket_is_permanently_unusable = 1; |
| |
| goto just_kill_connection; |
| } |
| |
| if (wsi->http.cgi) |
| lws_cgi_remove_and_kill(wsi); |
| #endif |
| |
| #if defined(LWS_WITH_CLIENT) |
| lws_free_set_NULL(wsi->stash); |
| #endif |
| |
| if (wsi->role_ops == &role_ops_raw_skt) { |
| wsi->socket_is_permanently_unusable = 1; |
| goto just_kill_connection; |
| } |
| #if defined(LWS_WITH_FILE_OPS) && (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)) |
| if (lwsi_role_http(wsi) && lwsi_role_server(wsi) && |
| wsi->http.fop_fd != NULL) |
| lws_vfs_file_close(&wsi->http.fop_fd); |
| #endif |
| |
| if (lwsi_state(wsi) == LRS_DEAD_SOCKET) |
| return; |
| |
| if (wsi->socket_is_permanently_unusable || |
| reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY || |
| lwsi_state(wsi) == LRS_SHUTDOWN) |
| goto just_kill_connection; |
| |
| switch (lwsi_state_PRE_CLOSE(wsi)) { |
| case LRS_DEAD_SOCKET: |
| return; |
| |
| /* we tried the polite way... */ |
| case LRS_WAITING_TO_SEND_CLOSE: |
| case LRS_AWAITING_CLOSE_ACK: |
| case LRS_RETURNED_CLOSE: |
| goto just_kill_connection; |
| |
| case LRS_FLUSHING_BEFORE_CLOSE: |
| if (lws_has_buffered_out(wsi) |
| #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) |
| || wsi->http.comp_ctx.buflist_comp || |
| wsi->http.comp_ctx.may_have_more |
| #endif |
| ) { |
| lws_callback_on_writable(wsi); |
| return; |
| } |
| lwsl_info("%p: end LRS_FLUSHING_BEFORE_CLOSE\n", wsi); |
| goto just_kill_connection; |
| default: |
| if (lws_has_buffered_out(wsi) |
| #if defined(LWS_WITH_HTTP_STREAM_COMPRESSION) |
| || wsi->http.comp_ctx.buflist_comp || |
| wsi->http.comp_ctx.may_have_more |
| #endif |
| ) { |
| lwsl_info("%p: LRS_FLUSHING_BEFORE_CLOSE\n", wsi); |
| lwsi_set_state(wsi, LRS_FLUSHING_BEFORE_CLOSE); |
| __lws_set_timeout(wsi, |
| PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5); |
| return; |
| } |
| break; |
| } |
| |
| if (lwsi_state(wsi) == LRS_WAITING_CONNECT || |
| lwsi_state(wsi) == LRS_WAITING_DNS || |
| lwsi_state(wsi) == LRS_H1C_ISSUE_HANDSHAKE) |
| goto just_kill_connection; |
| |
| if (!wsi->told_user_closed && wsi->user_space && wsi->protocol && |
| wsi->protocol_bind_balance) { |
| wsi->protocol->callback(wsi, |
| wsi->role_ops->protocol_unbind_cb[ |
| !!lwsi_role_server(wsi)], |
| wsi->user_space, (void *)__func__, 0); |
| wsi->protocol_bind_balance = 0; |
| } |
| |
| /* |
| * signal we are closing, lws_write will |
| * add any necessary version-specific stuff. If the write fails, |
| * no worries we are closing anyway. If we didn't initiate this |
| * close, then our state has been changed to |
| * LRS_RETURNED_CLOSE and we will skip this. |
| * |
| * Likewise if it's a second call to close this connection after we |
| * sent the close indication to the peer already, we are in state |
| * LRS_AWAITING_CLOSE_ACK and will skip doing this a second time. |
| */ |
| |
| if (wsi->role_ops->close_via_role_protocol && |
| wsi->role_ops->close_via_role_protocol(wsi, reason)) |
| return; |
| |
| just_kill_connection: |
| |
| #if defined(LWS_WITH_FILE_OPS) && (defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)) |
| if (lwsi_role_http(wsi) && lwsi_role_server(wsi) && |
| wsi->http.fop_fd != NULL) |
| lws_vfs_file_close(&wsi->http.fop_fd); |
| #endif |
| |
| #if defined(LWS_WITH_SYS_ASYNC_DNS) |
| lws_async_dns_cancel(wsi); |
| #endif |
| |
| #if defined(LWS_WITH_HTTP_PROXY) |
| if (wsi->http.buflist_post_body) |
| lws_buflist_destroy_all_segments(&wsi->http.buflist_post_body); |
| #endif |
| #if defined(LWS_WITH_UDP) |
| if (wsi->udp) |
| lws_free_set_NULL(wsi->udp); |
| #endif |
| |
| if (wsi->role_ops->close_kill_connection) |
| wsi->role_ops->close_kill_connection(wsi, reason); |
| |
| n = 0; |
| |
| if (!wsi->told_user_closed && wsi->user_space && |
| wsi->protocol_bind_balance && wsi->protocol) { |
| lwsl_debug("%s: %p: DROP_PROTOCOL %s\n", __func__, wsi, |
| wsi->protocol ? wsi->protocol->name: "NULL"); |
| if (wsi->protocol) |
| wsi->protocol->callback(wsi, |
| wsi->role_ops->protocol_unbind_cb[ |
| !!lwsi_role_server(wsi)], |
| wsi->user_space, (void *)__func__, 0); |
| wsi->protocol_bind_balance = 0; |
| } |
| |
| #if defined(LWS_WITH_CLIENT) |
| if ((lwsi_state(wsi) == LRS_WAITING_SERVER_REPLY || |
| lwsi_state(wsi) == LRS_WAITING_DNS || |
| lwsi_state(wsi) == LRS_WAITING_CONNECT) && |
| !wsi->already_did_cce && wsi->protocol) { |
| static const char _reason[] = "closed before established"; |
| |
| lws_inform_client_conn_fail(wsi, |
| (void *)_reason, sizeof(_reason)); |
| } |
| #endif |
| |
| /* |
| * Testing with ab shows that we have to stage the socket close when |
| * the system is under stress... shutdown any further TX, change the |
| * state to one that won't emit anything more, and wait with a timeout |
| * for the POLLIN to show a zero-size rx before coming back and doing |
| * the actual close. |
| */ |
| if (wsi->role_ops != &role_ops_raw_skt && !lwsi_role_client(wsi) && |
| lwsi_state(wsi) != LRS_SHUTDOWN && |
| lwsi_state(wsi) != LRS_UNCONNECTED && |
| reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY && |
| !wsi->socket_is_permanently_unusable) { |
| |
| #if defined(LWS_WITH_TLS) |
| if (lws_is_ssl(wsi) && wsi->tls.ssl) { |
| n = 0; |
| switch (__lws_tls_shutdown(wsi)) { |
| case LWS_SSL_CAPABLE_DONE: |
| case LWS_SSL_CAPABLE_ERROR: |
| case LWS_SSL_CAPABLE_MORE_SERVICE_READ: |
| case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE: |
| case LWS_SSL_CAPABLE_MORE_SERVICE: |
| break; |
| } |
| } else |
| #endif |
| { |
| lwsl_info("%s: shutdown conn: %p (sk %d, state 0x%x)\n", |
| __func__, wsi, (int)(long)wsi->desc.sockfd, |
| lwsi_state(wsi)); |
| if (!wsi->socket_is_permanently_unusable && |
| lws_socket_is_valid(wsi->desc.sockfd)) { |
| wsi->socket_is_permanently_unusable = 1; |
| n = shutdown(wsi->desc.sockfd, SHUT_WR); |
| } |
| } |
| if (n) |
| lwsl_debug("closing: shutdown (state 0x%x) ret %d\n", |
| lwsi_state(wsi), LWS_ERRNO); |
| |
| /* |
| * This causes problems on WINCE / ESP32 with disconnection |
| * when the events are half closing connection |
| */ |
| #if !defined(_WIN32_WCE) && !defined(LWS_PLAT_FREERTOS) |
| /* libuv: no event available to guarantee completion */ |
| if (!wsi->socket_is_permanently_unusable && |
| lws_socket_is_valid(wsi->desc.sockfd) && |
| lwsi_state(wsi) != LRS_SHUTDOWN && |
| (context->event_loop_ops->flags & LELOF_ISPOLL)) { |
| __lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); |
| lwsi_set_state(wsi, LRS_SHUTDOWN); |
| __lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH, |
| context->timeout_secs); |
| |
| return; |
| } |
| #endif |
| } |
| |
| lwsl_debug("%s: real just_kill_connection: %p (sockfd %d)\n", __func__, |
| wsi, wsi->desc.sockfd); |
| |
| #ifdef LWS_WITH_HUBBUB |
| if (wsi->http.rw) { |
| lws_rewrite_destroy(wsi->http.rw); |
| wsi->http.rw = NULL; |
| } |
| #endif |
| |
| if (wsi->http.pending_return_headers) |
| lws_free_set_NULL(wsi->http.pending_return_headers); |
| |
| /* |
| * we won't be servicing or receiving anything further from this guy |
| * delete socket from the internal poll list if still present |
| */ |
| __lws_ssl_remove_wsi_from_buffered_list(wsi); |
| __lws_wsi_remove_from_sul(wsi); |
| |
| //if (wsi->told_event_loop_closed) // cgi std close case (dummy-callback) |
| // return; |
| |
| // lwsl_notice("%s: wsi %p, fd %d\n", __func__, wsi, wsi->desc.sockfd); |
| |
| /* checking return redundant since we anyway close */ |
| if (wsi->desc.sockfd != LWS_SOCK_INVALID) |
| __remove_wsi_socket_from_fds(wsi); |
| else |
| __lws_same_vh_protocol_remove(wsi); |
| |
| lwsi_set_state(wsi, LRS_DEAD_SOCKET); |
| lws_buflist_destroy_all_segments(&wsi->buflist); |
| lws_dll2_remove(&wsi->dll_buflist); |
| |
| if (wsi->role_ops->close_role) |
| wsi->role_ops->close_role(pt, wsi); |
| |
| /* tell the user it's all over for this guy */ |
| |
| ccb = 0; |
| if ((lwsi_state_est_PRE_CLOSE(wsi) || |
| /* raw skt adopted but didn't complete tls hs should CLOSE */ |
| (wsi->role_ops == &role_ops_raw_skt && !lwsi_role_client(wsi)) || |
| lwsi_state_PRE_CLOSE(wsi) == LRS_WAITING_SERVER_REPLY) && |
| !wsi->told_user_closed && |
| wsi->role_ops->close_cb[lwsi_role_server(wsi)]) { |
| if (!wsi->upgraded_to_http2 || !lwsi_role_client(wsi)) |
| ccb = 1; |
| /* |
| * The network wsi for a client h2 connection shouldn't |
| * call back for its role: the child stream connections |
| * own the role. Otherwise h2 will call back closed |
| * one too many times as the children do it and then |
| * the closing network stream. |
| */ |
| } |
| |
| if (!wsi->told_user_closed && |
| !lws_dll2_is_detached(&wsi->vh_awaiting_socket)) |
| /* |
| * He's a guy who go started with dns, but failed or is |
| * caught with a shutdown before he got the result. We have |
| * to issue him a close cb |
| */ |
| ccb = 1; |
| |
| pro = wsi->protocol; |
| |
| #if defined(LWS_WITH_CLIENT) |
| if (!ccb && (lwsi_state_PRE_CLOSE(wsi) & LWSIFS_NOT_EST) && |
| lwsi_role_client(wsi)) { |
| lws_inform_client_conn_fail(wsi, "Closed before conn", 18); |
| } |
| #endif |
| if (ccb) { |
| |
| if (!wsi->protocol && wsi->vhost && wsi->vhost->protocols) |
| pro = &wsi->vhost->protocols[0]; |
| |
| if (pro) |
| pro->callback(wsi, |
| wsi->role_ops->close_cb[lwsi_role_server(wsi)], |
| wsi->user_space, NULL, 0); |
| wsi->told_user_closed = 1; |
| } |
| |
| #if defined(LWS_ROLE_RAW_FILE) |
| async_close: |
| #endif |
| lws_remove_child_from_any_parent(wsi); |
| wsi->socket_is_permanently_unusable = 1; |
| |
| if (wsi->context->event_loop_ops->wsi_logical_close) |
| if (wsi->context->event_loop_ops->wsi_logical_close(wsi)) |
| return; |
| |
| __lws_close_free_wsi_final(wsi); |
| } |
| |
| void |
| __lws_close_free_wsi_final(struct lws *wsi) |
| { |
| int n; |
| |
| if (!wsi->shadow && |
| lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) { |
| lwsl_debug("%s: wsi %p: fd %d\n", __func__, wsi, wsi->desc.sockfd); |
| n = compatible_close(wsi->desc.sockfd); |
| if (n) |
| lwsl_debug("closing: close ret %d\n", LWS_ERRNO); |
| |
| wsi->desc.sockfd = LWS_SOCK_INVALID; |
| } |
| |
| /* outermost destroy notification for wsi (user_space still intact) */ |
| if (wsi->vhost) |
| wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, |
| wsi->user_space, NULL, 0); |
| |
| #ifdef LWS_WITH_CGI |
| if (wsi->http.cgi) { |
| lws_spawn_piped_destroy(&wsi->http.cgi->lsp); |
| lws_free_set_NULL(wsi->http.cgi); |
| } |
| #endif |
| |
| __lws_free_wsi(wsi); |
| } |
| |
| |
| void |
| lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *caller) |
| { |
| struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; |
| |
| lws_pt_lock(pt, __func__); |
| __lws_close_free_wsi(wsi, reason, caller); |
| lws_pt_unlock(pt); |
| } |
| |
| |