Skip to content

Simplify asyncio and server list #2033

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

Closed
wants to merge 4 commits into from
Closed

Simplify asyncio and server list #2033

wants to merge 4 commits into from

Conversation

alexrudd2
Copy link
Collaborator

Note: this code is very confusing to me, so @janiversen please review carefully.

Solves another 5 mypy errors, and speeds up the Window tests.

Instancing the class _serverList from its own method
(1) is... strange?
(2) is unnecessary to use the methods, which are all @classmethod
(3) is unnecessary to keep track of the event loop, since every server class already has self.loop
==> Use the class methods directly

The methods shutdown() and server_close() are essentially redundant.
==> combine

There's no use for asyncio.wait() directly after an await.
==> remove

time.sleep() is inefficient, as described in #1992 (comment))
==> replace with waiting+timeout for the Future

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.

It looks to me as you have misunderstood what _serverlist does.

_serverlist maintains a list of running servers, allowing us to shutdown all tasks.

def __init__(self, server):
"""Register new server."""
self.server = server
self.loop = asyncio.get_event_loop()
Copy link
Collaborator

Choose a reason for hiding this comment

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

We do need to register the server, that is sort of the whole purpose of the class (keep track of multiple servers).

it used in line 596 and can not be undefined (its interesting that the linters do not complain in line 596, since the class no longer have a loop variable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

They do not complain because each server has already has self.loop

self.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.

aah...but we still need to register the server (or somehow have a list somewhere).


@classmethod
async def run(cls, server, custom_functions):
"""Help starting/stopping server."""
for func in custom_functions:
server.decoder.register(func)
cls.active_server = _serverList(server)
cls.active_server = server
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 how we build a list of servers, shortcutting that means no list of servers.

await asyncio.sleep(1)
else:
await asyncio.sleep(0)
await cls.active_server.shutdown()
Copy link
Collaborator

Choose a reason for hiding this comment

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

active_server is a _serverlist object, not the real server (name is quite confusing though).

Copy link
Collaborator Author

@alexrudd2 alexrudd2 Feb 20, 2024

Choose a reason for hiding this comment

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

This comment and below "These are needed to allow the async task to run" got confused because the diff view does not show the changes well.

Really the changes are this:

-- await cls.active_server.server.shutdown()
++ await cls.active_server.shutdown()

The new code shuts down a server correctly since active_server is now a server object.

await cls.active_server.shutdown()
-- if os.name == "nt":
--  await asyncio.sleep(1)
-- else:
--  await asyncio.sleep(0)

I do not see why the sleeps are "needed to allow the asyncio tasks to run". The await cls.active_server.shutdown() already yields control flow back to the event loop and the await asyncio.sleep() is redundant, no?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We have had severe problems in the past, but not forcing a task switch which is essentially what asyncio.sleep(0) does. Not having a task switch mean e.g. that the task is cancelled but not yet terminated, not always but sometimes (especially on windows).

Copy link
Collaborator

Choose a reason for hiding this comment

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

So you are saying there are no reason to keep a list of active servers, but then we could remove _server_list totally.

_server_list was build to maintain a list of active server, which comes handy when debugging...I do not like to keep _server_list without the list.

@alexrudd2
Copy link
Collaborator Author

alexrudd2 commented Feb 20, 2024

We do need to register the server, that is sort of the whole purpose of the class (keep track of multiple servers).

active_server is a _serverlist object, not the real server (name is quite confusing though).

I was indeed confused here. The name suggests it's a list, but the code looks more like a mixin than a list. (I'm still not sure how the class method run() can instance its own class! **) Each server already knows the event loop (via self.loop in ModbusBaseServer) and handles its own start/stop (via Start/StopServer).

How do multiple servers work?

If it was something like below I would understand.

class _serverlist:
def __init__(self):
    self.servers = []
    self.active_server = None

async def run(cls, server, custom_functions):
   self.servers.append(server)
   self.active_server = server

**this is the most confusing code I have seen in a long time 😆

cls.active_server = _serverList(server)

@alexrudd2
Copy link
Collaborator Author

I considered making this a discussion/issue, but think you prefer to talk about the code. You can consider this PR a draft, I guess. However, it does function ... somehow :)

Each commit is separate, so I'll cherry-pick dbe3cd5 as a separate PR.

@janiversen
Copy link
Collaborator

I understand your confusion, this is code that took quite a while to get right.

StartServer can be called multiple times (we start a async loop each time and call StartAsyncServer)
StartAsyncServer can be called multiple times, but do not return unless terminated !

ServerClose and ServerAsyncClose closes the newest started server, but remark it is not a class method, so it have no way of knowing which servers are running... That is where _server_list is used.

_server_list "remembers" the servers started in a class variable.

Run instantiates an object of itself, that is a pattern you see in a lot of code.

@janiversen
Copy link
Collaborator

To be quite blunt the convenience functions Start* Close* are not what I like, I find it much more correct to instantiate the server object, and work directly.

However removing these function caused quite a fuzz years ago, so they remain, until I find a bigger excuse to make 4.0

@alexrudd2
Copy link
Collaborator Author

Do you want me to split out 0071ac1?

So you are saying there are no reason to keep a list of active servers, but then we could remove _server_list totally.
_server_list was build to maintain a list of active server, which comes handy when debugging...I do not like to keep _server_list without the list.

I do not know enough to have a preference here. Hopefully I have sparked enough discussion for you to improve this some other way.

@janiversen
Copy link
Collaborator

Yes please. As we go down the road _server_list needs to die.

@alexrudd2 alexrudd2 marked this pull request as draft February 21, 2024 16:52
@alexrudd2 alexrudd2 mentioned this pull request Mar 7, 2024
Copy link

github-actions bot commented Apr 7, 2024

This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.

Copy link

This PR was closed because it has been stalled for 10 days with no activity.

@github-actions github-actions bot closed this Apr 18, 2024
@janiversen janiversen deleted the simplify-server-list branch April 18, 2024 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants