├── dev ├── nginx-pkg │ ├── ngx_lua_ipc │ ├── nginx.conf │ ├── .gitignore │ ├── openresty.logrotate │ ├── bl.txt │ ├── nginx.logrotate │ ├── openresty.install │ ├── install │ ├── nginx.service │ ├── service │ ├── ngx_slab.patch │ └── PKGBUILD ├── .gitignore ├── Gemfile ├── examine_coredump.sh ├── test.rb ├── dev.conf ├── vg.supp ├── nginx.sh ├── rebuild.sh ├── nginx.conf └── memparse.lua ├── src ├── .gitignore ├── lua │ ├── reply.lua │ └── register_receive_handler.lua ├── ngx_lua_ipc.h ├── ngx_lua_ipc_scripts.h ├── ipc.h ├── ngx_lua_ipc.c └── ipc.c ├── config ├── LICENSE └── README.md /dev/nginx-pkg/ngx_lua_ipc: -------------------------------------------------------------------------------- 1 | ../../ -------------------------------------------------------------------------------- /dev/nginx-pkg/nginx.conf: -------------------------------------------------------------------------------- 1 | NGINX_CONFIG=/etc/nginx/conf/nginx.conf 2 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | nginx 2 | nginx-source 3 | lua-nginx-module-source -------------------------------------------------------------------------------- /dev/.gitignore: -------------------------------------------------------------------------------- 1 | coredump/* 2 | clang-analyzer/* 3 | .nginx.thisrun.conf 4 | Gemfile.lock 5 | errors*.log 6 | vgcore.* 7 | captions.txt 8 | nginx -------------------------------------------------------------------------------- /dev/nginx-pkg/.gitignore: -------------------------------------------------------------------------------- 1 | src 2 | pkg 3 | *.tar.gz 4 | *.pkg.tar* 5 | *no_pool.patch 6 | ngx_debug_pool/ 7 | lua-nginx-module/ 8 | nginx 9 | openresty 10 | -------------------------------------------------------------------------------- /dev/Gemfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | source 'https://rubygems.org' 3 | gem 'pry' 4 | gem 'pry-rescue' 5 | 6 | gem 'minitest' 7 | gem 'minitest-reporters' 8 | 9 | gem "hsss" 10 | -------------------------------------------------------------------------------- /dev/nginx-pkg/openresty.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/nginx/*.log { 2 | missingok 3 | sharedscripts 4 | compress 5 | postrotate 6 | test -r /var/run/nginx.pid && kill -USR1 `cat /var/run/nginx.pid` 7 | endscript 8 | } 9 | -------------------------------------------------------------------------------- /dev/nginx-pkg/bl.txt: -------------------------------------------------------------------------------- 1 | fun:ngx_regex_module_init 2 | fun:ngx_http_script_copy_capture_len_code 3 | fun:ngx_http_script_copy_capture_code 4 | fun:ngx_http_parse_request_line 5 | fun:ngx_writev 6 | src:*ngx_writev_chain.c 7 | -------------------------------------------------------------------------------- /dev/nginx-pkg/nginx.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/nginx/*log /var/log/nginx/*/*log { 2 | daily 3 | create 640 http log 4 | compress 5 | postrotate 6 | [ ! -f /run/nginx.pid ] || kill -USR1 `cat /run/nginx.pid` 7 | endscript 8 | } 9 | -------------------------------------------------------------------------------- /dev/nginx-pkg/openresty.install: -------------------------------------------------------------------------------- 1 | post_install(){ 2 | cat<<'EOL' 3 | Please add a following line to the ~/.bashrc or ~/.zshrc : 4 | export PATH=/opt/openresty/bin:$PATH 5 | EOL 6 | 7 | } 8 | post_upgrade(){ 9 | post_install 10 | } 11 | -------------------------------------------------------------------------------- /dev/nginx-pkg/install: -------------------------------------------------------------------------------- 1 | #!sh 2 | post_install() { 3 | post_upgrade 4 | } 5 | post_upgrade() { 6 | rm -fv /etc/nginx/http 2>/dev/null 7 | ln -sf /usr/share/nginx/http /etc/nginx/html 8 | } 9 | post_uninstall() { 10 | rm /etc/nginx/http 11 | } 12 | -------------------------------------------------------------------------------- /src/lua/reply.lua: -------------------------------------------------------------------------------- 1 | return function(ipc) 2 | return function(name, data) 3 | if not ipc.sender then 4 | error("Can't reply, ngx.ipc.reply called outside of IPC alert handler.") 5 | end 6 | return ipc.send(ipc.sender, name, data) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /src/ngx_lua_ipc.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_LUA_IPC_H 2 | #define NGX_LUA_IPC_H 3 | 4 | #include 5 | #include 6 | 7 | extern ngx_module_t ngx_lua_ipc_module; 8 | 9 | typedef struct { 10 | ngx_pid_t sender_pid; 11 | ngx_int_t sender_slot; 12 | ngx_str_t *name; 13 | ngx_str_t *data; 14 | } lua_ipc_alert_t; 15 | 16 | #endif //NGX_LUA_IPC_H 17 | -------------------------------------------------------------------------------- /dev/examine_coredump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | target=$1 3 | core_dir="./coredump" 4 | if [ -z $target ]; then 5 | target=$(realpath ./nginx) 6 | dump=$core_dir/last.core 7 | else 8 | dump=$core_dir/$target.core 9 | fi 10 | 11 | mkdir $core_dir 2>/dev/null 12 | 13 | echo "saving coredump for $target at $dump" 14 | 15 | sudo coredumpctl dump $target > $dump 16 | kdbg ./nginx "$dump" 2>/dev/null 17 | # rm "$dump" #keep it around for now 18 | -------------------------------------------------------------------------------- /dev/test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | require 'minitest' 3 | require 'minitest/reporters' 4 | require "minitest/autorun" 5 | Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new(:color => true)] 6 | require 'securerandom' 7 | 8 | SERVER=ENV["NGINX_SERVER"] || "127.0.0.1" 9 | PORT=ENV["NGINX_PORT"] || "8082" 10 | 11 | puts "Server at #{url}" 12 | 13 | class PubSubTest < Minitest::Test 14 | def test_nothing 15 | end 16 | end 17 | 18 | 19 | -------------------------------------------------------------------------------- /dev/nginx-pkg/nginx.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=A high performance web server and a reverse proxy server 3 | After=syslog.target network.target 4 | 5 | [Service] 6 | Type=forking 7 | PIDFile=/run/nginx.pid 8 | ExecStartPre=/usr/sbin/nginx -t -q -g 'pid /run/nginx.pid; daemon on; master_process on;' 9 | ExecStart=/usr/sbin/nginx -g 'pid /run/nginx.pid; daemon on; master_process on;' 10 | ExecReload=/usr/sbin/nginx -g 'pid /run/nginx.pid; daemon on; master_process on;' -s reload 11 | ExecStop=/usr/sbin/nginx -g 'pid /run/nginx.pid;' -s quit 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /dev/nginx-pkg/service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=A high performance web server and a reverse proxy server 3 | After=syslog.target network.target 4 | 5 | [Service] 6 | Type=forking 7 | PIDFile=/run/openresty.pid 8 | ExecStartPre=/opt/openresty/nginx/sbin/nginx -t -q -g 'pid /run/openresty.pid; daemon on; master_process on;' 9 | ExecStart=/opt/openresty/nginx/sbin/nginx -g 'pid /run/openresty.pid; daemon on; master_process on;' 10 | ExecReload=/opt/openresty/nginx/sbin/nginx -g 'pid /run/openresty.pid; daemon on; master_process on;' -s reload 11 | ExecStop=/opt/openresty/nginx/sbin/nginx -g 'pid /run/openresty.pid;' -s quit 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_lua_ipc_module 2 | 3 | # ngx_feature_libs="" 4 | 5 | _NGX_LUA_IPC_SRCS="\ 6 | ${ngx_addon_dir}/src/ipc.c \ 7 | ${ngx_addon_dir}/src/ngx_lua_ipc.c \ 8 | " 9 | 10 | ngx_module_incs=$ngx_addon_dir/src 11 | 12 | have=NGX_HTTP_HEADERS . auto/have 13 | 14 | if test -n "$ngx_module_link"; then 15 | ngx_module_type=HTTP 16 | ngx_module_name=$ngx_addon_name 17 | ngx_module_srcs="$_NGX_LUA_IPC_SRCS" 18 | ngx_module_libs=$ngx_feature_libs 19 | . auto/module 20 | else 21 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $_NGX_LUA_IPC_SRCS" 22 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs" 23 | CORE_INCS="$CORE_INCS $ngx_module_incs" 24 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 25 | fi 26 | -------------------------------------------------------------------------------- /dev/dev.conf: -------------------------------------------------------------------------------- 1 | #!/bin/sh #good enough highlighting 2 | worker_processes 16; 3 | worker_rlimit_nofile 150000; 4 | 5 | #error_log /dev/stderr debug; 6 | error_log /dev/stderr notice; 7 | #error_log err.log notice; 8 | 9 | pid /tmp/nchan-test-nginx.pid; 10 | daemon off; 11 | 12 | events { 13 | worker_connections 50000; 14 | } 15 | 16 | http { 17 | #include mime.types; 18 | default_type application/octet-stream; 19 | 20 | access_log off; 21 | sendfile on; 22 | tcp_nopush on; 23 | tcp_nodelay on; 24 | keepalive_timeout 30s; 25 | types_hash_max_size 2048; 26 | client_max_body_size 10M; 27 | server_tokens off; 28 | gzip on; 29 | 30 | server { 31 | listen 127.0.0.1:8082; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is distributed under the MIT Licence. 2 | 3 | Written by Leo Ponomarev (slact) 2016-2017. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above authorship notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /src/lua/register_receive_handler.lua: -------------------------------------------------------------------------------- 1 | return function(ipc, run_timer_handler, add_hacktimer, get_last_alert_data) 2 | local timer_handler 3 | local meh = function() end 4 | timer_handler = function(premature) 5 | while true do 6 | local src_slot, src_pid, name, data = get_last_alert_data() 7 | if src_slot == nil then 8 | break 9 | end 10 | ipc.sender = src_pid 11 | local handler = ipc.handlers[name] 12 | if handler then 13 | run_timer_handler(handler, name, data, false) 14 | elseif ipc.default_handler then 15 | run_timer_handler(ipc.default_handler, name, data, true) 16 | else 17 | run_timer_handler(meh, name, data, true) 18 | end 19 | ipc.sender = nil 20 | end 21 | 22 | --add timer again and hack it 23 | add_hacktimer(timer_handler) 24 | end 25 | 26 | local hacktimer_started = false 27 | 28 | local register_handler = function(name, handler, intable) 29 | if type(name) ~= "string" then 30 | 31 | error("bad ".. (intable and "table key in argument #1" or "argument #1") .. " to 'ngx.ipc.receive' (string expected, got " .. type(name) .. ")") 32 | end 33 | local handler_type = type(handler) 34 | if handler_type ~= "function" and handler_type ~= "nil" then 35 | error("bad " .. (intable and "table value in argument #1" or "argument #2") .. " to 'ngx.ipc.receive' (function or nil expected, got " .. type(name) .. ")") 36 | end 37 | ipc.handlers[name]=handler 38 | end 39 | 40 | return function(name, handler) 41 | if type(name)~="table" then 42 | register_handler(name, handler, false) 43 | else 44 | if handler ~= nil then 45 | error("bad argument #2 to 'ngx.ipc.receive' when argument #1 is table (nil expected, got "..type(handler)..")") 46 | end 47 | for n, h in pairs(name) do 48 | register_handler(n, h, true) 49 | end 50 | end 51 | 52 | if not hacktimer_started then 53 | add_hacktimer(timer_handler) 54 | hacktimer_started = true 55 | end 56 | 57 | return true 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /dev/vg.supp: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | epoll_ctl(event) 5 | fun:epoll_ctl 6 | fun:ngx_epoll_test_rdhup 7 | fun:ngx_epoll_init 8 | fun:ngx_event_process_init 9 | fun:ngx_worker_process_init 10 | fun:ngx_worker_process_cycle 11 | fun:ngx_spawn_process 12 | fun:ngx_start_worker_processes 13 | fun:ngx_master_process_cycle 14 | fun:main 15 | } 16 | { 17 | 18 | Memcheck:Leak 19 | match-leak-kinds: reachable 20 | fun:malloc 21 | fun:ngx_alloc 22 | fun:ngx_init_setproctitle 23 | fun:ngx_os_init 24 | fun:main 25 | } 26 | { 27 | 28 | Memcheck:Leak 29 | match-leak-kinds: reachable,possible 30 | fun:malloc 31 | fun:ngx_alloc 32 | fun:ngx_epoll_init 33 | fun:ngx_event_process_init 34 | fun:ngx_worker_process_init 35 | fun:ngx_worker_process_cycle 36 | fun:ngx_spawn_process 37 | fun:ngx_start_worker_processes 38 | fun:ngx_master_process_cycle 39 | fun:main 40 | } 41 | { 42 | 43 | Memcheck:Leak 44 | match-leak-kinds: reachable,possible 45 | fun:malloc 46 | fun:ngx_alloc 47 | fun:ngx_event_process_init 48 | fun:ngx_worker_process_init 49 | fun:ngx_worker_process_cycle 50 | fun:ngx_spawn_process 51 | fun:ngx_start_worker_processes 52 | fun:ngx_master_process_cycle 53 | fun:main 54 | } 55 | 56 | { 57 | 58 | Memcheck:Leak 59 | match-leak-kinds: reachable 60 | ... 61 | fun:ngx_save_argv 62 | fun:main 63 | } 64 | { 65 | 66 | Memcheck:Leak 67 | match-leak-kinds: reachable 68 | fun:malloc 69 | fun:ngx_strerror_init 70 | fun:main 71 | } 72 | 73 | { 74 | 75 | Memcheck:Leak 76 | match-leak-kinds: reachable 77 | ... 78 | fun:ngx_ssl_init 79 | fun:main 80 | } 81 | 82 | { 83 | 84 | Memcheck:Leak 85 | match-leak-kinds: reachable 86 | ... 87 | fun:ngx_http_lua_set_ssl 88 | fun:ngx_http_lua_merge_loc_conf 89 | fun:ngx_http_merge_servers 90 | fun:ngx_http_block 91 | fun:ngx_conf_handler 92 | fun:ngx_conf_parse 93 | fun:ngx_init_cycle 94 | fun:main 95 | } 96 | 97 | { 98 | 99 | Memcheck:Leak 100 | match-leak-kinds: reachable,possible 101 | fun:malloc 102 | fun:ngx_alloc 103 | fun:ngx_crc32_table_init 104 | fun:main 105 | } 106 | -------------------------------------------------------------------------------- /src/ngx_lua_ipc_scripts.h: -------------------------------------------------------------------------------- 1 | // don't edit this please, it was auto-generated by hsss 2 | // https://github.com/slact/hsss 3 | 4 | typedef struct { 5 | char *register_receive_handler; 6 | 7 | char *reply; 8 | 9 | } ngx_ipc_lua_scripts_t; 10 | 11 | ngx_ipc_lua_scripts_t ngx_ipc_lua_scripts = { 12 | //register_receive_handler 13 | "return function(ipc, run_timer_handler, add_hacktimer, get_last_alert_data)\n" 14 | " local timer_handler\n" 15 | " local meh = function() end\n" 16 | " timer_handler = function(premature)\n" 17 | " while true do\n" 18 | " local src_slot, src_pid, name, data = get_last_alert_data()\n" 19 | " if src_slot == nil then \n" 20 | " break\n" 21 | " end\n" 22 | " ipc.sender = src_pid\n" 23 | " local handler = ipc.handlers[name]\n" 24 | " if handler then\n" 25 | " run_timer_handler(handler, name, data, false)\n" 26 | " elseif ipc.default_handler then\n" 27 | " run_timer_handler(ipc.default_handler, name, data, true)\n" 28 | " else\n" 29 | " run_timer_handler(meh, name, data, true)\n" 30 | " end\n" 31 | " ipc.sender = nil\n" 32 | " end\n" 33 | " \n" 34 | " --add timer again and hack it\n" 35 | " add_hacktimer(timer_handler)\n" 36 | " end\n" 37 | "\n" 38 | " local hacktimer_started = false\n" 39 | " \n" 40 | " local register_handler = function(name, handler, intable)\n" 41 | " if type(name) ~= \"string\" then\n" 42 | " \n" 43 | " error(\"bad \".. (intable and \"table key in argument #1\" or \"argument #1\") .. \" to 'ngx.ipc.receive' (string expected, got \" .. type(name) .. \")\")\n" 44 | " end\n" 45 | " local handler_type = type(handler)\n" 46 | " if handler_type ~= \"function\" and handler_type ~= \"nil\" then\n" 47 | " error(\"bad \" .. (intable and \"table value in argument #1\" or \"argument #2\") .. \" to 'ngx.ipc.receive' (function or nil expected, got \" .. type(name) .. \")\")\n" 48 | " end\n" 49 | " ipc.handlers[name]=handler\n" 50 | " end\n" 51 | " \n" 52 | " return function(name, handler)\n" 53 | " if type(name)~=\"table\" then\n" 54 | " register_handler(name, handler, false)\n" 55 | " else\n" 56 | " if handler ~= nil then\n" 57 | " error(\"bad argument #2 to 'ngx.ipc.receive' when argument #1 is table (nil expected, got \"..type(handler)..\")\")\n" 58 | " end\n" 59 | " for n, h in pairs(name) do\n" 60 | " register_handler(n, h, true)\n" 61 | " end\n" 62 | " end\n" 63 | " \n" 64 | " if not hacktimer_started then\n" 65 | " add_hacktimer(timer_handler)\n" 66 | " hacktimer_started = true\n" 67 | " end\n" 68 | " \n" 69 | " return true\n" 70 | " end\n" 71 | "end\n", 72 | 73 | //reply 74 | "return function(ipc)\n" 75 | " return function(name, data)\n" 76 | " if not ipc.sender then\n" 77 | " error(\"Can't reply, ngx.ipc.reply called outside of IPC alert handler.\")\n" 78 | " end\n" 79 | " return ipc.send(ipc.sender, name, data)\n" 80 | " end \n" 81 | "end\n" 82 | }; 83 | 84 | -------------------------------------------------------------------------------- /dev/nginx-pkg/ngx_slab.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/core/ngx_slab.c b/src/core/ngx_slab.c 2 | --- a/src/core/ngx_slab.c 3 | +++ b/src/core/ngx_slab.c 4 | @@ -129,6 +129,8 @@ ngx_slab_init(ngx_slab_pool_t *pool) 5 | pool->pages->slab = pages; 6 | } 7 | 8 | + pool->last = pool->pages + pages; 9 | + 10 | pool->log_nomem = 1; 11 | pool->log_ctx = &pool->zero; 12 | pool->zero = '\0'; 13 | @@ -626,6 +628,8 @@ ngx_slab_alloc_pages(ngx_slab_pool_t *po 14 | if (page->slab >= pages) { 15 | 16 | if (page->slab > pages) { 17 | + page[page->slab - 1].prev = (uintptr_t) &page[pages]; 18 | + 19 | page[pages].slab = page->slab - pages; 20 | page[pages].next = page->next; 21 | page[pages].prev = page->prev; 22 | @@ -672,7 +676,8 @@ static void 23 | ngx_slab_free_pages(ngx_slab_pool_t *pool, ngx_slab_page_t *page, 24 | ngx_uint_t pages) 25 | { 26 | - ngx_slab_page_t *prev; 27 | + ngx_uint_t type; 28 | + ngx_slab_page_t *prev, *join; 29 | 30 | page->slab = pages--; 31 | 32 | @@ -686,6 +691,53 @@ ngx_slab_free_pages(ngx_slab_pool_t *poo 33 | page->next->prev = page->prev; 34 | } 35 | 36 | + join = page + page->slab; 37 | + 38 | + if (join < pool->last) { 39 | + type = join->prev & NGX_SLAB_PAGE_MASK; 40 | + 41 | + if (type == NGX_SLAB_PAGE && join->next != NULL) { 42 | + pages += join->slab; 43 | + page->slab += join->slab; 44 | + 45 | + prev = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); 46 | + prev->next = join->next; 47 | + join->next->prev = join->prev; 48 | + 49 | + join->slab = NGX_SLAB_PAGE_FREE; 50 | + join->next = NULL; 51 | + join->prev = NGX_SLAB_PAGE; 52 | + } 53 | + } 54 | + 55 | + if (page > pool->pages) { 56 | + join = page - 1; 57 | + type = join->prev & NGX_SLAB_PAGE_MASK; 58 | + 59 | + if (type == NGX_SLAB_PAGE && join->slab == NGX_SLAB_PAGE_FREE) { 60 | + join = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); 61 | + } 62 | + 63 | + if (type == NGX_SLAB_PAGE && join->next != NULL) { 64 | + pages += join->slab; 65 | + join->slab += page->slab; 66 | + 67 | + prev = (ngx_slab_page_t *) (join->prev & ~NGX_SLAB_PAGE_MASK); 68 | + prev->next = join->next; 69 | + join->next->prev = join->prev; 70 | + 71 | + page->slab = NGX_SLAB_PAGE_FREE; 72 | + page->next = NULL; 73 | + page->prev = NGX_SLAB_PAGE; 74 | + 75 | + page = join; 76 | + } 77 | + } 78 | + 79 | + if (pages) { 80 | + page[pages].prev = (uintptr_t) page; 81 | + } 82 | + 83 | page->prev = (uintptr_t) &pool->free; 84 | page->next = pool->free.next; 85 | 86 | diff --git a/src/core/ngx_slab.h b/src/core/ngx_slab.h 87 | --- a/src/core/ngx_slab.h 88 | +++ b/src/core/ngx_slab.h 89 | @@ -29,6 +29,7 @@ typedef struct { 90 | size_t min_shift; 91 | 92 | ngx_slab_page_t *pages; 93 | + ngx_slab_page_t *last; 94 | ngx_slab_page_t free; 95 | 96 | u_char *start; 97 | 98 | -- -------------------------------------------------------------------------------- /src/ipc.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_IPC_H 2 | #define NGX_IPC_H 3 | typedef struct ipc_alert_link_s ipc_alert_link_t; 4 | 5 | typedef struct { 6 | struct iovec iov[3]; 7 | int n; 8 | } ipc_iovec_t; 9 | 10 | struct ipc_alert_link_s { 11 | ipc_alert_link_t *next; 12 | ipc_iovec_t iovec; 13 | }; 14 | 15 | typedef struct { 16 | ngx_atomic_uint_t packets_sent; 17 | ngx_atomic_uint_t packets_received; 18 | ngx_atomic_uint_t packets_pending; //waiting to be sent 19 | //ngx_atomic_uint_t dropped_packets; 20 | } ipc_stats_t; 21 | 22 | typedef struct ipc_writebuf_s ipc_writebuf_t; 23 | struct ipc_writebuf_s { 24 | ipc_alert_link_t *head; 25 | ipc_alert_link_t *tail; 26 | ipc_iovec_t last_iovec; 27 | uint32_t n; 28 | }; //ipc_writebuf_t 29 | 30 | 31 | typedef struct { 32 | ngx_pid_t src_pid; 33 | uint32_t tot_len; 34 | uint16_t src_slot; 35 | uint16_t pkt_len; //<=4K (or PIPE_BUF) 36 | uint8_t name_len; 37 | u_char ctrl; // '$': whole, '>': part start, '+': part piece 38 | } ipc_packet_header_t; 39 | #define IPC_ALERT_NAME_MAX_LEN (UINT8_MAX - 1) 40 | #define IPC_ALERT_DATA_MAX_LEN (UINT32_MAX - IPC_ALERT_NAME_MAX_LEN - 1) 41 | 42 | #define IPC_PKT_HEADER_SIZE (sizeof(ipc_packet_header_t)) 43 | #define IPC_PKT_MAX_BODY_SIZE (PIPE_BUF - IPC_PKT_HEADER_SIZE) 44 | 45 | #define IPC_PKT_MAX_SIZE PIPE_BUF 46 | 47 | typedef struct ipc_readbuf_s ipc_readbuf_t; 48 | struct ipc_readbuf_s { 49 | ipc_readbuf_t *prev; 50 | ipc_readbuf_t *next; 51 | ipc_packet_header_t header; 52 | struct { 53 | char *name; 54 | char *data; 55 | size_t bytes_read; 56 | } body; 57 | }; //ipc_readbuf_t 58 | 59 | typedef struct ipc_s ipc_t; 60 | 61 | typedef enum {IPC_NGX_PROCESS_UNKNOWN = 0, IPC_NGX_PROCESS_WORKER, IPC_NGX_PROCESS_CACHE_MANAGER, IPC_NGX_PROCESS_CACHE_LOADER, IPC_NGX_PROCESS_ANY} ipc_ngx_process_type_t; 62 | 63 | typedef enum {IPC_PIPE, IPC_SOCKETPAIR} ipc_socket_type_t; 64 | typedef struct { 65 | ipc_t *ipc; //need this backrerefence for write events 66 | ipc_socket_type_t socket_type; 67 | ngx_socket_t pipe[2]; 68 | ngx_connection_t *read_conn; 69 | ngx_connection_t *write_conn; 70 | ipc_writebuf_t wbuf; 71 | ipc_readbuf_t *rbuf_head; 72 | unsigned active:1; 73 | } ipc_channel_t; 74 | 75 | typedef void (*ipc_alert_handler_pt)(ngx_pid_t alert_sender_pid, ngx_int_t alert_sender_slot, ngx_str_t *alert_name, ngx_str_t *alert_data); 76 | 77 | 78 | #define IPC_MAX_ERROR_LEN 512 79 | struct ipc_s { 80 | const char *name; 81 | void *shm; 82 | size_t shm_sz; 83 | ipc_channel_t channel[NGX_MAX_PROCESSES]; 84 | ngx_int_t worker_process_count; 85 | ipc_alert_handler_pt alert_handler; 86 | u_char last_error[IPC_MAX_ERROR_LEN]; 87 | 88 | unsigned track_stats:1; 89 | }; //ipc_t 90 | 91 | //IPC needs to be initialized in two steps init_module (prefork), and init_worker (post-fork) 92 | ipc_t *ipc_init_module(const char *ipc_name, ngx_cycle_t *cycle); 93 | ngx_int_t ipc_init_worker(ipc_t *ipc, ngx_cycle_t *cycle); 94 | 95 | ngx_int_t ipc_set_alert_handler(ipc_t *ipc, ipc_alert_handler_pt handler); 96 | 97 | ngx_int_t ipc_destroy(ipc_t *ipc); // for exit_worker, exit_master 98 | 99 | ngx_pid_t ipc_get_pid(ipc_t *ipc, int process_slot); 100 | ngx_int_t ipc_get_slot(ipc_t *ipc, ngx_pid_t pid); 101 | 102 | ngx_int_t ipc_alert_slot(ipc_t *ipc, ngx_int_t slot, ngx_str_t *name, ngx_str_t *data); 103 | ngx_int_t ipc_alert_pid(ipc_t *ipc, ngx_pid_t pid, ngx_str_t *name, ngx_str_t *data); 104 | 105 | ngx_pid_t *ipc_get_process_pids(ipc_t *ipc, int *pid_count, ipc_ngx_process_type_t type); 106 | ngx_int_t *ipc_get_process_slots(ipc_t *ipc, int *slot_count, ipc_ngx_process_type_t type); 107 | ngx_pid_t *ipc_get_worker_pids(ipc_t *ipc, int *pid_count); 108 | ngx_int_t *ipc_get_worker_slots(ipc_t *ipc, int *pid_count); 109 | 110 | ngx_int_t ipc_alert_all_processes(ipc_t *ipc, ipc_ngx_process_type_t type, ngx_str_t *name, ngx_str_t *data); 111 | ngx_int_t ipc_alert_all_workers(ipc_t *ipc, ngx_str_t *name, ngx_str_t *data); //poor man's broadcast 112 | 113 | ipc_stats_t *ipc_get_stats(ipc_t *ipc); 114 | 115 | char *ipc_get_last_error(ipc_t *ipc); 116 | 117 | 118 | 119 | #endif //NGX_IPC_H 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Interprocess communication for lua_nginx_module and openresty. Send named alerts with string data between Nginx worker processes. 2 | 3 | Asynchronous, nonblocking, non-locking, and [*fast*](#speed)! 4 | 5 | ### History 6 | 7 | I wrote this as a quick hack to separate the [interprocess code](https://github.com/slact/nchan/tree/master/src/store/memory) out of [Nchan](https://github.com/slact/nchan) mostly on a flight back from [Nginx Conf 2016](https://www.nginx.com/nginxconf/2016). The completion of this module was generously sponsored by [ring.com](https://ring.com). Thanks guys! 8 | 9 | # API 10 | 11 | ```lua 12 | local ipc = require "ngx.ipc" 13 | ``` 14 | 15 | ### `ipc.send` 16 | Send alert to a worker process. 17 | ```lua 18 | ipc.send(destination_worker_pid, alert_name, alert_data) 19 | ``` 20 | 21 | Returns: 22 | - `true` on success 23 | - `nil, error_msg` if `alert_name` length is > 254, `alert_data` length is > 4G, `destination_worker_pid` is not a valid worker process. 24 | 25 | 26 | ### `ipc.broadcast` 27 | Broadcast alert to all workers (including sender). 28 | ```lua 29 | ipc.broadcast(alert_name, alert_data) 30 | ``` 31 | 32 | Returns: 33 | - `true` on success 34 | - `nil, error_msg` if `alert_name` length is > 254 or `alert_data` length is > 4G 35 | 36 | ### `ipc.receive` 37 | Register one or several alert handlers. 38 | Note that `receive` cannot be used in the `init_by_lua*` context. During startup, use `init_worker_by_lua*`. 39 | 40 | Register an alert handler: 41 | ```lua 42 | ipc.receive(alert_name, function(data) 43 | --ipc receiver function for all alerts with string name alert_name 44 | end) 45 | ``` 46 | Returns: 47 | - `true` 48 | 49 | Several alert names can be registered at once by passing a table: 50 | ```lua 51 | ipc.receive({ 52 | hello = function(data) 53 | --got a hello 54 | end, 55 | goodbye = function(data) 56 | --got a goodbye 57 | end 58 | }) 59 | ``` 60 | 61 | Deleting an alert handler: 62 | ```lua 63 | ipc.receive(ipc_alert_name, nil) 64 | ``` 65 | 66 | *Alerts received without a handler are discarded.* 67 | 68 | ### `ipc.reply` 69 | Reply to worker that sent an alert. Works only when in an alert receiver handler function. 70 | 71 | ```lua 72 | ipc.receive("hello", function(data) 73 | ipc.reply("hello-response", "hi, you said "..data) 74 | end) 75 | ``` 76 | 77 | Returns: 78 | - `true` 79 | 80 | Raises error if used outside of a `ipc.receive` handler. 81 | 82 | 83 | ### `ipc.sender` 84 | When receiving an alert, `ipc.sender` contains the sending worker"s process id. 85 | all other times, it is nil 86 | ```lua 87 | ipc.receive("hello", function(data) 88 | if ipc.sender == ngx.worker.pid() then 89 | --just said hello to myself 90 | end 91 | end) 92 | ``` 93 | 94 | # Example 95 | 96 | `nginx.conf` 97 | ```lua 98 | http { 99 | init_worker_by_lua_block { 100 | local ipc = require "ngx.ipc" 101 | ipc.receive("hello", function(data) 102 | ngx.log(ngx.ALERT, "sender" .. ipc.sender .. " says " .. data) 103 | 104 | ipc.reply("reply", "hello to you too. you said " .. data) 105 | 106 | end) 107 | 108 | ipc.receive("reply", function(data) 109 | ngx.log(ngx.ALERT, tostring(ipc.sender) .. " replied " .. data) 110 | end) 111 | } 112 | 113 | server { 114 | listen 80; 115 | 116 | location ~ /send/(\d+)/(.*)$ { 117 | set $dst_pid $1; 118 | set $data $2; 119 | content_by_lua_block { 120 | local ipc = require "ngx.ipc" 121 | local ok, err = ipc.send(ngx.var.dst_pid, "hello", ngx.var.data) 122 | if ok then 123 | ngx.say("Sent alert to pid " .. ngx.var.dst_pid); 124 | else 125 | ngx.status = 500 126 | ngx.say(err) 127 | end 128 | } 129 | } 130 | 131 | location ~ /broadcast/(.*)$ { 132 | set $data $1; 133 | content_by_lua_block { 134 | local ipc = require "ngx.ipc" 135 | ipc.broadcast("hello", ngx.var.data) 136 | } 137 | } 138 | 139 | } 140 | } 141 | 142 | ``` 143 | 144 | # How it works 145 | 146 | IPC alerts are split into 4K packets and delivered to workers via Unix pipes. On the receiving end, a persistent timer started with `ngx.timer.at` hangs around waiting to be manually triggered by the reading IPC event handler, and thes is re-added to wait for the next alert. A simple hack in concept, but a bit convoluted in implementation. 147 | 148 | # Speed 149 | 150 | It's pretty fast. On an i5-2500K (2 core, 4 thread) running Nginx with the Lua module built with Luajit, here are the results of my benchmarks: 151 | - 5 workers, 10b alerts: 220K alerts/sec 152 | - 5 workers, 10Kb alerts: 110K alerts/sec 153 | - 20 workers, 10b alerts: 220K alerts/sec 154 | - 20 workers, 10Kb alerts: 33K alerts/sec 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /dev/nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | DEVDIR=`pwd` 3 | SRCDIR=$(readlink -m $DEVDIR/../src) 4 | echo $DEVDIR $SRCDIR 5 | 6 | VALGRIND_OPT=( "--tool=memcheck" "--trace-children=yes" "--track-origins=yes" "--read-var-info=yes" ) 7 | 8 | VG_MEMCHECK_OPT=( "--leak-check=full" "--show-leak-kinds=all" "--leak-check-heuristics=all" "--keep-stacktraces=alloc-and-free" "--suppressions=${DEVDIR}/vg.supp" ) 9 | 10 | #expensive definedness checks (newish option) 11 | VALGRIND_OPT+=( "--expensive-definedness-checks=yes" ) 12 | 13 | #long stack traces 14 | VG_MEMCHECK_OPT+=("--num-callers=20") 15 | 16 | #generate suppresions 17 | #VG_MEMCHECK_OPT+=("--gen-suppressions=all") 18 | 19 | #track files 20 | #VG_MEMCHECK_OPT+=("--track-fds=yes") 21 | 22 | 23 | 24 | WORKERS=5 25 | NGINX_DAEMON="off" 26 | NGINX_CONF="" 27 | ACCESS_LOG="/dev/null" 28 | ERROR_LOG="stderr" 29 | ERRLOG_LEVEL="notice" 30 | TMPDIR="" 31 | MEM="32M" 32 | 33 | DEBUGGER_NAME="kdbg" 34 | DEBUGGER_CMD="dbus-run-session kdbg -p %s $SRCDIR/nginx" 35 | 36 | #DEBUGGER_NAME="nemiver" 37 | #DEBUGGER_CMD="nemiver --attach=%s $SRCDIR/nginx" 38 | 39 | 40 | _pkgdir="${DEVDIR}/nginx-nchan/pkg/nginx-nchan-dev" 41 | _dynamic_module="$_pkgdir/etc/nginx/modules/ngx_nchan_module.so" 42 | 43 | _cacheconf=" proxy_cache_path _CACHEDIR_ levels=1:2 keys_zone=cache:1m; \\n server {\\n listen 8007;\\n location / { \\n proxy_cache cache; \\n }\\n }\\n" 44 | 45 | NGINX_CONF_FILE="nginx.conf" 46 | 47 | for opt in $*; do 48 | if [[ "$opt" = <-> ]]; then 49 | WORKERS=$opt 50 | fi 51 | case $opt in 52 | leak|leakcheck|valgrind|memcheck) 53 | valgrind=1 54 | VALGRIND_OPT+=($VG_MEMCHECK_OPT);; 55 | debug-memcheck) 56 | valgrind=1 57 | VALGRIND_OPT+=($VG_MEMCHECK_OPT) 58 | VALGRIND_OPT+=( "--vgdb=yes" "--vgdb-error=1" ) 59 | #ATTACH_DDD=1 60 | ;; 61 | massif) 62 | VALGRIND_OPT=( "--tool=massif" "--heap=yes" "--stacks=yes" "--massif-out-file=massif-nginx-%p.out") 63 | valgrind=1 64 | ;; 65 | sanitize-undefined) 66 | FSANITIZE_UNDEFINED=1 67 | ;; 68 | callgrind|profile) 69 | VALGRIND_OPT=( "--tool=callgrind" "--collect-jumps=yes" "--collect-systime=yes" "--branch-sim=yes" "--cache-sim=yes" "--simulate-hwpref=yes" "--simulate-wb=yes" "--callgrind-out-file=callgrind-nginx-%p.out") 70 | valgrind=1;; 71 | helgrind) 72 | VALGRIND_OPT=( "--tool=helgrind" "--free-is-write=yes") 73 | valgrind=1 74 | ;; 75 | cachegrind) 76 | VALGRIND_OPT=( "--tool=cachegrind" ) 77 | valgrind=1;; 78 | alleyoop) 79 | alleyoop=1;; 80 | cache) 81 | CACHE=1;; 82 | access) 83 | ACCESS_LOG="/dev/stdout";; 84 | worker|one|single) 85 | WORKERS=1 86 | ;; 87 | debugmaster|debug-master) 88 | WORKERS=1 89 | debug_master=1 90 | NGINX_DAEMON="off" 91 | ;; 92 | devconf) 93 | NGINX_CONF_FILE="dev.conf" 94 | ;; 95 | debug) 96 | WORKERS=1 97 | NGINX_DAEMON="on" 98 | debugger=1 99 | ;; 100 | altport) 101 | ALTPORT=1 102 | ;; 103 | debuglog) 104 | ERRLOG_LEVEL="debug" 105 | ;; 106 | errorlog) 107 | ERROR_LOG="errors.log" 108 | rm ./errors.log 2>/dev/null 109 | ;; 110 | lomem|lowmem|small) 111 | MEM="5M";; 112 | himem|highmem|large) 113 | MEM="256M";; 114 | verylowmem|tiny) 115 | MEM="1M";; 116 | esac 117 | done 118 | 119 | NGINX_CONFIG=`pwd`/$NGINX_CONF_FILE 120 | NGINX_TEMP_CONFIG=`pwd`/.nginx.thisrun.conf 121 | NGINX_OPT=( -p `pwd`/ 122 | -c $NGINX_TEMP_CONFIG 123 | ) 124 | cp -fv $NGINX_CONFIG $NGINX_TEMP_CONFIG 125 | 126 | conf_replace(){ 127 | echo "$1 $2" 128 | sed "s|^\(\s*\)\($1\)\(\s\+\).*|\1\2\3$2;|g" $NGINX_TEMP_CONFIG -i 129 | } 130 | 131 | ulimit -c unlimited 132 | 133 | if [[ ! -z $NGINX_CONF ]]; then 134 | NGINX_OPT+=( -g "$NGINX_CONF" ) 135 | fi 136 | #echo $NGINX_CONF 137 | #echo $NGINX_OPT 138 | 139 | export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer 140 | export ASAN_OPTIONS=symbolize=1 141 | 142 | echo "nginx $NGINX_OPT" 143 | if [[ ! -z $ALTPORT ]]; then 144 | sed "s|^\(\s\+\)listen\(\s\+\)\(.*\)|\1listen\21\3|g" $NGINX_TEMP_CONFIG -i 145 | fi 146 | 147 | conf_replace "access_log" $ACCESS_LOG 148 | conf_replace "error_log" "$ERROR_LOG $ERRLOG_LEVEL" 149 | conf_replace "worker_processes" $WORKERS 150 | conf_replace "daemon" $NGINX_DAEMON 151 | conf_replace "working_directory" "\"$(pwd)\"" 152 | conf_replace "push_max_reserved_memory" "$MEM" 153 | if [[ ! -z $CACHE ]]; then 154 | sed "s|^\s*#cachetag.*|${_cacheconf}|g" $NGINX_TEMP_CONFIG -i 155 | tmpdir=`pwd`"/.tmp" 156 | mkdir $tmpdir 2>/dev/null 157 | sed "s|_CACHEDIR_|\"$tmpdir\"|g" $NGINX_TEMP_CONFIG -i 158 | fi 159 | 160 | if [[ -f "$_dynamic_module" ]]; then 161 | sed "s|^\s*#load_module.*|load_module \"${_dynamic_module}\";|g" $NGINX_TEMP_CONFIG -i 162 | fi 163 | 164 | debugger_pids=() 165 | 166 | TRAPINT() { 167 | if [[ $debugger == 1 ]]; then 168 | sudo kill $debugger_pids 169 | fi 170 | } 171 | 172 | attach_debugger() { 173 | master_pid=`cat /tmp/nchan-test-nginx.pid` 174 | while [[ -z $child_pids ]]; do 175 | child_pids=`pgrep -P $master_pid` 176 | sleep 0.1 177 | done 178 | while read -r line; do 179 | echo "attaching $1 to $line" 180 | sudo $(printf $2 $line) & 181 | debugger_pids+="$!" 182 | done <<< $child_pids 183 | echo "$1 at $debugger_pids" 184 | } 185 | 186 | attach_ddd_vgdb() { 187 | master_pid=$1 188 | echo "attaching DDD for vgdb to master process $master_pid" 189 | ddd --eval-command "set non-stop off" --eval-command "target remote | vgdb --pid=$master_pid" "$SRCDIR/nginx" 2>/dev/null & 190 | debugger_pids+="$!" 191 | sleep 1 192 | while [[ -z $child_pids ]]; do 193 | child_pids=`pgrep -P $master_pid` 194 | sleep 0.3 195 | done 196 | echo "child pids: $child_pids" 197 | 198 | while read -r line; do 199 | echo "attaching DDD for vgdb to $line" 200 | ddd --eval-command "set non-stop off" --eval-command "target remote | vgdb --pid=$line" "$SRCDIR/nginx" 2>/dev/null & 201 | debugger_pids+="$!" 202 | done <<< $child_pids 203 | echo "$1 at $debugger_pids" 204 | } 205 | 206 | if [[ $debugger == 1 ]]; then 207 | ./nginx $NGINX_OPT 208 | sleep 0.2 209 | attach_debugger "$DEBUGGER_NAME" "$DEBUGGER_CMD" 210 | wait $debugger_pids 211 | kill $master_pid 212 | elif [[ $debug_master == 1 ]]; then 213 | pushd $SRCDIR 214 | kdbg -a "$NGINX_OPT" "./nginx" 215 | popd 216 | elif [[ $valgrind == 1 ]]; then 217 | mkdir ./coredump 2>/dev/null 218 | pushd ./coredump >/dev/null 219 | if [[ $ATTACH_DDD == 1 ]]; then 220 | valgrind $VALGRIND_OPT ../nginx $NGINX_OPT & 221 | _master_pid=$! 222 | echo "nginx at $_master_pid" 223 | sleep 4 224 | attach_ddd_vgdb $_master_pid 225 | wait $debugger_pids 226 | kill $master_pid 227 | else 228 | echo valgrind $VALGRIND_OPT ../nginx $NGINX_OPT 229 | valgrind $VALGRIND_OPT ../nginx $NGINX_OPT 230 | fi 231 | popd >/dev/null 232 | elif [[ $alleyoop == 1 ]]; then 233 | alleyoop ./nginx $NGINX_OPT 234 | else 235 | ./nginx $NGINX_OPT & 236 | wait $! 237 | fi 238 | -------------------------------------------------------------------------------- /dev/rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | MY_PATH="`dirname \"$0\"`" 3 | MY_PATH="`( cd \"$MY_PATH\" && pwd )`" 4 | pkg_path=$MY_PATH/nginx-pkg 5 | _src_dir=${MY_PATH}/../src 6 | 7 | 8 | _clang="ccache clang -Qunused-arguments -fcolor-diagnostics" 9 | 10 | #clang_memcheck="-fsanitize=address,undefined -fno-omit-frame-pointer" 11 | clang_sanitize_memory="-use-gold-plugins -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -fsanitize-blacklist=bl.txt" 12 | clang_sanitize_addres="-fsanitize=address,undefined -fno-omit-frame-pointer" 13 | 14 | optimize_level=0; 15 | 16 | export WITH_HTTP_SSL=1 17 | export CONFIGURE_WITH_DEBUG=0 18 | _extra_config_opt=() 19 | 20 | export WITH_LUA_MODULE=1 21 | 22 | for opt in $*; do 23 | case $opt in 24 | clang) 25 | export CC=$_clang;; 26 | clang-sanitize|sanitize|sanitize-memory) 27 | export CC="CMAKE_LD=llvm-link $_clang -Xclang -cc1 $clang_sanitize_memory " 28 | export CLINKER=$clang 29 | ;; 30 | gcc-sanitize-undefined) 31 | export SANITIZE_UNDEFINED=1 32 | ;; 33 | sanitize-address) 34 | export CC="$_clang $clang_sanitize_addres";; 35 | gcc5) 36 | export CC=gcc-5;; 37 | gcc4|gcc47|gcc4.7) 38 | export CC=gcc-4.7;; 39 | nopool|no-pool|nop) 40 | export NO_POOL=1;; 41 | debug-pool|debugpool) 42 | export NGX_DEBUG_POOL=1;; 43 | dynamic) 44 | export DYNAMIC=1;; 45 | re|remake) 46 | export REMAKE="-B" 47 | export CONTINUE=1;; 48 | c|continue|cont) 49 | export CONTINUE=1;; 50 | noextract) 51 | export NO_EXTRACT_SOURCE=1;; 52 | nomake) 53 | export NO_MAKE=1;; 54 | nodebug) 55 | export NO_DEBUG=1;; 56 | echo_module) 57 | export WITH_NGX_ECHO_MODULE=1;; 58 | O0) 59 | optimize_level=0;; 60 | O1) 61 | optimize_level=1;; 62 | O2) 63 | optimize_level=2;; 64 | O3) 65 | optimize_level=3;; 66 | mudflap) 67 | export MUDFLAP=1 68 | export CC=gcc 69 | ;; 70 | stable|stableversion) 71 | export NGINX_STABLEVERSION=1;; 72 | legacyversion|legacy) 73 | export NGINX_LEGACYVERSION=1;; 74 | oldversion|old) 75 | export NGINX_OLDVERSION=1;; 76 | veryoldversion|veryold) 77 | export NGINX_VERYOLDVERSION=1;; 78 | version=*) 79 | export NGINX_CUSTOM_VERSION="${opt:8}";; 80 | release=*) 81 | RELEASE="${opt:8}";; 82 | slabpatch|slab) 83 | export NGX_SLAB_PATCH=1;; 84 | withdebug) 85 | export CONFIGURE_WITH_DEBUG=1;; 86 | clang-analyzer|analyzer|scan|analyze) 87 | export CC="clang" 88 | export CLANG_ANALYZER=$MY_PATH/clang-analyzer 89 | mkdir $CLANG_ANALYZER 2>/dev/null 90 | ;; 91 | nossl|no_ssl) 92 | export WITH_HTTP_SSL="" 93 | ;; 94 | stub_status) 95 | export WITH_STUB_STATUS_MODULE=1 96 | ;; 97 | default_prefix) 98 | export DEFAULT_PREFIX=1;; 99 | prefix=*) 100 | export CUSTOM_PREFIX="${opt:7}";; 101 | openresty) 102 | export EXPLICIT_CFLAGS=1 103 | export WITH_LUA_MODULE="" 104 | export USE_OPENRESTY=1 105 | ;; 106 | openresty=*) 107 | export OPENRESTY_CUSTOM_VERSION="${opt:10}" 108 | export EXPLICIT_CFLAGS=1 109 | export WITH_LUA_MODULE="" 110 | export USE_OPENRESTY=1 111 | ;; 112 | lua_stream_module) 113 | export WITH_LUA_STREAM_MODULE=1 114 | export WITH_STREAM_MODULE=1 115 | ;; 116 | lua_module) 117 | export WITH_LUA_MODULE=1 118 | ;; 119 | luajit) 120 | export LUAJIT_INC=/usr/include/luajit-2.1 121 | export LUAJIT_LIB=/usr/lib/ 122 | ;; 123 | --*) 124 | _extra_config_opt+=( "$opt" ) 125 | esac 126 | done 127 | 128 | export NO_WITH_DEBUG=$NO_WITH_DEBUG; 129 | export EXTRA_CONFIG_OPT=`echo $_extra_config_opt` 130 | 131 | _build_nginx() { 132 | 133 | if type "makepkg" > /dev/null; then 134 | if [[ $CONTINUE == 1 ]] || [[ $NO_EXTRACT_SOURCE == 1 ]]; then 135 | makepkg -f -e 136 | else 137 | makepkg -f 138 | fi 139 | return 0 140 | fi 141 | 142 | export NO_MAKEPKG=1 143 | export NO_NGINX_USER=1 144 | export NO_GCC_COLOR=1 145 | export startdir="$(pwd)" 146 | export EXPLICIT_CFLAGS=1 147 | 148 | rm "${startdir}/pkg/" -Rf 149 | srcdir="${startdir}/src" 150 | 151 | source ./PKGBUILD 152 | 153 | pkgdir="${startdir}/pkg/${pkgname}" 154 | mkdir -p "$srcdir" "$pkgdir" 155 | 156 | echo $_source 157 | echo $_no_pool_patch_source 158 | 159 | wget --no-clobber $_source 160 | wget --no-clobber $_no_pool_patch_source 161 | wget --no-clobber $_lua_nginx_module_url 162 | wget --no-clobber $_lua_upstream_nginx_module_url 163 | 164 | if [[ -n $WITH_LUA_STREAM_MODULE ]]; then 165 | wget --no-clobber $_lua_stream_module_src 166 | fi 167 | 168 | if [[ -z $NO_EXTRACT_SOURCE ]]; then 169 | pushd src 170 | _nginx_src_file="${_source##*/}" 171 | echo $_nginx_src_file 172 | tar xf "../${_nginx_src_file}" 173 | cp "../${_no_pool_patch_source##*/}" ./ 174 | if [[ ! -d ngx_debug_pool ]]; then 175 | git clone "$_ngx_debug_pool_url" 176 | else 177 | pushd ngx_debug_pool 178 | git pull 179 | popd 180 | fi 181 | 182 | tar xf "../v${_lua_nginx_module_ver}.tar.gz" 183 | tar xf "../v${_lua_upstream_nginx_module_ver}.tar.gz" 184 | 185 | if [[ -n $WITH_LUA_STREAM_MODULE ]]; then 186 | tar xf "../v${_lua_stream_module_ver}.tar.gz" 187 | fi 188 | popd 189 | fi 190 | 191 | rm "${srcdir}/nginx" 192 | ln -sf "${srcdir}/${_extracted_dir}" "${srcdir}/nginx" 193 | 194 | build 195 | 196 | pushd "${srcdir}/nginx" 197 | ls -alh 198 | make DESTDIR="$pkgdir/" install 199 | popd 200 | } 201 | 202 | 203 | export OPTIMIZE_LEVEL=$optimize_level 204 | 205 | if [[ -z $NO_MAKE ]]; then 206 | 207 | #./gen_config_commands.rb 208 | #if ! [ $? -eq 0 ]; then; 209 | # echo "failed generating nginx directives"; 210 | # exit 1 211 | #fi 212 | 213 | #if [[ -n $RELEASE ]]; then 214 | # ./redocument.rb --release $RELEASE 215 | #else 216 | # ./redocument.rb 217 | #fi 218 | #if ! [ $? -eq 0 ]; then; 219 | # echo "failed generating documentation"; 220 | # exit 1 221 | #fi 222 | 223 | bundle exec hsss --format split \ 224 | --no-hashes --no-name --no-each --no-count --no-static \ 225 | --struct ngx_ipc_lua_scripts_t \ 226 | --scripts ngx_ipc_lua_scripts \ 227 | "${_src_dir}"/lua/* > "${_src_dir}/ngx_lua_ipc_scripts.h" 228 | if ! [ $? -eq 0 ]; then; 229 | echo "failed generating redis lua scripts"; 230 | exit 1 231 | fi 232 | pushd $pkg_path >/dev/null 233 | 234 | _build_nginx 235 | ln -sf "${pkg_path}"/pkg/*/usr/bin/nginx "${MY_PATH}/nginx" > /dev/null 236 | ln -sf "${MY_PATH}/nginx" "${_src_dir}/nginx" > /dev/null 237 | rm "${_src_dir}/nginx-source" >/dev/null 238 | ln -sf "${pkg_path}/src/nginx/src" "${_src_dir}/nginx-source" > /dev/null 239 | 240 | popd >/dev/null 241 | fi 242 | if ! [[ -z $CLANG_ANALYZER ]]; then 243 | pushd $CLANG_ANALYZER >/dev/null 244 | latest_scan=`ls -c |head -n1` 245 | echo "run 'scan-view ${CLANG_ANALYZER}/${latest_scan}' for static analysis." 246 | scan-view $latest_scan 2>/dev/null 247 | popd >/dev/null 248 | fi 249 | 250 | 251 | -------------------------------------------------------------------------------- /dev/nginx.conf: -------------------------------------------------------------------------------- 1 | #!/bin/sh #good enough highlighting 2 | #load_module "nginx-nchan/pkg/nginx-nchan-dev/etc/nginx/modules/ngx_nchan_module.so"; 3 | #user nobody; 4 | worker_processes 20; 5 | working_directory /tmp; 6 | 7 | worker_rlimit_core 1024M; 8 | worker_rlimit_nofile 100000; 9 | #debug_points stop; 10 | 11 | error_log /dev/stderr; 12 | 13 | pid /tmp/nchan-test-nginx.pid; 14 | daemon off; 15 | 16 | 17 | events { 18 | worker_connections 50000; 19 | accept_mutex on; 20 | } 21 | 22 | http { 23 | access_log /dev/stdout; 24 | # access_log /dev/stdout; 25 | default_type application/octet-stream; 26 | client_body_temp_path /tmp/ 1 2; 27 | sendfile on; 28 | keepalive_timeout 65; 29 | client_max_body_size 100m; 30 | #client_body_in_file_only clean; 31 | #client_body_buffer_size 32K; 32 | 33 | #cachetag 34 | 35 | init_worker_by_lua_block { 36 | local ipc = require "ngx.ipc" 37 | 38 | ipc.receive({ 39 | hello = function(data) 40 | ngx.log(ngx.ALERT, ("%d says %s"):format(ipc.sender, data)) 41 | ipc.reply("reply", ("kthx (you said %s)"):format(data)) 42 | end, 43 | check_long = function(data) 44 | ngx.log(ngx.ALERT, ("%d got alert of length %d"):format(ipc.sender, #data)) 45 | 46 | local i = 1 47 | for n in data:gmatch("%d+") do 48 | if tonumber(n) ~= i then 49 | ngx.log(ngx.ERR, ("long alert doesn't check out at number %d (saw %s)"):format(i, n)) 50 | return 51 | end 52 | i = i + 1 53 | end 54 | 55 | ngx.log(ngx.ALERT, "long alert checks out") 56 | 57 | ipc.reply("reply", ("kthx")) 58 | end, 59 | reply = function(data) 60 | ngx.log(ngx.ALERT, ("%d has replied %s"):format(ipc.sender, data)) 61 | end, 62 | benchmark_ping = function(data) 63 | if ipc.sender ~= ngx.worker.pid() then 64 | ipc.reply("benchmark_pong", data) 65 | end 66 | end, 67 | largemsg = function(data) 68 | local sz = #data 69 | print("got large msg from " .. ipc.sender .. ", length: " .. sz .. "bytes.") 70 | end 71 | }) 72 | } 73 | 74 | server { 75 | listen 8082; 76 | listen 8085 http2; 77 | #listen 18010; 78 | # root ./; 79 | 80 | location ~ /send/(\d+)/(.*)$ { 81 | set $dst_pid $1; 82 | set $data $2; 83 | content_by_lua_block { 84 | local ipc = require "ngx.ipc" 85 | local ok, err = ipc.send(ngx.var.dst_pid, "hello", ngx.var.data) 86 | if ok then 87 | ngx.say("Sent alert to pid " .. ngx.var.dst_pid); 88 | else 89 | ngx.status = 500 90 | ngx.say(err) 91 | end 92 | } 93 | } 94 | 95 | location ~ /send/any/(.*)$ { 96 | set $data $1; 97 | content_by_lua_block { 98 | local ipc = require "ngx.ipc" 99 | local dst_pid = ipc.get_other_worker_pids()[1] 100 | local ok, err = ipc.send(dst_pid, "hello", ngx.var.data) 101 | if ok then 102 | ngx.say("Sent alert to pid " .. dst_pid); 103 | else 104 | ngx.status = 500 105 | ngx.say(err) 106 | end 107 | } 108 | } 109 | 110 | location ~ /send/flood/(\d+)$ { 111 | set $n $1; 112 | content_by_lua_block { 113 | local ipc = require "ngx.ipc" 114 | local dst_pid = ipc.get_other_worker_pids()[1] 115 | ipc.send(dst_pid, "flood_test", "start") 116 | for i=0, tonumber(ngx.var.n) do 117 | ipc.send(dst_pid, "flood_test", tostring(i)) 118 | end 119 | if ok then 120 | ngx.say("Sent " .. ngx.var.n .. " alerts to pid " .. dst_pid) 121 | else 122 | ngx.status = 500 123 | ngx.say(err) 124 | end 125 | } 126 | } 127 | 128 | location ~ /send/long/(\d+)$ { 129 | set $n $1; 130 | content_by_lua_block { 131 | local n = tonumber(ngx.var.n) 132 | local ipc = require "ngx.ipc" 133 | local dst_pid = ipc.get_other_worker_pids()[1] 134 | local t = {} 135 | for i=1,n do 136 | t[i]=tostring(i) 137 | end 138 | local str = table.concat(t, " ") 139 | 140 | local ok, err = ipc.send(dst_pid, "check_long", str) 141 | if ok then 142 | ngx.say("Sent alert to pid " .. dst_pid); 143 | else 144 | ngx.status = 500 145 | ngx.say(err) 146 | end 147 | } 148 | } 149 | 150 | location ~ /pids$ { 151 | content_by_lua_block { 152 | local ipc = require "ngx.ipc" 153 | print(table.concat(ipc.get_other_worker_pids(), " ")) 154 | } 155 | } 156 | 157 | location ~ /large/(\d+)$ { 158 | set $len $1; 159 | content_by_lua_block { 160 | local ipc = require "ngx.ipc" 161 | local len = ngx.var.len 162 | local count = 0 163 | local enough = 1 164 | ipc.receive("largemsg-pong", function(data) 165 | count = count + 1 166 | print("got largemsg-pong") 167 | if count < enough then 168 | ipc.reply("largemsg", data) 169 | else 170 | print("enough!!") 171 | end 172 | end) 173 | local dst_pid = ipc.get_other_worker_pids())[1] 174 | local ok, err = ipc.send((dst_pid, "largemsg", ("ab"):rep(len/2)) 175 | if ok then 176 | ngx.say("Sent alert (size " .. len .. ") to pid " .. dst_pid); 177 | else 178 | ngx.status = 500 179 | ngx.say(err) 180 | end 181 | 182 | } 183 | } 184 | 185 | location = /broadcast_bench { 186 | content_by_lua_block { 187 | local ipc = require "ngx.ipc" 188 | local count = 0 189 | local enough = 1000000 190 | local extra_bytes = 0 191 | ipc.receive("benchmark_pong", function(data) 192 | count = count + 1 193 | if count >= enough then 194 | ngx.update_time() 195 | print(("got %d responses after %f sec (now: %f)"):format(count, ngx.now() -tonumber(data:match("%S+")), ngx.now())) 196 | else 197 | --print("count is" .. count) 198 | ipc.reply("benchmark_ping", data) 199 | end 200 | end) 201 | ngx.update_time() 202 | local msg = ("%f %s"):format(ngx.now(), ("b"):rep(extra_bytes)); 203 | print("start at " .. ngx.now() .. " len: " .. #msg) 204 | 205 | local ok, err = ipc.broadcast("benchmark_ping", msg) 206 | if ok then 207 | ngx.say("Benchmark begin."); 208 | else 209 | ngx.status = 500 210 | ngx.say(err) 211 | end 212 | } 213 | } 214 | 215 | location = /broadcast_debug { 216 | content_by_lua_block { 217 | local ipc = require "ngx.ipc" 218 | local count = 0 219 | local enough = 3 220 | local len = 70000 221 | ipc.receive("bdb-pong", function(data) 222 | count = count + 1 223 | if count >= enough then 224 | print("it's over") 225 | else 226 | print("count is " .. count) 227 | ipc.reply("benchmark_ping", data) 228 | end 229 | end) 230 | local msg = ("%f %s"):format(ngx.now(), ("b"):rep(extra_bytes)); 231 | print("start at " .. ngx.now() .. " len: " .. #msg) 232 | 233 | local ok, err = ipc.broadcast("bdb-ping", msg) 234 | if ok then 235 | ngx.say("Benchmark begin."); 236 | else 237 | ngx.status = 400 238 | ngx.say(err) 239 | end 240 | 241 | } 242 | } 243 | 244 | location ~ /broadcast/(.*)$ { 245 | set $data $1; 246 | content_by_lua_block { 247 | local ipc = require "ngx.ipc" 248 | local ok, err = ipc.broadcast("hello", ngx.var.data) 249 | if ok then 250 | ngx.say("Benchmark begin."); 251 | else 252 | ngx.status = 500 253 | ngx.say(err) 254 | end 255 | } 256 | } 257 | 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /dev/memparse.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/luajit 2 | local lpty = require "lpty" 3 | 4 | local lines_tailed=100000 5 | local filename="errors.log" 6 | local follow=false 7 | 8 | local write=io.write 9 | 10 | local shm, size_by_label, count_by_label, weird_log 11 | function init() 12 | print "initialize" 13 | shm, size_by_label, count_by_label, weird_log={}, {}, {}, {} 14 | memmap={} 15 | end 16 | 17 | function err(str, ...) 18 | str = str or "Unknown error occurred" 19 | io.stderr:write(str:format(...)) 20 | io.stderr:write("\n") 21 | end 22 | function printf(str, ...) 23 | print(str:format(...)) 24 | end 25 | 26 | 27 | local poolsize=0 28 | local poolstart=0 29 | local memmap={} 30 | local membuckets=1024 31 | local bucketsize=0 32 | 33 | local resolve_weird_log; do 34 | local weirdlog={} 35 | resolve_weird_log=function(line, is_weird) 36 | local prev = weirdlog[1] 37 | if prev and prev.ptr == line.ptr and prev.t == line.t and prev.pid ~= line.pid then 38 | if prev.action == "free" and line.action == "alloc" then 39 | --oh, a free happened in a different worker and at the same time? 40 | --the log was probably written out-of-sequence 41 | table.remove(weirdlog, 1) 42 | alloc_raw(line.ptr, line.size, line.lbl, line.t, line.pid) 43 | free_raw(prev.ptr, prev.t, prev.pid) 44 | printf("resolved weird free/alloc at %s", line.ptr) 45 | return false 46 | elseif prev.action == "alloc" and line.action == "free" then 47 | --oh, an alloc happened in a different worker and at the same time? 48 | --the log was probably written out-of-sequence 49 | table.remove(weirdlog, 1) 50 | free_raw(line.ptr, line.t, line.pid) 51 | alloc_raw(prev.ptr, prev.size, prev.lbl, prev.t, prev.pid) 52 | printf("resolved weird alloc/free at %s", line.ptr) 53 | return false 54 | end 55 | end 56 | if is_weird then 57 | table.insert(weirdlog, 1, line) 58 | return false 59 | else 60 | return true 61 | end 62 | end 63 | end 64 | 65 | local memmap_add 66 | do 67 | local bucketsize=0 68 | memmap_add= function(starthex, size, val) 69 | if not poolsize or not poolstart then return err("poolsize or poolstart not known") end 70 | val = val or 1 71 | local start=tonumber(starthex, 16) 72 | if not start then return err("starthex was not a hex number") end 73 | --start should be relative to pool start 74 | start=start-poolstart 75 | 76 | if not size then 77 | if shm[starthex] then 78 | size=shm[starthex].size 79 | else 80 | err("shm[%s] is nil", starthex) 81 | end 82 | end 83 | 84 | local bstart = math.floor(start/poolsize * membuckets) 85 | local bend = math.floor((start+size)/poolsize * membuckets) 86 | if bstart == math.huge then 87 | return err("alloc before shm init") 88 | end 89 | for i=bstart, bend do 90 | memmap[i]=(memmap[i] or 0)+val 91 | if memmap[i]<0 then 92 | err("negative memmap at bucket %s", i) 93 | memmap[i]=0 94 | end 95 | end 96 | end 97 | end 98 | 99 | function alloc(ptr, size, label, time, pid) 100 | size=tonumber(size) 101 | local looks_weird=false 102 | if shm[ptr] ~= nil then 103 | err("BAD ALLOC AT ptr %s pid %i time %s", ptr, pid, time) 104 | looks_weird = true 105 | end 106 | if resolve_weird_log({action="alloc", ptr=ptr, size=size, lbl=label, t=time, pid=pid}, looks_weird) then 107 | alloc_raw(ptr, size, label, time, pid) 108 | end 109 | end 110 | function alloc_raw(ptr, size, label, time, pid) 111 | shm[ptr]={size=size, label=label, time=time} 112 | size_by_label[label]=(size_by_label[label] or 0) + size 113 | count_by_label[label]=(count_by_label[label] or 0) + 1 114 | memmap_add(ptr, size) 115 | end 116 | 117 | function free(ptr, time, pid) 118 | local looks_weird=false 119 | local ref=shm[ptr] 120 | if ref == nil then 121 | err ("DOUBLE FREE AT ptr %s pid %i time %s", ptr, pid, time) 122 | looks_weird = true 123 | end 124 | if resolve_weird_log({action="free", ptr=ptr, size=nil, lbl=nil, t=time, pid=pid}, looks_weird) then 125 | free_raw(ptr, time, pid) 126 | end 127 | end 128 | function free_raw(ptr, time, pid) 129 | local ref=shm[ptr] 130 | if ref==nil then 131 | err("executed double free on ptr %s pid %i time %s", ptr, pid, time) 132 | return 133 | end 134 | memmap_add(ptr, nil, -1) 135 | size_by_label[ref.label]=size_by_label[ref.label]-ref.size 136 | count_by_label[ref.label]=(count_by_label[ref.label] or 0) - 1 137 | shm[ptr]=nil 138 | end 139 | 140 | function formatsize(bytes) 141 | if bytes<1024 then 142 | return string.format("%ib", bytes) 143 | elseif bytes > 1024 and bytes < 1048576 then 144 | return string.format("%.3gKb", bytes/1024) 145 | else 146 | return string.format("%.3gMb", bytes/1048576) 147 | end 148 | end 149 | 150 | local alphabet={1,2,3,4,5,6,7,8,9,"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"} 151 | function summary() 152 | local compare=function(a,b) 153 | return a[2] > b[2] 154 | end 155 | local total=0; 156 | local resort={} 157 | for k,v in pairs(size_by_label) do table.insert(resort, {k, v}) end 158 | table.sort(resort, compare) 159 | for k,v in ipairs(resort) do 160 | printf("%-40s %-10s %i", v[1], formatsize(v[2]), count_by_label[v[1]]) 161 | total = total + v[2] 162 | end 163 | print (" -------- ") 164 | printf("%-40s %s", "total", formatsize(total)) 165 | 166 | --memory map 167 | local n 168 | for i=0,membuckets do 169 | n=memmap[i] 170 | if n==0 or n == nil then 171 | write("-") 172 | elseif n<#alphabet then 173 | write(alphabet[n] or "?") 174 | else 175 | write("#") 176 | end 177 | end 178 | print "" 179 | end 180 | 181 | 182 | function parse_line(str) 183 | local out_of_memory 184 | local time,errlevel, pid, msg=str:match("(%d+/%d+/%d+%s+%d+:%d+:%d+)%s+%[(%w+)%]%s+(%d+)#%d+:%s+(.+)") 185 | if msg ~= nil then 186 | local ptr, size, label, start 187 | 188 | if errlevel == "crit" then 189 | print("CRITICAL:", time, pid, msg) 190 | if msg == "ngx_slab_alloc() failed: no memory" then 191 | return true 192 | end 193 | end 194 | ptr, size, label = msg:match("shpool alloc addr (%w+) size (%d+) label (.+)") 195 | if ptr then 196 | alloc(ptr, size, label, time, pid) 197 | return ptr 198 | end 199 | 200 | ptr = msg:match("shpool free addr (%w+)") 201 | if ptr then 202 | free(ptr, time, pid) 203 | return ptr 204 | end 205 | 206 | start, size = msg:match("nchan_shpool start (%w+) size (%d+)") 207 | if size and start then 208 | init() 209 | poolsize=tonumber(size) 210 | poolstart = tonumber(start, 16) 211 | printf("shm start %s size %s", start, formatsize(poolsize)) 212 | end 213 | end 214 | end 215 | 216 | 217 | 218 | ---------------------------------------------------------------- 219 | --NO FUNCTIONS DEFINED BELOW THIS LINE PLEASE (except lambdas)-- 220 | 221 | --handle arguments 222 | if arg[1] == "tail" or arg[1] == "follow" then 223 | follow=true 224 | elseif arg[1] ~= nil then 225 | filename=arg[1] 226 | end 227 | 228 | 229 | init() 230 | 231 | 232 | if follow then 233 | local lasttime, now=os.time(), nil 234 | print "follow errors.log" 235 | 236 | pty = lpty.new() 237 | pty:startproc('tail', ("--lines=%s"):format(lines_tailed), "-F", filename) 238 | 239 | while true do 240 | line = pty:readline(false, 1) 241 | now=os.time() 242 | if not line then 243 | --nothing 244 | elseif line:match('truncated') or 245 | line:match(("‘%s’ has become inaccessible: No such file or directory"):format(filename)) then 246 | --reset 247 | init() 248 | else 249 | parse_line(line) 250 | end 251 | 252 | if now - lasttime >= 1 then 253 | summary() 254 | lasttime=now 255 | end 256 | end 257 | else 258 | printf("open %s", filename) 259 | local f = io.open(filename, "r") 260 | for line in f:lines() do 261 | if parse_line(line) == true then 262 | summary() 263 | end 264 | end 265 | f:close() 266 | end 267 | 268 | 269 | summary() 270 | -------------------------------------------------------------------------------- /src/ngx_lua_ipc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "ngx_http_lua_api.h" 5 | 6 | #include "ngx_lua_ipc_scripts.h" 7 | 8 | #include 9 | 10 | //#define DEBUG_ON 11 | 12 | #define LOAD_SCRIPTS_AS_NAMED_CHUNKS 13 | 14 | #ifdef DEBUG_ON 15 | #define DBG(fmt, args...) ngx_log_error(NGX_LOG_WARN, ngx_cycle->log, 0, "IPC:" fmt, ##args) 16 | #else 17 | #define DBG(fmt, args...) 18 | #endif 19 | #define ERR(fmt, args...) ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "IPC:" fmt, ##args) 20 | 21 | #ifndef container_of 22 | #define container_of(ptr, type, member) ({ \ 23 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 24 | (type *)( (char *)__mptr - offsetof(type,member) );}) 25 | #endif 26 | 27 | 28 | #ifdef LOAD_SCRIPTS_AS_NAMED_CHUNKS 29 | #define ngx_lua_ipc_loadscript(lua_state, name) \ 30 | luaL_loadbuffer(lua_state, ngx_ipc_lua_scripts.name, strlen(ngx_ipc_lua_scripts.name), #name); \ 31 | lua_call(lua_state, 0, LUA_MULTRET) 32 | #else 33 | #define ngx_lua_ipc_loadscript(lua_state, name) \ 34 | luaL_dostring(lua_state, ngx_ipc_lua_scripts.name) 35 | #endif 36 | 37 | static ipc_t *ipc = NULL; 38 | 39 | // lua for the ipc alert handlers will be run from this timer's context 40 | static ngx_event_t *hacktimer = NULL; 41 | static int running_hacked_timer_handler = 0; 42 | 43 | static int alert_available = 0; 44 | static lua_ipc_alert_t last_alert; 45 | 46 | static void ngx_lua_ipc_alert_handler(ngx_pid_t sender_pid, ngx_int_t sender, ngx_str_t *name, ngx_str_t *data); 47 | 48 | static int push_ipc_return_value(lua_State *L, int success) { 49 | if(success) { 50 | lua_pushboolean(L, 1); 51 | return 1; 52 | } 53 | else { 54 | lua_pushnil(L); 55 | lua_pushstring (L, ipc_get_last_error(ipc)); 56 | return 2; 57 | } 58 | } 59 | 60 | ngx_int_t luaL_checklstring_as_ngx_str(lua_State *L, int n, ngx_str_t *str) { 61 | size_t data_sz; 62 | const char *data = luaL_checklstring(L, n, &data_sz); 63 | 64 | str->data = (u_char *)data; 65 | str->len = data_sz; 66 | return NGX_OK; 67 | } 68 | 69 | static ngx_int_t ngx_lua_ipc_get_alert_args(lua_State *L, int stack_offset, ngx_str_t *name, ngx_str_t *data) { 70 | luaL_checklstring_as_ngx_str(L, 1+stack_offset, name); 71 | if(lua_gettop(L) >= 2+stack_offset) { 72 | luaL_checklstring_as_ngx_str(L, 2+stack_offset, data); 73 | } 74 | else { 75 | data->len=0; 76 | data->data=NULL; 77 | } 78 | 79 | return NGX_OK; 80 | } 81 | 82 | static int ngx_lua_ipc_hacktimer_get_last_alert(lua_State *L) { 83 | if(alert_available) { 84 | lua_pushinteger(L, last_alert.sender_slot); 85 | lua_pushinteger(L, last_alert.sender_pid); 86 | lua_pushlstring (L, (const char *)last_alert.name->data, last_alert.name->len); 87 | lua_pushlstring (L, (const char *)last_alert.data->data, last_alert.data->len); 88 | alert_available = 0; 89 | return 4; 90 | } 91 | else { 92 | lua_pushnil(L); 93 | return 1; 94 | } 95 | } 96 | 97 | static int ngx_lua_ipc_hacktimer_run_handler(lua_State *L) { 98 | luaL_checktype (L, 1, LUA_TFUNCTION); //handler 99 | luaL_checktype (L, 2, LUA_TSTRING); //event name 100 | luaL_checktype (L, 3, LUA_TSTRING); //event data 101 | luaL_checktype (L, 4, LUA_TBOOLEAN); //should name be passed to handler as first argument? 102 | 103 | int pass_name = lua_toboolean(L, 4); 104 | 105 | hacktimer = NULL; //it's about to be deleted (and a new one added), might as well clear it right now. 106 | running_hacked_timer_handler = 1; 107 | if(!pass_name) { 108 | lua_pushvalue(L, 1); //callback function 109 | lua_pushvalue(L, 3); //event data 110 | lua_call(L, 1, 0); 111 | } 112 | else { 113 | lua_pushvalue(L, 1); //callback function 114 | lua_pushvalue(L, 2); //event name 115 | lua_pushvalue(L, 3); //event data 116 | lua_call(L, 2, 0); 117 | } 118 | running_hacked_timer_handler = 0; 119 | return 0; 120 | } 121 | 122 | static ngx_event_t *ngx_lua_ipc_get_hacktimer(ngx_msec_t magic_key) { 123 | 124 | ngx_event_t *ev; 125 | ngx_rbtree_node_t **p, *sentinel=ngx_event_timer_rbtree.sentinel, *temp=ngx_event_timer_rbtree.root; 126 | if(temp == sentinel) { 127 | return NULL; 128 | } 129 | while(1) { 130 | p = ((ngx_rbtree_key_int_t) (magic_key - temp->key) < 0) ? &temp->left : &temp->right; 131 | if (*p == sentinel) { 132 | break; 133 | } 134 | temp = *p; 135 | } 136 | 137 | if(temp != sentinel && temp->key == magic_key) { 138 | ev = container_of(temp, ngx_event_t, timer); 139 | return ev; 140 | } 141 | else { 142 | return NULL; 143 | } 144 | } 145 | 146 | static int ngx_lua_ipc_hacktimer_unique_timeout(lua_State *L, uint32_t *unique_timeout, ngx_msec_t *unique_timeout_key) { 147 | uint32_t i; 148 | ngx_msec_t timeout_msec, timeout_key; 149 | for(i=4596313; i != 0; i++) { 150 | lua_pushinteger(L, i); 151 | timeout_msec = (ngx_msec_t)(luaL_checknumber(L, -1) * 1000); 152 | //generate the timeout just like the lua call would 153 | lua_pop(L, 1); 154 | timeout_key = timeout_msec + ngx_current_msec; 155 | if(ngx_lua_ipc_get_hacktimer(timeout_key) == NULL) { 156 | //no timer exists with this timeout 157 | *unique_timeout = i; 158 | *unique_timeout_key = timeout_key; 159 | return 1; 160 | } 161 | 162 | DBG("timer with timeout %i exists, try again", timeout_msec); 163 | } 164 | return 0; 165 | } 166 | 167 | static int ngx_lua_ipc_hacktimer_add_and_hack(lua_State *L) { 168 | uint32_t unique_timeout; 169 | ngx_msec_t unique_timeout_key; 170 | ngx_event_t *ev; 171 | 172 | if(ngx_quit || ngx_exiting) { 173 | //do nothing 174 | return 0; 175 | } 176 | 177 | if(!ngx_event_timer_rbtree.sentinel) { //tree not ready yet 178 | luaL_error(L, "Can't register receive handlers in init_by_lua, too early in Nginx start. Try init_worker_by_lua."); 179 | } 180 | 181 | DBG("ngx_lua_ipc_hacktimer_add_and_hack"); 182 | //assert(hacktimer == NULL); 183 | 184 | luaL_checktype (L, 1, LUA_TFUNCTION); 185 | 186 | lua_getglobal(L, "ngx"); 187 | lua_getfield(L, -1, "timer"); 188 | lua_getfield(L, -1, "at"); 189 | 190 | if(!ngx_lua_ipc_hacktimer_unique_timeout(L, &unique_timeout, &unique_timeout_key)) { 191 | ERR("couldn't find unique timeout. hack failed."); 192 | return 0; 193 | } 194 | 195 | lua_pushinteger(L, unique_timeout); 196 | lua_pushvalue(L, 1); //callback function 197 | lua_call(L, 2, 0); 198 | 199 | //now find that crazy timer 200 | ev = ngx_lua_ipc_get_hacktimer(unique_timeout_key); 201 | assert(ev); 202 | 203 | ngx_del_timer(ev); 204 | ngx_add_timer(ev, 10000000); 205 | DBG("set hacked timer %p (prev %p)", ev, hacktimer); 206 | hacktimer = ev; 207 | 208 | return 0; 209 | } 210 | 211 | static int ngx_lua_ipc_send_alert(lua_State *L) { 212 | int target_worker_pid = luaL_checknumber(L, 1); 213 | ngx_int_t rc; 214 | ngx_str_t name, data; 215 | ngx_lua_ipc_get_alert_args(L, 1, &name, &data); 216 | 217 | rc = ipc_alert_pid(ipc, target_worker_pid, &name, &data); 218 | 219 | return push_ipc_return_value(L, rc == NGX_OK); 220 | } 221 | 222 | static int ngx_lua_ipc_broadcast_alert(lua_State * L) { 223 | ngx_str_t name, data; 224 | ngx_int_t rc; 225 | ngx_lua_ipc_get_alert_args(L, 0, &name, &data); 226 | 227 | rc = ipc_alert_all_workers(ipc, &name, &data); 228 | 229 | return push_ipc_return_value(L, rc == NGX_OK); 230 | } 231 | 232 | static void ngx_lua_ipc_alert_handler(ngx_pid_t sender_pid, ngx_int_t sender_slot, ngx_str_t *name, ngx_str_t *data) { 233 | int prev_alert_available = alert_available; 234 | if(!hacktimer && !running_hacked_timer_handler) { 235 | //no alert handlers here 236 | return; 237 | } 238 | 239 | last_alert.sender_slot = sender_slot; 240 | last_alert.sender_pid = sender_pid; 241 | 242 | last_alert.name = name; 243 | last_alert.data = data; 244 | 245 | alert_available = 1; 246 | //listener timer now!! 247 | if(hacktimer && hacktimer->timer.key > ngx_current_msec) { 248 | DBG("run hacked timer right now: %p", hacktimer); 249 | ngx_del_timer(hacktimer); 250 | hacktimer->handler(hacktimer); 251 | //ngx_add_timer(hacktimer, 0); //the slow way to do it -- run it next cycle 252 | } 253 | else if(!hacktimer) { 254 | DBG("timer handler running right now"); 255 | if(prev_alert_available) { 256 | ERR("missed alert sent from %i to %i", last_alert.sender_pid, ngx_pid); 257 | } 258 | assert(running_hacked_timer_handler == 1); 259 | } 260 | else { 261 | DBG("hacked timer %p already set to run on next cycle", hacktimer); 262 | } 263 | return; 264 | 265 | } 266 | 267 | static int ngx_lua_ipc_get_other_worker_pids(lua_State *L) { 268 | int i, ti=1, n; 269 | ngx_pid_t *pids; 270 | 271 | pids = ipc_get_worker_pids(ipc, &n); 272 | 273 | lua_createtable(L, n-1, 0); 274 | 275 | for(i=0; itrack_stats = 1; 332 | ipc_set_alert_handler(ipc, ngx_lua_ipc_alert_handler); 333 | 334 | return NGX_OK; 335 | } 336 | 337 | 338 | static ngx_int_t ngx_lua_ipc_init_worker(ngx_cycle_t *cycle) { 339 | return ipc_init_worker(ipc, cycle); 340 | } 341 | 342 | static void ngx_lua_ipc_exit_worker(ngx_cycle_t *cycle) { 343 | ipc_destroy(ipc); 344 | } 345 | 346 | static void ngx_lua_ipc_exit_master(ngx_cycle_t *cycle) { 347 | ipc_destroy(ipc); 348 | } 349 | 350 | static ngx_command_t ngx_lua_ipc_commands[] = { 351 | ngx_null_command 352 | }; 353 | 354 | static ngx_http_module_t ngx_lua_ipc_ctx = { 355 | NULL, /* preconfiguration */ 356 | ngx_lua_ipc_init_postconfig, /* postconfiguration */ 357 | NULL, /* create main configuration */ 358 | NULL, /* init main configuration */ 359 | NULL, /* create server configuration */ 360 | NULL, /* merge server configuration */ 361 | NULL, /* create location configuration */ 362 | NULL, /* merge location configuration */ 363 | }; 364 | 365 | ngx_module_t ngx_lua_ipc_module = { 366 | NGX_MODULE_V1, 367 | &ngx_lua_ipc_ctx, /* module context */ 368 | ngx_lua_ipc_commands, /* module directives */ 369 | NGX_HTTP_MODULE, /* module type */ 370 | NULL, /* init master */ 371 | ngx_lua_ipc_init_module, /* init module */ 372 | ngx_lua_ipc_init_worker, /* init process */ 373 | NULL, /* init thread */ 374 | NULL, /* exit thread */ 375 | ngx_lua_ipc_exit_worker, /* exit process */ 376 | ngx_lua_ipc_exit_master, /* exit master */ 377 | NGX_MODULE_V1_PADDING 378 | }; 379 | -------------------------------------------------------------------------------- /dev/nginx-pkg/PKGBUILD: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | _nginx_ver_latest=1.11.6 3 | _nginx_ver_stable=1.10.3 4 | _nginx_ver_nopool=1.11.2 5 | _nginx_ver_legacy=1.8.1 6 | _nginx_ver_old=1.6.3 7 | _nginx_ver_very_old=0.8.55 8 | _ngx_echo_module_ver=0.60 9 | _lua_nginx_module_ver="master" 10 | _lua_upstream_nginx_module_ver="0.06" 11 | _openresty_ver=1.11.2.2 12 | 13 | _target_module_name=ngx_lua_ipc 14 | 15 | pkgname=nginx-${_target_module_name}-dev-git 16 | 17 | if [[ -n $OPENRESTY_CUSTOM_VERSION ]]; then 18 | _openresty_ver=$OPENRESTY_CUSTOM_VERSION 19 | fi 20 | 21 | OPTIONS=(!strip debug docs libtool staticlibs emptydirs zipman purge !upx) 22 | if [[ -z $NO_DEBUG ]]; then 23 | #debug build. clear cflags 24 | CFLAGS=" -ggdb -fvar-tracking-assignments -O$OPTIMIZE_LEVEL" 25 | fi 26 | 27 | CFLAGS="$CFLAGS -Wno-error -Wall -Wextra -Wno-unused-parameter -Wpointer-sign -Wpointer-arith -Wshadow -Wnested-externs -Wsign-compare" 28 | if [[ $SANITIZE_UNDEFINED == 1 ]]; then 29 | CFLAGS="$CFLAGS -fsanitize=undefined -fsanitize=shift -fsanitize=integer-divide-by-zero -fsanitize=unreachable -fsanitize=vla-bound -fsanitize=null -fsanitize=return -fsanitize=bounds -fsanitize=alignment -fsanitize=object-size -fsanitize=float-divide-by-zero -fsanitize=float-cast-overflow -fsanitize=nonnull-attribute -fsanitize=returns-nonnull-attribute -fsanitize=enum -lubsan" 30 | fi 31 | _include_http2=0 32 | 33 | _semver_lt() { 34 | _ver1=(${1//./ }) 35 | _ver2=(${2//./ }) 36 | 37 | if (( ${_ver1[0]} < ${_ver2[0]} )); then 38 | return 0 39 | elif (( ${_ver1[0]} == ${_ver2[0]} && ${_ver1[1]} < ${_ver2[1]} )); then 40 | return 0 41 | elif (( ${_ver1[0]} == ${_ver2[0]} && ${_ver1[1]} == ${_ver2[1]} && ${_ver1[2]} < ${_ver2[2]} )); then 42 | return 0 43 | else 44 | return 1 45 | fi 46 | } 47 | 48 | _semver_gteq() { 49 | if (_semver_lt "$1" "$2"); then 50 | return 1 51 | else 52 | return 0 53 | fi 54 | } 55 | 56 | if [[ $NO_POOL == 1 ]] && [[ -z $USE_OPENRESTY ]]; then 57 | _nginx_ver=$_nginx_ver_nopool 58 | elif [[ $NGINX_LEGACYVERSION == 1 ]]; then 59 | _nginx_ver=$_nginx_ver_legacy 60 | elif [[ $NGINX_OLDVERSION == 1 ]]; then 61 | _nginx_ver=$_nginx_ver_old 62 | elif [[ $NGINX_VERYOLDVERSION == 1 ]]; then 63 | _nginx_ver=$_nginx_ver_very_old 64 | elif [[ $NGINX_STABLEVERSION == 1 ]]; then 65 | _nginx_ver=$_nginx_ver_stable 66 | elif [[ -n $NGINX_CUSTOM_VERSION ]]; then 67 | _nginx_ver=$NGINX_CUSTOM_VERSION 68 | else 69 | _nginx_ver=$_nginx_ver_latest 70 | fi 71 | 72 | if [[ -n $_nginx_ver ]]; then 73 | if (_semver_gteq $_nginx_ver 1.9.5); then 74 | _include_http2=1 75 | elif (_semver_gteq $_nginx_ver 1.3.15); then 76 | _include_spdy=1 77 | fi 78 | _nginx_git_source_fragment="#tag=release-${_nginx_ver}" 79 | else 80 | #using master 81 | _include_http2=1 82 | fi 83 | 84 | _pkgname="nginx" 85 | PKGEXT=".pkg.tar" 86 | 87 | _user="http" 88 | _group="http" 89 | _doc_root="/usr/share/${_pkgname}/http" 90 | _tmp_path="/tmp" 91 | _pid_path="/run" 92 | _lock_path="/var/lock" 93 | _access_log="/dev/stdout" 94 | _error_log="errors.log" 95 | _target_module_dir="${startdir}/${_target_module_name}" 96 | 97 | _pkgver() { 98 | pushd "$_target_module_dir" >/dev/null 99 | printf "%s.%s.%s.%s" "$_nginx_ver" "$(git rev-parse --abbrev-ref HEAD | sed -r 's/-/_/g')" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" 100 | popd >/dev/null 101 | } 102 | 103 | pkgver=0 104 | pkgrel=1 105 | pkgdesc="Nginx + ngx_lua_ipc" 106 | arch=('any') 107 | install=install 108 | 109 | 110 | depends=('pcre' 'zlib' 'openssl') 111 | url="http://nginx.org" 112 | license=('custom') 113 | conflicts=('nginx' 'nginx-mainline' 'nginx-devel') 114 | provides=('nginx' 'nginx-custom') 115 | 116 | if [[ -n $USE_OPENRESTY ]]; then 117 | _name="openresty" 118 | else 119 | _name="nginx" 120 | fi 121 | 122 | 123 | if [[ -z $DEFAULT_PREFIX ]]; then 124 | if [[ -n $CUSTOM_PREFIX ]]; then 125 | _prefix_path=$CUSTOM_PREFIX 126 | else 127 | _prefix_path="/etc/$_name" 128 | fi 129 | else 130 | _prefix_path="/usr/local/$_name" 131 | fi 132 | 133 | if [[ -n $USE_OPENRESTY ]]; then 134 | _conf_path="${_prefix_path:1}/nginx" 135 | else 136 | _conf_path="${_prefix_path:1}" 137 | fi 138 | 139 | _nginxconf_path="${_conf_path}/conf/nginx.conf" 140 | 141 | backup=("${_nginxconf_path}" 142 | "${_conf_path}/conf/koi-win" 143 | "${_conf_path}/conf/koi-utf" 144 | "${_conf_path}/conf/win-utf" 145 | "${_conf_path}/conf/mime.types" 146 | "${_conf_path}/conf/fastcgi.conf" 147 | "${_conf_path}/conf/fastcgi_params" 148 | "${_conf_path}/conf/scgi_params" 149 | "${_conf_path}/conf/uwsgi_params" 150 | "etc/logrotate.d/nginx") 151 | _user=http 152 | _group=http 153 | 154 | _nginx_source="http://nginx.org/download/nginx-${_nginx_ver}.tar.gz" 155 | 156 | _openresty_source="https://openresty.org/download/openresty-${_openresty_ver}.tar.gz" 157 | 158 | _nginx_git_source="git+https://github.com/nginx/nginx.git${_nginx_git_source_fragment}" 159 | 160 | _no_pool_patch_source="https://raw.github.com/shrimp/no-pool-nginx/master/nginx-${_nginx_ver_nopool}-no_pool.patch" 161 | 162 | if [[ $_lua_nginx_module_ver == "master" ]]; then 163 | _lua_nginx_module_url="git+https://github.com/openresty/lua-nginx-module.git" 164 | else 165 | _lua_nginx_module_url="https://github.com/openresty/lua-nginx-module/archive/v${_lua_nginx_module_ver}.tar.gz" 166 | fi 167 | 168 | _lua_upstream_nginx_module_url="https://github.com/openresty/lua-upstream-nginx-module/archive/v${_lua_upstream_nginx_module_ver}.tar.gz" 169 | _ngx_debug_pool_url="https://github.com/chobits/ngx_debug_pool.git" 170 | _ngx_echo_module="https://github.com/openresty/echo-nginx-module/archive/v${_ngx_echo_module_ver}.tar.gz" 171 | 172 | _lua_stream_module_ver=0.0.1 173 | _lua_stream_module_src="https://github.com/openresty/stream-lua-nginx-module/archive/v${_lua_stream_module_ver}.tar.gz" 174 | 175 | if [[ -n $USE_OPENRESTY ]]; then 176 | _source=$_openresty_source 177 | _extracted_dir="openresty-${_openresty_ver}" 178 | else 179 | if [[ -n $NO_MAKEPKG ]]; then 180 | _source=$_nginx_source 181 | _extracted_dir="nginx-${_nginx_ver}" 182 | else 183 | _source=$_nginx_git_source 184 | _extracted_dir="nginx" 185 | fi 186 | fi 187 | 188 | source=("$_source" 189 | "nginx.conf" 190 | "nginx.logrotate" 191 | "nginx.service" 192 | "git+${_ngx_debug_pool_url}" 193 | "bl.txt" 194 | ) 195 | 196 | md5sums=('SKIP' 197 | '1fe7a3ca0773ce13f9f92e239a99f8b9' 198 | 'ab1eb640c978536c1dad16674d6b3c3c' 199 | '62d494d23aef31d0b867161f9fffa6eb' 200 | 'SKIP' 201 | 'SKIP') 202 | 203 | if [[ -n $WITH_LUA_STREAM_MODULE ]]; then 204 | source+=( "$_lua_stream_module_src" ) 205 | md5sums+=( 'SKIP') 206 | fi 207 | 208 | if [[ $WITH_NGX_ECHO_MODULE == 1 ]]; then 209 | source+=( "$_ngx_echo_module" ) 210 | md5sums+=( 'SKIP' ) 211 | fi 212 | 213 | if [[ -n $WITH_LUA_MODULE ]]; then 214 | source+=( "$_lua_nginx_module_url" "$_lua_upstream_nginx_module_url" ) 215 | md5sums+=( 'SKIP' 'SKIP' ) 216 | 217 | fi 218 | 219 | if [[ $NO_POOL == 1 ]] && [[ -z $USE_OPENRESTY ]]; then 220 | source+=( "$_no_pool_patch_source" ) 221 | md5sums+=( 'SKIP') 222 | fi 223 | 224 | 225 | 226 | build() { 227 | local _src_dir="${srcdir}/${_extracted_dir}" 228 | #ln -sfT "$srcdir" "$srcdir/nginx" 229 | ln -sfT "$startdir/../../" "$srcdir/${_target_module_name}" 230 | local _build_dir="${_src_dir}/objs" 231 | cd "$srcdir/${_extracted_dir}" 232 | 233 | if [[ -z $USE_OPENRESTY ]]; then 234 | sed -i.bak '/n == SSL_R_NO_CIPHERS_PASSED/d' src/event/ngx_event_openssl.c 235 | else 236 | sed -i.bak '/n == SSL_R_NO_CIPHERS_PASSED/d' build/nginx-*/src/event/ngx_event_openssl.c 237 | fi 238 | 239 | if [[ -z $CONTINUE ]] && [[ -z $NO_EXTRACT_SOURCE ]]; then 240 | if [[ $NO_POOL == 1 ]]; then 241 | echo "using the no-pool patch" 242 | if [[ -n $USE_OPENRESTY ]]; then 243 | pushd bundle 244 | patch -p0 < "./nginx-no_pool.patch" 245 | popd 246 | else 247 | patch -p1 < "${srcdir}/nginx-${_nginx_ver}-no_pool.patch" 248 | fi 249 | fi 250 | if [[ $NGX_SLAB_PATCH == 1 ]]; then 251 | echo "using the ngx_slab patch to fix large alloc/frees" 252 | patch -p1 < "${startdir}/ngx_slab.patch" 253 | fi 254 | if [[ $NGX_DEBUG_POOL == 1 ]]; then 255 | echo "patch nginx to debug pools" 256 | patch -p1 < "${srcdir}/ngx_debug_pool/debug_pool.patch" 257 | fi 258 | fi 259 | 260 | if [[ $MUDFLAP == 1 ]]; then 261 | export CFLAGS="$CFLAGS -fmudflap" 262 | fi 263 | 264 | CFLAGS="${CFLAGS/-Werror/}" #no warning-as-error 265 | 266 | CONFIGURE=() 267 | 268 | if [[ -n $EXTRA_CONFIG_OPT ]]; then 269 | if type "readarray"; then 270 | readarray -t lines <<<"$EXTRA_CONFIG_OPT" 271 | else 272 | lines="${(@f)EXTRA_CONFIG_OPT}" 273 | fi 274 | CONFIGURE+=$lines 275 | fi 276 | 277 | if [[ -z $DEFAULT_PREFIX ]]; then 278 | CONFIGURE+=( --prefix=$_prefix_path ) 279 | fi 280 | 281 | CONFIGURE+=( 282 | --sbin-path=/usr/bin/nginx 283 | --pid-path=${_pid_path}/nginx.pid 284 | --lock-path=${_pid_path}/nginx.lock 285 | --http-client-body-temp-path=${_tmp_path}/client_body_temp 286 | --http-proxy-temp-path=${_tmp_path}/proxy_temp 287 | --http-fastcgi-temp-path=${_tmp_path}/fastcgi_temp 288 | --http-uwsgi-temp-path=${_tmp_path}/uwsgi_temp 289 | --http-log-path=${_access_log} 290 | --error-log-path=${_error_log} 291 | ) 292 | 293 | if [[ -n $WITH_HTTP_SSL ]]; then 294 | CONFIGURE+=( --with-http_ssl_module ) 295 | fi 296 | 297 | if [[ -n $WITH_STREAM_MODULE ]]; then 298 | CONFIGURE+=( --with-stream ) 299 | fi 300 | 301 | if [[ -n $WITH_STUB_STATUS_MODULE ]]; then 302 | CONFIGURE+=( --with-http_stub_status_module ) 303 | fi 304 | 305 | if [[ -n $WITH_LUA_STREAM_MODULE ]]; then 306 | CONFIGURE+=( --add-module=../stream-lua-nginx-module-${_lua_stream_module_ver} ) 307 | fi 308 | 309 | if [[ -n $WITH_LUA_MODULE ]]; then 310 | if [[ $_lua_nginx_module_ver == "master" ]]; then 311 | CONFIGURE+=( "--add-module=${srcdir}/lua-nginx-module") 312 | else 313 | CONFIGURE+=( "--add-module=${srcdir}/lua-nginx-module-${_lua_nginx_module_ver}") 314 | fi 315 | CONFIGURE+=("--add-module=${srcdir}/lua-upstream-nginx-module-${_lua_upstream_nginx_module_ver}") 316 | fi 317 | 318 | if [[ $DYNAMIC == 1 ]]; then 319 | CONFIGURE+=( --add-dynamic-module=../${_target_module_name} ) 320 | else 321 | CONFIGURE+=( --add-module=../${_target_module_name} ) 322 | fi 323 | 324 | if [[ $CONFIGURE_WITH_DEBUG == 1 ]]; then 325 | CONFIGURE+=( "--with-debug" ) 326 | fi 327 | 328 | if [[ -z $NO_NGINX_USER ]]; then 329 | CONFIGURE+=( "--user=${_user}" "--group=${_group}" ) 330 | fi 331 | 332 | if [[ $_include_http2 == 1 ]]; then 333 | CONFIGURE+=( "--with-http_v2_module" ) 334 | fi 335 | 336 | if [[ $_include_spdy == 1 ]]; then 337 | CONFIGURE+=( "--with-http_spdy_module" ) 338 | fi 339 | 340 | if [[ $NGX_DEBUG_POOL == 1 ]]; then 341 | CONFIGURE+=( "--add-module=../ngx_debug_pool" ) 342 | fi 343 | 344 | if [[ $WITH_NGX_ECHO_MODULE == 1 ]]; then 345 | CONFIGURE+=( "--add-module=../echo-nginx-module-${_ngx_echo_module_ver}") 346 | fi 347 | 348 | if [[ -z $NGINX_VERYOLDVERSION ]]; then 349 | CONFIGURE+=( "--http-scgi-temp-path=${_tmp_path}/scgi_temp" ) 350 | fi 351 | 352 | if [[ $SANITIZE_UNDEFINED == 1 ]]; then 353 | CONFIGURE+=("--with-ld-opt=-lubsan") 354 | fi 355 | 356 | 357 | if [[ $CC == *clang* ]]; then 358 | #not a valid clang parameter 359 | CFLAGS="${CFLAGS/-fvar-tracking-assignments/}" 360 | CFLAGS="${CFLAGS/-fvar-tracking-assignments/}" 361 | if [[ -z $CLANG_ANALYZER ]]; then 362 | CFLAGS="-ferror-limit=5 $CFLAGS" 363 | fi 364 | elif [[ $CC == "cc" ]] || [[ $CC == "gcc" ]] || [[ -z $CC ]] && [[ -z $NO_GCC_COLOR ]]; then 365 | CFLAGS="-fdiagnostics-color=always -Wmaybe-uninitialized $CFLAGS" 366 | fi 367 | 368 | echo $CFLAGS 369 | export CCACHE_CPP2=yes 370 | if ! [[ -z $CLANG_ANALYZER ]]; then 371 | scan-build -o "$CLANG_ANALYZER" ./configure ${CONFIGURE[@]} 372 | scan-build -o "$CLANG_ANALYZER" make 373 | elif ! [[ -z $CONTINUE ]]; then 374 | make $REMAKE 375 | else 376 | ln -svf $srcdir/bl.txt 377 | 378 | if [[ ! -f ./configure ]]; then 379 | ln -s auto/configure ./ 380 | fi 381 | 382 | 383 | if [[ -n $EXPLICIT_CFLAGS ]]; then 384 | echo "./configure ${CONFIGURE[@]} --with-cc-opt=${CFLAGS}" 385 | ./configure ${CONFIGURE[@]} "--with-cc-opt=${CFLAGS}" 386 | else 387 | ./configure ${CONFIGURE[@]} 388 | fi 389 | 390 | 391 | #ln -sf ${srcdir}/lua-nginx-module-${_lua_nginx_module_ver}/src ../ngx_lua_ipc/src/lua-nginx-module-source > /dev/null 392 | 393 | make 394 | fi 395 | } 396 | 397 | package() { 398 | echo "make install" 399 | cd "${srcdir}/${_extracted_dir}" 400 | make DESTDIR="$pkgdir/" install >/dev/null 401 | sed -i -e "s/\ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "ipc.h" 9 | 10 | #include 11 | #include 12 | 13 | //#define IPC_DEBUG_ON 14 | 15 | #define LOG(ipc, code, lvl, fmt, args...) ngx_log_error(lvl, ngx_cycle->log, code, "%s IPC: " fmt, ((ipc) ? (ipc)->name : ""), ##args) 16 | #ifdef IPC_DEBUG_ON 17 | #define DBG(fmt, args...) LOG((ipc_t *)NULL, 0, NGX_LOG_WARN, fmt, ##args) 18 | #else 19 | #define DBG(fmt, args...) 20 | #endif 21 | #define ERR(ipc, fmt, args...) LOG(ipc, 0, NGX_LOG_ERR, fmt, ##args); \ 22 | ngx_snprintf((ipc)->last_error, IPC_MAX_ERROR_LEN, fmt "%Z", ##args) 23 | #define ERR_CODE(ipc, code, fmt, args...) LOG(ipc, 0, NGX_LOG_ERR, fmt, ##args); \ 24 | ngx_snprintf((ipc)->last_error, IPC_MAX_ERROR_LEN, fmt "%Z", ##args) 25 | 26 | #define NGX_MAX_HELPER_PROCESSES 2 // don't extend IPC to helpers. just workers for now. 27 | 28 | #define UPDATE_STAT(ipc, stat_name, delta) \ 29 | ngx_atomic_fetch_add((ngx_atomic_uint_t *)&((ipc_shm_data_t *)ipc->shm)->stats.stat_name, delta) 30 | 31 | 32 | //shared memory stuff 33 | typedef struct { 34 | ngx_pid_t pid; 35 | ngx_int_t slot; 36 | ngx_int_t ngx_process_type; 37 | ipc_ngx_process_type_t process_type; 38 | } process_slot_tracking_t; 39 | 40 | typedef struct { 41 | process_slot_tracking_t *process_slots; 42 | ngx_int_t process_count; 43 | ngx_int_t max_process_count; 44 | ipc_stats_t stats; 45 | ngx_shmtx_sh_t lock; 46 | ngx_shmtx_t mutex; 47 | } ipc_shm_data_t; 48 | 49 | static void ipc_worker_read_handler(ngx_event_t *ev); 50 | 51 | static ngx_int_t ipc_open(ipc_t *ipc, ngx_cycle_t *cycle, ngx_int_t workers); 52 | static ngx_int_t ipc_register_worker(ipc_t *ipc, ngx_cycle_t *cycle); 53 | static void ipc_free_readbuf(ipc_channel_t *chan, ipc_readbuf_t *rbuf); 54 | static ngx_int_t ipc_close_channel(ipc_channel_t *chan); 55 | 56 | 57 | static ngx_int_t ipc_init_channel(ipc_t *ipc, ipc_channel_t *chan) { 58 | chan->ipc = ipc; 59 | chan->pipe[0]=NGX_INVALID_FILE; 60 | chan->pipe[1]=NGX_INVALID_FILE; 61 | chan->read_conn = NULL; 62 | chan->write_conn = NULL; 63 | chan->active = 0; 64 | chan->wbuf.head = NULL; 65 | chan->wbuf.tail = NULL; 66 | chan->wbuf.n = 0; 67 | chan->wbuf.last_iovec.n = 0; 68 | chan->rbuf_head = NULL; 69 | return NGX_OK; 70 | } 71 | 72 | static ipc_t *ipc_create(const char *ipc_name) { 73 | ipc_t *ipc=malloc(sizeof(*ipc)); 74 | ngx_memzero(ipc, sizeof(*ipc)); 75 | int i = 0; 76 | for(i=0; i< NGX_MAX_PROCESSES; i++) { 77 | ipc_init_channel(ipc, &ipc->channel[i]); 78 | } 79 | 80 | ipc->shm = NULL; 81 | ipc->shm_sz = 0; 82 | 83 | ipc->name = ipc_name; 84 | ipc->worker_process_count = NGX_ERROR; 85 | 86 | return ipc; 87 | } 88 | 89 | ipc_t *ipc_init_module(const char *ipc_name, ngx_cycle_t *cycle) { 90 | ipc_t *ipc = ipc_create(ipc_name); 91 | ngx_core_conf_t *ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); 92 | ngx_int_t max_processes = ccf->worker_processes + NGX_MAX_HELPER_PROCESSES; 93 | size_t process_slots_sz = sizeof(process_slot_tracking_t) * max_processes; 94 | ipc_shm_data_t *shdata; 95 | 96 | 97 | ipc->worker_process_count = ccf->worker_processes; 98 | 99 | ipc->shm_sz = sizeof(ipc_shm_data_t) + process_slots_sz; 100 | ipc->shm = mmap(NULL, ipc->shm_sz, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); 101 | shdata = ipc->shm; 102 | ngx_memzero(shdata, sizeof(*shdata)); 103 | shdata->process_slots = (process_slot_tracking_t *)&shdata[1]; 104 | shdata->process_count = 0; 105 | shdata->max_process_count = max_processes; 106 | 107 | ngx_shmtx_create(&shdata->mutex, &shdata->lock, (u_char *)ipc_name); 108 | 109 | ipc_open(ipc, cycle, ccf->worker_processes); 110 | return ipc; 111 | } 112 | 113 | void ipc_scrape_proctitle(ngx_event_t *ev) { 114 | int i; 115 | ipc_t *ipc = ev->data; 116 | ipc_shm_data_t *shdata = ipc->shm; 117 | ipc_ngx_process_type_t process_type = IPC_NGX_PROCESS_UNKNOWN; 118 | if(ngx_strstr(ngx_os_argv[0], "cache manager")) { 119 | process_type = IPC_NGX_PROCESS_CACHE_MANAGER; 120 | } 121 | else if(ngx_strstr(ngx_os_argv[0], "cache loader")) { 122 | process_type = IPC_NGX_PROCESS_CACHE_LOADER; 123 | } 124 | else if(ngx_strstr(ngx_os_argv[0], "worker")) { 125 | process_type = IPC_NGX_PROCESS_WORKER; 126 | } 127 | 128 | ngx_shmtx_lock(&shdata->mutex); 129 | 130 | for(i=0; iprocess_count; i++) { 131 | if(shdata->process_slots[i].pid == ngx_pid) { 132 | shdata->process_slots[i].process_type = process_type; 133 | break; 134 | } 135 | } 136 | 137 | ngx_shmtx_unlock(&shdata->mutex); 138 | ngx_free(ev); 139 | } 140 | 141 | ngx_int_t ipc_init_worker(ipc_t *ipc, ngx_cycle_t *cycle) { 142 | ipc_shm_data_t *shdata = ipc->shm; 143 | ngx_int_t max_processes; 144 | int i, found = 0; 145 | process_slot_tracking_t *procslot; 146 | ngx_event_t *ev = ngx_calloc(sizeof(*ev), cycle->log); 147 | 148 | if (ngx_process != NGX_PROCESS_WORKER && ngx_process != NGX_PROCESS_SINGLE && ngx_process != NGX_PROCESS_HELPER) { 149 | //not a worker, stop initializing stuff. 150 | return NGX_OK; 151 | } 152 | 153 | 154 | ngx_shmtx_lock(&shdata->mutex); 155 | 156 | max_processes = shdata->max_process_count; 157 | 158 | for(i=0; !found && i < max_processes; i++) { 159 | procslot = &shdata->process_slots[i]; 160 | if(procslot->slot == ngx_process_slot) { 161 | found = 1; 162 | } 163 | } 164 | 165 | if(found) { 166 | procslot->pid = ngx_pid; 167 | procslot->slot = ngx_process_slot; 168 | procslot->ngx_process_type = ngx_process; 169 | DBG("ADD process %i slot %i type %i", ngx_pid, ngx_process_slot, ngx_process); 170 | shdata->process_count++; 171 | } 172 | else { 173 | ERR(ipc, "NOT FOUND"); 174 | ngx_shmtx_unlock(&shdata->mutex); 175 | return NGX_ERROR; 176 | } 177 | ngx_shmtx_unlock(&shdata->mutex); 178 | 179 | if(found) { 180 | #if nginx_version >= 1008000 181 | ev->cancelable = 1; 182 | #endif 183 | ev->handler = ipc_scrape_proctitle; 184 | ev->data = ipc; 185 | ev->log = cycle->log; 186 | ngx_add_timer(ev, 0); 187 | 188 | return ipc_register_worker(ipc, cycle); 189 | } 190 | else { 191 | DBG("SKIP process %i slot %i type %i", ngx_pid, ngx_process_slot, ngx_process); 192 | return NGX_ERROR; 193 | } 194 | } 195 | 196 | ngx_int_t ipc_destroy(ipc_t *ipc) { 197 | int i; 198 | 199 | for (i=0; ichannel[i]); 201 | ipc->channel[i].active = 0; 202 | } 203 | 204 | munmap(ipc->shm, ipc->shm_sz); 205 | free(ipc); 206 | return NGX_OK; 207 | } 208 | 209 | ngx_pid_t ipc_get_pid(ipc_t *ipc, int process_slot) { 210 | ipc_shm_data_t *shdata = ipc->shm; 211 | int max_processes = shdata->process_count; 212 | int i; 213 | process_slot_tracking_t *process_slots = shdata->process_slots; 214 | 215 | for(i=0; ishm; 224 | int max_processes = shdata->process_count; 225 | int i; 226 | process_slot_tracking_t *process_slots = shdata->process_slots; 227 | 228 | for(i=0; ialert_handler=alert_handler; 238 | return NGX_OK; 239 | } 240 | 241 | static void ipc_try_close_fd(ngx_socket_t *fd) { 242 | if(*fd != NGX_INVALID_FILE) { 243 | ngx_close_socket(*fd); 244 | *fd=NGX_INVALID_FILE; 245 | } 246 | } 247 | 248 | static ngx_int_t ipc_activate_channel(ipc_t *ipc, ngx_cycle_t *cycle, ipc_channel_t *channel, ipc_socket_type_t socktype) { 249 | int rc = NGX_OK; 250 | ngx_socket_t *socks = channel->pipe; 251 | if(channel->active) { 252 | // reinitialize already active pipes. This is done to prevent IPC alerts 253 | // from a previous restart that were never read from being received by 254 | // a newly restarted worker 255 | ipc_try_close_fd(&socks[0]); 256 | ipc_try_close_fd(&socks[1]); 257 | channel->active = 0; 258 | } 259 | 260 | assert(socks[0] == NGX_INVALID_FILE && socks[1] == NGX_INVALID_FILE); 261 | 262 | channel->socket_type = socktype; 263 | if(socktype == IPC_PIPE) { 264 | //make-a-pipe 265 | rc = pipe(socks); 266 | } 267 | else if(socktype == IPC_SOCKETPAIR) { 268 | rc = socketpair(AF_LOCAL, SOCK_STREAM, 0, socks); 269 | } 270 | 271 | if(rc == -1) { 272 | ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "pipe() failed while initializing IPC %s", ipc->name); 273 | return NGX_ERROR; 274 | } 275 | //make both ends nonblocking 276 | if (ngx_nonblocking(socks[0]) == -1 || ngx_nonblocking(socks[1]) == -1) { 277 | ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, ngx_nonblocking_n " failed on pipe socket %i while initializing IPC %s", ipc->name); 278 | ipc_try_close_fd(&socks[0]); 279 | ipc_try_close_fd(&socks[1]); 280 | return NGX_ERROR; 281 | } 282 | //It's ALIIIIIVE! ... erm.. active... 283 | channel->active = 1; 284 | 285 | return NGX_OK; 286 | } 287 | 288 | static ngx_int_t ipc_open(ipc_t *ipc, ngx_cycle_t *cycle, ngx_int_t workers) { 289 | //initialize pipes for workers in advance. 290 | int i, s = 0; 291 | ngx_int_t last_expected_process = ngx_last_process; 292 | ipc_channel_t *channel; 293 | ipc_shm_data_t *shdata = ipc->shm; 294 | /* here's the deal: we have no control over fork()ing, nginx's internal 295 | * socketpairs are unusable for our purposes (as of nginx 0.8 -- check the 296 | * code to see why), and the module initialization callbacks occur before 297 | * any workers are spawned. Rather than futzing around with existing 298 | * socketpairs, we make our own pipes array. 299 | * Trouble is, ngx_spawn_process() creates them one-by-one, and we need to 300 | * do it all at once. So we must guess all the workers' ngx_process_slots in 301 | * advance. Meaning the spawning logic must be copied to the T. 302 | * ... with some allowances for already-opened sockets... 303 | */ 304 | for(i=0; i < workers + NGX_MAX_HELPER_PROCESSES; i++) { //workers and possible cache loader and manager 305 | //copypasta from os/unix/ngx_process.c (ngx_spawn_process) 306 | while (s < last_expected_process && ngx_processes[s].pid != -1) { 307 | //find empty existing slot 308 | s++; 309 | } 310 | 311 | channel = &ipc->channel[s]; 312 | 313 | if(ipc_activate_channel(ipc, cycle, channel, IPC_PIPE) != NGX_OK) { 314 | return NGX_ERROR; 315 | } 316 | 317 | shdata->process_slots[i].slot = s; 318 | if(i < workers) { 319 | //definitely a worker 320 | shdata->process_count++; 321 | shdata->process_slots[i].ngx_process_type = NGX_PROCESS_WORKER; 322 | } 323 | 324 | 325 | s++; //NEXT!! 326 | } 327 | 328 | return NGX_OK; 329 | } 330 | 331 | static ngx_int_t ipc_close_channel(ipc_channel_t *chan) { 332 | ipc_alert_link_t *cur, *cur_next; 333 | ipc_readbuf_t *rcur; 334 | 335 | if(!chan->active) { 336 | return NGX_OK; 337 | } 338 | 339 | if(chan->read_conn) { 340 | ngx_close_connection(chan->read_conn); 341 | chan->read_conn = NULL; 342 | } 343 | if(chan->write_conn) { 344 | ngx_close_connection(chan->write_conn); 345 | chan->write_conn = NULL; 346 | } 347 | 348 | for(cur = chan->wbuf.head; cur != NULL; cur = cur_next) { 349 | cur_next = cur->next; 350 | free(cur); 351 | } 352 | 353 | while((rcur = chan->rbuf_head) != NULL) { 354 | ipc_free_readbuf(chan, rcur); 355 | } 356 | 357 | ipc_try_close_fd(&chan->pipe[0]); 358 | ipc_try_close_fd(&chan->pipe[1]); 359 | 360 | return NGX_OK; 361 | } 362 | 363 | static inline int ipc_iovec_sz(struct iovec *iov, int n) { 364 | int sz = 0; 365 | while(n>0) { 366 | sz += iov[--n].iov_len; 367 | } 368 | return sz; 369 | } 370 | 371 | static ngx_int_t ipc_write_iovec(ipc_t *ipc, ngx_socket_t fd, ipc_iovec_t *vec) { 372 | int n; 373 | int expected_iovec_sz = ipc_iovec_sz(vec->iov, vec->n); 374 | int expected_len = sizeof(ipc_packet_header_t) + ((ipc_packet_header_t *)vec->iov[0].iov_base)->pkt_len; 375 | n = writev(fd, vec->iov, vec->n); 376 | if (n == -1 && (ngx_errno) == NGX_EAGAIN) { 377 | //ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err, "write() EAGAINED..."); 378 | return NGX_AGAIN; 379 | } 380 | else if(n != expected_iovec_sz) { 381 | ERR(ipc, "writev() failed with n=%i, expected %i", n, expected_iovec_sz); 382 | return NGX_ERROR; 383 | } 384 | else if(n != expected_len) { 385 | ERR(ipc, "writev() inconsistent, expected %i, got %i", expected_len, n); 386 | return NGX_ERROR; 387 | } 388 | //DBG("wrote %i byte pkt", n); 389 | if(ipc->track_stats) 390 | UPDATE_STAT(ipc, packets_sent, 1); 391 | return NGX_OK; 392 | } 393 | 394 | static ngx_int_t ipc_enqueue_tmp_iovec(ipc_t *ipc, ipc_writebuf_t *wb) { 395 | size_t sz=0, len; 396 | ipc_alert_link_t *link; 397 | 398 | ipc_iovec_t *vec = &wb->last_iovec; 399 | u_char *cur; 400 | 401 | int i; 402 | 403 | if(vec->n == 0) { 404 | return NGX_OK; 405 | } 406 | 407 | for(i=0; i < vec->n; i++) { 408 | sz += vec->iov[i].iov_len; 409 | } 410 | 411 | link = malloc(sizeof(*link) + sz); 412 | 413 | if(!link) { 414 | ERR(ipc, "out of memory while allocating buffered iovec for writing"); 415 | return NGX_ERROR; 416 | } 417 | 418 | link->iovec.iov[0].iov_len = sz; 419 | cur = (u_char *)&link[1]; 420 | link->iovec.iov[0].iov_base = cur; 421 | link->iovec.n = 1; 422 | link->next = NULL; 423 | 424 | for(i=0; i < vec->n; i++) { 425 | len = vec->iov[i].iov_len; 426 | if(len > 0) { 427 | ngx_memcpy(cur, vec->iov[i].iov_base, len); 428 | cur += len; 429 | } 430 | } 431 | 432 | if(!wb->head) { 433 | wb->head = link; 434 | } 435 | if(wb->tail) { 436 | wb->tail->next = link; 437 | } 438 | wb->tail = link; 439 | 440 | vec->n = 0; 441 | 442 | if(ipc->track_stats) { 443 | UPDATE_STAT(ipc, packets_pending, 1); 444 | } 445 | return NGX_OK; 446 | } 447 | 448 | static ngx_int_t ipc_enqueue_write_iovec(ipc_t *ipc, ipc_writebuf_t *wb, ipc_iovec_t *v) { 449 | ipc_enqueue_tmp_iovec(ipc, wb); 450 | wb->last_iovec = *v; 451 | return NGX_OK; 452 | } 453 | 454 | static void ipc_write_handler(ngx_event_t *ev) { 455 | ngx_connection_t *c = ev->data; 456 | ngx_socket_t fd = c->fd; 457 | 458 | ipc_channel_t *chan = c->data; 459 | ipc_alert_link_t *cur; 460 | ipc_t *ipc = chan->ipc; 461 | ngx_int_t rc = NGX_OK; 462 | 463 | int delta = 0; 464 | while((cur = chan->wbuf.head) != NULL) { 465 | rc = ipc_write_iovec(ipc, fd, &cur->iovec); 466 | 467 | if(rc == NGX_OK) { 468 | chan->wbuf.head = cur->next; 469 | if(chan->wbuf.tail == cur) { 470 | chan->wbuf.tail = NULL; 471 | } 472 | free(cur); 473 | delta--; 474 | } 475 | else { 476 | break; 477 | } 478 | } 479 | 480 | if(delta != 0 && ipc->track_stats) { 481 | UPDATE_STAT(ipc, packets_pending, delta); 482 | } 483 | 484 | if(rc == NGX_OK && chan->wbuf.last_iovec.n > 0) { 485 | rc = ipc_write_iovec(chan->ipc, fd, &chan->wbuf.last_iovec); 486 | } 487 | 488 | if(rc == NGX_OK) { 489 | assert(chan->wbuf.head == NULL); 490 | assert(chan->wbuf.tail == NULL); 491 | } 492 | else { 493 | //re-add event because the write failed 494 | if(chan->wbuf.last_iovec.n > 0) { 495 | ipc_enqueue_tmp_iovec(chan->ipc, &chan->wbuf); 496 | } 497 | ngx_handle_write_event(c->write, 0); 498 | } 499 | chan->wbuf.last_iovec.n = 0; 500 | } 501 | 502 | 503 | typedef enum {IPC_CONN_READ, IPC_CONN_WRITE} ipc_conn_type_t; 504 | 505 | static ngx_int_t ipc_channel_setup_conn(ipc_channel_t *chan, ngx_cycle_t *cycle, ipc_conn_type_t conn_type, void (*event_handler)(ngx_event_t *), void *data) { 506 | ngx_connection_t *c; 507 | //set up read connection 508 | c = ngx_get_connection(chan->pipe[conn_type == IPC_CONN_READ ? 0 : 1], cycle->log); 509 | c->data = data; 510 | 511 | if(conn_type == IPC_CONN_READ) { 512 | c->read->handler = event_handler; 513 | c->read->log = cycle->log; 514 | c->write->handler = NULL; 515 | ngx_add_event(c->read, NGX_READ_EVENT, 0); 516 | chan->read_conn=c; 517 | } 518 | else if(conn_type == IPC_CONN_WRITE) { 519 | c->read->handler = NULL; 520 | c->write->log = cycle->log; 521 | c->write->handler = ipc_write_handler; 522 | chan->write_conn=c; 523 | } 524 | else { 525 | return NGX_ERROR; 526 | } 527 | return NGX_OK; 528 | } 529 | 530 | static ngx_int_t ipc_register_worker(ipc_t *ipc, ngx_cycle_t *cycle) { 531 | int i; 532 | ipc_channel_t *chan; 533 | 534 | for(i=0; i< NGX_MAX_PROCESSES; i++) { 535 | 536 | chan = &ipc->channel[i]; 537 | 538 | if(!chan->active) continue; 539 | 540 | assert(chan->pipe[0] != NGX_INVALID_FILE); 541 | assert(chan->pipe[1] != NGX_INVALID_FILE); 542 | 543 | if(i==ngx_process_slot) { 544 | //set up read connection 545 | ipc_channel_setup_conn(chan, cycle, IPC_CONN_READ, ipc_worker_read_handler, ipc); 546 | } 547 | else { 548 | //set up write connection 549 | ipc_channel_setup_conn(chan, cycle, IPC_CONN_WRITE, ipc_write_handler, chan); 550 | } 551 | } 552 | 553 | return NGX_OK; 554 | } 555 | 556 | static void ipc_free_readbuf(ipc_channel_t *chan, ipc_readbuf_t *rbuf) { 557 | if(rbuf->next) { 558 | rbuf->next->prev = rbuf->prev; 559 | } 560 | if(rbuf->prev) { 561 | rbuf->prev->next = rbuf->next; 562 | } 563 | if(chan->rbuf_head == rbuf) { 564 | chan->rbuf_head = rbuf->next; 565 | } 566 | free(rbuf); 567 | } 568 | 569 | static ipc_readbuf_t *channel_get_readbuf(ipc_channel_t *chan, ipc_packet_header_t *header, char **err) { 570 | ipc_readbuf_t *cur; 571 | for(cur = chan->rbuf_head; cur!= NULL; cur = cur->next) { 572 | if(header->src_slot == cur->header.src_slot) { 573 | if(header->src_pid != cur->header.src_pid) { 574 | ERR(chan->ipc, "got packets from different processes for the same slot: old %i, new %i. Clearing out old packet buffer.", cur->header.src_pid, header->src_pid); 575 | ipc_free_readbuf(chan, cur); 576 | break; 577 | } 578 | else if(header->ctrl != '+') { 579 | ipc_free_readbuf(chan, cur); 580 | *err = "unexpected packet ctrl (wanted '+')"; 581 | return NULL; 582 | } 583 | else if(header->tot_len != cur->header.tot_len) { 584 | ipc_free_readbuf(chan, cur); 585 | *err = "wrong packet length"; 586 | return NULL; 587 | } 588 | else { 589 | return cur; 590 | } 591 | } 592 | } 593 | 594 | if(header->ctrl != '>') { 595 | *err = "unexpected packet ctrl (wanted '>')"; 596 | return NULL; 597 | } 598 | 599 | cur = malloc(sizeof(*cur) + header->tot_len + 2*sizeof(void*)); 600 | if(!cur) { 601 | *err = "out of memory"; 602 | return NULL; 603 | } 604 | 605 | cur->header = *header; 606 | cur->body.name = (char *)ngx_align_ptr((char *)&cur[1], sizeof(void*)); 607 | cur->body.data = (char *)ngx_align_ptr(cur->body.name + header->name_len, sizeof(void*)); 608 | cur->body.bytes_read = 0; 609 | 610 | cur->prev = NULL; 611 | cur->next = chan->rbuf_head; 612 | 613 | if(chan->rbuf_head) { 614 | chan->rbuf_head->prev = cur; 615 | cur->next = chan->rbuf_head; 616 | } 617 | chan->rbuf_head = cur; 618 | 619 | return cur; 620 | } 621 | 622 | static int ipc_clear_socket_readbuf(ngx_socket_t s, size_t limit) { 623 | char buf[PIPE_BUF]; 624 | int total = 0; 625 | int n = 0; 626 | 627 | if(limit == 0) { 628 | do { 629 | total += n; 630 | n = read(s, buf, sizeof(buf)); 631 | } while(n > 0); 632 | } 633 | else { 634 | size_t read_sz; 635 | do { 636 | total += n; 637 | read_sz = limit > sizeof(buf) ? sizeof(buf) : limit; 638 | limit -= read_sz; 639 | n = read(s, buf, sizeof(buf)); 640 | } while(n > 0 && limit > 0); 641 | } 642 | 643 | return total; 644 | } 645 | 646 | static ngx_int_t ipc_read(ipc_t *ipc, ipc_channel_t *ipc_channel, ipc_alert_handler_pt handler, ngx_log_t *log) { 647 | ssize_t n; 648 | ngx_socket_t s = ipc_channel->read_conn->fd; 649 | ngx_str_t name, data; 650 | 651 | ipc_packet_header_t header; 652 | struct { 653 | char name[IPC_ALERT_NAME_MAX_LEN]; 654 | char data[IPC_PKT_MAX_BODY_SIZE]; 655 | } body; 656 | 657 | struct iovec iov[2]; 658 | 659 | int discarded; 660 | char *err; 661 | ipc_readbuf_t *rbuf; 662 | 663 | while(1) { 664 | n = read(s, &header, IPC_PKT_HEADER_SIZE); 665 | 666 | if (n == -1 && ngx_errno == NGX_EAGAIN) { 667 | return NGX_AGAIN; 668 | } 669 | else if(n == -1) { 670 | ERR_CODE(ipc, ngx_errno, "read() failed"); 671 | return NGX_ERROR; 672 | } 673 | else if(n != IPC_PKT_HEADER_SIZE) { 674 | discarded = ipc_clear_socket_readbuf(s, 0); 675 | ERR(ipc, "unexpected non-atomic read of packet header size %i, expected %i bytes. Discarded %i bytes of data.", n, IPC_PKT_HEADER_SIZE, header.pkt_len, discarded); 676 | return NGX_AGAIN; 677 | } 678 | else if(header.pkt_len > IPC_PKT_MAX_BODY_SIZE) { 679 | discarded = ipc_clear_socket_readbuf(s, 0); 680 | ERR(ipc, "got corrupt packet size %i. Discarded %i bytes of data.", header.pkt_len, discarded); 681 | return NGX_AGAIN; 682 | } 683 | else if(header.name_len > header.tot_len) { 684 | discarded = ipc_clear_socket_readbuf(s, 0); 685 | ERR(ipc, "got corrupt packet alert-name size %i. Discarded %i bytes of data.", header.name_len, discarded); 686 | return NGX_AGAIN; 687 | } 688 | 689 | switch (header.ctrl) { 690 | case '$': 691 | if(header.tot_len != header.pkt_len) { 692 | discarded = ipc_clear_socket_readbuf(s, 0); 693 | ERR(ipc, "got inconsistent whole-packet size %i. Discarded %i bytes of data.", header.pkt_len, discarded); 694 | return NGX_AGAIN; 695 | } 696 | //assert(n == pkt.pkt_len); 697 | 698 | iov[0].iov_base = body.name; 699 | iov[0].iov_len = header.name_len; 700 | iov[1].iov_base = body.data; 701 | iov[1].iov_len = header.pkt_len - header.name_len; 702 | n = readv(s, iov, 2); 703 | if(n != header.pkt_len) { 704 | discarded = ipc_clear_socket_readbuf(s, 0); 705 | ERR(ipc, "unexpected non-atomic read of size %i, expected %i. Discarded %i bytes of data.", n, header.pkt_len, discarded); 706 | return NGX_AGAIN; 707 | } 708 | name.data = iov[0].iov_base; 709 | name.len = iov[0].iov_len; 710 | 711 | data.data = iov[1].iov_base; 712 | data.len = iov[1].iov_len; 713 | 714 | //DBG("read %i byte pkt", n + IPC_PKT_HEADER_SIZE); 715 | if(ipc->track_stats) 716 | UPDATE_STAT(ipc, packets_received, 1); 717 | 718 | handler(header.src_pid, header.src_slot, &name, &data); 719 | break; 720 | 721 | case '>': 722 | case '+': 723 | if(header.tot_len <= header.pkt_len) { 724 | discarded = ipc_clear_socket_readbuf(s, 0); 725 | ERR(ipc, "got unexpectedly small part-packet total size %i. Discarded %i bytes of data.", header.tot_len, discarded); 726 | return NGX_AGAIN; 727 | } 728 | 729 | rbuf = channel_get_readbuf(ipc_channel, &header, &err); 730 | if(!rbuf) { 731 | ipc_clear_socket_readbuf(s, header.pkt_len); 732 | ERR(ipc, "dropped weird packet: %s", err); 733 | return NGX_AGAIN; 734 | } 735 | 736 | if(rbuf->body.bytes_read < header.name_len) { 737 | if(rbuf->body.bytes_read != 0) { 738 | ipc_clear_socket_readbuf(s, header.pkt_len); 739 | ERR(ipc, "dropped weird alert name doesn't fit in first packet: %s", err); 740 | return NGX_AGAIN; 741 | } 742 | iov[0].iov_base = rbuf->body.name; 743 | iov[0].iov_len = header.name_len; 744 | iov[1].iov_base = rbuf->body.data; 745 | iov[1].iov_len = header.pkt_len - header.name_len; 746 | n = readv(s, iov, 2); 747 | } 748 | else { 749 | //just reading data now 750 | char *cur = rbuf->body.data + rbuf->body.bytes_read - header.name_len; 751 | n = read(s, cur, header.pkt_len); 752 | } 753 | 754 | if(n != header.pkt_len) { 755 | discarded = ipc_clear_socket_readbuf(s, 0); 756 | ERR(ipc, "unexpected non-atomic read of size %i, expected %i. Discarded %i bytes of data.", n, header.pkt_len, discarded); 757 | return NGX_AGAIN; 758 | } 759 | //DBG("read %i byte pkt", n + IPC_PKT_HEADER_SIZE); 760 | rbuf->body.bytes_read += n; 761 | 762 | if(rbuf->body.bytes_read == rbuf->header.tot_len) { //alert finished 763 | name.len = rbuf->header.name_len; 764 | name.data = (u_char *)rbuf->body.name; 765 | data.len = rbuf->header.tot_len - name.len; 766 | data.data = (u_char *)rbuf->body.data; 767 | handler(header.src_pid, header.src_slot, &name, &data); 768 | ipc_free_readbuf(ipc_channel, rbuf); 769 | } 770 | break; 771 | 772 | default: 773 | discarded = ipc_clear_socket_readbuf(s, 0); 774 | ERR(ipc, "got unexpected packet ctrl code '%c'. Discarded %i bytes of data.", header.ctrl, discarded); 775 | return NGX_AGAIN; 776 | } 777 | } 778 | 779 | return NGX_OK; 780 | } 781 | 782 | static void ipc_worker_read_handler(ngx_event_t *ev) { 783 | ngx_int_t rc; 784 | ngx_connection_t *c; 785 | ipc_channel_t *ipc_channel; 786 | ipc_t *ipc; 787 | 788 | if (ev->timedout) { 789 | ev->timedout = 0; 790 | return; 791 | } 792 | c = ev->data; 793 | ipc = c->data; 794 | ipc_channel = &ipc->channel[ngx_process_slot]; 795 | 796 | rc = ipc_read(ipc, ipc_channel, ipc->alert_handler, ev->log); 797 | if (rc == NGX_ERROR) { 798 | ERR(ipc, "IPC_READ_SOCKET failed: bad connection. This should never have happened, yet here we are..."); 799 | assert(0); 800 | return; 801 | } 802 | else if (rc == NGX_AGAIN) { 803 | return; 804 | } 805 | } 806 | 807 | static ngx_int_t ipc_alert_channel(ipc_channel_t *chan, ngx_str_t *name, ngx_str_t *data) { 808 | ipc_packet_header_t header; 809 | ipc_writebuf_t *wb = &chan->wbuf; 810 | ipc_iovec_t vec; 811 | 812 | int pad; 813 | 814 | if(!chan->active) { 815 | return NGX_ERROR; 816 | } 817 | 818 | 819 | if(name->len > IPC_ALERT_NAME_MAX_LEN) { 820 | ERR(chan->ipc, "alert name length cannot exceed %i, was %i", IPC_ALERT_NAME_MAX_LEN, name->len); 821 | return NGX_ERROR; 822 | } 823 | if(data->len > IPC_ALERT_DATA_MAX_LEN) { 824 | ERR(chan->ipc, "alert data length cannot exceed %i, was %i", IPC_ALERT_DATA_MAX_LEN, data->len); 825 | return NGX_ERROR; 826 | } 827 | 828 | header.tot_len = data->len + name->len; 829 | header.name_len = name->len; 830 | header.src_slot = ngx_process_slot; 831 | header.src_pid = ngx_pid; 832 | 833 | vec.n = 3; 834 | 835 | //zero the struct padding 836 | pad = (u_char *)(&header + 1) - (&header.ctrl + 1); 837 | if(pad > 0) { 838 | ngx_memzero(&header.ctrl + 1, pad); 839 | } 840 | 841 | vec.iov[0].iov_base = &header; 842 | vec.iov[0].iov_len = IPC_PKT_HEADER_SIZE; 843 | 844 | if(header.tot_len <= IPC_PKT_MAX_BODY_SIZE) { 845 | header.pkt_len = header.tot_len; 846 | header.ctrl = '$'; 847 | 848 | vec.iov[1].iov_base = name->data; 849 | vec.iov[1].iov_len = name->len; 850 | 851 | vec.iov[2].iov_base = data->data; 852 | vec.iov[2].iov_len = data->len; 853 | 854 | ipc_enqueue_write_iovec(chan->ipc, wb, &vec); 855 | ipc_write_handler(chan->write_conn->write); 856 | } 857 | else { 858 | size_t name_left = name->len; 859 | size_t data_left = data->len; 860 | u_char *name_cur = name->data; 861 | u_char *data_cur = data->data; 862 | size_t name_len = 0; 863 | size_t data_len = 0; 864 | 865 | int pktnum; 866 | 867 | for(pktnum = 0; name_left + data_left > 0; pktnum++) { 868 | 869 | header.ctrl = pktnum == 0 ? '>' : '+'; 870 | 871 | if(name_left == 0) { 872 | name_len = 0; 873 | } 874 | else { 875 | name_cur += name_len; 876 | name_len = name_left > IPC_PKT_MAX_BODY_SIZE ? IPC_PKT_MAX_BODY_SIZE : name_left; 877 | name_left -= name_len; 878 | } 879 | 880 | data_cur += data_len; 881 | data_len = data_left > (IPC_PKT_MAX_BODY_SIZE - name_len) ? (IPC_PKT_MAX_BODY_SIZE - name_len) : data_left; 882 | data_left -= data_len; 883 | 884 | header.pkt_len = name_len + data_len; 885 | 886 | vec.iov[1].iov_base = name_cur; 887 | vec.iov[1].iov_len = name_len; 888 | vec.iov[2].iov_base = data_cur; 889 | vec.iov[2].iov_len = data_len; 890 | 891 | ipc_enqueue_write_iovec(chan->ipc, wb, &vec); 892 | ipc_write_handler(chan->write_conn->write); 893 | } 894 | 895 | //assert(name_left + data_left == 0); 896 | } 897 | 898 | return NGX_OK; 899 | } 900 | 901 | ngx_int_t ipc_alert_slot(ipc_t *ipc, ngx_int_t slot, ngx_str_t *name, ngx_str_t *data) { 902 | DBG("send alert '%V' to slot %i", name, slot); 903 | 904 | ngx_str_t empty = {0, NULL}; 905 | if(!name) name = ∅ 906 | if(!data) data = ∅ 907 | 908 | if(slot == ngx_process_slot) { 909 | ipc->alert_handler(ngx_pid, slot, name, data); 910 | return NGX_OK; 911 | } 912 | return ipc_alert_channel(&ipc->channel[slot], name, data); 913 | } 914 | 915 | 916 | ngx_int_t ipc_alert_pid(ipc_t *ipc, ngx_pid_t worker_pid, ngx_str_t *name, ngx_str_t *data) { 917 | ngx_int_t slot = ipc_get_slot(ipc, worker_pid); 918 | if(slot == NGX_ERROR) { 919 | ngx_snprintf((ipc)->last_error, IPC_MAX_ERROR_LEN, "No worker process with PID %i%Z", worker_pid); 920 | return NGX_ERROR; 921 | } 922 | return ipc_alert_slot(ipc, slot, name, data); 923 | } 924 | 925 | ngx_int_t ipc_alert_all_processes(ipc_t *ipc, ipc_ngx_process_type_t type, ngx_str_t *name, ngx_str_t *data) { 926 | ipc_shm_data_t *shdata = ipc->shm; 927 | int max_workers = shdata->process_count; 928 | int i; 929 | int rc = NGX_OK, trc; 930 | process_slot_tracking_t *process_slots = shdata->process_slots; 931 | 932 | for(i=0; iprocess_slots[i].process_type == type) { 934 | trc = ipc_alert_slot(ipc, process_slots[i].slot, name, data); 935 | if(trc != NGX_OK) rc = trc; 936 | } 937 | } 938 | return rc; 939 | } 940 | 941 | ngx_int_t ipc_alert_all_workers(ipc_t *ipc, ngx_str_t *name, ngx_str_t *data) { 942 | return ipc_alert_all_processes(ipc, IPC_NGX_PROCESS_WORKER, name, data); 943 | } 944 | 945 | #define COLLECT_PROCESS_PROPERTY(shdata, ipc_process_type, prop, dst_array, found_count) \ 946 | for(int i=0; i < shdata->process_count; i++) { \ 947 | if ((ipc_process_type == IPC_NGX_PROCESS_WORKER && shdata->process_slots[i].ngx_process_type == NGX_PROCESS_WORKER) \ 948 | || (ipc_process_type == IPC_NGX_PROCESS_ANY) \ 949 | || (shdata->process_slots[i].process_type == ipc_process_type)){ \ 950 | dst_array[*found_count] = shdata->process_slots[i].prop; \ 951 | (*found_count)++; \ 952 | } \ 953 | } 954 | 955 | ngx_pid_t *ipc_get_process_pids(ipc_t *ipc, int *pid_count, ipc_ngx_process_type_t type) { 956 | static ngx_pid_t pid_array[NGX_MAX_PROCESSES + NGX_MAX_HELPER_PROCESSES]; 957 | ipc_shm_data_t *shdata = ipc->shm; 958 | *pid_count=0; 959 | COLLECT_PROCESS_PROPERTY(shdata, type, pid, pid_array, pid_count) 960 | return pid_array; 961 | } 962 | 963 | ngx_int_t *ipc_get_process_slots(ipc_t *ipc, int *slot_count, ipc_ngx_process_type_t type) { 964 | static ngx_int_t slot_array[NGX_MAX_PROCESSES + NGX_MAX_HELPER_PROCESSES]; 965 | ipc_shm_data_t *shdata = ipc->shm; 966 | *slot_count=0; 967 | COLLECT_PROCESS_PROPERTY(shdata, type, slot, slot_array, slot_count) 968 | return slot_array; 969 | } 970 | 971 | ngx_pid_t *ipc_get_worker_pids(ipc_t *ipc, int *pid_count) { 972 | return ipc_get_process_pids(ipc, pid_count, IPC_NGX_PROCESS_WORKER); 973 | } 974 | ngx_int_t *ipc_get_worker_slots(ipc_t *ipc, int *slot_count) { 975 | return ipc_get_process_slots(ipc, slot_count, IPC_NGX_PROCESS_WORKER); 976 | } 977 | 978 | 979 | char *ipc_get_last_error(ipc_t *ipc) { 980 | return (char *)ipc->last_error; 981 | } 982 | 983 | ipc_stats_t *ipc_get_stats(ipc_t *ipc) { 984 | static ipc_stats_t stats; 985 | stats = ((ipc_shm_data_t *)ipc->shm)->stats; 986 | return &stats; 987 | } 988 | --------------------------------------------------------------------------------