quickfixgo / quickfix

The Go FIX Protocol Library :rocket:
https://www.quickfixgo.org/
Other
723 stars 282 forks source link

Quickfix/Go resets sequence when using StartTime/EndTime #607

Open florian-besser opened 6 months ago

florian-besser commented 6 months ago

I'm raising this issue here as I don't have access to https://groups.google.com/g/quickfixgo

We configure our QuickFix/Go sessions like so:

BeginString=FIX.4.4
# Removed several irrelevant items
StartTime=07:03:00
EndTime=07:00:00

This (to me, as per docs) implies ResetOnLogon=N, ResetOnLogout=N, ResetOnDisconnect=N meaning I expect no session resets to happen. This is indeed true for e.g. us restarting Quickfix/Go, but there is one exception.

Whenever we hit 7AM UTC we see Quickfix/Go disconnect (correctly), followed by reconnection attempts at 7:03AM (also correct), except these reconnection attempts now use the wrong sequence number.

Expected behavior: Reconnection attempts and future messages should use the next sequence number from before the disconnect.

Example: Before the disconnect our last outgoing message has sequence id 123. The next message (i.e. the logon at 7:03) should use sequence id 124

Observed behavior: Quickfix/Go resets the sequence, the next sequence id is always 1.

This leads to our counterparty complaining that the sequence number is wrong.

I currently believe this is due to the following code:

func (sm *stateMachine) CheckSessionTime(session *session, now time.Time) {
    if !session.SessionTime.IsInRange(now) {
        if sm.IsSessionTime() {
            session.log.OnEvent("Not in session")
        }

        sm.State.ShutdownNow(session)
        sm.setState(session, notSessionTime{})

        if sm.notifyOnInSessionTime == nil {
            sm.notifyOnInSessionTime = make(chan interface{})
        }
        return
    }

    if !sm.IsSessionTime() {
        session.log.OnEvent("In session")
        sm.notifyInSessionTime()
        sm.setState(session, latentState{})
    }

    if !session.SessionTime.IsInSameRange(session.store.CreationTime(), now) {
        session.log.OnEvent("Session reset")
        sm.State.ShutdownNow(session)
        if err := session.dropAndReset(); err != nil {
            session.logError(err)
        }
        sm.setState(session, latentState{})
    }
}

Specifically the last if statement. From the code I don't see a way for us to configure this behavior and the library will always reset the sequence.