Re: [PATCH] net: use sync wakeups for socket error reports
From: Breno Leitao <leitao@debian.org>
Date: 2026-07-17 12:21:05
Also in:
lkml
Subsystem:
alpha port, generic include/asm header files, mips, networking [general], networking [sockets], parisc architecture, sparc + ultrasparc (sparc/sparc64), the rest · Maintainers:
Richard Henderson, Matt Turner, Magnus Lindholm, Arnd Bergmann, Thomas Bogendoerfer, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Kuniyuki Iwashima, Willem de Bruijn, "James E.J. Bottomley", Helge Deller, Andreas Larsson, Linus Torvalds
On Wed, Jul 08, 2026 at 06:32:02PM +0200, Eric Dumazet wrote:
Perhaps this SYNC heuristic should be a per-socket choice so that applications can decide what is best for them.
Something like this?
net: add SO_ERR_WAKE_SYNC for sync error-report wakeups
sock_def_error_report() wakes EPOLLERR waiters with
wake_up_interruptible_poll(), while sock_def_readable() and
sock_def_write_space() already pass the sync hint. A socket with
SO_TIMESTAMPING enabled delivers every TX and ACK timestamp through
sk_error_queue and raises EPOLLERR, so the error path wakes a sleeping
consumer very often.
Without the sync hint the scheduler often places the woken consumer on a
remote CPU, which costs a rescheduling IPI. Usama Arif measured 16,326
such IPIs/min on a 176-core host running a production workload with
SO_TIMESTAMPING enabled. [1]
Switching the error path to a sync wakeup unconditionally is not the
right fix, as there are different requirements for it to be async, see
[2].
Eric suggested that an options is to add SO_ERR_WAKE_SYNC so
applications choose per socket, so a consumer draining a high-rate error
queue can opt in to keep the wakeup local and drop the IPI, using socket
flag SO_ERR_WAKE_SYNC.
Link: https://lore.kernel.org/all/CANn89iLc1Bv_wmKvr_9mtGRM3gL7kgoy2Prr2SgtHY4C=ZgfBg@mail.gmail.com/ (local) [1]
Link: https://lore.kernel.org/netdev/20260526063650.952-1-xuewen.yan@unisoc.com/ (local) [2]
Suggested-by: Eric Dumazet [off-list ref]
Signed-off-by: Breno Leitao [off-list ref]
diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 5ef57f88df6b3..2a3c27aaf4e95 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h@@ -155,6 +155,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_ERR_WAKE_SYNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64
diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
index 72fb1b006da93..00f31c74a63df 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h@@ -166,6 +166,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_ERR_WAKE_SYNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64
diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
index c16ec36dfee6b..db5b6cad17d49 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h@@ -147,6 +147,8 @@ #define SO_INQ 0x4052 #define SCM_INQ SO_INQ +#define SO_ERR_WAKE_SYNC 0x4053 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64
diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
index 71befa109e1cf..5e9ff3634265c 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h@@ -148,6 +148,8 @@ #define SO_INQ 0x005d #define SCM_INQ SO_INQ +#define SO_ERR_WAKE_SYNC 0x005e + #if !defined(__KERNEL__)
diff --git a/include/net/sock.h b/include/net/sock.h
index 51185222aac29..d8ee1dae8ecaf 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h@@ -1022,6 +1022,7 @@ enum sock_flags { SOCK_RCVMARK, /* Receive SO_MARK ancillary data with packet */ SOCK_RCVPRIORITY, /* Receive SO_PRIORITY ancillary data with packet */ SOCK_TIMESTAMPING_ANY, /* Copy of sk_tsflags & TSFLAGS_ANY */ + SOCK_ERR_WAKE_SYNC, /* Sync wakeup on error report, %SO_ERR_WAKE_SYNC */ }; #define SK_FLAGS_TIMESTAMP ((1UL << SOCK_TIMESTAMP) | (1UL << SOCK_TIMESTAMPING_RX_SOFTWARE))
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 53b5a8c002b1e..5527c9318b40e 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h@@ -150,6 +150,8 @@ #define SO_INQ 84 #define SCM_INQ SO_INQ +#define SO_ERR_WAKE_SYNC 85 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
diff --git a/net/core/sock.c b/net/core/sock.c
index 498a57f34f5b5..59caab6a7223a 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c@@ -1557,6 +1557,10 @@ int sk_setsockopt(struct sock *sk, int level, int optname, sock_valbool_flag(sk, SOCK_SELECT_ERR_QUEUE, valbool); break; + case SO_ERR_WAKE_SYNC: + sock_valbool_flag(sk, SOCK_ERR_WAKE_SYNC, valbool); + break; + case SO_PASSCRED: if (sk_may_scm_recv(sk)) sk->sk_scm_credentials = valbool;
@@ -2064,6 +2068,10 @@ int sk_getsockopt(struct sock *sk, int level, int optname, v.val = sock_flag(sk, SOCK_SELECT_ERR_QUEUE); break; + case SO_ERR_WAKE_SYNC: + v.val = sock_flag(sk, SOCK_ERR_WAKE_SYNC); + break; + #ifdef CONFIG_NET_RX_BUSY_POLL case SO_BUSY_POLL: v.val = READ_ONCE(sk->sk_ll_usec);
@@ -3641,8 +3649,12 @@ static void sock_def_error_report(struct sock *sk) rcu_read_lock(); wq = rcu_dereference(sk->sk_wq); - if (skwq_has_sleeper(wq)) - wake_up_interruptible_poll(&wq->wait, EPOLLERR); + if (skwq_has_sleeper(wq)) { + if (sock_flag(sk, SOCK_ERR_WAKE_SYNC)) + wake_up_interruptible_sync_poll(&wq->wait, EPOLLERR); + else + wake_up_interruptible_poll(&wq->wait, EPOLLERR); + } sk_wake_async_rcu(sk, SOCK_WAKE_IO, POLL_ERR); rcu_read_unlock(); }