Skip to main content

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 = new Date (e.g. Thu Mar 10 17:45:00)
  • sp.rdp(logonKey) = old Date (e.g. Thu Mar 10 17:30:01) or null if the reconnect took longer than logon-interval
  • Either way, !sessionId.equals(...) is true → 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 echoKey expires → 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 echoKey is not renewed
  • Next iteration → echo required → retries immediately
  • The channel may be broken; if ChannelAdaptor detects a disconnect and reconnects, the ready semaphore will change and force a re-logon before the next echo

Shutdown sequence

When stopService() is called:

  1. shutDownLatch.countDown() unblocks any Runner sleeping in relax().
  2. Each Runner exits the while(running()) loop (since running() now returns false).
  3. Each Runner calls doLogoff(), which clears logonKey and sends a 2800/002.
  4. allDone.countDown() signals the service has fully stopped.
  5. stopService() waits up to 5 seconds on allDone.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.

<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.