diff --git a/shadowsocks/eventloop.py b/shadowsocks/eventloop.py index 09e9761..05927d3 100644 --- a/shadowsocks/eventloop.py +++ b/shadowsocks/eventloop.py @@ -192,7 +192,7 @@ class EventLoop(object): def run(self): events = [] while not self._stopping: - now = time.time() + asap = False try: events = self.poll(TIMEOUT_PRECISION) except (OSError, IOError) as e: @@ -200,6 +200,7 @@ class EventLoop(object): # EPIPE: Happens when the client closes the connection # EINTR: Happens when received a signal # handles them as soon as possible + asap = True logging.debug('poll:%s', e) else: logging.error('poll:%s', e) @@ -214,7 +215,8 @@ class EventLoop(object): handler.handle_event(sock, fd, event) except (OSError, IOError) as e: shell.print_exception(e) - if now - self._last_time >= TIMEOUT_PRECISION: + now = time.time() + if asap or now - self._last_time >= TIMEOUT_PRECISION: for callback in self._periodic_callbacks: callback() self._last_time = now diff --git a/shadowsocks/tcprelay.py b/shadowsocks/tcprelay.py index 38d4101..516da32 100644 --- a/shadowsocks/tcprelay.py +++ b/shadowsocks/tcprelay.py @@ -689,18 +689,19 @@ class TCPRelay(object): logging.warn('poll removed fd') def handle_periodic(self): - self._sweep_timeout() if self._closed: if self._server_socket: self._eventloop.remove(self._server_socket, self) - self._eventloop.remove_periodic(self.handle_periodic) self._server_socket.close() self._server_socket = None - logging.info('closed listen port %d', self._listen_port) + logging.info('closed TCP port %d', self._listen_port) if not self._fd_to_handlers: + logging.info('stopping') self._eventloop.stop() + self._sweep_timeout() def close(self, next_tick=False): + logging.debug('TCP close') self._closed = True if not next_tick: if self._eventloop: diff --git a/shadowsocks/udprelay.py b/shadowsocks/udprelay.py index c2386d9..99e9ed3 100644 --- a/shadowsocks/udprelay.py +++ b/shadowsocks/udprelay.py @@ -272,14 +272,18 @@ class UDPRelay(object): self._handle_client(sock) def handle_periodic(self): + if self._closed: + if self._server_socket: + logging.info('closed UDP port %d', self._listen_port) + self._server_socket.close() + self._server_socket = None + for sock in self._sockets: + sock.close() self._cache.sweep() self._client_fd_to_server_addr.sweep() - if self._closed: - self._server_socket.close() - for sock in self._sockets: - sock.close() def close(self, next_tick=False): + logging.debug('UDP close') self._closed = True if not next_tick: if self._eventloop: diff --git a/tests/graceful.json b/tests/graceful.json new file mode 100644 index 0000000..4b95434 --- /dev/null +++ b/tests/graceful.json @@ -0,0 +1,10 @@ +{ + "server":"127.0.0.1", + "server_port":8387, + "local_port":1081, + "password":"aes_password", + "timeout":15, + "method":"aes-256-cfb", + "local_address":"127.0.0.1", + "fast_open":false +} diff --git a/tests/graceful_cli.py b/tests/graceful_cli.py new file mode 100644 index 0000000..ef6110e --- /dev/null +++ b/tests/graceful_cli.py @@ -0,0 +1,18 @@ +#!/usr/bin/python + +import socket +import socks +import time + + +SERVER_IP = '127.0.0.1' +SERVER_PORT = 8001 + + +if __name__ == '__main__': + s = socks.socksocket() + s.set_proxy(socks.SOCKS5, SERVER_IP, 1081) + s.connect((SERVER_IP, SERVER_PORT)) + s.send(b'test') + time.sleep(30) + s.close() diff --git a/tests/graceful_server.py b/tests/graceful_server.py new file mode 100644 index 0000000..d7038f1 --- /dev/null +++ b/tests/graceful_server.py @@ -0,0 +1,13 @@ +#!/usr/bin/python + +import socket + + +if __name__ == '__main__': + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('127.0.0.1', 8001)) + s.listen(1024) + c = None + while True: + c = s.accept() diff --git a/tests/jenkins.sh b/tests/jenkins.sh index ea5c163..32dd30e 100755 --- a/tests/jenkins.sh +++ b/tests/jenkins.sh @@ -69,6 +69,7 @@ if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then fi run_test tests/test_large_file.sh +run_test tests/test_graceful_restart.sh run_test tests/test_udp_src.sh run_test tests/test_command.sh diff --git a/tests/test_graceful_restart.sh b/tests/test_graceful_restart.sh new file mode 100755 index 0000000..cec984a --- /dev/null +++ b/tests/test_graceful_restart.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +PYTHON="coverage run -p -a" +URL=http://127.0.0.1/file + + +# setup processes +$PYTHON shadowsocks/local.py -c tests/graceful.json & +LOCAL=$! + +$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & +SERVER=$! + +python tests/graceful_server.py & +GSERVER=$! + +sleep 1 + +python tests/graceful_cli.py & +GCLI=$! + +sleep 1 + +# graceful restart server: send SIGQUIT to old process and start a new one +kill -s SIGQUIT $SERVER +$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" & +NEWSERVER=$! + +sleep 1 + +# check old server +ps x | grep -v grep | grep $SERVER +OLD_SERVER_RUNNING1=$? +# old server should not quit at this moment +echo old server running: $OLD_SERVER_RUNNING1 + +sleep 1 + +# close connections on old server +kill -s SIGINT $GCLI +kill -s SIGKILL $GSERVER +kill -s SIGINT $LOCAL + +sleep 11 + +# check old server +ps x | grep -v grep | grep $SERVER +OLD_SERVER_RUNNING2=$? +# old server should quit at this moment +echo old server running: $OLD_SERVER_RUNNING2 + +# new server is expected running +kill -s SIGINT $NEWSERVER || exit 1 + +if [ $OLD_SERVER_RUNNING1 -ne 0 ]; then + exit 1 +fi + +if [ $OLD_SERVER_RUNNING2 -ne 1 ]; then + kill -s SIGINT $SERVER + sleep 1 + exit 1 +fi