├── .gitattributes ├── .gitignore ├── .luacheckrc ├── .travis.yml ├── LICENSE ├── Makefile ├── README.markdown ├── lib └── resty │ └── worker │ └── events.lua ├── lua-resty-worker-events-scm-1.rockspec ├── rockspecs ├── lua-resty-worker-events-0.3.3-1.rockspec ├── lua-resty-worker-events-1.0.0-1.rockspec ├── lua-resty-worker-events-2.0.0-1.rockspec └── lua-resty-worker-events-2.0.1-1.rockspec ├── t └── sanity.t └── valgrind.suppress /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | nginx 8 | ctags 9 | tags 10 | a.lua 11 | .idea/ 12 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | -- Configuration file for LuaCheck 2 | -- see: https://luacheck.readthedocs.io/en/stable/ 3 | -- 4 | -- To run do: `luacheck .` from the repo 5 | 6 | std = "ngx_lua" 7 | unused_args = false 8 | redefined = false 9 | max_line_length = false 10 | 11 | 12 | globals = { 13 | "_KONG", 14 | "kong", 15 | "ngx.IS_CLI", 16 | } 17 | 18 | 19 | not_globals = { 20 | "string.len", 21 | "table.getn", 22 | } 23 | 24 | 25 | ignore = { 26 | -- "6.", -- ignore whitespace warnings 27 | } 28 | 29 | 30 | exclude_files = { 31 | --"spec/fixtures/invalid-module.lua", 32 | --"spec-old-api/fixtures/invalid-module.lua", 33 | } 34 | 35 | 36 | --files["spec/**/*.lua"] = { 37 | -- std = "ngx_lua+busted", 38 | --} 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # vim: st=2 sts=2 sw=2 et: 2 | 3 | sudo: false 4 | 5 | language: c 6 | 7 | compiler: gcc 8 | 9 | notifications: 10 | email: false 11 | 12 | cache: 13 | directories: 14 | - download-cache 15 | - perl5 16 | 17 | env: 18 | global: 19 | - JOBS=2 20 | - LUAROCKS_VER=2.4.3 21 | matrix: 22 | - OPENRESTY_VER=1.11.2.2 23 | - OPENRESTY_VER=1.11.2.3 24 | - OPENRESTY_VER=1.11.2.4 25 | - OPENRESTY_VER=1.11.2.5 26 | - OPENRESTY_VER=1.13.6.1 27 | - OPENRESTY_VER=1.13.6.2 28 | - LINT=1 29 | 30 | install: 31 | - mkdir -p download-cache 32 | - if [ -z "$OPENRESTY_VER" ]; then export OPENRESTY_VER=1.13.6.2; fi 33 | - if [ ! -f download-cache/openresty-$OPENRESTY_VER.tar.gz ]; then wget -O download-cache/openresty-$OPENRESTY_VER.tar.gz http://openresty.org/download/openresty-$OPENRESTY_VER.tar.gz; fi 34 | - if [ ! -f download-cache/luarocks-$LUAROCKS_VER.tar.gz ]; then wget -O download-cache/luarocks-$LUAROCKS_VER.tar.gz https://luarocks.github.io/luarocks/releases/luarocks-$LUAROCKS_VER.tar.gz; fi 35 | - if [ ! -f download-cache/cpanm ]; then wget -O download-cache/cpanm https://cpanmin.us/; fi 36 | - tar -zxf download-cache/openresty-$OPENRESTY_VER.tar.gz 37 | - tar -zxf download-cache/luarocks-$LUAROCKS_VER.tar.gz 38 | - chmod +x download-cache/cpanm 39 | - download-cache/cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 40 | - download-cache/cpanm --notest --local-lib=$TRAVIS_BUILD_DIR/perl5 local::lib && eval $(perl -I $TRAVIS_BUILD_DIR/perl5/lib/perl5/ -Mlocal::lib) 41 | - pushd openresty-$OPENRESTY_VER 42 | - export OPENRESTY_PREFIX=$TRAVIS_BUILD_DIR/openresty-$OPENRESTY_VER 43 | - ./configure --prefix=$OPENRESTY_PREFIX --without-http_ssl_module -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) 44 | - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) 45 | - make install > build.log 2>&1 || (cat build.log && exit 1) 46 | - popd 47 | - pushd luarocks-$LUAROCKS_VER 48 | - export LUAROCKS_PREFIX=$TRAVIS_BUILD_DIR/luarocks-$LUAROCKS_VER 49 | - ./configure --prefix=$LUAROCKS_PREFIX --with-lua=$OPENRESTY_PREFIX/luajit --with-lua-include=$OPENRESTY_PREFIX/luajit/include/luajit-2.1 --lua-suffix=jit 50 | - make build 51 | - make install 52 | - popd 53 | - export PATH=$OPENRESTY_PREFIX/nginx/sbin:$LUAROCKS_PREFIX/bin:$PATH 54 | - luarocks install luacheck > build.log 2>&1 || (cat build.log && exit 1) 55 | - luarocks --version 56 | - nginx -V 57 | 58 | script: 59 | - if [ -z "$LINT" ]; then TEST_NGINX_RANDOMIZE=1 prove -j$JOBS -r t; else luacheck lib; fi 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty 2 | 3 | PREFIX ?= /usr/local 4 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 5 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 6 | INSTALL ?= install 7 | 8 | .PHONY: all test install 9 | 10 | all: ; 11 | 12 | install: all 13 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty/worker/ 14 | $(INSTALL) lib/resty/worker/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/worker/ 15 | 16 | test: all 17 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 18 | 19 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Kong/lua-resty-worker-events.svg?branch=master)](https://travis-ci.org/Kong/lua-resty-worker-events) 2 | 3 | lua-resty-worker-events 4 | ======================= 5 | 6 | Inter process events for Nginx worker processes 7 | 8 | Table of Contents 9 | ================= 10 | 11 | * [Name](#name) 12 | * [Status](#status) 13 | * [Synopsis](#synopsis) 14 | * [Description](#description) 15 | * [Troubleshooting](#troubleshooting) 16 | * [Methods](#methods) 17 | * [configure](#configure) 18 | * [configured](#configured) 19 | * [event_list](#event_list) 20 | * [poll](#poll) 21 | * [post](#post) 22 | * [post_local](#post_local) 23 | * [register](#register) 24 | * [register_weak](#register_weak) 25 | * [unregister](#unregister) 26 | * [Installation](#installation) 27 | * [Bugs and Patches](#bugs-and-patches) 28 | * [Author](#author) 29 | * [Copyright and License](#copyright-and-license) 30 | * [History](#history) 31 | * [See Also](#see-also) 32 | 33 | Status 34 | ====== 35 | 36 | This library is production ready. 37 | 38 | Synopsis 39 | ======== 40 | 41 | ```nginx 42 | http { 43 | lua_package_path "/path/to/lua-resty-worker-events/lib/?.lua;;"; 44 | 45 | # the size depends on the number of event to handle: 46 | lua_shared_dict process_events 1m; 47 | 48 | init_worker_by_lua_block { 49 | local ev = require "resty.worker.events" 50 | 51 | local handler = function(data, event, source, pid) 52 | print("received event; source=",source, 53 | ", event=",event, 54 | ", data=", tostring(data), 55 | ", from process ",pid) 56 | end 57 | 58 | ev.register(handler) 59 | 60 | local ok, err = ev.configure { 61 | shm = "process_events", -- defined by "lua_shared_dict" 62 | timeout = 2, -- life time of unique event data in shm 63 | interval = 1, -- poll interval (seconds) 64 | 65 | wait_interval = 0.010, -- wait before retry fetching event data 66 | wait_max = 0.5, -- max wait time before discarding event 67 | shm_retries = 999, -- retries for shm fragmentation (no memory) 68 | } 69 | if not ok then 70 | ngx.log(ngx.ERR, "failed to start event system: ", err) 71 | return 72 | end 73 | } 74 | 75 | server { 76 | ... 77 | 78 | # example for polling: 79 | location = /some/path { 80 | 81 | default_type text/plain; 82 | content_by_lua_block { 83 | -- manually call `poll` to stay up to date, can be used instead, 84 | -- or together with the timer interval. Polling is efficient, 85 | -- so if staying up-to-date is important, this is preferred. 86 | require("resty.worker.events").poll() 87 | 88 | -- do regular stuff here 89 | 90 | } 91 | } 92 | } 93 | } 94 | ``` 95 | 96 | Description 97 | =========== 98 | 99 | [Back to TOC](#table-of-contents) 100 | 101 | This module provides a way to send events to the other worker processes in an Nginx 102 | server. Communication is through a shared memory zone where event data will be stored. 103 | 104 | The order of events in all workers is __guaranteed__ to be the same. 105 | 106 | The worker process will setup a timer to check for events in the background. The 107 | module follows a singleton pattern and hence runs once per worker. If staying 108 | up-to-date is important though, the interval can be set to a lesser frequency and a 109 | call to [poll](#poll) upon each request received makes sure everything is handled 110 | as soon as possible. 111 | 112 | The design allows for 3 usecases; 113 | 114 | 1. broadcast an event to all workers processes, see [post](#post). In this case 115 | the order of the events is guaranteed to be the same in all worker processes. Example; 116 | a healthcheck running in one worker, but informing all workers of a failed 117 | upstream node. 118 | 2. broadcast an event to the local worker only, see [post_local](#post_local). 119 | 3. coalesce external events to a single action. Example; all workers watch 120 | external events indicating an in-memory cache needs to be refreshed. When 121 | receiving it they all post it with a unique event hash (all workers generate the 122 | same hash), see `unique` parameter of [post](#post). Now only 1 worker will 123 | receive the event _only once_, so only one worker will hit the upstream 124 | database to refresh the in-memory data. 125 | 126 | This module itself will fire two events with `source="resty-worker-events"`; 127 | * `event="started"` when the module is first configured (note: the event handler must be 128 | [registered](#register) before calling [configure](#configure) to be able to catch the event) 129 | * `event="stopping"` when the worker process exits (based on a timer `premature` setting) 130 | 131 | See [event_list](#event_list) for using events without hardcoded magic 132 | values/strings. 133 | 134 | 135 | Troubleshooting 136 | ================ 137 | 138 | To properly size the shm, it is important to understand how it is being used. 139 | Event data is stored in the shm to pass it to the other workers. As such there 140 | are 2 types of entries in the shm: 141 | 142 | 1. events that are to be executed by only a single worker (see the 143 | `unique` parameter of the `post` method). These entries get a `ttl` in the 144 | shm and will hence expire. 145 | 2. all other events (except local events which do not use the SHM). In these 146 | cases there is no `ttl` set. 147 | 148 | The result of the above is that the SHM will always be full! so that is not a 149 | metric to investigate at. 150 | 151 | How to prevent problems: 152 | 153 | * the SHM size must at least be a multiple of the maximum payload expected. It 154 | must be able to cater for all the events that might be send within one 155 | `interval` (see `configure`). 156 | * `no memory` errors *cannot* be resolved by making the SHM bigger. The only way 157 | to resolve those is by increasing the `shm_retries` option passed to 158 | `configure` (which already has a high default). 159 | This is because the error is due to fragmentation and not a lack of memory. 160 | * the `waiting for event data timed out` error happens if event data gets 161 | evicted before all the workers got to deal with it. This can happen if 162 | there is a burst of (large-payload) events. To resolve these: 163 | 164 | * try to avoid big event payloads 165 | * use a smaller `interval`, so workers check for (and deal with) events 166 | more frequently (see `interval` option as passed to `configure`) 167 | * increase the SHM size, such that it can hold all the event data that 168 | might be send within 1 interval. 169 | 170 | 171 | Methods 172 | ======= 173 | 174 | [Back to TOC](#table-of-contents) 175 | 176 | configure 177 | --------- 178 | `syntax: success, err = events.configure(opts)` 179 | 180 | Will initialize the event listener. This should typically be called from the 181 | `init_by_lua` handler, because it will make sure all workers start with the 182 | first event. In case of a reload of the system (starting new and stopping old 183 | workers) past events will not be replayed. And because the order in which 184 | workers reload cannot be guaranteed, also the event start cannot be guaranteed. 185 | So if some sort of state is derived from the events you have to manage that 186 | state separately. 187 | 188 | The `opts` parameter is a Lua table with named options: 189 | 190 | * `shm`: (required) name of the shared memory to use. Event data will not expire, so 191 | the module relies on the shm lru mechanism to evict old events from the shm. As such 192 | the shm should probably not be used for other purposes. 193 | * `shm_retries`: (optional) number of retries when the shm returns "no memory" on posting 194 | an event, default 999. Each time there is an insertion attempt and no memory is available 195 | (either no space is available or the memory is available but fragmented), "up to tens" 196 | of old entries are evicted. After that, if there's still no memory available, the 197 | "no memory" error is returned. Retrying the insertion triggers the eviction phase 198 | several times, increasing the memory available as well as the probability of finding a 199 | large enough contiguous memory block available for the new event data. 200 | * `interval`: (optional) interval to poll for events (in seconds), default 1. Set to 0 to 201 | disable polling. 202 | * `wait_interval`: (optional) interval between two tries when a new eventid is found, but the 203 | data is not available yet (due to asynchronous behaviour of the worker processes) 204 | * `wait_max`: (optional) max time to wait for data when event id is found, before discarding 205 | the event. This is a fail-safe setting in case something went wrong. 206 | * `timeout`: (optional) timeout of unique event data stored in shm (in seconds), default 2. 207 | See the `unique` parameter of the [post](#post) method. 208 | 209 | The return value will be `true`, or `nil` and an error message. 210 | 211 | This method can be called repeatedly to update the settings, except for the `shm` value which 212 | cannot be changed after the initial configuration. 213 | 214 | NOTE: the `wait_interval` is executed using the `ngx.sleep` function. In contexts where this 215 | function is not available (eg. `init_worker`) it will execute a busy-wait to execute the delay. 216 | 217 | [Back to TOC](#table-of-contents) 218 | 219 | configured 220 | ---------- 221 | `syntax: is_already_configured = events.configured()` 222 | 223 | The events module runs as a singelton per workerprocess. The `configured` 224 | function allows to check whether it is already up and running. 225 | A check before starting any dependencies is recommended; 226 | ```lua 227 | local events = require "resty.worker.events" 228 | 229 | local initialization_of_my_module = function() 230 | assert(events.configured(), "Please configure the 'lua-resty-worker-events' ".. 231 | "module before using my_module") 232 | 233 | -- do initialization here 234 | end 235 | ``` 236 | 237 | [Back to TOC](#table-of-contents) 238 | 239 | event_list 240 | ---------- 241 | `syntax: _M.events = events.event_list(sourcename, event1, event2, ...)` 242 | 243 | Utility function to generate event lists and prevent typos in 244 | magic strings. Accessing a non-existing event on the returned table will result 245 | in an 'unknown event error'. 246 | The first parameter `sourcename` is a unique name that identifies the event 247 | source, which will be available as field `_source`. All following parameters 248 | are the named events generated by the event source. 249 | 250 | Example usage; 251 | ```lua 252 | local ev = require "resty.worker.events" 253 | 254 | -- Event source example 255 | 256 | local events = ev.event_list( 257 | "my-module-event-source", -- available as _M.events._source 258 | "started", -- available as _M.events.started 259 | "event2" -- available as _M.events.event2 260 | ) 261 | 262 | local raise_event = function(event, data) 263 | return ev.post(events._source, event, data) 264 | end 265 | 266 | -- Post my own 'started' event 267 | raise_event(events.started, nil) -- nil for clarity, no eventdata is passed 268 | 269 | -- define my module table 270 | local _M = { 271 | events = events -- export events table 272 | 273 | -- implementation goes here 274 | } 275 | return _M 276 | 277 | 278 | 279 | -- Event client example; 280 | local mymod = require("some_module") -- module with an `events` table 281 | 282 | -- define a callback and use source modules events table 283 | local my_callback = function(data, event, source, pid) 284 | if event == mymod.events.started then -- 'started' is the event name 285 | 286 | -- started event from the resty-worker-events module 287 | 288 | elseif event == mymod.events.stoppping then -- 'stopping' is the event name 289 | 290 | -- the above will throw an error because of the typo in `stoppping` 291 | 292 | end 293 | end 294 | 295 | ev.register(my_callback, mymod.events._source) 296 | 297 | ``` 298 | 299 | [Back to TOC](#table-of-contents) 300 | 301 | poll 302 | ---- 303 | `syntax: success, err = events.poll()` 304 | 305 | Will poll for new events and handle them all (call the registered callbacks). The implementation is 306 | efficient, it will only check a single shared memory value and return immediately if no new events 307 | are available. 308 | 309 | The return value will be `"done"` when it handled all events, `"recursive"` if it was 310 | already in a polling-loop, or `nil + error` if something went wrong. 311 | The `"recursive"` result simply means that an event-handler called `poll` again. 312 | 313 | [Back to TOC](#table-of-contents) 314 | 315 | post 316 | ---- 317 | `syntax: success, err = events.post(source, event, data, unique)` 318 | 319 | Will post a new event. `source` and `event` are both strings. `data` can be anything (including `nil`) 320 | as long as it is (de)serializable by the cjson module. 321 | 322 | If the `unique` parameter is provided then only one worker will execute the event, 323 | the other workers will ignore it. Also any follow up events with the same `unique` 324 | value will be ignored (for the `timeout` period specified to [configure](#configure)). 325 | The process executing the event will not necessarily be the process posting the event. 326 | 327 | The return value will be `true` when the event was successfully posted or 328 | `nil + error` in case of failure. 329 | 330 | *Note*: the worker process sending the event, will also receive the event! So if 331 | the eventsource will also act upon the event, it should not do so from the event 332 | posting code, but only when receiving it. 333 | 334 | [Back to TOC](#table-of-contents) 335 | 336 | post_local 337 | ---------- 338 | `syntax: success, err = events.post_local(source, event, data)` 339 | 340 | The same as [post](#post) except that the event will be local to the worker process, 341 | it will not be broadcasted to other workers. With this method, the `data` element 342 | will not be jsonified. 343 | 344 | The return value will be `true` when the event was successfully posted or 345 | `nil + error` in case of failure. 346 | 347 | [Back to TOC](#table-of-contents) 348 | 349 | register 350 | -------- 351 | `syntax: events.register(callback, source, event1, event2, ...)` 352 | 353 | Will register a callback function to receive events. If `source` and `event` are omitted, then the 354 | callback will be executed on _every_ event, if `source` is provided, then only events with a 355 | matching source will be passed. If (one or more) event name is given, then only when 356 | both `source` and `event` match the callback is invoked. 357 | 358 | The callback should have the following signature; 359 | 360 | `syntax: callback = function(data, event, source, pid)` 361 | 362 | The parameters will be the same as the ones provided to [post](#post), except for the extra value 363 | `pid` which will be the pid of the originating worker process, or `nil` if it was a local event 364 | only. Any return value from `callback` will be discarded. 365 | *Note:* `data` may be a reference type of data (eg. a Lua `table` type). The same value is passed 366 | to all callbacks, _so do not change the value in your handler, unless you know what you are doing!_ 367 | 368 | The return value of `register` will be `true`, or it will throw an error if `callback` is not a 369 | function value. 370 | 371 | *WARNING*: event handlers must return quickly. If a handler takes more time than 372 | the configured `timeout` value, events will be dropped! 373 | 374 | *Note*: to receive the process own `started` event, the handler must be registered before 375 | calling [configure](#configure) 376 | 377 | [Back to TOC](#table-of-contents) 378 | 379 | register_weak 380 | ------------- 381 | `syntax: events.register_weak(callback, source, event1, event2, ...)` 382 | 383 | This function is identical to `register`, with the exception that the module 384 | will only hold _weak references_ to the `callback` function. 385 | 386 | [Back to TOC](#table-of-contents) 387 | 388 | unregister 389 | ---------- 390 | `syntax: events.unregister(callback, source, event1, event2, ...)` 391 | 392 | Will unregister the callback function and prevent it from receiving further events. The parameters 393 | work exactly the same as with [register](#register). 394 | 395 | The return value will be `true` if it was removed, `false` if it was not in the handlers list, or 396 | it will throw an error if `callback` is not a function value. 397 | 398 | [Back to TOC](#table-of-contents) 399 | 400 | 401 | Installation 402 | ============ 403 | 404 | Nothing special is required, install like any other pure Lua module. Just make 405 | sure its location is in the module search path. 406 | 407 | [Back to TOC](#table-of-contents) 408 | 409 | 410 | Bugs and Patches 411 | ================ 412 | 413 | Please report bugs or submit patches by creating a ticket on the [GitHub Issue Tracker](http://github.com/Kong/lua-resty-worker-events/issues), 414 | 415 | [Back to TOC](#table-of-contents) 416 | 417 | Author 418 | ====== 419 | 420 | Thijs Schreijer , Kong Inc. 421 | 422 | [Back to TOC](#table-of-contents) 423 | 424 | Copyright and License 425 | ===================== 426 | 427 | This module is licensed under the [Apache 2.0 license](https://opensource.org/licenses/Apache-2.0). 428 | 429 | Copyright (C) 2016-2020, by Thijs Schreijer, Kong Inc. 430 | 431 | All rights reserved. 432 | 433 | [Back to TOC](#table-of-contents) 434 | 435 | 436 | History 437 | ======= 438 | 439 | ### Releasing new versions 440 | 441 | - make sure changelog below is up-to-date 442 | - update version number in the code 443 | - create a new rockspec in `./rockspecs` 444 | - commit with message `release x.x.x` 445 | - tag the commit as `x.x.x` 446 | - push commit and tags 447 | - upload to luarocks 448 | 449 | ### unreleased 450 | 451 | - chore: remove redundant type checking of unique_timeout. 452 | - chore: add stacktrace to `post` errors for better debug information. 453 | 454 | ### 2.0.1, 28-June-2021 455 | 456 | - fix: possible deadlock in the `init` phase 457 | 458 | ### 2.0.0, 16-September-2020 459 | 460 | - BREAKING: the `post` function does not call `poll` anymore, making all events 461 | asynchronous. When an immediate treatment to an event is needed an explicit 462 | call to `poll` must be done. 463 | - BREAKING: the `post_local` function does not immediately execute the 464 | event anymore, making all local events asynchronous. When an immediate 465 | treatment to an event is needed an explicit call to `poll` must be done. 466 | - fix: prevent spinning at 100% CPU when during a reload the event-shm is 467 | cleared 468 | - fix: improved logging in case of failure to write to shm (add payload size 469 | for troubleshooting purposes) 470 | - fix: do not log the payload anymore, since it might expose sensitive data 471 | through the logs 472 | - change: updated `shm_retries` default to 999 473 | - change: changed timer loop to a sleep-loop (performance) 474 | - fix: when re-configuring make sure callbacks table is initialized 475 | 476 | ### 1.1.0, 23-Dec-2020 (maintenance release) 477 | 478 | - feature: the polling loop now runs forever, sleeping for 0.5 seconds between 479 | runs, avoiding to create new timers on every step. 480 | 481 | ### 1.0.0, 18-July-2019 482 | 483 | - BREAKING: the return values from `poll` (and hence also `post` and `post_local`) 484 | changed to be more lua-ish, to be truthy when all is well. 485 | - feature: new option `shm_retries` to fix "no memory" errors caused by memory 486 | fragmentation in the shm when posting events. 487 | - fix: fixed two typos in variable names (edge cases) 488 | 489 | ### 0.3.3, 8-May-2018 490 | 491 | - fix: timeouts in init phases, by removing timeout setting, see issue #9 492 | 493 | ### 0.3.2, 11-Apr-2018 494 | 495 | - change: add a stacktrace to handler errors 496 | - fix: failing error handler if value was non-serializable, see issue #5 497 | - fix: fix a test for the weak handlers 498 | 499 | 500 | [Back to TOC](#table-of-contents) 501 | 502 | 503 | See Also 504 | ======== 505 | * OpenResty: http://openresty.org 506 | 507 | [Back to TOC](#table-of-contents) 508 | 509 | -------------------------------------------------------------------------------- /lib/resty/worker/events.lua: -------------------------------------------------------------------------------- 1 | local ngx = ngx 2 | local log = ngx.log 3 | local ERR = ngx.ERR 4 | local WARN = ngx.WARN 5 | local DEBUG = ngx.DEBUG 6 | local new_timer = ngx.timer.at 7 | local tostring = tostring 8 | local ipairs = ipairs 9 | local pcall = pcall 10 | local xpcall = xpcall 11 | local cjson = require("cjson.safe").new() 12 | local get_pid = ngx.worker.pid 13 | local now = ngx.now 14 | local sleep = ngx.sleep 15 | local exiting = ngx.worker.exiting 16 | local traceback = debug.traceback 17 | local assert = assert 18 | local select = select 19 | local type = type 20 | local error = error 21 | local pairs = pairs 22 | local setmetatable = setmetatable 23 | local getmetatable = getmetatable 24 | local next = next 25 | local min = math.min 26 | 27 | -- event keys to shm 28 | local KEY_LAST_ID = "events-last" -- ID of last event posted 29 | local KEY_DATA = "events-data:" -- serialized event json data 30 | local KEY_ONE = "events-one:" -- key for 'one' events check 31 | 32 | -- constants 33 | local SLEEP_INTERVAL = 0.5 -- sleep step in the timer loop (in seconds) 34 | 35 | -- globals as upvalues (module is intended to run once per worker process) 36 | local _dict -- the shared dictionary to use 37 | local _unique_timeout -- expire time for unique data posted in shm (seconds) 38 | local _interval -- polling interval (in seconds) 39 | local _pid = get_pid() 40 | local _last_event -- event id of the last event handled 41 | local _wait_max -- how long (in seconds) to wait when we have an event id, 42 | -- but no data, for the data to show up. 43 | local _wait_interval -- interval between tries when event data is unavailable 44 | local _shm_retries -- retries for "no memory" shm fragmentation 45 | 46 | --local dump = function(...) 47 | -- ngx.log(ngx.DEBUG,"\027[31m", require("pl.pretty").write({...}),"\027[0m") 48 | --end 49 | 50 | do 51 | -- test whether xpcall is 5.2 compatible, and supports extra arguments 52 | local xpcall_52 = xpcall(function(x) 53 | assert(x == 1) 54 | end, function() end, 1) 55 | 56 | if not xpcall_52 then 57 | -- No support for extra args, so need to wrap xpcall 58 | local _xpcall = xpcall 59 | local unpack = unpack or table.unpack -- luacheck: ignore 60 | xpcall = function(f, eh, ...) 61 | local args = { n = select("#", ...), ...} 62 | return _xpcall(function() 63 | return f(unpack(args, 1, args.n)) 64 | end, eh) 65 | end 66 | end 67 | end 68 | 69 | -- defaults 70 | local DEFAULT_UNIQUE_TIMEOUT = 2 71 | local DEFAULT_INTERVAL = 1 72 | local DEFAULT_WAIT_MAX = 0.5 73 | local DEFAULT_WAIT_INTERVAL = 0.010 74 | local DEFAULT_SHM_RETRIES = 999 75 | 76 | -- creates a new level structure for the callback tree 77 | local new_struct = function() 78 | return { 79 | weak_count = 0, 80 | weak_list = setmetatable({},{ __mode = "v"}), 81 | strong_count = 0, 82 | strong_list = {}, 83 | subs = {} -- nested sub tables; source based, and event based 84 | -- (initial one is global) 85 | } 86 | end 87 | -- metatable that auto creates sub tables if a key is not found 88 | -- __index function to do the auto table magic 89 | local autotable__index = function(self, key) 90 | local mt = getmetatable(self) 91 | local t = new_struct() 92 | if mt.depth ~= 1 then 93 | setmetatable(t.subs, { 94 | __index = mt.__index, 95 | depth = mt.depth - 1, 96 | }) 97 | end 98 | self[key] = t 99 | return t 100 | end 101 | 102 | --- Creates a new auto-table. 103 | -- @param depth (optional, default 1) how deep to auto-generate tables. 104 | -- The last table in the chain generated will itself not be an auto-table. 105 | -- If `depth == 0` then there is no limit. 106 | -- @param mode (optional) set the weak table behavior 107 | -- @return new auto-table 108 | local function autotable(depth) 109 | 110 | local at = new_struct() 111 | setmetatable(at.subs, { 112 | __index = autotable__index, 113 | depth = depth, 114 | }) 115 | return at 116 | end 117 | 118 | -- callbacks 119 | local _callbacks = autotable(2) 120 | -- strong/weak; array = global handlers called on every event 121 | -- strong/weak; hash = subtables for a specific eventsource 122 | -- eventsource-sub-table has the same structure, except the hash part contains 123 | -- not 'eventsource', but 'event' specific handlers, no more sub tables 124 | 125 | local local_event_queue = {} 126 | 127 | local _M = { 128 | _VERSION = '2.0.1', 129 | } 130 | 131 | 132 | -- gets current event id 133 | -- @return event_id 134 | local function get_event_id() 135 | return _dict:get(KEY_LAST_ID) or 0 136 | end 137 | 138 | -- gets event data 139 | -- @return event_id, or nil+error 140 | local function get_event_data(event_id) 141 | return _dict:get(KEY_DATA..tostring(event_id)) 142 | end 143 | 144 | -- posts a new event in shm 145 | local function post_event(source, event, data, unique) 146 | local json, err, event_id, success, retries 147 | 148 | json, err = cjson.encode({ 149 | source = source, 150 | event = event, 151 | data = data, 152 | unique = unique, 153 | pid = _pid, 154 | }) 155 | if not json then return json, err end 156 | 157 | _dict:add(KEY_LAST_ID, 0) 158 | event_id, err = _dict:incr(KEY_LAST_ID, 1) 159 | if err then return event_id, err end 160 | 161 | retries = 0 162 | while not success and retries <= _shm_retries do 163 | success, err = _dict:add(KEY_DATA..tostring(event_id), json) 164 | if success then 165 | return event_id 166 | 167 | elseif err ~= "no memory" then 168 | return success, err 169 | 170 | elseif retries >= _shm_retries then 171 | log(WARN, "worker-events: could not write to shm after ", retries + 1, 172 | " tries (no memory), it is either fragmented or cannot ", 173 | "allocate more memory, consider increasing ", 174 | "'opts.shm_retries'. Payload size: ", #json, " bytes, ", 175 | "source: '", tostring(source), "', event: '", tostring(event)) 176 | return success, err 177 | end 178 | 179 | retries = retries + 1 180 | end 181 | -- unreachable 182 | end 183 | 184 | 185 | local function do_handlerlist(handler_list, source, event, data, pid) 186 | local err, success 187 | 188 | local count_key = "weak_count" 189 | local list_key = "weak_list" 190 | while true do 191 | local i = 1 192 | local list = handler_list[list_key] 193 | while i <= handler_list[count_key] do 194 | local handler = list[i] 195 | if type(handler) ~= "function" then 196 | -- handler was removed, unregistered, or GC'ed, cleanup. 197 | -- Entry is nil, but recreated as a table due to the auto-table 198 | list[i] = list[handler_list[count_key]] 199 | list[handler_list[count_key]] = nil 200 | handler_list[count_key] = handler_list[count_key] - 1 201 | else 202 | success, err = xpcall(handler, traceback, data, event, source, pid) 203 | if not success then 204 | local d, e 205 | if type(data) == "table" then 206 | d, e = cjson.encode(data) 207 | if not d then d = tostring(e) end 208 | else 209 | d = tostring(data) 210 | end 211 | log(ERR, "worker-events: event callback failed; source=",source, 212 | ", event=", event,", pid=",pid, " error='", tostring(err), 213 | "', data=", d) 214 | end 215 | i = i + 1 216 | end 217 | end 218 | if list_key == "strong_list" then 219 | return 220 | end 221 | count_key = "strong_count" 222 | list_key = "strong_list" 223 | end 224 | end 225 | 226 | 227 | local function do_event(source, event, data, pid) 228 | log(DEBUG, "worker-events: handling event; source=",source, 229 | ", event=", event, ", pid=", pid) --,", data=",tostring(data)) 230 | -- do not log potentially private data, hence skip 'data' 231 | 232 | local list = _callbacks 233 | do_handlerlist(list, source, event, data, pid) 234 | list = list.subs[source] 235 | do_handlerlist(list, source, event, data, pid) 236 | list = list.subs[event] 237 | do_handlerlist(list, source, event, data, pid) 238 | end 239 | 240 | 241 | -- for 'one' events, returns `true` when this worker is supposed to handle it 242 | local function mine_to_have(id, unique) 243 | local key = KEY_ONE .. tostring(unique) 244 | local success, err = _dict:add(key, _pid, _unique_timeout) 245 | 246 | if success then return true end 247 | 248 | if err == "exists" then 249 | log(DEBUG, "worker-events: skipping event ",id," was handled by worker ", 250 | _dict:get(key)) 251 | else 252 | log(ERR, "worker-events: cannot determine who handles event ", id, 253 | ", dropping it: ", err) 254 | end 255 | end 256 | 257 | -- Handle incoming json based event 258 | local function do_event_json(id, json) 259 | local d, err 260 | d, err = cjson.decode(json) 261 | if not d then 262 | return log(ERR, "worker-events: failed decoding json event data: ", err) 263 | end 264 | 265 | if d.unique and not mine_to_have(id, d.unique) then return end 266 | 267 | return do_event(d.source, d.event, d.data, d.pid) 268 | end 269 | 270 | -- Posts a new event. 271 | -- @param source string identifying the event source 272 | -- @param event string identifying the event name 273 | -- @param data the data for the event, anything as long as it can be used 274 | -- with cjson 275 | -- @param unique a unique identifier for this event, providing it will make 276 | -- only 1 277 | -- worker execute the event 278 | -- @return true if the event was successfully posted, nil+error if there was an 279 | -- error posting the event 280 | _M.post = function(source, event, data, unique) 281 | if type(source) ~= "string" or source == "" then 282 | return nil, "source is required" 283 | end 284 | 285 | if type(event) ~= "string" or event == "" then 286 | return nil, "event is required" 287 | end 288 | 289 | local success, err = post_event(source, event, data, unique) 290 | if not success then 291 | err = 'failed posting event "' .. event .. '" by "' .. 292 | source .. '"; ' .. tostring(err) 293 | log(ERR, "worker-events: ", debug.traceback(err)) 294 | return nil, err 295 | end 296 | 297 | return true 298 | end 299 | 300 | -- Similar to `post`. The event will only be handled in the worker 301 | -- it was posted from, it will not be broadcasted to other worker processes. 302 | -- @return `true` or nil+error 303 | _M.post_local = function(source, event, data) 304 | if type(source) ~= "string" or source == "" then 305 | return nil, "source is required" 306 | end 307 | if type(event) ~= "string" or event == "" then 308 | return nil, "event is required" 309 | end 310 | 311 | local_event_queue[#local_event_queue + 1] = { 312 | source = source, 313 | event = event, 314 | data = data, 315 | } 316 | 317 | return true 318 | end 319 | 320 | -- flag to indicate we're already in a polling loop 321 | local _busy_polling 322 | 323 | -- poll for events and execute handlers 324 | -- @return `"done"` when all is done, `"recursive"` if a loop is already 325 | -- running, or `nil+error` 326 | _M.poll = function() 327 | if _busy_polling then 328 | -- we're probably calling the `poll` method from an event 329 | -- handler so we cannot handle it here right now. 330 | return "recursive" 331 | end 332 | 333 | while #local_event_queue > 0 do 334 | -- exchange queue with a new one, so we can post new ones while 335 | -- dealing with existing ones 336 | local queue = local_event_queue 337 | local_event_queue = {} 338 | 339 | -- deal with local events 340 | for i, data in ipairs(queue) do 341 | _busy_polling = true -- need to flag to make sure the eventhandlers do not re-enter 342 | do_event(data.source, data.event, data.data, nil) 343 | _busy_polling = nil 344 | end 345 | end 346 | 347 | local event_id, err = get_event_id() 348 | if event_id <= _last_event then 349 | -- if event_id < _last_event then a reload is executed whilst clearing the SHM 350 | return "done" 351 | end 352 | 353 | if not event_id then 354 | local err = "failed to get current event id: " .. tostring(err) 355 | log(ERR, "worker-events: ", err) 356 | return nil, err 357 | end 358 | 359 | local count = 0 360 | local cache_data = {} 361 | local cache_err = {} 362 | -- in case an event id has been published, but we're fetching it before 363 | -- its data was posted and we have to wait, we don't want the next 364 | -- event to timeout before we get to it, so go and cache what's 365 | -- available, to minimize lost data 366 | while _last_event < event_id do 367 | count = count + 1 368 | _last_event = _last_event + 1 369 | --debug("fetching event", _last_event) 370 | cache_data[count], cache_err[count] = get_event_data(_last_event) 371 | end 372 | 373 | local expire = now() + _wait_max 374 | for idx = 1, count do 375 | local data = cache_data[idx] 376 | local err = cache_err[idx] 377 | while not data do 378 | if err then 379 | log(ERR, "worker-events: error fetching event data: ", err) 380 | break 381 | else 382 | -- just nil, so must wait for data to appear 383 | if now() >= expire then 384 | break 385 | end 386 | -- wait and retry 387 | -- if the `sleep` function is unavailable in the current openresty 388 | -- 'context' (eg. 'init_worker'), then the pcall fails. We're not 389 | -- checking the result, but will effectively be doing a busy-wait 390 | -- by looping until it hits the time-out, or the data is retrieved 391 | _busy_polling = true -- need to flag because `sleep` will yield control 392 | -- and another coroutine might re-enter 393 | if not pcall(sleep, _wait_interval) then 394 | -- `pcall` failed, so this is a non-yieldable phase to prevent a deadlock we 395 | -- must force update the cached time reported by `ngx.now`. 396 | ngx.update_time() 397 | end 398 | _busy_polling = nil 399 | data, err = get_event_data(_last_event - count + idx) 400 | end 401 | end 402 | 403 | if data then 404 | _busy_polling = true -- need to flag to make sure the eventhandlers 405 | -- do not re-enter 406 | do_event_json(_last_event - count + idx, data) 407 | _busy_polling = nil 408 | else 409 | log(ERR, "worker-events: dropping event; waiting for event data ", 410 | "timed out, id: ", _last_event - count + idx) 411 | end 412 | end 413 | 414 | -- in case we waited, recurse to handle any new pending events 415 | return _M.poll() 416 | end 417 | 418 | -- executes a polling loop 419 | local function do_timer(premature) 420 | while true do 421 | if premature then 422 | _M.post(_M.events._source, _M.events.stopping) 423 | end 424 | 425 | local ok, err = _M.poll() 426 | if not ok then 427 | log(ERR, "worker-events: timer-poll returned: ", err) 428 | end 429 | 430 | if _interval == 0 or premature then 431 | break -- exit overall timer loop 432 | end 433 | 434 | local sleep_left = _interval 435 | while sleep_left > 0 do 436 | sleep(min(sleep_left, SLEEP_INTERVAL)) 437 | sleep_left = sleep_left - SLEEP_INTERVAL 438 | 439 | if exiting() then 440 | premature = true 441 | break -- exit sleep loop only 442 | end 443 | end 444 | end 445 | end 446 | 447 | -- @param mode either "weak" or "strong" 448 | local register = function(callback, mode, source, ...) 449 | assert(type(callback) == "function", "expected function, got: ".. 450 | type(callback)) 451 | 452 | local count_key, list_key 453 | if mode == "weak" then 454 | count_key = "weak_count" 455 | list_key = "weak_list" 456 | else 457 | count_key = "strong_count" 458 | list_key = "strong_list" 459 | end 460 | 461 | if not source then 462 | -- register as global event handler 463 | local list = _callbacks 464 | local n = list[count_key] + 1 465 | list[count_key] = n 466 | list[list_key][n] = callback 467 | else 468 | local events = {...} 469 | if #events == 0 then 470 | -- register as an eventsource handler 471 | local list = _callbacks.subs[source] 472 | local n = list[count_key] + 1 473 | list[count_key] = n 474 | list[list_key][n] = callback 475 | else 476 | -- register as an event specific handler, for multiple events 477 | for _, event in ipairs(events) do 478 | local list = _callbacks.subs[source].subs[event] 479 | local n = list[count_key] + 1 480 | list[count_key] = n 481 | list[list_key][n] = callback 482 | end 483 | end 484 | end 485 | return true 486 | end 487 | 488 | 489 | -- registers an event handler callback. 490 | -- signature; callback(source, event, data, originating_pid) 491 | -- @param callback the eventhandler callback to add 492 | -- @param source (optional) if given only this source is being called for 493 | -- @param ... (optional) event names (0 or more) to register for 494 | -- @return true 495 | _M.register = function(callback, source, ...) 496 | register(callback, "strong", source, ...) 497 | end 498 | 499 | -- registers a weak-event handler callback. 500 | -- Workerevents will maintain a weak reference to the handler. 501 | -- signature; callback(source, event, data, originating_pid) 502 | -- @param callback the eventhandler callback to add 503 | -- @param source (optional) if given only this source is being called for 504 | -- @param ... (optional) event names (0 or more) to register for 505 | -- @return true 506 | _M.register_weak = function(callback, source, ...) 507 | register(callback, "weak", source, ...) 508 | end 509 | 510 | -- unregisters an event handler callback. 511 | -- Will remove both the weak and strong references. 512 | -- @param callback the eventhandler callback to remove 513 | -- @return `true` if it was removed, `false` if it was not in the list. 514 | -- If multiple eventnames have been specified, `true` means at least 1 515 | -- occurrence was removed 516 | _M.unregister = function(callback, source, ...) 517 | assert(type(callback) == "function", "expected function, got: ".. 518 | type(callback)) 519 | 520 | local success 521 | local count_key = "weak_count" 522 | local list_key = "weak_list" 523 | -- NOTE: we only set entries to `nil`, the event runner will 524 | -- cleanup and remove those entries to 'heal' the lists 525 | while true do 526 | local list = _callbacks 527 | if not source then 528 | -- remove as global event handler 529 | for i = 1, list[count_key] do 530 | local cb = list[list_key][i] 531 | if cb == callback then 532 | list[list_key][i] = nil 533 | success = true 534 | end 535 | end 536 | else 537 | local events = {...} 538 | if not next(events) then 539 | -- remove as an eventsource handler 540 | local target = list.subs[source] 541 | for i = 1, target[count_key] do 542 | local cb = target[list_key][i] 543 | if cb == callback then 544 | target[list_key][i] = nil 545 | success = true 546 | end 547 | end 548 | else 549 | -- remove as an event specific handler, for multiple events 550 | for _, event in ipairs(events) do 551 | local target = list.subs[source].subs[event] 552 | for i = 1, target[count_key] do 553 | local cb = target[list_key][i] 554 | if cb == callback then 555 | target[list_key][i] = nil 556 | success = true 557 | end 558 | end 559 | end 560 | end 561 | end 562 | if list_key == "strong_list" then 563 | break 564 | end 565 | count_key = "strong_count" 566 | list_key = "strong_list" 567 | end 568 | 569 | return (success == true) 570 | end 571 | 572 | -- (re) configures the event system 573 | -- shm : name of the shared memory to use 574 | -- timeout : timeout of event data stored in shm (in seconds) 575 | -- interval: interval to poll for events (in seconds) 576 | -- wait_interval : interval between two tries when an eventid is found, 577 | -- but no data. 578 | -- wait_max: max time to wait for data when event id is found, before discarding 579 | -- shm_retries: how often to retry when the shm gives an "out of memory" when posting 580 | -- debug : if true a value `_callbacks` is exported on the module table 581 | _M.configure = function(opts) 582 | assert(type(opts) == "table", "Expected a table, got "..type(opts)) 583 | 584 | local started = _dict ~= nil 585 | 586 | if get_pid() ~= _pid then 587 | -- pid changed, so new process was forked, must reset 588 | _pid = get_pid() 589 | --_dict = nil -- this value can actually stay, because its shared 590 | _interval = nil 591 | _unique_timeout = nil 592 | _callbacks = autotable(2) 593 | _wait_max = nil 594 | _wait_interval = nil 595 | _shm_retries = nil 596 | started = nil 597 | end 598 | 599 | local shm = opts.shm 600 | if shm and (_dict ~= nil) then 601 | return nil, "Already started, cannot change shm" 602 | end 603 | if not (shm or _dict) then 604 | return nil, '"shm" option required to start' 605 | end 606 | 607 | local dict = ngx.shared[shm] 608 | if not dict then 609 | return nil, 'shm "' .. tostring(shm) .. '" not found' 610 | end 611 | 612 | local unique_timeout = opts.timeout or 613 | (_unique_timeout or DEFAULT_UNIQUE_TIMEOUT) 614 | if type(unique_timeout) ~= "number" then 615 | return nil, 'optional "timeout" option must be a number' 616 | end 617 | if unique_timeout <= 0 then 618 | return nil, '"timeout" must be greater than 0' 619 | end 620 | 621 | local interval = opts.interval or (_interval or DEFAULT_INTERVAL) 622 | if type(interval) ~= "number" and interval ~= nil then 623 | return nil, 'optional "interval" option must be a number' 624 | end 625 | if interval < 0 then 626 | return nil, '"interval" must be greater than or equal to 0' 627 | end 628 | 629 | local wait_interval = opts.wait_interval or (_wait_interval or 630 | DEFAULT_WAIT_INTERVAL) 631 | if type(wait_interval) ~= "number" and wait_interval ~= nil then 632 | return nil, 'optional "wait_interval" option must be a number' 633 | end 634 | if wait_interval < 0 then 635 | return nil, '"wait_interval" must be greater than or equal to 0' 636 | end 637 | 638 | local wait_max = opts.wait_max or (_wait_max or DEFAULT_WAIT_MAX) 639 | if type(wait_max) ~= "number" and wait_max ~= nil then 640 | return nil, 'optional "wait_max" option must be a number' 641 | end 642 | if wait_max < 0 then 643 | return nil, '"wait_max" must be greater than or equal to 0' 644 | end 645 | 646 | local shm_retries = opts.shm_retries or (_shm_retries or DEFAULT_SHM_RETRIES) 647 | if type(shm_retries) ~= "number" and shm_retries ~= nil then 648 | return nil, 'optional "shm_retries" option must be a number' 649 | end 650 | if shm_retries < 0 then 651 | return nil, '"shm_retries" must be 0 or greater' 652 | end 653 | 654 | local old_interval = _interval 655 | _interval = interval 656 | _dict = dict 657 | _unique_timeout = unique_timeout 658 | _wait_interval = wait_interval 659 | _wait_max = wait_max 660 | _shm_retries = shm_retries 661 | _last_event = _last_event or get_event_id() 662 | 663 | if not started then 664 | -- we're live, let's celebrate it with an event 665 | local id, err = _M.post(_M.events._source, _M.events.started) 666 | if not id then return id, err end 667 | end 668 | 669 | if not old_interval then 670 | -- haven't got a timer setup yet, must create one 671 | local success, err = new_timer(0, do_timer) 672 | if not success then 673 | if err == "process exiting" then 674 | _M.post(_M.events._source, _M.events.stopping) 675 | end 676 | err = "failed to create timer: " .. tostring(err) 677 | log(ERR, "worker-events: ", err) 678 | return nil, err 679 | end 680 | 681 | else 682 | _M.poll() 683 | end 684 | 685 | if opts.debug then 686 | _M._callbacks = _callbacks 687 | end 688 | 689 | return true 690 | end 691 | 692 | -- Check whether the event module has already been configured 693 | -- @return `true` if configured and ready to accept events, or `false` if not 694 | _M.configured = function() 695 | return (_dict ~= nil) 696 | end 697 | 698 | -- Utility function to generate event lists and prevent typos in 699 | -- magic strings. Accessing a non-existing event on the table will result in 700 | -- an unknown event error. 701 | -- @param source string with the event source name 702 | -- @param ... vararg, strings, with all events available 703 | -- @return events table where key `_source` contains the event source name and 704 | -- all other eventnames are in the hashtable by their own name. 705 | _M.event_list = function(source, ...) 706 | local events = { _source = source } 707 | for _, event in pairs({...}) do 708 | events[event] = event 709 | end 710 | return setmetatable(events, { 711 | __index = function(self, key) 712 | error("event '"..tostring(key).."' is an unknown event", 2) 713 | end 714 | }) 715 | end 716 | 717 | _M.events = _M.event_list( 718 | "resty-worker-events", -- event source for own events 719 | "started", -- event when started 720 | "stopping") -- event when stopping 721 | 722 | return _M 723 | -------------------------------------------------------------------------------- /lua-resty-worker-events-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "lua-resty-worker-events" 2 | local package_version = "scm" 3 | local rockspec_revision = "1" 4 | local github_account_name = "Kong" 5 | local github_repo_name = package_name 6 | local git_checkout = package_version == "scm" and "master" or package_version 7 | 8 | 9 | package = package_name 10 | version = package_version.."-"..rockspec_revision 11 | source = { 12 | url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", 13 | tag = git_checkout, 14 | } 15 | 16 | description = { 17 | summary = "Cross worker eventbus for OpenResty", 18 | detailed = [[ 19 | lua-resty-worker-events is a module that can emit events 20 | to be handled local (in the worker emitting the event), global 21 | (in all worker processes), or once (only in one worker). 22 | The order of the events is guaranteed the same in all workers. 23 | ]], 24 | license = "Apache 2.0", 25 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 26 | } 27 | 28 | dependencies = { 29 | } 30 | 31 | build = { 32 | type = "builtin", 33 | modules = { 34 | ["resty.worker.events"] = "lib/resty/worker/events.lua", 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-worker-events-0.3.3-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-worker-events" 2 | version = "0.3.3-1" 3 | source = { 4 | url = "https://github.com/Kong/lua-resty-worker-events/archive/0.3.3.tar.gz", 5 | dir = "lua-resty-worker-events-0.3.3" 6 | } 7 | description = { 8 | summary = "Cross worker eventbus for OpenResty", 9 | detailed = [[ 10 | lua-resty-worker-events is a module that can emit events 11 | to be handled local (in the worker emitting the event), global 12 | (in all worker processes), or once (only in one worker). 13 | The order of the events is guaranteed the same in all workers. 14 | ]], 15 | license = "Apache 2.0", 16 | homepage = "https://github.com/Kong/lua-resty-worker-events" 17 | } 18 | dependencies = { 19 | } 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | ["resty.worker.events"] = "lib/resty/worker/events.lua", 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-worker-events-1.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-worker-events" 2 | version = "1.0.0-1" 3 | source = { 4 | url = "https://github.com/Kong/lua-resty-worker-events/archive/1.0.0.tar.gz", 5 | dir = "lua-resty-worker-events-1.0.0" 6 | } 7 | description = { 8 | summary = "Cross worker eventbus for OpenResty", 9 | detailed = [[ 10 | lua-resty-worker-events is a module that can emit events 11 | to be handled local (in the worker emitting the event), global 12 | (in all worker processes), or once (only in one worker). 13 | The order of the events is guaranteed the same in all workers. 14 | ]], 15 | license = "Apache 2.0", 16 | homepage = "https://github.com/Kong/lua-resty-worker-events" 17 | } 18 | dependencies = { 19 | } 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | ["resty.worker.events"] = "lib/resty/worker/events.lua", 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-worker-events-2.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-worker-events" 2 | version = "2.0.0-1" 3 | source = { 4 | url = "https://github.com/Kong/lua-resty-worker-events/archive/2.0.0.tar.gz", 5 | dir = "lua-resty-worker-events-2.0.0" 6 | } 7 | description = { 8 | summary = "Cross worker eventbus for OpenResty", 9 | detailed = [[ 10 | lua-resty-worker-events is a module that can emit events 11 | to be handled local (in the worker emitting the event), global 12 | (in all worker processes), or once (only in one worker). 13 | The order of the events is guaranteed the same in all workers. 14 | ]], 15 | license = "Apache 2.0", 16 | homepage = "https://github.com/Kong/lua-resty-worker-events" 17 | } 18 | dependencies = { 19 | } 20 | build = { 21 | type = "builtin", 22 | modules = { 23 | ["resty.worker.events"] = "lib/resty/worker/events.lua", 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rockspecs/lua-resty-worker-events-2.0.1-1.rockspec: -------------------------------------------------------------------------------- 1 | local package_name = "lua-resty-worker-events" 2 | local package_version = "2.0.1" 3 | local rockspec_revision = "1" 4 | local github_account_name = "Kong" 5 | local github_repo_name = package_name 6 | local git_checkout = package_version == "scm" and "master" or package_version 7 | 8 | 9 | package = package_name 10 | version = package_version.."-"..rockspec_revision 11 | source = { 12 | url = "git://github.com/"..github_account_name.."/"..github_repo_name..".git", 13 | tag = git_checkout, 14 | } 15 | 16 | description = { 17 | summary = "Cross worker eventbus for OpenResty", 18 | detailed = [[ 19 | lua-resty-worker-events is a module that can emit events 20 | to be handled local (in the worker emitting the event), global 21 | (in all worker processes), or once (only in one worker). 22 | The order of the events is guaranteed the same in all workers. 23 | ]], 24 | license = "Apache 2.0", 25 | homepage = "https://github.com/"..github_account_name.."/"..github_repo_name, 26 | } 27 | 28 | dependencies = { 29 | } 30 | 31 | build = { 32 | type = "builtin", 33 | modules = { 34 | ["resty.worker.events"] = "lib/resty/worker/events.lua", 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use lib 'lib'; 3 | use Test::Nginx::Socket::Lua; 4 | use Cwd qw(cwd); 5 | 6 | #worker_connections(1014); 7 | #master_process_enabled(1); 8 | #log_level('warn'); 9 | 10 | #repeat_each(2); 11 | 12 | plan tests => repeat_each() * (blocks() * 6 - 9); 13 | 14 | my $pwd = cwd(); 15 | 16 | our $HttpConfig = <<_EOC_; 17 | lua_socket_log_errors off; 18 | lua_package_path "$pwd/lib/?.lua;;"; 19 | _EOC_ 20 | 21 | #no_diff(); 22 | no_long_string(); 23 | master_on(); 24 | run_tests(); 25 | 26 | __DATA__ 27 | 28 | === TEST 1: worker.events starting and stopping, with its own events 29 | --- SKIP 30 | --- http_config eval 31 | "$::HttpConfig" 32 | . q{ 33 | upstream foo.com { 34 | server 127.0.0.1:12354; 35 | } 36 | 37 | server { 38 | listen 12354; 39 | location = /status { 40 | return 200; 41 | } 42 | } 43 | 44 | lua_shared_dict worker_events 1m; 45 | init_worker_by_lua ' 46 | ngx.shared.worker_events:flush_all() 47 | local we = require "resty.worker.events" 48 | we.register(function(data, event, source, pid) 49 | ngx.log(ngx.DEBUG, "worker-events: handler event; ","source=",source,", event=",event, ", pid=", pid, 50 | ", data=", data) 51 | end) 52 | local ok, err = we.configure{ 53 | shm = "worker_events", 54 | interval = 0.001, 55 | } 56 | if not ok then 57 | ngx.log(ngx.ERR, "failed to configure worker events: ", err) 58 | return 59 | end 60 | '; 61 | } 62 | --- config 63 | location = /t { 64 | access_log off; 65 | content_by_lua ' 66 | ngx.print("hello world\\n") 67 | local f = assert(io.open("t/servroot/logs/nginx.pid")) 68 | local pid = assert(tonumber(f:read()), "read pid") 69 | f:close() 70 | assert(os.execute("kill -HUP "..pid)) 71 | '; 72 | } 73 | 74 | --- request 75 | GET /t 76 | 77 | --- response_body 78 | hello world 79 | --- no_error_log 80 | [error] 81 | [alert] 82 | [warn] 83 | dropping event: waiting for event data timed out 84 | --- grep_error_log eval: qr/worker-events: .*|gracefully .*/ 85 | --- grep_error_log_out eval 86 | qr/^worker-events: handling event; source=resty-worker-events, event=started, pid=\d+, data=nil 87 | worker-events: handler event; source=resty-worker-events, event=started, pid=\d+, data=nil 88 | worker-events: handling event; source=resty-worker-events, event=started, pid=\d+, data=nil 89 | worker-events: handler event; source=resty-worker-events, event=started, pid=\d+, data=nil 90 | gracefully shutting down 91 | worker-events: handling event; source=resty-worker-events, event=stopping, pid=\d+, data=nil 92 | worker-events: handler event; source=resty-worker-events, event=stopping, pid=\d+, data=nil 93 | worker-events: handling event; source=resty-worker-events, event=stopping, pid=\d+, data=nil 94 | worker-events: handler event; source=resty-worker-events, event=stopping, pid=\d+, data=nil$/ 95 | --- timeout: 6 96 | --- wait: 0.2 97 | 98 | 99 | 100 | === TEST 2: worker.events posting and handling events, broadcast and local 101 | --- http_config eval 102 | "$::HttpConfig" 103 | . q{ 104 | upstream foo.com { 105 | server 127.0.0.1:12354; 106 | } 107 | 108 | server { 109 | listen 12354; 110 | location = /status { 111 | return 200; 112 | } 113 | } 114 | 115 | lua_shared_dict worker_events 1m; 116 | init_worker_by_lua ' 117 | ngx.shared.worker_events:flush_all() 118 | local we = require "resty.worker.events" 119 | we.register(function(data, event, source, pid) 120 | ngx.log(ngx.DEBUG, "worker-events: handler event; ","source=",source,", event=",event, ", pid=", pid, 121 | ", data=", data) 122 | end) 123 | local ok, err = we.configure{ 124 | interval = 0.1, 125 | shm = "worker_events", 126 | } 127 | if not ok then 128 | ngx.log(ngx.ERR, "failed to configure worker events: ", err) 129 | return 130 | end 131 | '; 132 | } 133 | --- config 134 | location = /t { 135 | access_log off; 136 | content_by_lua_block { 137 | local we = require "resty.worker.events" 138 | we.post("content_by_lua","request1","01234567890") 139 | we.post_local("content_by_lua","request2","01234567890") 140 | we.post("content_by_lua","request3","01234567890") 141 | ngx.say("hello world") 142 | ngx.sleep(0.2) -- wait for the polling interval 143 | } 144 | } 145 | 146 | --- request 147 | GET /t 148 | 149 | --- response_body 150 | hello world 151 | --- no_error_log 152 | [error] 153 | [alert] 154 | [warn] 155 | dropping event: waiting for event data timed out 156 | --- grep_error_log eval: qr/worker-events: .*/ 157 | --- grep_error_log_out eval 158 | qr/^worker-events: handling event; source=resty-worker-events, event=started, pid=\d+ 159 | worker-events: handler event; source=resty-worker-events, event=started, pid=\d+, data=nil 160 | worker-events: handling event; source=content_by_lua, event=request2, pid=nil 161 | worker-events: handler event; source=content_by_lua, event=request2, pid=nil, data=01234567890 162 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 163 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=01234567890 164 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ 165 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=01234567890$/ 166 | --- timeout: 6 167 | 168 | 169 | 170 | === TEST 3: worker.events handling remote events 171 | --- http_config eval 172 | "$::HttpConfig" 173 | . q{ 174 | upstream foo.com { 175 | server 127.0.0.1:12354; 176 | } 177 | 178 | server { 179 | listen 12354; 180 | location = /status { 181 | return 200; 182 | } 183 | } 184 | 185 | lua_shared_dict worker_events 1m; 186 | init_worker_by_lua ' 187 | ngx.shared.worker_events:flush_all() 188 | local we = require "resty.worker.events" 189 | we.register(function(data, event, source, pid) 190 | ngx.log(ngx.DEBUG, "worker-events: handler event; ","source=",source,", event=",event, ", pid=", pid, 191 | ", data=", tostring(data)) 192 | end) 193 | local ok, err = we.configure{ 194 | interval = 0.1, 195 | shm = "worker_events", 196 | } 197 | 198 | local cjson = require("cjson.safe").new() 199 | 200 | local event_id = assert(ngx.shared.worker_events:incr("events-last", 1)) 201 | assert(ngx.shared.worker_events:add("events-data:"..tostring(event_id), 202 | cjson.encode({ source="hello", event="1", data="there-1", pid=123456}), 2)) 203 | 204 | local event_id = assert(ngx.shared.worker_events:incr("events-last", 1)) 205 | assert(ngx.shared.worker_events:add("events-data:"..tostring(event_id), 206 | cjson.encode({ source="hello", event="2", data="there-2", pid=123456}), 2)) 207 | 208 | local event_id = assert(ngx.shared.worker_events:incr("events-last", 1)) 209 | assert(ngx.shared.worker_events:add("events-data:"..tostring(event_id), 210 | cjson.encode({ source="hello", event="3", data="there-3", pid=123456}), 2)) 211 | 212 | if not ok then 213 | ngx.log(ngx.ERR, "failed to configure worker events: ", err) 214 | return 215 | end 216 | '; 217 | } 218 | --- config 219 | location = /t { 220 | access_log off; 221 | content_by_lua_block { 222 | local cjson = require("cjson.safe").new() 223 | ngx.sleep(0.1) 224 | local we = require "resty.worker.events" 225 | we.post("content_by_lua","request1","01234567890") 226 | we.post_local("content_by_lua","request2","01234567890") 227 | 228 | local event_id = assert(ngx.shared.worker_events:incr("events-last", 1)) 229 | assert(ngx.shared.worker_events:add("events-data:"..tostring(event_id), 230 | cjson.encode({ source="hello", event="4", data="there-4", pid=123456}), 2)) 231 | 232 | we.post("content_by_lua","request3","01234567890") 233 | ngx.say("hello world") 234 | ngx.sleep(0.2) -- wait for the polling interval 235 | } 236 | } 237 | 238 | --- request 239 | GET /t 240 | 241 | --- response_body 242 | hello world 243 | --- no_error_log 244 | [error] 245 | [alert] 246 | [warn] 247 | dropping event: waiting for event data timed out 248 | --- grep_error_log eval: qr/worker-events: .*/ 249 | --- grep_error_log_out eval 250 | qr/^worker-events: handling event; source=resty-worker-events, event=started, pid=\d+ 251 | worker-events: handler event; source=resty-worker-events, event=started, pid=\d+, data=nil 252 | worker-events: handling event; source=hello, event=1, pid=123456 253 | worker-events: handler event; source=hello, event=1, pid=123456, data=there-1 254 | worker-events: handling event; source=hello, event=2, pid=123456 255 | worker-events: handler event; source=hello, event=2, pid=123456, data=there-2 256 | worker-events: handling event; source=hello, event=3, pid=123456 257 | worker-events: handler event; source=hello, event=3, pid=123456, data=there-3 258 | worker-events: handling event; source=content_by_lua, event=request2, pid=nil 259 | worker-events: handler event; source=content_by_lua, event=request2, pid=nil, data=01234567890 260 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 261 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=01234567890 262 | worker-events: handling event; source=hello, event=4, pid=123456 263 | worker-events: handler event; source=hello, event=4, pid=123456, data=there-4 264 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ 265 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=01234567890$/ 266 | --- timeout: 6 267 | 268 | 269 | 270 | === TEST 4: worker.events missing data, timeout 271 | --- http_config eval 272 | "$::HttpConfig" 273 | . q{ 274 | upstream foo.com { 275 | server 127.0.0.1:12354; 276 | } 277 | 278 | server { 279 | listen 12354; 280 | location = /status { 281 | return 200; 282 | } 283 | } 284 | 285 | lua_shared_dict worker_events 1m; 286 | init_worker_by_lua ' 287 | ngx.shared.worker_events:flush_all() 288 | local we = require "resty.worker.events" 289 | we.register(function(data, event, source, pid) 290 | ngx.log(ngx.DEBUG, "worker-events: handler event; ","source=",source,", event=",event, ", pid=", pid, 291 | ", data=", tostring(data)) 292 | end) 293 | local ok, err = we.configure{ 294 | shm = "worker_events", 295 | interval = 0.1, 296 | timeout = 0.2, 297 | wait_max = 0.05, 298 | wait_interval = 0.0200, 299 | } 300 | 301 | local cjson = require("cjson.safe").new() 302 | 303 | if not ok then 304 | ngx.log(ngx.ERR, "failed to configure worker events: ", err) 305 | return 306 | end 307 | '; 308 | } 309 | --- config 310 | location = /t { 311 | access_log off; 312 | content_by_lua ' 313 | local cjson = require("cjson.safe").new() 314 | ngx.sleep(0.1) 315 | local we = require "resty.worker.events" 316 | we.post("content_by_lua","request1","01234567890") 317 | we.post("content_by_lua","request2","01234567890", true) 318 | 319 | local event_id = assert(ngx.shared.worker_events:incr("events-last", 1)) 320 | 321 | we.post("content_by_lua","request3","01234567890") 322 | ngx.print("hello world\\n") 323 | ngx.sleep(0.2) -- wait for the polling interval 324 | 325 | '; 326 | } 327 | 328 | --- request 329 | GET /t 330 | 331 | --- response_body 332 | hello world 333 | --- no_error_log 334 | [alert] 335 | --- grep_error_log eval: qr/worker-events: .*|worker-events: dropping event; waiting for event data timed out*/ 336 | --- grep_error_log_out eval 337 | qr/^worker-events: handling event; source=resty-worker-events, event=started, pid=\d+ 338 | worker-events: handler event; source=resty-worker-events, event=started, pid=\d+, data=nil 339 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 340 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=01234567890 341 | worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ 342 | worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=01234567890 343 | worker-events: dropping event; waiting for event data timed out, id: 4.* 344 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ 345 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=01234567890$/ 346 | --- timeout: 6 347 | 348 | 349 | 350 | === TEST 5: worker.events 'one' being done, and only once 351 | --- http_config eval 352 | "$::HttpConfig" 353 | . q{ 354 | upstream foo.com { 355 | server 127.0.0.1:12354; 356 | } 357 | 358 | server { 359 | listen 12354; 360 | location = /status { 361 | return 200; 362 | } 363 | } 364 | 365 | lua_shared_dict worker_events 1m; 366 | init_worker_by_lua ' 367 | ngx.shared.worker_events:flush_all() 368 | local we = require "resty.worker.events" 369 | we.register(function(data, event, source, pid) 370 | ngx.log(ngx.DEBUG, "worker-events: handler event; ","source=",source,", event=",event, ", pid=", pid, 371 | ", data=", data) 372 | end) 373 | local ok, err = we.configure{ 374 | interval = 0.1, 375 | timeout = 0.04, 376 | shm = "worker_events", 377 | } 378 | if not ok then 379 | ngx.log(ngx.ERR, "failed to configure worker events: ", err) 380 | return 381 | end 382 | '; 383 | } 384 | --- config 385 | location = /t { 386 | access_log off; 387 | content_by_lua ' 388 | ngx.sleep(0.1) 389 | local we = require "resty.worker.events" 390 | we.post("content_by_lua","request1","01234567890") 391 | we.post("content_by_lua","request2","01234567890", "unique_value") 392 | we.post("content_by_lua","request3","01234567890", "unique_value") 393 | ngx.sleep(0.1) -- wait for unique timeout to expire 394 | we.post("content_by_lua","request4","01234567890", "unique_value") 395 | we.post("content_by_lua","request5","01234567890", "unique_value") 396 | we.post("content_by_lua","request6","01234567890") 397 | ngx.print("hello world\\n") 398 | ngx.sleep(0.2) -- wait for the polling interval 399 | 400 | '; 401 | } 402 | 403 | --- request 404 | GET /t 405 | 406 | --- response_body 407 | hello world 408 | --- no_error_log 409 | [error] 410 | [alert] 411 | [warn] 412 | dropping event: waiting for event data timed out 413 | --- grep_error_log eval: qr/worker-events: .*?, pid=.*/ 414 | --- grep_error_log_out eval 415 | qr/^worker-events: handling event; source=resty-worker-events, event=started, pid=\d+ 416 | worker-events: handler event; source=resty-worker-events, event=started, pid=\d+, data=nil 417 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 418 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=01234567890 419 | worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ 420 | worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=01234567890 421 | worker-events: handling event; source=content_by_lua, event=request4, pid=\d+ 422 | worker-events: handler event; source=content_by_lua, event=request4, pid=\d+, data=01234567890 423 | worker-events: handling event; source=content_by_lua, event=request6, pid=\d+ 424 | worker-events: handler event; source=content_by_lua, event=request6, pid=\d+, data=01234567890$/ 425 | --- timeout: 6 426 | 427 | 428 | 429 | === TEST 6: worker.events 'unique' being done by another worker 430 | --- http_config eval 431 | "$::HttpConfig" 432 | . q{ 433 | upstream foo.com { 434 | server 127.0.0.1:12354; 435 | } 436 | 437 | server { 438 | listen 12354; 439 | location = /status { 440 | return 200; 441 | } 442 | } 443 | 444 | lua_shared_dict worker_events 1m; 445 | init_worker_by_lua ' 446 | ngx.shared.worker_events:flush_all() 447 | local we = require "resty.worker.events" 448 | we.register(function(data, event, source, pid) 449 | ngx.log(ngx.DEBUG, "worker-events: handler event; ","source=",source,", event=",event, ", pid=", pid, 450 | ", data=", data) 451 | end) 452 | local ok, err = we.configure{ 453 | interval = 0.1, 454 | shm = "worker_events", 455 | } 456 | if not ok then 457 | ngx.log(ngx.ERR, "failed to configure worker events: ", err) 458 | return 459 | end 460 | '; 461 | } 462 | --- config 463 | location = /t { 464 | access_log off; 465 | content_by_lua ' 466 | ngx.sleep(0.1) 467 | local we = require "resty.worker.events" 468 | we.post("content_by_lua","request1","01234567890") 469 | assert(ngx.shared.worker_events:add("events-one:unique_value", 666)) 470 | we.post("content_by_lua","request2","01234567890", "unique_value") 471 | we.post("content_by_lua","request3","01234567890") 472 | ngx.print("hello world\\n") 473 | ngx.sleep(0.2) -- wait for the polling interval 474 | '; 475 | } 476 | 477 | --- request 478 | GET /t 479 | 480 | --- response_body 481 | hello world 482 | --- no_error_log 483 | [error] 484 | [alert] 485 | [warn] 486 | dropping event: waiting for event data timed out 487 | --- grep_error_log eval: qr/worker-events: .*?, pid=.*|worker-events: skipping event \d+ was handled by worker \d+/ 488 | --- grep_error_log_out eval 489 | qr/^worker-events: handling event; source=resty-worker-events, event=started, pid=\d+ 490 | worker-events: handler event; source=resty-worker-events, event=started, pid=\d+, data=nil 491 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 492 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=01234567890 493 | worker-events: skipping event 3 was handled by worker 666 494 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ 495 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=01234567890$/ 496 | --- timeout: 6 497 | 498 | 499 | 500 | === TEST 7: registering and unregistering event handlers at different levels 501 | --- http_config eval 502 | "$::HttpConfig" 503 | . q{ 504 | upstream foo.com { 505 | server 127.0.0.1:12354; 506 | } 507 | 508 | server { 509 | listen 12354; 510 | location = /status { 511 | return 200; 512 | } 513 | } 514 | 515 | lua_shared_dict worker_events 1m; 516 | init_worker_by_lua ' 517 | ngx.shared.worker_events:flush_all() 518 | local we = require "resty.worker.events" 519 | local cb = function(extra, data, event, source, pid) 520 | ngx.log(ngx.DEBUG, "worker-events: handler event; ","source=",source,", event=",event, ", pid=", pid, 521 | ", data=", data, ", callback=",extra) 522 | end 523 | ngx.cb_global = function(...) return cb("global", ...) end 524 | ngx.cb_source = function(...) return cb("source", ...) end 525 | ngx.cb_event12 = function(...) return cb("event12", ...) end 526 | ngx.cb_event3 = function(...) return cb("event3", ...) end 527 | 528 | we.register(ngx.cb_global) 529 | we.register(ngx.cb_source, "content_by_lua") 530 | we.register(ngx.cb_event12, "content_by_lua", "request1", "request2") 531 | we.register(ngx.cb_event3, "content_by_lua", "request3") 532 | 533 | local ok, err = we.configure{ 534 | interval = 0.1, 535 | shm = "worker_events", 536 | } 537 | if not ok then 538 | ngx.log(ngx.ERR, "failed to configure worker events: ", err) 539 | return 540 | end 541 | '; 542 | } 543 | --- config 544 | location = /t { 545 | access_log off; 546 | content_by_lua ' 547 | ngx.sleep(0.1) 548 | local we = require "resty.worker.events" 549 | we.post("content_by_lua","request1","123") 550 | we.post("content_by_lua","request2","123") 551 | we.post("content_by_lua","request3","123") 552 | ngx.sleep(0.2) -- wait for the polling interval 553 | we.unregister(ngx.cb_global) 554 | we.post("content_by_lua","request1","124") 555 | we.post("content_by_lua","request2","124") 556 | we.post("content_by_lua","request3","124") 557 | ngx.sleep(0.2) -- wait for the polling interval 558 | we.unregister(ngx.cb_source, "content_by_lua") 559 | we.post("content_by_lua","request1","125") 560 | we.post("content_by_lua","request2","125") 561 | we.post("content_by_lua","request3","125") 562 | ngx.sleep(0.2) -- wait for the polling interval 563 | we.unregister(ngx.cb_event12, "content_by_lua", "request1", "request2") 564 | we.post("content_by_lua","request1","126") 565 | we.post("content_by_lua","request2","126") 566 | we.post("content_by_lua","request3","126") 567 | ngx.sleep(0.2) -- wait for the polling interval 568 | we.unregister(ngx.cb_event3, "content_by_lua", "request3") 569 | we.post("content_by_lua","request1","127") 570 | we.post("content_by_lua","request2","127") 571 | we.post("content_by_lua","request3","127") 572 | ngx.print("hello world\\n") 573 | ngx.sleep(0.2) -- wait for the polling interval 574 | 575 | '; 576 | } 577 | 578 | --- request 579 | GET /t 580 | 581 | --- response_body 582 | hello world 583 | --- no_error_log 584 | [error] 585 | [alert] 586 | [warn] 587 | dropping event: waiting for event data timed out 588 | --- grep_error_log eval: qr/worker-events: .*/ 589 | --- grep_error_log_out eval 590 | qr/^worker-events: handling event; source=resty-worker-events, event=started, pid=\d+ 591 | worker-events: handler event; source=resty-worker-events, event=started, pid=\d+, data=nil, callback=global 592 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 593 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=123, callback=global 594 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=123, callback=source 595 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=123, callback=event12 596 | worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ 597 | worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=123, callback=global 598 | worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=123, callback=source 599 | worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=123, callback=event12 600 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ 601 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=123, callback=global 602 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=123, callback=source 603 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=123, callback=event3 604 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 605 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=124, callback=source 606 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=124, callback=event12 607 | worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ 608 | worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=124, callback=source 609 | worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=124, callback=event12 610 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ 611 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=124, callback=source 612 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=124, callback=event3 613 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 614 | worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=125, callback=event12 615 | worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ 616 | worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=125, callback=event12 617 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ 618 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=125, callback=event3 619 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 620 | worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ 621 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ 622 | worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=126, callback=event3 623 | worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ 624 | worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ 625 | worker-events: handling event; source=content_by_lua, event=request3, pid=\d+$/ 626 | --- timeout: 15 627 | 628 | 629 | 630 | === TEST 8: registering and GC'ing weak event handlers at different levels 631 | --- http_config eval 632 | "$::HttpConfig" 633 | . q{ 634 | upstream foo.com { 635 | server 127.0.0.1:12354; 636 | } 637 | 638 | server { 639 | listen 12354; 640 | location = /status { 641 | return 200; 642 | } 643 | } 644 | 645 | lua_shared_dict worker_events 1m; 646 | init_worker_by_lua ' 647 | ngx.shared.worker_events:flush_all() 648 | '; 649 | } 650 | --- config 651 | location = /t { 652 | access_log off; 653 | content_by_lua ' 654 | ngx.shared.worker_events:flush_all() 655 | local we = require "resty.worker.events" 656 | local ok, err = we.configure{ 657 | interval = 0.1, 658 | interval = 0.01, 659 | shm = "worker_events", 660 | } 661 | ngx.sleep(0.1) 662 | 663 | local count = 0 664 | 665 | local cb = { 666 | global = function(source, event) 667 | ngx.log(ngx.DEBUG, "global handler: ", source, ", ", event) 668 | count = count + 1 669 | end, 670 | source = function(source, event) 671 | ngx.log(ngx.DEBUG, "global source: ", source, ", ", event) 672 | count = count + 1 673 | end, 674 | event12 = function(source, event) 675 | ngx.log(ngx.DEBUG, "global event12: ", source, ", ", event) 676 | count = count + 1 677 | end, 678 | event3 = function(source, event) 679 | ngx.log(ngx.DEBUG, "global event3: ", source, ", ", event) 680 | count = count + 1 681 | end, 682 | } 683 | we.register_weak(cb.global) 684 | we.register_weak(cb.source, "content_by_lua") 685 | we.register_weak(cb.event12, "content_by_lua", "request1", "request2") 686 | we.register_weak(cb.event3, "content_by_lua", "request3") 687 | 688 | we.post("content_by_lua","request1","123") 689 | we.post("content_by_lua","request2","123") 690 | we.post("content_by_lua","request3","123") 691 | ngx.sleep(0.2) -- wait for the polling interval 692 | ngx.say("before GC:", count) 693 | 694 | cb = nil 695 | collectgarbage() 696 | collectgarbage() 697 | count = 0 698 | 699 | we.post("content_by_lua","request1","123") 700 | we.post("content_by_lua","request2","123") 701 | we.post("content_by_lua","request3","123") 702 | ngx.sleep(0.2) -- wait for the polling interval 703 | ngx.say("after GC:", count) -- 0 704 | 705 | '; 706 | } 707 | 708 | --- request 709 | GET /t 710 | --- response_body 711 | before GC:9 712 | after GC:0 713 | --- no_error_log 714 | --- timeout: 6 715 | 716 | 717 | 718 | === TEST 9: callback error handling 719 | --- http_config eval 720 | "$::HttpConfig" 721 | . q{ 722 | upstream foo.com { 723 | server 127.0.0.1:12354; 724 | } 725 | 726 | server { 727 | listen 12354; 728 | location = /status { 729 | return 200; 730 | } 731 | } 732 | 733 | lua_shared_dict worker_events 1m; 734 | init_worker_by_lua ' 735 | ngx.shared.worker_events:flush_all() 736 | '; 737 | } 738 | --- config 739 | location = /t { 740 | access_log off; 741 | content_by_lua ' 742 | ngx.shared.worker_events:flush_all() 743 | local we = require "resty.worker.events" 744 | local ok, err = we.configure{ 745 | interval = 0.1, 746 | shm = "worker_events", 747 | } 748 | local error_func = function() 749 | error("something went wrong here!") 750 | end 751 | local test_callback = function(source, event, data, pid) 752 | error_func() -- nested call to check stack trace 753 | end 754 | we.register(test_callback) 755 | 756 | -- non-serializable test data containing a function value 757 | -- use "nil" as data, reproducing issue #5 758 | we.post("content_by_lua","test_event", nil) 759 | ngx.sleep(0.2) -- wait for the polling interval 760 | 761 | ngx.say("ok") 762 | '; 763 | } 764 | 765 | --- request 766 | GET /t 767 | --- response_body 768 | ok 769 | --- error_log 770 | something went wrong here! 771 | 772 | 773 | 774 | === TEST 10: callback error stacktrace 775 | --- http_config eval 776 | "$::HttpConfig" 777 | . q{ 778 | upstream foo.com { 779 | server 127.0.0.1:12354; 780 | } 781 | 782 | server { 783 | listen 12354; 784 | location = /status { 785 | return 200; 786 | } 787 | } 788 | 789 | lua_shared_dict worker_events 1m; 790 | init_worker_by_lua ' 791 | ngx.shared.worker_events:flush_all() 792 | '; 793 | } 794 | --- config 795 | location = /t { 796 | access_log off; 797 | content_by_lua ' 798 | ngx.shared.worker_events:flush_all() 799 | local we = require "resty.worker.events" 800 | local ok, err = we.configure{ 801 | interval = 0.1, 802 | shm = "worker_events", 803 | } 804 | 805 | local error_func = function() 806 | error("something went wrong here!") 807 | end 808 | local in_between = function() 809 | error_func() -- nested call to check stack trace 810 | end 811 | local test_callback = function(source, event, data, pid) 812 | in_between() -- nested call to check stack trace 813 | end 814 | 815 | we.register(test_callback) 816 | we.post("content_by_lua","test_event") 817 | ngx.sleep(0.2) -- wait for the polling interval 818 | 819 | ngx.say("ok") 820 | '; 821 | } 822 | 823 | --- request 824 | GET /t 825 | --- response_body 826 | ok 827 | --- error_log 828 | something went wrong here! 829 | in function 'error_func' 830 | in function 'in_between' 831 | 832 | 833 | 834 | === TEST 11: shm fragmentation 835 | --- http_config eval 836 | "$::HttpConfig" 837 | . q{ 838 | upstream foo.com { 839 | server 127.0.0.1:12354; 840 | } 841 | 842 | server { 843 | listen 12354; 844 | location = /status { 845 | return 200; 846 | } 847 | } 848 | 849 | lua_shared_dict worker_events 1m; 850 | init_worker_by_lua ' 851 | ngx.shared.worker_events:flush_all() 852 | '; 853 | } 854 | --- config 855 | location = /t { 856 | access_log off; 857 | content_by_lua ' 858 | ngx.shared.worker_events:flush_all() 859 | local we = require "resty.worker.events" 860 | local ok, err = we.configure{ 861 | shm = "worker_events", 862 | shm_retries = 999, 863 | } 864 | 865 | -- fill the shm 866 | for i = 1, 1500000 do 867 | ngx.shared.worker_events:add(i, tostring(i)) 868 | end 869 | 870 | local ok, err = we.post("source", "event", ("y"):rep(1024):rep(500)) 871 | ngx.say(err == nil and "event posted") 872 | '; 873 | } 874 | 875 | --- request 876 | GET /t 877 | --- response_body 878 | event posted 879 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | # Valgrind suppression file for LuaJIT 2.0. 2 | { 3 | Optimized string compare 4 | Memcheck:Addr4 5 | fun:lj_str_cmp 6 | } 7 | { 8 | Optimized string compare 9 | Memcheck:Addr1 10 | fun:lj_str_cmp 11 | } 12 | { 13 | Optimized string compare 14 | Memcheck:Addr4 15 | fun:lj_str_new 16 | } 17 | { 18 | Optimized string compare 19 | Memcheck:Addr1 20 | fun:lj_str_new 21 | } 22 | { 23 | Optimized string compare 24 | Memcheck:Cond 25 | fun:lj_str_new 26 | } 27 | { 28 | 29 | Memcheck:Leak 30 | fun:malloc 31 | fun:ngx_alloc 32 | fun:ngx_event_process_init 33 | } 34 | { 35 | 36 | Memcheck:Param 37 | epoll_ctl(event) 38 | fun:epoll_ctl 39 | fun:ngx_epoll_add_event 40 | } 41 | { 42 | 43 | Memcheck:Param 44 | epoll_ctl(event) 45 | fun:epoll_ctl 46 | fun:ngx_epoll_add_connection 47 | } 48 | { 49 | 50 | Memcheck:Addr4 51 | fun:ngx_init_cycle 52 | fun:ngx_master_process_cycle 53 | fun:main 54 | } 55 | { 56 | 57 | Memcheck:Cond 58 | fun:ngx_init_cycle 59 | fun:ngx_master_process_cycle 60 | fun:main 61 | } 62 | { 63 | 64 | Memcheck:Cond 65 | fun:index 66 | fun:expand_dynamic_string_token 67 | fun:_dl_map_object 68 | fun:map_doit 69 | fun:_dl_catch_error 70 | fun:do_preload 71 | fun:dl_main 72 | fun:_dl_sysdep_start 73 | fun:_dl_start 74 | } 75 | { 76 | 77 | Memcheck:Param 78 | epoll_ctl(event) 79 | fun:epoll_ctl 80 | fun:ngx_epoll_init 81 | fun:ngx_event_process_init 82 | } 83 | { 84 | 85 | Memcheck:Param 86 | epoll_ctl(event) 87 | fun:epoll_ctl 88 | fun:ngx_epoll_notify_init 89 | fun:ngx_epoll_init 90 | fun:ngx_event_process_init 91 | } 92 | --------------------------------------------------------------------------------