Async ignored on AJAX requests on Nginx server

eComEvo asked:

Despite sending an async request to the server over AJAX, the server will not respond until the previous unrelated request has finished. The following code is only broken in this way on Nginx, but runs perfectly on Apache.

This call will start a background process and it waits for it to complete so it can display the final result.

$.ajax({
    type: 'GET',
    async: true,
    url: $(this).data('route'),
    data: $('input[name=data]').val(),
    dataType: 'json',
    success: function (data) { /* do stuff */}
    error: function (data) { /* handle errors */}
});

The below is called after the above, which on Apache requires 100ms to execute and repeats itself, showing progress for data being written in the background:

checkStatusInterval = setInterval(function () {
    $.ajax({
        type: 'GET',
        async: false,
        cache: false,
        url: '/process-status?process=' + currentElement.attr('id'),
        dataType: 'json',
        success: function (data) { /* update progress bar and status message */ }
    });
}, 1000);

Unfortunately, when this script is run from nginx, the above progress request never even finishes a single request until the first AJAX request that sent the data is done. If I change the async to TRUE in the above, it executes one every interval, but none of them complete until that very first AJAX request finishes.

Here is the main nginx conf file:

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    server_names_hash_bucket_size 64;

    # configure temporary paths
    # nginx is started with param -p, setting nginx path to serverpack installdir
    fastcgi_temp_path temp/fastcgi;
    uwsgi_temp_path temp/uwsgi;
    scgi_temp_path temp/scgi;
    client_body_temp_path temp/client-body 1 2;
    proxy_temp_path temp/proxy;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    # Sendfile copies data between one FD and other from within the kernel.
    # More efficient than read() + write(), since the requires transferring data to and from the user space.
    sendfile on;

    # Tcp_nopush causes nginx to attempt to send its HTTP response head in one packet,
    # instead of using partial frames. This is useful for prepending headers before calling sendfile,
    # or for throughput optimization.
    tcp_nopush on;

    # don't buffer data-sends (disable Nagle algorithm). Good for sending frequent small bursts of data in real time.
    tcp_nodelay on;

    types_hash_max_size 2048;

    # Timeout for keep-alive connections. Server will close connections after this time.
    keepalive_timeout 90;

    # Number of requests a client can make over the keep-alive connection. This is set high for testing.
    keepalive_requests 100000;

    # allow the server to close the connection after a client stops responding. Frees up socket-associated memory.
    reset_timedout_connection on;

    # send the client a "request timed out" if the body is not loaded by this time. Default 60.
    client_header_timeout   20;
    client_body_timeout 60;

    # If the client stops reading data, free up the stale client connection after this much time. Default 60.
    send_timeout 60;

    # Size Limits
    client_body_buffer_size   64k;
    client_header_buffer_size 4k;
    client_max_body_size      8M;

    # FastCGI
    fastcgi_connect_timeout 60;
    fastcgi_send_timeout 120;
    fastcgi_read_timeout 300; # default: 60 secs; when step debugging with XDEBUG, you need to increase this value
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 128k;

    # Caches information about open FDs, freqently accessed files.
    open_file_cache max=200000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # Turn on gzip output compression to save bandwidth.
    # http://wiki.nginx.org/HttpGzipModule
    gzip  on;
    gzip_disable "MSIE [1-6].(?!.*SV1)";
    gzip_http_version 1.1;
    gzip_vary on;
    gzip_proxied any;
    #gzip_proxied expired no-cache no-store private auth;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;

    # show all files and folders
    autoindex on;

    server {
        # access from localhost only
        listen       127.0.0.1:80;
        server_name  localhost;
        root         www;

        # the following default "catch-all" configuration, allows access to the server from outside.
        # please ensure your firewall allows access to tcp/port 80. check your "skype" config.
        # listen       80;
        # server_name  _;

        log_not_found off;
        charset utf-8;

        access_log  logs/access.log  main;

        # handle files in the root path /www
        location / {
            index  index.php index.html index.htm;
        }

        #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   www;
        }

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9100
        #
        location ~ .php$ {
            try_files      $uri =404;
            fastcgi_pass   127.0.0.1:9100;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }

        # add expire headers
        location ~* ^.+.(gif|ico|jpg|jpeg|png|flv|swf|pdf|mp3|mp4|xml|txt|js|css)$ {
            expires 30d;
        }

        # deny access to .htaccess files (if Apache's document root concurs with nginx's one)
        # deny access to git & svn repositories
        location ~ /(.ht|.git|.svn) {
            deny  all;
        }
    }

    # include config files of "enabled" domains
    include domains-enabled/*.conf;
}

Here is the enabled domain conf file:

access_log  off;
access_log  C:/server/www/test.dev/logs/access.log;
error_log   C:/server/www/test.dev/logs/error.log;

# HTTP Server
server {
    listen 127.0.0.1:80;
    server_name test.dev;
    root        C:/server/www/test.dev/public;
    index       index.php;
    rewrite_log on;
    default_type    application/octet-stream;
    #include    /etc/nginx/mime.types;

    # Include common configurations.
    include     domains-common/location.conf;
}

# HTTPS server
server {
    listen      443 ssl;
    server_name test.dev;
    root        C:/server/www/test.dev/public;
    index       index.php;
    rewrite_log on;
    default_type    application/octet-stream;
    #include    /etc/nginx/mime.types;

    # Include common configurations.
    include     domains-common/location.conf;
    include     domains-common/ssl.conf;
}

Contents of ssl.conf:

# OpenSSL for HTTPS connections.
ssl                  on;
ssl_certificate      C:/server/bin/openssl/certs/cert.pem;
ssl_certificate_key  C:/server/bin/openssl/certs/cert.key;
ssl_session_timeout  5m;
ssl_protocols              SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers                HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers  on;

# Pass the PHP scripts to FastCGI server listening on 127.0.0.1:9100
location ~ .php$ {
    try_files      $uri =404;
    fastcgi_param  HTTPS on;
    fastcgi_pass   127.0.0.1:9100;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
}

Contents of location.conf:

# Remove trailing slash to please Laravel routing system.
if (!-d $request_filename) {
    rewrite  ^/(.+)/$ /$1 permanent;
}

location / {
    try_files $uri $uri/ /index.php?$query_string;
}

# We don't need .ht files with nginx.
location ~ /(.ht|.git|.svn) {
    deny  all;
}

# Added cache headers for images.
location ~* .(png|jpg|jpeg|gif)$ {
    expires 30d;
    log_not_found off;
}

# Only 3 hours on CSS/JS to allow me to roll out fixes during early weeks.
location ~* .(js|css)$ {
    expires 3h;
    log_not_found off;
}

# Add expire headers.
location ~* ^.+.(gif|ico|jpg|jpeg|png|flv|swf|pdf|mp3|mp4|xml|txt)$ {
    expires 30d;
}

# Pass the PHP scripts to FastCGI server listening on 127.0.0.1:9100
location ~ .php$ {
    try_files $uri /index.php =404;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    fastcgi_pass  127.0.0.1:9100;
}

Any ideas where this is going wrong?

My answer:


Actually it is definitely the fact that you’re running nginx on Windows.

It has some serious limitations, chief among them that it uses select() for connection processing, and only runs a single thread in a single process. This means that, while it technically can accept() 1024 simultaneous connections, it can only do actual work for one request at a time.

In your case this means Windows is absolutely positively inappropriate as a development platform.

Your best bet, if you really want to continue running Windows, is to run the nginx server in a Linux virtual machine. You should also consider the advantages of running a Linux workstation.


View the full question and answer on Server Fault.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.