haproxy/include/haproxy/session.h
Willy Tarreau 83efc320aa MEDIUM: listener: allocate the connection before queuing a new connection
Till now we would keep a per-thread queue of pending incoming connections
for which we would store:
  - the listener
  - the accepted FD
  - the source address
  - the source address' length

And these elements were first used in session_accept_fd() running on the
target thread to allocate a connection and duplicate them again. Doing
this induces various problems. The first one is that session_accept_fd()
may only run on file descriptors and cannot be reused for QUIC. The second
issue is that it induces lots of memory copies and that the listerner
queue thrashes a lot of cache, consuming 64 bytes per entry.

This patch changes this by allocating the connection before queueing it,
and by only placing the connection's pointer into the queue. Indeed, the
first two calls used to initialize the connection already store all the
information above, which can be retrieved from the connection pointer
alone. So we just have to pop one pointer from the target thread, and
pass it to session_accept_fd() which only needs the FD for the final
settings.

This starts to make the accept path a bit more transport-agnostic, and
saves memory and CPU cycles at the same time (1% connection rate increase
was noticed with 4 threads). Thanks to dividing the accept-queue entry
size from 64 to 8 bytes, its size could be increased from 256 to 1024
connections while still dividing the overall size by two. No single
queue full condition was met.

One minor drawback is that connection may be allocated from one thread's
pool to be used into another one. But this already happens a lot with
connection reuse so there is really nothing new here.
2020-10-15 21:47:56 +02:00

186 lines
5.4 KiB
C

/*
* include/haproxy/session.h
* This file contains functions used to manage sessions.
*
* Copyright (C) 2000-2020 Willy Tarreau - w@1wt.eu
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, version 2.1
* exclusively.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _HAPROXY_SESSION_H
#define _HAPROXY_SESSION_H
#include <haproxy/api.h>
#include <haproxy/global-t.h>
#include <haproxy/obj_type-t.h>
#include <haproxy/pool.h>
#include <haproxy/server.h>
#include <haproxy/session-t.h>
#include <haproxy/stick_table.h>
extern struct pool_head *pool_head_session;
extern struct pool_head *pool_head_sess_srv_list;
struct session *session_new(struct proxy *fe, struct listener *li, enum obj_type *origin);
void session_free(struct session *sess);
int session_accept_fd(struct connection *cli_conn);
int conn_complete_session(struct connection *conn);
/* Remove the refcount from the session to the tracked counters, and clear the
* pointer to ensure this is only performed once. The caller is responsible for
* ensuring that the pointer is valid first.
*/
static inline void session_store_counters(struct session *sess)
{
void *ptr;
int i;
struct stksess *ts;
for (i = 0; i < MAX_SESS_STKCTR; i++) {
struct stkctr *stkctr = &sess->stkctr[i];
ts = stkctr_entry(stkctr);
if (!ts)
continue;
ptr = stktable_data_ptr(stkctr->table, ts, STKTABLE_DT_CONN_CUR);
if (ptr) {
HA_RWLOCK_WRLOCK(STK_SESS_LOCK, &ts->lock);
if (stktable_data_cast(ptr, conn_cur) > 0)
stktable_data_cast(ptr, conn_cur)--;
HA_RWLOCK_WRUNLOCK(STK_SESS_LOCK, &ts->lock);
/* If data was modified, we need to touch to re-schedule sync */
stktable_touch_local(stkctr->table, ts, 0);
}
stkctr_set_entry(stkctr, NULL);
stksess_kill_if_expired(stkctr->table, ts, 1);
}
}
/* Remove the connection from the session list, and destroy the srv_list if it's now empty */
static inline void session_unown_conn(struct session *sess, struct connection *conn)
{
struct sess_srv_list *srv_list = NULL;
if (conn->flags & CO_FL_SESS_IDLE)
sess->idle_conns--;
LIST_DEL_INIT(&conn->session_list);
list_for_each_entry(srv_list, &sess->srv_list, srv_list) {
if (srv_list->target == conn->target) {
if (LIST_ISEMPTY(&srv_list->conn_list)) {
LIST_DEL(&srv_list->srv_list);
pool_free(pool_head_sess_srv_list, srv_list);
}
break;
}
}
}
/* Add the connection <conn> to the server list of the session <sess>. This
* function is called only if the connection is private. Nothing is performed if
* the connection is already in the session sever list or if the session does
* not own the connection.
*/
static inline int session_add_conn(struct session *sess, struct connection *conn, void *target)
{
struct sess_srv_list *srv_list = NULL;
int found = 0;
/* Already attach to the session or not the connection owner */
if (!LIST_ISEMPTY(&conn->session_list) || conn->owner != sess)
return 1;
list_for_each_entry(srv_list, &sess->srv_list, srv_list) {
if (srv_list->target == target) {
found = 1;
break;
}
}
if (!found) {
/* The session has no connection for the server, create a new entry */
srv_list = pool_alloc(pool_head_sess_srv_list);
if (!srv_list)
return 0;
srv_list->target = target;
LIST_INIT(&srv_list->conn_list);
LIST_ADDQ(&sess->srv_list, &srv_list->srv_list);
}
LIST_ADDQ(&srv_list->conn_list, &conn->session_list);
return 1;
}
/* Returns 0 if the session can keep the idle conn, -1 if it was destroyed. The
* connection must be private.
*/
static inline int session_check_idle_conn(struct session *sess, struct connection *conn)
{
/* Another session owns this connection */
if (conn->owner != sess)
return 0;
if (sess->idle_conns >= sess->fe->max_out_conns) {
session_unown_conn(sess, conn);
conn->owner = NULL;
conn->flags &= ~CO_FL_SESS_IDLE;
conn->mux->destroy(conn->ctx);
return -1;
} else {
conn->flags |= CO_FL_SESS_IDLE;
sess->idle_conns++;
}
return 0;
}
/* Look for an available connection matching the target <target> in the server
* list of the session <sess>. It returns a connection if found. Otherwise it
* returns NULL.
*/
static inline struct connection *session_get_conn(struct session *sess, void *target)
{
struct connection *srv_conn = NULL;
struct sess_srv_list *srv_list;
list_for_each_entry(srv_list, &sess->srv_list, srv_list) {
if (srv_list->target == target) {
list_for_each_entry(srv_conn, &srv_list->conn_list, session_list) {
if (srv_conn->mux && (srv_conn->mux->avail_streams(srv_conn) > 0)) {
if (srv_conn->flags & CO_FL_SESS_IDLE) {
srv_conn->flags &= ~CO_FL_SESS_IDLE;
sess->idle_conns--;
}
goto end;
}
}
srv_conn = NULL; /* No available connection found */
goto end;
}
}
end:
return srv_conn;
}
#endif /* _HAPROXY_SESSION_H */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/