From 87b8b93695200a501d5ecbb36f1dfc337f3404b9 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Thu, 24 Dec 2020 01:20:12 +0100 Subject: [PATCH] don't quit listener after a temporary accept error (#211) * don't quit listener after a temporary accept error Fixes #210 * fix lint issues Co-authored-by: Florent Clairambault --- server.go | 22 ++++++++++++++++ server_test.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/server.go b/server.go index 5eb44059..d8a5ed93 100644 --- a/server.go +++ b/server.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net" + "time" "github.com/fclairamb/ftpserverlib/log" ) @@ -177,6 +178,8 @@ func (server *FtpServer) Listen() error { // Serve accepts and processes any new incoming client func (server *FtpServer) Serve() error { + var tempDelay time.Duration // how long to sleep on accept failure + for { connection, err := server.listener.Accept() @@ -190,6 +193,25 @@ func (server *FtpServer) Serve() error { } } + if ne, ok := err.(net.Error); ok && ne.Temporary() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 + } + + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + + server.Logger.Warn( + "accept error", err, + "retry delay", tempDelay) + time.Sleep(tempDelay) + + continue + } + server.Logger.Error("Listener accept error", "err", err) return err diff --git a/server_test.go b/server_test.go index a1eccd9e..5ca5eb03 100644 --- a/server_test.go +++ b/server_test.go @@ -1,11 +1,82 @@ package ftpserver import ( + "errors" + "net" "testing" "github.com/stretchr/testify/require" + + "github.com/fclairamb/ftpserverlib/log" ) +var errListenerAccept = errors.New("error accepting a connection") + +type fakeNetError struct { + error + count int +} + +func (e *fakeNetError) Timeout() bool { + return false +} + +func (e *fakeNetError) Temporary() bool { + e.count++ + + return e.count < 10 +} + +func (e *fakeNetError) Error() string { + return e.error.Error() +} + +type fakeListener struct { + server net.Conn + client net.Conn + err error +} + +func (l *fakeListener) Accept() (net.Conn, error) { + return l.client, l.err +} + +func (l *fakeListener) Close() error { + errClient := l.client.Close() + errServer := l.server.Close() + + if errServer != nil { + return errServer + } + + return errClient +} + +func (l *fakeListener) Addr() net.Addr { + return l.server.LocalAddr() +} + +func newFakeListener(err error) net.Listener { + server, client := net.Pipe() + + return &fakeListener{ + server: server, + client: client, + err: err, + } +} + +func TestListernerAcceptErrors(t *testing.T) { + errNetFake := &fakeNetError{error: errListenerAccept} + + server := FtpServer{ + listener: newFakeListener(errNetFake), + Logger: log.Nothing(), + } + err := server.Serve() + require.EqualError(t, err, errListenerAccept.Error()) +} + func TestPortCommandFormatOK(t *testing.T) { net, err := parsePORTAddr("127,0,0,1,239,163") require.NoError(t, err, "Problem parsing")