Browse Source

Move server opening/closing to its own task

Fixes the issue referred to in #583
patch-2
Neil Booth 7 years ago
parent
commit
8ea2bed403
  1. 50
      electrumx/server/session.py

50
electrumx/server/session.py

@ -21,7 +21,8 @@ from functools import partial
from aiorpcx import ( from aiorpcx import (
ServerSession, JSONRPCAutoDetect, JSONRPCConnection, ServerSession, JSONRPCAutoDetect, JSONRPCConnection,
TaskGroup, handler_invocation, RPCError, Request, ignore_after TaskGroup, handler_invocation, RPCError, Request, ignore_after, sleep,
Event
) )
import electrumx import electrumx
@ -107,8 +108,6 @@ class SessionGroup(object):
class SessionManager(object): class SessionManager(object):
'''Holds global state about all sessions.''' '''Holds global state about all sessions.'''
CATCHING_UP, LISTENING, PAUSED, SHUTTING_DOWN = range(4)
def __init__(self, env, db, bp, daemon, mempool, notifications, def __init__(self, env, db, bp, daemon, mempool, notifications,
shutdown_event): shutdown_event):
env.max_send = max(350000, env.max_send) env.max_send = max(350000, env.max_send)
@ -122,11 +121,8 @@ class SessionManager(object):
self.logger = util.class_logger(__name__, self.__class__.__name__) self.logger = util.class_logger(__name__, self.__class__.__name__)
self.servers = {} self.servers = {}
self.sessions = set() self.sessions = set()
self.max_sessions = env.max_sessions
self.low_watermark = self.max_sessions * 19 // 20
self.max_subs = env.max_subs self.max_subs = env.max_subs
self.cur_group = SessionGroup(0) self.cur_group = SessionGroup(0)
self.state = self.CATCHING_UP
self.txs_sent = 0 self.txs_sent = 0
self.start_time = time.time() self.start_time = time.time()
self.history_cache = pylru.lrucache(256) self.history_cache = pylru.lrucache(256)
@ -138,7 +134,8 @@ class SessionManager(object):
self.mn_cache_height = 0 self.mn_cache_height = 0
self.mn_cache = [] self.mn_cache = []
# Event triggered when electrumx is listening for incoming requests. # Event triggered when electrumx is listening for incoming requests.
self.server_listening = asyncio.Event() self.server_listening = Event()
self.session_event = Event()
# Tell sessions about subscription changes # Tell sessions about subscription changes
notifications.add_callback(self._notify_sessions) notifications.add_callback(self._notify_sessions)
@ -179,8 +176,6 @@ class SessionManager(object):
sslc = ssl.SSLContext(ssl.PROTOCOL_TLS) sslc = ssl.SSLContext(ssl.PROTOCOL_TLS)
sslc.load_cert_chain(env.ssl_certfile, keyfile=env.ssl_keyfile) sslc.load_cert_chain(env.ssl_certfile, keyfile=env.ssl_keyfile)
await self._start_server('SSL', host, env.ssl_port, ssl=sslc) await self._start_server('SSL', host, env.ssl_port, ssl=sslc)
# Change state
self.state = self.LISTENING
self.server_listening.set() self.server_listening.set()
async def _close_servers(self, kinds): async def _close_servers(self, kinds):
@ -194,21 +189,31 @@ class SessionManager(object):
server.close() server.close()
await server.wait_closed() await server.wait_closed()
async def _restart_if_paused(self): async def _manage_servers(self):
paused = False
max_sessions = self.env.max_sessions
low_watermark = max_sessions * 19 // 20
while True: while True:
await asyncio.sleep(15) await self.session_event.wait()
self.session_event.clear()
if not paused and len(self.sessions) >= max_sessions:
session.logger.info(f'maximum sessions {max_sessions:,d} '
f'reached, stopping new connections until '
f'count drops to {low_watermark:,d}')
await self._close_servers(['TCP', 'SSL'])
paused = True
# Start listening for incoming connections if paused and # Start listening for incoming connections if paused and
# session count has fallen # session count has fallen
if (self.state == self.PAUSED and if paused and len(self.sessions) <= low_watermark:
len(self.sessions) <= self.low_watermark):
await self._start_external_servers() await self._start_external_servers()
paused = False
async def _log_sessions(self): async def _log_sessions(self):
'''Periodically log sessions.''' '''Periodically log sessions.'''
log_interval = self.env.log_sessions log_interval = self.env.log_sessions
if log_interval: if log_interval:
while True: while True:
await asyncio.sleep(log_interval) await sleep(log_interval)
data = self._session_data(for_log=True) data = self._session_data(for_log=True)
for line in text.sessions_lines(data): for line in text.sessions_lines(data):
self.logger.info(line) self.logger.info(line)
@ -250,7 +255,7 @@ class SessionManager(object):
async def _clear_stale_sessions(self): async def _clear_stale_sessions(self):
'''Cut off sessions that haven't done anything for 10 minutes.''' '''Cut off sessions that haven't done anything for 10 minutes.'''
while True: while True:
await asyncio.sleep(60) await sleep(60)
stale_cutoff = time.time() - self.env.session_timeout stale_cutoff = time.time() - self.env.session_timeout
stale_sessions = [session for session in self.sessions stale_sessions = [session for session in self.sessions
if session.last_recv < stale_cutoff] if session.last_recv < stale_cutoff]
@ -480,7 +485,7 @@ class SessionManager(object):
await self._start_server('RPC', self.env.cs_host(for_rpc=True), await self._start_server('RPC', self.env.cs_host(for_rpc=True),
self.env.rpc_port) self.env.rpc_port)
await event.wait() await event.wait()
self.logger.info(f'max session count: {self.max_sessions:,d}') self.logger.info(f'max session count: {self.env.max_sessions:,d}')
self.logger.info(f'session timeout: ' self.logger.info(f'session timeout: '
f'{self.env.session_timeout:,d} seconds') f'{self.env.session_timeout:,d} seconds')
self.logger.info('session bandwidth limit {:,d} bytes' self.logger.info('session bandwidth limit {:,d} bytes'
@ -501,10 +506,9 @@ class SessionManager(object):
await group.spawn(self.peer_mgr.discover_peers()) await group.spawn(self.peer_mgr.discover_peers())
await group.spawn(self._clear_stale_sessions()) await group.spawn(self._clear_stale_sessions())
await group.spawn(self._log_sessions()) await group.spawn(self._log_sessions())
await group.spawn(self._restart_if_paused()) await group.spawn(self._manage_servers())
finally: finally:
# Close servers and sessions # Close servers and sessions
self.state = self.SHUTTING_DOWN
await self._close_servers(list(self.servers.keys())) await self._close_servers(list(self.servers.keys()))
async with TaskGroup() as group: async with TaskGroup() as group:
for session in list(self.sessions): for session in list(self.sessions):
@ -568,14 +572,7 @@ class SessionManager(object):
def add_session(self, session): def add_session(self, session):
self.sessions.add(session) self.sessions.add(session)
if (len(self.sessions) >= self.max_sessions self.session_event.set()
and self.state == self.LISTENING):
self.state = self.PAUSED
session.logger.info('maximum sessions {:,d} reached, stopping new '
'connections until count drops to {:,d}'
.format(self.max_sessions, self.low_watermark))
loop = asyncio.get_event_loop()
loop.call_soon(self._close_servers(['TCP', 'SSL']))
gid = int(session.start_time - self.start_time) // 900 gid = int(session.start_time - self.start_time) // 900
if self.cur_group.gid != gid: if self.cur_group.gid != gid:
self.cur_group = SessionGroup(gid) self.cur_group = SessionGroup(gid)
@ -584,6 +581,7 @@ class SessionManager(object):
def remove_session(self, session): def remove_session(self, session):
'''Remove a session from our sessions list if there.''' '''Remove a session from our sessions list if there.'''
self.sessions.remove(session) self.sessions.remove(session)
self.session_event.set()
def new_subscription(self): def new_subscription(self):
if self.subs_room <= 0: if self.subs_room <= 0:

Loading…
Cancel
Save