Skip to content

Commit 111acf6

Browse files
author
clowwindy
committed
fix graceful restart and add unit test
1 parent e8b2946 commit 111acf6

File tree

8 files changed

+121
-9
lines changed

8 files changed

+121
-9
lines changed

shadowsocks/eventloop.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,15 @@ def stop(self):
192192
def run(self):
193193
events = []
194194
while not self._stopping:
195-
now = time.time()
195+
asap = False
196196
try:
197197
events = self.poll(TIMEOUT_PRECISION)
198198
except (OSError, IOError) as e:
199199
if errno_from_exception(e) in (errno.EPIPE, errno.EINTR):
200200
# EPIPE: Happens when the client closes the connection
201201
# EINTR: Happens when received a signal
202202
# handles them as soon as possible
203+
asap = True
203204
logging.debug('poll:%s', e)
204205
else:
205206
logging.error('poll:%s', e)
@@ -214,7 +215,8 @@ def run(self):
214215
handler.handle_event(sock, fd, event)
215216
except (OSError, IOError) as e:
216217
shell.print_exception(e)
217-
if now - self._last_time >= TIMEOUT_PRECISION:
218+
now = time.time()
219+
if asap or now - self._last_time >= TIMEOUT_PRECISION:
218220
for callback in self._periodic_callbacks:
219221
callback()
220222
self._last_time = now

shadowsocks/tcprelay.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -689,18 +689,19 @@ def handle_event(self, sock, fd, event):
689689
logging.warn('poll removed fd')
690690

691691
def handle_periodic(self):
692-
self._sweep_timeout()
693692
if self._closed:
694693
if self._server_socket:
695694
self._eventloop.remove(self._server_socket, self)
696-
self._eventloop.remove_periodic(self.handle_periodic)
697695
self._server_socket.close()
698696
self._server_socket = None
699-
logging.info('closed listen port %d', self._listen_port)
697+
logging.info('closed TCP port %d', self._listen_port)
700698
if not self._fd_to_handlers:
699+
logging.info('stopping')
701700
self._eventloop.stop()
701+
self._sweep_timeout()
702702

703703
def close(self, next_tick=False):
704+
logging.debug('TCP close')
704705
self._closed = True
705706
if not next_tick:
706707
if self._eventloop:

shadowsocks/udprelay.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,14 +272,18 @@ def handle_event(self, sock, fd, event):
272272
self._handle_client(sock)
273273

274274
def handle_periodic(self):
275+
if self._closed:
276+
if self._server_socket:
277+
logging.info('closed UDP port %d', self._listen_port)
278+
self._server_socket.close()
279+
self._server_socket = None
280+
for sock in self._sockets:
281+
sock.close()
275282
self._cache.sweep()
276283
self._client_fd_to_server_addr.sweep()
277-
if self._closed:
278-
self._server_socket.close()
279-
for sock in self._sockets:
280-
sock.close()
281284

282285
def close(self, next_tick=False):
286+
logging.debug('UDP close')
283287
self._closed = True
284288
if not next_tick:
285289
if self._eventloop:

tests/graceful.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"server":"127.0.0.1",
3+
"server_port":8387,
4+
"local_port":1081,
5+
"password":"aes_password",
6+
"timeout":15,
7+
"method":"aes-256-cfb",
8+
"local_address":"127.0.0.1",
9+
"fast_open":false
10+
}

tests/graceful_cli.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/python
2+
3+
import socket
4+
import socks
5+
import time
6+
7+
8+
SERVER_IP = '127.0.0.1'
9+
SERVER_PORT = 8001
10+
11+
12+
if __name__ == '__main__':
13+
s = socks.socksocket()
14+
s.set_proxy(socks.SOCKS5, SERVER_IP, 1081)
15+
s.connect((SERVER_IP, SERVER_PORT))
16+
s.send(b'test')
17+
time.sleep(30)
18+
s.close()

tests/graceful_server.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/python
2+
3+
import socket
4+
5+
6+
if __name__ == '__main__':
7+
s = socket.socket()
8+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
9+
s.bind(('127.0.0.1', 8001))
10+
s.listen(1024)
11+
c = None
12+
while True:
13+
c = s.accept()

tests/jenkins.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ if [ -f /proc/sys/net/ipv4/tcp_fastopen ] ; then
6969
fi
7070

7171
run_test tests/test_large_file.sh
72+
run_test tests/test_graceful_restart.sh
7273
run_test tests/test_udp_src.sh
7374
run_test tests/test_command.sh
7475

tests/test_graceful_restart.sh

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/bin/bash
2+
3+
PYTHON="coverage run -p -a"
4+
URL=http://127.0.0.1/file
5+
6+
7+
# setup processes
8+
$PYTHON shadowsocks/local.py -c tests/graceful.json &
9+
LOCAL=$!
10+
11+
$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" &
12+
SERVER=$!
13+
14+
python tests/graceful_server.py &
15+
GSERVER=$!
16+
17+
sleep 1
18+
19+
python tests/graceful_cli.py &
20+
GCLI=$!
21+
22+
sleep 1
23+
24+
# graceful restart server: send SIGQUIT to old process and start a new one
25+
kill -s SIGQUIT $SERVER
26+
$PYTHON shadowsocks/server.py -c tests/graceful.json --forbidden-ip "" &
27+
NEWSERVER=$!
28+
29+
sleep 1
30+
31+
# check old server
32+
ps x | grep -v grep | grep $SERVER
33+
OLD_SERVER_RUNNING1=$?
34+
# old server should not quit at this moment
35+
echo old server running: $OLD_SERVER_RUNNING1
36+
37+
sleep 1
38+
39+
# close connections on old server
40+
kill -s SIGINT $GCLI
41+
kill -s SIGKILL $GSERVER
42+
kill -s SIGINT $LOCAL
43+
44+
sleep 11
45+
46+
# check old server
47+
ps x | grep -v grep | grep $SERVER
48+
OLD_SERVER_RUNNING2=$?
49+
# old server should quit at this moment
50+
echo old server running: $OLD_SERVER_RUNNING2
51+
52+
# new server is expected running
53+
kill -s SIGINT $NEWSERVER || exit 1
54+
55+
if [ $OLD_SERVER_RUNNING1 -ne 0 ]; then
56+
exit 1
57+
fi
58+
59+
if [ $OLD_SERVER_RUNNING2 -ne 1 ]; then
60+
kill -s SIGINT $SERVER
61+
sleep 1
62+
exit 1
63+
fi

0 commit comments

Comments
 (0)