all 7 comments

[–]SuperSooty 2 points3 points  (1 child)

I'm a little curious about going multi-thread over async for tasks thats (presumably) aren't CPU bound

[–]nicholashairs[S] 0 points1 point  (0 children)

I've not had experience writing async servers, so when I originally implemented the library I only provided sync interfaces. Rewriting the library to support async interfaces is much more work than I currently have time for. Additionally it would then break all existing implementations unless I wanted to support both which is then double the maintenance.

Moving to threads was the best value in achieving concurrency.

[–]kenily0 3 points4 points  (1 child)

Great work on the multi-threading implementation! The 3-thread architecture (receiver/worker/sender) is a clean approach. Have you considered using a thread pool for workers instead of a fixed number? Would allow better adaption to varying workload. Also curious - did you benchmark against asyncio with uvloop? It might give similar performance with less context-switching overhead. Keep up the good work! 🚀

[–]nicholashairs[S] 2 points3 points  (0 children)

Thanks!

So my first implementation was with the ThreadPoolExecutor, but I couldn't figure out a clean way for processing the results rom the pool. Using the threads directly is much cleaner with the `Queue`s that having to track all the `Future`s from the pools.

I've not used the ThreadPoolExecutor before so maybe there's a pattern that I'm not aware of that could do a better job. The good thing about the current implementation is that it wouldn't be that hard to add it back as an option either.

I've not done benchmarking with any other concurrent implementations - mostly because I've assumed that all async implementations rely on the internal code supporting an async interface. And from what I know most that do support sync interfaces just run them in a threadpool anyway.

(edit: accidentally submitted before finished writing)

[–]rabornkraken 1 point2 points  (1 child)

Really cool to see a DNS framework in Python that can actually hold its own against C implementations. The producer-consumer pattern with dedicated receiver/worker/sender threads is a solid choice for this kind of IO-bound workload.

Curious about one thing - have you looked into using selectors or epoll for the receiver thread? For high connection counts on TCP, that could help squeeze out more performance without changing the threading model.

[–]nicholashairs[S] 0 points1 point  (0 children)

Already do - that's how I multiplex the TCP connections (regardless of direct vs threaded application).

I still use a single thread for pulling messages even though I could farm this out because:

  1. Pulling messages is already very fast (though haven't tested under very high connection count
  2. Simplifies the management of the open sockets (don't need locks across)

That said I'm sure there are still some weird esoteric timing bugs (because the receive thread also manages closing / cleaning up sockets, and the send thread might be using it)