Skip to content

Return due keep-alives from queued publishes#1786

Merged
kevinherron merged 1 commit into
eclipse-milo:mainfrom
kevinherron:ctt/fix/subscription-publish-response-timing
Jul 1, 2026
Merged

Return due keep-alives from queued publishes#1786
kevinherron merged 1 commit into
eclipse-milo:mainfrom
kevinherron:ctt/fix/subscription-publish-response-timing

Conversation

@kevinherron

@kevinherron kevinherron commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix a keep-alive timing edge in the server-side Subscription state machine when a client queues multiple Publish requests and the revised MaxKeepAliveCount is 1.

OPC UA Part 4 describes Publish requests as a shared session queue: Subscriptions dequeue a Publish request when a NotificationMessage or keep-alive message is ready. The first keep-alive after subscription creation is special, but after that the keep-alive counter controls when the next empty NotificationMessage should be returned. This PR handles the boundary case where Milo transitions from NORMAL into KEEPALIVE, resets the counter, and immediately decrements it to zero in the same publishing cycle.

  • Return the queued Publish response as soon as the keep-alive becomes due during the NORMAL to KEEPALIVE transition.
  • Preserve the existing LATE behavior when a keep-alive is due but no valid Publish request is queued.
  • Add a regression test for the CTT scenario: two queued Publish requests, no notifications, publishing interval 1000ms, and MaxKeepAliveCount=1.

Key Changes

  • Subscription timing edge: In the OPC UA subscription state table, row 9 is the path taken when the publishing timer expires after a message has already been sent and there are still no notifications available. Milo already reset and decremented the keep-alive counter on this path; with MaxKeepAliveCount=1, that made the counter due immediately but did not return the queued Publish response on that timer tick.
  • Due keep-alive handling: When that row 9 decrement crosses the due threshold, Milo now consumes a queued Publish request and returns a keep-alive immediately. This matches the practical timing expectation that a keep-alive count of 1 produces one keep-alive per publishing interval while Publish requests are available.
  • Late fallback: If the keep-alive is due but the queue no longer contains a usable Publish request, the Subscription transitions to LATE and waits for the next Publish request through the existing late-state path.
  • Regression coverage: SubscriptionKeepAliveTest drives the state machine directly: it queues two Publish requests, advances the publishing timer twice, and verifies the second response completes on the second tick.

Testing

  • SubscriptionKeepAliveTest covers the MaxKeepAliveCount=1 two queued Publish keep-alive sequence.
  • mvn -q -pl opc-ua-sdk/sdk-server -am test -Dtest=SubscriptionKeepAliveTest -Dsurefire.failIfNoSpecifiedTests=false
  • mvn -q spotless:apply
  • mvn -q clean compile

Live CTT was not rerun from this branch because the demo server endpoint and CTT project are shared resources controlled by the Standard 2025 train orchestrator.

References

When MaxKeepAliveCount is 1, the row 9 transition to KeepAlive can decrement the keep-alive counter to zero. Return a queued Publish immediately in that case so clients receive the next keep-alive on the expected publishing interval instead of waiting for timeout handling.
@kevinherron kevinherron added this to the 1.1.5 milestone Jul 1, 2026
@kevinherron kevinherron merged commit 0387a85 into eclipse-milo:main Jul 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant