Merge 0.9->0.10
[prosody.git] / net / server_select.lua
index 70825adaaeeec13e842359a900f2e0324bf172e2..3503c30d0079bd2f50daa04e89867b091efeefb2 100644 (file)
@@ -1,7 +1,7 @@
--- 
+--
 -- server.lua by blastbeat of the luadch project
 -- Re-used here under the MIT/X Consortium License
--- 
+--
 -- Modifications (C) 2008-2010 Matthew Wild, Waqas Hussain
 --
 
 local use = function( what )
        return _G[ what ]
 end
-local clean = function( tbl )
-       for i, k in pairs( tbl ) do
-               tbl[ i ] = nil
-       end
-end
 
 local log, table_concat = require ("util.logger").init("socket"), table.concat;
 local out_put = function (...) return log("debug", table_concat{...}); end
 local out_error = function (...) return log("warn", table_concat{...}); end
-local mem_free = collectgarbage
 
 ----------------------------------// DECLARATION //--
 
@@ -34,7 +28,6 @@ local pairs = use "pairs"
 local ipairs = use "ipairs"
 local tonumber = use "tonumber"
 local tostring = use "tostring"
-local collectgarbage = use "collectgarbage"
 
 --// lua libs //--
 
@@ -45,29 +38,26 @@ local coroutine = use "coroutine"
 
 --// lua lib methods //--
 
-local os_difftime = os.difftime
 local math_min = math.min
 local math_huge = math.huge
 local table_concat = table.concat
-local table_remove = table.remove
-local string_len = string.len
 local string_sub = string.sub
 local coroutine_wrap = coroutine.wrap
 local coroutine_yield = coroutine.yield
 
 --// extern libs //--
 
-local luasec = use "ssl"
+local has_luasec, luasec = pcall ( require , "ssl" )
 local luasocket = use "socket" or require "socket"
 local luasocket_gettime = luasocket.gettime
+local getaddrinfo = luasocket.dns.getaddrinfo
 
 --// extern lib methods //--
 
-local ssl_wrap = ( luasec and luasec.wrap )
+local ssl_wrap = ( has_luasec and luasec.wrap )
 local socket_bind = luasocket.bind
 local socket_sleep = luasocket.sleep
 local socket_select = luasocket.select
-local ssl_newcontext = ( luasec and luasec.newcontext )
 
 --// functions //--
 
@@ -78,13 +68,13 @@ local idfalse
 local closeall
 local addsocket
 local addserver
+local addtimer
 local getserver
 local wrapserver
 local getsettings
 local closesocket
 local removesocket
 local removeserver
-local changetimeout
 local wrapconnection
 local changesettings
 
@@ -98,6 +88,7 @@ local _socketlist
 local _closelist
 local _readtimes
 local _writetimes
+local _fullservers
 
 --// simple data types //--
 
@@ -111,6 +102,8 @@ local _readtraffic
 
 local _selecttimeout
 local _sleeptime
+local _tcpbacklog
+local _accepretry
 
 local _starttime
 local _currenttime
@@ -122,11 +115,10 @@ local _checkinterval
 local _sendtimeout
 local _readtimeout
 
-local _cleanqueue
-
 local _timer
 
-local _maxclientsperserver
+local _maxselectlen
+local _maxfd
 
 local _maxsslhandshake
 
@@ -140,6 +132,7 @@ _socketlist = { } -- key = socket, value = wrapped socket (handlers)
 _readtimes = { } -- key = handler, value = timestamp of last data reading
 _writetimes = { } -- key = handler, value = timestamp of last data writing/sending
 _closelist = { } -- handlers to close
+_fullservers = { } -- servers in a paused state while there are too many clients
 
 _readlistlen = 0 -- length of readlist
 _sendlistlen = 0 -- length of sendlist
@@ -150,25 +143,31 @@ _readtraffic = 0
 
 _selecttimeout = 1 -- timeout of socket.select
 _sleeptime = 0 -- time to wait at the end of every loop
+_tcpbacklog = 128 -- some kind of hint to the OS
+_accepretry = 10 -- seconds to wait until the next attempt of a full server to accept
 
 _maxsendlen = 51000 * 1024 -- max len of send buffer
 _maxreadlen = 25000 * 1024 -- max len of read buffer
 
-_checkinterval = 1200000 -- interval in secs to check idle clients
+_checkinterval = 30 -- interval in secs to check idle clients
 _sendtimeout = 60000 -- allowed send idle time in secs
 _readtimeout = 6 * 60 * 60 -- allowed read idle time in secs
 
-_cleanqueue = false -- clean bufferqueue after using
-
-_maxclientsperserver = 1000
+local is_windows = package.config:sub(1,1) == "\\" -- check the directory separator, to detemine whether this is Windows
+_maxfd = (is_windows and math.huge) or luasocket._SETSIZE or 1024 -- max fd number, limit to 1024 by default to prevent glibc buffer overflow, but not on Windows
+_maxselectlen = luasocket._SETSIZE or 1024 -- But this still applies on Windows
 
 _maxsslhandshake = 30 -- max handshake round-trips
 
 ----------------------------------// PRIVATE //--
 
-wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxconnections ) -- this function wraps a server
+wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx ) -- this function wraps a server -- FIXME Make sure FD < _maxfd
 
-       maxconnections = maxconnections or _maxclientsperserver
+       if socket:getfd() >= _maxfd then
+               out_error("server.lua: Disallowed FD number: "..socket:getfd())
+               socket:close()
+               return nil, "fd-too-large"
+       end
 
        local connections = 0
 
@@ -190,14 +189,11 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxco
        end
        handler.remove = function( )
                connections = connections - 1
-       end
-       handler.close = function( )
-               for _, handler in pairs( _socketlist ) do
-                       if handler.serverport == serverport then
-                               handler.disconnect( handler, "server closed" )
-                               handler:close( true )
-                       end
+               if handler then
+                       handler.resume( )
                end
+       end
+       handler.close = function()
                socket:close( )
                _sendlistlen = removesocket( _sendlist, socket, _sendlistlen )
                _readlistlen = removesocket( _readlist, socket, _readlistlen )
@@ -208,6 +204,31 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxco
                --mem_free( )
                out_put "server.lua: closed server handler and removed sockets from list"
        end
+       handler.pause = function( hard )
+               if not handler.paused then
+                       _readlistlen = removesocket( _readlist, socket, _readlistlen )
+                       if hard then
+                               _socketlist[ socket ] = nil
+                               socket:close( )
+                               socket = nil;
+                       end
+                       handler.paused = true;
+                       out_put("server.lua: server [", ip, "]:", serverport, " paused")
+               end
+       end
+       handler.resume = function( )
+               if handler.paused then
+                       if not socket then
+                               socket = socket_bind( ip, serverport, _tcpbacklog );
+                               socket:settimeout( 0 )
+                       end
+                       _readlistlen = addsocket(_readlist, socket, _readlistlen)
+                       _socketlist[ socket ] = handler
+                       _fullservers[ handler ] = nil
+                       handler.paused = false;
+                       out_put("server.lua: server [", ip, "]:", serverport, " resumed")
+               end
+       end
        handler.ip = function( )
                return ip
        end
@@ -218,26 +239,29 @@ wrapserver = function( listeners, socket, ip, serverport, pattern, sslctx, maxco
                return socket
        end
        handler.readbuffer = function( )
-               if connections > maxconnections then
+               if _readlistlen >= _maxselectlen or _sendlistlen >= _maxselectlen then
+                       handler.pause( )
+                       _fullservers[ handler ] = _currenttime
                        out_put( "server.lua: refused new client connection: server full" )
                        return false
                end
                local client, err = accept( socket )    -- try to accept
                if client then
                        local ip, clientport = client:getpeername( )
-                       client:settimeout( 0 )
                        local handler, client, err = wrapconnection( handler, listeners, client, ip, serverport, clientport, pattern, sslctx ) -- wrap new client socket
                        if err then -- error while wrapping ssl socket
                                return false
                        end
                        connections = connections + 1
                        out_put( "server.lua: accepted new client connection from ", tostring(ip), ":", tostring(clientport), " to ", tostring(serverport))
-                       if dispatch then
+                       if dispatch and not sslctx then -- SSL connections will notify onconnect when handshake completes
                                return dispatch( handler );
                        end
                        return;
                elseif err then -- maybe timeout or something else
                        out_put( "server.lua: error with new client connection: ", tostring(err) )
+                       handler.pause( )
+                       _fullservers[ handler ] = _currenttime
                        return false
                end
        end
@@ -246,6 +270,15 @@ end
 
 wrapconnection = function( server, listeners, socket, ip, serverport, clientport, pattern, sslctx ) -- this function wraps a client to a handler object
 
+       if socket:getfd() >= _maxfd then
+               out_error("server.lua: Disallowed FD number: "..socket:getfd()) -- PROTIP: Switch to libevent
+               socket:close( ) -- Should we send some kind of error here?
+               if server then
+                       _fullservers[ server ] = _currenttime
+                       server.pause( )
+               end
+               return nil, nil, "fd-too-large"
+       end
        socket:settimeout( 0 )
 
        --// local import of socket methods //--
@@ -262,6 +295,8 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
        local status = listeners.onstatus
        local disconnect = listeners.ondisconnect
        local drain = listeners.ondrain
+       local onreadtimeout = listeners.onreadtimeout;
+       local detach = listeners.ondetach
 
        local bufferqueue = { } -- buffer array
        local bufferqueuelen = 0        -- end of buffer array
@@ -290,11 +325,18 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
        handler.disconnect = function( )
                return disconnect
        end
+       handler.onreadtimeout = onreadtimeout;
+
        handler.setlistener = function( self, listeners )
+               if detach then
+                       detach(self) -- Notify listener that it is no longer responsible for this connection
+               end
                dispatch = listeners.onincoming
                disconnect = listeners.ondisconnect
                status = listeners.onstatus
                drain = listeners.ondrain
+               handler.onreadtimeout = listeners.onreadtimeout
+               detach = listeners.ondetach
        end
        handler.getstats = function( )
                return readtraffic, sendtraffic
@@ -320,22 +362,25 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                end
                return false, "setoption not implemented";
        end
-       handler.close = function( self, forced )
+       handler.force_close = function ( self, err )
+               if bufferqueuelen ~= 0 then
+                       out_put("server.lua: discarding unwritten data for ", tostring(ip), ":", tostring(clientport))
+                       bufferqueuelen = 0;
+               end
+               return self:close(err);
+       end
+       handler.close = function( self, err )
                if not handler then return true; end
                _readlistlen = removesocket( _readlist, socket, _readlistlen )
                _readtimes[ handler ] = nil
                if bufferqueuelen ~= 0 then
-                       if not ( forced or fatalerror ) then
-                               handler.sendbuffer( )
-                               if bufferqueuelen ~= 0 then -- try again...
-                                       if handler then
-                                               handler.write = nil -- ... but no further writing allowed
-                                       end
-                                       toclose = true
-                                       return false
+                       handler.sendbuffer() -- Try now to send any outstanding data
+                       if bufferqueuelen ~= 0 then -- Still not empty, so we'll try again later
+                               if handler then
+                                       handler.write = nil -- ... but no further writing allowed
                                end
-                       else
-                               send( socket, table_concat( bufferqueue, "", 1, bufferqueuelen ), 1, bufferlen )        -- forced send
+                               toclose = true
+                               return false
                        end
                end
                if socket then
@@ -350,7 +395,12 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                if handler then
                        _writetimes[ handler ] = nil
                        _closelist[ handler ] = nil
+                       local _handler = handler;
                        handler = nil
+                       if disconnect then
+                               disconnect(_handler, err or false);
+                               disconnect = nil
+                       end
                end
                if server then
                        server.remove( )
@@ -358,6 +408,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                out_put "server.lua: closed client handler and removed socket from list"
                return true
        end
+       handler.server = function ( )
+               return server
+       end
        handler.ip = function( )
                return ip
        end
@@ -367,8 +420,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
        handler.clientport = function( )
                return clientport
        end
+       handler.port = handler.clientport -- COMPAT server_event
        local write = function( self, data )
-               bufferlen = bufferlen + string_len( data )
+               bufferlen = bufferlen + #data
                if bufferlen > maxsendlen then
                        _closelist[ handler ] = "send buffer exceeded"   -- cannot close the client at the moment, have to wait to the end of the cycle
                        handler.write = idfalse -- dont write anymore
@@ -450,10 +504,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                local buffer, err, part = receive( socket, pattern )    -- receive buffer with "pattern"
                if not err or (err == "wantread" or err == "timeout") then -- received something
                        local buffer = buffer or part or ""
-                       local len = string_len( buffer )
+                       local len = #buffer
                        if len > maxreadlen then
-                               disconnect( handler, "receive buffer exceeded" )
-                               handler:close( true )
+                               handler:close( "receive buffer exceeded" )
                                return false
                        end
                        local count = len * STAT_UNIT
@@ -465,24 +518,24 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                else    -- connections was closed or fatal error
                        out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " read error: ", tostring(err) )
                        fatalerror = true
-                       disconnect( handler, err )
-                       _ = handler and handler:close( )
+                       _ = handler and handler:force_close( err )
                        return false
                end
        end
        local _sendbuffer = function( ) -- this function sends data
                local succ, err, byte, buffer, count;
-               local count;
                if socket then
                        buffer = table_concat( bufferqueue, "", 1, bufferqueuelen )
                        succ, err, byte = send( socket, buffer, 1, bufferlen )
                        count = ( succ or byte or 0 ) * STAT_UNIT
                        sendtraffic = sendtraffic + count
                        _sendtraffic = _sendtraffic + count
-                       _ = _cleanqueue and clean( bufferqueue )
+                       for i = bufferqueuelen,1,-1 do
+                               bufferqueue[ i ] = nil
+                       end
                        --out_put( "server.lua: sended '", buffer, "', bytes: ", tostring(succ), ", error: ", tostring(err), ", part: ", tostring(byte), ", to: ", tostring(ip), ":", tostring(clientport) )
                else
-                       succ, err, count = false, "closed", 0;
+                       succ, err, count = false, "unexpected close", 0;
                end
                if succ then    -- sending succesful
                        bufferqueuelen = 0
@@ -493,7 +546,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                                drain(handler)
                        end
                        _ = needtls and handler:starttls(nil)
-                       _ = toclose and handler:close( )
+                       _ = toclose and handler:force_close( )
                        return true
                elseif byte and ( err == "timeout" or err == "wantwrite" ) then -- want write
                        buffer = string_sub( buffer, byte + 1, bufferlen ) -- new buffer
@@ -505,8 +558,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                else    -- connection was closed during sending or fatal error
                        out_put( "server.lua: client ", tostring(ip), ":", tostring(clientport), " write error: ", tostring(err) )
                        fatalerror = true
-                       disconnect( handler, err )
-                       _ = handler and handler:close( )
+                       _ = handler and handler:force_close( err )
                        return false
                end
        end
@@ -530,6 +582,9 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                                                _ = status and status( handler, "ssl-handshake-complete" )
                                                if self.autostart_ssl and listeners.onconnect then
                                                        listeners.onconnect(self);
+                                                       if bufferqueuelen ~= 0 then
+                                                               _sendlistlen = addsocket(_sendlist, client, _sendlistlen)
+                                                       end
                                                end
                                                _readlistlen = addsocket(_readlist, client, _readlistlen)
                                                return true
@@ -547,14 +602,14 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                                                coroutine_yield( ) -- handshake not finished
                                        end
                                end
-                               out_put( "server.lua: ssl handshake error: ", tostring(err or "handshake too long") )
-                               disconnect( handler, "ssl handshake failed" )
-                               _ = handler and handler:close( true )    -- forced disconnect
-                               return false    -- handshake failed
+                               err = "ssl handshake error: " .. ( err or "handshake too long" );
+                               out_put( "server.lua: ", err );
+                               _ = handler and handler:force_close(err)
+                               return false, err -- handshake failed
                        end
                )
        end
-       if luasec then
+       if has_luasec then
                handler.starttls = function( self, _sslctx)
                        if _sslctx then
                                handler:set_sslctx(_sslctx);
@@ -580,7 +635,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
                        shutdown = id
                        _socketlist[ socket ] = handler
                        _readlistlen = addsocket(_readlist, socket, _readlistlen)
-                       
+
                        -- remove traces of the old socket
                        _readlistlen = removesocket( _readlist, oldsocket, _readlistlen )
                        _sendlistlen = removesocket( _sendlist, oldsocket, _sendlistlen )
@@ -594,7 +649,7 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
 
                        handler.readbuffer = handshake
                        handler.sendbuffer = handshake
-                       handshake( socket ) -- do handshake
+                       return handshake( socket ) -- do handshake
                end
        end
 
@@ -607,10 +662,13 @@ wrapconnection = function( server, listeners, socket, ip, serverport, clientport
        _socketlist[ socket ] = handler
        _readlistlen = addsocket(_readlist, socket, _readlistlen)
 
-       if sslctx and luasec then
+       if sslctx and has_luasec then
                out_put "server.lua: auto-starting ssl negotiation..."
                handler.autostart_ssl = true;
-               handler:starttls(sslctx);
+               local ok, err = handler:starttls(sslctx);
+               if ok == false then
+                       return nil, nil, err
+               end
        end
 
        return handler, socket
@@ -665,7 +723,7 @@ local function link(sender, receiver, buffersize)
                        sender_locked = nil;
                end
        end
-       
+
        local _readbuffer = sender.readbuffer;
        function sender.readbuffer()
                _readbuffer();
@@ -674,33 +732,35 @@ local function link(sender, receiver, buffersize)
                        sender:lock_read(true);
                end
        end
+       sender:set_mode("*a");
 end
 
 ----------------------------------// PUBLIC //--
 
 addserver = function( addr, port, listeners, pattern, sslctx ) -- this function provides a way for other scripts to reg a server
+       addr = addr or "*"
        local err
        if type( listeners ) ~= "table" then
                err = "invalid listener table"
-       end
-       if type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
+       elseif type ( addr ) ~= "string" then
+               err = "invalid address"
+       elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
                err = "invalid port"
        elseif _server[ addr..":"..port ] then
                err = "listeners on '[" .. addr .. "]:" .. port .. "' already exist"
-       elseif sslctx and not luasec then
+       elseif sslctx and not has_luasec then
                err = "luasec not found"
        end
        if err then
                out_error( "server.lua, [", addr, "]:", port, ": ", err )
                return nil, err
        end
-       addr = addr or "*"
-       local server, err = socket_bind( addr, port )
+       local server, err = socket_bind( addr, port, _tcpbacklog )
        if err then
                out_error( "server.lua, [", addr, "]:", port, ": ", err )
                return nil, err
        end
-       local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx, _maxclientsperserver ) -- wrap new server socket
+       local handler, err = wrapserver( listeners, server, addr, port, pattern, sslctx ) -- wrap new server socket
        if not handler then
                server:close( )
                return nil, err
@@ -744,23 +804,38 @@ closeall = function( )
 end
 
 getsettings = function( )
-       return  _selecttimeout, _sleeptime, _maxsendlen, _maxreadlen, _checkinterval, _sendtimeout, _readtimeout, _cleanqueue, _maxclientsperserver, _maxsslhandshake
+       return {
+               select_timeout = _selecttimeout;
+               select_sleep_time = _sleeptime;
+               tcp_backlog = _tcpbacklog;
+               max_send_buffer_size = _maxsendlen;
+               max_receive_buffer_size = _maxreadlen;
+               select_idle_check_interval = _checkinterval;
+               send_timeout = _sendtimeout;
+               read_timeout = _readtimeout;
+               max_connections = _maxselectlen;
+               max_ssl_handshake_roundtrips = _maxsslhandshake;
+               highest_allowed_fd = _maxfd;
+               accept_retry_interval = _accepretry;
+       }
 end
 
 changesettings = function( new )
        if type( new ) ~= "table" then
                return nil, "invalid settings table"
        end
-       _selecttimeout = tonumber( new.timeout ) or _selecttimeout
-       _sleeptime = tonumber( new.sleeptime ) or _sleeptime
-       _maxsendlen = tonumber( new.maxsendlen ) or _maxsendlen
-       _maxreadlen = tonumber( new.maxreadlen ) or _maxreadlen
-       _checkinterval = tonumber( new.checkinterval ) or _checkinterval
-       _sendtimeout = tonumber( new.sendtimeout ) or _sendtimeout
-       _readtimeout = tonumber( new.readtimeout ) or _readtimeout
-       _cleanqueue = new.cleanqueue
-       _maxclientsperserver = new._maxclientsperserver or _maxclientsperserver
-       _maxsslhandshake = new._maxsslhandshake or _maxsslhandshake
+       _selecttimeout = tonumber( new.select_timeout ) or _selecttimeout
+       _sleeptime = tonumber( new.select_sleep_time ) or _sleeptime
+       _maxsendlen = tonumber( new.max_send_buffer_size ) or _maxsendlen
+       _maxreadlen = tonumber( new.max_receive_buffer_size ) or _maxreadlen
+       _checkinterval = tonumber( new.select_idle_check_interval ) or _checkinterval
+       _tcpbacklog = tonumber( new.tcp_backlog ) or _tcpbacklog
+       _sendtimeout = tonumber( new.send_timeout ) or _sendtimeout
+       _readtimeout = tonumber( new.read_timeout ) or _readtimeout
+       _accepretry = tonumber( new.accept_retry_interval ) or _accepretry
+       _maxselectlen = new.max_connections or _maxselectlen
+       _maxsslhandshake = new.max_ssl_handshake_roundtrips or _maxsslhandshake
+       _maxfd = new.highest_allowed_fd or _maxfd
        return true
 end
 
@@ -809,10 +884,33 @@ loop = function(once) -- this is the main loop of the program
                end
                for handler, err in pairs( _closelist ) do
                        handler.disconnect( )( handler, err )
-                       handler:close( true )    -- forced disconnect
+                       handler:force_close()    -- forced disconnect
+                       _closelist[ handler ] = nil;
                end
-               clean( _closelist )
                _currenttime = luasocket_gettime( )
+
+               -- Check for socket timeouts
+               if _currenttime - _starttime > _checkinterval then
+                       _starttime = _currenttime
+                       for handler, timestamp in pairs( _writetimes ) do
+                               if _currenttime - timestamp > _sendtimeout then
+                                       handler.disconnect( )( handler, "send timeout" )
+                                       handler:force_close()    -- forced disconnect
+                               end
+                       end
+                       for handler, timestamp in pairs( _readtimes ) do
+                               if _currenttime - timestamp > _readtimeout then
+                                       if not(handler.onreadtimeout) or handler:onreadtimeout() ~= true then
+                                               handler.disconnect( )( handler, "read timeout" )
+                                               handler:close( )        -- forced disconnect?
+                                       else
+                                               _readtimes[ handler ] = _currenttime -- reset timer
+                                       end
+                               end
+                       end
+               end
+
+               -- Fire timers
                if _currenttime - _timer >= math_min(next_timer_time, 1) then
                        next_timer_time = math_huge;
                        for i = 1, _timerlistlen do
@@ -823,10 +921,19 @@ loop = function(once) -- this is the main loop of the program
                else
                        next_timer_time = next_timer_time - (_currenttime - _timer);
                end
-               socket_sleep( _sleeptime ) -- wait some time
-               --collectgarbage( )
+
+               for server, paused_time in pairs( _fullservers ) do
+                       if _currenttime - paused_time > _accepretry then
+                               _fullservers[ server ] = nil;
+                               server.resume();
+                       end
+               end
+
+               -- wait some time (0 by default)
+               socket_sleep( _sleeptime )
        until quitting;
        if once and quitting == "once" then quitting = nil; return; end
+       closeall();
        return "quitting"
 end
 
@@ -841,7 +948,8 @@ end
 --// EXPERIMENTAL //--
 
 local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx )
-       local handler = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx )
+       local handler, socket, err = wrapconnection( nil, listeners, socket, ip, serverport, "clientport", pattern, sslctx )
+       if not handler then return nil, err end
        _socketlist[ socket ] = handler
        if not sslctx then
                _sendlistlen = addsocket(_sendlist, socket, _sendlistlen)
@@ -849,30 +957,55 @@ local wrapclient = function( socket, ip, serverport, listeners, pattern, sslctx
                        -- When socket is writeable, call onconnect
                        local _sendbuffer = handler.sendbuffer;
                        handler.sendbuffer = function ()
-                               _sendlistlen = removesocket( _sendlist, socket, _sendlistlen );
                                handler.sendbuffer = _sendbuffer;
                                listeners.onconnect(handler);
-                               -- If there was data with the incoming packet, handle it now.
-                               if #handler:bufferqueue() > 0 then
-                                       return _sendbuffer();
-                               end
+                               return _sendbuffer(); -- Send any queued outgoing data
                        end
                end
        end
        return handler, socket
 end
 
-local addclient = function( address, port, listeners, pattern, sslctx )
-       local client, err = luasocket.tcp( )
+local addclient = function( address, port, listeners, pattern, sslctx, typ )
+       local err
+       if type( listeners ) ~= "table" then
+               err = "invalid listener table"
+       elseif type ( address ) ~= "string" then
+               err = "invalid address"
+       elseif type( port ) ~= "number" or not ( port >= 0 and port <= 65535 ) then
+               err = "invalid port"
+       elseif sslctx and not has_luasec then
+               err = "luasec not found"
+       end
+       if not typ then
+               local addrinfo, err = getaddrinfo(address)
+               if not addrinfo then return nil, err end
+               if addrinfo[1] and addrinfo[1].family == "inet6" then
+                       typ = "tcp6"
+               else
+                       typ = "tcp"
+               end
+       end
+       local create = luasocket[typ]
+       if type( create ) ~= "function"  then
+               err = "invalid socket type"
+       end
+
+       if err then
+               out_error( "server.lua, addclient: ", err )
+               return nil, err
+       end
+
+       local client, err = create( )
        if err then
                return nil, err
        end
        client:settimeout( 0 )
-       _, err = client:connect( address, port )
-       if err then -- try again
-               local handler = wrapclient( client, address, port, listeners )
+       local ok, err = client:connect( address, port )
+       if ok or err == "timeout" then
+               return wrapclient( client, address, port, listeners, pattern, sslctx )
        else
-               wrapconnection( nil, listeners, client, address, port, "clientport", pattern, sslctx )
+               return nil, err
        end
 end
 
@@ -887,28 +1020,6 @@ use "setmetatable" ( _writetimes, { __mode = "k" } )
 _timer = luasocket_gettime( )
 _starttime = luasocket_gettime( )
 
-addtimer( function( )
-               local difftime = os_difftime( _currenttime - _starttime )
-               if difftime > _checkinterval then
-                       _starttime = _currenttime
-                       for handler, timestamp in pairs( _writetimes ) do
-                               if os_difftime( _currenttime - timestamp ) > _sendtimeout then
-                                       --_writetimes[ handler ] = nil
-                                       handler.disconnect( )( handler, "send timeout" )
-                                       handler:close( true )    -- forced disconnect
-                               end
-                       end
-                       for handler, timestamp in pairs( _readtimes ) do
-                               if os_difftime( _currenttime - timestamp ) > _readtimeout then
-                                       --_readtimes[ handler ] = nil
-                                       handler.disconnect( )( handler, "read timeout" )
-                                       handler:close( )        -- forced disconnect?
-                               end
-                       end
-               end
-       end
-)
-
 local function setlogger(new_logger)
        local old_logger = log;
        if new_logger then
@@ -924,7 +1035,7 @@ return {
 
        addclient = addclient,
        wrapclient = wrapclient,
-       
+
        loop = loop,
        link = link,
        step = step,