Runner Lifecycle and Reconnect Handling
The Runner loop
Each MUX configured via <property name="mux"> gets its own Runner thread. The thread body is a tight loop with a 1-second sleep at the end:
while (running()) {
sessionId = sp.rd(readyKey, 60s) ← wait for channel
if sessionId == null → log "not ready", continue
if logonRequired(sessionId) → doLogon()
else if echoRequired() → doEcho()
relax(1000) ← sleep 1s (interruptible by stop)
}
doLogoff()
allDone.countDown()
The loop is deliberately simple. There is no scheduler, no timer, no explicit sleep-until-next-echo. Everything is driven by two Space entries acting as TTL-gated flags.
The TTL semaphore pattern
Re-logon on reconnect
This is the most important part of the design. The logonRequired() check is:
private boolean logonRequired(Object sessionId) {
return !sessionId.equals(sp.rdp(logonKey));
}
sessionId comes from sp.rd(readyKey) — it is the Date object that ChannelAdaptor publishes when it connects:
// ChannelAdaptor.java
sp.out(ready, new Date());
After a successful logon, the Runner stores that same Date under logonKey:
sp.out(logonKey, sessionId, logonInterval);
So logonRequired() returns false as long as the Date in logonKey equals the Date in readyKey.
When the channel drops and reconnects, ChannelAdaptor publishes a new Date to readyKey. On the next loop:
sessionId= newDate(e.g.Thu Mar 10 17:45:00)sp.rdp(logonKey)= oldDate(e.g.Thu Mar 10 17:30:01) ornullif the reconnect took longer thanlogon-interval- Either way,
!sessionId.equals(...)istrue→ re-logon fires
No explicit reconnect detection code is needed. The pattern falls out naturally from the ready semaphore that ChannelAdaptor already maintains.
Re-logon on TTL expiry
If logon-interval passes without a reconnect (24h in the default config), the logonKey entry expires. On the next loop, sp.rdp(logonKey) returns null. !sessionId.equals(null) is true → re-logon fires and the session is renewed.
This means the LogonManager handles scheduled daily re-logon with zero additional code: just configure logon-interval.
Echo keeps the session alive
echoKey is stored with echo-interval TTL (60s default). The Runner always sleeps 1 second at the end of each iteration, so:
- If both logon and echo are satisfied → sleep 1s → loop → no action → sleep 1s → ...
- After 60s the
echoKeyexpires → next iteration sends an echo → renews the TTL
The effective echo period is echo-interval + ~1s (the loop sleep). Close enough for any practical keep-alive.
If the echo times out (mux.request() returns null):
- The
echoKeyis not renewed - Next iteration → echo required → retries immediately
- The channel may be broken; if
ChannelAdaptordetects a disconnect and reconnects, thereadysemaphore will change and force a re-logon before the next echo
Shutdown sequence
When stopService() is called:
shutDownLatch.countDown()unblocks anyRunnersleeping inrelax().- Each
Runnerexits thewhile(running())loop (sincerunning()now returnsfalse). - Each
RunnercallsdoLogoff(), which clearslogonKeyand sends a 2800/002. allDone.countDown()signals the service has fully stopped.stopService()waits up to 5 seconds onallDone.await().
The logoff uses a short timeout (timeout config value, 30s default). If the remote host doesn't respond, the logoff is best-effort: the logonKey was already wiped so the next startup will re-logon unconditionally.
Multi-link setup
<property name="mux"> is repeatable. Each occurrence spawns an independent Runner:
<logon-manager class="org.jpos.tutorial.LogonManager" logger="Q2" name="logon-manager">
<property name="mux" value="upstream-primary" />
<property name="mux" value="upstream-backup" />
...
</logon-manager>
Each Runner uses its own set of Space keys derived from its MUX's ready indicator:
logon-manager.logon.upstream-primary.ready.logon-manager
logon-manager.logon.upstream-backup.ready.logon-manager
logon-manager.echo.upstream-primary.ready.logon-manager
logon-manager.echo.upstream-backup.ready.logon-manager
This means primary and backup links can be in different logon states at the same time, and a failover does not require a global logon — only the reconnected link re-logons.