vendredi 10 janvier 2020

Creating a good state management for a client that connects to a server

I working a project that has a gotcha’s that I have been wrestling with. I have a client that connects to a server. The server has session(s) and each session has 2 lines (let’s call them S_TO_C and C_TO_S lines). After connecting to the server, you have to send “green light” message for each pipeline to the server so it knows to start sending and receiving messing to you as a client. For every message sent on a pipeline, it should have a messageID and well as the sessionID. For example the first message for session with ID 4562 from the client , messageID 1, next message will have sessionIdD 4562, messageID 2 and messageID’s increases thereafter until there’s a server restart. When that happens, the server increases it’s sessionIdID and expects messageID’s for pipelines to start from 1 again.

Additionally, the server sends a heartbeat message every second, informing a client about the current sessionID and the next messagedID clients should expect.

  • Let say we connected on sessionID 4562. Server sent 23 messages and client sent 14 messages. Server restarts and sends a heartbeat message with sessionID 4563, and next messageID as 1. Server expects that, first you know it’s restarted because you would have received a heartbeat message that has a sessionID higher

  • Secondly, because the server restarted, it will start sending another heartbeat once every second for the old session. In total, client will get 2 heartbeats once a second. One with sessionID 4562 and next messageID 24(since 24 would have been the next ID of the message if it had not restarted) and the other with sessionID 4563 and next messageID as 1. The reason for the old session is so the client can request for any missed messages on an older session. To do so, client has to send the “green light” and then request for any messages .

  • For every server restart, client get additional heartbeat once a second informing about the last messageID sent on that session if you want to request for any missed messages

Now the main issue I’ve been struggling with is how to deal with theses start changes and for client reading heartbeats to ignore if we’re on the most active session or we already restarted and there’s nothing else to do. For example in LunaClientSession, when we have restart and logged back again, how do I ensure I'm not restarting a again when I move on to the new session Below is a sample code I’ve extracted to shed more light on.

public class LunaClientSession implements ILunaClientSession, IClientRestart {

    private LunaClientSessionRepo sessionRepo;
    private List<LunaMessage> receivedMsgs;
    private int lastMessageId;


    public LunaClientSession(){
        this.sessionRepo = new LunaClientSessionRepo();
        this.receivedMsgs = new ArrayList<>();
        this.lastMessageId = -1;

        this.connect();
    }

    @Override
    public void onHeartbeat(long sessionId, long nextMessageId) {
        sessionRepo.onHeartbeat(sessionId, nextMessageId, this);
    }

    @Override
    public void onNewMessage(LunaMessage lunaMessage) {
        this.lastMessageId = lunaMessage.getMessageId();

    }

    @Override
    public void restartClient(long nextMessageId) {
        //check we're good with old session
        if (lastMessageId == nextMessageId - 1){

        }
        else {
            //requestForMessages
            requestMessage(nextMessageId);
        }

    }

    private void requestMessage(long endMessageId){
        //request messages
    }

    private void connect(){
        //connect
    }
}


public interface ILunaClientSession {

    void onHeartbeat(long sessionId, long nextMessageId);
    void onNewMessage(LunaMessage lunaMessage);
}

public interface IClientRestart {

    void restartClient(long nextMessageId);
}


public class LunaClientSessionRepo {


    private NavigableMap<Long, Session> sessions = new TreeMap<>(); //so we know which session is highest
    private long activeId = -1;
    void onHeartbeat(long sessionId, long nextMessageId, IClientRestart restart){

        Session session = sessions.get(sessionId);

        if (session == null){
            session = new Session(sessionId, nextMessageId);
            sessions.put(sessionId, session);
        }else {
            //this block means we have seen all the sessionID's we can proceed to either restart

            if (sessionId == -1){
                activeId = sessionId;
            }else {
                if (sessionId > activeId){
                    restart.restartClient(nextMessageId);
                }
            }
        }
    }

Aucun commentaire:

Enregistrer un commentaire