From: Stephen Hemminger <stephen@networkplumber.org>
To: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
Cc: <dev@dpdk.org>, Jakub Grajciar <jgrajcia@cisco.com>,
David Marchand <david.marchand@redhat.com>,
Thomas Monjalon <thomas@monjalon.net>, <stable@dpdk.org>
Subject: Re: [PATCH v2] net/memif: redesign control channel teardown
Date: Mon, 9 Nov 2026 14:16:45 -0800 [thread overview]
Message-ID: <20261109141645.78f3a7ce@phoenix.local> (raw)
In-Reply-To: <20261105112026.432015-1-sriram.yagnaraman@ericsson.com>
On Thu, 5 Nov 2026 12:20:26 +0100
Sriram Yagnaraman <sriram.yagnaraman@ericsson.com> wrote:
> The control channel can be torn down from two threads: the EAL interrupt
> thread (memif_intr_handler on peer disconnect/error) and an application
> thread (dev_stop/dev_close -> memif_disconnect). Both race to unregister
> the callback and free the channel, which memif_pmd_autotest SIGSEGVs on
> during EAL teardown (seen on amd64 and arm64).
>
> Rework teardown around a single owner:
>
> - memif_disconnect() clears pmd->cc under cc_lock; whoever clears it
> owns the free.
> - Device teardown (link down, flags, queue eventfds, regions) moves to
> memif_disconnect_queues(), run by whichever thread owns the
> disconnect, so flag/link state is left consistent for reconnect.
> - memif_cc_release() frees the channel: unregister_pending() on the
> interrupt thread (deferred free, drops the fd from epoll after
> dispatch), unregister_sync() on an application thread.
> - memif_intr_handler() self-unregisters on disconnect so the EOF
> (level-triggered) socket fd cannot re-fire (no livelock).
>
> Reported-by: Stephen Hemminger <stephen@networkplumber.org>
> Suggested-by: Stephen Hemminger <stephen@networkplumber.org>
> Signed-off-by: Sriram Yagnaraman <sriram.yagnaraman@ericsson.com>
> ---
AI found an error here..
[PATCH v2] net/memif: redesign control channel teardown
Error: the application thread no longer waits for the handler once the
handler has taken ownership, so the reported SIGSEGV is still reachable.
owned = (pmd->cc == cc);
if (owned)
pmd->cc = NULL;
...
if (owned)
memif_disconnect_queues(cc->dev);
Peer hangs up, handler clears pmd->cc and runs
memif_disconnect_queues() after dropping cc_lock. Meanwhile dev_stop
-> memif_disconnect() finds cc == NULL, skips memif_cc_release() and
calls memif_disconnect_queues(dev) concurrently. memif_free_regions()
then does munmap/close/rte_free on the same regions from two threads.
dev_close followed by port release frees dev->data while the handler
is still inside memif_disconnect_queues(cc->dev), which is the original
memif_pmd_autotest crash. The "Idempotent ... safe to run from
whichever thread" comment is not true; idempotent is not thread safe.
Do the device teardown under cc_lock in both paths, and have the
handler touch only cc after it drops the lock:
/* handler */
rte_spinlock_lock(&pmd->cc_lock);
owned = (pmd->cc == cc);
if (owned) {
pmd->cc = NULL;
memif_disconnect_queues(cc->dev);
}
rte_spinlock_unlock(&pmd->cc_lock);
rte_intr_callback_unregister_pending(cc->intr_handle,
memif_intr_handler, cc,
owned ? memif_intr_unregister_handler : NULL);
/* memif_disconnect(), after memif_cc_release() */
rte_spinlock_lock(&pmd->cc_lock);
memif_disconnect_queues(dev);
rte_spinlock_unlock(&pmd->cc_lock);
Error: pmd->cc is set to NULL while the handler may still be running
and the handler dereferences pmd->cc.
cc = pmd->cc;
pmd->cc = NULL;
The application thread then waits in rte_intr_callback_unregister_sync()
on the assumption the handler is safe. It is not: memif_msg_receive()
on HELLO/INIT/ADD_REGION/ADD_RING/CONNECT calls memif_msg_enq_ack(),
memif_msg_enq_init(), memif_msg_enq_add_region(),
memif_msg_enq_add_ring(), memif_msg_enq_connect() and
memif_msg_enq_connected(), all of which do
struct memif_msg_queue_elt *e = memif_msg_enq(pmd->cc);
and memif_msg_enq() does TAILQ_INSERT_TAIL(&cc->msg_queue, ...) on
NULL. Pass the channel down instead of re-reading pmd->cc on the
interrupt thread, e.g. memif_msg_enq_ack(cc) using cc->dev for pmd.
The channel stays valid until unregister_sync() returns.
Error: memif_cc_release() leaks the channel when memif_disconnect()
runs on the interrupt thread for a source that is not dispatching.
if (rte_thread_is_intr()) {
ret = rte_intr_callback_unregister_pending(ih,
memif_bad_desc_disconnect() is an EAL alarm, and alarms run on the
interrupt thread. The active source is the alarm timerfd, not the
control socket, so unregister_pending() returns -EAGAIN. pmd->cc is
already NULL, the callback stays registered and cc is never freed.
When the peer later closes, the handler sees owned == false and
passes a NULL free callback, so cc and its fd leak; if the port was
released first, the handler dereferences cc->dev->data after free.
The old code tried rte_intr_callback_unregister() first. Keep that:
if (rte_thread_is_intr()) {
ret = rte_intr_callback_unregister(ih,
memif_intr_handler, cc);
if (ret == -EAGAIN)
ret = rte_intr_callback_unregister_pending(ih,
memif_intr_handler, cc,
memif_intr_unregister_handler);
else if (ret > 0)
memif_intr_unregister_handler(ih, cc);
}
Warning: on a protocol error the peer no longer gets the DISCONNECT
message with the reason.
memif_disconnect_queues(cc->dev);
memif_msg_receive() queues a disconnect ("Invalid message size",
"Unknown message type", "Incorrect secret", ...) and returns -1, which
goes straight to the disconnect label. The old code called
memif_disconnect(), which dropped the other queued messages and sent
the DISCONNECT. The new handler path never calls
memif_msg_send_from_queue(), and memif_intr_unregister_handler() frees
the queued message. The peer now sees a bare EOF. Flush the
disconnect message in the owned branch before the teardown.
Warning: v1 had "Fixes: 09c7e63a71f9" and "Cc: stable@dpdk.org"; v2
dropped both. The race is in released versions; restore them.
Info: pre-existing, not introduced by this patch. The single-owner
model needs every write of pmd->cc under cc_lock, but
memif_msg_receive_init() still does
pmd->cc = cc;
without the lock. A server port being stopped can have a new channel
attached after memif_disconnect() has already looked at pmd->cc.
next prev parent reply other threads:[~2026-11-09 22:16 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 17:03 memif test failures on next-net Stephen Hemminger
2026-10-30 10:22 ` [PATCH] net/memif: fix race between interrupt thread and stop Sriram Yagnaraman
2026-11-04 16:07 ` Thomas Monjalon
2026-11-05 11:23 ` Sriram Yagnaraman
2026-11-04 16:42 ` David Marchand
2026-11-05 10:08 ` Sriram Yagnaraman
2026-11-05 11:20 ` [PATCH v2] net/memif: redesign control channel teardown Sriram Yagnaraman
2026-11-09 22:16 ` Stephen Hemminger [this message]
2026-10-30 11:15 ` memif test failures on next-net Sriram Yagnaraman
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=20261109141645.78f3a7ce@phoenix.local \
--to=stephen@networkplumber.org \
--cc=david.marchand@redhat.com \
--cc=dev@dpdk.org \
--cc=jgrajcia@cisco.com \
--cc=sriram.yagnaraman@ericsson.com \
--cc=stable@dpdk.org \
--cc=thomas@monjalon.net \
/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