Add server-side flag-based thread stopping with socket timeouts

This commit is contained in:
Xevion
2022-06-12 16:03:31 -05:00
parent 66dd219b78
commit 8537eeced6
2 changed files with 50 additions and 26 deletions

View File

@@ -5,12 +5,12 @@ import socket
import time import time
import uuid import uuid
from json import JSONDecodeError from json import JSONDecodeError
from typing import Any, List, Optional from typing import Any, Callable, List, Optional
from shared import constants from shared import constants
from shared import helpers from shared import helpers
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from shared.exceptions import DataReceptionException from shared.exceptions import DataReceptionException, StopException
from server import db from server import db
from server.commands import CommandHandler from server.commands import CommandHandler
@@ -21,11 +21,14 @@ logger.setLevel(logging.DEBUG)
class BaseClient(object): class BaseClient(object):
"""A simple base class for the client containing basic client communication methods.""" """A simple base class for the client containing basic client communication methods."""
def __init__(self, conn: socket.socket, all_clients: List['Client'], address) -> None: def __init__(self, conn: socket.socket, all_clients: List['Client'], address, stop_flag: Callable[[], bool]) -> None:
self.conn, self.all_clients, self.address = conn, all_clients, address self.conn, self.all_clients, self.address, self.stop_flag = conn, all_clients, address, stop_flag
self.db: Optional[db.ServerDatabase] = None self.db: Optional[db.ServerDatabase] = None
self.conn.settimeout(0.5)
def connect_database(self): def connect_database(self):
""""""
if self.db is None: if self.db is None:
logger.debug('Connecting client to database.') logger.debug('Connecting client to database.')
self.db = db.ServerDatabase() self.db = db.ServerDatabase()
@@ -68,8 +71,8 @@ class Client(BaseClient):
Client.run() should be ran in a thread alongside the other clients. Client.run() should be ran in a thread alongside the other clients.
""" """
def __init__(self, conn: socket.socket, address: Any, all_clients: List['Client']): def __init__(self, conn: socket.socket, address: Any, all_clients: List['Client'], stop_flag: Callable[[], bool]):
super().__init__(conn, all_clients, address) super().__init__(conn, all_clients, address, stop_flag)
self.id = str(uuid.uuid4()) self.id = str(uuid.uuid4())
self.short_id = self.id[:8] self.short_id = self.id[:8]
@@ -126,12 +129,17 @@ class Client(BaseClient):
cur.close() cur.close()
def receive(self) -> Any: def receive(self) -> Any:
while True:
try: try:
self.check_stop()
length = int(self.conn.recv(constants.HEADER_LENGTH).decode('utf-8')) length = int(self.conn.recv(constants.HEADER_LENGTH).decode('utf-8'))
except socket.timeout:
continue
except ValueError: except ValueError:
raise DataReceptionException('The socket did not receive the expected header.') raise DataReceptionException('The socket did not receive the expected header.')
else: else:
logger.debug(f'Header received - Length {length}') logger.debug(f'Header received - Length {length}')
break
try: try:
data = self.conn.recv(length).decode('utf-8') data = self.conn.recv(length).decode('utf-8')
@@ -155,8 +163,14 @@ class Client(BaseClient):
for client in self.all_clients: for client in self.all_clients:
client.send_connections_list() client.send_connections_list()
def check_stop(self) -> None:
"""Raises a StopException if the stop flag is set to true by the commanding main thread."""
stop_flag: bool = self.stop_flag()
if stop_flag:
raise StopException()
def close(self) -> None: def close(self) -> None:
logger.info(f'Client {self.id} closed. ({self.nickname})') logger.info(f'Shutting down Client {self.id}. ({self.nickname})')
self.conn.close() # Close socket connection self.conn.close() # Close socket connection
self.all_clients.remove(self) # Remove the user from the global client list self.all_clients.remove(self) # Remove the user from the global client list
self.broadcast_message(f'{self.nickname} left!') # Now we can broadcast it's exit message self.broadcast_message(f'{self.nickname} left!') # Now we can broadcast it's exit message
@@ -209,7 +223,11 @@ class Client(BaseClient):
self.close() self.close()
break break
except ConnectionResetError: except ConnectionResetError:
logger.critical('Lost connection to the client. Exiting.') logger.critical('Lost connection to the client.')
self.close()
break
except StopException:
logger.info('Stop flag received from main thread.')
self.close() self.close()
break break
except Exception as e: except Exception as e:

View File

@@ -1,5 +1,11 @@
class TCPChatException(BaseException): class TCPChatException(BaseException):
pass pass
class DataReceptionException(TCPChatException): class DataReceptionException(TCPChatException):
pass pass
class StopException(TCPChatException):
"""An exception that occurs when a thread finds a stop flag."""
pass