Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gracefuly check connection status with server PINGs #124

Merged
merged 4 commits into from
Aug 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 12 additions & 30 deletions pydle/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class BasicClient:
Base IRC client class.
This class on its own is not complete: in order to be able to run properly, _has_message, _parse_message and _create_message have to be overloaded.
"""
PING_TIMEOUT = 300
READ_TIMEOUT = 300
RECONNECT_ON_ERROR = True
RECONNECT_MAX_ATTEMPTS = 3
RECONNECT_DELAYED = True
Expand Down Expand Up @@ -66,7 +66,6 @@ def _reset_attributes(self):
self._receive_buffer = b''
self._pending = {}
self._handler_top_level = False
self._ping_checker_handle = None

# Misc.
self.logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -115,10 +114,6 @@ async def connect(self, hostname=None, port=None, reconnect=False, **kwargs):
async def disconnect(self, expected=True):
""" Disconnect from server. """
if self.connected:
# Unschedule ping checker.
if self._ping_checker_handle:
self._ping_checker_handle.cancel()

# Schedule disconnect.
await self._disconnect(expected)

Expand Down Expand Up @@ -159,21 +154,6 @@ def _reconnect_delay(self):
else:
return 0

async def _perform_ping_timeout(self, delay: int):
""" Handle timeout gracefully.

Args:
delay (int): delay before raising the timeout (in seconds)
"""

# pause for delay seconds
await sleep(delay)
# then continue
error = TimeoutError(
'Ping timeout: no data received from server in {timeout} seconds.'.format(
timeout=self.PING_TIMEOUT))
await self.on_data_error(error)

## Internal database management.

def _create_channel(self, channel):
Expand Down Expand Up @@ -365,7 +345,17 @@ async def _send(self, input):
async def handle_forever(self):
""" Handle data forever. """
while self.connected:
data = await self.connection.recv()
try:
data = await self.connection.recv(timeout=self.READ_TIMEOUT)
except asyncio.TimeoutError:
self.logger.warning('>> Receive timeout reached, sending ping to check connection state...')

try:
await self.rawmsg("PING", self.server_tag)
data = await self.connection.recv(timeout=self.READ_TIMEOUT)
except asyncio.TimeoutError:
data = None

if not data:
if self.connected:
await self.disconnect(expected=False)
Expand All @@ -378,14 +368,6 @@ async def on_data(self, data):
""" Handle received data. """
self._receive_buffer += data

# Schedule new timeout event.
if self._ping_checker_handle:
self._ping_checker_handle.cancel()

# create a task for the ping checker
self._ping_checker_handle = self.eventloop.create_task(
self._perform_ping_timeout(self.PING_TIMEOUT))

while self._has_message():
message = self._parse_message()
self.eventloop.create_task(self.on_raw(message))
Expand Down
4 changes: 2 additions & 2 deletions pydle/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,5 @@ async def send(self, data):
self.writer.write(data)
await self.writer.drain()

async def recv(self):
return await self.reader.readline()
async def recv(self, *, timeout=None):
shizmob marked this conversation as resolved.
Show resolved Hide resolved
return await asyncio.wait_for(self.reader.readline(), timeout=timeout)
3 changes: 3 additions & 0 deletions pydle/features/rfc1459/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ async def on_raw_error(self, message):
error = protocol.ServerError(' '.join(message.params))
await self.on_data_error(error)

async def on_raw_pong(self, message):
self.logger.debug('>> PONG received')

async def on_raw_invite(self, message):
""" INVITE command. """
nick, metadata = self._parse_user(message.source)
Expand Down