FreeOpcUa / opcua-asyncio

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

sync.Clinet is not reusable #1364

Open okapies opened 1 year ago

okapies commented 1 year ago

Describe the bug Calling connect() on sync.Client after disconnect() will raise ThreadLoopNotRunning error.

To Reproduce

>>> client = Client('opc.tcp://localhost')
>>> client.connect()
>>> client.disconnect()
>>> client.connect()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/project/venv/lib/python3.10/site-packages/asyncua/sync.py", line 95, in wrapper
    result = self.tloop.post(aio_func(*args, **kwargs))
  File "/home/user/project/venv/lib/python3.10/site-packages/asyncua/sync.py", line 51, in post
    raise ThreadLoopNotRunning(f"could not post {coro}")
asyncua.sync.ThreadLoopNotRunning: could not post <coroutine object Client.connect at 0x7faf0b51ace0>

Expected behavior (Re-)initialize tloop every time we call connect().

Version

oroulet commented 1 year ago

This is really common. You cannot restart a Thread object. But the error message is is really hard to understand...we need to improve that..

okapies commented 1 year ago

Yes, but the async Client is reusable instead. This is problematic especially when want to combine both versions. Can we fix to start the thread when connect instead of __init__?

miki5799 commented 12 months ago

Same is happening to me and I agree with your proposed expected behaviour.

aclementptw commented 12 months ago

I'd like to bump this, as we are currently migrating from opcua to opcua-asyncio using the wrapper functions. We do reuse Clients and this poses a problem for us.

miki5799 commented 11 months ago

Yes, but the async Client is reusable instead. This is problematic especially when want to combine both versions. Can we fix to start the thread when connect instead of __init__?

Hello everyone,

I have submitted a PR #1503, in which a new thread is started analogous to __init__ iff the ThreadLoop has stopped, so that the updated connect method looks like this:

def connect(self) -> None:
    if not self.tloop.is_alive():
        self.tloop = ThreadLoop()
        self.tloop.start()
        self.close_tloop = True
    self.tloop.post(self.aio_obj.connect())

Analogous for the Server's start method.

This now ensures reusability of client and server, allowing a smoother migration, as mentioned by @aclementptw.