From: Alexander Bluhm Subject: Re: tcp(4): use per-sockbuf mutex to protect `so_snd' socket buffer To: Vitaliy Makkoveev Cc: tech@openbsd.org Date: Tue, 24 Dec 2024 17:49:32 +0100 On Sat, Dec 21, 2024 at 08:16:05AM +0300, Vitaliy Makkoveev wrote: > This diff only unlocks sosend() path, all the rest is still locked > exclusively. > > Even for tcp(4) case, sosend() only checks `so_snd' free space and > sleeps if necessary, actual buffer handling happening within exclusively > locked PCB layer. In the PCB layer we don't need to protect read-only > access to `so_snd' because corresponding read-write access is serialized > by socket lock. > > sosend() needs to be serialized with somove(), so sotask() takes > sblock() on `so_snd' of spliced socket. This discards previous > protection of tcp(4) spliced sockets where solock() was used to prevent > concurrent unsplicing. This scheme was used to avoid sleep in sofree(). > > However, sleep is sofree() is possible. We have two cases: > > 1. Socket was not yet accept(2)ed. Such sockets can't be accessed from > the userland and can't be spliced. sofree() could be called only from > PCB layer and don't need to sleep. > > 2. Socket was accepted. While called form PCB layer, sofree() ignores it > because SS_NOFDREF bit is not set. Socket remains spliced, but without > PCB. Sockets without PCB can't be accessed from PCB layer, only from > userland. So, soclose()/sofree() thread needs to wait concurrent > soclose()/sofree() thread for spliced socket as is is already done for > udp(4) case. In such case it is safe to release solock() and sleep > within sofree(). > > Note, I intentionally left all "if (dosolock)" dances as is. While running your diff though my performnce tests, I got this panic twice. I have never seen it before, so I guess it is related. panic: softclock: invalid to_clock: 19668320 Stopped at db_enter+0x14: popq %rbp TID PID UID PRFLAGS PFLAGS CPU COMMAND 120146 91986 89 0x1100012 0 1 relayd 65124 13759 89 0x1100012 0 3 relayd 33160 19438 0 0x14000 0x200 2 softnet0 db_enter() at db_enter+0x14 panic(ffffffff82883be3) at panic+0xdd softclock(0) at softclock+0x1d5 softintr_dispatch(0) at softintr_dispatch+0xe6 Xsoftclock() at Xsoftclock+0x27 acpicpu_idle() at acpicpu_idle+0x2b9 sched_idle(ffffffff83593ff0) at sched_idle+0x298 end trace frame: 0x0, count: 8 https://www.openbsd.org/ddb.html describes the minimum info required in bug reports. Insufficient info makes it difficult to find and fix bugs. ddb{0}> x/s version version: OpenBSD 7.6-current (GENERIC.MP) #cvs : D2024.12.23.00.00.00: Tue Dec 24 14:53:39 CET 2024 root@ot15.obsd-lab.genua.de:/usr/src/sys/arch/amd64/compile/GENERIC.MP ddb{0}> show panic *cpu0: softclock: invalid to_clock: 19668320 ddb{0}> trace db_enter() at db_enter+0x14 panic(ffffffff82883be3) at panic+0xdd softclock(0) at softclock+0x1d5 softintr_dispatch(0) at softintr_dispatch+0xe6 Xsoftclock() at Xsoftclock+0x27 acpicpu_idle() at acpicpu_idle+0x2b9 sched_idle(ffffffff83593ff0) at sched_idle+0x298 end trace frame: 0x0, count: -7 ddb{0}> show register rdi 0xd rsi 0x10 rbp 0xffff80002f68d130 rbx 0xffff80000002d080 rdx 0x8000000000000000 rcx 0x282 rax 0x2d r8 0x101010101010101 r9 0 r10 0xadea166f5087a043 r11 0x2bfd2c462c554af2 r12 0xffffffff83594ba0 cpu_info_full_primary+0x2ba0 r13 0x1 r14 0 r15 0xffffffff82883be3 apollo_pio_rec+0x7109 rip 0xffffffff816bfef4 db_enter+0x14 cs 0x8 rflags 0x202 rsp 0xffff80002f68d130 ss 0x10 db_enter+0x14: popq %rbp ddb{0}> ps PID TID PPID UID S FLAGS WAIT COMMAND 73584 108417 0 0 2 0x14200 sosplice 7477 251218 60716 0 3 0x100082 kqread tcpbench 60716 124268 54242 0 3 0x10008a sigsusp ksh 54242 337229 12679 0 3 0x98 kqread sshd-session 12679 720 33354 0 3 0x92 kqread sshd-session 66636 19868 86133 0 3 0x100082 kqread tcpbench 86133 472778 49433 0 3 0x10008a sigsusp ksh 49433 13943 98462 0 3 0x98 kqread sshd-session 98462 302770 33354 0 3 0x92 kqread sshd-session 47843 33219 1 0 3 0x100083 ttyin getty 54288 103991 1 0 3 0x100098 kqread cron 81679 486005 1 0 3 0x100080 kqread iperf3 11419 283134 1 0 3 0x100080 kqread iperf3 98512 409047 1 99 3 0x1100090 kqread sndiod 69524 267896 1 110 3 0x100090 kqread sndiod 8245 321396 63281 95 3 0x1100092 kqread smtpd 83176 145209 63281 103 3 0x1100092 kqread smtpd 90889 188254 63281 95 3 0x1100092 kqread smtpd 34640 473980 63281 95 3 0x100092 kqread smtpd 92195 94566 63281 95 3 0x1100092 kqread smtpd 9893 144694 63281 95 3 0x1100092 kqread smtpd 63281 283730 1 0 3 0x100080 kqread smtpd 50606 379758 94394 89 3 0x1100092 kqread relayd 98300 523357 94394 89 3 0x1100092 kqread relayd 74167 429861 94394 89 3 0x1100092 kqread relayd 91986 120146 94394 89 7 0x1100012 relayd 13759 65124 94394 89 7 0x1100012 relayd 15622 250572 94394 89 3 0x1100092 kqread relayd 65837 358426 94394 89 3 0x1100092 kqread relayd 45864 99476 94394 89 2 0x1100092 relayd 94394 25533 1 0 3 0x80 kqread relayd 52723 463271 21242 91 3 0x92 kqread snmpd_metrics 28635 373814 21242 91 3 0x1100092 kqread snmpd 21242 215707 1 0 3 0x100080 kqread snmpd 33354 216155 1 0 3 0x88 kqread sshd 5174 452768 0 0 3 0x14280 nfsidl nfsio 61188 392826 0 0 3 0x14280 nfsidl nfsio 19241 42949 0 0 3 0x14280 nfsidl nfsio 29000 218874 0 0 3 0x14280 nfsidl nfsio 84747 311957 1 0 3 0x100080 kqread ntpd 46183 214005 80830 83 3 0x100092 kqread ntpd 80830 54208 1 83 3 0x1100092 kqread ntpd 98690 143438 50519 74 3 0x1100092 bpf pflogd 50519 519451 1 0 3 0x80 sbwait pflogd 97331 148497 57845 73 3 0x1100090 kqread syslogd 57845 372377 1 0 3 0x100082 sbwait syslogd 33406 307447 1 0 3 0x100080 kqread resolvd 28118 284489 39612 77 3 0x100092 kqread dhcpleased 2799 119056 39612 77 3 0x100092 kqread dhcpleased 39612 91167 1 0 3 0x80 kqread dhcpleased 25679 93938 3362 115 3 0x100092 kqread slaacd 76655 165419 3362 115 3 0x100092 kqread slaacd 3362 405831 1 0 3 0x100080 kqread slaacd 19284 370800 0 0 3 0x14200 bored smr 55324 8699 0 0 3 0x14200 pgzero zerothread 34421 61421 0 0 3 0x14200 aiodoned aiodoned 53137 331468 0 0 3 0x14200 syncer update 46196 161555 0 0 3 0x14200 cleaner cleaner 55676 492435 0 0 3 0x14200 reaper reaper 28210 68611 0 0 3 0x14200 pgdaemon pagedaemon 7723 258211 0 0 3 0x14200 usbtsk usbtask 63406 162199 0 0 3 0x14200 usbatsk usbatsk 67564 342517 0 0 3 0x40014200 acpi0 acpi0 69404 487689 0 0 3 0x40014200 idle3 98904 159131 0 0 3 0x40014200 idle2 38382 270651 0 0 3 0x40014200 idle1 46636 117171 0 0 3 0x14200 bored sensors 96859 56036 0 0 2 0x14200 softnet3 35522 499886 0 0 3 0x14200 bored softnet2 59486 420481 0 0 2 0x14200 softnet1 19438 33160 0 0 7 0x14200 softnet0 55477 305831 0 0 3 0x14200 bored systqmp 7303 480675 0 0 3 0x14200 bored systq 59682 29868 0 0 3 0x14200 tmoslp softclockmp 27811 510536 0 0 3 0x40014200 tmoslp softclock *22881 483268 0 0 7 0x40014200 idle0 1 58740 0 0 3 0x82 wait init 0 0 -1 0 3 0x10200 scheduler swapper ddb{1}> trace x86_ipi_db(ffff80002f4dbff0) at x86_ipi_db+0x16 x86_ipi_handler() at x86_ipi_handler+0x80 Xresume_lapic_ipi() at Xresume_lapic_ipi+0x27 __mp_release_all(ffffffff837f8000) at __mp_release_all+0x38 msleep(fffffd818af558a8,fffffd818af558a8,318,ffffffff82928096,aff) at msleep+0x128 kqueue_sleep(fffffd818af558a8,ffff80002f814c68) at kqueue_sleep+0xc7 kqueue_scan(ffff80002f814b68,8,ffff80002f814a60,ffff80002f814c68,ffff80002f739c00,ffff80002f814cbc) at kqueue_scan+0x111 sys_kevent(ffff80002f739c00,ffff80002f814dd0,ffff80002f814d40) at sys_kevent+0x3b5 syscall(ffff80002f814dd0) at syscall+0x620 Xsyscall() at Xsyscall+0x128 end of kernel end trace frame: 0x7e03cb938dd0, count: -10 ddb{2}> trace x86_ipi_db(ffff80002f4e4ff0) at x86_ipi_db+0x16 x86_ipi_handler() at x86_ipi_handler+0x80 Xresume_lapic_ipi() at Xresume_lapic_ipi+0x27 random() at random+0x4b clockrequest_advance_random(ffff80002f4e5da0,789680,3fffff) at clockrequest_advance_random+0x55 statclock(ffff80002f4e5da0,ffff80002f6ab1a0,0) at statclock+0x59 clockintr_dispatch(ffff80002f6ab1a0) at clockintr_dispatch+0x249 lapic_clockintr(0,0) at lapic_clockintr+0x43 Xresume_lapic_ltimer() at Xresume_lapic_ltimer+0x2a msleep_nsec(ffff80000002f000,ffff80000002f018,10,ffff80002f6ab250,246) at msleep_nsec+0xa taskq_next_work(ffff80000002f000,ffff80002f6ab2b0) at taskq_next_work+0x71 taskq_thread(ffff80000002f000) at taskq_thread+0x10b end trace frame: 0x0, count: -12 ddb{3}> trace x86_ipi_db(ffff80002f4edff0) at x86_ipi_db+0x16 x86_ipi_handler() at x86_ipi_handler+0x80 Xresume_lapic_ipi() at Xresume_lapic_ipi+0x27 schedclock(ffff80002f761458) at schedclock+0xce statclock(ffff80002f4eeda0,ffff80002f80e510,0) at statclock+0x1b1 clockintr_dispatch(ffff80002f80e510) at clockintr_dispatch+0x249 lapic_clockintr(0,0) at lapic_clockintr+0x43 Xresume_lapic_ltimer() at Xresume_lapic_ltimer+0x2a msleep_nsec(fffffd8190739da8,fffffd8190739da8,318,ffffffff82928096,68ca4f280) at msleep_nsec+0x8e kqueue_sleep(fffffd8190739da8,ffff80002f80e968) at kqueue_sleep+0xc7 kqueue_scan(ffff80002f80e868,8,ffff80002f80e760,ffff80002f80e968,ffff80002f761458,ffff80002f80e9bc) at kqueue_scan+0x111 sys_kevent(ffff80002f761458,ffff80002f80ead0,ffff80002f80ea40) at sys_kevent+0x3b5 syscall(ffff80002f80ead0) at syscall+0x620 Xsyscall() at Xsyscall+0x128 end of kernel end trace frame: 0x7518ca381aa0, count: -14 > Index: sys/kern/uipc_socket.c > =================================================================== > RCS file: /cvs/src/sys/kern/uipc_socket.c,v > diff -u -p -r1.347 uipc_socket.c > --- sys/kern/uipc_socket.c 19 Dec 2024 22:11:35 -0000 1.347 > +++ sys/kern/uipc_socket.c 21 Dec 2024 04:12:02 -0000 > @@ -153,27 +153,8 @@ soalloc(const struct protosw *prp, int w > TAILQ_INIT(&so->so_q0); > TAILQ_INIT(&so->so_q); > > - switch (dp->dom_family) { > - case AF_INET: > - case AF_INET6: > - switch (prp->pr_type) { > - case SOCK_RAW: > - case SOCK_DGRAM: > - so->so_snd.sb_flags |= SB_MTXLOCK; > - /* FALLTHROUGH */ > - case SOCK_STREAM: > - so->so_rcv.sb_flags |= SB_MTXLOCK; > - break; > - } > - break; > - case AF_KEY: > - case AF_ROUTE: > - case AF_UNIX: > - case AF_FRAME: > - so->so_snd.sb_flags |= SB_MTXLOCK; > - so->so_rcv.sb_flags |= SB_MTXLOCK; > - break; > - } > + so->so_snd.sb_flags |= SB_MTXLOCK; > + so->so_rcv.sb_flags |= SB_MTXLOCK; > > return (so); > } > @@ -327,17 +308,13 @@ sofree(struct socket *so, int keep_lock) > sounlock(head); > } > > - switch (so->so_proto->pr_domain->dom_family) { > - case AF_INET: > - case AF_INET6: > - if (so->so_proto->pr_type == SOCK_STREAM) > - break; > - /* FALLTHROUGH */ > - default: > + if (!keep_lock) { > + /* > + * sofree() was called from soclose(). Sleep is safe > + * even for tcp(4) sockets. > + */ > sounlock(so); > refcnt_finalize(&so->so_refcnt, "sofinal"); > - solock(so); > - break; > } > > sigio_free(&so->so_sigio); > @@ -358,9 +335,6 @@ sofree(struct socket *so, int keep_lock) > (*so->so_proto->pr_domain->dom_dispose)(so->so_rcv.sb_mb); > m_purge(so->so_rcv.sb_mb); > > - if (!keep_lock) > - sounlock(so); > - > #ifdef SOCKET_SPLICE > if (so->so_sp) { > /* Reuse splice idle, sounsplice() has been called before. */ > @@ -458,31 +432,6 @@ discard: > if (so->so_sp) { > struct socket *soback; > > - if (so->so_proto->pr_flags & PR_WANTRCVD) { > - /* > - * Copy - Paste, but can't relock and sleep in > - * sofree() in tcp(4) case. That's why tcp(4) > - * still rely on solock() for splicing and > - * unsplicing. > - */ > - > - if (issplicedback(so)) { > - int freeing = SOSP_FREEING_WRITE; > - > - if (so->so_sp->ssp_soback == so) > - freeing |= SOSP_FREEING_READ; > - sounsplice(so->so_sp->ssp_soback, so, freeing); > - } > - if (isspliced(so)) { > - int freeing = SOSP_FREEING_READ; > - > - if (so == so->so_sp->ssp_socket) > - freeing |= SOSP_FREEING_WRITE; > - sounsplice(so, so->so_sp->ssp_socket, freeing); > - } > - goto free; > - } > - > sounlock(so); > mtx_enter(&so->so_snd.sb_mtx); > /* > @@ -530,7 +479,6 @@ notsplicedback: > > solock(so); > } > -free: > #endif /* SOCKET_SPLICE */ > /* sofree() calls sounlock(). */ > sofree(so, 0); > @@ -1587,17 +1535,22 @@ sotask(void *arg) > */ > > sblock(&so->so_rcv, SBL_WAIT | SBL_NOINTR); > - if (sockstream) > - solock(so); > - > if (so->so_rcv.sb_flags & SB_SPLICE) { > - if (sockstream) > + struct socket *sosp = so->so_sp->ssp_socket; > + > + if (sockstream) { > + sblock(&sosp->so_snd, SBL_WAIT | SBL_NOINTR); > + solock(so); > doyield = 1; > + } > + > somove(so, M_DONTWAIT); > - } > > - if (sockstream) > - sounlock(so); > + if (sockstream) { > + sounlock(so); > + sbunlock(&sosp->so_snd); > + } > + } > sbunlock(&so->so_rcv); > > if (doyield) { > Index: sys/netinet/tcp_input.c > =================================================================== > RCS file: /cvs/src/sys/netinet/tcp_input.c,v > diff -u -p -r1.410 tcp_input.c > --- sys/netinet/tcp_input.c 20 Dec 2024 19:20:34 -0000 1.410 > +++ sys/netinet/tcp_input.c 21 Dec 2024 04:12:02 -0000 > @@ -957,7 +957,10 @@ findpcb: > acked); > tp->t_rcvacktime = now; > ND6_HINT(tp); > + > + mtx_enter(&so->so_snd.sb_mtx); > sbdrop(so, &so->so_snd, acked); > + mtx_leave(&so->so_snd.sb_mtx); > > /* > * If we had a pending ICMP message that > @@ -1738,10 +1741,14 @@ trimthenstep6: > tp->snd_wnd -= so->so_snd.sb_cc; > else > tp->snd_wnd = 0; > + mtx_enter(&so->so_snd.sb_mtx); > sbdrop(so, &so->so_snd, (int)so->so_snd.sb_cc); > + mtx_leave(&so->so_snd.sb_mtx); > ourfinisacked = 1; > } else { > + mtx_enter(&so->so_snd.sb_mtx); > sbdrop(so, &so->so_snd, acked); > + mtx_leave(&so->so_snd.sb_mtx); > if (tp->snd_wnd > acked) > tp->snd_wnd -= acked; > else > @@ -2999,6 +3006,7 @@ tcp_mss_update(struct tcpcb *tp) > if (rt == NULL) > return; > > + mtx_enter(&so->so_snd.sb_mtx); > bufsize = so->so_snd.sb_hiwat; > if (bufsize < mss) { > mss = bufsize; > @@ -3010,6 +3018,7 @@ tcp_mss_update(struct tcpcb *tp) > bufsize = sb_max; > (void)sbreserve(so, &so->so_snd, bufsize); > } > + mtx_leave(&so->so_snd.sb_mtx); > > mtx_enter(&so->so_rcv.sb_mtx); > bufsize = so->so_rcv.sb_hiwat; > Index: sys/netinet/tcp_output.c > =================================================================== > RCS file: /cvs/src/sys/netinet/tcp_output.c,v > diff -u -p -r1.146 tcp_output.c > --- sys/netinet/tcp_output.c 19 Dec 2024 22:11:35 -0000 1.146 > +++ sys/netinet/tcp_output.c 21 Dec 2024 04:12:02 -0000 > @@ -202,7 +202,7 @@ tcp_output(struct tcpcb *tp) > u_int32_t optbuf[howmany(MAX_TCPOPTLEN, sizeof(u_int32_t))]; > u_char *opt = (u_char *)optbuf; > unsigned int optlen, hdrlen, packetlen; > - int idle, sendalot = 0; > + int doing_sosend, idle, sendalot = 0; > int i, sack_rxmit = 0; > struct sackhole *p; > uint64_t now; > @@ -227,6 +227,10 @@ tcp_output(struct tcpcb *tp) > > now = tcp_now(); > > + mtx_enter(&so->so_snd.sb_mtx); > + doing_sosend=soissending(so); > + mtx_leave(&so->so_snd.sb_mtx); > + > /* > * Determine length of data that should be transmitted, > * and flags that will be used. > @@ -243,7 +247,7 @@ tcp_output(struct tcpcb *tp) > tp->snd_cwnd = 2 * tp->t_maxseg; > > /* remember 'idle' for next invocation of tcp_output */ > - if (idle && soissending(so)) { > + if (idle && doing_sosend) { > tp->t_flags |= TF_LASTIDLE; > idle = 0; > } else > @@ -392,7 +396,7 @@ again: > if (len >= txmaxseg) > goto send; > if ((idle || (tp->t_flags & TF_NODELAY)) && > - len + off >= so->so_snd.sb_cc && !soissending(so) && > + len + off >= so->so_snd.sb_cc && !doing_sosend && > (tp->t_flags & TF_NOPUSH) == 0) > goto send; > if (tp->t_force) > @@ -725,7 +729,7 @@ send: > * give data to the user when a buffer fills or > * a PUSH comes in.) > */ > - if (off + len == so->so_snd.sb_cc && !soissending(so)) > + if (off + len == so->so_snd.sb_cc && !doing_sosend) > flags |= TH_PUSH; > tp->t_sndtime = now; > } else { > Index: sys/netinet/tcp_usrreq.c > =================================================================== > RCS file: /cvs/src/sys/netinet/tcp_usrreq.c,v > diff -u -p -r1.233 tcp_usrreq.c > --- sys/netinet/tcp_usrreq.c 19 Dec 2024 22:11:35 -0000 1.233 > +++ sys/netinet/tcp_usrreq.c 21 Dec 2024 04:12:02 -0000 > @@ -302,10 +302,12 @@ tcp_fill_info(struct tcpcb *tp, struct s > ti->tcpi_so_rcv_sb_lowat = so->so_rcv.sb_lowat; > ti->tcpi_so_rcv_sb_wat = so->so_rcv.sb_wat; > mtx_leave(&so->so_rcv.sb_mtx); > + mtx_enter(&so->so_snd.sb_mtx); > ti->tcpi_so_snd_sb_cc = so->so_snd.sb_cc; > ti->tcpi_so_snd_sb_hiwat = so->so_snd.sb_hiwat; > ti->tcpi_so_snd_sb_lowat = so->so_snd.sb_lowat; > ti->tcpi_so_snd_sb_wat = so->so_snd.sb_wat; > + mtx_leave(&so->so_snd.sb_mtx); > > return 0; > } > @@ -842,7 +844,9 @@ tcp_send(struct socket *so, struct mbuf > if (so->so_options & SO_DEBUG) > ostate = tp->t_state; > > + mtx_enter(&so->so_snd.sb_mtx); > sbappendstream(so, &so->so_snd, m); > + mtx_leave(&so->so_snd.sb_mtx); > m = NULL; > > error = tcp_output(tp); > @@ -895,7 +899,9 @@ tcp_sense(struct socket *so, struct stat > if ((error = tcp_sogetpcb(so, &inp, &tp))) > return (error); > > + mtx_enter(&so->so_snd.sb_mtx); > ub->st_blksize = so->so_snd.sb_hiwat; > + mtx_leave(&so->so_snd.sb_mtx); > > if (so->so_options & SO_DEBUG) > tcp_trace(TA_USER, tp->t_state, tp, tp, NULL, PRU_SENSE, 0); > @@ -970,7 +976,9 @@ tcp_sendoob(struct socket *so, struct mb > * of data past the urgent section. > * Otherwise, snd_up should be one lower. > */ > + mtx_enter(&so->so_snd.sb_mtx); > sbappendstream(so, &so->so_snd, m); > + mtx_leave(&so->so_snd.sb_mtx); > m = NULL; > tp->snd_up = tp->snd_una + so->so_snd.sb_cc; > tp->t_force = 1; > @@ -1519,7 +1527,11 @@ void > tcp_update_sndspace(struct tcpcb *tp) > { > struct socket *so = tp->t_inpcb->inp_socket; > - u_long nmax = so->so_snd.sb_hiwat; > + u_long nmax; > + > + mtx_enter(&so->so_snd.sb_mtx); > + > + nmax = so->so_snd.sb_hiwat; > > if (sbchecklowmem()) { > /* low on memory try to get rid of some */ > @@ -1535,7 +1547,7 @@ tcp_update_sndspace(struct tcpcb *tp) > } > > /* a writable socket must be preserved because of poll(2) semantics */ > - if (sbspace(so, &so->so_snd) >= so->so_snd.sb_lowat) { > + if (sbspace_locked(so, &so->so_snd) >= so->so_snd.sb_lowat) { > if (nmax < so->so_snd.sb_cc + so->so_snd.sb_lowat) > nmax = so->so_snd.sb_cc + so->so_snd.sb_lowat; > /* keep in sync with sbreserve() calculation */ > @@ -1548,6 +1560,8 @@ tcp_update_sndspace(struct tcpcb *tp) > > if (nmax != so->so_snd.sb_hiwat) > sbreserve(so, &so->so_snd, nmax); > + > + mtx_leave(&so->so_snd.sb_mtx); > } > > /* > @@ -1581,9 +1595,11 @@ tcp_update_rcvspace(struct tcpcb *tp) > } > > /* a readable socket must be preserved because of poll(2) semantics */ > + mtx_enter(&so->so_snd.sb_mtx); > if (so->so_rcv.sb_cc >= so->so_rcv.sb_lowat && > nmax < so->so_snd.sb_lowat) > nmax = so->so_snd.sb_lowat; > + mtx_leave(&so->so_snd.sb_mtx); > > if (nmax != so->so_rcv.sb_hiwat) { > /* round to MSS boundary */