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

Refactor message sending interface to be a coroutine #497

Merged
merged 17 commits into from
Dec 23, 2019

Conversation

Askaholic
Copy link
Collaborator

@Askaholic Askaholic commented Oct 16, 2019

This refactors the old callback interface for message sending to use async/await native instead. Not only is the coroutine interface cleaner and more intuitive, it also helps to ensure that the code behaves as we expect it to (for instance backpressure handling) without us needing to even think about edge cases.

Also adds a few additional test cases.

server/gameconnection.py Outdated Show resolved Hide resolved
server/ladder_service.py Outdated Show resolved Hide resolved
server/ladder_service.py Outdated Show resolved Hide resolved
encode_queues(dirty_queues),
lambda lobby_conn: lobby_conn.authenticated
)
)

Choose a reason for hiding this comment

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

This will cause report_dirties to not wait for these broadcasts. Worst case scenario, we'll start doing these in parallel. We need to change report_dirties to a coroutine as well. Also an opportunity to make its infinite loop sane.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I just took a look at aiocron and this is pretty much exactly how it is implemented. It has a synchronous function which fires off a coroutine without waiting for it to complete, and then schedules its self to be called again via loop.call_at. I tried working on refactoring it to use more or less of a sleep loop, but then you need to have a way of signaling the loop to shut down when the server shuts down, which is really annoying to do. With the call_at method, you don't need a shutdown signal, because if the server shuts down then the coroutine just won't be called again.

server/__init__.py Outdated Show resolved Hide resolved
server/gameconnection.py Outdated Show resolved Hide resolved
@Askaholic Askaholic force-pushed the handle-backpressure branch 2 times, most recently from 135045b to af399d1 Compare December 19, 2019 03:28
Copy link

@Wesmania Wesmania left a comment

Choose a reason for hiding this comment

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

Looks fine otherwise.

lambda lobby_conn: lobby_conn.authenticated and validation_func(lobby_conn)
))

await asyncio.gather(*tasks)

Choose a reason for hiding this comment

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

Is it safe to run all these broadcasts in parallel? Is it possible for data they send to get interleaved?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well you have to remember that with asyncio, nothing actually runs in parallel. There is no scheduler that will interrupt a coroutine, so once a coroutine starts, it keeps running until it decides it’s ready to stop. So I don’t imagine that it’s possible for any messages to become corrupted. I would also guess that they’ll likely execute in the order that they’re created, although it wouldn’t be guaranteed.

Choose a reason for hiding this comment

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

Only chunks of code that don't contain any await calls are guaranteed to be atomic, so if we sent any command in chunks using multiple await calls, then they could get interleaved. It does seem that we're only writing whole commands, though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yea, I don't think the code does that anywhere. The QDataStreamProtocol.send_message function always encodes an entire message and writes it with a single call to StreamWriter.write

@Askaholic Askaholic merged commit 79721a9 into FAForever:develop Dec 23, 2019
@Askaholic Askaholic deleted the handle-backpressure branch December 23, 2019 21:53
Brutus5000 pushed a commit that referenced this pull request Jan 27, 2020
* Refactor message sending interface to be a coroutine

* Use gather to wait on some messages simultaneously

* Clean up imports

* Call gather correctly

* Check results for exceptions

* Make abort() a coroutine

* Make report_dirties a coroutine so that messages are guaranteed to be sent in order

* Add test to verify that ping message is sent

* Refactor report dirty interval using aiocron style timer

* Refactor a few other unnecessary uses of asyncio.ensure_future

* Clean up timer

* Revert geoip service change

* All commands must be coroutines, added tests for lobbyconnection error conditions

* Adjust tests so they actually work

* Add test for verifying that your foes don't see your games

* Add test for verifying that only your friends can see games with 'friends' visibility state

* Make command_pong a coroutine
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.

3 participants