MaxScale/server/core/session.c
VilhoRaatikka 6b3c7041e3 Bug #468, http://bugs.skysql.com/show_bug.cgi?id=468, Query classifier accessed freed thread context. If parsing fails thd doesn't need to be freed because it holds correct information about command type.
session.c:session_setup_filters : fixed memory leak
hintparser.c: added token_free for HINT_TOKENs and fixed a few memory leaks.
mysql_client_server_protocol.h: added mysql_protocol_done which frees memory blocks pointed to by protocol members. Those can't be freed in dcb.c because dcb.c doesn't know about protocol's members.
mysql_backend.c:gw_backend_close: fixed memory leak
mysql_client.c: gw_client_close: fixed memory leak
mysql_common.c: added implementation of mysql_protocol_done
	:protocol_archive_srv_command: tried to fix memory leak. Some memory is still leaking according to valgrind. Removed use of uninitialized local variable len.
readwritesplit.c:execute_sescmd_in_backend: fixed a memory leak - visible only in DEBUG=Y build.
2014-08-05 09:31:10 +03:00

760 lines
20 KiB
C

/*
* This file is distributed as part of the SkySQL Gateway. It is free
* software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation,
* version 2.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright SkySQL Ab 2013
*/
/**
* @file session.c - A representation of the session within the gateway.
*
* @verbatim
* Revision History
*
* Date Who Description
* 17/06/13 Mark Riddoch Initial implementation
* 02/09/13 Massimiliano Pinto Added session refcounter
* 29/05/14 Mark Riddoch Addition of filter mechanism
*
* @endverbatim
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <session.h>
#include <service.h>
#include <router.h>
#include <dcb.h>
#include <spinlock.h>
#include <atomic.h>
#include <skygw_utils.h>
#include <log_manager.h>
extern int lm_enabled_logfiles_bitmask;
static SPINLOCK session_spin = SPINLOCK_INIT;
static SESSION *allSessions = NULL;
static int session_setup_filters(SESSION *session);
/**
* Allocate a new session for a new client of the specified service.
*
* Create the link to the router session by calling the newSession
* entry point of the router using the router instance of the
* service this session is part of.
*
* @param service The service this connection was established by
* @param client_dcb The client side DCB
* @return The newly created session or NULL if an error occured
*/
SESSION *
session_alloc(SERVICE *service, DCB *client_dcb)
{
SESSION *session;
session = (SESSION *)calloc(1, sizeof(SESSION));
ss_info_dassert(session != NULL,
"Allocating memory for session failed.");
if (session == NULL) {
int eno = errno;
errno = 0;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Failed to allocate memory for "
"session object due error %d, %s.",
eno,
strerror(eno))));
goto return_session;
}
#if defined(SS_DEBUG)
session->ses_chk_top = CHK_NUM_SESSION;
session->ses_chk_tail = CHK_NUM_SESSION;
#endif
spinlock_init(&session->ses_lock);
/*<
* Prevent backend threads from accessing before session is completely
* initialized.
*/
spinlock_acquire(&session->ses_lock);
session->service = service;
session->client = client_dcb;
session->n_filters = 0;
memset(&session->stats, 0, sizeof(SESSION_STATS));
session->stats.connect = time(0);
session->state = SESSION_STATE_ALLOC;
/*<
* Associate the session to the client DCB and set the reference count on
* the session to indicate that there is a single reference to the
* session. There is no need to protect this or use atomic add as the
* session has not been made available to the other threads at this
* point.
*/
session->data = client_dcb->data;
client_dcb->session = session;
session->refcount = 1;
/*<
* This indicates that session is ready to be shared with backend
* DCBs. Note that this doesn't mean that router is initialized yet!
*/
session->state = SESSION_STATE_READY;
/*< Release session lock */
spinlock_release(&session->ses_lock);
/*
* Only create a router session if we are not the listening
* DCB or an internal DCB. Creating a router session may create a connection to a
* backend server, depending upon the router module implementation
* and should be avoided for the listener session
*
* Router session creation may create other DCBs that link to the
* session, therefore it is important that the session lock is
* relinquished beforethe router call.
*/
if (client_dcb->state != DCB_STATE_LISTENING &&
client_dcb->dcb_role != DCB_ROLE_INTERNAL)
{
session->router_session =
service->router->newSession(service->router_instance,
session);
if (session->router_session == NULL) {
/**
* Inform other threads that session is closing.
*/
session->state = SESSION_STATE_STOPPING;
/*<
* Decrease refcount, set dcb's session pointer NULL
* and set session pointer to NULL.
*/
session_free(session);
client_dcb->session = NULL;
session = NULL;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Failed to create %s session.",
service->name)));
goto return_session;
}
/*
* Pending filter chain being setup set the head of the chain to
* be the router. As filters are inserted the current head will
* be pushed to the filter and the head updated.
*
* NB This dictates that filters are created starting at the end
* of the chain nearest the router working back to the client
* protocol end of the chain.
*/
session->head.instance = service->router_instance;
session->head.session = session->router_session;
session->head.routeQuery = (void *)(service->router->routeQuery);
session->tail.instance = session;
session->tail.session = session;
session->tail.clientReply = session_reply;
if (service->n_filters > 0)
{
if (!session_setup_filters(session))
{
/**
* Inform other threads that session is closing.
*/
session->state = SESSION_STATE_STOPPING;
/*<
* Decrease refcount, set dcb's session pointer NULL
* and set session pointer to NULL.
*/
session_free(session);
client_dcb->session = NULL;
session = NULL;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Failed to create %s session.",
service->name)));
goto return_session;
}
}
}
spinlock_acquire(&session_spin);
if (session->state != SESSION_STATE_READY)
{
session_free(session);
client_dcb->session = NULL;
session = NULL;
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Error : Failed to create %s session.",
service->name)));
spinlock_release(&session_spin);
}
else
{
session->state = SESSION_STATE_ROUTER_READY;
session->next = allSessions;
allSessions = session;
spinlock_release(&session_spin);
atomic_add(&service->stats.n_sessions, 1);
atomic_add(&service->stats.n_current, 1);
CHK_SESSION(session);
}
return_session:
return session;
}
/**
* Link a session to a DCB.
*
* @param session The session to link with the dcb
* @param dcb The DCB to be linked
* @return True if the session was sucessfully linked to the DCB
*/
bool
session_link_dcb(SESSION *session, DCB *dcb)
{
spinlock_acquire(&session->ses_lock);
ss_info_dassert(session->state != SESSION_STATE_FREE,
"If session->state is SESSION_STATE_FREE then this attempt to "
"access freed memory block.");
if (session->state == SESSION_STATE_FREE)
{
spinlock_release(&session->ses_lock);
return false;
}
atomic_add(&session->refcount, 1);
dcb->session = session;
spinlock_release(&session->ses_lock);
return true;
}
int session_unlink_dcb(
SESSION* session,
DCB* dcb)
{
int nlink;
CHK_SESSION(session);
spinlock_acquire(&session->ses_lock);
ss_dassert(session->refcount > 0);
/*<
* Remove dcb from session's router_client_session.
*/
nlink = atomic_add(&session->refcount, -1);
nlink -= 1;
if (nlink == 0)
{
session->state = SESSION_STATE_FREE;
}
if (dcb != NULL)
{
dcb->session = NULL;
}
spinlock_release(&session->ses_lock);
return nlink;
}
/**
* Deallocate the specified session
*
* @param session The session to deallocate
*/
bool session_free(
SESSION *session)
{
bool succp = false;
SESSION *ptr;
int nlink;
int i;
CHK_SESSION(session);
/*<
* Remove one reference. If there are no references left,
* free session.
*/
nlink = session_unlink_dcb(session, NULL);
if (nlink != 0) {
ss_dassert(nlink > 0);
goto return_succp;
}
/* First of all remove from the linked list */
spinlock_acquire(&session_spin);
if (allSessions == session)
{
allSessions = session->next;
}
else
{
ptr = allSessions;
while (ptr && ptr->next != session)
{
ptr = ptr->next;
}
if (ptr)
ptr->next = session->next;
}
spinlock_release(&session_spin);
atomic_add(&session->service->stats.n_current, -1);
/* Free router_session and session */
if (session->router_session) {
session->service->router->freeSession(
session->service->router_instance,
session->router_session);
}
if (session->n_filters)
{
for (i = 0; i < session->n_filters; i++)
{
session->filters[i].filter->obj->closeSession(
session->filters[i].instance,
session->filters[i].session);
}
for (i = 0; i < session->n_filters; i++)
{
session->filters[i].filter->obj->freeSession(
session->filters[i].instance,
session->filters[i].session);
}
free(session->filters);
}
free(session);
succp = true;
return_succp :
return succp;
}
/**
* Check to see if a session is valid, i.e. in the list of all sessions
*
* @param session Session to check
* @return 1 if the session is valid otherwise 0
*/
int
session_isvalid(SESSION *session)
{
SESSION *ptr;
int rval = 0;
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
{
if (ptr == session)
{
rval = 1;
break;
}
ptr = ptr->next;
}
spinlock_release(&session_spin);
return rval;
}
/**
* Print details of an individual session
*
* @param session Session to print
*/
void
printSession(SESSION *session)
{
printf("Session %p\n", session);
printf("\tState: %s\n", session_state(session->state));
printf("\tService: %s (%p)\n", session->service->name, session->service);
printf("\tClient DCB: %p\n", session->client);
printf("\tConnected: %s", asctime(localtime(&session->stats.connect)));
}
/**
* Print all sessions
*
* Designed to be called within a debugger session in order
* to display all active sessions within the gateway
*/
void
printAllSessions()
{
SESSION *ptr;
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
{
printSession(ptr);
ptr = ptr->next;
}
spinlock_release(&session_spin);
}
/**
* Check sessions
*
* Designed to be called within a debugger session in order
* to display information regarding "interesting" sessions
*/
void
CheckSessions()
{
SESSION *ptr;
int noclients = 0;
int norouter = 0;
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
{
if (ptr->state != SESSION_STATE_LISTENER ||
ptr->state != SESSION_STATE_LISTENER_STOPPED)
{
if (ptr->client == NULL && ptr->refcount)
{
if (noclients == 0)
{
printf("Sessions without a client DCB.\n");
printf("==============================\n");
}
printSession(ptr);
noclients++;
}
}
ptr = ptr->next;
}
spinlock_release(&session_spin);
if (noclients)
printf("%d Sessions have no clients\n", noclients);
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
{
if (ptr->state != SESSION_STATE_LISTENER ||
ptr->state != SESSION_STATE_LISTENER_STOPPED)
{
if (ptr->router_session == NULL && ptr->refcount)
{
if (norouter == 0)
{
printf("Sessions without a router session.\n");
printf("==================================\n");
}
printSession(ptr);
norouter++;
}
}
ptr = ptr->next;
}
spinlock_release(&session_spin);
if (norouter)
printf("%d Sessions have no router session\n", norouter);
}
/**
* Print all sessions to a DCB
*
* Designed to be called within a debugger session in order
* to display all active sessions within the gateway
*
* @param dcb The DCB to print to
*/
void
dprintAllSessions(DCB *dcb)
{
SESSION *ptr;
spinlock_acquire(&session_spin);
ptr = allSessions;
while (ptr)
{
dcb_printf(dcb, "Session %p\n", ptr);
dcb_printf(dcb, "\tState: %s\n", session_state(ptr->state));
dcb_printf(dcb, "\tService: %s (%p)\n", ptr->service->name, ptr->service);
dcb_printf(dcb, "\tClient DCB: %p\n", ptr->client);
if (ptr->client && ptr->client->remote)
dcb_printf(dcb, "\tClient Address: %s\n", ptr->client->remote);
dcb_printf(dcb, "\tConnected: %s", asctime(localtime(&ptr->stats.connect)));
ptr = ptr->next;
}
spinlock_release(&session_spin);
}
/**
* Print a particular session to a DCB
*
* Designed to be called within a debugger session in order
* to display all active sessions within the gateway
*
* @param dcb The DCB to print to
* @param ptr The session to print
*/
void
dprintSession(DCB *dcb, SESSION *ptr)
{
int i;
dcb_printf(dcb, "Session %p\n", ptr);
dcb_printf(dcb, "\tState: %s\n", session_state(ptr->state));
dcb_printf(dcb, "\tService: %s (%p)\n", ptr->service->name, ptr->service);
dcb_printf(dcb, "\tClient DCB: %p\n", ptr->client);
if (ptr->client && ptr->client->remote)
dcb_printf(dcb, "\tClient Address: %s\n", ptr->client->remote);
dcb_printf(dcb, "\tConnected: %s", asctime(localtime(&ptr->stats.connect)));
if (ptr->n_filters)
{
for (i = 0; i < ptr->n_filters; i++)
{
dcb_printf(dcb, "\tFilter: %s\n",
ptr->filters[i].filter->name);
ptr->filters[i].filter->obj->diagnostics(
ptr->filters[i].instance,
ptr->filters[i].session,
dcb);
}
}
}
/**
* List all sessions in tabular form to a DCB
*
* Designed to be called within a debugger session in order
* to display all active sessions within the gateway
*
* @param dcb The DCB to print to
*/
void
dListSessions(DCB *dcb)
{
SESSION *ptr;
spinlock_acquire(&session_spin);
ptr = allSessions;
if (ptr)
{
dcb_printf(dcb, "Sessions.\n");
dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n");
dcb_printf(dcb, "Session | Client | Service | State\n");
dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n");
}
while (ptr)
{
dcb_printf(dcb, "%-16p | %-15s | %-14s | %s\n", ptr,
((ptr->client && ptr->client->remote)
? ptr->client->remote : ""),
(ptr->service && ptr->service->name ? ptr->service->name
: ""),
session_state(ptr->state));
ptr = ptr->next;
}
if (allSessions)
dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n\n");
spinlock_release(&session_spin);
}
/**
* Convert a session state to a string representation
*
* @param state The session state
* @return A string representation of the session state
*/
char *
session_state(int state)
{
switch (state)
{
case SESSION_STATE_ALLOC:
return "Session Allocated";
case SESSION_STATE_READY:
return "Session Ready";
case SESSION_STATE_ROUTER_READY:
return "Session ready for routing";
case SESSION_STATE_LISTENER:
return "Listener Session";
case SESSION_STATE_LISTENER_STOPPED:
return "Stopped Listener Session";
default:
return "Invalid State";
}
}
SESSION* get_session_by_router_ses(
void* rses)
{
SESSION* ses = allSessions;
while (ses->router_session != rses && ses->next != NULL)
ses = ses->next;
if (ses->router_session != rses)
{
ses = NULL;
}
return ses;
}
/**
* Create the filter chain for this session.
*
* Filters must be setup in reverse order, starting with the last
* filter in the chain and working back towards the client connection
* Each filter is passed the current session head of the filter chain
* this head becomes the destination for the filter. The newly created
* filter becomes the new head of the filter chain.
*
* @param session The session that requires the chain
* @return 0 if filter creation fails
*/
static int
session_setup_filters(SESSION *session)
{
SERVICE *service = session->service;
DOWNSTREAM *head;
UPSTREAM *tail;
int i;
if ((session->filters = calloc(service->n_filters,
sizeof(SESSION_FILTER))) == NULL)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Insufficient memory to allocate session filter "
"tracking.\n")));
return 0;
}
session->n_filters = service->n_filters;
for (i = service->n_filters - 1; i >= 0; i--)
{
if ((head = filterApply(service->filters[i], session,
&session->head)) == NULL)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Failed to create filter '%s' for service '%s'.\n",
service->filters[i]->name,
service->name)));
return 0;
}
session->filters[i].filter = service->filters[i];
session->filters[i].session = head->session;
session->filters[i].instance = head->instance;
session->head = *head;
free(head);
}
for (i = 0; i < service->n_filters; i++)
{
if ((tail = filterUpstream(service->filters[i],
session->filters[i].session,
&session->tail)) == NULL)
{
LOGIF(LE, (skygw_log_write_flush(
LOGFILE_ERROR,
"Failed to create filter '%s' for service '%s'.\n",
service->filters[i]->name,
service->name)));
return 0;
}
session->tail = *tail;
}
return 1;
}
/**
* Entry point for the final element int he upstream filter, i.e. the writing
* of the data to the client.
*
* @param instance The "instance" data
* @param session The session
* @param data The buffer chain to write
*/
int
session_reply(void *instance, void *session, GWBUF *data)
{
SESSION *the_session = (SESSION *)session;
return the_session->client->func.write(the_session->client, data);
}
/**
* Return the client connection address or name
*
* @param session The session whose client address to return
*/
char *
session_get_remote(SESSION *session)
{
if (session && session->client)
return session->client->remote;
return NULL;
}
bool session_route_query (
SESSION* ses,
GWBUF* buf)
{
bool succp;
if (ses->head.routeQuery == NULL ||
ses->head.instance == NULL ||
ses->head.session == NULL)
{
succp = false;
goto return_succp;
}
if (ses->head.routeQuery(ses->head.instance, ses->head.session, buf) == 1)
{
succp = true;
}
else
{
succp = false;
}
return_succp:
return succp;
}
/**
* Return the username of the user connected to the client side of the
* session.
*
* @param session The session pointer.
* @return The user name or NULL if it can not be determined.
*/
char *
session_getUser(SESSION *session)
{
return (session && session->client) ? session->client->user : NULL;
}