On 07/16/2017 03:14 PM, Nadav Har'El wrote:

On Fri, Jul 14, 2017 at 12:02 PM, Justin Cinkelj <[email protected] <mailto:[email protected]>> wrote:

    Based on @myechuri work,
    https://github.com/myechuri/osv-apps/commits/nginx-osv
    <https://github.com/myechuri/osv-apps/commits/nginx-osv>


Thanks (to both of you)!

I want to commit this patch (we can always improve it later), but have one question first - does this patch depend on any of your recent (yet uncomitted) patches, like the sigsuspend stuff?
Yes, the sigsuspend patch is required.


I also have some more minor comments and questions below:

    A single nginx worker thread is run, so only one CPU core is utilized.


What would it involve to have multiple worker threads?
The linux fork is commented out, and then the main thread is used to run the only one worker thread. I think I can replace execve with osv_execve (or something like that), but I haven't tried yet.


    Example configuration includes very long keepalive_timeout/requests


I think you don't actually need a very long keepalive *timeout* - even 1 second should be more than enough (and anything more will obviously be fine) - if you only start a new connection once ever 1 second, in 2 minutes (the default segment life, if I remember correctly) you only pass through 120 connections.

What you did need is a long keepalive *request limit* - say, 10,000. I think that in any case, regardless of any problem in OSv, it is a good idea to have a very large limit there. I still remember why this limit was added in Apache HTTPd: The thinking is that the server surely has bugs, and after you process a large number of requests in the same server process (it was processes, not threads, originally) is likely to have accrued bugs - especially memory leaks - so it's a good idea to stop the process and start a new one, once in a while, e.g., every 1000 requests. This rationale is no longer relevant in nginx (where we have just one thread anyway, which never stops even if we break the client's connection), and there is no rationale, as far as I can see, why it ever makes sense to break a connection with a client who is still constantly sending requests. And there's especially no reason to break the connection after as little as 100 requests. Is this "100" an nginx default or some random limit which myechuri used in his original config file?
100 is nginx default. I used very high limit for both keepalive_timeout (to avoid problem with test longer than 75 sec - the default value is 75s) and keepalive_requests (as some test are also longer that 100 req :)). I agree that restart process due to bugs cannot be applied on OSv (even after nginx stops, leaked memory is not freed - we need to restart whole VM to achieve that).

    setting to avoid possible problems with OSV and TCP client port reuse.
    With example configuration will server offer two URLs:
     http://$IP/index.html and
     http://$IP/basic_status

    Signed-off-by: Justin Cinkelj <[email protected]
    <mailto:[email protected]>>
    ---
     nginx/.gitignore                                   |   2 +
     nginx/Makefile                                     |  45 +++++++
     nginx/module.py                                    |   3 +
     .../0001-nginx-OSv-fix-process-spawning.patch |  59 +++++++++
     nginx/patches/nginx.conf                           | 143
    +++++++++++++++++++++
     5 files changed, 252 insertions(+)
     create mode 100644 nginx/.gitignore
     create mode 100644 nginx/Makefile
     create mode 100644 nginx/module.py
     create mode 100644
    nginx/patches/0001-nginx-OSv-fix-process-spawning.patch
     create mode 100644 nginx/patches/nginx.conf

    diff --git a/nginx/.gitignore b/nginx/.gitignore
    new file mode 100644
    index 0000000..cb8005e
    --- /dev/null
    +++ b/nginx/.gitignore
    @@ -0,0 +1,2 @@
    +usr.manifest
    +upstream.tar
    diff --git a/nginx/Makefile b/nginx/Makefile
    new file mode 100644
    index 0000000..be5fa9a
    --- /dev/null
    +++ b/nginx/Makefile
    @@ -0,0 +1,45 @@
    +VERSION=1.12.1
    +SOURCE=http://nginx.org/download/nginx-${VERSION}.tar.gz
    <http://nginx.org/download/nginx-$%7BVERSION%7D.tar.gz>
    +CONFIGURE_MODULES=--prefix=/nginx/ --with-debug
    --without-http_rewrite_module --with-threads
    --with-http_stub_status_module
    +
    +.PHONY: module clean
    +
    +SRC=upstream/nginx
    +
    +module: usr.manifest
    +
    +usr.manifest: $(SRC)/nginx.so
    +       echo '[manifest]' > usr.manifest
    +       echo '/nginx.so: $${MODULE_DIR}/$(SRC)/nginx.so' >>
    usr.manifest
    +       echo '/nginx/html/**:
    $${MODULE_DIR}/upstream/nginx/html/**' >> usr.manifest
    +       echo '/nginx/logs/**:
    $${MODULE_DIR}/upstream/nginx/logs/**' >> usr.manifest
    +       echo '/nginx/conf/**:
    $${MODULE_DIR}/upstream/nginx/conf/**' >> usr.manifest
    +       echo '/nginx/conf/nginx.conf:
    $${MODULE_DIR}/patches/nginx.conf' >> usr.manifest
    +
    +clean:
    +       rm -fr upstream
    +       rm -f upstream.tar usr.manifest
    +
    +# Note: the touch commands below are needed after commands which
    create files
    +# "in the past", like wget and tar, and can confuse Make to
    rebuild a target
    +# which is new, just pretends to be old.
    +upstream.tar:
    +       wget -O $@ $(SOURCE)
    +       touch $@
    +
    +$(SRC)/configure: upstream.tar
    +       mkdir upstream
    +       tar -C upstream -xf upstream.tar
    +       cd upstream; ln -sf nginx-${VERSION} nginx
    +       cd $(SRC); patch -p1 <
    ../../patches/0001-nginx-OSv-fix-process-spawning.patch
    +       mkdir $(SRC)/logs; touch $(SRC)/logs/dummy-file
    +       touch $(SRC)/configure
    +
    +$(SRC)/Makefile: $(SRC)/configure
    +       cd $(SRC); ./configure $(CONFIGURE_MODULES)
    --with-cc-opt='-O2 -D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-pie'
    +
    +$(SRC)/nginx.so: $(SRC)/Makefile
    +       make -j4 -C $(SRC)


I think (but haven't tried this in a while) that if you use $(MAKE) instake of "make" you won't need to use "-j4" because it will inherit the parent's paralellism?
I will try this! Hope it works, it would be much nicer.

    +       mv $(SRC)/objs/nginx $(SRC)/nginx.so
    +
    +.DELETE_ON_ERROR:
    diff --git a/nginx/module.py b/nginx/module.py
    new file mode 100644
    index 0000000..3a126f6
    --- /dev/null
    +++ b/nginx/module.py
    @@ -0,0 +1,3 @@
    +from osv.modules import api
    +
    +default = api.run('/nginx.so -c /nginx/conf/nginx.conf')
    diff --git
    a/nginx/patches/0001-nginx-OSv-fix-process-spawning.patch
    b/nginx/patches/0001-nginx-OSv-fix-process-spawning.patch
    new file mode 100644
    index 0000000..7ef301a
    --- /dev/null
    +++ b/nginx/patches/0001-nginx-OSv-fix-process-spawning.patch
    @@ -0,0 +1,59 @@
    +From 0b19a4133fb3afa580a9a8a822b13abfdc37b0e4 Mon Sep 17 00:00:00
    2001
    +From: Justin Cinkelj <[email protected]
    <mailto:[email protected]>>
    +Date: Fri, 14 Jul 2017 10:07:53 +0200
    +Subject: [PATCH] nginx: OSv fix process spawning
    +
    +Original author @myechuri,
    +https://github.com/myechuri/osv-apps/tree/nginx-osv
    <https://github.com/myechuri/osv-apps/tree/nginx-osv>
    +
    +Justin only commented out FIOASYNC part - it is not supported on OSv.
    +
    +Signed-off-by: Justin Cinkelj <[email protected]
    <mailto:[email protected]>>
    +---
    + src/os/unix/ngx_process.c | 14 ++++++++++----
    + 1 file changed, 10 insertions(+), 4 deletions(-)
    +
    +diff --git a/src/os/unix/ngx_process.c b/src/os/unix/ngx_process.c
    +index 24a63fb..1128045 100644
    +--- a/src/os/unix/ngx_process.c
    ++++ b/src/os/unix/ngx_process.c
    +@@ -144,18 +144,22 @@ ngx_spawn_process(ngx_cycle_t *cycle,
    ngx_spawn_proc_pt proc, void *data,
    +
    +         on = 1;
    +         if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) ==
    -1) {
    ++            // OSv: ignore error
    +             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
    +-                          "ioctl(FIOASYNC) failed while spawning
    \"%s\"", name);
    +-            ngx_close_channel(ngx_processes[s].channel, cycle->log);
    +-            return NGX_INVALID_PID;
    ++                          "ioctl(FIOASYNC) failed while spawning
    \"%s\", ignore on OSv", name);
    ++            // ngx_close_channel(ngx_processes[s].channel,
    cycle->log);
    ++            // return NGX_INVALID_PID;


I am not familiar with this code, but if I remember correctly FIOASYNC is about sending a *signal* when I/O is possible on this socket - some sort of archaic replacement for epoll. So I wonder how this stuff actually works *without* the right signals...

Maybe the fact that this is in "ngx_process.c" means this is not part of the socket handling, but some sort of process support which we don't care about anyway? Or something? Would be nice to have a longer comment on why this patch is ok, and what we're giving up on (e.g., that it won't be fine if we had more than one process - or whatever).
I will look into this. I got error, and tried first to just ignore error due to ioctl(FIOASYNC).

    +         }
    +
    ++        // OSv: No need to set owner for the socket since pid 0
    will be running worker.
    ++        /*
    +         if (fcntl(ngx_processes[s].channel[0], F_SETOWN,
    ngx_pid) == -1) {
    +             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
    +                           "fcntl(F_SETOWN) failed while spawning
    \"%s\"", name);
    +             ngx_close_channel(ngx_processes[s].channel, cycle->log);
    +             return NGX_INVALID_PID;
    +         }
    ++        */


Maybe we should implement F_SETOWN, ignoring a parameter of 0 (what we return for getpid()) and warning about any other parameter.

However, I just noticed we have in sohasoutofband() the code which sends the SIGURG signal commented out, so F_SETOWN will not really work as expected, not for FIOASYNC's SIGIO, and not for SIGURG. Hmm.... Maybe worth printing a warning message but still succeeding, so you wouldn't need this patch?
So a patch for OSv, to stub fcntl(fd, F_SETOWN, pid==0), with descriptive WARN_ONCE message.


    +
    +         if (fcntl(ngx_processes[s].channel[0], F_SETFD,
    FD_CLOEXEC) == -1) {
    +             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
    +@@ -183,7 +187,9 @@ ngx_spawn_process(ngx_cycle_t *cycle,
    ngx_spawn_proc_pt proc, void *data,
    +     ngx_process_slot = s;
    +
    +
    +-    pid = fork();
    ++    // OSv: Fake master process as worker.
    ++    // pid = fork();
    ++    pid = 0;
    +
    +     switch (pid) {
    +
    +--
    +2.9.4
    +
    diff --git a/nginx/patches/nginx.conf b/nginx/patches/nginx.conf
    new file mode 100644
    index 0000000..44a984c
    --- /dev/null
    +++ b/nginx/patches/nginx.conf
    @@ -0,0 +1,143 @@
    +
    +#user  nobody;
    +worker_processes  1;
    +
    +# Set error_log to stderr so that log messages are displayed on
    +# OSv console that started "scripts/run.py -nvd".
    +# Although this is less ideal when compared to redirecting error
    +# and access logs to syslog for example, it is a workable first
    +# solution that is comparable to redirection used while starting
    +# Nginx in a container
    +# (reference:
    
http://serverfault.com/questions/657863/nginx-how-to-use-docker-log-collector-when-nginx-is-running-under-supervisord
    
<http://serverfault.com/questions/657863/nginx-how-to-use-docker-log-collector-when-nginx-is-running-under-supervisord>).
    +#error_log stderr debug;


I think that this comment is outdated, because the line is commented out, and you have logging to files below?
I left myechuri logging to stderr in the conf file, but commented it out. When nginx doesn't start (possible due to attempt to open log file in nonexistent directory), it is useful to write errors to stderr. And that is easier to achieve if default config has example lines.


    +
    +#pid        logs/nginx.pid;
    +
    +# Run in foreground, primarily because fork() is stubbed in OSv.
    +# This setting is consistent with official Nginx Dockerfile
    configuration:
    +#
    
https://github.com/nginxinc/docker-nginx/blob/41aa13f7d2c24407e483c40fb1e8b33e73462ff1/mainline/jessie/Dockerfile#L27
    
<https://github.com/nginxinc/docker-nginx/blob/41aa13f7d2c24407e483c40fb1e8b33e73462ff1/mainline/jessie/Dockerfile#L27>
    +daemon off;
    +
    +events {
    +    worker_connections  1024;


Good enough for tests (in your latency test, there is just one connection...) but isn't this much too small for actual deployments? Back in year 2000, we were already talking about C10K, i.e., 10,000 connections...
What is nginx's normal default, if we don't set this?
http://nginx.org/en/docs/ngx_core_module.html#worker_connections - it's only 512. Otherwise, that line is unmodified from nginx.tar.gz. As is most of nginx.conf. Hm, I could submit nginx.conf as patch to file from .tar.gz, but I guess that would be only more difficult to read. So I stayed with nginx.conf as whole file.



    +}
    +
    +
    +http {
    +    include       mime.types;
    +    default_type  application/octet-stream;
    +
    +    #log_format  main  '$remote_addr - $remote_user [$time_local]
    "$request" '
    +    #                  '$status $body_bytes_sent "$http_referer" '
    +    #                  '"$http_user_agent" "$http_x_forwarded_for"';
    +
    +    # Default logging
    +    access_log logs/access.log combined;
    +    error_log logs/error.log error;
    +    # Send access and error logs to stderr for the time being.
    +    #access_log stderr;
    +    #error_log stderr debug;
    +
    +    sendfile        on;
    +    tcp_nopush     on;
    +
    +    # Default keepalive param values
    +    #keepalive_timeout  75;
    +    #keepalive_requests 100;
    +    # Long keepalive to avoid/reduce preblems with TCP port resue
    +    # See https://github.com/cloudius-systems/osv/issues/889
    <https://github.com/cloudius-systems/osv/issues/889>
    +    keepalive_timeout  75000;


As I commented above, I don't think such a high timeout is necessary - if 75 was nginx's default, I think it should be perfectly fine for us too. The main purpose of this timeout is to get rid of client connections which have been idle for a very long time and just wasting memory on our server. It doesn't affect your benchmarks, where a connection is never idle.
I thought the keepalive_timeout is about connection "age". Have to look at https://tools.ietf.org/id/draft-thomson-hybi-http-timeout-01.html#rfc.section.2.1 ('Keep-Alive: timeout" section) - and yes, it's about connection idle time.
So yes, default 75sec is more than enough.

    +    keepalive_requests 1000000000;


Well, there is something between 100 and 1000000000 :-) But I actually think that 1000000000(or even infinity) is fine, as I explained above. So ok.

    +
    +    #gzip  on;
    +
    +    server {
    +        listen       80;
    +        server_name  localhost;
    +
    +        # server_name  192.168.122.1;
    +
    +        #charset koi8-r;
    +
    +        #access_log  logs/host.access.log  main;
    +
    +        location / {
    +            root   html;
    +            index  index.html index.htm;
    +            #aio threads;
    +        }
    +
    +        location /basic_status {
    +            stub_status;
    +        }
    +
    +        #error_page  404              /404.html;
    +
    +        # redirect server error pages to the static page /50x.html
    +        #
    +        error_page   500 502 503 504  /50x.html;
    +        location = /50x.html {
    +            root   html;
    +        }
    +
    +        # proxy the PHP scripts to Apache listening on
    127.0.0.1:80 <http://127.0.0.1:80>
    +        #
    +        #location ~ \.php$ {
    +        #    proxy_pass http://127.0.0.1;
    +        #}
    +
    +        # pass the PHP scripts to FastCGI server listening on
    127.0.0.1:9000 <http://127.0.0.1:9000>
    +        #
    +        #location ~ \.php$ {
    +        #    root           html;
    +        #    fastcgi_pass 127.0.0.1:9000 <http://127.0.0.1:9000>;
    +        #    fastcgi_index  index.php;
    +        #    fastcgi_param  SCRIPT_FILENAME
    /scripts$fastcgi_script_name;
    +        #    include        fastcgi_params;
    +        #}
    +
    +        # deny access to .htaccess files, if Apache's document root
    +        # concurs with nginx's one
    +        #
    +        #location ~ /\.ht {
    +        #    deny  all;
    +        #}
    +    }
    +
    +
    +    # another virtual host using mix of IP-, name-, and
    port-based configuration
    +    #
    +    #server {
    +    #    listen       8000;
    +    #    listen       somename:8080;
    +    #    server_name  somename  alias  another.alias;
    +
    +    #    location / {
    +    #        root   html;
    +    #        index  index.html index.htm;
    +    #    }
    +    #}
    +
    +
    +    # HTTPS server
    +    #
    +    #server {
    +    #    listen       443 ssl;
    +    #    server_name  localhost;
    +
    +    #    ssl_certificate      cert.pem;
    +    #    ssl_certificate_key  cert.key;
    +
    +    #    ssl_session_cache    shared:SSL:1m;
    +    #    ssl_session_timeout  5m;
    +
    +    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    +    #    ssl_prefer_server_ciphers  on;
    +
    +    #    location / {
    +    #        root   html;
    +    #        index  index.html index.htm;
    +    #    }
    +    #}
    +
    +}
    --
    2.9.4

    --
    You received this message because you are subscribed to the Google
    Groups "OSv Development" group.
    To unsubscribe from this group and stop receiving emails from it,
    send an email to [email protected]
    <mailto:osv-dev%[email protected]>.
    For more options, visit https://groups.google.com/d/optout
    <https://groups.google.com/d/optout>.



--
You received this message because you are subscribed to the Google Groups "OSv 
Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to