Skip to content

http-socket does not support Keep-Alive #1097

Closed
eduNEXT/drydock
#126
@parasyte

Description

@parasyte
Contributor

The Native HTTP Support does not support HTTP Keep-Alive. It's easy to test with uwsgi --http-socket :8080 and using telnet to send a valid HTTP/1.1 request; uwsgi will close the socket after sending the response.

A well-behaved HTTP/1.1 server will keep the socket open unless the client requests Connection: Close.

Activity

Darvame

Darvame commented on Nov 2, 2015

@Darvame
Contributor

uwsgi --http-socket :8080 --http-keepalive --add-header "Connection: keep-alive"

parasyte

parasyte commented on Nov 2, 2015

@parasyte
ContributorAuthor

@Darvame Perhaps you could try this yourself, because it does not work:

# wsgi.py
import gevent.monkey
gevent.monkey.patch_all()

import bottle

app = bottle.Bottle()

@app.route('/ping')
def ping():
    return {
        'message' : "pong",
    }

if __name__ == '__main__':
    bottle.run(
        app,
        server=bottle.GeventServer,
    )

Run the server:

$ uwsgi --http-socket :8080 --add-header "Connection: Keep-Alive" --http-keepalive --so-keepalive --gevent 100 --virtualenv env --module wsgi:app

Test keepalive:

$ time telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /ping HTTP/1.1
Host: localhost

HTTP/1.1 200 OK
Content-Length: 19
Content-Type: application/json
Connection: Keep-Alive

{"message": "pong"}Connection closed by foreign host.

real    0m0.444s
user    0m0.014s
sys     0m0.009s

The following invocation works as expected:

$ uwsgi --http :8080 --http-keepalive --so-keepalive --gevent 100 --virtualenv env --module wsgi:app

Test keepalive:

$ time telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /ping HTTP/1.1
Host: localhost

HTTP/1.1 200 OK
Content-Length: 19
Content-Type: application/json

{"message": "pong"}Connection closed by foreign host.

real    1m0.223s
user    0m0.013s
sys     0m0.008s

Here are a few backtraces from lldb (plain ol' Python and gevent plugin) showing where the close is occurring unconditionally:

Python: https://github.com/unbit/uwsgi/blob/2.0.11.2/core/loop.c#L149

(lldb) b close
Breakpoint 1: 5 locations.
(lldb) c
Process 59371 resuming
Process 59371 stopped
* thread #1: tid = 0x3a67e2, 0x00007fff838e29d8 libsystem_kernel.dylib`close, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3
    frame #0: 0x00007fff838e29d8 libsystem_kernel.dylib`close
libsystem_kernel.dylib`close:
->  0x7fff838e29d8 <+0>:  movl   $0x2000006, %eax
    0x7fff838e29dd <+5>:  movq   %rcx, %r10
    0x7fff838e29e0 <+8>:  syscall
    0x7fff838e29e2 <+10>: jae    0x7fff838e29ec            ; <+20>
(lldb) bt
* thread #1: tid = 0x3a67e2, 0x00007fff838e29d8 libsystem_kernel.dylib`close, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3
  * frame #0: 0x00007fff838e29d8 libsystem_kernel.dylib`close
    frame #1: 0x0000000100002ee2 uwsgi`close_and_free_request(wsgi_req=0x00000001003fb078) + 34 at utils.c:1006
    frame #2: 0x00000001000031af uwsgi`uwsgi_close_request(wsgi_req=0x00000001003fb078) + 639 at utils.c:1105
    frame #3: 0x000000010004e886 uwsgi`simple_loop_run(arg1=<unavailable>) + 230 at loop.c:149
    frame #4: 0x0000000100055e0b uwsgi`uwsgi_ignition + 443 at uwsgi.c:3537
    frame #5: 0x0000000100055c03 uwsgi`uwsgi_worker_run + 883 at uwsgi.c:3465
    frame #6: 0x000000010005362e uwsgi`uwsgi_run + 478 at uwsgi.c:3375
    frame #7: 0x00000001000512ae uwsgi`main(argc=<unavailable>, argv=<unavailable>, envp=<unavailable>) + 14 at uwsgi.c:2002
    frame #8: 0x00007fff9314d5ad libdyld.dylib`start + 1
    frame #9: 0x00007fff9314d5ad libdyld.dylib`start + 1

gevent: https://github.com/unbit/uwsgi/blob/2.0.11.2/plugins/gevent/gevent.c#L325

(lldb) b close
Breakpoint 1: 5 locations.
(lldb) c
Process 59862 resuming
Process 59862 stopped
* thread #1: tid = 0x3a80a5, 0x00007fff838e29d8 libsystem_kernel.dylib`close, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3
    frame #0: 0x00007fff838e29d8 libsystem_kernel.dylib`close
libsystem_kernel.dylib`close:
->  0x7fff838e29d8 <+0>:  movl   $0x2000006, %eax
    0x7fff838e29dd <+5>:  movq   %rcx, %r10
    0x7fff838e29e0 <+8>:  syscall
    0x7fff838e29e2 <+10>: jae    0x7fff838e29ec            ; <+20>
(lldb) bt
* thread #1: tid = 0x3a80a5, 0x00007fff838e29d8 libsystem_kernel.dylib`close, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3
  * frame #0: 0x00007fff838e29d8 libsystem_kernel.dylib`close
    frame #1: 0x0000000100002ee2 uwsgi`close_and_free_request(wsgi_req=0x0000000101be1618) + 34 at utils.c:1006
    frame #2: 0x00000001000031af uwsgi`uwsgi_close_request(wsgi_req=0x0000000101be1618) + 639 at utils.c:1105
    frame #3: 0x00000001000743ed uwsgi`py_uwsgi_gevent_request(self=<unavailable>, args=<unavailable>) + 349 at gevent.c:325
    frame #4: 0x000000010022e2a5 Python`PyEval_EvalFrameEx + 12479
    frame #5: 0x000000010022afb4 Python`PyEval_EvalCodeEx + 1387
    frame #6: 0x00000001001cfbf5 Python`function_call + 352
    frame #7: 0x00000001001b1ad7 Python`PyObject_Call + 99
    frame #8: 0x00000001001bc962 Python`instancemethod_call + 174
    frame #9: 0x00000001001b1ad7 Python`PyObject_Call + 99
    frame #10: 0x0000000100230e2e Python`PyEval_CallObjectWithKeywords + 93
    frame #11: 0x0000000101ff1b2a greenlet.so`g_initialstub + 1114
    frame #12: 0x0000000101ff13ad greenlet.so`g_switch + 333
    frame #13: 0x00000001001bc8b4 Python`instancemethod_hash + 97
    frame #14: 0x000000010029da10 Python`instancemethod_getset + 80

These lines also execute with --http :8080 (i.e. in the working case), but I assume it's closing the socket on the backend?

Darvame

Darvame commented on Nov 2, 2015

@Darvame
Contributor

Yes, does not work for me too :C

unbit

unbit commented on Nov 2, 2015

@unbit
Owner

You need uWSGI 2.1 (master branch) and the --http11-socket option. Once set the server will try to maintain the connection opened if a bunch of rules are respected. This is not a smart http 1.1 parser (to avoid parsing the whole response) but assumes the developer is generating the right headers. It has been added to support RTSP protocol for video streaming.

depaolim

depaolim commented on Nov 6, 2015

@depaolim
Contributor

Does this new option replace completely the old ones?

I used

./uwsgi --http11-socket :8080 --gevent 100 --virtualenv env --module wsgi:app

and I obtained this:

$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /ping HTTP/1.1

HTTP/1.1 200 OK
Content-Length: 19
Content-Type: application/json

{"message": "pong"}

without any "Connection closed by foreign host."

So it seems to work as a "Keep-alive" without any need of the old options

--add-header "Connection: Keep-Alive" --http-keepalive --so-keepalive

Does it mean that they are no more necessary?

xrmx

xrmx commented on Nov 13, 2015

@xrmx
Collaborator

@depaolim i think --http11-socket may replace the add-header and http-keepalive options but i don't see it touching tcp stuff as so-keepalive does.

xrmx

xrmx commented on Nov 15, 2015

@xrmx
Collaborator

Documentation has been updated thanks to @depaolim, closing this.

marc1n

marc1n commented on Apr 1, 2017

@marc1n

I confirm (by testing it) that "http-keepalive" option works only with "http" (not "http-socket") in uWSGI 2.0.

Why it is "http-keepalive" not working with "http-socket"? Is it any reason behind this?

The "http11-socket" is a future option (in uWSGI 2.1 which for now is not released yet) so in reality I cannot use HTTP Keep-Alive in uWSGI not paying penalty of "http" option overhead.

Btw, "http-keepalive" option value is a number which specify timeout (in seconds) after which an inactive connection is closed by server. How can I configure this timeout when I will use "http11-socket"?

ushuz

ushuz commented on Apr 30, 2018

@ushuz

Seconding @marc1n, we observed the same problem. http-keepalive doesn't work with http-socket but work with http. There's also a stackoverflow question reporting the same.

http11-socket does enable keepalive, but it doesn't work well with gevent.


UPDATE (14 Nov 2018): http11-socket doesn't work well with non-gevent either, half workers got jammed after a while.

jf

jf commented on Sep 15, 2020

@jf

Btw, "http-keepalive" option value is a number which specify timeout (in seconds) after which an inactive connection is closed by server. How can I configure this timeout when I will use "http11-socket"?

@marc1n what is your source for this? I see #2018, which seems to indicate that this is a boolean instead.

marc1n

marc1n commented on Sep 15, 2020

@marc1n
jf

jf commented on Sep 15, 2020

@jf

@jf From the source code: https://github.com/unbit/uwsgi/blob/uwsgi-2.0/plugins/http/http.c#L658

Excellent, thanks! I see it now. On another note, if anybody has any recommendations for another server to check out that has proper http keep-alive support, I'd be glad to hear it.

xqliang

xqliang commented on Nov 22, 2022

@xqliang

@depaolim i think --http11-socket may replace the add-header and http-keepalive options but i don't see it touching tcp stuff as so-keepalive does.

--http-socket + --http-keepalive + add-header not worked, and --http11-socket works like --http + --http-keepalive + add-header,but the add-header is still needed for some clients(e.g. ab -k) to keep connection open(alive). BTW, use --socket-timeout(defaults to 4s) to set the keepalive timeout in seconds.

NOTE: Currently(uWSGI==2.0.21 2022.10.25) only --http + --http-keepalive + add-header works well with Nginx keepalive, #2133.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      Participants

      @jf@xrmx@unbit@depaolim@parasyte

      Issue actions

        http-socket does not support Keep-Alive · Issue #1097 · unbit/uwsgi