Summary
Propose adding three new package-level functions to enable programmatic
runtime control of individual FIX sessions:
Logon(sessionID SessionID) error
Logout(sessionID SessionID, reason string) error
IsLoggedOn(sessionID SessionID) (bool, error)
These mirror the existing pattern established by ResetSession,
SetNextTargetMsgSeqNum, SetNextSenderMsgSeqNum, etc. in registry.go.
Motivation
Operational tooling for FIX gateways commonly needs to control individual
sessions at runtime without bouncing the entire process:
- Force a clean logout (FIX 35=5) of a single session while preserving
sequence numbers — e.g. when a downstream system needs the session
paused for maintenance, when seqnum drift requires a coordinated
reset-and-relogon cycle with the counterparty, or when the operator
wants to disconnect a misbehaving counterparty cleanly rather than via
TCP drop.
- Force a logon attempt on demand — e.g. after seqnum reset or after
a transient downstream issue is resolved.
- Query logon state for status/health endpoints without requiring the
application to mirror state via OnLogon/OnLogout callbacks.
Today the only exported per-session lifecycle primitive is ResetSession,
which has the side effect of resetting sequence numbers. This is too
destructive for the common operational workflow of "logout, fix something,
log back on, resume from the same seqnums."
The session type itself owns the relevant state (session.IsLoggedOn() on
the embedded stateMachine, session.sendLogon, session.initiateLogout)
but is unexported and unreachable from outside the package. There is
already a precedent for routing operations through the existing
session.admin channel (see connect, stopReq, waitForInSessionReq in
session.go), and this proposal follows that exact pattern.
Proposed API
// Logon requests that the session matching sessionID send a Logon (35=A)
// message if it is not already logged on. For acceptor-mode sessions, this
// re-enables the session for incoming Logon. No-op if already logged on.
// Returns errUnknownSession if no session is registered for sessionID.
func Logon(sessionID SessionID) error
// Logout requests that the session matching sessionID send a Logout (35=5)
// message and disconnect cleanly. Sequence numbers are NOT reset (use
// ResetSession for that). The reason argument is propagated as tag 58
// (Text) on the Logout message. No-op if not currently logged on.
// Returns errUnknownSession if no session is registered for sessionID.
func Logout(sessionID SessionID, reason string) error
// IsLoggedOn returns whether the session matching sessionID is currently
// in the LoggedOn state. Returns errUnknownSession if no session is
// registered for sessionID.
func IsLoggedOn(sessionID SessionID) (bool, error)
Implementation sketch
In registry.go, three new exported funcs that resolve the session via
the existing private lookupSession helper and dispatch a command on
session.admin (mirroring how stop() and connect() already work):
type logonReq struct{ rep chan error }
type logoutReq struct{ reason string; rep chan error }
func Logon(sessionID SessionID) error {
s, ok := lookupSession(sessionID)
if !ok { return errUnknownSession }
rep := make(chan error, 1)
s.admin <- logonReq{rep: rep}
return <-rep
}
func Logout(sessionID SessionID, reason string) error {
s, ok := lookupSession(sessionID)
if !ok { return errUnknownSession }
rep := make(chan error, 1)
s.admin <- logoutReq{reason: reason, rep: rep}
return <-rep
}
func IsLoggedOn(sessionID SessionID) (bool, error) {
s, ok := lookupSession(sessionID)
if !ok { return false, errUnknownSession }
return s.IsLoggedOn(), nil
}
In session.go, two new cases in the onAdmin switch that delegate to
the existing private sendLogon / initiateLogout helpers:
case logonReq:
if s.IsLoggedOn() { msg.rep <- nil; return }
msg.rep <- s.sendLogon()
case logoutReq:
if !s.IsLoggedOn() { msg.rep <- nil; return }
msg.rep <- s.initiateLogout(msg.reason)
Why the admin channel
The session.admin channel is the existing serialization point for
external commands into the session goroutine (currently used by connect
and stop). Routing Logon/Logout through it preserves the
single-goroutine-per-session invariant and avoids new locking — the same
pattern that makes the rest of registry.go safe.
Backward compatibility
Purely additive — three new exported symbols and two new cases in an
internal switch. No existing API or behavior is changed.
Happy to open the PR if this direction is acceptable.
Summary
Propose adding three new package-level functions to enable programmatic
runtime control of individual FIX sessions:
Logon(sessionID SessionID) errorLogout(sessionID SessionID, reason string) errorIsLoggedOn(sessionID SessionID) (bool, error)These mirror the existing pattern established by
ResetSession,SetNextTargetMsgSeqNum,SetNextSenderMsgSeqNum, etc. inregistry.go.Motivation
Operational tooling for FIX gateways commonly needs to control individual
sessions at runtime without bouncing the entire process:
sequence numbers — e.g. when a downstream system needs the session
paused for maintenance, when seqnum drift requires a coordinated
reset-and-relogon cycle with the counterparty, or when the operator
wants to disconnect a misbehaving counterparty cleanly rather than via
TCP drop.
a transient downstream issue is resolved.
application to mirror state via
OnLogon/OnLogoutcallbacks.Today the only exported per-session lifecycle primitive is
ResetSession,which has the side effect of resetting sequence numbers. This is too
destructive for the common operational workflow of "logout, fix something,
log back on, resume from the same seqnums."
The session type itself owns the relevant state (
session.IsLoggedOn()onthe embedded
stateMachine,session.sendLogon,session.initiateLogout)but is unexported and unreachable from outside the package. There is
already a precedent for routing operations through the existing
session.adminchannel (seeconnect,stopReq,waitForInSessionReqinsession.go), and this proposal follows that exact pattern.Proposed API
Implementation sketch
In
registry.go, three new exported funcs that resolve the session viathe existing private
lookupSessionhelper and dispatch a command onsession.admin(mirroring howstop()andconnect()already work):In
session.go, two new cases in theonAdminswitch that delegate tothe existing private
sendLogon/initiateLogouthelpers:Why the admin channel
The
session.adminchannel is the existing serialization point forexternal commands into the session goroutine (currently used by
connectand
stop). RoutingLogon/Logoutthrough it preserves thesingle-goroutine-per-session invariant and avoids new locking — the same
pattern that makes the rest of
registry.gosafe.Backward compatibility
Purely additive — three new exported symbols and two new cases in an
internal switch. No existing API or behavior is changed.
Happy to open the PR if this direction is acceptable.