Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add callback to delete subscriptions #1656

Merged
merged 9 commits into from
Jun 17, 2024

Conversation

chrisjbremner
Copy link
Contributor

@chrisjbremner chrisjbremner commented Jun 12, 2024

As discussed here, we need to clean up some subscription objects when a subscription deletion within internal_subscription.py due to an exceeded RevisedLifetimeCount. In this PR, I implement a callback that cleans up the subscription objects in the subscription service that is called when the subscription is stopped in this fashion.

@chrisjbremner chrisjbremner marked this pull request as draft June 12, 2024 23:58
@@ -44,7 +44,6 @@ def __init__(self, internal_server: "InternalServer", aspace: AddressSpace, subm
self.state = SessionState.Created
self.session_id = ua.NodeId(self._counter)
InternalSession._counter += 1
self.subscriptions = []
Copy link
Contributor Author

@chrisjbremner chrisjbremner Jun 13, 2024

Choose a reason for hiding this comment

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

This didn't appear to be referenced anywhere, so it felt safe to use the subscription service's version

@@ -116,7 +129,14 @@ async def publish_results(self, requestdata=None):
self._publish_cycles_count, self.data.RevisedLifetimeCount)
# FIXME this will never be send since we do not have publish request anyway
await self.monitored_item_srv.trigger_statuschange(ua.StatusCode(ua.StatusCodes.BadTimeout))
await self.stop()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using self.stop() directly was tricky due to the context of the subscription task and stop trying to directly await the task. Instead, I opted to decouple the stopping of the subscription from the task by setting self._closing which will immediately terminate the task because it gets checked after exiting this function.

self.aspace,
callback,
request_callback=request_callback,
delete_callback=lambda: self.subscriptions.pop(result.SubscriptionId, None),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alternatively the subscription service could be passed to each internal subscription and the dict could be modified there, but this seemed lighter.

@chrisjbremner chrisjbremner marked this pull request as ready for review June 13, 2024 02:58
@chrisjbremner
Copy link
Contributor Author

I tested this locally by setting my RevisedLifetimeCount to 0 on my subscription and changing the > in this line to >=, which then triggered this error, and I confirmed the subscription was successfully "deleted". I did note that before my changes here, the memory leak was not fully fixed by #1654 because await self.stop() did not fully behave the way I expected due to a suppressed CancelledError

@@ -86,7 +85,13 @@ async def close_session(self, delete_subs=True):
if InternalSession._current_connections < 0:
InternalSession._current_connections = 0
self.state = SessionState.Closed
await self.delete_subscriptions(self.subscriptions)
await self.delete_subscriptions(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of maintaining a list of subscriptions in two places that would need to be updated, store the session ID in the internal subscription so that it can be fetched directly. Makes managing the lists much easier at the expense of a slight efficiency hit on a function that's not called very often.

for id, sub in self.subscription_service.subscriptions.items()
if sub.session_id == self.session_id
]
)
Copy link
Member

Choose a reason for hiding this comment

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

I would really make a method get_subscription_by_id(id) or similar. but OK maybe this is fine..

@oroulet oroulet merged commit 176c077 into FreeOpcUa:master Jun 17, 2024
5 checks passed
@chrisjbremner chrisjbremner deleted the delete_subscription branch June 17, 2024 14:56
@chrisjbremner
Copy link
Contributor Author

Would it make sense to do a new release soon to include these changes?

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.

2 participants