diff --git a/errors/errors.go b/errors/errors.go index c3e8b6a7e..0624a284f 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -58,4 +58,11 @@ var ( ErrUnsupportedLength = errors.New("unsupported lengthFieldLength. (expected: 1, 2, 3, 4, or 8)") // ErrTooLessLength occurs when adjusted frame length is less than zero. ErrTooLessLength = errors.New("adjusted frame length is less than zero") + + // ================================================ internal errors ================================================ + + // ErrShortWritev occurs when internal/io.Writev fails to send all data. + ErrShortWritev = errors.New("short writev") + // ErrShortReadv occurs when internal/io.Writev fails to send all data. + ErrShortReadv = errors.New("short readv") ) diff --git a/eventloop_unix.go b/eventloop_unix.go index 98c194aa3..f83b9ba87 100644 --- a/eventloop_unix.go +++ b/eventloop_unix.go @@ -33,11 +33,13 @@ import ( "time" "unsafe" + "golang.org/x/sys/unix" + gerrors "github.com/panjf2000/gnet/errors" + "github.com/panjf2000/gnet/internal/io" "github.com/panjf2000/gnet/internal/logging" "github.com/panjf2000/gnet/internal/netpoll" "github.com/panjf2000/gnet/internal/socket" - "golang.org/x/sys/unix" ) type eventloop struct { @@ -180,24 +182,14 @@ func (el *eventloop) loopWrite(c *conn) error { el.eventHandler.PreWrite() head, tail := c.outboundBuffer.LazyReadAll() - n, err := unix.Write(c.fd, head) - if err != nil { - if err == unix.EAGAIN { - return nil - } - return el.loopCloseConn(c, os.NewSyscallError("write", err)) - } + n, err := io.Writev(c.fd, [][]byte{head, tail}) c.outboundBuffer.Shift(n) - - if n == len(head) && tail != nil { - n, err = unix.Write(c.fd, tail) - if err != nil { - if err == unix.EAGAIN { - return nil - } - return el.loopCloseConn(c, os.NewSyscallError("write", err)) - } - c.outboundBuffer.Shift(n) + switch err { + case nil, gerrors.ErrShortWritev: // do nothing, just go on + case unix.EAGAIN: + return nil + default: + return el.loopCloseConn(c, os.NewSyscallError("write", err)) } // All data have been drained, it's no need to monitor the writable events, diff --git a/gnet_test.go b/gnet_test.go index 8df42c983..dba96b867 100644 --- a/gnet_test.go +++ b/gnet_test.go @@ -35,11 +35,12 @@ import ( "testing" "time" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "github.com/panjf2000/gnet/errors" "github.com/panjf2000/gnet/pool/bytebuffer" "github.com/panjf2000/gnet/pool/goroutine" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) func TestCodecServe(t *testing.T) { diff --git a/internal/io/io_bsd.go b/internal/io/io_bsd.go new file mode 100644 index 000000000..81f4e8fc5 --- /dev/null +++ b/internal/io/io_bsd.go @@ -0,0 +1,67 @@ +// Copyright (c) 2021 Andy Pan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// +build freebsd dragonfly darwin + +package io + +import ( + "golang.org/x/sys/unix" + + "github.com/panjf2000/gnet/errors" +) + +// Writev simply calls write() multiple times cuz writev() on BSD-like OS's is not yet implemented in Go. +func Writev(fd int, iov [][]byte) (int, error) { + var sum int + for i := range iov { + n, err := unix.Write(fd, iov[i]) + if err != nil { + if sum == 0 { + sum = n + } + return sum, err + } + sum += n + if n < len(iov[i]) { + return sum, errors.ErrShortWritev + } + } + return sum, nil +} + +// Readv simply calls read() multiple times cuz readv() on BSD-like OS's is not yet implemented in Go. +func Readv(fd int, iov [][]byte) (int, error) { + var sum int + for i := range iov { + n, err := unix.Read(fd, iov[i]) + if err != nil { + if sum == 0 { + sum = n + } + return sum, err + } + sum += n + if n < len(iov[i]) { + return sum, errors.ErrShortReadv + } + } + return sum, nil +} diff --git a/internal/io/io_linux.go b/internal/io/io_linux.go new file mode 100644 index 000000000..8d744b69d --- /dev/null +++ b/internal/io/io_linux.go @@ -0,0 +1,35 @@ +// Copyright (c) 2021 Andy Pan +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// +build linux + +package io + +import "golang.org/x/sys/unix" + +// Writev calls writev() on Linux. +func Writev(fd int, iov [][]byte) (int, error) { + return unix.Writev(fd, iov) +} + +// Readv calls readv() on Linux. +func Readv(fd int, iov [][]byte) (int, error) { + return unix.Readv(fd, iov) +}