pymodbus-dev / pymodbus

A full modbus protocol written in python
Other
2.16k stars 891 forks source link

Runtime Error when init AsyncModbusTcpClient outside of an running event loop #2102

Closed PJ-Schulz closed 3 months ago

PJ-Schulz commented 3 months ago

Versions

Pymodbus Specific

Description

Hello, I found something which works in pymodbus 3.6.4 but not in 3.6.5, 3.6.6.

Previously in version 3.6.4 I could create the client outside of an async function started with asyncio. With the newer versions this is no longer possible. If the client is now created outside, a RuntimeError: no running event loop is raised.

The constructor itself is synchronous. I am surprised that I first have to create an asyncio event loop and then call the constructor.

I think the change came in this PR: #2046

Code and Logs

import asyncio
import pymodbus
from pymodbus.client import AsyncModbusTcpClient

pymodbus.pymodbus_apply_logging_config("DEBUG")

client = AsyncModbusTcpClient("127.0.0.1", 5020)

async def test_connection():
    await client.connect()
    await client.read_holding_registers(100, 10)

asyncio.run(test_connection())
Traceback (most recent call last):
  File "/home/philipp/repos/iiot-app-http-modbus-adapter/foobar.py", line 8, in <module>
    client = AsyncModbusTcpClient("127.0.0.1", 5020)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/philipp/repos/iiot-app-http-modbus-adapter/.venv/lib/python3.11/site-packages/pymodbus/client/tcp.py", line 72, in __init__
    ModbusBaseClient.__init__(
  File "/home/philipp/repos/iiot-app-http-modbus-adapter/.venv/lib/python3.11/site-packages/pymodbus/client/base.py", line 64, in __init__
    ModbusProtocol.__init__(
  File "/home/philipp/repos/iiot-app-http-modbus-adapter/.venv/lib/python3.11/site-packages/pymodbus/transport/transport.py", line 152, in __init__
    self.loop: asyncio.AbstractEventLoop = asyncio.get_running_loop()
                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: no running event loop
janiversen commented 3 months ago

It is correct it worked earlier and no longer works.

Please see the release notes, it is a documented change.

Since it is an async client, you need to instantiate it with a running loop, which anyhow seems logical.

Allowing the async client or server to be instantiated without a running loop gave all kinds of nasty racing problems.

janiversen commented 3 months ago

And just to be clear, the client class constructor is synchronous but lower parts of the code depends on asyncio to work.

As far as I know you cannot define init as async (I haven't tried).