Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why is gevent-websocket synchronous?

I am playing with gevent and websockets. This is a simple echo server:

from gevent.pywsgi import WSGIServer
from geventwebsocket.handler import WebSocketHandler
from gevent import sleep
from datetime import datetime
def app(environ, start_response):
    ws = environ['wsgi.websocket']
    while True:
        data = ws.receive()
        print('{} got data "{}"'.format(
            datetime.now().strftime('%H:%M:%S'), data))
        sleep(5)
        ws.send(data)

server = WSGIServer(("", 10004), app,
    handler_class=WebSocketHandler)
server.serve_forever()

and the client:

<html>
    <body>
        <button type="button" id="push_data">Push</button>
    </body>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.js"></script>
    <script>
        var ws = new WebSocket("ws://localhost:10004");
        ws.onmessage = function(evt) {
            console.log(evt)
        };
        $('#push_data').click(function(){
            console.log('sending data...');
            ws.send('sample data');
        });
    </script>
</html>

Because of gevent I was expecting to have several greenlets serving the data asynchronously; that is, if I pushed some data to the websocket several times (quickly clicking on the Push button), I was expecting to have it all back simultaneously after 5 seconds of waiting.

However, no matter how fast I click the Push button, this is what I get in the console:

18:28:07 got data "sample data"
18:28:12 got data "sample data"
18:28:17 got data "sample data"
18:28:22 got data "sample data"
18:28:27 got data "sample data"

why does it receive my data synchronously, pausing every 5 seconds? How do I turn it into an asynchronous server?

like image 845
kurtgn Avatar asked Jul 02 '15 15:07

kurtgn


1 Answers

The behavior is synchronous because your own code is synchronous. gevent is only a coroutine library using an event loop. It does not magically turn synchronous code into asynchronous code.

Please have a look at the documentation at: http://www.gevent.org/servers.html

It is said that the servers spawn one greenlet per connection (not per request). The execution of multiple requests for the same connection is therefore serialized.

If you want to concurrently handle multiple requests for the same connection, you need to spawn new greenlets, or delegate the processing to a pool of greenlets.

Here is an example (spawning a greenlet at each request):

import gevent
from gevent.pywsgi import WSGIServer
from gevent.lock import Semaphore
from geventwebsocket.handler import WebSocketHandler
from datetime import datetime

def process(ws,data,sem):
    print('{} got data "{}"'.format(datetime.now().strftime('%H:%M:%S'), data))
    gevent.sleep(5)
    with sem:
        ws.send(data)

def app(environ, start_response):
    ws = environ['wsgi.websocket']
    sem = Semaphore()
    while True:
        data = ws.receive()
        gevent.spawn(process,ws,data,sem)

server = WSGIServer(("", 10004), app,handler_class=WebSocketHandler)
server.serve_forever()

Note the presence of the semaphore. Because the processing is concurrent, it is needed to prevent two concurrent greenlets to write at the same time on the socket, resulting in corrupted messages.

Last point, with this implementation, there is no guarantee that the replies will be sent in the order of the requests.

like image 169
Didier Spezia Avatar answered Sep 19 '22 14:09

Didier Spezia