* [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-11 14:25 ` netdev-bot+sashiko
2026-10-10 14:22 ` [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets Jia Jia
` (3 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
Split virtio_transport_recv_pkt() so socket lookup is separate from the
receive state machine that runs under the socket lock.
Keep virtio_transport_recv_pkt() on its existing per-packet locking path.
Pass the source and destination addresses used for lookup to the locked
receive path so they are decoded only once.
Pass the network namespace and decoded address tuple through a packet
context to the locked receive path.
Preserve source validation and failure handling. Keep the lookup reference
separate from the skb owner reference so the locked receive path can manage
the two lifetimes independently.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
net/vmw_vsock/virtio_transport_common.c | 139 ++++++++++++++++--------
1 file changed, 96 insertions(+), 43 deletions(-)
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index f225f53..e3e75e5 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1774,81 +1774,97 @@ static bool virtio_transport_valid_type(u16 type)
(type == VIRTIO_VSOCK_TYPE_SEQPACKET);
}
-/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
- * lock.
- */
-void virtio_transport_recv_pkt(struct virtio_transport *t,
- struct sk_buff *skb, struct net *net)
+static void
+virtio_transport_recv_pkt_init_addrs(struct sk_buff *skb,
+ struct sockaddr_vm *src,
+ struct sockaddr_vm *dst)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
- struct sockaddr_vm src, dst;
- struct vsock_sock *vsk;
- struct sock *sk;
- bool space_available;
- vsock_addr_init(&src, le64_to_cpu(hdr->src_cid),
+ vsock_addr_init(src, le64_to_cpu(hdr->src_cid),
le32_to_cpu(hdr->src_port));
- vsock_addr_init(&dst, le64_to_cpu(hdr->dst_cid),
+ vsock_addr_init(dst, le64_to_cpu(hdr->dst_cid),
le32_to_cpu(hdr->dst_port));
+}
+
+static void
+virtio_transport_trace_recv_pkt(struct sk_buff *skb,
+ const struct sockaddr_vm *src,
+ const struct sockaddr_vm *dst)
+{
+ struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
- trace_virtio_transport_recv_pkt(src.svm_cid, src.svm_port,
- dst.svm_cid, dst.svm_port,
+ trace_virtio_transport_recv_pkt(src->svm_cid, src->svm_port,
+ dst->svm_cid, dst->svm_port,
le32_to_cpu(hdr->len),
le16_to_cpu(hdr->type),
le16_to_cpu(hdr->op),
le32_to_cpu(hdr->flags),
le32_to_cpu(hdr->buf_alloc),
le32_to_cpu(hdr->fwd_cnt));
+}
- if (!virtio_transport_valid_type(le16_to_cpu(hdr->type))) {
- (void)virtio_transport_reset_no_sock(t, skb, net);
- goto free_pkt;
- }
+static struct sock *
+virtio_transport_recv_pkt_find_socket(struct sk_buff *skb,
+ struct sockaddr_vm *src,
+ struct sockaddr_vm *dst,
+ struct net *net)
+{
+ struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+ struct sock *sk;
- /* The socket must be in connected or bound table
- * otherwise send reset back
- */
- sk = vsock_find_connected_socket_net(&src, &dst, net);
- if (!sk) {
- sk = vsock_find_bound_socket_net(&dst, net);
- if (!sk) {
- (void)virtio_transport_reset_no_sock(t, skb, net);
- goto free_pkt;
- }
- }
+ if (!virtio_transport_valid_type(le16_to_cpu(hdr->type)))
+ return NULL;
+
+ sk = vsock_find_connected_socket_net(src, dst, net);
+ if (!sk)
+ sk = vsock_find_bound_socket_net(dst, net);
+ if (!sk)
+ return NULL;
if (virtio_transport_get_type(sk) != le16_to_cpu(hdr->type)) {
- (void)virtio_transport_reset_no_sock(t, skb, net);
sock_put(sk);
- goto free_pkt;
+ return NULL;
}
- if (!skb_set_owner_sk_safe(skb, sk)) {
- WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
- goto free_pkt;
- }
+ return sk;
+}
- vsk = vsock_sk(sk);
+struct virtio_transport_rx_pkt_ctx {
+ struct net *net;
+ const struct sockaddr_vm *src;
+ const struct sockaddr_vm *dst;
+};
- lock_sock(sk);
+/*
+ * The caller holds sk's socket lock and must free skb if this returns true.
+ */
+static bool
+virtio_transport_recv_pkt_locked(struct virtio_transport *t,
+ struct sk_buff *skb, struct sock *sk,
+ const struct virtio_transport_rx_pkt_ctx *ctx)
+{
+ const struct sockaddr_vm *src = ctx->src;
+ const struct sockaddr_vm *dst = ctx->dst;
+ struct vsock_sock *vsk = vsock_sk(sk);
+ struct net *net = ctx->net;
+ bool space_available;
- /* Check if sk has been closed or assigned to another transport before
- * lock_sock (note: listener sockets are not assigned to any transport)
+ /* Check after acquiring the socket lock. Listener sockets accept packets
+ * from any source and are not assigned to a transport.
*/
if (sock_flag(sk, SOCK_DONE) ||
(sk->sk_state != TCP_LISTEN &&
- !vsock_check_source(vsk, &t->transport, &src))) {
+ !vsock_check_source(vsk, &t->transport, src))) {
(void)virtio_transport_reset_no_sock(t, skb, net);
- release_sock(sk);
- sock_put(sk);
- goto free_pkt;
+ return true;
}
space_available = virtio_transport_space_update(sk, skb);
/* Update CID in case it has changed after a transport reset event */
if (vsk->local_addr.svm_cid != VMADDR_CID_ANY)
- vsk->local_addr.svm_cid = dst.svm_cid;
+ vsk->local_addr.svm_cid = dst->svm_cid;
if (space_available)
sk->sk_write_space(sk);
@@ -1875,12 +1891,49 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
break;
}
+ return false;
+}
+
+/* We are under the virtio-vsock's vsock->rx_lock or vhost-vsock's vq->mutex
+ * lock.
+ */
+void virtio_transport_recv_pkt(struct virtio_transport *t,
+ struct sk_buff *skb, struct net *net)
+{
+ struct virtio_transport_rx_pkt_ctx ctx;
+ struct sockaddr_vm src, dst;
+ struct sock *sk;
+ bool free_pkt;
+
+ virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
+ virtio_transport_trace_recv_pkt(skb, &src, &dst);
+
+ sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
+ if (!sk) {
+ (void)virtio_transport_reset_no_sock(t, skb, net);
+ goto free_pkt;
+ }
+
+ if (!skb_set_owner_sk_safe(skb, sk)) {
+ WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+ goto free_pkt;
+ }
+
+ lock_sock(sk);
+ ctx = (struct virtio_transport_rx_pkt_ctx) {
+ .net = net,
+ .src = &src,
+ .dst = &dst,
+ };
+ free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
release_sock(sk);
/* Release refcnt obtained when we fetched this socket out of the
* bound or connected list.
*/
sock_put(sk);
+ if (free_pkt)
+ kfree_skb(skb);
return;
free_pkt:
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing
2026-10-10 14:22 ` [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing Jia Jia
@ 2026-10-11 14:25 ` netdev-bot+sashiko
0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 14:25 UTC (permalink / raw)
To: physicalmtea
Cc: stefanha, sgarzare, netdev, virtualization, kvm, mst, jasowangio,
eperezma, xuanzhuo, davem, edumazet, kuba, pabeni, horms,
linux-kernel, bpf
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] Commit message concern: the message for "vsock/virtio: split
socket lookup from locked RX processing" lists what the refactor does…
--
vsock/virtio: split socket lookup from locked RX processing
This commit splits virtio_transport_recv_pkt() into two helpers. One
does the socket lookup. The other runs the receive state machine under
the socket lock. The decoded source and destination addresses and the
network namespace reach the locked helper through a new struct
virtio_transport_rx_pkt_ctx.
> Keep virtio_transport_recv_pkt() on its existing per-packet locking path.
> Pass the source and destination addresses used for lookup to the locked
> receive path so they are decoded only once.
[Severity: Low]
This isn't a bug, but does "decoded only once" describe this patch on
its own? The old virtio_transport_recv_pkt() also decoded src and dst
once with vsock_addr_init(). It then reused them for the lookup and for
vsock_check_source().
> Preserve source validation and failure handling. Keep the lookup reference
> separate from the skb owner reference so the locked receive path can manage
> the two lifetimes independently.
In this patch, virtio_transport_recv_pkt_locked() doesn't touch either
reference. The caller still calls skb_set_owner_sk_safe(),
release_sock(), sock_put() and kfree_skb() in the same order as before:
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
release_sock(sk);
...
sock_put(sk);
if (free_pkt)
kfree_skb(skb);
Also, struct virtio_transport_rx_pkt_ctx has only one user here.
These statements seem to describe the next patch in the series,
"vsock/virtio: amortize RX socket locking for stream packets". That
patch adds virtio_transport_recv_pkt_batch() as a second caller of
virtio_transport_recv_pkt_find_socket() and
virtio_transport_recv_pkt_locked().
It also adds batchable, batch and defer_data_ready to the ctx struct.
It keeps the lookup reference for the life of the batch and takes the
skb owner reference per packet.
The diff reorders one thing: on a type mismatch, sock_put() now runs
before virtio_transport_reset_no_sock(). This has no effect, because
virtio_transport_reset_no_sock() uses skb->sk and not the looked-up sk.
Could the commit message say that this is preparation for the batched
RX work, and add a "No functional change intended" line?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010142247.99223-1-physicalmtea%40gmail.com
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-11 14:25 ` netdev-bot+sashiko
2026-10-10 14:22 ` [PATCH net-next v2 3/5] vsock/virtio: reuse same-flow socket lookup in RX batches Jia Jia
` (2 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
The virtio-vsock RX worker currently acquires and releases the socket lock
for every received packet. Keep the lock held while processing a bounded
run of packets that belongs to the same socket.
Only ordinary RW packets for an established STREAM socket whose transport
and protocol remain unchanged are eligible. Control packets, SEQPACKET
traffic, state or transport changes, malformed packets, and sockets whose
protocol has been replaced end the active batch and retain their existing
handling.
Serialize the initial eligibility check with sk_callback_lock because
sockmap removal restores the protocol without taking the socket lock.
Limit each batch to 64 packets or 64KB of payload, bounding the work under
one socket lock. Keep the lookup reference until the batch finishes.
Keep a batch only while another skb metadata charge fits in the receive
buffer. Share this predicate with virtio_transport_inc_rx_pkt(). When an
enqueued skb consumes the final metadata slot, finish the batch and release
the socket lock before processing another packet, restoring the reader's
scheduling opportunity from the per-packet path.
The virtqueue callback remains a queue_work() callback. Batching runs only
in the process-context RX worker.
Introduce virtio_transport_rx_batch_finish() to tear down the batch and
release the socket lock and lookup reference.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
include/linux/virtio_vsock.h | 11 ++
net/vmw_vsock/virtio_transport.c | 29 ++++-
net/vmw_vsock/virtio_transport_common.c | 147 +++++++++++++++++++++++-
3 files changed, 183 insertions(+), 4 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index f91704731057..26dde6909c4e 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -282,6 +282,17 @@ void virtio_transport_destruct(struct vsock_sock *vsk);
void virtio_transport_recv_pkt(struct virtio_transport *t,
struct sk_buff *skb, struct net *net);
+
+struct virtio_transport_rx_batch {
+ struct sock *sk;
+ unsigned int pkts;
+ size_t bytes;
+};
+
+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
+ struct sk_buff *skb, struct net *net,
+ struct virtio_transport_rx_batch *batch);
+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch);
void virtio_transport_inc_tx_pkt(struct virtio_vsock_sock *vvs, struct sk_buff *skb);
u32 virtio_transport_get_credit(struct virtio_vsock_sock *vvs, u32 wanted);
void virtio_transport_put_credit(struct virtio_vsock_sock *vvs, u32 credit);
diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
index 4f9aa9c4c3aa..ef076732158d 100644
--- a/net/vmw_vsock/virtio_transport.c
+++ b/net/vmw_vsock/virtio_transport.c
@@ -629,8 +629,16 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
return seqpacket_allow;
}
+/*
+ * Keep a bounded run of packets for one socket under a single socket lock.
+ * Limit packet count and payload size to bound the work done while locked.
+ */
+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS 64
+#define VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES (64 * 1024)
+
static void virtio_transport_rx_work(struct work_struct *work)
{
+ struct virtio_transport_rx_batch batch = {};
struct virtio_vsock *vsock =
container_of(work, struct virtio_vsock, rx_work);
struct virtqueue *vq;
@@ -666,6 +674,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
/* Drop short/long packets */
if (unlikely(len < sizeof(*hdr) ||
len > virtio_vsock_skb_len(skb))) {
+ virtio_transport_rx_batch_finish(&batch);
kfree_skb(skb);
continue;
}
@@ -673,6 +682,7 @@ static void virtio_transport_rx_work(struct work_struct *work)
hdr = virtio_vsock_hdr(skb);
payload_len = le32_to_cpu(hdr->len);
if (unlikely(payload_len > len - sizeof(*hdr))) {
+ virtio_transport_rx_batch_finish(&batch);
kfree_skb(skb);
continue;
}
@@ -680,16 +690,33 @@ static void virtio_transport_rx_work(struct work_struct *work)
if (payload_len)
virtio_vsock_skb_put(skb, payload_len);
+ if (batch.sk &&
+ payload_len > VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES -
+ batch.bytes) {
+ virtio_transport_rx_batch_finish(&batch);
+ }
+
virtio_transport_deliver_tap_pkt(skb);
/* Force virtio-transport into global mode since it
* does not yet support local-mode namespacing.
*/
- virtio_transport_recv_pkt(&virtio_transport, skb, NULL);
+ virtio_transport_recv_pkt_batch(&virtio_transport, skb, NULL,
+ &batch);
+
+ if (batch.sk) {
+ batch.pkts++;
+ batch.bytes += payload_len;
+ if (batch.pkts >= VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS ||
+ batch.bytes >= VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES) {
+ virtio_transport_rx_batch_finish(&batch);
+ }
+ }
}
} while (!virtqueue_enable_cb(vq));
out:
+ virtio_transport_rx_batch_finish(&batch);
if (vsock->rx_buf_nr < vsock->rx_buf_max_nr / 2)
virtio_vsock_rx_fill(vsock);
out_nofill:
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index e3e75e59896f..a73e37b3fa62 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -576,10 +576,16 @@ virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,
skb_queue_splice(&new_queue, &vvs->rx_queue);
}
+static bool
+virtio_transport_rx_skb_has_headroom(struct virtio_vsock_sock *vvs)
+{
+ return (u64)(skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0) <=
+ vvs->buf_alloc;
+}
+
static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
u32 len)
{
- u64 skb_overhead = (skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0);
/* Allow at most buf_alloc * 2 total budget (payload + overhead),
* similar to how SO_RCVBUF is doubled to reserve space for sk_buff
@@ -588,7 +594,7 @@ static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
* queue growth.
*/
if ((u64)vvs->buf_used + len > vvs->buf_alloc ||
- skb_overhead > vvs->buf_alloc)
+ !virtio_transport_rx_skb_has_headroom(vvs))
return false;
vvs->rx_bytes += len;
@@ -1834,10 +1840,28 @@ struct virtio_transport_rx_pkt_ctx {
struct net *net;
const struct sockaddr_vm *src;
const struct sockaddr_vm *dst;
+ bool *batchable;
};
+static bool
+virtio_transport_recv_pkt_batchable(struct virtio_transport *t,
+ struct sock *sk)
+{
+ struct vsock_sock *vsk = vsock_sk(sk);
+ struct virtio_vsock_sock *vvs = vsk->trans;
+
+ return sk->sk_state == TCP_ESTABLISHED &&
+ sk->sk_type == SOCK_STREAM &&
+ READ_ONCE(sk->sk_prot) == sk->sk_prot_creator &&
+ !sock_flag(sk, SOCK_DONE) &&
+ vsk->transport == &t->transport &&
+ vvs && virtio_transport_rx_skb_has_headroom(vvs);
+}
+
/*
- * The caller holds sk's socket lock and must free skb if this returns true.
+ * The caller holds sk's socket lock. Set @batchable if the socket can remain
+ * locked for another ordinary STREAM/RW packet. Return true if the caller
+ * must free @skb.
*/
static bool
virtio_transport_recv_pkt_locked(struct virtio_transport *t,
@@ -1850,6 +1874,9 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
struct net *net = ctx->net;
bool space_available;
+ if (ctx->batchable)
+ *ctx->batchable = false;
+
/* Check after acquiring the socket lock. Listener sockets accept packets
* from any source and are not assigned to a transport.
*/
@@ -1891,6 +1918,9 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
break;
}
+ if (ctx->batchable)
+ *ctx->batchable = virtio_transport_recv_pkt_batchable(t, sk);
+
return false;
}
@@ -1941,6 +1971,117 @@ void virtio_transport_recv_pkt(struct virtio_transport *t,
}
EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
+/*
+ * Finish the RX batch.
+ * For a non-empty batch, the caller must hold the socket lock acquired with
+ * lock_sock(). This function releases the lock and the batch's lookup
+ * reference.
+ * An empty batch is a no-op.
+ */
+void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
+{
+ struct sock *sk = batch->sk;
+
+ batch->sk = NULL;
+ batch->pkts = 0;
+ batch->bytes = 0;
+
+ if (!sk)
+ return;
+
+ release_sock(sk);
+ sock_put(sk);
+}
+EXPORT_SYMBOL_GPL(virtio_transport_rx_batch_finish);
+
+void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
+ struct sk_buff *skb, struct net *net,
+ struct virtio_transport_rx_batch *batch)
+{
+ struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+ struct virtio_transport_rx_pkt_ctx ctx;
+ struct sockaddr_vm src, dst;
+ bool batchable, start_batch;
+ struct sock *sk;
+ bool free_pkt;
+
+ /* Only STREAM/RW packets can share a socket lock. */
+ if (le16_to_cpu(hdr->type) != VIRTIO_VSOCK_TYPE_STREAM ||
+ le16_to_cpu(hdr->op) != VIRTIO_VSOCK_OP_RW) {
+ virtio_transport_rx_batch_finish(batch);
+ virtio_transport_recv_pkt(t, skb, net);
+ return;
+ }
+
+ virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
+ virtio_transport_trace_recv_pkt(skb, &src, &dst);
+
+ sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
+ if (!sk) {
+ virtio_transport_rx_batch_finish(batch);
+ (void)virtio_transport_reset_no_sock(t, skb, net);
+ kfree_skb(skb);
+ return;
+ }
+
+ if (!skb_set_owner_sk_safe(skb, sk)) {
+ WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+ virtio_transport_rx_batch_finish(batch);
+ kfree_skb(skb);
+ return;
+ }
+
+ if (batch->sk && batch->sk != sk) {
+ /* Never acquire a second socket lock. */
+ virtio_transport_rx_batch_finish(batch);
+ }
+
+ if (batch->sk == sk) {
+ /* Keep the batch reference; drop this packet's lookup reference. */
+ sock_put(sk);
+ ctx = (struct virtio_transport_rx_pkt_ctx) {
+ .net = net,
+ .src = &src,
+ .dst = &dst,
+ .batchable = &batchable,
+ };
+ free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+ if (!batchable)
+ virtio_transport_rx_batch_finish(batch);
+ if (free_pkt)
+ kfree_skb(skb);
+ return;
+ }
+
+ lock_sock(sk);
+ /*
+ * Sockmap removal restores the native protocol under sk_callback_lock.
+ * Serialize this initial eligibility check with that update.
+ */
+ read_lock_bh(&sk->sk_callback_lock);
+ start_batch = virtio_transport_recv_pkt_batchable(t, sk);
+ read_unlock_bh(&sk->sk_callback_lock);
+
+ ctx = (struct virtio_transport_rx_pkt_ctx) {
+ .net = net,
+ .src = &src,
+ .dst = &dst,
+ .batchable = start_batch ? &batchable : NULL,
+ };
+ free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+ if (start_batch && batchable) {
+ /* Keep the lookup reference until the batch is released. */
+ batch->sk = sk;
+ return;
+ }
+
+ release_sock(sk);
+ sock_put(sk);
+ if (free_pkt)
+ kfree_skb(skb);
+}
+EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt_batch);
+
/* Remove skbs found in a queue that have a vsk that matches.
*
* Each skb is freed.
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets
2026-10-10 14:22 ` [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets Jia Jia
@ 2026-10-11 14:25 ` netdev-bot+sashiko
0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 14:25 UTC (permalink / raw)
To: physicalmtea
Cc: stefanha, sgarzare, netdev, virtualization, kvm, mst, jasowangio,
eperezma, xuanzhuo, davem, edumazet, kuba, pabeni, horms,
linux-kernel, bpf
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The patch is presented purely as a performance optimisation
("amortize RX socket locking") but gives no throughput, latency or CPU…
- [Low] Removing the local skb_overhead declaration from
virtio_transport_inc_rx_pkt()…
--
vsock/virtio: amortize RX socket locking for stream packets
This commit keeps the socket lock held in the virtio-vsock RX worker
while it processes a bounded run of RW packets for the same established
STREAM socket. A batch is limited to 64 packets or 64KB of payload. The
commit adds the exported helpers virtio_transport_recv_pkt_batch() and
virtio_transport_rx_batch_finish(), and virtio_transport_inc_rx_pkt()
now shares the skb metadata headroom check.
> diff --git a/net/vmw_vsock/virtio_transport.c b/net/vmw_vsock/virtio_transport.c
> index 4f9aa9c4c3aa5..ef076732158d4 100644
> --- a/net/vmw_vsock/virtio_transport.c
> +++ b/net/vmw_vsock/virtio_transport.c
> @@ -629,8 +629,16 @@ virtio_transport_seqpacket_allow(struct vsock_sock *vsk, u32 remote_cid)
> return seqpacket_allow;
> }
>
> +/*
> + * Keep a bounded run of packets for one socket under a single socket lock.
> + * Limit packet count and payload size to bound the work done while locked.
> + */
> +#define VIRTIO_TRANSPORT_RX_BATCH_MAX_PKTS 64
> +#define VIRTIO_TRANSPORT_RX_BATCH_MAX_BYTES (64 * 1024)
[Severity: Low]
This isn't a bug, but are there throughput, latency or CPU numbers for
this change? Which workload does it help?
The commit is presented only as a performance optimization. To get that
benefit it adds a new struct in include/linux/virtio_vsock.h, two
exported symbols, and new rules for when a packet can join a batch.
It also keeps lock_sock() held across iterations of the loop in
virtio_transport_rx_work(). As a result, recvmsg, sendmsg, setsockopt or
close on the same socket can now wait for up to 64 packets / 64KB
instead of one packet.
In this patch, the batched path in virtio_transport_recv_pkt_batch()
still does the lookup and refcount work for every packet:
sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
...
if (!skb_set_owner_sk_safe(skb, sk)) {
...
if (batch->sk == sk) {
/* Keep the batch reference; drop this packet's lookup reference. */
sock_put(sk);
virtio_transport_recv_pkt_find_socket() takes the global
vsock_table_lock and does a sock_hold(). That means only the
lock_sock()/release_sock() pair is saved.
sk_data_ready() also still runs while the lock is held. A reader it
wakes can therefore go back to sleep on sk_lock.wq until the batch
ends.
Two later commits in the series remove those costs: "vsock/virtio:
reuse same-flow socket lookup in RX batches" and "vsock/virtio: defer
RX readable notifications until batch unlock". Even so, no commit
message in the series has numbers showing that the change is a net win.
Could benchmark data be added to the commit message?
[ ... ]
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index e3e75e59896fe..a73e37b3fa62f 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
> @@ -576,10 +576,16 @@ virtio_transport_collapse_rx_queue(struct virtio_vsock_sock *vvs,
[ ... ]
> static bool virtio_transport_inc_rx_pkt(struct virtio_vsock_sock *vvs,
> u32 len)
> {
> - u64 skb_overhead = (skb_queue_len(&vvs->rx_queue) + 1) * SKB_TRUESIZE(0);
>
> /* Allow at most buf_alloc * 2 total budget (payload + overhead),
[Severity: Low]
This isn't a bug, but removing skb_overhead leaves a blank line right
after the opening brace of virtio_transport_inc_rx_pkt().
checkpatch --strict warns about this ("Blank lines aren't necessary
after an open brace"). The blank line is still there at the end of the
series.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010142247.99223-1-physicalmtea%40gmail.com
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v2 3/5] vsock/virtio: reuse same-flow socket lookup in RX batches
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 1/5] vsock/virtio: split socket lookup from locked RX processing Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 2/5] vsock/virtio: amortize RX socket locking for stream packets Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 4/5] vsock/virtio: coalesce RX write-space notifications in lock batches Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock Jia Jia
4 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
An RX lock batch still looks up the socket for every packet and takes a
temporary lookup reference, even though the batch already holds a reference
to the locked socket.
Record the network namespace and packet address tuple when a batch starts.
Reuse the batch socket for later STREAM/RW packets with the same tuple, and
release the batch before looking up a different flow.
The cached path still traces every packet, takes an skb owner reference,
validates socket state, source and transport, updates credit, and runs the
receive state machine. Only the socket table lookup and its temporary
reference are skipped.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
include/linux/virtio_vsock.h | 3 ++
net/vmw_vsock/virtio_transport_common.c | 58 +++++++++++++++----------
2 files changed, 38 insertions(+), 23 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index 26dde6909c4e..d6528681e052 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -287,6 +287,9 @@ struct virtio_transport_rx_batch {
struct sock *sk;
unsigned int pkts;
size_t bytes;
+ struct net *net;
+ struct sockaddr_vm src;
+ struct sockaddr_vm dst;
};
void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index e67dfcaa279c..73e5c7dfe8b4 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1977,6 +1977,7 @@ void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
batch->sk = NULL;
batch->pkts = 0;
batch->bytes = 0;
+ batch->net = NULL;
if (!sk)
return;
@@ -2008,6 +2009,37 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
virtio_transport_recv_pkt_init_addrs(skb, &src, &dst);
virtio_transport_trace_recv_pkt(skb, &src, &dst);
+ if (batch->sk) {
+ if (batch->net == net &&
+ vsock_addr_equals_addr(&batch->src, &src) &&
+ vsock_addr_equals_addr(&batch->dst, &dst) &&
+ virtio_transport_recv_pkt_batchable(t, batch->sk)) {
+ sk = batch->sk;
+ if (!skb_set_owner_sk_safe(skb, sk)) {
+ WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
+ virtio_transport_rx_batch_finish(batch);
+ kfree_skb(skb);
+ return;
+ }
+
+ ctx = (struct virtio_transport_rx_pkt_ctx) {
+ .net = net,
+ .src = &src,
+ .dst = &dst,
+ .batchable = &batchable,
+ };
+ free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
+
+ if (!batchable)
+ virtio_transport_rx_batch_finish(batch);
+ if (free_pkt)
+ kfree_skb(skb);
+ return;
+ }
+
+ virtio_transport_rx_batch_finish(batch);
+ }
+
sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
if (!sk) {
virtio_transport_rx_batch_finish(batch);
@@ -2018,33 +2050,10 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
if (!skb_set_owner_sk_safe(skb, sk)) {
WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
- virtio_transport_rx_batch_finish(batch);
kfree_skb(skb);
return;
}
- if (batch->sk && batch->sk != sk) {
- /* Never acquire a second socket lock. */
- virtio_transport_rx_batch_finish(batch);
- }
-
- if (batch->sk == sk) {
- /* Keep the batch reference; drop this packet's lookup reference. */
- sock_put(sk);
- ctx = (struct virtio_transport_rx_pkt_ctx) {
- .net = net,
- .src = &src,
- .dst = &dst,
- .batchable = &batchable,
- };
- free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
- if (!batchable)
- virtio_transport_rx_batch_finish(batch);
- if (free_pkt)
- kfree_skb(skb);
- return;
- }
-
lock_sock(sk);
/*
* Sockmap removal restores the native protocol under sk_callback_lock.
@@ -2063,6 +2072,9 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
if (start_batch && batchable) {
/* Keep the lookup reference until the batch is released. */
+ batch->net = net;
+ batch->src = src;
+ batch->dst = dst;
batch->sk = sk;
return;
}
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH net-next v2 4/5] vsock/virtio: coalesce RX write-space notifications in lock batches
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
` (2 preceding siblings ...)
2026-10-10 14:22 ` [PATCH net-next v2 3/5] vsock/virtio: reuse same-flow socket lookup in RX batches Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-10 14:22 ` [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock Jia Jia
4 siblings, 0 replies; 9+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
Each received packet updates peer credit and calls sk_write_space() when
send space is available. A writer cannot use the newly advertised credit
until the socket lock is released, so repeated callbacks within one lock
batch cannot let it make progress sooner.
For the callback installed by sock_init_data(), record one pending
write-space notification and deliver it through the saved default callback
before release_sock(). If the callback has been replaced by batch finish,
invoke the replacement as well. This keeps the native writer wakeup from
being lost across a sockmap callback change while still notifying sockmap.
Save the initial sock_def_write_space() callback when the AF_VSOCK socket
is created because it is not visible to virtio_transport_common when built
as a module. The fast path uses READ_ONCE() and adds no callback lock.
Set the batch socket before processing its first packet so a batch ending
on that packet cannot lose the notification.
Packets outside the eligible STREAM/RW batch path retain per-packet
notification behavior. The 64-packet and 64K limits cap the packets whose
notifications can be coalesced.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
include/linux/virtio_vsock.h | 1 +
include/net/af_vsock.h | 2 ++
net/vmw_vsock/af_vsock.c | 1 +
net/vmw_vsock/virtio_transport_common.c | 42 ++++++++++++++++++++++---
4 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index d6528681e052..3a58120d9078 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -290,6 +290,7 @@ struct virtio_transport_rx_batch {
struct net *net;
struct sockaddr_vm src;
struct sockaddr_vm dst;
+ bool write_space_pending;
};
void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 5549298c1ec6..9d8ae62209ed 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -63,6 +63,8 @@ struct vsock_sock {
u32 peer_shutdown;
bool sent_request;
bool ignore_connecting_rst;
+ /* Initial callback, used to identify replacements. */
+ void (*default_write_space)(struct sock *sk);
/* Protected by lock_sock(sk) */
u64 buffer_size;
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index 44cee7451955..fe7f49da6c4b 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -958,6 +958,7 @@ static struct sock *__vsock_create(struct net *net,
sk->sk_type = type;
vsk = vsock_sk(sk);
+ vsk->default_write_space = sk->sk_write_space;
vsock_addr_init(&vsk->local_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
vsock_addr_init(&vsk->remote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index 73e5c7dfe8b4..f0398ae2200d 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1835,6 +1835,7 @@ struct virtio_transport_rx_pkt_ctx {
const struct sockaddr_vm *src;
const struct sockaddr_vm *dst;
bool *batchable;
+ struct virtio_transport_rx_batch *batch;
};
static bool
@@ -1863,6 +1864,7 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
const struct sockaddr_vm *src = ctx->src;
const struct sockaddr_vm *dst = ctx->dst;
struct vsock_sock *vsk = vsock_sk(sk);
+ void (*write_space)(struct sock *sk);
struct net *net = ctx->net;
bool space_available;
@@ -1885,8 +1887,17 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
if (vsk->local_addr.svm_cid != VMADDR_CID_ANY)
vsk->local_addr.svm_cid = dst->svm_cid;
- if (space_available)
- sk->sk_write_space(sk);
+ if (space_available) {
+ write_space = READ_ONCE(sk->sk_write_space);
+ if (ctx->batch &&
+ write_space == vsk->default_write_space &&
+ virtio_transport_recv_pkt_batchable(t, sk)) {
+ ctx->batch->write_space_pending = true;
+ } else {
+ /* Use the callback seen for this packet. */
+ write_space(sk);
+ }
+ }
switch (sk->sk_state) {
case TCP_LISTEN:
@@ -1972,16 +1983,28 @@ EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
*/
void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
{
+ bool write_space_pending = batch->write_space_pending;
+ void (*write_space)(struct sock *sk);
struct sock *sk = batch->sk;
+ struct vsock_sock *vsk;
batch->sk = NULL;
batch->pkts = 0;
batch->bytes = 0;
batch->net = NULL;
+ batch->write_space_pending = false;
if (!sk)
return;
+ if (write_space_pending) {
+ vsk = vsock_sk(sk);
+ vsk->default_write_space(sk);
+ write_space = READ_ONCE(sk->sk_write_space);
+ if (write_space != vsk->default_write_space)
+ write_space(sk);
+ }
+
release_sock(sk);
sock_put(sk);
}
@@ -2027,6 +2050,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
.src = &src,
.dst = &dst,
.batchable = &batchable,
+ .batch = batch,
};
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
@@ -2063,11 +2087,15 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
start_batch = virtio_transport_recv_pkt_batchable(t, sk);
read_unlock_bh(&sk->sk_callback_lock);
+ if (start_batch)
+ batch->sk = sk;
+
ctx = (struct virtio_transport_rx_pkt_ctx) {
.net = net,
.src = &src,
.dst = &dst,
.batchable = start_batch ? &batchable : NULL,
+ .batch = start_batch ? batch : NULL,
};
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
if (start_batch && batchable) {
@@ -2075,12 +2103,16 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
batch->net = net;
batch->src = src;
batch->dst = dst;
- batch->sk = sk;
return;
}
- release_sock(sk);
- sock_put(sk);
+ if (start_batch) {
+ virtio_transport_rx_batch_finish(batch);
+ } else {
+ release_sock(sk);
+ sock_put(sk);
+ }
+
if (free_pkt)
kfree_skb(skb);
}
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock
2026-10-10 14:22 [PATCH net-next v2 0/5] vsock/virtio: reduce RX per-packet socket overhead Jia Jia
` (3 preceding siblings ...)
2026-10-10 14:22 ` [PATCH net-next v2 4/5] vsock/virtio: coalesce RX write-space notifications in lock batches Jia Jia
@ 2026-10-10 14:22 ` Jia Jia
2026-10-11 14:25 ` netdev-bot+sashiko
4 siblings, 1 reply; 9+ messages in thread
From: Jia Jia @ 2026-10-10 14:22 UTC (permalink / raw)
To: stefanha, sgarzare, netdev, virtualization, kvm
Cc: mst, jasowangio, eperezma, xuanzhuo, davem, edumazet, kuba,
pabeni, horms, linux-kernel, bpf, Jia Jia
An RX batch can make a socket readable while the worker still owns its
socket lock. Waking a reader at that point can make it immediately wait
for the same lock.
For the callback installed by sock_init_data(), record when the existing
low-watermark condition is met and issue one notification after the batch
releases the lock. Save the initial sock_def_readable() callback when the
AF_VSOCK socket is created because virtio_transport_common can be built as
a module and cannot refer to that unexported symbol directly.
When sk_data_ready or sk_write_space no longer matches that initial
callback, pass queued skbs to the current callback one skb at a time. The
handoff stops if the callback is the default again or the queue does not
shrink. After 64 successful callbacks, queue another bounded handoff if
data remains. vsock_bpf_update_proto() schedules the same handoff so an
attach or detach outside the worker still drains skbs already queued.
Always deliver a coalesced write-space event to the saved default callback
before sampling the callback state. A current replacement is invoked by
the handoff as well, so a callback change cannot hide the native writer
wakeup or pending sockmap work.
Sampling at batch finish takes sk_callback_lock once. The per-packet fast
path still uses READ_ONCE() and does not take that lock. Control packets,
state transitions, transport changes and non-batched sockets keep their
existing behavior.
Signed-off-by: Jia Jia <physicalmtea@gmail.com>
---
include/linux/virtio_vsock.h | 1 +
include/net/af_vsock.h | 8 +-
net/vmw_vsock/af_vsock.c | 103 ++++++++++++++++++++++++
net/vmw_vsock/virtio_transport_common.c | 73 ++++++++++++++---
net/vmw_vsock/vsock_bpf.c | 2 +
5 files changed, 174 insertions(+), 13 deletions(-)
diff --git a/include/linux/virtio_vsock.h b/include/linux/virtio_vsock.h
index 3a58120d9078..21a3460b940f 100644
--- a/include/linux/virtio_vsock.h
+++ b/include/linux/virtio_vsock.h
@@ -291,6 +291,7 @@ struct virtio_transport_rx_batch {
struct sockaddr_vm src;
struct sockaddr_vm dst;
bool write_space_pending;
+ bool data_ready_pending;
};
void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h
index 9d8ae62209ed..86d6e6ba3d9d 100644
--- a/include/net/af_vsock.h
+++ b/include/net/af_vsock.h
@@ -63,8 +63,11 @@ struct vsock_sock {
u32 peer_shutdown;
bool sent_request;
bool ignore_connecting_rst;
- /* Initial callback, used to identify replacements. */
+ /* Initial callbacks, used to identify replacements. */
+ void (*default_data_ready)(struct sock *sk);
void (*default_write_space)(struct sock *sk);
+ struct delayed_work rx_cb_work;
+ bool rx_cb_retried;
/* Protected by lock_sock(sk) */
u64 buffer_size;
@@ -80,6 +83,9 @@ s64 vsock_stream_has_data(struct vsock_sock *vsk);
s64 vsock_stream_has_space(struct vsock_sock *vsk);
struct sock *vsock_create_connected(struct sock *parent);
void vsock_data_ready(struct sock *sk);
+bool vsock_rx_cb_is_native(struct sock *sk);
+void vsock_rx_callback_kick(struct sock *sk);
+void vsock_rx_callback_handoff(struct sock *sk);
/**** TRANSPORT ****/
diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
index fe7f49da6c4b..df6faee0bd37 100644
--- a/net/vmw_vsock/af_vsock.c
+++ b/net/vmw_vsock/af_vsock.c
@@ -931,6 +931,107 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
return retval;
}
+#define VSOCK_RX_CB_HANDOFF_MAX 64
+
+static void vsock_rx_callback_queue(struct sock *sk, unsigned long delay)
+{
+ struct vsock_sock *vsk = vsock_sk(sk);
+
+ sock_hold(sk);
+ if (!schedule_delayed_work(&vsk->rx_cb_work, delay))
+ sock_put(sk);
+}
+
+void vsock_rx_callback_kick(struct sock *sk)
+{
+ vsock_rx_callback_queue(sk, 0);
+}
+
+/* Caller holds sk_callback_lock. */
+bool vsock_rx_cb_is_native(struct sock *sk)
+{
+ struct vsock_sock *vsk = vsock_sk(sk);
+
+ return READ_ONCE(sk->sk_prot) == sk->sk_prot_creator &&
+ READ_ONCE(sk->sk_data_ready) == vsk->default_data_ready &&
+ READ_ONCE(sk->sk_write_space) == vsk->default_write_space;
+}
+EXPORT_SYMBOL_GPL(vsock_rx_cb_is_native);
+
+/* Caller holds lock_sock(). */
+void vsock_rx_callback_handoff(struct sock *sk)
+{
+ struct vsock_sock *vsk = vsock_sk(sk);
+ void (*write_space)(struct sock *sk);
+ void (*data_ready)(struct sock *sk);
+ bool wrote = false;
+ s64 before, after;
+ int i;
+
+ if (sock_flag(sk, SOCK_DEAD) || !vsk->transport)
+ return;
+
+ read_lock_bh(&sk->sk_callback_lock);
+ data_ready = READ_ONCE(sk->sk_data_ready);
+ if (READ_ONCE(sk->sk_prot) != sk->sk_prot_creator &&
+ data_ready == vsk->default_data_ready) {
+ read_unlock_bh(&sk->sk_callback_lock);
+ /*
+ * start_verdict publishes sk_data_ready after the proto swap.
+ * Retry once before treating this as a stable non-RX setup.
+ */
+ if (!vsk->rx_cb_retried) {
+ vsk->rx_cb_retried = true;
+ vsock_rx_callback_queue(sk, 1);
+ return;
+ }
+ vsk->rx_cb_retried = false;
+ data_ready(sk);
+ return;
+ }
+ read_unlock_bh(&sk->sk_callback_lock);
+ vsk->rx_cb_retried = false;
+
+ for (i = 0; i < VSOCK_RX_CB_HANDOFF_MAX; i++) {
+ read_lock_bh(&sk->sk_callback_lock);
+ data_ready = READ_ONCE(sk->sk_data_ready);
+ write_space = READ_ONCE(sk->sk_write_space);
+ read_unlock_bh(&sk->sk_callback_lock);
+
+ if (!wrote && write_space != vsk->default_write_space) {
+ write_space(sk);
+ wrote = true;
+ }
+ if (data_ready == vsk->default_data_ready) {
+ data_ready(sk);
+ break;
+ }
+ before = vsock_stream_has_data(vsk);
+ if (before <= 0)
+ break;
+ data_ready(sk);
+ after = vsock_stream_has_data(vsk);
+ if (after >= before)
+ break;
+ if (i == VSOCK_RX_CB_HANDOFF_MAX - 1 && after > 0)
+ vsock_rx_callback_queue(sk, 0);
+ }
+}
+EXPORT_SYMBOL_GPL(vsock_rx_callback_handoff);
+
+static void vsock_rx_callback_work(struct work_struct *work)
+{
+ struct vsock_sock *vsk = container_of(work, struct vsock_sock,
+ rx_cb_work.work);
+ struct sock *sk = sk_vsock(vsk);
+
+ lock_sock(sk);
+ if (!sock_flag(sk, SOCK_DEAD))
+ vsock_rx_callback_handoff(sk);
+ release_sock(sk);
+ sock_put(sk);
+}
+
static void vsock_connect_timeout(struct work_struct *work);
static struct sock *__vsock_create(struct net *net,
@@ -958,6 +1059,7 @@ static struct sock *__vsock_create(struct net *net,
sk->sk_type = type;
vsk = vsock_sk(sk);
+ vsk->default_data_ready = sk->sk_data_ready;
vsk->default_write_space = sk->sk_write_space;
vsock_addr_init(&vsk->local_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
vsock_addr_init(&vsk->remote_addr, VMADDR_CID_ANY, VMADDR_PORT_ANY);
@@ -976,6 +1078,7 @@ static struct sock *__vsock_create(struct net *net,
WRITE_ONCE(vsk->peer_shutdown, 0);
INIT_DELAYED_WORK(&vsk->connect_work, vsock_connect_timeout);
INIT_DELAYED_WORK(&vsk->pending_work, vsock_pending_work);
+ INIT_DELAYED_WORK(&vsk->rx_cb_work, vsock_rx_callback_work);
psk = parent ? vsock_sk(parent) : NULL;
if (parent) {
diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
index f0398ae2200d..5499cd1d8a25 100644
--- a/net/vmw_vsock/virtio_transport_common.c
+++ b/net/vmw_vsock/virtio_transport_common.c
@@ -1583,10 +1583,12 @@ out:
static int
virtio_transport_recv_connected(struct sock *sk,
- struct sk_buff *skb)
+ struct sk_buff *skb,
+ bool *data_ready_pending)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
struct vsock_sock *vsk = vsock_sk(sk);
+ void (*data_ready)(struct sock *sk);
int err = 0;
switch (le16_to_cpu(hdr->op)) {
@@ -1602,7 +1604,23 @@ virtio_transport_recv_connected(struct sock *sk,
vsock_remove_sock(vsk);
break;
}
- vsock_data_ready(sk);
+ if (!data_ready_pending) {
+ vsock_data_ready(sk);
+ } else {
+ data_ready = READ_ONCE(sk->sk_data_ready);
+ if (data_ready == vsk->default_data_ready) {
+ if (!*data_ready_pending &&
+ (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+ sock_flag(sk, SOCK_DONE)))
+ *data_ready_pending = true;
+ } else {
+ /* Use the callback seen for this packet. */
+ *data_ready_pending = false;
+ if (vsock_stream_has_data(vsk) >= sk->sk_rcvlowat ||
+ sock_flag(sk, SOCK_DONE))
+ data_ready(sk);
+ }
+ }
return err;
case VIRTIO_VSOCK_OP_CREDIT_REQUEST:
virtio_transport_send_credit_update(vsk);
@@ -1836,6 +1854,7 @@ struct virtio_transport_rx_pkt_ctx {
const struct sockaddr_vm *dst;
bool *batchable;
struct virtio_transport_rx_batch *batch;
+ bool defer_data_ready;
};
static bool
@@ -1909,7 +1928,9 @@ virtio_transport_recv_pkt_locked(struct virtio_transport *t,
kfree_skb(skb);
break;
case TCP_ESTABLISHED:
- virtio_transport_recv_connected(sk, skb);
+ virtio_transport_recv_connected(sk, skb,
+ ctx->defer_data_ready && ctx->batch ?
+ &ctx->batch->data_ready_pending : NULL);
break;
case TCP_CLOSING:
virtio_transport_recv_disconnecting(sk, skb);
@@ -1978,33 +1999,45 @@ EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
* Finish the RX batch.
* For a non-empty batch, the caller must hold the socket lock acquired with
* lock_sock(). This function releases the lock and the batch's lookup
- * reference.
- * An empty batch is a no-op.
+ * reference. An empty batch is a no-op.
*/
void virtio_transport_rx_batch_finish(struct virtio_transport_rx_batch *batch)
{
bool write_space_pending = batch->write_space_pending;
- void (*write_space)(struct sock *sk);
+ bool data_ready_pending = batch->data_ready_pending;
struct sock *sk = batch->sk;
struct vsock_sock *vsk;
+ bool native;
batch->sk = NULL;
batch->pkts = 0;
batch->bytes = 0;
batch->net = NULL;
batch->write_space_pending = false;
+ batch->data_ready_pending = false;
if (!sk)
return;
- if (write_space_pending) {
- vsk = vsock_sk(sk);
+ vsk = vsock_sk(sk);
+ if (write_space_pending)
vsk->default_write_space(sk);
- write_space = READ_ONCE(sk->sk_write_space);
- if (write_space != vsk->default_write_space)
- write_space(sk);
+
+ read_lock_bh(&sk->sk_callback_lock);
+ native = vsock_rx_cb_is_native(sk);
+ read_unlock_bh(&sk->sk_callback_lock);
+
+ if (native) {
+ release_sock(sk);
+ if (data_ready_pending)
+ vsk->default_data_ready(sk);
+ sock_put(sk);
+ return;
}
+ if (write_space_pending || data_ready_pending)
+ vsock_rx_callback_handoff(sk);
+
release_sock(sk);
sock_put(sk);
}
@@ -2015,9 +2048,9 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
struct virtio_transport_rx_batch *batch)
{
struct virtio_vsock_hdr *hdr = virtio_vsock_hdr(skb);
+ bool batchable, defer_data_ready, start_batch;
struct virtio_transport_rx_pkt_ctx ctx;
struct sockaddr_vm src, dst;
- bool batchable, start_batch;
struct sock *sk;
bool free_pkt;
@@ -2038,6 +2071,17 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
vsock_addr_equals_addr(&batch->dst, &dst) &&
virtio_transport_recv_pkt_batchable(t, batch->sk)) {
sk = batch->sk;
+ defer_data_ready = READ_ONCE(sk->sk_data_ready) ==
+ vsock_sk(sk)->default_data_ready;
+ if (unlikely(batch->data_ready_pending && !defer_data_ready)) {
+ /*
+ * BPF map updates can replace callbacks while lock_sock()
+ * remains held. Finish the old notification first.
+ */
+ virtio_transport_rx_batch_finish(batch);
+ goto lookup;
+ }
+
if (!skb_set_owner_sk_safe(skb, sk)) {
WARN_ONCE(1, "receiving vsock socket has sk_refcnt == 0\n");
virtio_transport_rx_batch_finish(batch);
@@ -2051,6 +2095,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
.dst = &dst,
.batchable = &batchable,
.batch = batch,
+ .defer_data_ready = defer_data_ready,
};
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
@@ -2064,6 +2109,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
virtio_transport_rx_batch_finish(batch);
}
+lookup:
sk = virtio_transport_recv_pkt_find_socket(skb, &src, &dst, net);
if (!sk) {
virtio_transport_rx_batch_finish(batch);
@@ -2089,6 +2135,8 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
if (start_batch)
batch->sk = sk;
+ defer_data_ready = start_batch &&
+ READ_ONCE(sk->sk_data_ready) == vsock_sk(sk)->default_data_ready;
ctx = (struct virtio_transport_rx_pkt_ctx) {
.net = net,
@@ -2096,6 +2144,7 @@ void virtio_transport_recv_pkt_batch(struct virtio_transport *t,
.dst = &dst,
.batchable = start_batch ? &batchable : NULL,
.batch = start_batch ? batch : NULL,
+ .defer_data_ready = defer_data_ready,
};
free_pkt = virtio_transport_recv_pkt_locked(t, skb, sk, &ctx);
if (start_batch && batchable) {
diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
index 9049d2648646..7770a6c51dbe 100644
--- a/net/vmw_vsock/vsock_bpf.c
+++ b/net/vmw_vsock/vsock_bpf.c
@@ -154,6 +154,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
if (restore) {
sk->sk_write_space = psock->saved_write_space;
sock_replace_proto(sk, psock->sk_proto);
+ vsock_rx_callback_kick(sk);
return 0;
}
@@ -166,6 +167,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
vsock_bpf_check_needs_rebuild(psock->sk_proto);
sock_replace_proto(sk, &vsock_bpf_prot);
+ vsock_rx_callback_kick(sk);
return 0;
}
--
2.34.1
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock
2026-10-10 14:22 ` [PATCH net-next v2 5/5] vsock/virtio: defer RX readable notifications until batch unlock Jia Jia
@ 2026-10-11 14:25 ` netdev-bot+sashiko
0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-11 14:25 UTC (permalink / raw)
To: physicalmtea
Cc: stefanha, sgarzare, netdev, virtualization, kvm, mst, jasowangio,
eperezma, xuanzhuo, davem, edumazet, kuba, pabeni, horms,
linux-kernel, bpf
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 (1) · Low: 2
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The new attach-time handoff passes partially read skbs to
read_skb, which ignores the read offset.
- [Medium] Sockmap attach/detach now drains queued data on every vsock
socket, and the commit message wrongly says non-batched sockets keep…
- [Low] The commit message says "Sampling at batch finish takes
sk_callback_lock once." That holds only on the native path.
- [Low] vsock_rx_callback_handoff() calls sk_data_ready without the
sk_rcvlowat/SOCK_DONE check that vsock_data_ready() and the per-packet…
Pre-existing issues:
- [Medium] vsock_rx_callback_handoff() reads sk_data_ready under
read_lock_bh(&sk->sk_callback_lock), releases the lock, and only then…
--
vsock/virtio: defer RX readable notifications until batch unlock
During virtio vsock RX batches, this commit holds back the native
sock_def_readable() notification until the batch has dropped the socket
lock. If sk_data_ready or sk_write_space has been replaced, for example
by sockmap, a new vsock_rx_callback_handoff() passes queued skbs to the
current callback one at a time. vsock_bpf_update_proto() now schedules
that handoff on sockmap attach and detach.
> diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
> index fe7f49da6c4bc..df6faee0bd37a 100644
> --- a/net/vmw_vsock/af_vsock.c
> +++ b/net/vmw_vsock/af_vsock.c
> @@ -931,6 +931,107 @@ static int __vsock_bind(struct sock *sk, struct sockaddr_vm *addr)
[ ... ]
> +/* Caller holds lock_sock(). */
> +void vsock_rx_callback_handoff(struct sock *sk)
> +{
> + struct vsock_sock *vsk = vsock_sk(sk);
> + void (*write_space)(struct sock *sk);
> + void (*data_ready)(struct sock *sk);
> + bool wrote = false;
> + s64 before, after;
> + int i;
> +
> + if (sock_flag(sk, SOCK_DEAD) || !vsk->transport)
> + return;
> +
> + read_lock_bh(&sk->sk_callback_lock);
> + data_ready = READ_ONCE(sk->sk_data_ready);
> + if (READ_ONCE(sk->sk_prot) != sk->sk_prot_creator &&
> + data_ready == vsk->default_data_ready) {
> + read_unlock_bh(&sk->sk_callback_lock);
> + /*
> + * start_verdict publishes sk_data_ready after the proto swap.
> + * Retry once before treating this as a stable non-RX setup.
> + */
> + if (!vsk->rx_cb_retried) {
> + vsk->rx_cb_retried = true;
> + vsock_rx_callback_queue(sk, 1);
> + return;
> + }
> + vsk->rx_cb_retried = false;
> + data_ready(sk);
> + return;
> + }
> + read_unlock_bh(&sk->sk_callback_lock);
> + vsk->rx_cb_retried = false;
> +
> + for (i = 0; i < VSOCK_RX_CB_HANDOFF_MAX; i++) {
> + read_lock_bh(&sk->sk_callback_lock);
> + data_ready = READ_ONCE(sk->sk_data_ready);
> + write_space = READ_ONCE(sk->sk_write_space);
> + read_unlock_bh(&sk->sk_callback_lock);
[Severity: Medium]
This is a pre-existing issue, but sk_callback_lock here only covers
reading the pointers, not calling them. Can a concurrent sockmap delete
restore sk_data_ready and clear the psock between this read_unlock_bh()
and the data_ready(sk) call below?
The delete path takes only write_lock_bh(&sk->sk_callback_lock), not
lock_sock():
__sock_map_delete()
sock_map_unref()
sock_map_del_link() / sk_psock_drop()
In that case the stale sk_psock_verdict_data_ready() still dequeues an
skb through virtio_transport_read_skb(). That lowers rx_bytes and sends a
credit update. sk_psock_verdict_recv() then finds no psock:
net/core/skmsg.c:sk_psock_verdict_recv() {
...
if (unlikely(!psock)) {
len = 0;
tcp_eat_skb(sk, skb);
sock_drop(sk, skb);
goto out;
}
...
}
So the skb is dropped after it has already been credited to the peer.
The baseline vsock_data_ready() has the same window. It calls
sk->sk_data_ready(sk) under lock_sock() without sk_callback_lock, and
tcp_data_ready() has the same issue. This patch adds more call sites
with the same pattern: the attach-kicked work item and the batch-finish
handoff. Taking the read lock here can also make it look as if the call
is protected when it is not.
> +
> + if (!wrote && write_space != vsk->default_write_space) {
> + write_space(sk);
> + wrote = true;
> + }
> + if (data_ready == vsk->default_data_ready) {
> + data_ready(sk);
> + break;
> + }
> + before = vsock_stream_has_data(vsk);
> + if (before <= 0)
> + break;
> + data_ready(sk);
[Severity: Low]
Neither of these data_ready(sk) calls checks sk->sk_rcvlowat or
SOCK_DONE, unlike vsock_data_ready() and the per-packet path in
virtio_transport_recv_connected(). Is that intended?
For a replaced callback such as the sockmap verdict, data_ready(sk) runs
whenever vsock_stream_has_data() > 0. The verdict program can then see
data below SO_RCVLOWAT that the per-packet path would hold back.
For the default callback the call is unconditional, both here and in the
retry branch above:
vsk->rx_cb_retried = false;
data_ready(sk);
return;
Doesn't this wake readers when no readable event was pending? That
happens on every attach and restore kick from vsock_bpf_update_proto().
It also happens when virtio_transport_rx_batch_finish() enters the
handoff with only write_space_pending set.
> + after = vsock_stream_has_data(vsk);
> + if (after >= before)
> + break;
[Severity: High]
Can this hand a partially read skb to read_skb?
A native stream read can consume only part of the head skb. In that
case virtio_transport_stream_do_dequeue() advances the private offset,
leaves the skb queued, and lowers rx_bytes only by the bytes copied:
VIRTIO_VSOCK_SKB_CB(skb)->offset += bytes;
...
virtio_transport_dec_rx_pkt(vvs, bytes, dequeued);
If the socket is then added to a sockmap with a verdict program, the new
kick in vsock_bpf_update_proto() runs this loop:
vsock_rx_callback_work()
vsock_rx_callback_handoff()
sk_psock_verdict_data_ready()
vsock_read_skb()
virtio_transport_read_skb()
virtio_transport_read_skb() does not look at the offset:
pkt_len = le32_to_cpu(hdr->len);
virtio_transport_dec_rx_pkt(vvs, pkt_len, pkt_len);
...
return recv_actor(sk, skb);
Take a 1000 byte skb with 400 bytes already read, so rx_bytes is 600.
The verdict program sees all 1000 bytes, 400 of them a second time. The
u32 rx_bytes then wraps to 4294966896.
The after >= before check above sees no progress and stops. But
vsock_stream_has_data() now reports about 4GB, so poll stays readable.
The next native read hits WARN_ONCE("rx_queue is empty, but rx_bytes is
non-zero") and returns -EFAULT.
The offset handling in virtio_transport_read_skb() predates this patch.
In the baseline the same corruption needs a partial read, then an
attach, then another packet from the peer. With this patch the extra
packet is no longer needed, and the new attach-time drain depends on
read_skb handling already-queued skbs correctly.
> + if (i == VSOCK_RX_CB_HANDOFF_MAX - 1 && after > 0)
> + vsock_rx_callback_queue(sk, 0);
> + }
> +}
[ ... ]
> diff --git a/net/vmw_vsock/virtio_transport_common.c b/net/vmw_vsock/virtio_transport_common.c
> index e5637291f3185..6b96d8ac0d598 100644
> --- a/net/vmw_vsock/virtio_transport_common.c
> +++ b/net/vmw_vsock/virtio_transport_common.c
[ ... ]
> @@ -1986,33 +2007,45 @@ EXPORT_SYMBOL_GPL(virtio_transport_recv_pkt);
[ ... ]
> - if (write_space_pending) {
> - vsk = vsock_sk(sk);
> + vsk = vsock_sk(sk);
> + if (write_space_pending)
> vsk->default_write_space(sk);
> - write_space = READ_ONCE(sk->sk_write_space);
> - if (write_space != vsk->default_write_space)
> - write_space(sk);
> +
> + read_lock_bh(&sk->sk_callback_lock);
> + native = vsock_rx_cb_is_native(sk);
> + read_unlock_bh(&sk->sk_callback_lock);
> +
> + if (native) {
> + release_sock(sk);
> + if (data_ready_pending)
> + vsk->default_data_ready(sk);
> + sock_put(sk);
> + return;
> }
>
> + if (write_space_pending || data_ready_pending)
> + vsock_rx_callback_handoff(sk);
[Severity: Low]
The commit message says:
Sampling at batch finish takes sk_callback_lock once.
Does that hold on the non-native path? sk_callback_lock is taken once
here for vsock_rx_cb_is_native(). vsock_rx_callback_handoff() then takes
it again at the top. It takes it once more on each loop iteration, up
to VSOCK_RX_CB_HANDOFF_MAX (64) times, and the requeued work can do all
of this again.
[ ... ]
> diff --git a/net/vmw_vsock/vsock_bpf.c b/net/vmw_vsock/vsock_bpf.c
> index 9049d26486460..7770a6c51dbe5 100644
> --- a/net/vmw_vsock/vsock_bpf.c
> +++ b/net/vmw_vsock/vsock_bpf.c
> @@ -154,6 +154,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
> if (restore) {
> sk->sk_write_space = psock->saved_write_space;
> sock_replace_proto(sk, psock->sk_proto);
> + vsock_rx_callback_kick(sk);
> return 0;
> }
>
> @@ -166,6 +167,7 @@ int vsock_bpf_update_proto(struct sock *sk, struct sk_psock *psock, bool restore
>
> vsock_bpf_check_needs_rebuild(psock->sk_proto);
> sock_replace_proto(sk, &vsock_bpf_prot);
> + vsock_rx_callback_kick(sk);
> return 0;
> }
[Severity: Medium]
These kicks run for every AF_VSOCK socket added to or removed from a
sockmap or sockhash, not only for sockets in a virtio RX batch. Is this
change in behaviour intended?
RX batching exists only in the guest virtio transport
(virtio_transport_rx_work() -> virtio_transport_recv_pkt_batch()).
vhost_vsock and vsock_loopback always use virtio_transport_recv_pkt().
Both still set .read_skb = virtio_transport_read_skb, so their sockets
reach this code too. The same is true of SOCK_SEQPACKET sockets, which
virtio_transport_recv_pkt_batchable() rejects.
Before this patch, sk_data_ready was not called by either path:
sock_map_link() -> sock_map_init_proto() -> vsock_bpf_update_proto()
sk_psock_start_verdict()
Data queued before attach only reached the verdict program when a later
packet arrived.
Now the kick pushes that data through sk_psock_verdict_data_ready(), up
to 64 skbs per pass, and requeues itself if data remains. The verdict
program can redirect or drop data that arrived before attach, where it
used to stay for the native reader.
If only the prot was swapped, for example with a msg_parser-only map,
the handoff calls the default callback unconditionally after one retry.
That adds an extra wakeup on every attach and detach.
The commit message says "non-batched sockets keep their existing
behavior". It also says the handoff is scheduled "so an attach or detach
outside the worker still drains skbs already queued".
Neither statement seems to fit what the code does: this is a new sockmap
behaviour for every vsock transport that has read_skb, and the subject
line does not mention it. Could this be split out, or at least
described in the commit message?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261010142247.99223-1-physicalmtea%40gmail.com
^ permalink raw reply [flat|nested] 9+ messages in thread