Skip to content

fix(client): keep failed monitored items out of the recreated subscription - #2028

Merged
oroulet merged 3 commits into
FreeOpcUa:masterfrom
Poseidonas:fix-2020-subscription-race
Sep 15, 2026
Merged

oroulet merged 3 commits into
FreeOpcUa:masterfrom
Poseidonas:fix-2020-subscription-race

Conversation

@Poseidonas

Copy link
Copy Markdown
Contributor

Fixes #2020.

create_monitored_items registers items client-side before the server call, so that notifications arriving ahead of the response are not lost. When the whole service call failed that registration was never undone — per-item failures were removed, a service-level failure was not — and recreate() then carried those half-registered items into the new subscription. A subscribe_data_change() that raised BadSubscriptionIdInvalid ended up delivering notifications anyway.

Three things changed:

  • the registry is cleaned up when the service call raises;
  • a call that started against one incarnation of the subscription and returns against another is told so, rather than reporting success for work the server no longer knows about;
  • recreate() carries over only items the server confirmed. Adopting one still in flight would make its call raise while its node was quietly being watched — the same bug through another door.

On the approach. Ordering these with a lock was my first attempt and it was wrong: a call holding the lock while stuck on a dropped connection blocks the very reconnect that would free it. That deadlocks the client, which is worse than the bug being fixed — a test pins it. A generation counter gives the same guarantee and never waits.

Measured with the reporter's kill-and-restart sequence: subscribe_data_change() now genuinely succeeds instead of raising while working. The suite is unchanged, ruff check and mypy are clean.

…ption

create_monitored_items registers items client-side before the server call
so that notifications arriving ahead of the response are not lost. When the
whole service call failed, that registration was never undone: per-item
failures were removed, a service-level failure was not. With auto_reconnect
the supervisor's recreate() then carried those half-registered items into
the new subscription, so a subscribe_data_change() that raised
BadSubscriptionIdInvalid ended up delivering notifications anyway (FreeOpcUa#2020).

The registry mutations now clean up when the service call raises, and a
call that started against one incarnation of the subscription and returns
against another is told so rather than reporting success for work the
server no longer knows about.

Ordering these with a lock was the obvious approach and the wrong one: a
call holding it while stuck on a dropped connection blocks the very
reconnect that would free it, which deadlocks the client — worse than the
bug being fixed. A generation counter gives the same guarantee and never
waits. recreate() also carries over only items the server confirmed, since
adopting one still in flight would make its call raise while its node was
quietly being watched.
Comment thread asyncua/common/subscription.py Outdated
Comment thread asyncua/common/subscription.py
Comment thread asyncua/common/subscription.py
@Poseidonas

Copy link
Copy Markdown
Contributor Author

Done. The comments are gone, the wrapper methods too (all five, they were left over from the lock attempt), and the counter is now _recreate_count.

Comment thread asyncua/common/subscription.py Outdated
return
saved_items = list(self._monitored_items.values())
self._recreate_count += 1
saved_items: list[SubscriptionItemData] = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

precise these are montored items. I ha to read that several times to understand

Comment thread asyncua/common/subscription.py Outdated
mids.append(result.MonitoredItemId)
return mids

def _forget(self, requests: list[ua.MonitoredItemCreateRequest], recreate_count: int) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cal it _forget_monitored_items

@oroulet

oroulet commented Sep 12, 2026

Copy link
Copy Markdown
Member

some more comments but I think it looks ok

@Poseidonas

Copy link
Copy Markdown
Contributor Author

Done — renamed to saved_monitored_items and _forget_monitored_items.

@oroulet
oroulet merged commit ea670a8 into FreeOpcUa:master Sep 15, 2026
6 checks passed
@Poseidonas Poseidonas moved this from Waiting on review to Done in Industrial open source Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subscribe_data_change() fails... but it doesn't

2 participants