Adding handshake websocket#114
Conversation
7659cb6 to
d88a0e2
Compare
Benjamin-Nussbaum
left a comment
There was a problem hiding this comment.
Change the global state object to be an injected dependency with Depends. Maybe we can think about better endpoint locations/naming/etc, but generally it seems ok.
| # Leader's state | ||
| leading: bool = False | ||
| followers_address: str | None = None | ||
|
|
||
| # Follower's state | ||
| following: bool = False | ||
| # Other node requested this node to follow it. | ||
| following_requested: bool = False | ||
| # User's response to the follow request. None if no response yet, True if accepted, False if rejected. | ||
| following_requested_user_response: bool | None = None | ||
| # The address of the leader this node is following. None if not following anyone. | ||
| leaders_address: str | None = None | ||
| leaders_name: str | None = None |
There was a problem hiding this comment.
Remove "or none" fields. If we want to account content differences in requests/responses, we can add extra models.
| # Leader's state | |
| leading: bool = False | |
| followers_address: str | None = None | |
| # Follower's state | |
| following: bool = False | |
| # Other node requested this node to follow it. | |
| following_requested: bool = False | |
| # User's response to the follow request. None if no response yet, True if accepted, False if rejected. | |
| following_requested_user_response: bool | None = None | |
| # The address of the leader this node is following. None if not following anyone. | |
| leaders_address: str | None = None | |
| leaders_name: str | None = None | |
| # Leader's state | |
| leading: bool = False | |
| followers_address: str = "" | |
| # Follower's state | |
| following: bool = False | |
| # Other node requested this node to follow it. | |
| following_requested: bool = False | |
| # User's response to the follow request. None if no response yet, True if accepted, False if rejected. | |
| following_requested_user_response: bool = False | |
| # The address of the leader this node is following. None if not following anyone. | |
| leaders_address: str = "" | |
| leaders_name: str = "" |
|
I moved all the coordination flags into its own model, and made it a dep. I only left |
Benjamin-Nussbaum
left a comment
There was a problem hiding this comment.
A few comments, otherwise looks fine modulo general cleanup.
| # Check if the client is ready to accept a follower request and that node is not already following someone. | ||
| if coord.client_listening_for_follower_requests and not coord.following: | ||
| coord.following_requested = True | ||
| coord.leaders_name = leaders_name | ||
| coord.leaders_address = leaders_address | ||
| # Trigger the state change to get the websocket to send question to user | ||
| ask_user_for_follow_event.set() | ||
|
|
||
| logger.debug("Asking user to accept follow request from %s (%s)", leaders_name, leaders_address) | ||
| await user_replied_event.wait() # Wait for a state change event to see if user accepted | ||
| user_replied_event.clear() # Reset the event for the next change | ||
| if coord.following_requested_user_response: | ||
| logger.debug("Follow request from %s accepted.", leaders_address) | ||
| coord.following = True | ||
| coord.leaders_name = leaders_name | ||
| coord.leaders_address = leaders_address | ||
| ask_user_for_follow_event.set() | ||
| return True | ||
|
|
||
| logger.debug("Follow request from %s rejected.", leaders_address) | ||
| # Clean up the state if user rejected | ||
| coord.leaders_address = "" | ||
| coord.leaders_name = "" | ||
| coord.following_requested = False | ||
|
|
||
| else: | ||
| logger.info( | ||
| "Request rejected because %s", | ||
| ( | ||
| "client is not listening for requests" | ||
| if coord.client_listening_for_follower_requests | ||
| else "this node is already following someone" | ||
| ), | ||
| ) |
There was a problem hiding this comment.
I suggest inverting the logic here to reduce the indent level and keep the error handling close to the condition.
| async def get_coordination_state() -> AsyncGenerator[CoordinationState, None]: | ||
| yield state.coordination_state |
There was a problem hiding this comment.
Some suggestions:
- Put this function in the same module as the class definition
- doesn't need to be a generator
- probably doesn't need to be async
- rather than a whole separate dependency for a sub-component of the overall state (potentially opening the door for additional complexity via more separate things for other sub-components), just depend on the overall state object, and use the component - getting the whole thing isn't that much more overhead, and it keeps the style consistent across endpoints.
| qkd_request_bit_list: list[int] = [] | ||
|
|
||
|
|
||
| state = NodeState() |
There was a problem hiding this comment.
This state object should be returned in a get_state() function in this file as mentioned in my other comment.
- check it isn't imported directly for use in any endpoints
| ret = await http_client.post(f"http://{address}/coordination/follow_requested?leaders_name={settings.node_name}") | ||
| if ret.status_code != status.HTTP_200_OK: | ||
| raise HTTPException(status_code=ret.status_code, detail=ret.text) | ||
|
|
||
| if ret.json() is True: | ||
| coord.leading = True | ||
| coord.followers_address = address | ||
| logger.info("Successfully collected follower") | ||
| return True | ||
| if ret.json() is False: | ||
| logger.info("Follower rejected follow request") | ||
| return False |
There was a problem hiding this comment.
Also, this section could be clearer if follow_requested returned a structured response rather than a raw bool. (either a small dict[str,bool] with a meaningful key, or a more robust ResponseModel
|
@Benjamin-Nussbaum, I implemented all the changes let me know if anything else needs changing. I would hold on the merge though since. I wasn't able to test the code properly |
Benjamin-Nussbaum
left a comment
There was a problem hiding this comment.
Looks fine - we can discuss later about other possible changes
| try: | ||
| await websocket.send_text( | ||
| f"Do you want to accept a connection from {state.leaders_name} ({state.leaders_address})?" | ||
| ) | ||
| except RuntimeError as e: | ||
| if "websocket.close" in str(e) or "response already completed" in str(e): | ||
| logger.debug("WebSocket already closed, cannot send message") | ||
| break | ||
| raise |
There was a problem hiding this comment.
Multiple nested try blocks adds confusion. Rather than just trying to send text and handling an exception if the socket is closed, I suggest checking if the socket is closed before attempting to send.
34281a3 to
64d8884
Compare
|
New changes incoming in different PR |
Adding a new routes file called "coordination" in charge of handling the coordination between nodes. At the moment this is what it does:
reset_coordination_state: New call that the frontend should call when loading the main page. Makes sure all the following and leading state are reset and clean for a new interaction (if frontend is in the main page, it is not following or leading anyone).collect_follower: Leader node call to make it look for a follower in a separate node. Not much use for this right now, but that is how it would like. Mostly have been used for development.follow_requested: follower node called by a leader requesting that it follows it. Checks if the frontend is connected and ready to accept a follow request (this can only happen in the main page right now). This is done by a flag (state.client_listening_for_follower_requests) that changes when the websocket gets connected or not. If the client is ready to accept a follow, a second flag (state.following_requested = True) changes and an event gets triggered that makes the websocket send the message to the front end.follow_requested_alert: Websocket that detects when the frontend connects, this changes the flag ``state.client_listening_for_follower_requeststoTrue` to indicate that the client is ready to follow. Sends a message to the client when a follow request comes, receives the response from the client and detects when the websocket gets disconnected and changes the flag back to `False`