@ -27,12 +27,14 @@ __license__ = "GPL"
from pickle import dumps , loads , HIGHEST_PROTOCOL
import asynchat
import asyncore
import errno
import fcntl
import os
import socket
import sys
import traceback
from . utils import Utils
from . . protocol import CSPROTO
from . . helpers import getLogger , formatExceptionInfo
@ -89,6 +91,29 @@ class RequestHandler(asynchat.async_chat):
self . close ( )
def loop ( active , timeout = None , use_poll = False ) :
# Use poll instead of loop, because of recognition of active flag,
# because of loop timeout mistake: different in poll and poll2 (sec vs ms),
# and to prevent sporadical errors like EBADF 'Bad file descriptor' etc. (see gh-161)
if timeout is None :
timeout = Utils . DEFAULT_SLEEP_TIME
poll = asyncore . poll
if use_poll and asyncore . poll2 and hasattr ( asyncore . select , ' poll ' ) : # pragma: no cover
logSys . debug ( ' Server listener (select) uses poll ' )
# poll2 expected a timeout in milliseconds (but poll and loop in seconds):
timeout = float ( timeout ) / 1000
poll = asyncore . poll2
# Poll as long as active:
while active ( ) :
try :
poll ( timeout )
except Exception as e : # pragma: no cover
if e . args [ 0 ] in ( errno . ENOTCONN , errno . EBADF ) : # (errno.EBADF, 'Bad file descriptor')
logSys . info ( ' Server connection was closed: %s ' , str ( e ) )
else :
logSys . error ( ' Server connection was closed: %s ' , str ( e ) )
##
# Asynchronous server class.
#
@ -102,6 +127,7 @@ class AsyncServer(asyncore.dispatcher):
self . __transmitter = transmitter
self . __sock = " /var/run/fail2ban/fail2ban.sock "
self . __init = False
self . __active = False
##
# Returns False as we only read the socket first.
@ -129,7 +155,7 @@ class AsyncServer(asyncore.dispatcher):
# @param sock: socket file.
# @param force: remove the socket file if exists.
def start ( self , sock , force ):
def start ( self , sock , force , use_poll = False ):
self . __sock = sock
# Remove socket
if os . path . exists ( sock ) :
@ -149,28 +175,31 @@ class AsyncServer(asyncore.dispatcher):
AsyncServer . __markCloseOnExec ( self . socket )
self . listen ( 1 )
# Sets the init flag.
self . __init = True
# TODO Add try..catch
# There's a bug report for Python 2.6/3.0 that use_poll=True yields some 2.5 incompatibilities:
if ( sys . version_info > = ( 2 , 7 ) and sys . version_info < ( 2 , 8 ) ) \
or ( sys . version_info > = ( 3 , 4 ) ) : # if python 2.7 ...
logSys . debug ( " Detected Python 2.7. asyncore.loop() using poll " )
asyncore . loop ( use_poll = True ) # workaround for the "Bad file descriptor" issue on Python 2.7, gh-161
else :
asyncore . loop ( use_poll = False ) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
self . __init = self . __active = True
# Event loop as long as active:
loop ( lambda : self . __active )
# Cleanup all
self . stop ( )
def close ( self ) :
if self . __active :
asyncore . dispatcher . close ( self )
# Remove socket (file) only if it was created:
if self . __init and os . path . exists ( self . __sock ) :
logSys . debug ( " Removed socket file " + self . __sock )
os . remove ( self . __sock )
logSys . debug ( " Socket shutdown " )
self . __active = False
##
# Stops the communication server.
def stop ( self ) :
if self . __init :
# Only closes the socket if it was initialized first.
self . close ( )
# Remove socket
if os . path . exists ( self . __sock ) :
logSys . debug ( " Removed socket file " + self . __sock )
os . remove ( self . __sock )
logSys . debug ( " Socket shutdown " )
self . close ( )
def isActive ( self ) :
return self . __active
##
# Marks socket as close-on-exec to avoid leaking file descriptors when