1

I have a network application which is listening on multiple sockets. To handle each socket individually, I use Python's threading.Thread module.

These sockets must be able to run tasks on packet reception without delaying any further packet reception from the socket handling thread.

To do so, I've declared the method(s) that are running the previously mentioned tasks with the keyword async so I can run them asynchronously with asyncio.run(my_async_task(my_parameters)).

I have tested this approach on a single socket (running on the main thread) with great success. But when I use multiple sockets (each one with it's independent handler thread), the following exception is raised:

ValueError: set_wakeup_fd only works in main thread

My question is the following: Is asyncio the appropriate tool for what I need? If it is, how do I run an async method from a thread that is not a main thread.

Most of my search results are including "event loops" and "awaiting" assync results, which (if I understand these results correctly) is not what I am looking for.

I am talking about sockets in this question to provide context but my problem is mostly about the behaviour of asyncio in child threads.

I can, if needed, write a short code sample to reproduce the error. Thank you for the help!

Edit1, here is a minimal reproducible code example:

import asyncio
import threading
import time


# Handle a specific packet from any socket without interrupting the listenning thread
async def handle_it(val):
    print("handled: {}".format(val))  


# A class to simulate a threaded socket listenner
class MyFakeSocket(threading.Thread):
    def __init__(self, val):
        threading.Thread.__init__(self)
        self.val = val  # Value for a fake received packet

    def run(self):
        for i in range(10):
            # The (fake) socket will sequentially receive [val, val+1, ... val+9]
            asyncio.run(handle_it(self.val + i))
            time.sleep(0.5)


# Entry point
sockets = MyFakeSocket(0), MyFakeSocket(10)
for socket in sockets:
    socket.start()
5
  • Your example doesn't raise an error for me. Does it for you? What version of python do you have? Commented Dec 15, 2019 at 14:29
  • @PirateNinjas, I am using Python 3.8. I am currently developping on Windows, mainly because the servers runs on this os, but I'd like it to be multi-platform (at least windows & debian-like) Commented Dec 15, 2019 at 15:06
  • @PirateNinjas Does it print an output for you? (even if it is mish-mashed due to the concurrent print ?) Commented Dec 15, 2019 at 15:12
  • It prints a load of "handled" messages, which seems correct. I'm on ubuntu 18.04 if that makes a difference. Commented Dec 15, 2019 at 15:21
  • Ok, looks like the expected behavior indeed... Thought python was a good scripting language for multi-platform support. (It is actually the main reason we choosed this language) :( Commented Dec 15, 2019 at 15:44

1 Answer 1

1

This is possibly related to the bug discussed here: https://bugs.python.org/issue34679

If so, this would be a problem with python 3.8 on windows. To work around this, you could try either downgrading to python 3.7, which doesn't include asyncio.main so you will need to get and run the event loop manually like:

loop = asyncio.get_event_loop()
loop.run_until_complete(<your tasks>)
loop.close()

Otherwise, would you be able to run the code in a docker container? This might work for you and would then be detached from the OS behaviour, but is a lot more work!

Sign up to request clarification or add additional context in comments.

7 Comments

This seems to be the source of the problem indeed, I don't know how this issue didn't come up in any of my research. I'd like however to keep python supported version as high as possible (to reduce up to come maintainabylity tasks). Do you think the manual handling of the loop would work in the 3.8 python versions ? Or is this usage being depreciated ?
manual handling will work in python 3.8, for sure. I can't test if the error will still occur though, because it's only windows. I would imagine it will be patched soon, so keep an eye out for a 3.8 patch would be my recommendation.
Seems to be the best course of actions! One last question before I can accept the answer. As stated, I want my "MyFakeSocket" to be able to continue running (the run method) even if the "task" function is still being executed (some of these functions/procedures can actiually take a very long time to finish). Wouldn't "run_until_complete()" awaits the end of the task before continuing the for loop (in "MyFakeSocket.run()") ?
About Docker, I've use it many times in the past but only for CI so I'm ok with building one (don't think it would take me too much time), but can you "distribute" a docker image and if so, wouldn't there be a ton of overhead (in terms of application size)?
You might need to break if out of the for loop and have a separate function that creates and awaits all your coroutines, which you then run in the event loop. I'm not 100% on how to do that! As for docker, you can distribute it, but it sounds like it would be unnecessary overhead in your case so perhaps ignore that suggestion!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.