Files
3cloud-backend/websocket_server/events/messages.py
T
coryHawkvelt 3bca326b51 feat(websocket): add debug flag for ping operations
Introduce `ENABLE_WEBSOCKET_PING_DEBUG` environment variable to
conditionally enable verbose logging for websocket ping sends and
pong receptions, reducing log noise in production.
2025-10-27 07:21:16 +10:30

148 lines
5.4 KiB
Python

# websocket_server/events/messages.py
import time
from flask import request
from flask_socketio import emit
import logging
from websocket_server.config import get_redis_client
from api_client.client import IaaSClient
from websocket_server.shared_state import connected_workers, connected_sids_lock, connected_sids, worker_lock, ping_tracker
from websocket_server.config import REDIS_PING_EXPIRY_SECONDS, ENABLE_WEBSOCKET_PING_DEBUG
logger = logging.getLogger("websocket_server")
def register_socketio_handlers(socketio):
@socketio.on("connect")
def handle_connect():
with connected_sids_lock:
connected_sids.add(request.sid)
logger.info(f"Client connected from {request.remote_addr} SID={request.sid}")
emit("welcome", {"message": f"Connected to server. Your SID is {request.sid}"})
@socketio.on("disconnect")
def handle_disconnect():
from websocket_server.worker_manager import notify_worker_disconnect
sid = request.sid
logger.info(f"SID={sid} disconnected")
with connected_sids_lock:
connected_sids.discard(sid)
# Check if it's a worker
worker_id = None
with worker_lock:
for w_id, s_id in connected_workers.items():
if s_id == sid:
worker_id = w_id
break
if worker_id:
del connected_workers[worker_id]
logger.warning(f"Worker {worker_id} disconnected")
# Delete Redis ping keys to prevent stale heartbeats from marking worker online
redis_client = get_redis_client()
redis_client.delete(f"ws_liveness:{worker_id}")
redis_client.delete(f"ws_latency:{worker_id}")
notify_worker_disconnect(worker_id)
@socketio.on("join_request")
def handle_join_request(data):
from websocket_server.worker_manager import notify_worker_online, start_worker_thread
from websocket_server.task_assigner import assign_task_to_worker
worker_id = data.get("worker_id")
worker_secret = data.get("worker_secret")
logger.info(f"Join request received from {worker_id}")
try:
api = IaaSClient("xxx")
db_worker = api.get_workload_host(worker_id)
if not db_worker:
logger.error(f"{worker_id} not found in DB")
emit("join_reject", {"message": "Join request rejected"}, to=request.sid)
return
if db_worker["secret_key"] != worker_secret:
logger.error(f"Invalid secret for worker {worker_id}")
emit("join_reject", {"message": "Join request rejected"}, to=request.sid)
return
with worker_lock:
connected_workers[worker_id] = request.sid
redis_client = get_redis_client()
redis_client.set(f"worker_status_{worker_id}", "idle")
start_worker_thread(worker_id)
notify_worker_online(worker_id)
logger.info(f"Worker {worker_id} accepted")
emit("join_accept", {"message": f"Worker {worker_id} accepted"}, to=request.sid)
emit("join_broadcast", {"message": f"Worker {worker_id} subscribed to task queue."})
assign_task_to_worker(worker_id)
except Exception as e:
logger.exception(f"Error processing join for {worker_id}: {e}")
emit("join_reject", {"message": "Internal error during join"}, to=request.sid)
@socketio.on("send_message")
def handle_send_message(data):
"""
Relay a message from a client to a specific worker by worker_id.
"""
worker_id = data.get("worker_id")
message = data.get("message")
if not worker_id or not message:
logger.warning(f"Invalid send_message request: {data}")
emit("error", {"message": "Missing worker_id or message"})
return
with worker_lock:
target_sid = connected_workers.get(worker_id)
if not target_sid:
logger.error(f"Worker {worker_id} is not connected")
emit("error", {"message": f"Worker {worker_id} is not connected"})
return
try:
logger.info(f"Relaying message to Worker {worker_id} (SID={target_sid})")
emit("message", {"message": message}, to=target_sid)
except Exception as e:
logger.exception(f"Failed to send message to {worker_id}: {e}")
emit("error", {"message": f"Failed to send message to {worker_id}"})
@socketio.on("worker_pong")
def handle_worker_pong(data):
worker_id = data.get("worker_id")
pong_id = data.get("ping_id")
redis = get_redis_client()
if not worker_id or not pong_id:
logger.warning("Malformed pong received")
return
ping_info = ping_tracker.get(worker_id)
if not ping_info or ping_info["ping_id"] != pong_id:
logger.warning(f"[{worker_id}] Received unmatched pong")
return
latency_ms = int((time.time() - ping_info["sent_time"]) * 1000)
if ENABLE_WEBSOCKET_PING_DEBUG:
logger.debug(f"[{worker_id}] Pong received. Latency: {latency_ms}ms")
redis.set(f"ws_liveness:{worker_id}", int(time.time()), ex=REDIS_PING_EXPIRY_SECONDS)
redis.set(f"ws_latency:{worker_id}", latency_ms, ex=REDIS_PING_EXPIRY_SECONDS)
del ping_tracker[worker_id]