add Leader election

This commit is contained in:
hjlarry
2025-08-21 16:17:16 +08:00
parent 72037a1865
commit 7dc8557033
4 changed files with 243 additions and 19 deletions

View File

@ -70,10 +70,17 @@ def handle_user_connect(sid, data):
redis_client.hset(f"workflow_online_users:{workflow_id}", user_id, json.dumps(user_info))
redis_client.set(f"ws_sid_map:{sid}", json.dumps({"workflow_id": workflow_id, "user_id": user_id}))
# Leader election: first user becomes the leader
leader_id = get_or_set_leader(workflow_id, user_id)
is_leader = leader_id == user_id
sio.enter_room(sid, workflow_id)
broadcast_online_users(workflow_id)
# Notify user of their status
sio.emit("status", {"isLeader": is_leader}, room=sid)
return {"msg": "connected", "user_id": user_id, "sid": sid}
return {"msg": "connected", "user_id": user_id, "sid": sid, "isLeader": is_leader}
@sio.on("disconnect")
@ -89,9 +96,83 @@ def handle_disconnect(sid):
redis_client.hdel(f"workflow_online_users:{workflow_id}", user_id)
redis_client.delete(f"ws_sid_map:{sid}")
# Handle leader re-election if the leader disconnected
handle_leader_disconnect(workflow_id, user_id)
broadcast_online_users(workflow_id)
def get_or_set_leader(workflow_id, user_id):
"""
Get current leader or set the user as leader if no leader exists.
Returns the leader user_id.
"""
leader_key = f"workflow_leader:{workflow_id}"
current_leader = redis_client.get(leader_key)
if not current_leader:
# No leader exists, make this user the leader
redis_client.set(leader_key, user_id, ex=3600) # Expire in 1 hour
return user_id
return current_leader.decode('utf-8') if isinstance(current_leader, bytes) else current_leader
def handle_leader_disconnect(workflow_id, disconnected_user_id):
"""
Handle leader re-election when a user disconnects.
"""
leader_key = f"workflow_leader:{workflow_id}"
current_leader = redis_client.get(leader_key)
if current_leader:
current_leader = current_leader.decode('utf-8') if isinstance(current_leader, bytes) else current_leader
if current_leader == disconnected_user_id:
# Leader disconnected, elect a new leader
users_json = redis_client.hgetall(f"workflow_online_users:{workflow_id}")
if users_json:
# Get the first remaining user as new leader
new_leader_id = list(users_json.keys())[0]
if isinstance(new_leader_id, bytes):
new_leader_id = new_leader_id.decode('utf-8')
redis_client.set(leader_key, new_leader_id, ex=3600)
# Notify all users about the new leader
broadcast_leader_change(workflow_id, new_leader_id)
else:
# No users left, remove leader
redis_client.delete(leader_key)
def broadcast_leader_change(workflow_id, new_leader_id):
"""
Broadcast leader change to all users in the workflow.
"""
users_json = redis_client.hgetall(f"workflow_online_users:{workflow_id}")
for user_id, user_info_json in users_json.items():
try:
user_info = json.loads(user_info_json)
user_sid = user_info.get("sid")
if user_sid:
is_leader = (user_id.decode('utf-8') if isinstance(user_id, bytes) else user_id) == new_leader_id
sio.emit("status", {"isLeader": is_leader}, room=user_sid)
except Exception:
continue
def get_current_leader(workflow_id):
"""
Get the current leader for a workflow.
"""
leader_key = f"workflow_leader:{workflow_id}"
leader = redis_client.get(leader_key)
return leader.decode('utf-8') if leader and isinstance(leader, bytes) else leader
def broadcast_online_users(workflow_id):
"""
broadcast online users to the workflow room
@ -103,7 +184,15 @@ def broadcast_online_users(workflow_id):
users.append(json.loads(user_info_json))
except Exception:
continue
sio.emit("online_users", {"workflow_id": workflow_id, "users": users}, room=workflow_id)
# Get current leader
leader_id = get_current_leader(workflow_id)
sio.emit("online_users", {
"workflow_id": workflow_id,
"users": users,
"leader": leader_id
}, room=workflow_id)
@sio.on("collaboration_event")