Skip to content
Start free evaluation

Connections and timeouts

Use this page when an HSMS connection doesn’t come up or a request ends with a timeout. All output and log records on this page come from real runs of the tutorial peers. The logs are diagnostics logs (JSONL) written with PDCOMM_LOG=debug. To keep them readable, each record here shows only level, component, event, and fields; real records also carry fields such as timestamp_unix_ms, instance_id, and sequence. To turn the log on, see Diagnose failures and send feedback.

For the HSMS states and timers themselves, see HSMS.

Active (Host) Passive (Equipment)
| TCP connect ------------------------> | on failure: RetryWait, reconnect after T5
| Select.req -------------------------> | Select.rsp must arrive within T6
| <------------------------ Select.rsp | Selected (within T7 of the TCP connect)
| S1F13 (W) --------------------------> | S1F14 must arrive within T3
| <----------------------------- S1F14 | Communicating
Timer What it limits When it expires, PDComm What the log shows
T3 The wait for a reply to a data message ends that request with FAILED (reason 2). The connection stays up. transaction_failed with T3Timeout
T5 The pause between a disconnect and the next connect (active side only) moves from RetryWait to Connecting and reconnects with a new generation. state_changed from RetryWait to Connecting
T6 The wait for a reply to Select, Deselect, or Linktest ends that request with FAILED (reason 3) and drops the connection. transaction_failed with T6Timeout
T7 The time from TCP connect to Selected drops the connection. There’s no failure code of its own. state_changed from NotSelected to RetryWait or Listening
T8 The gap between bytes of one message drops the connection. There’s no failure code of its own. state_changed, plus Disconnected for pending requests

When the connection drops, every pending request ends with FAILED (reason 4, Disconnected). After a reconnect, the generation number goes up by one, and tokens or events from the earlier generation return StaleToken. GEM communication also starts over, so the host must send S1F13 again.

Set timers and limits in PdcommTcpOptions (.NET: HsmsOptions), in milliseconds. A zero timer is rejected with InvalidArgument. PDComm has no built-in defaults.

  • When you connect to real equipment, use the values from the equipment’s GEM manual or interface specification. When both sides use the same values, failures are much easier to explain.
  • Make T3 longer than the slowest reply the peer can send. Large messages, such as process program transfers, take longer.
  • A very short T5 floods a peer that isn’t ready yet with connection attempts.
  • Make T8 comfortably longer than the jitter of your network.

The tutorial peers use short values so that failures show up quickly on one computer. Don’t copy them to real equipment. The headless simulator’s external-peer scenario, which talks to an outside program, uses longer values.

Option C++ tutorial C# tutorial headless external-peer
T3 (t3_ms, T3Milliseconds) 5000 2000 45000
T5 (t5_ms) 1000 20 10000
T6 (t6_ms) 2000 2000 5000
T7 (t7_ms) 2000 5000 10000
T8 (t8_ms) 1000 2000 5000
Connect attempt (connect_timeout_ms) 20 100 10
Write (write_timeout_ms) 2000 2000 5000

connect_timeout_ms is the time one connect attempt on the active side may take; a poll call can block for that long. When a write doesn’t finish within write_timeout_ms, you get a TRANSPORT_ERROR with operation Write and kind TimedOut.

Option code of the tutorial peers
tutorial/tutorial_options.hpp
#pragma once
#include "pdcomm_gem.hpp"
namespace tutorial {
PdcommSecsLimits itemLimits() {
return pdcomm::secsLimits(4096, 4096, 128, 8);
}
PdcommTcpOptions tcpOptions(uint32_t mode) {
PdcommTcpOptions value = {};
value.struct_size = sizeof(value);
value.version = PDCOMM_TCP_OPTIONS_VERSION;
value.mode = mode;
value.session_id = 7;
value.initial_system_bytes = 100;
value.t3_ms = 5000;
value.t5_ms = 1000;
value.t6_ms = 2000;
value.t7_ms = 2000;
value.t8_ms = 1000;
value.connect_timeout_ms = 20;
value.shutdown_timeout_ms = 200;
value.write_timeout_ms = 2000;
value.max_frame_bytes = 4110;
value.max_events = 96;
value.max_queued_bytes = 32768;
value.max_write_frames = 8;
value.max_write_bytes = 16384;
value.max_io_chunk = 4096;
value.max_application_events = 96;
value.max_application_bytes = 32768;
value.poll_budget = 16;
value.item_limits = itemLimits();
return value;
}
}
tutorial/PDComm.Tutorial/TutorialOptions.cs
using PDComm;
internal static class TutorialOptions
{
internal static readonly SecsLimits Limits = new(8192,4096,512,24);
internal static GemFormattedProgramConfiguration Configuration() => new(
new(4, 128, 512, 8, 8, 16, 32), new(4, 4, 8, 1024, 8, 8, 16, 32),
new(4, 4, 8, 1024, 8, 2048, 8, 8, 16, 32), new(4, 4, 8, 1024, 8, 8, 16, 32),
new(4, 32, 4, 8, 4, 32, 1024, 4096, 8, 8, 16, 32), new(4, 128, 512, 8, 8, 8, 16, 32));
internal static GemFormattedProgramCapabilityCommand Capability() => new("RUN", "Run", true, 0, [], [], [], 0, [], [], []);
internal static HsmsOptions Transport(HsmsMode mode) => new()
{ Mode = mode, SessionId = 7, InitialSystemBytes = 1, T3Milliseconds = 2000, T5Milliseconds = 20,
T6Milliseconds = 2000, T7Milliseconds = 5000, T8Milliseconds = 2000, ConnectTimeoutMilliseconds = 100,
ShutdownTimeoutMilliseconds = 1000, WriteTimeoutMilliseconds = 2000, MaxFrameBytes = 8206,
MaxEvents = 128, MaxQueuedBytes = 65536, MaxWriteFrames = 64, MaxWriteBytes = 65536,
MaxIoChunk = 4096, MaxApplicationEvents = 128, MaxApplicationBytes = 65536, PollBudget = 32, ItemLimits = Limits };
internal static GemHostOptions HostOptions() => new() { MaxPendingIds = 64, ItemLimits = Limits };
internal static GemEquipmentOptions EquipmentOptions() => new()
{ Model = "PDComm", SoftwareRevision = "0.1.0", InitialControl = GemControlState.HostOffline,
OnlineMode = GemOnlineMode.Remote, MaxVariables = 8, MaxMetadataBytes = 1024, MaxValueBytes = 8192, ItemLimits = Limits };
internal static HsmsWorkerOptions WorkerOptions() => new()
{ CommandCapacity = 32, MaxCommandBytes = 65536, EventCapacity = 32, MaxEventBytes = 131072,
PollIntervalMilliseconds = 1, ShutdownTimeoutMilliseconds = 2000 };
}

Symptom: The active side keeps printing STATE tcp=5 gem=0 (RetryWait).

If you point a host at a port where nothing listens, the log shows the following. The host reconnects every T5 (1 second here) and increases the generation each time.

{"level":"info","component":"hsms","event":"state_changed","fields":{"connection_id":1,"detail":{"from":"Stopped","to":"Connecting"},"generation":1}}
{"level":"error","component":"hsms","event":"transport_error","fields":{"connection_id":1,"detail":{"error_kind":"ConnectionRefused","generation":1,"operation":"Connect"},"generation":1}}
{"level":"info","component":"hsms","event":"state_changed","fields":{"connection_id":1,"detail":{"from":"Connecting","to":"RetryWait"},"generation":1}}
{"level":"info","component":"hsms","event":"state_changed","fields":{"connection_id":1,"detail":{"from":"RetryWait","to":"Connecting"},"generation":2}}
{"level":"error","component":"hsms","event":"transport_error","fields":{"connection_id":1,"detail":{"error_kind":"ConnectionRefused","generation":2,"operation":"Connect"},"generation":2}}

Your application receives a TRANSPORT_ERROR event with operation 3 (Connect) and kind 3 (Refused). Some operating systems report another kind, such as TimedOut. In the simulator GUI, the Application activity table of the Diagnostics dock shows TCP connect failed: connection refused just before TCP state: Connecting -> RetryWait. Windows reports a refused connection to a closed loopback port only after retrying for about two seconds, so the connect timeout can expire first and the row reads TCP connect failed: timed out.

What to check

  • The passive program runs and printed LISTEN with the same port.
  • A passive side bound to 127.0.0.1 can’t accept connections from other computers. Bind it to the network address instead.
  • No firewall blocks the port.
  • Both sides aren’t active. Two active sides never connect, because nobody listens.

Symptom: The passive program exits right after it starts. A passive endpoint binds its port when you create it, so pdcomm_tcp_create returns PDCOMM_IO_ERROR. No event is produced.

If you start a second equipment on the same port, the C++ peer prints the following and exits with code 1:

PDComm native operation failed

The C# peer prints this exception:

PDComm.PdcommException: PDComm native operation failed with status IoError.

In the simulator GUI, Start session turns the SESSION card to Faulted, and the status bar and the Application activity list of the Diagnostics panel show this message:

start failed [CORE_FAILURE]: core operation failed with status 12 (IO_ERROR); check that the address is free and reachable
Session overview of an Equipment session at 127.0.0.1:5000 with the SESSION card showing Faulted and a start failure with status 12 (IO_ERROR) in the Diagnostics panelSession overview of an Equipment session at 127.0.0.1:5000 with the SESSION card showing Faulted and a start failure with status 12 (IO_ERROR) in the Diagnostics panel

What to check: Find the program that owns the port.

터미널 창
lsof -nP -iTCP:5000 -sTCP:LISTEN
터미널 창
Get-NetTCPConnection -LocalPort 5000 -State Listen

On macOS, the AirPlay Receiver in Control Center listens on port 5000 on every address (*:5000). The lsof command above shows a ControlCe process:

COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
ControlCe 1305 user 11u IPv4 0x32a01e9144f8b294 0t0 TCP *:5000 (LISTEN)

You can still bind 127.0.0.1:5000, but 0.0.0.0:5000 fails. Use another port, or turn off AirPlay Receiver in System Settings > General > AirDrop & Handoff.

Symptom: The active side stays at STATE tcp=3 gem=0 (NotSelected), then drops the connection and reconnects.

If the peer accepts the TCP connection but never answers Select.req, the log shows the following. After T6 (2 seconds here) the host drops the connection and reconnects after T5.

{"level":"info","component":"hsms","event":"state_changed","fields":{"connection_id":1,"detail":{"from":"Connecting","to":"NotSelected"},"generation":1}}
{"level":"error","component":"hsms","event":"transaction_failed","fields":{"connection_id":1,"detail":{"generation":1,"reason":"T6Timeout","system_bytes":100},"generation":1}}
{"level":"info","component":"hsms","event":"state_changed","fields":{"connection_id":1,"detail":{"from":"NotSelected","to":"RetryWait"},"generation":1}}
{"level":"info","component":"hsms","event":"state_changed","fields":{"connection_id":1,"detail":{"from":"RetryWait","to":"Connecting"},"generation":2}}

What to check

  • The port belongs to an HSMS server. Other kinds of servers accept TCP connections too.
  • No other host already uses the equipment. See the next section.
  • If the peer is too busy to answer within T6, increase T6.

One HSMS-SS connection carries exactly one host. A PDComm passive endpoint doesn’t accept a new connection while it has one. The second host’s TCP connect completes in the operating system’s queue, but no HSMS answer comes back, so T6 expires.

If you connect a second host to tutorial equipment that already talks to a first host, the second host prints STATE tcp=3 gem=0, and its log shows this:

{"level":"info","component":"hsms","event":"state_changed","fields":{"connection_id":1,"detail":{"from":"Connecting","to":"NotSelected"},"generation":1}}
{"level":"error","component":"hsms","event":"transaction_failed","fields":{"connection_id":1,"detail":{"generation":1,"reason":"T6Timeout","system_bytes":100},"generation":1}}
{"level":"info","component":"hsms","event":"state_changed","fields":{"connection_id":1,"detail":{"from":"NotSelected","to":"RetryWait"},"generation":1}}

Equipment built on other implementations may answer Select.req with status 1 (already active) instead. You then get FAILED with reason 7 (SelectStatus) and value1 set to 1, and the log reason reads SelectStatus(1).

Fix: End the first host’s connection before you connect. If one equipment needs several hosts, check whether it offers more ports or sessions.

SEMI E37 defines these Select.rsp status values (SEMI E37, Select procedure):

Status Meaning PDComm
0 Communication established A passive endpoint sends it for a normal Select.
1 Communication already active Sent when the endpoint is already Selected.
2 Connection not ready Never sent.
3 Connect exhaust Never sent.

When PDComm receives a nonzero status, it reports FAILED with reason 7 and stays in NotSelected. After T7 it drops the connection and tries again.

PDComm sends Reject.req with these reasons:

Reason Meaning When PDComm sends it
1 SType not supported It received an unknown message type.
2 PType not supported It received a body format other than SECS-II.
3 Transaction not open It received a Select.rsp, Deselect.rsp, or Linktest.rsp it didn’t ask for.
4 Entity not selected It received a data message while not Selected.

When the peer rejects one of your messages with Reject.req, you get FAILED with reason 6; value1 holds the rejected type and value2 the reason.

When PDComm receives Deselect.req, it answers with status 0, ends pending requests with FAILED (reason 5, Deselected), and returns to NotSelected. When it receives Separate.req, it drops the connection. When your application stops a Selected endpoint, PDComm sends Separate.req and ends pending requests with FAILED (reason 1, Cancelled).

Symptom: The state reaches tcp=4 (Selected), but S1F13 fails without a reply.

HSMS-SS control messages such as Select use session ID 0xFFFF, so Select succeeds even when the session IDs differ. Data messages, however, are dropped by the receiver. When the tutorial host (session ID 7) talks to equipment set to session ID 8, the host prints this after T3 (5 seconds):

RESULT kind=3 request=1 outcome=0
STATE tcp=4 gem=1

The equipment log records that it dropped the message:

{"level":"error","component":"hsms","event":"protocol_error","fields":{"connection_id":5,"detail":{"error":"WrongSession { expected: 8, received: 7 }","generation":1,"system_bytes":101},"generation":1}}

The host log records the T3 timeout:

{"level":"error","component":"hsms","event":"transaction_failed","fields":{"connection_id":1,"detail":{"generation":1,"reason":"T3Timeout","system_bytes":101},"generation":1}}

Fix: Use the same session ID on both sides. The tutorial peers use 7. The simulator GUI’s Session ID defaults to 0, so set it to 7 before you connect a tutorial peer.

Symptom: A request ends with RESULT kind=3 (Failed), and the log reason is T3Timeout.

You can reproduce a T3 timeout yourself with three macOS or Linux terminals:

  1. In terminal A, start the equipment:

    터미널 창
    "$PDCOMM_ROOT/bin/pdcomm-tutorial" equipment passive 127.0.0.1:5000
  2. In terminal B, start the host with logging turned on, and type establish:

    터미널 창
    mkdir -p logs
    PDCOMM_LOG=debug PDCOMM_LOG_FILE=./logs/host.jsonl "$PDCOMM_ROOT/bin/pdcomm-tutorial" host active 127.0.0.1:5000
  3. In terminal C, pause the equipment process:

    터미널 창
    kill -STOP $(pgrep -f 'pdcomm-tutorial equipment')
  4. In terminal B, type values 1 2. After 5 seconds, the T3 of the C++ tutorial, the host prints:

    RESULT kind=3 request=3 outcome=0
  5. In terminal C, let the equipment run again:

    터미널 창
    kill -CONT $(pgrep -f 'pdcomm-tutorial equipment')

    The equipment now handles the S1F3 it received while paused and sends a late S1F4.

  6. In terminal B, type values 1 2 again. This time you get RESULT kind=2 request=3 outcome=5. The connection never dropped.

Check the log:

터미널 창
grep -E '"event":"(transaction_failed|protocol_error)"' ./logs/host.jsonl
{"level":"error","component":"hsms","event":"transaction_failed","fields":{"connection_id":1,"detail":{"generation":1,"reason":"T3Timeout","system_bytes":102},"generation":1}}
{"level":"error","component":"hsms","event":"protocol_error","fields":{"connection_id":1,"detail":{"error":"UnexpectedReply","generation":1,"system_bytes":102},"generation":1}}

The first record says that the request with system bytes 102 got no reply within T3. The second says that the late reply with the same system bytes arrived after its transaction had closed, so the host dropped it.

Common causes

  • The peer doesn’t handle the message and sends no reply. See Unhandled messages and S9.
  • The session IDs differ. See Session ID mismatch.
  • The peer takes longer than T3. Increase T3 to match the peer’s specification.
  • The peer application doesn’t take and handle events. PDComm has no internal threads, so replies go out only while the application keeps calling poll and handling events.

When S1F13 gets no reply within T3, the result is RESULT kind=3 request=1 outcome=0, and GEM communication returns to NotCommunicating (1). When the peer denies it with COMMACK 1, the result is RESULT kind=2 request=1 outcome=3 (CommunicationRejected), and the state also returns to NotCommunicating.

PDComm doesn’t resend S1F13 by itself. Fix the cause, then call pdcomm_gem_begin_communication again (establish in the tutorial). Calling it in any state other than NotCommunicating returns InvalidState.

T7 and T8 have no failure codes of their own. The log shows only the state change and, if a request was pending, a Disconnected failure.

  • T7: If the connection doesn’t reach Selected within T7 of the TCP connect, PDComm drops it. The active side goes from NotSelected to RetryWait, and the passive side from NotSelected to Listening. A passive side sees this when the peer connects over TCP but never sends Select.req.
  • T8: If the gap between bytes of one incoming message exceeds T8, PDComm drops the connection. This happens when the network stalls or the peer stops in the middle of writing a message.