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

modbus_server: execute to handle message execute to be coroutine or method #2139

Merged

Conversation

ilkka-ollakka
Copy link
Contributor

@ilkka-ollakka ilkka-ollakka commented Mar 31, 2024

Split from #2127

execute() is used as callback, so we don't change it to async, but we use it to create async_task that can handle async calls

Copy link
Collaborator

@janiversen janiversen left a comment

Choose a reason for hiding this comment

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

Good PR, the only real problem is the create_task, which we really do not want for performance reasons.

@@ -173,17 +175,28 @@ def execute(self, request, *addr):
if self.server.request_tracer:
self.server.request_tracer(request, *addr)

task = self.loop.create_task(self._async_execute(request, *addr))
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a lot of overhead, creating a task for every request. It is possible and efficient to call async methods from sync methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems that asyncio documentation seems to prefer create_task, but if you have proposal to alternative approach, I can change it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed this to run_coroutine_threadsafe, ensure_future seemed to end up using create_task.

@@ -49,6 +49,8 @@ def __init__(self, owner):
self.receive_queue: asyncio.Queue = asyncio.Queue()
self.handler_task = None # coroutine to be run on asyncio loop
self.framer: ModbusFramer
self.request_tasks: set[asyncio.Task] = set()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is opening for parallel processing in the server, that is one big can of worms, please do not open that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll remove use of set

try:
if self.server.broadcast_enable and not request.slave_id:
broadcast = True
# if broadcasting then execute on all slave contexts,
# note response will be ignored
for slave_id in self.server.context.slaves():
response = request.execute(self.server.context[slave_id])
if asyncio.iscoroutine(response):
Copy link
Collaborator

Choose a reason for hiding this comment

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

A short comment telling this is temporary would be nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add mention about temporary

else:
context = self.server.context[request.slave_id]
response = request.execute(context)
if asyncio.iscoroutine(response):
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you move the "if" outside the "else:" you do not need to duplicate the test.

Remark: the "for slave_id" is not the best programming, because it can only be there once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, good point, I'll move it outside of else block.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only problem with it being outside, is that with broadcast it doesn't raise exception in other than last request. But as this being temporary code, I think it is acceptable transition phase?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I left these in place, mainly so that it still raises exceptions from execute in broadcast when it happens and it would end up leaving other execute calls to be not awaited.

# no response when broadcasting
if not broadcast:
if not broadcast and response is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why can response be None now, when it wasn't before ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I set it to None at the beginning, I'll clear it out, so it is closer to original code.

Copy link
Collaborator

@janiversen janiversen left a comment

Choose a reason for hiding this comment

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

One very small change and this seems ready to go.

@@ -49,6 +49,7 @@ def __init__(self, owner):
self.receive_queue: asyncio.Queue = asyncio.Queue()
self.handler_task = None # coroutine to be run on asyncio loop
self.framer: ModbusFramer
self.event_loop = asyncio.get_running_loop()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please call the variable self.loop to be consistent with the rest of the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed

@ilkka-ollakka ilkka-ollakka force-pushed the feat/async_io_execute branch from 3ce922a to 6ffb46f Compare April 2, 2024 07:41
Copy link
Collaborator

@janiversen janiversen left a comment

Choose a reason for hiding this comment

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

LGTM, thanks.

@janiversen janiversen merged commit 24c200c into pymodbus-dev:dev Apr 2, 2024
1 check passed
janiversen pushed a commit that referenced this pull request Jun 18, 2024
janiversen added a commit that referenced this pull request Jun 27, 2024
* add _legacy_decoder to message rtu (#2119)

* Add generate_ssl() to TLS client as helper. (#2120)

* ASCII framer using message decode() (#2128)

* SOCKET/TLS framer using message decode(). (#2129)

* Fix decode for wrong mdap len.

* Streamline message class. (#2133)

* modbus_server: call execute in a way that those can be either coroutines or normal methods (#2139)

* Clean datastore setValues. (#2145)

* fixed kwargs not being expanded for actions on bit registers, adjusted tests to catch this issue (#2161)

* datastore: add async_setValues/getValues methods (#2165)

Co-authored-by: Ilkka Ollakka <[email protected]>

* Request/Response: change execute to be async method (#2142)

* Bump actions CI. (#2166)

* Fix usage of AsyncModbusTcpClient in client docs page (#2169)

* Sphinx: do not turn warnings into errors.

* Add minimal devcontainer. (#2172)

* Transaction id overrun.

* call async datastore from modbus server (#2144)

* Datastore will not return ExceptionResponse. (#2175)

* Describe zero_mode in ModbusSlaveContext.__init__ (#2187)

* Solve pylint error.

* Show error if example is run without support files. (#2189)

* Fix usage file names (#2194)

* Update client.rst (#2199)

* Transaction_id for serial == 0. (#2208)

* Remember to remove serial writer. (#2209)

* Fix writing to serial (rs485) on windows os. (#2191)

Co-authored-by: jan iversen <[email protected]>

* test convert registers with 1234.... (#2217)

* Solve serial unrequested frame. (#2219)

* Log comm retries. (#2220)

* prepare v3.6.9.

* pylint.

* Remove python 3.8 from CI.

---------

Co-authored-by: Ilkka Ollakka <[email protected]>
Co-authored-by: sumguytho <[email protected]>
Co-authored-by: Ilkka Ollakka <[email protected]>
Co-authored-by: Yohrog <[email protected]>
Co-authored-by: James Cameron <[email protected]>
Co-authored-by: Qi Li <[email protected]>
Co-authored-by: andrew-harness <[email protected]>
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