From: Cai Xinchen <caixinchen1@huawei.com>
To: Eric Dumazet <edumazet@google.com>
Cc: <tj@kernel.org>, <hannes@cmpxchg.org>, <mkoutny@suse.com>,
<corbet@lwn.net>, <skhan@linuxfoundation.org>,
<rdunlap@infradead.org>, <kuniyu@google.com>, <pabeni@redhat.com>,
<willemb@google.com>, <davem@davemloft.net>, <kuba@kernel.org>,
<horms@kernel.org>, <ncardwell@google.com>, <matttbe@kernel.org>,
<martineau@kernel.org>, <geliang@kernel.org>, <mhocko@kernel.org>,
<roman.gushchin@linux.dev>, <shakeel.butt@linux.dev>,
<muchun.song@linux.dev>, <cgroups@vger.kernel.org>,
<linux-doc@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<netdev@vger.kernel.org>, <mptcp@lists.linux.dev>,
<linux-mm@kvack.org>, <linux-kselftest@vger.kernel.org>,
<lujialin4@huawei.com>
Subject: Re: [PATCH RFC -next 0/5] net: charge socket memory budget to memcg upfront
Date: Thu, 24 Sep 2026 17:25:35 +0800 [thread overview]
Message-ID: <d8c12b4e-4ff2-4c62-a40d-d67bb4680a0d@huawei.com> (raw)
In-Reply-To: <CANn89iJVGmYwQerOixGurH6vw-GtvSAQq6u6zSNZoyZrcEx6Jw@mail.gmail.com>
Hi,
Thank you for the review.
We found that sk_forward_alloc is a plain int updated by a non-atomic
RMW (sk_forward_alloc_add(), where even the read side is not
READ_ONCE), and its writers span three unrelated lock domains:
- socket lock, process context: SO_RESERVE_MEM and TX grants
(__sk_mem_schedule(), sk_forced_mem_schedule());
- receive-queue lock, softirq: UDP RX charges and the
udp_rmem_release() fold;
- no lock at all: sk_mem_charge()/sk_mem_uncharge() from
skb_set_owner_r() and skb destructors, and the sk_mem_reclaim()
fold itself.
Any cross-domain pair loses an update. A lost charge is still
returned in full by the matching skb free, so it resurfaces as a
phantom surplus in sk_forward_alloc, and the next fold hands it back
to the memcg via __sk_mem_reduce_allocated() ->
mem_cgroup_sk_uncharge() - an uncharge with no matching charge.
We first tried to fix it with locks, and hit three walls:
- the socket lock cannot be used: its holders free skbs (e.g.
tcp_recvmsg()), and the destructor's sk_mem_uncharge() would
need to re-acquire it - recursion. That is why these helpers
are lockless in the first place;
- a new per-socket spinlock serializes the RMWs but not the bug:
__sk_mem_schedule() publishes the grant before the memcg charge,
and the charge may sleep (GFP_KERNEL, memcg reclaim/OOM), so no
spinlock can cover both steps; a fold in that window can still
refund pages whose charge afterwards fails;
- such a lock would also sit on the per-packet charge/uncharge
paths, exactly the hot path the cacheline layout around
sk_forward_alloc was tuned to keep cheap.
Are there any good solutions to solve this problem?
On 9/24/2026 4:26 PM, Eric Dumazet wrote:
> On Thu, Sep 24, 2026 at 9:36 AM Cai Xinchen <caixinchen1@huawei.com> wrote:
>> The memcg socket accounting currently charges pages to the memory
>> cgroup per grant (__sk_mem_schedule() publishing forward allocation)
>> and refunds them later from skb destructors. The refund side folds
>> per-skb "was this charged" snapshots back into the socket balance
>> under concurrent lockless RMW, and races there can drive the memcg
>> socket balance negative, ending with:
>>
>> page_counter underflow
>> WARNING: ... mm/page_counter.c ... page_counter_cancel()
>>
>> This series flips the model: a socket is charged its whole memory
>> budget (sk_sndbuf + sk_rcvbuf + sk_reserved_mem) to its memcg when the
>> budget is established or grows, and refunded when the budget shrinks
>> or the socket dies. Grants and per-skb charge/uncharge stop touching
>> the memcg entirely, so the racy refund pairing has no code left to go
>> wrong: refunds can never exceed charges and the balance cannot
>> underflow by construction.
>>
>> Tested: full arm64 build with 0 warnings; each intermediate state
>> compiles (bisectable); tools/testing/selftests/cgroup builds clean.
>> Runtime validation on the workload that used to trigger the underflow
>> is pending.
>>
>
> Charging sk_sndbuf + sk_rcvbuf upfront to memory.current is not
> viable, especially for servers handling large numbers of connections
> (e.g. 1 million TCP sockets):
>
> We specifically went in the exact opposite direction in commit
> 4890b686f408 ("net: keep sk->sk_forward_alloc as small as possible")
> to make sure idle sockets hold zero forward-allocated memory and
> non-idle sockets hold less than one page (4 KB) in sk_forward_alloc.
>
>
> 1. Massive phantom memory charges and false OOMs:
> With default sysctl_tcp_wmem[1] (16 KB) and sysctl_tcp_rmem[1]
> (128 KB), 1 million completely idle TCP sockets immediately charge
> 144 GB to the cgroup's memory.current while holding 0 bytes of
> actual packet buffers.
> Worse, once TCP autotuning grows sk_rcvbuf / sk_sndbuf during a
> short burst (up to tcp_rmem[2] = 6 MB and tcp_wmem[2] = 4 MB by
> default), TCP does not shrink sk_rcvbuf or sk_sndbuf when the queues
> drain and the connection becomes idle again. 1 million long-lived,
> mostly-idle connections would permanently pin hundreds of GBs (up to
> several TBs) of non-existent memory in memory.current, forcing the
> memcg into constant reclaim thrashing of real page cache/anon pages
> and triggering premature memcg OOM kills.
>
> 2. Overcommitted caps vs. physical reservations:
> sk_sndbuf and sk_rcvbuf are per-socket upper bounds that are heavily
> overcommitted across sockets, not reservations (unlike SO_RESERVE_MEM).
> Comparing this to vm_committed_as is flawed: vm_committed_as tracks
> virtual address space overcommit globally and is never charged to
> memcg's memory.current for the exact same reason.
>
> 3. Broken memcg limit enforcement (memory.max bypass):
> __sk_mem_raise_allocated() drops mem_cgroup_sk_charge() completely,
> while sk_memcg_budget_sync() ignores charge failures ("the new budget
> is used uncharged"). When a cgroup reaches memory.max,
> sk_memcg_budget_sync() fails in sock_init_data_uid(), tcp_init_sock(),
> setsockopt(SO_SNDBUF/SO_RCVBUF), or autotuning, yet sk_sndbuf and
> sk_rcvbuf are still raised. Subsequent skb allocations in
> __sk_mem_schedule() will then allocate real physical memory without
> charging the memcg at all.
>
> 4. Unnecessary struct sock bloat and hot-path overhead:
> - Adds 8 bytes (sk_memcg_budget + sk_memcg_budget_lock) to struct sock
> (even when !CONFIG_MEMCG).
> - Acquires spin_lock_bh(&sk->sk_memcg_budget_lock) inside
> sk_mem_reclaim().
> - Every TCP socket creation charges rmem_default + wmem_default
> (416 KB) in sock_init_data_uid() and immediately uncharges 272 KB
> in tcp_init_sock().
>
> If you are hitting a page_counter underflow race in socket memcg
> accounting, please share the exact race / stack trace and fix the
> underlying accounting bug rather than charging uncommitted buffer limits.
next prev parent reply other threads:[~2026-09-24 9:25 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-24 8:02 Cai Xinchen
2026-09-24 8:02 ` [PATCH RFC -next 1/5] " Cai Xinchen
2026-09-24 8:02 ` [PATCH RFC -next 2/5] tcp: sync memcg budget on protocol buffer updates Cai Xinchen
2026-09-24 8:02 ` [PATCH RFC -next 3/5] mptcp: sync memcg budget and drop backlog page compensation Cai Xinchen
2026-09-24 8:02 ` [PATCH RFC -next 4/5] Docs/admin-guide/cgroup-v2: document upfront socket budget charging Cai Xinchen
2026-09-24 8:02 ` [PATCH RFC -next 5/5] selftests/cgroup: compare socket memory deltas in test_memcg_sock Cai Xinchen
2026-09-24 8:26 ` [PATCH RFC -next 0/5] net: charge socket memory budget to memcg upfront Eric Dumazet
2026-09-24 9:25 ` Cai Xinchen [this message]
2026-09-24 9:28 ` Cai Xinchen
2026-09-24 9:55 ` Eric Dumazet
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=d8c12b4e-4ff2-4c62-a40d-d67bb4680a0d@huawei.com \
--to=caixinchen1@huawei.com \
--cc=cgroups@vger.kernel.org \
--cc=corbet@lwn.net \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=geliang@kernel.org \
--cc=hannes@cmpxchg.org \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=lujialin4@huawei.com \
--cc=martineau@kernel.org \
--cc=matttbe@kernel.org \
--cc=mhocko@kernel.org \
--cc=mkoutny@suse.com \
--cc=mptcp@lists.linux.dev \
--cc=muchun.song@linux.dev \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=rdunlap@infradead.org \
--cc=roman.gushchin@linux.dev \
--cc=shakeel.butt@linux.dev \
--cc=skhan@linuxfoundation.org \
--cc=tj@kernel.org \
--cc=willemb@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®