SDK 2 retries LDS RegisterServer against its own endpoint forever (log noise since #874) #876

Closed
opened 2026-11-12 06:00:02 -05:00 by dohertj2 · 1 comment
Owner

Seen on wonder-app-vd03 after the 2026-11-12 upgrade to 5fb4b4db:

[WRN] SERVER - Service Fault Occurred. Reason=BadServiceUnsupported
[WRN] RegisterServer2 failed for "opc.tcp://localhost:4840/OtOpcUa". Exception=[800B0000] (BadServiceUnsupported)

The pairs back off (2 s, 6 s, 12 s, 20 s…). OpcUaApplicationHost builds ServerConfiguration without touching MaxRegistrationInterval/RegistrationEndpoint, so the SDK default registers with an LDS at opc.tcp://localhost:4840, which is this server itself (no LDS). It was silent before #874 routed SDK warnings to Serilog.

Proposal: set MaxRegistrationInterval = 0 unless an explicit OpcUa:DiscoveryServerUrl-style option is configured, and add a test asserting the built ServerConfiguration does not register by default.

https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk

Seen on wonder-app-vd03 after the 2026-11-12 upgrade to `5fb4b4db`: ``` [WRN] SERVER - Service Fault Occurred. Reason=BadServiceUnsupported [WRN] RegisterServer2 failed for "opc.tcp://localhost:4840/OtOpcUa". Exception=[800B0000] (BadServiceUnsupported) ``` The pairs back off (2 s, 6 s, 12 s, 20 s…). `OpcUaApplicationHost` builds `ServerConfiguration` without touching `MaxRegistrationInterval`/`RegistrationEndpoint`, so the SDK default registers with an LDS at `opc.tcp://localhost:4840`, which is this server itself (no LDS). It was silent before #874 routed SDK warnings to Serilog. Proposal: set `MaxRegistrationInterval = 0` unless an explicit `OpcUa:DiscoveryServerUrl`-style option is configured, and add a test asserting the built `ServerConfiguration` does not register by default. https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
dohertj2 added the type:followuppriority:p3 labels 2026-11-12 06:00:02 -05:00
Author
Owner

Fixed on master in f127f454 (fast-forward, pushed).

Root cause (confirmed in decompiled SDK 2.0.1 source): ServerConfiguration.MaxRegistrationInterval defaults to 30000. StandardServer.StartApplicationAsync arms its registration timer only when MaxRegistrationInterval > 0, and with a null RegistrationEndpoint it targets Utils.DiscoveryUrls[0] = opc.tcp://localhost:4840, SignAndEncrypt/Basic256Sha256. On a node that listens on 4840 itself, that is the server itself.

Change:

  • OpcUaApplicationHost.ApplyDiscoveryRegistration runs in BuildConfigurationAsync. By default it sets MaxRegistrationInterval = 0 and RegistrationEndpoint = null, so there is no registration and no timer.
  • New opt-in keys:
    • OpcUa:DiscoveryServerUrl (default unset). When set, the server registers against this URL, using SignAndEncrypt/Basic256Sha256 like the SDK's own default endpoint.
    • OpcUa:DiscoveryRegistrationIntervalMs (default 30000).
  • OpcUaApplicationHostOptionsValidator: when a URL is set, it must be an absolute opc.tcp:// URL and the interval must be greater than 0.
  • Docs: docs/Configuration.md (OpcUa table) and docs/OpcUaServer.md (new "Discovery-server registration" section).
  • Not changed: the ApplicationConfigPath XML path, where the XML's own registration settings still apply.

Evidence:

  • Solution build: 0 warnings, 0 errors.
  • OpcUaServer.Tests: 1264 passed, 1 skipped. Host.Tests: 288 passed. Validator tests: 21 passed, including a new 6-case theory.
  • New OpcUaApplicationHostDiscoveryRegistrationTests:
    • helper-level tests for the default and configured cases;
    • two real host-boot tests that read the built ServerConfiguration and the started StandardServer.m_registrationTimer. The timer is null by default and non-null when a URL is configured (positive control).
  • Revert check: with the ApplyDiscoveryRegistration call removed, Started_host_with_default_options_does_not_register fails (MaxRegistrationInterval is 30000, not 0).
  • Live 30 s run: a real host booted with default options and the #874 ServerSdkTelemetry forwarding.
    • With the fix: 0 SDK log lines.
    • Reverted: 8 CLIENTCHANNEL CONNECT FAILED via opc.tcp://localhost:4840/OtOpcUa errors on the 2/4/8 s backoff. Locally, 4840 belongs to another OPC UA container, so the reverted run failed at the secure channel rather than with BadServiceUnsupported; it was the same registration loop.

Not deployed. vd03 picks this up on its next OtOpcUa upgrade.

Fixed on `master` in **f127f454** (fast-forward, pushed). **Root cause (confirmed in decompiled SDK 2.0.1 source):** `ServerConfiguration.MaxRegistrationInterval` defaults to `30000`. `StandardServer.StartApplicationAsync` arms its registration timer only when `MaxRegistrationInterval > 0`, and with a null `RegistrationEndpoint` it targets `Utils.DiscoveryUrls[0]` = `opc.tcp://localhost:4840`, SignAndEncrypt/Basic256Sha256. On a node that listens on 4840 itself, that is the server itself. **Change:** - `OpcUaApplicationHost.ApplyDiscoveryRegistration` runs in `BuildConfigurationAsync`. By default it sets `MaxRegistrationInterval = 0` and `RegistrationEndpoint = null`, so there is no registration and no timer. - New opt-in keys: - `OpcUa:DiscoveryServerUrl` (default unset). When set, the server registers against this URL, using SignAndEncrypt/Basic256Sha256 like the SDK's own default endpoint. - `OpcUa:DiscoveryRegistrationIntervalMs` (default 30000). - `OpcUaApplicationHostOptionsValidator`: when a URL is set, it must be an absolute `opc.tcp://` URL and the interval must be greater than 0. - Docs: `docs/Configuration.md` (OpcUa table) and `docs/OpcUaServer.md` (new "Discovery-server registration" section). - Not changed: the `ApplicationConfigPath` XML path, where the XML's own registration settings still apply. **Evidence:** - Solution build: 0 warnings, 0 errors. - `OpcUaServer.Tests`: 1264 passed, 1 skipped. `Host.Tests`: 288 passed. Validator tests: 21 passed, including a new 6-case theory. - New `OpcUaApplicationHostDiscoveryRegistrationTests`: - helper-level tests for the default and configured cases; - two real host-boot tests that read the built `ServerConfiguration` and the started `StandardServer.m_registrationTimer`. The timer is null by default and non-null when a URL is configured (positive control). - Revert check: with the `ApplyDiscoveryRegistration` call removed, `Started_host_with_default_options_does_not_register` fails (`MaxRegistrationInterval` is 30000, not 0). - Live 30 s run: a real host booted with default options and the #874 `ServerSdkTelemetry` forwarding. - With the fix: **0** SDK log lines. - Reverted: 8 `CLIENTCHANNEL CONNECT FAILED via opc.tcp://localhost:4840/OtOpcUa` errors on the 2/4/8 s backoff. Locally, 4840 belongs to another OPC UA container, so the reverted run failed at the secure channel rather than with `BadServiceUnsupported`; it was the same registration loop. Not deployed. vd03 picks this up on its next OtOpcUa upgrade.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#876