Skip to main content

Correlation: How QMUX Matches Responses

The entire value of QMUX is in this one mechanism. Understanding it precisely makes debugging correlation failures — and later, configuring it correctly in production — straightforward.

The key

Every ISOMsg has a correlation key derived from a configurable set of fields. The default is fields 41 (terminal ID) and 11 (STAN). For the tutorial we override this to just field 11:

<key>11</key>

The key computation in QMUX.getKey() produces a string like:

upstream-receive.<mapped-MTI>.<field-11-value>

For a 2800 with STAN 000001 and the default MTI mapping (see below), this becomes:

upstream-receive.2800.000001

The request side puts a marker in the internal space under upstream-receive.2800.000001.req. The response side looks for that marker when a message arrives, removes it, and delivers the response.

The internal space

QMUX uses two distinct Space instances:

SpaceVariablePurpose
External (sp)LocalSpaceShared with ChannelAdaptor — the upstream-send / upstream-receive queues
Internal (isp)TSpace (default)Private to QMUX — holds .req markers and matched responses

The separation is deliberate: sp is the channel-level Space that multiple components may share; isp is QMUX's private scratchpad for in-flight request tracking. They are never mixed (unless you configure reuse-space=true, which is unusual).

Step-by-step: request(m, timeout)

Three things worth highlighting:

1. The .req marker prevents duplicate delivery. Before placing the message on the outbound queue, QMUX atomically checks for an existing .req marker under the same key. If one already exists, it throws ISOException("Duplicate key detected"). This protects against sending the same STAN twice while a response is still pending.

2. notify() runs on a Space thread, request() waits on a different thread. QMUX registers itself as a SpaceListener on the in queue. When ChannelAdaptor calls sp.out("upstream-receive", resp), Space calls QMUX.notify() inline. That method does the key lookup, removes the .req marker, and posts the response to isp. The application thread blocked in isp.in(key, timeout) then unblocks. No explicit thread coordination is needed.

3. Timeout handling has a small race-condition safeguard. If isp.in(key, timeout) returns null, QMUX checks whether the .req marker was already consumed (isp.inp(req) == null). If it was, the response may have arrived just after the timeout and been posted to isp before QMUX could collect it. In that case QMUX retries with a 10-second safety window.

MTI mapping

QMUX needs to correlate a response MTI back to its request MTI in order to compute the same key. The mapping is configured via <mtimapping>:

<mtimapping>0123456789 0123456789 0022446689</mtimapping>

Three strings, one per MTI digit position (version, class, function). The default maps:

Request MTIResponse MTINotes
08000810Network Management
28002810Network Management
02000210Financial
01000110Authorization

The third string 0022446689 is the function-digit mapping: 0→0, 1→0, 2→2, 3→2, 4→4, 5→4, 6→6, 7→6, 8→8, 9→8. In other words, odd function digits (response) are mapped down to the nearest even (request). A 2810 response maps to 2800, so getKey(response) produces the same key string as getKey(request).

Per-MTI key overrides

In production it is common for different message types to be correlated on different fields. QMUX supports per-MTI key overrides:

<qmux name="upstream" logger="Q2">
<in>upstream-receive</in>
<out>upstream-send</out>
<ready>upstream.ready</ready>
<key>41, 11</key> <!-- default for all MTIs -->
<key mti="28">11</key> <!-- 28xx: STAN only -->
<key mti="04" pcode="000000">41, 11</key> <!-- 04xx pcode 000000 -->
</qmux>

The mti attribute matches the first two digits of the MTI. An optional pcode attribute narrows the match further to a specific processing code (field 3).