Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 73 additions & 30 deletions fw/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,13 @@ static TDB *client_db;
static atomic_t shutdown_pending = ATOMIC_INIT(0);
static DECLARE_WAIT_QUEUE_HEAD(shutdown_wq);

static struct kmem_cache *cli_mem_cache;
static struct kmem_cache *tfw_cli_mem_cache;
static struct {
TfwClientMem *mem;
struct list_head free_list;
TfwClientMem *free_list;
unsigned int size;
unsigned int order;
} cli_mem_pool = {
.mem = NULL,
.free_list = LIST_HEAD_INIT(cli_mem_pool.free_list),
.size = 0,
.order = 0,
};
} cli_mem_pool;

static inline bool
tfw_cli_mem_belongs_to_pool(TfwClientMem *cli_mem)
Expand All @@ -97,9 +92,13 @@ __cli_mem_release(TfwClientMem *cli_mem)
percpu_ref_exit(&cli_mem->refcnt);
free_percpu(cli_mem->mem);
if (!tfw_cli_mem_belongs_to_pool(cli_mem))
kmem_cache_free(cli_mem_cache, cli_mem);
kmem_cache_free(tfw_cli_mem_cache, cli_mem);
}

/*
* Reset counters, reinit refcnt and put `cli_mem` back to the pool.
* Sohuld be called under `ga_lock`, to protect `cli_mem_pool.free_list`
*/
static inline void
tfw_cli_mem_pool_free(TfwClientMem *cli_mem)
{
Expand All @@ -110,26 +109,35 @@ tfw_cli_mem_pool_free(TfwClientMem *cli_mem)
for_each_online_cpu(cpu)
*per_cpu_ptr(cli_mem->mem, cpu) = 0;
percpu_ref_reinit(&cli_mem->refcnt);
list_add_tail(&cli_mem->in_free_list, &cli_mem_pool.free_list);
cli_mem->next_free = cli_mem_pool.free_list;
cli_mem_pool.free_list = cli_mem;
}

/*
* Get `TfwClientMem` object from pool if present.
* Object was already initialized during pool creation or
* releasing to pool.
*/
static inline TfwClientMem *
tfw_cli_mem_pool_alloc(void)
{
TfwClientMem *cli_mem;

assert_spin_locked(&client_db->ga_lock);

cli_mem = list_first_entry_or_null(&cli_mem_pool.free_list,
TfwClientMem, in_free_list);
if (!cli_mem)
if (!cli_mem_pool.free_list)
return NULL;

list_del_init(&cli_mem->in_free_list);
cli_mem = cli_mem_pool.free_list;
cli_mem_pool.free_list = cli_mem->next_free;

return cli_mem;
}

/*
* Final release of cli_mem: verify refcnt/memory are zero and either
* return to pool or free it. Signals shutdown completion if needed.
*/
static void
cli_mem_release(struct percpu_ref *ref)
{
Expand All @@ -138,6 +146,7 @@ cli_mem_release(struct percpu_ref *ref)
spin_lock_bh(&client_db->ga_lock);

WARN_ON_ONCE(!percpu_ref_is_zero(ref));
WARN_ON_ONCE(tfw_client_mem(cli_mem));
if (tfw_cli_mem_belongs_to_pool(cli_mem))
tfw_cli_mem_pool_free(cli_mem);
else
Expand All @@ -149,6 +158,16 @@ cli_mem_release(struct percpu_ref *ref)
wake_up(&shutdown_wq);
}

/*
* Workqueue handler for asynchronous cli_mem destruction.
*
* This function initiates final teardown of a TfwClientMem object:
* - percpu_ref_kill() marks the refcount as dead, preventing any new
* users from acquiring references.
* - percpu_ref_put() drops the caller’s reference, which may trigger
* final release via cli_mem_release() once all outstanding users
* are gone.
*/
static void
tfw_cli_mem_kill_work_fn(struct work_struct *work)
{
Expand All @@ -172,7 +191,7 @@ tfw_cli_mem_init(TfwClientMem *cli_mem, gfp_t flags)
if (unlikely(r))
goto free_per_cpu_mem;

INIT_LIST_HEAD(&cli_mem->in_free_list);
cli_mem->next_free = NULL;
INIT_WORK(&cli_mem->kill_work, tfw_cli_mem_kill_work_fn);

return 0;
Expand All @@ -186,19 +205,33 @@ tfw_cli_mem_init(TfwClientMem *cli_mem, gfp_t flags)
static inline void
tfw_cli_mem_pool_exit(void)
{
TfwClientMem *curr, *tmp;
TfwClientMem *tmp, *curr = cli_mem_pool.free_list;

list_for_each_entry_safe(curr, tmp, &cli_mem_pool.free_list,
in_free_list)
{
list_del_init(&curr->in_free_list);
__cli_mem_release(curr);
while (curr) {
tmp = curr;
curr = tmp->next_free;
__cli_mem_release(tmp);
}

free_pages((unsigned long)cli_mem_pool.mem, cli_mem_pool.order);
cli_mem_pool.mem = NULL;
}

/*
* Initialize cli_mem pool.
*
* Allocates a contiguous block of TfwClientMem objects and initializes each
* element, then builds a free list for fast allocation.
*
* Steps:
* - Validate pool size from configuration.
* - Compute allocation order and clamp it to MAX_PAGE_ORDER.
* - Allocate zeroed pages for the entire pool.
* - Initialize each TfwClientMem (per-cpu counters + refcnt + work).
* - Link all objects into a singly-linked free list.
*
* Provide fast allocations of `TfwClientMem` later.
*/
static inline int
tfw_cli_mem_pool_init(void)
{
Expand All @@ -220,11 +253,12 @@ tfw_cli_mem_pool_init(void)
return -ENOMEM;

block = cli_mem_pool.mem;
for (i = 0; i < client_cfg.lru_size; i++) {
for (i = client_cfg.lru_size - 1; i >= 0; i--) {
r = tfw_cli_mem_init(&block[i], GFP_KERNEL);
if (unlikely(r))
return r;
list_add(&block[i].in_free_list, &cli_mem_pool.free_list);
block[i].next_free = cli_mem_pool.free_list;
cli_mem_pool.free_list = &block[i];
cli_mem_pool.size++;
}

Expand Down Expand Up @@ -350,12 +384,16 @@ tfw_client_addr_eq(TdbRec *rec, void *data)
return true;
}

/*
* Allocate cli_mem from slab cache and fully initialize it.
* Used as a fallback when pool allocation is exhausted.
*/
static inline TfwClientMem *
tfw_cli_mem_alloc_from_cache(void)
{
TfwClientMem *cli_mem;

cli_mem = kmem_cache_alloc(cli_mem_cache, GFP_ATOMIC);
cli_mem = kmem_cache_alloc(tfw_cli_mem_cache, GFP_ATOMIC);
if (unlikely(!cli_mem))
return NULL;

Expand All @@ -365,11 +403,16 @@ tfw_cli_mem_alloc_from_cache(void)
return cli_mem;

free_cli_mem:
kmem_cache_free(cli_mem_cache, cli_mem);
kmem_cache_free(tfw_cli_mem_cache, cli_mem);

return NULL;
}

/*
* Allocate cli_mem:
* - Try fast pool first, then fallback to slab cache.
* - On success, take an extra refcnt reference before returning.
*/
static inline TfwClientMem *
tfw_cli_mem_alloc(void)
{
Expand Down Expand Up @@ -589,10 +632,10 @@ TfwMod tfw_client_mod = {
int __init
tfw_client_init(void)
{
cli_mem_cache = kmem_cache_create("cli_mem_cache",
sizeof(TfwClientMem),
0, 0, NULL);
if (!cli_mem_cache)
tfw_cli_mem_cache = kmem_cache_create("tfw_cli_mem_cache",
sizeof(TfwClientMem),
0, 0, NULL);
if (!tfw_cli_mem_cache)
return -ENOMEM;
tfw_mod_register(&tfw_client_mod);

Expand All @@ -602,6 +645,6 @@ tfw_client_init(void)
void
tfw_client_exit(void)
{
kmem_cache_destroy(cli_mem_cache);
kmem_cache_destroy(tfw_cli_mem_cache);
tfw_mod_unregister(&tfw_client_mod);
}
6 changes: 3 additions & 3 deletions fw/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ typedef struct tfw_client_mem_t {
struct percpu_ref refcnt;
struct work_struct kill_work;
long __percpu *mem;
struct list_head in_free_list;
struct tfw_client_mem_t *next_free;
} TfwClientMem;

/**
Expand Down Expand Up @@ -82,13 +82,13 @@ tfw_client_mem_put(TfwClientMem *cli_mem)
}

static inline long
tfw_client_mem(TfwClient *cli)
tfw_client_mem(TfwClientMem *cli_mem)
{
long mem = 0;
int cpu;

for_each_online_cpu(cpu)
mem += *(per_cpu_ptr(cli->cli_mem->mem, cpu));
mem += *(per_cpu_ptr(cli_mem->mem, cpu));

return mem;
}
Expand Down
1 change: 1 addition & 0 deletions fw/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ tfw_connection_validate_cleanup(TfwConn *conn)
BUG_ON(!conn);
BUG_ON(!list_empty(&conn->list));
BUG_ON(conn->stream.msg);
/* Was purged early during connection release. */
BUG_ON(conn->write_queue);

rc = atomic_read(&conn->refcnt);
Expand Down
17 changes: 13 additions & 4 deletions fw/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -2907,6 +2907,15 @@ tfw_http_conn_msg_alloc(TfwConn *conn, TfwStream *stream)
int delta = PAGE_SIZE << hm->pool->order;

hm->pool->owner = cli_mem;
/*
* `tfw_client_mem_get` returns false, if we already
* call `percpu_ref_kill` for `cli_mem` after client
* was freed. We hold the client reference counter
* here due to active connections, so this situation
* is impossible, moreover, false result here means
* memory corruption, since we access `cli_mem` for
* already released client.
*/
BUG_ON(!tfw_client_mem_get(cli_mem));
tfw_client_adjust_mem(cli_mem, delta);
}
Expand Down Expand Up @@ -5474,7 +5483,9 @@ tfw_h2_on_send_resp(void *conn, struct sk_buff **skb_head)
if (unlikely(!stream))
return -EPIPE;

BUG_ON(stream->xmit.skb_head || stream->xmit.resp);
if (WARN_ON(stream->xmit.skb_head || stream->xmit.resp))
return -EINVAL;

TFW_SKB_CB(*skb_head)->opaque_data =
CLIENT_MEM_FROM_CONN(resp->req->conn);
TFW_SKB_CB(*skb_head)->destructor = ss_skb_dflt_destructor;
Expand Down Expand Up @@ -7458,10 +7469,8 @@ tfw_http_resp_process(TfwConn *conn, TfwStream *stream, struct sk_buff *skb,
skb->truesize);

r = frang_client_mem_limit(cli_conn, false);
if (unlikely(r)) {
BUG_ON(r != T_BLOCK);
if (unlikely(r))
return tfw_http_resp_filtout(hmresp);
}
} else {
conn_stop = false;
}
Expand Down
2 changes: 1 addition & 1 deletion fw/http2.c
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ tfw_h2_stream_xmit_prepare_resp(TfwStream *stream)
ALLOW_ERROR_INJECTION(tfw_h2_stream_xmit_prepare_resp, ERRNO);

int
tfw_h2_entail_stream_skb(struct sock *sk, TfwH2Ctx *ctx, TfwStream *stream,
tfw_h2_entail_stream_skb(struct sock *sk, TfwStream *stream,
unsigned int *len, bool should_split)
{
unsigned char tls_type = skb_tfw_tls_type(stream->xmit.skb_head);
Expand Down
2 changes: 1 addition & 1 deletion fw/http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ TfwStream *tfw_h2_find_not_closed_stream(TfwH2Ctx *ctx, unsigned int id,
void tfw_h2_req_unlink_stream(TfwHttpReq *req);
void tfw_h2_req_unlink_and_close_stream(TfwHttpReq *req);
int tfw_h2_stream_xmit_prepare_resp(TfwStream *stream);
int tfw_h2_entail_stream_skb(struct sock *sk, TfwH2Ctx *ctx, TfwStream *stream,
int tfw_h2_entail_stream_skb(struct sock *sk, TfwStream *stream,
unsigned int *len, bool should_split);
TfwStreamSchedEntry *tfw_h2_alloc_stream_sched_entry(TfwH2Ctx *ctx);
void tfw_h2_free_stream_sched_entry(TfwH2Ctx *ctx, TfwStreamSchedEntry *entry);
Expand Down
Loading