Thread-less long-polling

Rational

The current implementation of long-polling creates a thread or a process per connection. This comes from the design of WSGI server (used: werkzeug or uwsgi). This has drawback because production ready server like uwsgi limit the number of thread or process and so this does not scale well to the point we had to disable it by default.
So we need a solution that is thread-less.

Proposal

We see two options:

  • Adding a separate server which will be thread-less. The first thought is to base it on asyncio. So it should be deployed separately and a reverse-proxy should be configured to dispatch the request between the two services.
  • Allow to run trytond and replace thread/fork by greenlet by using gevent. So a single process could be run for both requests or one thread/fork process can be launched as usual and a second with gevent to process long-polling request (using a reverse-proxy).

The two options are not mutually exclusive but it is probably better to have one way to scale up long-polling.

Implementation

Here are prototypes of both solution:

  • asyncio
  • gevent

0 voters

1 Like

asyncio is fully integrated into Python3 and has the Python-like syntax, so it is easier to adapt (https://www.youtube.com/watch?v=ZzfHjytDceU). Even more, (now I’m going into the dangerzone :slight_smile:) why not use websockets?

To sum up the discussions we had about this subject yesterday, here’s some points:

  • gevent patch is minimal
  • gevent can use tryton objects (eg for authentication)
  • asyncio is included with python and thus probably more sustainable
  • asyncio adds a pure python dependency, gevent is more low-level and thus complicated

The difficulty is implementing them in the GTK client, in sao it wouldn’t be a problem but in tryton it’s another story.

1 Like

I would say quite this opposite. The introduction of asyncio in Python has split the language into two flavors which can not really cooperate together. It is the main problem to use it with Tryton because almost any existing function/tools can not be reused.

But asyncio is younger 2012 and gevent 2009 and greenlet 2006. gevent and greenlet are currently actively maintained as we can see support for new Python version is added with alpha release.
Also being in the standard library is nice but it is not necessary always the best tool. For example we use requests in many modules while there are modules in standard library to do the same.

Except that we would need aiohttp (because asyncio is not battery included) which has C and Cython files.

I may sound very negative about asyncio but indeed I think it is a nice addition to Python (but it would have been nicer to stay compatible with existing lib). Indeed it is the trend to use it for everything that is wrong for me. Sequential and multi-thread programming are still relevant in most cases mainly because it is simpler.

Asynchronous programming is more complex than classical “sequential” programming

from 18.5. asyncio — Asynchronous I/O, event loop, coroutines and tasks — Python 3.6.15 documentation

Even if asyncio was really integrated to python, using it with Tryton wouldn’t be easy.

That’s the pure python dependency I talk about. It has indeed Cython files used to parse the HTTP requests.
Of course YMMV but I find this easier to grasp than a C library fidling with the stack.

account_fr_chorus and stock_package_shipping_* are using it. Not that many in fact.
But I am not against the inclusion of third-party libraries of course, it’s just that it’s nice that asyncio is included with python.

I agree, asyncio should indeed be used for what its name hints it should be used for : IO.
And that’s the issue here, the bus mainly does IO because there is very few computations (it’s almost a microservice :stuck_out_tongue:).

Sequential programming, when talking about concurrent programming I am not sure it’s really more complex.

PS: I did not vote as I don’t have yet an opinion about which solution is better. My biggest fear is that indeed greenlets were cool ten years ago but since then there has been a pradigm switch and I don’t expect it to change any time soon, so I think that it will be less and less used and will slowly become irrelevant.

Not for me, the bus is mainly about waiting. But not waiting the availability of IO resource (aka IO bounds) but that an external event happens. So the goal is to wait by consuming the less resources (DB connections, threads, memory etc.) possible.
That’s why the asyncio code is indeed very similar to the existing bus code. It is only removing threads and replace them by corouting/greenlet using a different syntax.

I think you have to take a step back and look at the reason why the long-polling service was implemented in the first place. So rethinking what the service is doing and why or what is needed. Then you can start looking for implementing it thread-less. Maybe on thread so clients can send each other messages etc?

I forgot one point:

  • asyncio requires a proxy (Tryton is commonly deployed behind a proxy so it’s a bit a non issue but it’s nice to know anyway).

I might not have understood you but implementing the message passing (aka chat) between clients is indeed a use case we have in mind (but it’s not the only one).

Yet for now we can do it using the long-polling solution (@ced will tell you that anyway with HTTP/2 websockets will not be supported anymore) and the GTK client does not support them (aiohttp do support websockets for the client side so it could be a start but it’s a big amount of work anyway).

I’ve just voted greenlet because (correct me if I’m wrong) using it will also improve the other trytond requests (not only the bus one’s). Also I find it’s patch simplier which will ease the maintenance.

Just on doubt here: Do you plan to add those dependencies as required for all users or use them optionally only for the ones that want to use the long-poling system?

Such “coroutined” tryton could also answer to standard request. I do not know if it improves the performance or not. Indeed I think it depends on the use case.

You can see the patch which I polished to be ready for inclusion. It adds only optional dependencies and it is even not required to run the bus.

I don’t think it will change anything wrt to the performances of other requests (at most it will add some overhead to the monkey patched calls).

Indeed the patches are not includable as is.
I think we will need to use conditional dependencies.

Oops, the review stayed on my laptop. It is updated now.

There may be some drawbacks to use coroutine with psycopg2: http://initd.org/psycopg/docs/advanced.html#support-for-coroutine-libraries

My gut feeling is that this simplicity hides (too much to my liking) real complexity.

For our use case, it’s not an issue given that the bus is fairly simple. But if I had to write a complete asynchronous application from scratch I would probably use asyncio instead of gevent (I really like the explicit nature of async / await, the main issue is the incompatibilities between the libraries).

1 Like

HI All…
I think asyncio Its impact on your code is very invasive and it is insufficiently integrated into the programming language and runtime. The later makes diagnosing problems more difficult due to insufficient help from tools like debuggers…
I think too gevent is much more intuitive to use, and faster…

But we are not an asynchronous application but a synchronous one with one asynchronous functionality (the bus message).

1 Like

Indeed, I just said that for our use case it’s not an issue. Yet if I had to write something from scratch I wouldn’t use gevent.

FYI, I submitted Issue 7943: Allow to redirect bus request - Tryton issue tracker to ease to add a separate server for long polling (no matter the thread-less solution chosen).

1 Like

This topic was automatically closed after 6 days. New replies are no longer allowed.