FreeOpcUa / opcua-asyncio

OPC UA library for python >= 3.7
GNU Lesser General Public License v3.0
1.14k stars 362 forks source link

Subscription tasks not closed properly on OPCUA server shutdown #1652

Open MaciejPMarciniak opened 5 months ago

MaciejPMarciniak commented 5 months ago

Describe the bug
When the server is closed / restarted by remote operators, the BadShutdown status is sent to the StatusChangeNotification. The tasks running in the threadloop are not closed, which causes the running program to hang. Some of the tasks are cancelled, which results in asyncio's CancelledError, which is not handled.

To Reproduce

async def start(self, nodes):
    ...
    return await self.api.subscribe(self, nodes, self.time_to_live)
async def subscribe(self, handler, node_names, lifetime):
        try:
            self.thread.post(self.do_subscribe(handler, node_names, lifetime))
        except TimeoutError as e:
            self.logger.error(
                f"TimeoutError: no response from server after {self.post_timeout} seconds"
            )
            self.stop()  # Close the connection to the server, never reached
          ...
async def do_subscribe(self, handler, node_names, lifetime):
        client = await self.client()  # method for connecting to the server
        nodes = [client.get_node(f"ns={self.namespace_index};s={node_name}") for node_name in node_names]
        subscription = await client.create_subscription(500, handler)
        await subscription.subscribe_data_change(nodes)
        await asyncio.sleep(lifetime)
        await subscription.delete()
        await asyncio.sleep(1)

This is the setup. The error occurs when the server is closed remotely.

Expected behavior
All tasks are either executed or cancelled gracefully, such that the program does not hang.

Screenshots

[OPCUA] [asyncua.client.client] [ERROR] Error in watchdog loop
Traceback (most recent call last):
File "/home/_/.pyenv/versions/3.12.2/lib/python3.12/asyncio/[tasks.py](http://tasks.py/)", line 520, in wait_for
return await fut
^^^^^^^^^
asyncio.exceptions.CancelledError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/client/[client.py](http://client.py/)", line 552, in _monitor_serverloop
\ = await self.nodes.server_state.read_value()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/common/[node.py](http://node.py/)", line 207, in read_value
result = await self.read_data_value()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/common/[node.py](http://node.py/)", line 220, in read_data_value
return await self.read_attribute(ua.AttributeIds.Value, None, raise_on_bad_status)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/common/[node.py](http://node.py/)", line 342, in read_attribute
result = await self.session.read(params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/client/ua_client.py", line 404, in read
data = await self.protocol.send_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/client/ua_[client.py](http://client.py/)", line 167, in send_request
data = await asyncio.wait_for(self._send_request(request, timeout, message_type), timeout if timeout else None)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/.pyenv/versions/3.12.2/lib/python3.12/asyncio/[tasks.py](http://tasks.py/)", line 519, in wait_for
async with timeouts.timeout(timeout):
File "/home/_/.pyenv/versions/3.12.2/lib/python3.12/asyncio/[timeouts.py](http://timeouts.py/)", line 115, in aexit
raise TimeoutError from exc_val
TimeoutError

[OPCUA] [asyncua.common.subscription] [ERROR] Exception calling status change handler
Traceback (most recent call last):
File "/home/_/.pyenv/versions/3.12.2/lib/python3.12/asyncio/[tasks.py](http://tasks.py/)", line 520, in wait_for
return await fut
^^^^^^^^^
asyncio.exceptions.CancelledError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/client/[client.py](http://client.py/)", line 552, in _monitor_serverloop
\ = await self.nodes.server_state.read_value()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/common/[node.py](http://node.py/)", line 207, in read_value
result = await self.read_data_value()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/common/[node.py](http://node.py/)", line 220, in read_data_value
return await self.read_attribute(ua.AttributeIds.Value, None, raise_on_bad_status)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/common/[node.py](http://node.py/)", line 342, in read_attribute
result = await self.session.read(params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/client/ua_client.py", line 404, in read
data = await self.protocol.send_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/work/EEP_DataExtractor_pipeline/.venv/lib/python3.12/site-packages/asyncua/client/ua_[client.py](http://client.py/)", line 167, in send_request
data = await asyncio.wait_for(self._send_request(request, timeout, message_type), timeout if timeout else None)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/_/.pyenv/versions/3.12.2/lib/python3.12/asyncio/[tasks.py](http://tasks.py/)", line 519, in wait_for
async with timeouts.timeout(timeout):
File "/home/_/.pyenv/versions/3.12.2/lib/python3.12/asyncio/[timeouts.py](http://timeouts.py/)", line 115, in aexit
raise TimeoutError from exc_val
TimeoutError

If applicable, add screenshots to help explain your problem.

Version
Python-Version: 3.12
opcua-asyncio Version (e.g. master branch, 0.9): 1.1.0

oroulet commented 5 months ago

what task is still running?

MaciejPMarciniak commented 5 months ago

renew_channel_loop and monitor_server_loop