diff --git a/include/maxscale/session.h b/include/maxscale/session.h index d86c063e5..1e512bb3a 100644 --- a/include/maxscale/session.h +++ b/include/maxscale/session.h @@ -143,6 +143,17 @@ typedef struct mxs_upstream UPSTREAMFUNC clientReply; } MXS_UPSTREAM; +/* Specific reasons why a session was closed */ +typedef enum +{ + SESSION_CLOSE_NONE = 0, // No special reason + SESSION_CLOSE_TIMEOUT, // Connection timed out + SESSION_CLOSE_HANDLEERROR_FAILED, // Router returned an error from handleError + SESSION_CLOSE_ROUTING_FAILED, // Router closed DCB + SESSION_CLOSE_KILLED, // Killed by another connection + SESSION_CLOSE_TOO_MANY_CONNECTIONS, // Too many connections +} session_close_t; + /** * Handler function for MaxScale specific session variables. * @@ -215,6 +226,7 @@ typedef struct session } response; /*< Shortcircuited response */ SessionStmtQueue* last_statements; /*< The N last statements by the client */ DCBSet* dcb_set; /*< Set of associated backend DCBs */ + session_close_t close_reason; /*< Reason why the session was closed */ skygw_chk_t ses_chk_tail; } MXS_SESSION; @@ -652,4 +664,14 @@ bool session_delay_routing(MXS_SESSION* session, MXS_DOWNSTREAM down, GWBUF* buf */ MXS_DOWNSTREAM router_as_downstream(MXS_SESSION* session); +/** + * Get the reason why a session was closed + * + * @param session Session to inspect + * + * @return String representation of the reason why the session was closed. If + * the session was closed normally, an empty string is returned. + */ +const char* session_get_close_reason(const MXS_SESSION* session); + MXS_END_DECLS diff --git a/server/core/dcb.cc b/server/core/dcb.cc index 6c567f4c9..b8caa5870 100644 --- a/server/core/dcb.cc +++ b/server/core/dcb.cc @@ -2479,6 +2479,7 @@ dcb_accept(DCB *dcb) { client_dcb->func.connlimit(client_dcb, client_dcb->service->max_connections); } + dcb->session->close_reason = SESSION_CLOSE_TOO_MANY_CONNECTIONS; dcb_close(client_dcb); client_dcb = NULL; } @@ -2887,6 +2888,7 @@ void dcb_process_idle_sessions(int thr) dcb->user ? dcb->user : "", dcb->remote ? dcb->remote : "", (float)idle / 10.f); + dcb->session->close_reason = SESSION_CLOSE_TIMEOUT; poll_fake_hangup_event(dcb); } } diff --git a/server/core/session.cc b/server/core/session.cc index fbb46fb65..09631fcd5 100644 --- a/server/core/session.cc +++ b/server/core/session.cc @@ -130,6 +130,7 @@ static MXS_SESSION* session_alloc_body(SERVICE* service, DCB* client_dcb, session->client_dcb = client_dcb; session->stats.connect = time(0); session->qualifies_for_pooling = false; + session->close_reason = SESSION_CLOSE_NONE; MXS_CONFIG *config = config_get_global_options(); // If MaxScale is running in Oracle mode, then autocommit needs to @@ -1434,3 +1435,31 @@ MXS_DOWNSTREAM router_as_downstream(MXS_SESSION* session) head.routeQuery = (DOWNSTREAMFUNC)session->service->router->routeQuery; return head; } + +const char* session_get_close_reason(const MXS_SESSION* session) +{ + switch (session->close_reason) + { + case SESSION_CLOSE_NONE: + return ""; + + case SESSION_CLOSE_TIMEOUT: + return "Timed out by MaxScale"; + + case SESSION_CLOSE_HANDLEERROR_FAILED: + return "Router could not recover from connection errors"; + + case SESSION_CLOSE_ROUTING_FAILED: + return "Router could not route query"; + + case SESSION_CLOSE_KILLED: + return "Killed by another connection"; + + case SESSION_CLOSE_TOO_MANY_CONNECTIONS: + return "Too many connections"; + + default: + ss_dassert(!true); + return "Internal error"; + } +} diff --git a/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.cc b/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.cc index a35e3601c..3ec42c1a9 100644 --- a/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.cc +++ b/server/modules/protocol/MySQL/mariadbbackend/mysql_backend.cc @@ -601,6 +601,7 @@ static void do_handle_error(DCB *dcb, mxs_error_action_t action, const char *err */ if (!succp) { + session->close_reason = SESSION_CLOSE_HANDLEERROR_FAILED; poll_fake_hangup_event(session->client_dcb); } } diff --git a/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc b/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc index 6c34f20e5..34c4a4c4d 100644 --- a/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc +++ b/server/modules/protocol/MySQL/mariadbclient/mysql_client.cc @@ -1193,12 +1193,14 @@ gw_read_finish_processing(DCB *dcb, GWBUF *read_buffer, uint64_t capabilities) if (return_code != 0) { /** Routing failed, close the client connection */ + dcb->session->close_reason = SESSION_CLOSE_ROUTING_FAILED; dcb_close(dcb); MXS_ERROR("Routing the query failed. Session will be closed."); } else if (proto->current_command == MXS_COM_QUIT) { /** Close router session which causes closing of backends */ + ss_info_dassert(session_valid_for_pool(dcb->session), "Session should qualify for pooling"); dcb_close(dcb); } @@ -1527,7 +1529,15 @@ static int gw_client_hangup_event(DCB *dcb) } // The client did not send a COM_QUIT packet - modutil_send_mysql_err_packet(dcb, 0, 0, 1927, "08S01", "Connection killed by MaxScale"); + std::string errmsg{"Connection killed by MaxScale"}; + std::string extra{session_get_close_reason(dcb->session)}; + + if (!extra.empty()) + { + errmsg += ": " + extra; + } + + modutil_send_mysql_err_packet(dcb, 0, 0, 1927, "08S01", errmsg.c_str()); } dcb_close(dcb); } diff --git a/server/modules/protocol/MySQL/mysql_common.cc b/server/modules/protocol/MySQL/mysql_common.cc index 851bfdfd9..a8e85dd26 100644 --- a/server/modules/protocol/MySQL/mysql_common.cc +++ b/server/modules/protocol/MySQL/mysql_common.cc @@ -1726,6 +1726,7 @@ static bool kill_func(DCB *dcb, void *data) else { // DCB is not yet connected, send a hangup to forcibly close it + dcb->session->close_reason = SESSION_CLOSE_KILLED; poll_fake_hangup_event(*it); } }