quickfixgo / quickfix

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

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

Open florian-besser opened 11 months ago

florian-besser commented 11 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.

filinvadim commented 4 months ago

AFAIK this is normal behavior in absence of StartDay and EndDay. Just set StartDay and EndDay in config

florian-besser commented 4 months ago

Hi @filinvadim , see the top of my post: We are using both StartTime and EndTime in our config, and yet the resets still happen.

filinvadim commented 4 months ago

Please also add StartDay=Monday and EndDay=Friday for example

florian-besser commented 4 months ago

I currently don't have access to this project anymore; will let you know if we go back to this library and whether that fixes the issue. Looking at the last if from my original post I'm not convinced that adding start- and end-day will change the behavior of:

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{})
    }
TatuMon commented 3 months ago

Please also add StartDay=Monday and EndDay=Friday for example

If you set this, StartTime and EndTime will be ignored. For day long sessions, you should use Weekdays, which is not documented.