├── .github └── workflows │ ├── lint.yml │ ├── sast.yml │ └── tests.yml ├── .gitignore ├── .luacheckrc ├── LICENSE ├── Makefile ├── README.md ├── config ├── lualib └── resty │ └── events │ ├── broker.lua │ ├── callback.lua │ ├── codec.lua │ ├── compat │ └── init.lua │ ├── disable_listening.lua │ ├── frame.lua │ ├── init.lua │ ├── protocol.lua │ ├── queue.lua │ ├── utils.lua │ └── worker.lua ├── src └── ngx_lua_events_module.c └── t ├── broadcast.t ├── callback.t ├── codec.t ├── deadlock.t ├── disable-listening.t ├── events-compat.t ├── events.t ├── listening-off.t ├── privileged.t ├── protocol-privileged.t ├── protocol.t ├── queue.t ├── retain-events.t ├── slow-client-on-startup.t ├── stream-broadcast.t ├── stream-events.t ├── stream-protocol-privileged.t └── stream-protocol.t /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: {} 5 | workflow_dispatch: {} 6 | push: 7 | branches: 8 | - main 9 | - master 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 14 | 15 | jobs: 16 | lua-check: 17 | name: Lua Check 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: read 21 | issues: read 22 | checks: write 23 | pull-requests: write 24 | if: (github.actor != 'dependabot[bot]') 25 | 26 | steps: 27 | - name: Checkout source code 28 | uses: actions/checkout@v3 29 | 30 | # Optional step to run on only changed files 31 | - name: Get changed files 32 | id: changed-files 33 | uses: kong/changed-files@4edd678ac3f81e2dc578756871e4d00c19191daf 34 | with: 35 | files: | 36 | **.lua 37 | 38 | - name: Lua Check 39 | if: steps.changed-files.outputs.any_changed == 'true' 40 | uses: Kong/public-shared-actions/code-check-actions/lua-lint@ac8939f0382827fbb43ce4e0028066a5ea4db01d # 4.1.2 41 | with: 42 | additional_args: '--no-default-config --config .luacheckrc' 43 | files: ${{ steps.changed-files.outputs.all_changed_files }} 44 | -------------------------------------------------------------------------------- /.github/workflows/sast.yml: -------------------------------------------------------------------------------- 1 | name: SAST 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - master 8 | - main 9 | workflow_dispatch: {} 10 | 11 | 12 | jobs: 13 | semgrep: 14 | name: Semgrep SAST 15 | runs-on: ubuntu-latest 16 | permissions: 17 | # required for all workflows 18 | security-events: write 19 | # only required for workflows in private repositories 20 | actions: read 21 | contents: read 22 | 23 | if: (github.actor != 'dependabot[bot]') 24 | 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: Kong/public-shared-actions/security-actions/semgrep@ac8939f0382827fbb43ce4e0028066a5ea4db01d # 4.1.2 28 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | tests: 11 | name: Tests 12 | runs-on: ubuntu-20.04 13 | 14 | strategy: 15 | matrix: 16 | include: 17 | # TODO: arm64 18 | - openresty: "1.21.4.1" 19 | - openresty: "1.21.4.3" 20 | - openresty: "1.25.3.1" 21 | - openresty: "1.27.1.1" 22 | 23 | env: 24 | JOBS: 1 # must be 1 as socket tests interfere with each other 25 | 26 | OPENRESTY: ${{ matrix.openresty }} 27 | CODE_PATH: ${{ github.workspace }} 28 | BASE_PATH: /home/runner/work/cache 29 | 30 | steps: 31 | - name: Checkout source code 32 | uses: actions/checkout@v2 33 | with: 34 | submodules: 'true' 35 | 36 | - name: Setup cache 37 | uses: actions/cache@v4 38 | id: cache-deps 39 | with: 40 | path: | 41 | /home/runner/work/cache 42 | key: ${{ runner.os }}-${{ hashFiles('**/tests.yml') }}-${{ hashFiles('**/*.c', '**/*.h') }}-openresty-${{ matrix.openresty }} 43 | 44 | - name: Install packages 45 | run: | 46 | sudo apt update 47 | sudo apt-get install -qq -y wget cpanminus net-tools libpcre3-dev build-essential valgrind 48 | if [ ! -e perl ]; then sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1); cp -r /usr/local/share/perl/ .; else sudo cp -r perl /usr/local/share; fi 49 | 50 | - name: Download OpenResty 51 | if: steps.cache-deps.outputs.cache-hit != 'true' 52 | run: | 53 | wget https://openresty.org/download/openresty-${OPENRESTY}.tar.gz 54 | mkdir -p ${BASE_PATH} 55 | tar xfz openresty-${OPENRESTY}.tar.gz -C ${BASE_PATH} 56 | 57 | - name: Setup tools 58 | if: steps.cache-deps.outputs.cache-hit != 'true' 59 | run: | 60 | cd ${BASE_PATH}/openresty-${OPENRESTY} 61 | ./configure --prefix=${BASE_PATH}/openresty --with-debug --add-module=${CODE_PATH} 62 | make && make install 63 | 64 | - name: Run Test 65 | run: | 66 | export PATH=${BASE_PATH}/openresty/bin:$PATH 67 | openresty -V 68 | make test OPENRESTY_PREFIX=${BASE_PATH}/openresty 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | *.swp 55 | servroot/ 56 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "ngx_lua" 2 | unused_args = false 3 | redefined = false 4 | max_line_length = false 5 | 6 | 7 | not_globals = { 8 | "string.len", 9 | "table.getn", 10 | } 11 | 12 | 13 | ignore = { 14 | "6.", -- ignore whitespace warnings 15 | } 16 | -------------------------------------------------------------------------------- /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 2022-2024 Kong Inc. 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/events/ 14 | $(INSTALL) -m 664 lualib/resty/events/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/events/ 15 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty/events/compat/ 16 | $(INSTALL) -m 664 lualib/resty/events/compat/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/events/compat/ 17 | 18 | test: all 19 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | lua-resty-events 3 | ======================= 4 | 5 | Inter process Pub/Sub pattern events propagation for Nginx worker processes 6 | 7 | Table of Contents 8 | ================= 9 | 10 | * [Name](#name) 11 | * [Status](#status) 12 | * [Synopsis](#synopsis) 13 | * [Description](#description) 14 | * [Methods](#methods) 15 | * [new](#new) 16 | * [init_worker](#init_worker) 17 | * [run](#run) 18 | * [publish](#publish) 19 | * [subscribe](#subscribe) 20 | * [unsubscribe](#unsubscribe) 21 | * [Copyright and License](#copyright-and-license) 22 | * [See Also](#see-also) 23 | 24 | Status 25 | ====== 26 | 27 | This library is considered production ready. 28 | 29 | Synopsis 30 | ======== 31 | 32 | ```nginx 33 | http { 34 | lua_package_path "/path/to/lua-resty-events/lib/?/init.lua;;"; 35 | 36 | init_by_lua_block { 37 | local opts = { 38 | listening = "unix:/tmp/events.sock", 39 | } 40 | 41 | local ev = require("resty.events").new(opts) 42 | if not ev then 43 | ngx.log(ngx.ERR, "failed to new events object") 44 | end 45 | 46 | -- store ev to global 47 | _G.ev = ev 48 | } 49 | 50 | init_worker_by_lua_block { 51 | -- fetch ev from global 52 | local ev = _G.ev 53 | 54 | local handler = function(data, event, source, wid) 55 | print("received event; source=", source, 56 | ", event=", event, 57 | ", data=", tostring(data), 58 | ", from process ", wid) 59 | end 60 | 61 | local id1 = ev:subscribe("*", "*", handler) 62 | local id2 = ev:subscribe("source", "*", handler) 63 | local id3 = ev:subscribe("source", "event", handler) 64 | 65 | local ok, err = ev:init_worker() 66 | if not ok then 67 | ngx.log(ngx.ERR, "failed to init events: ", err) 68 | end 69 | } 70 | 71 | # create a listening unix domain socket 72 | server { 73 | listen unix:/tmp/events.sock; 74 | location / { 75 | content_by_lua_block { 76 | -- fetch ev from global 77 | local ev = _G.ev 78 | ev:run() 79 | } 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | Description 86 | =========== 87 | 88 | This module provides a way to send events to the other worker processes in an Nginx 89 | server. Communication is through a unix domain socket which is listened by one and 90 | only one Nginx worker. 91 | 92 | The design allows for 3 usecases; 93 | 94 | 1. broadcast an event to all workers processes, see [publish](#publish). Example: 95 | a healthcheck running in one worker, but informing all workers of a failed 96 | upstream node. 97 | 2. broadcast an event to the current worker only, 98 | see `target` parameter of [publish](#publish). 99 | 3. coalesce external events to a single action. Example: all workers watch 100 | external events indicating an in-memory cache needs to be refreshed. When 101 | receiving it they all post it with a unique event hash (all workers generate the 102 | same hash), see `target` parameter of [publish](#publish). Now only 1 worker will 103 | receive the event _only once_, so only one worker will hit the upstream 104 | database to refresh the in-memory data. 105 | 106 | [Back to TOC](#table-of-contents) 107 | 108 | 109 | Methods 110 | ======= 111 | 112 | [Back to TOC](#table-of-contents) 113 | 114 | new 115 | --------- 116 | **syntax:** *ev = events.new(opts)* 117 | 118 | **context:** *init_by_lua** 119 | 120 | Return a new events object. 121 | It should be stored in global scope for [run](#run) later. 122 | 123 | The `opts` parameter is a Lua table with named options: 124 | 125 | * `listening`: the unix domain socket, which must be same as another `server` block. 126 | * `broker_id`: (optional) the worker id that will start to listen, default `0`. 127 | * `unique_timeout`: (optional) timeout of unique event data stored (in seconds), default `5`. 128 | See the `target` parameter of the [publish](#publish) method. 129 | * `max_queue_len`: (optional) max length of internal events buffer queue, default `1024 * 10`. 130 | * `max_payload_len`: (optional) max length of serialized event data, default `1024 * 64`, max `1024 * 1024 * 16`. 131 | * `enable_privileged_agent`: (optional) whether to enable privileged agent to receive events. By default 132 | it is enabled dynamically on broker connection for backward compatibility, 133 | but it is strongly suggested to explicitly configure this either with 134 | `true` or `false` as that ensures that events queue for privileged agent 135 | will be pre-created during initialization (or not created at all). 136 | 137 | The return value will be the event object or `nil`. 138 | 139 | There is a special parameter `testing`, which means the library will not enable 140 | unix domain socket listening, and the events will only be propagated in the worker process internally. 141 | In the meanwhile, `unique_timeout` will be meanless. 142 | 143 | This feature is very useful for testing, such as `resty cli`. 144 | The default value for `testing` is `false`. 145 | 146 | [Back to TOC](#table-of-contents) 147 | 148 | init_worker 149 | --------- 150 | **syntax:** *ok, err = ev:init_worker()* 151 | 152 | **context:** *init_worker_by_lua** 153 | 154 | Will initialize the event listener. This should typically be called from the 155 | `init_worker_by_lua` handler, because it will make sure only one Nginx worker 156 | starts to listen on unix domain socket. 157 | 158 | The return value will be `true`, or `nil` and an error message. 159 | 160 | [Back to TOC](#table-of-contents) 161 | 162 | run 163 | --------- 164 | **syntax:** *ev:run()* 165 | 166 | **context:** *content_by_lua** 167 | 168 | Active the event loop only in Nginx broker process, see opts `broker_id` of [new](#new). 169 | it must be called in `content_by_lua*`. 170 | 171 | `ev` object must be the same object returned by [new](#new). 172 | 173 | Should not call it if `testing` is set to `true`. 174 | 175 | [Back to TOC](#table-of-contents) 176 | 177 | publish 178 | ---- 179 | **syntax:** *ok, err = ev:publish(target, source, event, data)* 180 | 181 | **context:** *all phases except init_by_lua** 182 | 183 | Will post a new event. `target`, `source` and `event` are all strings. `data` can be anything (including `nil`) 184 | as long as it is (de)serializable by the LuaJIT string buffer serializer and cJSON (legacy). 185 | 186 | The `target` parameter could be: 187 | 188 | * "all" : the event will be broadcasted to all workers. 189 | * "current" : the event will be local to the worker process, 190 | it will not be broadcasted to other workers. With this method, the `data` element 191 | will not be serialized. 192 | * _unique hash_ : the event will be send to only one worker. 193 | Also any follow up events with the same hash value will be ignored 194 | (for the `unique_timeout` period specified to [new](#new)). 195 | 196 | The return value will be `true` when the event was successfully published or 197 | `nil + error` in case of cjson serializition failure or event queue full. 198 | 199 | *Note*: in case of "all" and "current" the worker process sending the event, 200 | will also receive the event! So if the eventsource will also act upon the event, 201 | it should not do so from the event posting code, but only when receiving it. 202 | 203 | *Note*: in case of "all" and "_unique hash_" the serialized data has a 204 | hard-coded limit `2^24 - 1` bytes. It means that we can not send any data which is 205 | larger than 16MB. 206 | 207 | [Back to TOC](#table-of-contents) 208 | 209 | subscribe 210 | -------- 211 | **syntax:** *id = ev:subscribe(source, event, callback)* 212 | 213 | **context:** *all phases except init_by_lua** 214 | 215 | Will register a callback function to receive events. If `source` and `event` are `*`, then the 216 | callback will be executed on _every_ event, if `source` is provided and `event` is `*`, then only events with a 217 | matching source will be passed. If event name is given, then only when 218 | both `source` and `event` match the callback is invoked. 219 | 220 | The callback should have the following signature; 221 | 222 | `syntax: callback = function(data, event, source, wid)` 223 | 224 | The parameters will be the same as the ones provided to [publish](#publish), except for the extra value 225 | `wid` which will be the worker id of the originating worker process, or `nil` if it was a local event 226 | only. Any return value from `callback` will be discarded. 227 | *Note:* `data` may be a reference type of data (eg. a Lua `table` type). The same value is passed 228 | to all callbacks, _so do not change the value in your handler, unless you know what you are doing!_ 229 | 230 | The return value of `subscribe` will be a callback id, or it will throw an error if `callback` is not a 231 | function value. 232 | 233 | [Back to TOC](#table-of-contents) 234 | 235 | unsubscribe 236 | ---------- 237 | **syntax:** *ev:unsubscribe(id)* 238 | 239 | **context:** *all phases except init_by_lua** 240 | 241 | Will unregister the callback function and prevent it from receiving further events. The 242 | parameter `id` is the return value of [subscribe](#subscribe). 243 | 244 | [Back to TOC](#table-of-contents) 245 | 246 | 247 | License 248 | ======= 249 | 250 | ``` 251 | Copyright 2022-2024 Kong Inc. 252 | 253 | Licensed under the Apache License, Version 2.0 (the "License"); 254 | you may not use this file except in compliance with the License. 255 | You may obtain a copy of the License at 256 | 257 | http://www.apache.org/licenses/LICENSE-2.0 258 | 259 | Unless required by applicable law or agreed to in writing, software 260 | distributed under the License is distributed on an "AS IS" BASIS, 261 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 262 | See the License for the specific language governing permissions and 263 | limitations under the License. 264 | ``` 265 | 266 | [Back to TOC](#table-of-contents) 267 | 268 | 269 | See Also 270 | ======== 271 | * Kong: https://konghq.com/ 272 | 273 | [Back to TOC](#table-of-contents) 274 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_module_type=CORE 2 | 3 | ngx_addon_name=ngx_lua_events_module 4 | 5 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_lua_events_module.c" 6 | 7 | 8 | -------------------------------------------------------------------------------- /lualib/resty/events/broker.lua: -------------------------------------------------------------------------------- 1 | local codec = require "resty.events.codec" 2 | local lrucache = require "resty.lrucache" 3 | local queue = require "resty.events.queue" 4 | local utils = require "resty.events.utils" 5 | local server = require("resty.events.protocol").server 6 | 7 | 8 | local is_timeout = utils.is_timeout 9 | local is_closed = utils.is_closed 10 | local get_worker_id = utils.get_worker_id 11 | local get_worker_name = utils.get_worker_name 12 | 13 | 14 | local setmetatable = setmetatable 15 | local random = math.random 16 | 17 | 18 | local ngx = ngx -- luacheck: ignore 19 | local log = ngx.log 20 | local exit = ngx.exit 21 | local exiting = ngx.worker.exiting 22 | local worker_count = ngx.worker.count 23 | local ERR = ngx.ERR 24 | local DEBUG = ngx.DEBUG 25 | local NOTICE = ngx.NOTICE 26 | 27 | 28 | local spawn = ngx.thread.spawn 29 | local kill = ngx.thread.kill 30 | local wait = ngx.thread.wait 31 | 32 | 33 | local decode = codec.decode 34 | 35 | 36 | local MAX_UNIQUE_EVENTS = 1024 37 | local WEAK_KEYS_MT = { __mode = "k", } 38 | 39 | 40 | local get_json 41 | do 42 | local cjson_encode = require("cjson.safe").encode 43 | 44 | get_json = function(data) 45 | return cjson_encode(decode(data)) 46 | end 47 | end 48 | 49 | 50 | local function terminating(self, worker_connection) 51 | return not self._clients[worker_connection] or exiting() 52 | end 53 | 54 | -- broadcast to all/unique workers 55 | local function broadcast_events(self, unique, data) 56 | local n = 0 57 | 58 | local queues = self._queues 59 | 60 | local first_worker_id = self._first_worker_id 61 | local last_worker_id = self._last_worker_id 62 | 63 | if unique then 64 | -- if unique, schedule to a random worker 65 | local worker_id = random(first_worker_id, last_worker_id) 66 | local worker_queue = queues[worker_id] 67 | local ok, err = worker_queue:push(data) 68 | if not ok then 69 | log(ERR, "failed to publish unique event to ", 70 | get_worker_name(worker_id), 71 | ": ", err, 72 | ". data is :", get_json(data)) 73 | 74 | else 75 | n = n + 1 76 | end 77 | 78 | else 79 | for worker_id = first_worker_id, last_worker_id do 80 | local worker_queue = queues[worker_id] 81 | local ok, err = worker_queue:push(data) 82 | if not ok then 83 | log(ERR, "failed to publish event to ", 84 | get_worker_name(worker_id), 85 | ": ", err, 86 | ". data is :", get_json(data)) 87 | 88 | else 89 | n = n + 1 90 | end 91 | end 92 | end 93 | 94 | log(DEBUG, "event published to ", n, " workers") 95 | end 96 | 97 | local function read_thread(self, worker_connection) 98 | local worker_id = worker_connection.info.id 99 | while not terminating(self, worker_connection) do 100 | local data, err = worker_connection:recv_frame() 101 | if err then 102 | if not is_timeout(err) then 103 | return nil, "failed to read event from worker: " .. err 104 | end 105 | 106 | -- timeout 107 | goto continue 108 | end 109 | 110 | if not data then 111 | if not exiting() then 112 | log(ERR, "did not receive event from ", get_worker_name(worker_id)) 113 | end 114 | goto continue 115 | end 116 | 117 | local event_data, err = decode(data) 118 | if not event_data then 119 | if not exiting() then 120 | log(ERR, "failed to decode event data on ", 121 | get_worker_name(worker_id), ": ", err) 122 | end 123 | goto continue 124 | end 125 | 126 | -- unique event 127 | local unique = event_data.spec.unique 128 | if unique then 129 | if self._uniques:get(unique) then 130 | log(DEBUG, "unique event is duplicate on ", 131 | get_worker_name(worker_id), ": ", unique) 132 | goto continue 133 | end 134 | 135 | self._uniques:set(unique, 1, self._opts.unique_timeout) 136 | end 137 | 138 | -- broadcast to all/unique workers 139 | broadcast_events(self, unique, event_data.data) 140 | 141 | ::continue:: 142 | end -- while not terminating 143 | 144 | return true 145 | end 146 | 147 | local function write_thread(self, worker_connection, worker_queue) 148 | local worker_id = worker_connection.info.id 149 | while not terminating(self, worker_connection) do 150 | local payload, err = worker_queue:pop() 151 | if not payload then 152 | if not is_timeout(err) then 153 | return nil, "semaphore wait error: " .. err 154 | end 155 | 156 | goto continue 157 | end 158 | 159 | local _, err = worker_connection:send_frame(payload) 160 | if err then 161 | local ok, push_err = worker_queue:push_front(payload) 162 | if not ok then 163 | log(ERR, "failed to retain event for ", 164 | get_worker_name(worker_id), ": ", push_err, 165 | ". data is :", get_json(payload)) 166 | end 167 | return nil, "failed to send event: " .. err 168 | end 169 | 170 | ::continue:: 171 | end -- while not terminating 172 | 173 | return true 174 | end 175 | 176 | local _M = {} 177 | local _MT = { __index = _M, } 178 | 179 | function _M.new(opts) 180 | return setmetatable({ 181 | _opts = opts, 182 | _queues = nil, 183 | _uniques = nil, 184 | _clients = nil, 185 | _first_worker_id = nil, 186 | _last_worker_id = nil, 187 | }, _MT) 188 | end 189 | 190 | function _M:init() 191 | local opts = self._opts 192 | 193 | assert(opts) 194 | 195 | local _uniques, err = lrucache.new(MAX_UNIQUE_EVENTS) 196 | if not _uniques then 197 | return nil, "failed to create the events cache: " .. (err or "unknown") 198 | end 199 | 200 | local queues = {} 201 | 202 | local first_worker_id = opts.enable_privileged_agent == true and -1 or 0 203 | local last_worker_id = worker_count() - 1 204 | 205 | for i = first_worker_id, last_worker_id do 206 | queues[i] = queue.new(opts.max_queue_len) 207 | end 208 | 209 | self._uniques = _uniques 210 | self._clients = setmetatable({}, WEAK_KEYS_MT) 211 | self._queues = queues 212 | self._first_worker_id = first_worker_id 213 | self._last_worker_id = last_worker_id 214 | 215 | log(NOTICE, "event broker is ready to accept connections on worker #", opts.broker_id) 216 | 217 | return true 218 | end 219 | 220 | function _M:run() 221 | local broker_id = get_worker_id() 222 | if broker_id ~= self._opts.broker_id then 223 | log(ERR, "broker got connection from worker on non-broker worker #", broker_id) 224 | return exit(444) 225 | end 226 | 227 | local clients = self._clients 228 | if not clients then 229 | log(ERR, "broker is not (yet) ready to accept connections on worker #", broker_id) 230 | return exit(444) 231 | end 232 | 233 | local worker_connection, err = server.new() 234 | if not worker_connection then 235 | log(ERR, "failed to init socket: ", err) 236 | return exit(444) 237 | end 238 | 239 | local queues = self._queues 240 | local worker_id = worker_connection.info.id 241 | local worker_pid = worker_connection.info.pid 242 | 243 | if worker_id == -1 and not queues[-1] then 244 | -- TODO: this is for backward compatibility 245 | -- 246 | -- Queue for the privileged agent is dynamically 247 | -- created because it is not always enabled or 248 | -- does not always connect to broker. This also 249 | -- means that privileged agent may miss some 250 | -- events on a startup. 251 | -- 252 | -- It is suggested to instead explicitly pass 253 | -- an option: enable_privileged_agent=true|false. 254 | queues[-1] = queue.new(self._opts.max_queue_len) 255 | self._first_worker_id = -1 256 | end 257 | 258 | clients[worker_connection] = true 259 | 260 | local read_thread_co = spawn(read_thread, self, worker_connection) 261 | local write_thread_co = spawn(write_thread, self, worker_connection, queues[worker_id]) 262 | 263 | log(NOTICE, get_worker_name(worker_id), 264 | " connected to events broker (worker pid: ", worker_pid, ")") 265 | 266 | local ok, err, perr = wait(read_thread_co, write_thread_co) 267 | 268 | clients[worker_connection] = nil 269 | 270 | if exiting() then 271 | kill(read_thread_co) 272 | kill(write_thread_co) 273 | return 274 | end 275 | 276 | if not ok and not is_closed(err) then 277 | log(ERR, "event broker failed on ", get_worker_name(worker_id), 278 | ": ", err, " (worker pid: ", worker_pid, ")") 279 | return exit(ngx.ERROR) 280 | end 281 | 282 | if perr and not is_closed(perr) then 283 | log(ERR, "event broker failed on ", get_worker_name(worker_id), 284 | ": ", perr, " (worker pid: ", worker_pid, ")") 285 | return exit(ngx.ERROR) 286 | end 287 | 288 | wait(read_thread_co) 289 | wait(write_thread_co) 290 | 291 | return exit(ngx.OK) 292 | end 293 | 294 | return _M 295 | -------------------------------------------------------------------------------- /lualib/resty/events/callback.lua: -------------------------------------------------------------------------------- 1 | local cjson = require "cjson.safe" 2 | 3 | local xpcall = xpcall 4 | local type = type 5 | local pairs = pairs 6 | local tostring = tostring 7 | local setmetatable = setmetatable 8 | local traceback = debug.traceback 9 | 10 | local ngx = ngx 11 | local log = ngx.log 12 | local ERR = ngx.ERR 13 | local DEBUG = ngx.DEBUG 14 | 15 | local encode = cjson.encode 16 | 17 | local _M = {} 18 | local _MT = { __index = _M, } 19 | 20 | function _M.new() 21 | return setmetatable({ 22 | _callbacks = {}, 23 | _funcs = {}, 24 | _counter = 0, 25 | }, _MT) 26 | end 27 | 28 | local function get_callback_list(self, source, event) 29 | return self._callbacks[source] and self._callbacks[source][event] 30 | end 31 | 32 | local function prepare_callback_list(self, source, event) 33 | local callbacks = self._callbacks 34 | if not callbacks[source] then 35 | callbacks[source] = { 36 | [event] = {} 37 | } 38 | elseif not callbacks[source][event] then 39 | callbacks[source][event] = {} 40 | end 41 | return callbacks[source][event] 42 | end 43 | 44 | -- subscribe('*', '*', func) 45 | -- subscribe('s', '*', func) 46 | -- subscribe('s', 'e', func) 47 | function _M:subscribe(source, event, callback) 48 | local list = prepare_callback_list(self, source, event) 49 | 50 | local count = self._counter + 1 51 | self._counter = count 52 | 53 | local id = tostring(count) 54 | 55 | self._funcs[id] = callback 56 | list[id] = true 57 | 58 | return id 59 | end 60 | 61 | function _M:unsubscribe(id) 62 | self._funcs[tostring(id)] = nil 63 | end 64 | 65 | local function do_handlerlist(funcs, list, source, event, data, wid) 66 | if not list then 67 | return 68 | end 69 | 70 | for id in pairs(list) do 71 | local handler = funcs[id] 72 | if type(handler) ~= "function" then 73 | list[id] = nil 74 | goto continue 75 | end 76 | 77 | local ok, err = xpcall(handler, traceback, data, event, source, wid) 78 | if not ok then 79 | local str, e 80 | 81 | if type(data) == "table" then 82 | str, e = encode(data) 83 | if not str then 84 | str = tostring(e) 85 | end 86 | 87 | else 88 | str = tostring(data) 89 | end 90 | 91 | log(ERR, "worker-events: event callback failed; source=", source, 92 | ", event=", event,", wid=", wid, " error='", tostring(err), 93 | "', data=", str) 94 | end 95 | 96 | ::continue:: 97 | end 98 | end 99 | 100 | -- Handle incoming table based event 101 | function _M:do_event(d) 102 | local source = d.source 103 | local event = d.event 104 | local data = d.data 105 | local wid = d.wid 106 | 107 | log(DEBUG, "worker-events: handling event; source=", source, 108 | ", event=", event, ", wid=", wid) 109 | 110 | local funcs = self._funcs 111 | 112 | -- global callback 113 | local list = get_callback_list(self, "*", "*") 114 | do_handlerlist(funcs, list, source, event, data, wid) 115 | 116 | -- source callback 117 | list = get_callback_list(self, source, "*") 118 | do_handlerlist(funcs, list, source, event, data, wid) 119 | 120 | -- source+event callback 121 | list = get_callback_list(self, source, event) 122 | do_handlerlist(funcs, list, source, event, data, wid) 123 | end 124 | 125 | return _M 126 | -------------------------------------------------------------------------------- /lualib/resty/events/codec.lua: -------------------------------------------------------------------------------- 1 | -- string.buffer is introduced since openresty 1.21.4.1 2 | 3 | local ok, buffer = pcall(require, "string.buffer") 4 | if not ok then 5 | return require "cjson.safe" 6 | end 7 | 8 | 9 | local options = { 10 | dict = { "spec", "data", 11 | "source", "event", "wid", "unique", 12 | }, 13 | } 14 | 15 | 16 | local buf_enc = buffer.new(options) 17 | local buf_dec = buffer.new(options) 18 | 19 | 20 | local _M = {} 21 | 22 | 23 | function _M.encode(obj) 24 | return buf_enc:reset():encode(obj):get() 25 | end 26 | 27 | 28 | function _M.decode(str) 29 | return buf_dec:set(str):decode() 30 | end 31 | 32 | 33 | return _M 34 | -------------------------------------------------------------------------------- /lualib/resty/events/compat/init.lua: -------------------------------------------------------------------------------- 1 | -- compatible with lua-resty-worker-events 1.0.0 2 | local events = require("resty.events") 3 | 4 | local ev 5 | 6 | local ipairs = ipairs 7 | local ngx = ngx 8 | local log = ngx.log 9 | local DEBUG = ngx.DEBUG 10 | local sleep = ngx.sleep 11 | 12 | -- store id for unsubscribe 13 | local handlers = {} 14 | 15 | local _configured 16 | 17 | local _M = { 18 | _VERSION = events._VERSION, 19 | } 20 | 21 | function _M.poll() 22 | sleep(0.002) -- wait events sync by unix socket connect 23 | 24 | log(DEBUG, "worker-events: emulate poll method") 25 | 26 | return "done" 27 | end 28 | 29 | function _M.configure(opts) 30 | ev = events.new(opts) 31 | 32 | local ok, err = ev:init_worker() 33 | if not ok then 34 | return nil, err 35 | end 36 | 37 | _configured = true 38 | 39 | return true 40 | end 41 | 42 | function _M.configured() 43 | return _configured 44 | end 45 | 46 | function _M.run() 47 | return ev:run() 48 | end 49 | 50 | _M.post = function(source, event, data, unique) 51 | local ok, err = ev:publish(unique or "all", source, event, data) 52 | if not ok then 53 | return nil, err 54 | end 55 | 56 | return "done" 57 | end 58 | 59 | _M.post_local = function(source, event, data) 60 | local ok, err = ev:publish("current", source, event, data) 61 | if not ok then 62 | return nil, err 63 | end 64 | 65 | return "done" 66 | end 67 | 68 | _M.register = function(callback, source, event, ...) 69 | local events = {event or "*", ...} 70 | for _, e in ipairs(events) do 71 | local id = ev:subscribe(source or "*", e or "*", callback) 72 | handlers[callback] = id 73 | end 74 | end 75 | 76 | _M.register_weak = _M.register 77 | 78 | _M.unregister = function(callback) 79 | local id = handlers[callback] 80 | if not id then 81 | return 82 | end 83 | 84 | handlers[callback] = nil 85 | 86 | return ev:unsubscribe(id) 87 | end 88 | 89 | return _M 90 | -------------------------------------------------------------------------------- /lualib/resty/events/disable_listening.lua: -------------------------------------------------------------------------------- 1 | require "resty.core.base" -- for ngx_str_t 2 | 3 | local ffi = require "ffi" 4 | local C = ffi.C 5 | 6 | local NGX_OK = ngx.OK 7 | 8 | ffi.cdef[[ 9 | int ngx_lua_ffi_disable_listening_unix_socket(ngx_str_t *sock_name); 10 | ]] 11 | 12 | local sock_name_str = ffi.new("ngx_str_t[1]") 13 | 14 | local disabled 15 | 16 | return function(sock_name) 17 | if disabled then 18 | return true 19 | end 20 | 21 | sock_name_str[0].data = sock_name 22 | sock_name_str[0].len = #sock_name 23 | 24 | local rc = C.ngx_lua_ffi_disable_listening_unix_socket(sock_name_str) 25 | if rc ~= NGX_OK then 26 | return nil, "failed to disable listening: " .. sock_name 27 | end 28 | 29 | disabled = true 30 | 31 | return true 32 | end 33 | -------------------------------------------------------------------------------- /lualib/resty/events/frame.lua: -------------------------------------------------------------------------------- 1 | local bit = require "bit" 2 | 3 | 4 | local byte = string.byte 5 | local char = string.char 6 | local band = bit.band 7 | local bor = bit.bor 8 | local lshift = bit.lshift 9 | local rshift = bit.rshift 10 | 11 | 12 | local type = type 13 | local assert = assert 14 | local tostring = tostring 15 | 16 | 17 | local _M = {} 18 | 19 | 20 | -- frame format: Len(3 bytes) + Payload(max to 2^24 - 1 bytes) 21 | 22 | 23 | local UINT_HEADER_LEN = 3 24 | local MAX_PAYLOAD_LEN = 2^24 - 1 -- 16MB 25 | 26 | 27 | local SEND_DATA = {} 28 | 29 | 30 | local function uint_to_bytes(num) 31 | if num < 0 or num > MAX_PAYLOAD_LEN then 32 | error("number " .. tostring(num) .. " out of range", 2) 33 | end 34 | 35 | return char(band(rshift(num, 16), 0xFF), 36 | band(rshift(num, 8 ), 0xFF), 37 | band(num, 0xFF)) 38 | end 39 | 40 | 41 | local function bytes_to_uint(str) 42 | assert(#str == UINT_HEADER_LEN) 43 | 44 | local b1, b2, b3 = byte(str, 1, UINT_HEADER_LEN) 45 | 46 | return bor(lshift(b1, 16), 47 | lshift(b2, 8 ), b3) 48 | end 49 | 50 | 51 | function _M.recv(sock) 52 | local data, err = sock:receive(UINT_HEADER_LEN) 53 | if not data then 54 | return nil, "failed to receive the header bytes: " .. err 55 | end 56 | 57 | local payload_len = bytes_to_uint(data) 58 | 59 | data, err = sock:receive(payload_len) 60 | if not data then 61 | return nil, "failed to read payload: " .. (err or "unknown") 62 | end 63 | 64 | return data 65 | end 66 | 67 | 68 | local function validate(payload) 69 | if type(payload) ~= "string" then 70 | return nil, "payload must be string" 71 | end 72 | 73 | local payload_len = #payload 74 | 75 | if payload_len > MAX_PAYLOAD_LEN then 76 | return nil, "payload too big" 77 | end 78 | 79 | return payload_len 80 | end 81 | _M.validate = validate 82 | 83 | 84 | function _M.send(sock, payload) 85 | local payload_len, err = validate(payload) 86 | if not payload_len then 87 | return nil, err 88 | end 89 | 90 | local data = SEND_DATA 91 | data[1] = uint_to_bytes(payload_len) 92 | data[2] = payload 93 | 94 | local bytes, err = sock:send(data) 95 | if not bytes then 96 | return nil, "failed to send frame: " .. err 97 | end 98 | 99 | return bytes 100 | end 101 | 102 | 103 | return _M 104 | -------------------------------------------------------------------------------- /lualib/resty/events/init.lua: -------------------------------------------------------------------------------- 1 | local utils = require "resty.events.utils" 2 | local events_broker = require "resty.events.broker" 3 | local events_worker = require "resty.events.worker" 4 | local disable_listening = require "resty.events.disable_listening" 5 | 6 | 7 | local get_worker_id = utils.get_worker_id 8 | 9 | 10 | local type = type 11 | local setmetatable = setmetatable 12 | local str_sub = string.sub 13 | 14 | 15 | local worker_count = utils.get_worker_count() 16 | 17 | 18 | local _M = { 19 | _VERSION = "0.3.1", 20 | } 21 | local _MT = { __index = _M, } 22 | 23 | 24 | local function check_options(opts) 25 | assert(type(opts) == "table", "Expected a table, got " .. type(opts)) 26 | 27 | local UNIX_PREFIX = "unix:" 28 | local DEFAULT_UNIQUE_TIMEOUT = 5 29 | local DEFAULT_MAX_QUEUE_LEN = 1024 * 10 30 | local DEFAULT_MAX_PAYLOAD_LEN = 1024 * 64 -- 64KB 31 | local LIMIT_MAX_PAYLOAD_LEN = 1024 * 1024 * 16 -- 16MB 32 | 33 | opts.broker_id = opts.broker_id or 0 34 | 35 | if type(opts.broker_id) ~= "number" then 36 | return nil, '"broker_id" option must be a number' 37 | end 38 | 39 | if opts.broker_id < 0 or opts.broker_id >= worker_count then 40 | return nil, '"broker_id" option is invalid' 41 | end 42 | 43 | if not opts.listening then 44 | return nil, '"listening" option required to start' 45 | end 46 | 47 | if type(opts.listening) ~= "string" then 48 | return nil, '"listening" option must be a string' 49 | end 50 | 51 | if str_sub(opts.listening, 1, #UNIX_PREFIX) ~= UNIX_PREFIX then 52 | return nil, '"listening" option must start with ' .. UNIX_PREFIX 53 | end 54 | 55 | opts.unique_timeout = opts.unique_timeout or DEFAULT_UNIQUE_TIMEOUT 56 | 57 | if type(opts.unique_timeout) ~= "number" then 58 | return nil, 'optional "unique_timeout" option must be a number' 59 | end 60 | 61 | if opts.unique_timeout <= 0 then 62 | return nil, '"unique_timeout" must be greater than 0' 63 | end 64 | 65 | opts.max_queue_len = opts.max_queue_len or DEFAULT_MAX_QUEUE_LEN 66 | 67 | if type(opts.max_queue_len) ~= "number" then 68 | return nil, '"max_queue_len" option must be a number' 69 | end 70 | 71 | if opts.max_queue_len <= 0 then 72 | return nil, '"max_queue_len" option is invalid' 73 | end 74 | 75 | opts.max_payload_len = opts.max_payload_len or DEFAULT_MAX_PAYLOAD_LEN 76 | 77 | if type(opts.max_payload_len) ~= "number" then 78 | return nil, '"max_payload_len" option must be a number' 79 | end 80 | 81 | if opts.max_payload_len <= 0 or opts.max_payload_len > LIMIT_MAX_PAYLOAD_LEN then 82 | return nil, '"max_payload_len" option is invalid' 83 | end 84 | 85 | if opts.enable_privileged_agent ~= nil and type(opts.enable_privileged_agent) ~= "boolean" then 86 | return nil, '"enable_privileged_agent" option must be a boolean' 87 | end 88 | 89 | opts.testing = opts.testing or false 90 | 91 | if type(opts.testing) ~= "boolean" then 92 | return nil, '"testing" option must be a boolean' 93 | end 94 | 95 | return true 96 | end 97 | 98 | function _M.new(opts) 99 | assert(check_options(opts)) 100 | 101 | local self = { 102 | opts = opts, 103 | broker = events_broker.new(opts), 104 | worker = events_worker.new(opts), 105 | } 106 | 107 | return setmetatable(self, _MT) 108 | end 109 | 110 | -- opts = {broker_id = n, listening = 'unix:...', unique_timeout = x,} 111 | function _M:init_worker() 112 | local opts = self.opts 113 | 114 | local worker_id = get_worker_id() 115 | 116 | local is_broker = opts.broker_id == worker_id or 117 | opts.testing == true 118 | 119 | local ok, err 120 | 121 | if is_broker then 122 | -- enable listening on broker 123 | ok, err = self.broker:init() 124 | 125 | elseif worker_id >= 0 then 126 | -- disable listening on other workers 127 | ok, err = disable_listening(opts.listening) 128 | 129 | else 130 | -- privileged agent 131 | if opts.enable_privileged_agent == false then 132 | return true 133 | end 134 | 135 | ok = true 136 | end 137 | 138 | if not ok then 139 | return nil, err 140 | end 141 | 142 | ok, err = self.worker:init() 143 | if not ok then 144 | return nil, err 145 | end 146 | 147 | return true 148 | end 149 | 150 | function _M:run() 151 | return self.broker:run() 152 | end 153 | 154 | function _M:publish(target, source, event, data) 155 | return self.worker:publish(target, source, event, data) 156 | end 157 | 158 | function _M:subscribe(source, event, callback) 159 | return self.worker:subscribe(source, event, callback) 160 | end 161 | 162 | function _M:unsubscribe(id) 163 | return self.worker:unsubscribe(id) 164 | end 165 | 166 | function _M:is_ready() 167 | return self.worker:is_ready() 168 | end 169 | 170 | return _M 171 | -------------------------------------------------------------------------------- /lualib/resty/events/protocol.lua: -------------------------------------------------------------------------------- 1 | local frame = require "resty.events.frame" 2 | local codec = require "resty.events.codec" 3 | local utils = require "resty.events.utils" 4 | 5 | 6 | local _recv_frame = frame.recv 7 | local _send_frame = frame.send 8 | local encode = codec.encode 9 | local decode = codec.decode 10 | 11 | 12 | local ngx = ngx -- luacheck: ignore 13 | local worker_pid = ngx.worker.pid 14 | local tcp = ngx.socket.tcp 15 | local req_sock = ngx.req.socket 16 | local ngx_header = ngx.header 17 | local send_headers = ngx.send_headers 18 | local flush = ngx.flush 19 | local subsystem = ngx.config.subsystem 20 | local get_worker_id = utils.get_worker_id 21 | 22 | 23 | local type = type 24 | local str_sub = string.sub 25 | local str_find = string.find 26 | local setmetatable = setmetatable 27 | 28 | 29 | -- for high traffic pressure 30 | local DEFAULT_TIMEOUT = 5000 -- 5000ms 31 | local WORKER_INFO = { 32 | id = 0, 33 | pid = 0, 34 | } 35 | 36 | 37 | local function recv_frame(self) 38 | local sock = self.sock 39 | if not sock then 40 | return nil, "not initialized yet" 41 | end 42 | 43 | return _recv_frame(sock) 44 | end 45 | 46 | local function send_frame(self, payload) 47 | local sock = self.sock 48 | if not sock then 49 | return nil, "not initialized yet" 50 | end 51 | 52 | return _send_frame(sock, payload) 53 | end 54 | 55 | local _Server = { 56 | recv_frame = recv_frame, 57 | send_frame = send_frame, 58 | } 59 | 60 | local _SERVER_MT = { __index = _Server, } 61 | 62 | function _Server.new() 63 | if subsystem == "http" then 64 | if ngx.headers_sent then 65 | return nil, "response header already sent" 66 | end 67 | 68 | ngx_header["Upgrade"] = "Kong-Worker-Events/1" 69 | ngx_header["Content-Type"] = nil 70 | ngx.status = 101 71 | 72 | local ok, err = send_headers() 73 | if not ok then 74 | return nil, "failed to send response header: " .. (err or "unknown") 75 | end 76 | 77 | ok, err = flush(true) 78 | if not ok then 79 | return nil, "failed to flush response header: " .. (err or "unknown") 80 | end 81 | end -- subsystem == "http" 82 | 83 | local sock, err = req_sock(true) 84 | if not sock then 85 | return nil, err 86 | end 87 | 88 | sock:settimeout(DEFAULT_TIMEOUT) 89 | 90 | local data, err = _recv_frame(sock) 91 | if err then 92 | return nil, "failed to read worker info: " .. err 93 | end 94 | 95 | local info, err = decode(data) 96 | if err then 97 | return nil, "invalid worker info received: " .. err 98 | end 99 | 100 | return setmetatable({ 101 | info = info, 102 | sock = sock, 103 | }, _SERVER_MT) 104 | end 105 | 106 | local _Client = { 107 | recv_frame = recv_frame, 108 | send_frame = send_frame, 109 | } 110 | 111 | local _CLIENT_MT = { __index = _Client, } 112 | 113 | function _Client.new() 114 | local sock, err = tcp() 115 | if not sock then 116 | return nil, err 117 | end 118 | 119 | sock:settimeout(DEFAULT_TIMEOUT) 120 | 121 | return setmetatable({ 122 | sock = sock, 123 | }, _CLIENT_MT) 124 | end 125 | 126 | function _Client:connect(addr) 127 | local sock = self.sock 128 | if not sock then 129 | return nil, "not initialized" 130 | end 131 | 132 | if type(addr) ~= "string" then 133 | return nil, "addr must be a string" 134 | end 135 | 136 | if str_sub(addr, 1, 5) ~= "unix:" then 137 | return nil, "addr must start with \"unix:\"" 138 | end 139 | 140 | local ok, err = sock:connect(addr) 141 | if not ok then 142 | return nil, "failed to connect: " .. err 143 | end 144 | 145 | if subsystem == "http" then 146 | local req = "GET / HTTP/1.1\r\n" .. 147 | "Host: localhost\r\n" .. 148 | "Connection: Upgrade\r\n" .. 149 | "Upgrade: Kong-Worker-Events/1\r\n\r\n" 150 | 151 | local bytes, err = sock:send(req) 152 | if not bytes then 153 | return nil, "failed to send the handshake request: " .. err 154 | end 155 | 156 | local header_reader = sock:receiveuntil("\r\n\r\n") 157 | local header, err, _ = header_reader() 158 | if not header then 159 | return nil, "failed to receive response header: " .. err 160 | end 161 | 162 | if str_find(header, "HTTP/1.1 ", nil, true) ~= 1 then 163 | return nil, "bad HTTP response status line: " .. header 164 | end 165 | end -- subsystem == "http" 166 | 167 | WORKER_INFO.id = get_worker_id() 168 | WORKER_INFO.pid = worker_pid() 169 | 170 | local _, err = _send_frame(sock, encode(WORKER_INFO)) 171 | if err then 172 | return nil, "failed to send worker info: " .. err 173 | end 174 | 175 | return true 176 | end 177 | 178 | function _Client:close() 179 | local sock = self.sock 180 | if not sock then 181 | return nil, "not initialized" 182 | end 183 | 184 | local ok, err = sock:close() 185 | if not ok then 186 | return nil, err 187 | end 188 | 189 | return true 190 | end 191 | 192 | return { 193 | server = _Server, 194 | client = _Client, 195 | } 196 | -------------------------------------------------------------------------------- /lualib/resty/events/queue.lua: -------------------------------------------------------------------------------- 1 | local semaphore = require "ngx.semaphore" 2 | local table_new = require "table.new" 3 | 4 | 5 | local assert = assert 6 | local setmetatable = setmetatable 7 | local math_min = math.min 8 | 9 | 10 | local _M = {} 11 | local _MT = { __index = _M, } 12 | 13 | 14 | local MAX_QUEUE_PREALLOCATE = 4096 15 | 16 | 17 | function _M.new(max_len) 18 | local self = { 19 | semaphore = assert(semaphore.new()), 20 | max = max_len, 21 | 22 | elts = table_new(math_min(max_len, MAX_QUEUE_PREALLOCATE), 0), 23 | first = 0, 24 | last = -1, 25 | } 26 | 27 | return setmetatable(self, _MT) 28 | end 29 | 30 | 31 | function _M:push(item) 32 | local last = self.last 33 | if last - self.first + 1 >= self.max then 34 | return nil, "queue overflow" 35 | end 36 | 37 | last = last + 1 38 | self.last = last 39 | self.elts[last] = item 40 | 41 | self.semaphore:post() 42 | 43 | return true 44 | end 45 | 46 | 47 | function _M:push_front(item) 48 | local first = self.first 49 | if first > self.last then 50 | return self:push(item) 51 | end 52 | 53 | first = first - 1 54 | self.first = first 55 | self.elts[first] = item 56 | 57 | self.semaphore:post() 58 | 59 | return true 60 | end 61 | 62 | 63 | function _M:pop() 64 | local ok, err = self.semaphore:wait(1) 65 | if not ok then 66 | return nil, err 67 | end 68 | 69 | local first = self.first 70 | if first > self.last then 71 | return nil, "queue is empty" 72 | end 73 | 74 | local item = self.elts[first] 75 | self.elts[first] = nil 76 | self.first = first + 1 77 | return item 78 | end 79 | 80 | 81 | return _M 82 | -------------------------------------------------------------------------------- /lualib/resty/events/utils.lua: -------------------------------------------------------------------------------- 1 | local str_sub = string.sub 2 | 3 | 4 | local ngx = ngx -- luacheck: ignore 5 | local ngx_worker_id = ngx.worker.id 6 | local ngx_worker_count = ngx.worker.count 7 | 8 | 9 | local function is_timeout(err) 10 | return err and str_sub(err, -7) == "timeout" 11 | end 12 | 13 | 14 | local function is_closed(err) 15 | return err and (str_sub(err, -6) == "closed" or 16 | str_sub(err, -11) == "broken pipe") 17 | end 18 | 19 | 20 | local function get_worker_id() 21 | return ngx_worker_id() or -1 -- -1 represents priviledged worker 22 | end 23 | 24 | 25 | local function get_worker_name(worker_id) 26 | return worker_id == -1 and -- -1 represents priviledged worker 27 | "privileged agent" or "worker #" .. worker_id 28 | end 29 | 30 | 31 | local function get_worker_count() 32 | return ngx_worker_count() 33 | end 34 | 35 | 36 | return { 37 | is_timeout = is_timeout, 38 | is_closed = is_closed, 39 | 40 | get_worker_id = get_worker_id, 41 | get_worker_name = get_worker_name, 42 | get_worker_count = get_worker_count, 43 | } 44 | -------------------------------------------------------------------------------- /lualib/resty/events/worker.lua: -------------------------------------------------------------------------------- 1 | local cjson = require "cjson.safe" 2 | local codec = require "resty.events.codec" 3 | local queue = require "resty.events.queue" 4 | local callback = require "resty.events.callback" 5 | local utils = require "resty.events.utils" 6 | 7 | 8 | local frame_validate = require("resty.events.frame").validate 9 | local client = require("resty.events.protocol").client 10 | local is_timeout = utils.is_timeout 11 | local get_worker_id = utils.get_worker_id 12 | local get_worker_name = utils.get_worker_name 13 | 14 | 15 | local type = type 16 | local assert = assert 17 | local setmetatable = setmetatable 18 | local random = math.random 19 | 20 | 21 | local ngx = ngx -- luacheck: ignore 22 | local log = ngx.log 23 | local sleep = ngx.sleep 24 | local exiting = ngx.worker.exiting 25 | local ERR = ngx.ERR 26 | local DEBUG = ngx.DEBUG 27 | local NOTICE = ngx.NOTICE 28 | 29 | 30 | local spawn = ngx.thread.spawn 31 | local kill = ngx.thread.kill 32 | local wait = ngx.thread.wait 33 | 34 | 35 | local timer_at = ngx.timer.at 36 | 37 | 38 | local encode = codec.encode 39 | local decode = codec.decode 40 | local cjson_encode = cjson.encode 41 | 42 | 43 | local EVENTS_COUNT_LIMIT = 100 44 | local EVENTS_POP_LIMIT = 2000 45 | local EVENTS_SLEEP_TIME = 0.05 46 | 47 | 48 | local EMPTY_T = {} 49 | 50 | local EVENT_T = { 51 | source = '', 52 | event = '', 53 | data = '', 54 | wid = '', 55 | } 56 | 57 | local SPEC_T = { 58 | unique = '', 59 | } 60 | 61 | local PAYLOAD_T = { 62 | spec = EMPTY_T, 63 | data = '', 64 | } 65 | 66 | local _M = {} 67 | local _MT = { __index = _M, } 68 | 69 | 70 | -- gen a random number [0.01, 0.05] 71 | -- it means that delay will be 10ms~50ms 72 | local function random_delay() 73 | return random(10, 50) / 1000 74 | end 75 | 76 | local function communicate(premature, self) 77 | if premature then 78 | return true 79 | end 80 | 81 | self:communicate() 82 | 83 | return true 84 | end 85 | 86 | local function process_events(premature, self) 87 | if premature then 88 | return true 89 | end 90 | 91 | self:process_events() 92 | 93 | return true 94 | end 95 | 96 | local function start_communicate_timer(self, delay) 97 | if exiting() then 98 | return 99 | end 100 | assert(timer_at(delay, communicate, self)) 101 | end 102 | 103 | local function start_process_events_timer(self) 104 | if exiting() then 105 | return 106 | end 107 | assert(timer_at(0, process_events, self)) 108 | end 109 | 110 | local function start_timers(self) 111 | start_communicate_timer(self, 0) 112 | start_process_events_timer(self) 113 | end 114 | 115 | local function terminating(self) 116 | return not self._connected or exiting() 117 | end 118 | 119 | local check_sock_exist 120 | do 121 | local ffi = require "ffi" 122 | local C = ffi.C 123 | ffi.cdef [[ 124 | int access(const char *pathname, int mode); 125 | ]] 126 | 127 | -- remove prefix 'unix:' 128 | check_sock_exist = function(fpath) 129 | local rc = C.access(fpath:sub(6), 0) 130 | return rc == 0 131 | end 132 | end 133 | 134 | function _M.new(opts) 135 | local max_queue_len = opts.max_queue_len 136 | 137 | local self = { 138 | _pub_queue = queue.new(max_queue_len), 139 | _sub_queue = queue.new(max_queue_len), 140 | _callback = callback.new(), 141 | _connected = nil, 142 | _worker_id = nil, 143 | _opts = opts, 144 | } 145 | 146 | return setmetatable(self, _MT) 147 | end 148 | 149 | local function read_thread(self, broker_connection) 150 | local sub_queue = self._sub_queue 151 | while not terminating(self) do 152 | local data, err = broker_connection:recv_frame() 153 | if err then 154 | if not is_timeout(err) then 155 | return nil, err 156 | end 157 | 158 | -- timeout 159 | goto continue 160 | end 161 | 162 | if not data then 163 | if not exiting() then 164 | log(ERR, "did not receive event from broker") 165 | end 166 | goto continue 167 | end 168 | 169 | local event_data, err = decode(data) 170 | if err then 171 | if not exiting() then 172 | log(ERR, "failed to decode event data: ", err) 173 | end 174 | goto continue 175 | end 176 | 177 | -- got an event data, push to queue, callback in events_thread 178 | local _, err = sub_queue:push(event_data) 179 | if err then 180 | if not exiting() then 181 | log(ERR, "failed to store event: ", err, ". data is: ", 182 | cjson_encode(event_data)) 183 | end 184 | goto continue 185 | end 186 | 187 | ::continue:: 188 | end -- while not terminating 189 | 190 | return true 191 | end 192 | 193 | local function write_thread(self, broker_connection) 194 | local counter = 0 195 | local pub_queue = self._pub_queue 196 | while not terminating(self) do 197 | local payload, err = pub_queue:pop() 198 | if err then 199 | if not is_timeout(err) then 200 | return nil, "semaphore wait error: " .. err 201 | end 202 | 203 | -- timeout 204 | goto continue 205 | end 206 | 207 | local _, err = broker_connection:send_frame(payload) 208 | if err then 209 | local ok, push_err = pub_queue:push_front(payload) 210 | if not ok then 211 | log(ERR, "failed to retain event: ", push_err) 212 | end 213 | return nil, "failed to send event: " .. err 214 | end 215 | 216 | -- events rate limiting 217 | counter = counter + 1 218 | if counter >= EVENTS_COUNT_LIMIT then 219 | sleep(EVENTS_SLEEP_TIME) 220 | counter = 0 221 | end 222 | 223 | ::continue:: 224 | end -- while not terminating 225 | 226 | return true 227 | end 228 | 229 | local function events_thread(self) 230 | local counter = 0 231 | 232 | while not exiting() do 233 | local data, err = self._sub_queue:pop() 234 | if err then 235 | if not is_timeout(err) then 236 | return nil, "semaphore wait error: " .. err 237 | end 238 | 239 | -- timeout 240 | goto continue 241 | end 242 | 243 | -- got an event data, callback 244 | self._callback:do_event(data) 245 | 246 | counter = counter + 1 247 | 248 | -- exit and restart timer to avoid memory leak 249 | if counter >= EVENTS_POP_LIMIT then 250 | break 251 | end 252 | 253 | -- yield, not block other threads 254 | sleep(0) 255 | 256 | ::continue:: 257 | end -- while not exiting 258 | 259 | return true 260 | end 261 | 262 | function _M:communicate() 263 | -- only for testing, skip read/write/events threads 264 | if self._opts.testing == true then 265 | self._connected = true 266 | return 267 | end 268 | 269 | local listening = self._opts.listening 270 | 271 | if not check_sock_exist(listening) then 272 | log(DEBUG, "unix domain socket (", listening, ") is not ready") 273 | 274 | -- try to reconnect broker, avoid crit error log 275 | start_communicate_timer(self, 0.002) 276 | return 277 | end 278 | 279 | local broker_connection = assert(client.new()) 280 | 281 | local ok, err = broker_connection:connect(listening) 282 | 283 | if exiting() then 284 | if ok then 285 | broker_connection:close() 286 | end 287 | return 288 | end 289 | 290 | if not ok then 291 | log(ERR, "failed to connect: ", err) 292 | 293 | -- try to reconnect broker 294 | start_communicate_timer(self, random_delay()) 295 | 296 | return 297 | end 298 | 299 | self._connected = true 300 | 301 | local read_thread_co = spawn(read_thread, self, broker_connection) 302 | local write_thread_co = spawn(write_thread, self, broker_connection) 303 | 304 | log(NOTICE, get_worker_name(self._worker_id), 305 | " is ready to accept events from ", listening) 306 | 307 | local ok, err, perr = wait(read_thread_co, write_thread_co) 308 | 309 | self._connected = nil 310 | 311 | if exiting() then 312 | kill(read_thread_co) 313 | kill(write_thread_co) 314 | 315 | broker_connection:close() 316 | 317 | return 318 | end 319 | 320 | if not ok then 321 | log(ERR, "event worker failed to communicate with broker (", err, ")") 322 | end 323 | 324 | if perr then 325 | log(ERR, "event worker failed to communicate with broker (", perr, ")") 326 | end 327 | 328 | wait(read_thread_co) 329 | wait(write_thread_co) 330 | 331 | broker_connection:close() 332 | 333 | start_communicate_timer(self, random_delay()) 334 | end 335 | 336 | function _M:process_events() 337 | local events_thread_co = spawn(events_thread, self) 338 | local ok, err, perr = wait(events_thread_co) 339 | if exiting() then 340 | return 341 | end 342 | 343 | if not ok then 344 | log(ERR, "event worker failed to process events (", err, ")") 345 | end 346 | 347 | if perr then 348 | log(ERR, "event worker failed to process events (", perr, ")") 349 | end 350 | 351 | start_process_events_timer(self) 352 | end 353 | 354 | function _M:init() 355 | assert(self._opts) 356 | 357 | self._worker_id = get_worker_id() 358 | 359 | start_timers(self) 360 | 361 | return true 362 | end 363 | 364 | -- posts a new event 365 | local function post_event(self, source, event, data, spec) 366 | local str, ok, len, err 367 | 368 | EVENT_T.source = source 369 | EVENT_T.event = event 370 | EVENT_T.data = data 371 | EVENT_T.wid = self._worker_id or get_worker_id() 372 | 373 | -- encode event info 374 | str, err = encode(EVENT_T) 375 | 376 | if not str then 377 | return nil, err 378 | end 379 | 380 | PAYLOAD_T.spec = spec or EMPTY_T 381 | PAYLOAD_T.data = str 382 | 383 | -- encode spec info 384 | str, err = encode(PAYLOAD_T) 385 | 386 | if not str then 387 | return nil, err 388 | end 389 | 390 | len, err = frame_validate(str) 391 | if not len then 392 | return nil, err 393 | end 394 | if len > self._opts.max_payload_len then 395 | return nil, "payload exceeds the limitation " .. 396 | "(" .. self._opts.max_payload_len .. ")" 397 | end 398 | 399 | ok, err = self._pub_queue:push(str) 400 | if not ok then 401 | return nil, err 402 | end 403 | 404 | return true 405 | end 406 | 407 | function _M:publish(target, source, event, data) 408 | assert(type(target) == "string" and target ~= "", "target is required") 409 | assert(type(source) == "string" and source ~= "", "source is required") 410 | assert(type(event) == "string" and event ~= "", "event is required") 411 | 412 | -- fall back to local events 413 | if self._opts.testing == true then 414 | log(DEBUG, "event published to 1 workers") 415 | 416 | self._callback:do_event({ 417 | source = source, 418 | event = event, 419 | data = data, 420 | }) 421 | 422 | return true 423 | end 424 | 425 | local ok, err 426 | if target == "current" then 427 | ok, err = self._sub_queue:push({ 428 | source = source, 429 | event = event, 430 | data = data, 431 | }) 432 | 433 | else 434 | -- add unique hash string 435 | SPEC_T.unique = target ~= "all" and target or nil 436 | 437 | ok, err = post_event(self, source, event, data, SPEC_T) 438 | end 439 | 440 | if not ok then 441 | return nil, "failed to publish event: " .. err 442 | end 443 | 444 | return true 445 | end 446 | 447 | function _M:subscribe(source, event, callback) 448 | assert(type(source) == "string" and source ~= "", "source is required") 449 | assert(type(event) == "string" and event ~= "", "event is required") 450 | assert(type(callback) == "function", "expected function, got: " .. 451 | type(callback)) 452 | 453 | return self._callback:subscribe(source, event, callback) 454 | end 455 | 456 | function _M:unsubscribe(id) 457 | assert(type(id) == "string" and id ~= "", "id is required") 458 | 459 | return self._callback:unsubscribe(id) 460 | end 461 | 462 | function _M:is_ready() 463 | return self._connected 464 | end 465 | 466 | return _M 467 | -------------------------------------------------------------------------------- /src/ngx_lua_events_module.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019-2024 Kong Inc. 3 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | #include 19 | 20 | 21 | static void 22 | ngx_lua_disable_listening_socket(ngx_listening_t *ls) 23 | { 24 | ngx_connection_t *c = ls->connection; 25 | 26 | /* copied from ngx_close_listening_sockets */ 27 | 28 | if (c) { 29 | if (c->read->active) { 30 | if (ngx_event_flags & NGX_USE_EPOLL_EVENT) { 31 | 32 | /* 33 | * it seems that Linux-2.6.x OpenVZ sends events 34 | * for closed shared listening sockets unless 35 | * the events was explicitly deleted 36 | */ 37 | 38 | ngx_del_event(c->read, NGX_READ_EVENT, 0); 39 | 40 | } else { 41 | ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT); 42 | } 43 | } 44 | 45 | } 46 | 47 | ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, 48 | "close listening %V #%d ", &ls->addr_text, ls->fd); 49 | } 50 | 51 | int 52 | ngx_lua_ffi_disable_listening_unix_socket(ngx_str_t *sock_name) 53 | { 54 | #if (NGX_HAVE_UNIX_DOMAIN) 55 | 56 | ngx_uint_t i; 57 | ngx_listening_t *ls; 58 | 59 | /* copied from ngx_close_listening_sockets */ 60 | 61 | ls = ngx_cycle->listening.elts; 62 | for (i = 0; i < ngx_cycle->listening.nelts; i++) { 63 | 64 | #if (NGX_HAVE_REUSEPORT) 65 | if (ls[i].fd == (ngx_socket_t) -1) { 66 | continue; 67 | } 68 | #endif 69 | 70 | if (ls[i].sockaddr->sa_family != AF_UNIX) { 71 | continue; 72 | } 73 | 74 | ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, 75 | "try to close listening %V #%d", &ls[i].addr_text, ls[i].fd); 76 | 77 | if (ngx_strncmp(ls[i].addr_text.data, 78 | sock_name->data, sock_name->len) == 0) { 79 | ngx_lua_disable_listening_socket(&ls[i]); 80 | return NGX_OK; 81 | } 82 | } 83 | 84 | return NGX_ERROR; 85 | 86 | #else 87 | 88 | #error unix domain socket is required! 89 | 90 | #endif 91 | } 92 | -------------------------------------------------------------------------------- /t/broadcast.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 14) + 2; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | master_on(); 17 | workers(4); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: posting events and handling events, broadcast 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 25 | init_by_lua_block { 26 | local opts = { 27 | broker_id = 3, 28 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 29 | } 30 | 31 | local ev = require("resty.events").new(opts) 32 | if not ev then 33 | ngx.log(ngx.ERR, "failed to new events") 34 | end 35 | 36 | _G.ev = ev 37 | } 38 | init_worker_by_lua_block { 39 | local ev = _G.ev 40 | local ok, err = ev:init_worker() 41 | if not ok then 42 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 43 | end 44 | 45 | local i = 0 46 | 47 | ev:subscribe("*", "*", function(data, event, source, wid) 48 | i = i + 1 49 | ngx.log(ngx.DEBUG, i, " worker-events: handler event; source=", source, ", event=", event, 50 | ", wid=", wid, ", by=", ngx.worker.id(), ", data=", data) 51 | end) 52 | 53 | _G.ev = ev 54 | } 55 | 56 | server { 57 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 58 | location / { 59 | content_by_lua_block { 60 | _G.ev:run() 61 | } 62 | } 63 | } 64 | --- config 65 | location = /test { 66 | content_by_lua_block { 67 | local ev = _G.ev 68 | 69 | ev:publish("all", "content_by_lua", "request1", "01234567890") 70 | 71 | ngx.say("ok") 72 | } 73 | } 74 | --- request 75 | GET /test 76 | --- response_body 77 | ok 78 | --- error_log eval 79 | [ 80 | qr/event published to 4 workers/, 81 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=0, data=01234567890/, 82 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=1, data=01234567890/, 83 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=2, data=01234567890/, 84 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=3, data=01234567890/ 85 | ] 86 | --- no_error_log 87 | [error] 88 | [crit] 89 | [alert] 90 | --- grep_error_log eval: qr/worker-events: handling event; .*/ 91 | --- grep_error_log_out eval 92 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 93 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 94 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 95 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+$/ 96 | 97 | 98 | 99 | === TEST 2: posting events and handling events, local 100 | --- http_config 101 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 102 | init_by_lua_block { 103 | local opts = { 104 | broker_id = 3, 105 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 106 | } 107 | 108 | local ev = require("resty.events").new(opts) 109 | if not ev then 110 | ngx.log(ngx.ERR, "failed to new events") 111 | end 112 | 113 | _G.ev = ev 114 | } 115 | init_worker_by_lua_block { 116 | local ev = _G.ev 117 | local ok, err = ev:init_worker() 118 | if not ok then 119 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 120 | end 121 | 122 | local i = 0 123 | 124 | ev:subscribe("*", "*", function(data, event, source, wid) 125 | if wid then 126 | i = 3 127 | else 128 | i = i + 1 129 | end 130 | 131 | ngx.log(ngx.DEBUG, i, " worker-events: handler event; source=", source, ", event=", event, 132 | ", wid=", wid, ", by=", ngx.worker.id(), ", data=", data) 133 | end) 134 | 135 | _G.ev = ev 136 | } 137 | 138 | server { 139 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 140 | location / { 141 | content_by_lua_block { 142 | _G.ev:run() 143 | } 144 | } 145 | } 146 | --- config 147 | location = /test { 148 | content_by_lua_block { 149 | local ev = _G.ev 150 | 151 | ev:publish("current", "content_by_lua", "request1", "ABCDEFGHIJK") 152 | ev:publish("current", "content_by_lua", "request2", "LMNOPQRSTUV") 153 | ngx.sleep(0.05) 154 | ev:publish("all", "content_by_lua", "request3", "01234567890") 155 | 156 | ngx.say("ok") 157 | } 158 | } 159 | --- request 160 | GET /test 161 | --- response_body 162 | ok 163 | --- error_log eval 164 | [ 165 | qr/event published to 4 workers/, 166 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=nil, by=\d+, data=ABCDEFGHIJK/, 167 | qr/2 worker-events: handler event; source=content_by_lua, event=request2, wid=nil, by=\d+, data=LMNOPQRSTUV/, 168 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=0, data=01234567890/, 169 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=1, data=01234567890/, 170 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=2, data=01234567890/, 171 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=3, data=01234567890/ 172 | ] 173 | --- no_error_log 174 | [error] 175 | [crit] 176 | [alert] 177 | --- grep_error_log eval: qr/worker-events: handling event; .*/ 178 | --- grep_error_log_out eval 179 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=nil 180 | worker-events: handling event; source=content_by_lua, event=request2, wid=nil 181 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 182 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 183 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 184 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+$/ 185 | 186 | 187 | 188 | === TEST 3: worker.events 'one' being done, and only once 189 | --- http_config 190 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 191 | init_by_lua_block { 192 | local opts = { 193 | unique_timeout = 0.04, 194 | --broker_id = 0, 195 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 196 | } 197 | 198 | local ev = require("resty.events").new(opts) 199 | if not ev then 200 | ngx.log(ngx.ERR, "failed to new events") 201 | end 202 | 203 | _G.ev = ev 204 | } 205 | init_worker_by_lua_block { 206 | local ev = _G.ev 207 | local ok, err = ev:init_worker() 208 | if not ok then 209 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 210 | end 211 | 212 | ev:subscribe("*", "*", function(data, event, source, wid) 213 | ngx.log(ngx.DEBUG, "worker-events: handler event; source=", source, ", event=", event, 214 | ", wid=", wid, ", by=", ngx.worker.id(), ", data=", data) 215 | end) 216 | 217 | _G.ev = ev 218 | } 219 | 220 | server { 221 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 222 | location / { 223 | content_by_lua_block { 224 | _G.ev:run() 225 | } 226 | } 227 | } 228 | --- config 229 | location = /test { 230 | content_by_lua_block { 231 | local ev = _G.ev 232 | 233 | ev:publish("all", "content_by_lua", "request1", "01234567890") 234 | 235 | ev:publish("unique_value", "content_by_lua", "request2", "ABCDEFGHIJK") 236 | ev:publish("unique_value", "content_by_lua", "request3", "LMNOPQRSTUV") 237 | 238 | ngx.sleep(0.1) -- wait for unique timeout to expire 239 | 240 | ev:publish("unique_value", "content_by_lua", "request4", "WXYZABCDEFG") 241 | ev:publish("unique_value", "content_by_lua", "request5", "HIJKLMNOPQR") 242 | 243 | ev:publish("all", "content_by_lua", "request6", "STUVWXYZABC") 244 | 245 | ngx.say("ok") 246 | } 247 | } 248 | --- request 249 | GET /test 250 | --- response_body 251 | ok 252 | --- error_log eval 253 | [ 254 | qr/event published to 1 workers/, 255 | qr/unique event is duplicate on worker #\d+: unique_value/, 256 | qr/event published to 4 workers/, 257 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=0, data=01234567890/, 258 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=1, data=01234567890/, 259 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=2, data=01234567890/, 260 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=3, data=01234567890/, 261 | qr/worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, by=\d+, data=ABCDEFGHIJK/, 262 | qr/worker-events: handler event; source=content_by_lua, event=request4, wid=\d+, by=\d+, data=WXYZABCDEFG/, 263 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=0, data=STUVWXYZABC/, 264 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=1, data=STUVWXYZABC/, 265 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=2, data=STUVWXYZABC/, 266 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=3, data=STUVWXYZABC/ 267 | ] 268 | --- no_error_log 269 | [error] 270 | [crit] 271 | [alert] 272 | LMNOPQRSTUV 273 | HIJKLMNOPQR 274 | -------------------------------------------------------------------------------- /t/callback.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 6) + 2; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: registering and unsubscribing event handlers at different levels 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 25 | --- config 26 | location = /test { 27 | content_by_lua_block { 28 | local ec = require("resty.events.callback").new() 29 | 30 | local wid = ngx.worker.id() 31 | local cb = function(extra, data, event, source, wid) 32 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 33 | ", data=", data, ", callback=",extra) 34 | end 35 | 36 | ngx.cb_global = function(...) return cb("global", ...) end 37 | ngx.cb_source = function(...) return cb("source", ...) end 38 | ngx.cb_event12 = function(...) return cb("event12", ...) end 39 | ngx.cb_event3 = function(...) return cb("event3", ...) end 40 | 41 | local id1 = ec:subscribe("*", "*", ngx.cb_global) 42 | local id2 = ec:subscribe("content_by_lua", '*', ngx.cb_source) 43 | local id3 = ec:subscribe("content_by_lua", "request1", ngx.cb_event12) 44 | local id4 = ec:subscribe("content_by_lua", "request2", ngx.cb_event12) 45 | local id5 = ec:subscribe("content_by_lua", "request3", ngx.cb_event3) 46 | 47 | local post = function(s, e, d) 48 | ec:do_event({source = s, event = e, data = d, wid = wid}) 49 | end 50 | 51 | post("content_by_lua", "request1", "123") 52 | post("content_by_lua", "request2", "123") 53 | post("content_by_lua", "request3", "123") 54 | 55 | --ec.unsubscribe("*", "*") 56 | ec:unsubscribe(id1) 57 | 58 | post("content_by_lua", "request1", "124") 59 | post("content_by_lua", "request2", "124") 60 | post("content_by_lua", "request3", "124") 61 | 62 | --ec:unsubscribe("content_by_lua", "*") 63 | ec:unsubscribe(id2) 64 | 65 | post("content_by_lua", "request1", "125") 66 | post("content_by_lua", "request2", "125") 67 | post("content_by_lua", "request3", "125") 68 | 69 | ec:unsubscribe(id3) 70 | ec:unsubscribe(id4) 71 | 72 | post("content_by_lua", "request1", "126") 73 | post("content_by_lua", "request2", "126") 74 | post("content_by_lua", "request3", "126") 75 | 76 | ec:unsubscribe(id5) 77 | 78 | post("content_by_lua", "request1", "127") 79 | post("content_by_lua", "request2", "127") 80 | post("content_by_lua", "request3", "127") 81 | 82 | ngx.say("ok") 83 | } 84 | } 85 | --- request 86 | GET /test 87 | --- response_body 88 | ok 89 | --- no_error_log 90 | [error] 91 | [crit] 92 | [alert] 93 | --- grep_error_log eval: qr/worker-events: .*/ 94 | --- grep_error_log_out eval 95 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 96 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=123, callback=global 97 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=123, callback=source 98 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=123, callback=event12 99 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 100 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=123, callback=global 101 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=123, callback=source 102 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=123, callback=event12 103 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 104 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=123, callback=global 105 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=123, callback=source 106 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=123, callback=event3 107 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 108 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=124, callback=source 109 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=124, callback=event12 110 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 111 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=124, callback=source 112 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=124, callback=event12 113 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 114 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=124, callback=source 115 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=124, callback=event3 116 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 117 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=125, callback=event12 118 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 119 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=125, callback=event12 120 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 121 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=125, callback=event3 122 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 123 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 124 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 125 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=126, callback=event3 126 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 127 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 128 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+$/ 129 | 130 | 131 | 132 | === TEST 2: callback error handling 133 | --- http_config 134 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 135 | --- config 136 | location = /test { 137 | content_by_lua_block { 138 | local ec = require("resty.events.callback").new() 139 | 140 | local post = function(s, e, d) 141 | ec:do_event({source = s, event = e, data = d, wid = wid}) 142 | end 143 | 144 | local error_func = function() 145 | error("something went wrong here!") 146 | end 147 | local test_callback = function(source, event, data, wid) 148 | error_func() -- nested call to check stack trace 149 | end 150 | ec:subscribe("*", "*", test_callback) 151 | 152 | -- non-serializable test data containing a function value 153 | -- use "nil" as data, reproducing issue #5 154 | post("content_by_lua", "test_event", nil) 155 | 156 | ngx.say("ok") 157 | } 158 | } 159 | --- request 160 | GET /test 161 | --- response_body 162 | ok 163 | --- error_log 164 | something went wrong here! 165 | --- no_error_log 166 | [crit] 167 | [alert] 168 | [emerg] 169 | 170 | 171 | 172 | === TEST 3: callback error stacktrace 173 | --- http_config 174 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 175 | --- config 176 | location = /test { 177 | content_by_lua_block { 178 | local ec = require("resty.events.callback").new() 179 | 180 | local post = function(s, e, d) 181 | ec:do_event({source = s, event = e, data = d, wid = wid}) 182 | end 183 | 184 | local error_func = function() 185 | error("something went wrong here!") 186 | end 187 | local in_between = function() 188 | error_func() -- nested call to check stack trace 189 | end 190 | local test_callback = function(source, event, data, wid) 191 | in_between() -- nested call to check stack trace 192 | end 193 | 194 | ec:subscribe("*", "*", test_callback) 195 | post("content_by_lua", "test_event") 196 | 197 | ngx.say("ok") 198 | } 199 | } 200 | --- request 201 | GET /test 202 | --- response_body 203 | ok 204 | --- error_log 205 | something went wrong here! 206 | in function 'error_func' 207 | in function 'in_between' 208 | --- no_error_log 209 | [crit] 210 | [alert] 211 | [emerg] 212 | -------------------------------------------------------------------------------- /t/codec.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 5); 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: sanity: encode, decode 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 25 | --- config 26 | location = /test { 27 | content_by_lua_block { 28 | local codec = require("resty.events.codec") 29 | 30 | local obj1 = {n = 100, s = "xxx", b = true,} 31 | 32 | local str = codec.encode(obj1) 33 | assert(str) 34 | 35 | local obj2 = codec.decode(str) 36 | assert(obj2) 37 | 38 | ngx.say(type(obj2)) 39 | ngx.say(type(obj2.n), obj2.n) 40 | ngx.say(type(obj2.s), obj2.s) 41 | ngx.say(type(obj2.b), obj2.b) 42 | } 43 | } 44 | --- request 45 | GET /test 46 | --- response_body 47 | table 48 | number100 49 | stringxxx 50 | booleantrue 51 | --- no_error_log 52 | [error] 53 | [crit] 54 | [alert] 55 | -------------------------------------------------------------------------------- /t/deadlock.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | plan tests => repeat_each() * (blocks() * 11); 5 | 6 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 7 | 8 | master_on(); 9 | workers(3); 10 | run_tests(); 11 | 12 | __DATA__ 13 | 14 | === TEST 1: do not prematurely kill the running events which may lead to a deadlock 15 | --- http_config 16 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 17 | lua_shared_dict dict 1m; 18 | init_by_lua_block { 19 | require("ngx.process").enable_privileged_agent(4096) 20 | local opts = { 21 | broker_id = 2, 22 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 23 | } 24 | 25 | local ev = require("resty.events").new(opts) 26 | if not ev then 27 | ngx.log(ngx.ERR, "failed to new events") 28 | end 29 | 30 | _G.ev = ev 31 | } 32 | init_worker_by_lua_block { 33 | local ev = _G.ev 34 | local ok, err = ev:init_worker() 35 | if not ok then 36 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 37 | end 38 | 39 | local semaphore = require("ngx.semaphore") 40 | local sema = semaphore.new(1) 41 | 42 | local id = ngx.worker.id() 43 | 44 | if id == 2 then 45 | ngx.shared.dict:set("broker-pid", ngx.worker.pid()) 46 | end 47 | 48 | if id == 0 or id == 1 then 49 | ev:subscribe("content_by_lua", "first", function() 50 | ngx.sleep(0) 51 | ngx.log(ngx.INFO, id, " first handler") 52 | local ok, err = sema:wait(0) 53 | if ok then 54 | ngx.log(ngx.INFO, id, " first got mutex") 55 | ngx.sleep(5) 56 | else 57 | ngx.log(ngx.CRIT, id, " first did not got mutex: ", err) 58 | end 59 | sema:post() 60 | end) 61 | end 62 | 63 | if id == nil then 64 | ev:subscribe("content_by_lua", "second", function() 65 | ngx.sleep(0) 66 | ngx.log(ngx.INFO, "privileged killer") 67 | ngx.timer.at(0, function() 68 | ngx.log(ngx.INFO, "killing ", ngx.shared.dict:get("broker-pid")) 69 | os.execute("kill -9 " .. ngx.shared.dict:get("broker-pid")) 70 | ngx.sleep(0) 71 | end) 72 | end) 73 | end 74 | 75 | if id == 0 or id == 1 then 76 | ev:subscribe("content_by_lua", "third", function() 77 | ngx.sleep(0) 78 | ngx.log(ngx.INFO, id, " third handler") 79 | local ok, err = sema:wait(0) 80 | if ok then 81 | ngx.log(ngx.INFO, id, " third got mutex") 82 | else 83 | ngx.log(ngx.CRIT, id, " third did not got mutex: ", err) 84 | end 85 | sema:post() 86 | end) 87 | end 88 | 89 | -- These tests are left to be enabled when we get this reliable (needs more reliability fixes in lib): 90 | --if id == 0 or id == 1 then 91 | -- ev:subscribe("content_by_lua", "fourth", function() 92 | -- ngx.sleep(0) 93 | -- ngx.log(ngx.INFO, id, " fourth handler") 94 | -- end) 95 | --end 96 | 97 | --ev:subscribe("content_by_lua", "fifth", function() 98 | -- ngx.sleep(0) 99 | -- ngx.log(ngx.INFO, id or "privileged", " fifth handler") 100 | --end) 101 | 102 | _G.ev = ev 103 | } 104 | 105 | server { 106 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 107 | location / { 108 | content_by_lua_block { 109 | _G.ev:run() 110 | } 111 | } 112 | } 113 | --- config 114 | location = /test { 115 | access_by_lua_block { 116 | local ev = _G.ev 117 | ev:publish("all", "content_by_lua", "first") 118 | ngx.sleep(0) 119 | ev:publish("all", "content_by_lua", "second") 120 | ngx.sleep(0) 121 | ev:publish("all", "content_by_lua", "third") 122 | ngx.sleep(0) 123 | -- These tests are left to be enabled when we get this reliable (needs more reliability fixes in lib): 124 | --ev:publish("all", "content_by_lua", "fourth") 125 | --ngx.sleep(0) 126 | --ev:publish("all", "content_by_lua", "fifth") 127 | --ngx.sleep(0) 128 | ngx.say("ok") 129 | } 130 | } 131 | --- request 132 | GET /test 133 | --- wait: 10 134 | --- response_body 135 | ok 136 | --- no_error_log 137 | [crit] 138 | --- error_log eval 139 | [ 140 | qr/0 first handler/, 141 | qr/1 first handler/, 142 | qr/0 first got mutex/, 143 | qr/1 first got mutex/, 144 | qr/privileged killer/, 145 | qr/exited on signal 9/, 146 | qr/0 third got mutex/, 147 | qr/1 third got mutex/, 148 | # These tests are left to be enabled when we get this reliable (needs more reliability fixes in lib): 149 | # qr/0 fourth handler/, 150 | # qr/1 fourth handler/, 151 | # qr/privileged fifth handler/, 152 | # qr/0 fifth handler/, 153 | # qr/1 fifth handler/, 154 | # qr/2 fifth handler/, 155 | ] 156 | --- skip_nginx 157 | 11: < 1.21.4 158 | -------------------------------------------------------------------------------- /t/disable-listening.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | # with index always turned on 3 | use Test::Nginx::Socket::Lua; 4 | 5 | #worker_connections(1014); 6 | #master_process_enabled(1); 7 | #log_level('warn'); 8 | 9 | repeat_each(2); 10 | 11 | plan tests => repeat_each() * (blocks() * 5); 12 | 13 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 14 | 15 | #no_diff(); 16 | #no_long_string(); 17 | master_on(); 18 | workers(2); 19 | run_tests(); 20 | 21 | __DATA__ 22 | 23 | === TEST 1: sanity: unix domain socket works well 24 | --- http_config 25 | server { 26 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 27 | location / { 28 | content_by_lua_block { 29 | ngx.say("unix ok") 30 | } 31 | } 32 | } 33 | 34 | --- config 35 | location = /test { 36 | content_by_lua_block { 37 | local sock = ngx.socket.tcp() 38 | 39 | sock:settimeout(500) 40 | 41 | local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") 42 | if not ok then 43 | ngx.say("failed to connect: ", err) 44 | return 45 | end 46 | ngx.say("connect unix ok") 47 | } 48 | } 49 | --- request 50 | GET /test 51 | --- response_body 52 | connect unix ok 53 | --- no_error_log 54 | [error] 55 | [crit] 56 | [alert] 57 | 58 | 59 | 60 | === TEST 2: enable unix domain socket in worker #1 61 | --- http_config 62 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 63 | init_worker_by_lua_block { 64 | if ngx.worker.id() ~= 1 then 65 | require("resty.events.disable_listening")("unix:$TEST_NGINX_HTML_DIR/nginx.sock") 66 | end 67 | } 68 | 69 | server { 70 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 71 | location / { 72 | content_by_lua_block { 73 | ngx.say("unix ok #", ngx.worker.id()) 74 | } 75 | } 76 | } 77 | 78 | --- config 79 | location = /test { 80 | content_by_lua_block { 81 | local sock = ngx.socket.tcp() 82 | 83 | sock:settimeout(200) 84 | 85 | local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") 86 | if not ok then 87 | ngx.say("failed to connect: ", err) 88 | return 89 | end 90 | ngx.say("connect unix ok") 91 | 92 | local req = "GET /HTTP/1.1\r\nHost: test.com\r\n\r\n" 93 | local bytes, err = sock:send(req) 94 | if not bytes then 95 | ngx.say("failed to send http request: ", err) 96 | return 97 | end 98 | ngx.say("send unix ok") 99 | 100 | local line, err = sock:receive() 101 | if not line then 102 | ngx.say("failed to receive unix request: ", err) 103 | return 104 | end 105 | ngx.say("receive unix ok") 106 | ngx.say(line) 107 | 108 | sock:close() 109 | } 110 | } 111 | --- request 112 | GET /test 113 | --- response_body 114 | connect unix ok 115 | send unix ok 116 | receive unix ok 117 | unix ok #1 118 | --- no_error_log 119 | [error] 120 | [crit] 121 | [alert] 122 | 123 | 124 | 125 | === TEST 3: disable unix domain socket with wrong name 126 | --- http_config 127 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 128 | --- config 129 | location = /test { 130 | content_by_lua_block { 131 | local disable = require("resty.events.disable_listening") 132 | 133 | local _, err = disable("unix:") 134 | ngx.say(err) 135 | 136 | local _, err = disable("unix:/tmp/xxx.sock") 137 | ngx.say(err) 138 | } 139 | } 140 | --- request 141 | GET /test 142 | --- response_body 143 | failed to disable listening: unix: 144 | failed to disable listening: unix:/tmp/xxx.sock 145 | --- no_error_log 146 | [error] 147 | [crit] 148 | [alert] 149 | -------------------------------------------------------------------------------- /t/events-compat.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 7) + 1; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: posting events and handling events, broadcast and local 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 25 | init_by_lua_block { 26 | local ev = require "resty.events.compat" 27 | _G.ev = ev 28 | } 29 | init_worker_by_lua_block { 30 | local ev = _G.ev 31 | 32 | local opts = { 33 | --broker_id = 0, 34 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 35 | } 36 | 37 | local ok, err = ev.configure(opts) 38 | if not ok then 39 | ngx.log(ngx.ERR, "failed to configure events: ", err) 40 | end 41 | 42 | assert(ev.configured()) 43 | 44 | ev.register(function(data, event, source, wid) 45 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 46 | ", data=", data) 47 | end) 48 | } 49 | 50 | server { 51 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 52 | location / { 53 | content_by_lua_block { 54 | require("resty.events.compat").run() 55 | } 56 | } 57 | } 58 | --- config 59 | location = /test { 60 | content_by_lua_block { 61 | local ev = require "resty.events.compat" 62 | 63 | ev.post("content_by_lua", "request1", "01234567890") 64 | ev.post_local("content_by_lua", "request2", "01234567890") 65 | ev.post("content_by_lua", "request3", "01234567890") 66 | 67 | ngx.say("ok") 68 | } 69 | } 70 | --- request 71 | GET /test 72 | --- response_body 73 | ok 74 | --- error_log 75 | event published to 1 workers 76 | --- no_error_log 77 | [error] 78 | [crit] 79 | [alert] 80 | --- grep_error_log eval: qr/worker-events: .*/ 81 | --- grep_error_log_out eval 82 | qr/^worker-events: handling event; source=content_by_lua, event=request2, wid=nil 83 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 84 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 85 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 86 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 87 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=01234567890$/ 88 | 89 | 90 | 91 | === TEST 2: worker.events handling remote events 92 | --- http_config 93 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 94 | init_by_lua_block { 95 | local ev = require "resty.events.compat" 96 | _G.ev = ev 97 | } 98 | init_worker_by_lua_block { 99 | local ev = _G.ev 100 | 101 | local opts = { 102 | --broker_id = 0, 103 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 104 | } 105 | 106 | local ok, err = ev.configure(opts) 107 | if not ok then 108 | ngx.log(ngx.ERR, "failed to configure events: ", err) 109 | end 110 | 111 | assert(ev.configured()) 112 | 113 | ev.register(function(data, event, source, wid) 114 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 115 | ", data=", tostring(data)) 116 | end) 117 | } 118 | 119 | server { 120 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 121 | location / { 122 | content_by_lua_block { 123 | require("resty.events.compat").run() 124 | } 125 | } 126 | } 127 | --- config 128 | location = /test { 129 | content_by_lua_block { 130 | local ev = require "resty.events.compat" 131 | 132 | ev.post("content_by_lua", "request1", "01234567890") 133 | ev.post_local("content_by_lua", "request2", "01234567890") 134 | ev.post("content_by_lua", "request3", "01234567890") 135 | 136 | ngx.say("ok") 137 | } 138 | } 139 | --- request 140 | GET /test 141 | --- response_body 142 | ok 143 | --- error_log 144 | event published to 1 workers 145 | --- no_error_log 146 | [error] 147 | [crit] 148 | [alert] 149 | --- grep_error_log eval: qr/worker-events: .*/ 150 | --- grep_error_log_out eval 151 | qr/^worker-events: handling event; source=content_by_lua, event=request2, wid=nil 152 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 153 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 154 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 155 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 156 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=01234567890$/ 157 | 158 | 159 | 160 | === TEST 3: worker.events 'one' being done, and only once 161 | --- http_config 162 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 163 | init_by_lua_block { 164 | local ev = require "resty.events.compat" 165 | _G.ev = ev 166 | } 167 | init_worker_by_lua_block { 168 | local ev = _G.ev 169 | 170 | local opts = { 171 | unique_timeout = 0.04, 172 | --broker_id = 0, 173 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 174 | } 175 | 176 | local ev = require "resty.events.compat" 177 | local ok, err = ev.configure(opts) 178 | if not ok then 179 | ngx.log(ngx.ERR, "failed to configure events: ", err) 180 | end 181 | 182 | assert(ev.configured()) 183 | 184 | ev.register(function(data, event, source, wid) 185 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 186 | ", data=", tostring(data)) 187 | end) 188 | } 189 | 190 | server { 191 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 192 | location / { 193 | content_by_lua_block { 194 | require("resty.events.compat").run() 195 | } 196 | } 197 | } 198 | --- config 199 | location = /test { 200 | content_by_lua_block { 201 | local ev = require "resty.events.compat" 202 | 203 | ev.post("content_by_lua", "request1", "01234567890") 204 | ev.post("content_by_lua", "request2", "01234567890", "unique_value") 205 | ev.post("content_by_lua", "request3", "01234567890", "unique_value") 206 | 207 | ngx.sleep(0.1) -- wait for unique timeout to expire 208 | 209 | ev.post("content_by_lua", "request4", "01234567890", "unique_value") 210 | ev.post("content_by_lua", "request5", "01234567890", "unique_value") 211 | ev.post("content_by_lua", "request6", "01234567890") 212 | 213 | ngx.say("ok") 214 | } 215 | } 216 | --- request 217 | GET /test 218 | --- response_body 219 | ok 220 | --- error_log 221 | event published to 1 workers 222 | unique event is duplicate on worker #0: unique_value 223 | --- no_error_log 224 | [error] 225 | [crit] 226 | [alert] 227 | --- grep_error_log eval: qr/worker-events: .*/ 228 | --- grep_error_log_out eval 229 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 230 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 231 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 232 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=01234567890 233 | worker-events: handling event; source=content_by_lua, event=request4, wid=\d+ 234 | worker-events: handler event; source=content_by_lua, event=request4, wid=\d+, data=01234567890 235 | worker-events: handling event; source=content_by_lua, event=request6, wid=\d+ 236 | worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, data=01234567890$/ 237 | -------------------------------------------------------------------------------- /t/events.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 7); 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: posting events and handling events, broadcast and local 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 25 | init_by_lua_block { 26 | local opts = { 27 | --broker_id = 0, 28 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 29 | } 30 | 31 | local ev = require("resty.events").new(opts) 32 | if not ev then 33 | ngx.log(ngx.ERR, "failed to new events") 34 | end 35 | 36 | _G.ev = ev 37 | } 38 | init_worker_by_lua_block { 39 | local ev = _G.ev 40 | local ok, err = ev:init_worker() 41 | if not ok then 42 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 43 | end 44 | 45 | assert(not ev:is_ready()) 46 | 47 | ev:subscribe("*", "*", function(data, event, source, wid) 48 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 49 | ", data=", data) 50 | end) 51 | 52 | _G.ev = ev 53 | } 54 | 55 | server { 56 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 57 | location / { 58 | content_by_lua_block { 59 | _G.ev:run() 60 | } 61 | } 62 | } 63 | --- config 64 | location = /test { 65 | content_by_lua_block { 66 | local ev = _G.ev 67 | 68 | assert(ev:is_ready()) 69 | 70 | ev:publish("all", "content_by_lua", "request1", "01234567890") 71 | ev:publish("current", "content_by_lua", "request2", "01234567890") 72 | ev:publish("all", "content_by_lua", "request3", "01234567890") 73 | 74 | ngx.say("ok") 75 | } 76 | } 77 | --- request 78 | GET /test 79 | --- response_body 80 | ok 81 | --- error_log 82 | event published to 1 workers 83 | --- no_error_log 84 | [error] 85 | [crit] 86 | [alert] 87 | --- grep_error_log eval: qr/worker-events: .*/ 88 | --- grep_error_log_out eval 89 | qr/^worker-events: handling event; source=content_by_lua, event=request2, wid=nil 90 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 91 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 92 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 93 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 94 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=01234567890$/ 95 | 96 | 97 | 98 | === TEST 2: worker.events handling remote events 99 | --- http_config 100 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 101 | init_by_lua_block { 102 | local opts = { 103 | --broker_id = 0, 104 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 105 | } 106 | 107 | local ev = require("resty.events").new(opts) 108 | if not ev then 109 | ngx.log(ngx.ERR, "failed to new events") 110 | end 111 | 112 | _G.ev = ev 113 | } 114 | init_worker_by_lua_block { 115 | local ev = _G.ev 116 | local ok, err = ev:init_worker() 117 | if not ok then 118 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 119 | end 120 | 121 | assert(not ev:is_ready()) 122 | 123 | ev:subscribe("*", "*", function(data, event, source, wid) 124 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 125 | ", data=", tostring(data)) 126 | end) 127 | 128 | _G.ev = ev 129 | } 130 | 131 | server { 132 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 133 | location / { 134 | content_by_lua_block { 135 | _G.ev:run() 136 | } 137 | } 138 | } 139 | --- config 140 | location = /test { 141 | content_by_lua_block { 142 | local ev = _G.ev 143 | 144 | assert(ev:is_ready()) 145 | 146 | ev:publish("all", "content_by_lua", "request1", "01234567890") 147 | ev:publish("current", "content_by_lua", "request2", "01234567890") 148 | ev:publish("all", "content_by_lua", "request3", "01234567890") 149 | 150 | ngx.say("ok") 151 | } 152 | } 153 | --- request 154 | GET /test 155 | --- response_body 156 | ok 157 | --- error_log 158 | event published to 1 workers 159 | --- no_error_log 160 | [error] 161 | [crit] 162 | [alert] 163 | --- grep_error_log eval: qr/worker-events: .*/ 164 | --- grep_error_log_out eval 165 | qr/^worker-events: handling event; source=content_by_lua, event=request2, wid=nil 166 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 167 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 168 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 169 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 170 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=01234567890$/ 171 | 172 | 173 | 174 | === TEST 3: worker.events 'one' being done, and only once 175 | --- http_config 176 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 177 | init_by_lua_block { 178 | local opts = { 179 | unique_timeout = 0.04, 180 | --broker_id = 0, 181 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 182 | } 183 | 184 | local ev = require("resty.events").new(opts) 185 | if not ev then 186 | ngx.log(ngx.ERR, "failed to new events") 187 | end 188 | 189 | _G.ev = ev 190 | } 191 | init_worker_by_lua_block { 192 | local ev = _G.ev 193 | local ok, err = ev:init_worker() 194 | if not ok then 195 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 196 | end 197 | 198 | assert(not ev:is_ready()) 199 | 200 | ev:subscribe("*", "*", function(data, event, source, wid) 201 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 202 | ", data=", tostring(data)) 203 | end) 204 | 205 | _G.ev = ev 206 | } 207 | 208 | server { 209 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 210 | location / { 211 | content_by_lua_block { 212 | _G.ev:run() 213 | } 214 | } 215 | } 216 | --- config 217 | location = /test { 218 | content_by_lua_block { 219 | local ev = _G.ev 220 | 221 | assert(ev:is_ready()) 222 | 223 | ev:publish("all", "content_by_lua", "request1", "01234567890") 224 | ev:publish("unique_value", "content_by_lua", "request2", "01234567890") 225 | ev:publish("unique_value", "content_by_lua", "request3", "01234567890") 226 | 227 | ngx.sleep(0.1) -- wait for unique timeout to expire 228 | 229 | ev:publish("unique_value", "content_by_lua", "request4", "01234567890") 230 | ev:publish("unique_value", "content_by_lua", "request5", "01234567890") 231 | ev:publish("all", "content_by_lua", "request6", "01234567890") 232 | 233 | ngx.say("ok") 234 | } 235 | } 236 | --- request 237 | GET /test 238 | --- response_body 239 | ok 240 | --- error_log 241 | event published to 1 workers 242 | unique event is duplicate on worker #0: unique_value 243 | --- no_error_log 244 | [error] 245 | [crit] 246 | [alert] 247 | --- grep_error_log eval: qr/worker-events: .*/ 248 | --- grep_error_log_out eval 249 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 250 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 251 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 252 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=01234567890 253 | worker-events: handling event; source=content_by_lua, event=request4, wid=\d+ 254 | worker-events: handler event; source=content_by_lua, event=request4, wid=\d+, data=01234567890 255 | worker-events: handling event; source=content_by_lua, event=request6, wid=\d+ 256 | worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, data=01234567890$/ 257 | 258 | 259 | 260 | === TEST 4: publish events at anywhere 261 | --- http_config 262 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 263 | init_by_lua_block { 264 | local opts = { 265 | --broker_id = 0, 266 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 267 | } 268 | 269 | local ev = require("resty.events").new(opts) 270 | if not ev then 271 | ngx.log(ngx.ERR, "failed to new events") 272 | end 273 | 274 | _G.ev = ev 275 | } 276 | init_worker_by_lua_block { 277 | local ev = _G.ev 278 | 279 | ev:publish("all", "content_by_lua", "request1", "01234567890") 280 | 281 | local ok, err = ev:init_worker() 282 | if not ok then 283 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 284 | end 285 | 286 | assert(not ev:is_ready()) 287 | 288 | ev:publish("current", "content_by_lua", "request2", "01234567890") 289 | 290 | ev:subscribe("*", "*", function(data, event, source, wid) 291 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 292 | ", data=", tostring(data)) 293 | end) 294 | 295 | ev:publish("all", "content_by_lua", "request3", "01234567890") 296 | 297 | _G.ev = ev 298 | } 299 | 300 | server { 301 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 302 | location / { 303 | content_by_lua_block { 304 | _G.ev:run() 305 | } 306 | } 307 | } 308 | --- config 309 | location = /test { 310 | content_by_lua_block { 311 | ngx.say("ok") 312 | } 313 | } 314 | --- request 315 | GET /test 316 | --- response_body 317 | ok 318 | --- error_log 319 | event published to 1 workers 320 | --- no_error_log 321 | [error] 322 | [crit] 323 | [alert] 324 | --- grep_error_log eval: qr/worker-events: .*/ 325 | --- grep_error_log_out eval 326 | qr/^worker-events: handling event; source=content_by_lua, event=request2, wid=nil 327 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 328 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 329 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 330 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 331 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=01234567890$/ 332 | 333 | 334 | 335 | === TEST 5: configure with wrong params 336 | --- http_config 337 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 338 | --- config 339 | location = /test { 340 | content_by_lua_block { 341 | local function trim(str) 342 | return string.sub(str, #"lualib/resty/events/init.lua:62: " + 1) 343 | end 344 | 345 | local ev = require("resty.events") 346 | 347 | local ok, err = pcall(ev.new, {broker_id = "1"}) 348 | assert(not ok) 349 | ngx.say(trim(err)) 350 | 351 | local ok, err = pcall(ev.new, {broker_id = -1}) 352 | assert(not ok) 353 | ngx.say(trim(err)) 354 | 355 | local ok, err = pcall(ev.new, {broker_id = 2}) 356 | assert(not ok) 357 | ngx.say(trim(err)) 358 | 359 | local ok, err = pcall(ev.new, {}) 360 | assert(not ok) 361 | ngx.say(trim(err)) 362 | 363 | local ok, err = pcall(ev.new, {listening = 123}) 364 | assert(not ok) 365 | ngx.say(trim(err)) 366 | 367 | local ok, err = pcall(ev.new, {listening = "/tmp/xxx.sock"}) 368 | assert(not ok) 369 | ngx.say(trim(err)) 370 | 371 | local ok, err = pcall(ev.new, {listening = "unix:x", unique_timeout = '1'}) 372 | assert(not ok) 373 | ngx.say(trim(err)) 374 | 375 | local ok, err = pcall(ev.new, {listening = "unix:x", unique_timeout = -1}) 376 | assert(not ok) 377 | ngx.say(trim(err)) 378 | 379 | local ok, err = pcall(ev.new, {listening = "unix:x", max_queue_len = "1"}) 380 | assert(not ok) 381 | ngx.say(trim(err)) 382 | 383 | local ok, err = pcall(ev.new, {listening = "unix:x", max_queue_len = -1}) 384 | assert(not ok) 385 | ngx.say(trim(err)) 386 | 387 | local ok, err = pcall(ev.new, {listening = "unix:x", max_payload_len = "1"}) 388 | assert(not ok) 389 | ngx.say(trim(err)) 390 | 391 | local ok, err = pcall(ev.new, {listening = "unix:x", max_payload_len = -1}) 392 | assert(not ok) 393 | ngx.say(trim(err)) 394 | 395 | local ok, err = pcall(ev.new, {listening = "unix:x", max_payload_len = 2^24 + 1}) 396 | assert(not ok) 397 | ngx.say(trim(err)) 398 | 399 | local ok, err = pcall(ev.new, {listening = "unix:x", enable_privileged_agent = "invalid"}) 400 | assert(not ok) 401 | ngx.say(trim(err)) 402 | } 403 | } 404 | --- request 405 | GET /test 406 | --- response_body 407 | "broker_id" option must be a number 408 | "broker_id" option is invalid 409 | "broker_id" option is invalid 410 | "listening" option required to start 411 | "listening" option must be a string 412 | "listening" option must start with unix: 413 | optional "unique_timeout" option must be a number 414 | "unique_timeout" must be greater than 0 415 | "max_queue_len" option must be a number 416 | "max_queue_len" option is invalid 417 | "max_payload_len" option must be a number 418 | "max_payload_len" option is invalid 419 | "max_payload_len" option is invalid 420 | "enable_privileged_agent" option must be a boolean 421 | --- no_error_log 422 | [error] 423 | [crit] 424 | [alert] 425 | [emerg] 426 | 427 | 428 | 429 | === TEST 6: publish events exceeding 65535 bytes 430 | --- http_config 431 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 432 | init_by_lua_block { 433 | local opts = { 434 | --broker_id = 0, 435 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 436 | max_payload_len = 1024 * 1024 * 2, 437 | } 438 | 439 | local ev = require("resty.events").new(opts) 440 | if not ev then 441 | ngx.log(ngx.ERR, "failed to new events") 442 | end 443 | 444 | _G.ev = ev 445 | } 446 | init_worker_by_lua_block { 447 | local ev = _G.ev 448 | local ok, err = ev:init_worker() 449 | if not ok then 450 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 451 | end 452 | 453 | assert(not ev:is_ready()) 454 | 455 | ev:subscribe("*", "*", function(data, event, source, wid) 456 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 457 | ", big=", #data > 65535) 458 | end) 459 | 460 | _G.ev = ev 461 | } 462 | 463 | server { 464 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 465 | location / { 466 | content_by_lua_block { 467 | _G.ev:run() 468 | } 469 | } 470 | } 471 | --- config 472 | location = /test { 473 | content_by_lua_block { 474 | local ev = _G.ev 475 | 476 | assert(ev:is_ready()) 477 | 478 | local ok, err = ev:publish("all", "content_by_lua", "request1", 479 | string.rep("a", 65537)) 480 | ngx.say(err) 481 | 482 | ok, err = ev:publish("unique_hash", "content_by_lua", "request2", 483 | string.rep("a", 10* 65535)) 484 | ngx.say(err) 485 | 486 | local ok, err = ev:publish("all", "content_by_lua", "request3", 487 | string.rep("a", 1024*1024)) 488 | ngx.say(err) 489 | 490 | ok, err = ev:publish("all", "content_by_lua", "request4", "01234567890") 491 | ngx.say(err) 492 | 493 | ngx.say("ok") 494 | } 495 | } 496 | --- request 497 | GET /test 498 | --- response_body 499 | nil 500 | nil 501 | nil 502 | nil 503 | ok 504 | --- no_error_log 505 | [warn] 506 | [error] 507 | [crit] 508 | [alert] 509 | --- grep_error_log eval: qr/worker-events: .*/ 510 | --- grep_error_log_out eval 511 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 512 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, big=true 513 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 514 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, big=true 515 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 516 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, big=true 517 | worker-events: handling event; source=content_by_lua, event=request4, wid=\d+ 518 | worker-events: handler event; source=content_by_lua, event=request4, wid=\d+, big=false$/ 519 | 520 | 521 | 522 | === TEST 7: customize publish events limitation 523 | --- http_config 524 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 525 | init_by_lua_block { 526 | local opts = { 527 | --broker_id = 0, 528 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 529 | max_payload_len = 200, 530 | } 531 | 532 | local ev = require("resty.events").new(opts) 533 | if not ev then 534 | ngx.log(ngx.ERR, "failed to new events") 535 | end 536 | 537 | _G.ev = ev 538 | } 539 | init_worker_by_lua_block { 540 | local ev = _G.ev 541 | local ok, err = ev:init_worker() 542 | if not ok then 543 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 544 | end 545 | 546 | assert(not ev:is_ready()) 547 | 548 | ev:subscribe("*", "*", function(data, event, source, wid) 549 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 550 | ", data=", data) 551 | end) 552 | 553 | _G.ev = ev 554 | } 555 | 556 | server { 557 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 558 | location / { 559 | content_by_lua_block { 560 | _G.ev:run() 561 | } 562 | } 563 | } 564 | --- config 565 | location = /test { 566 | content_by_lua_block { 567 | local ev = _G.ev 568 | 569 | assert(ev:is_ready()) 570 | 571 | local ok, err = ev:publish("all", "content_by_lua", "request1", 572 | string.rep("a", 200)) 573 | ngx.say(err) 574 | 575 | local ok, err = ev:publish("unique_hash", "content_by_lua", "request2", 576 | string.rep("a", 200)) 577 | ngx.say(err) 578 | 579 | ok, err = ev:publish("unique_hash", "content_by_lua", "request3", "01234567890") 580 | ngx.say(err) 581 | 582 | ngx.say("ok") 583 | } 584 | } 585 | --- request 586 | GET /test 587 | --- response_body 588 | failed to publish event: payload exceeds the limitation (200) 589 | failed to publish event: payload exceeds the limitation (200) 590 | nil 591 | ok 592 | --- no_error_log 593 | [warn] 594 | [error] 595 | [crit] 596 | [alert] 597 | --- grep_error_log eval: qr/worker-events: .*/ 598 | --- grep_error_log_out eval 599 | qr/^worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 600 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=01234567890$/ 601 | -------------------------------------------------------------------------------- /t/listening-off.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 7); 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: posting events and handling events, broadcast and local 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 25 | init_by_lua_block { 26 | local opts = { 27 | --broker_id = 0, 28 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 29 | testing = true, 30 | } 31 | 32 | local ev = require("resty.events").new(opts) 33 | if not ev then 34 | ngx.log(ngx.ERR, "failed to new events") 35 | end 36 | 37 | _G.ev = ev 38 | } 39 | init_worker_by_lua_block { 40 | local ev = _G.ev 41 | local ok, err = ev:init_worker() 42 | if not ok then 43 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 44 | end 45 | 46 | assert(not ev:is_ready()) 47 | 48 | ev:subscribe("*", "*", function(data, event, source, wid) 49 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 50 | ", data=", data) 51 | end) 52 | 53 | _G.ev = ev 54 | } 55 | --- config 56 | location = /test { 57 | content_by_lua_block { 58 | local ev = _G.ev 59 | 60 | assert(ev:is_ready()) 61 | 62 | ev:publish("all", "content_by_lua", "request1", "01234567890") 63 | ev:publish("current", "content_by_lua", "request2", "01234567890") 64 | ev:publish("all", "content_by_lua", "request3", "01234567890") 65 | 66 | ngx.say("ok") 67 | } 68 | } 69 | --- request 70 | GET /test 71 | --- response_body 72 | ok 73 | --- error_log 74 | event published to 1 workers 75 | --- no_error_log 76 | [error] 77 | [crit] 78 | [alert] 79 | --- grep_error_log eval: qr/worker-events: .*/ 80 | --- grep_error_log_out eval 81 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=nil 82 | worker-events: handler event; source=content_by_lua, event=request1, wid=nil, data=01234567890 83 | worker-events: handling event; source=content_by_lua, event=request2, wid=nil 84 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 85 | worker-events: handling event; source=content_by_lua, event=request3, wid=nil 86 | worker-events: handler event; source=content_by_lua, event=request3, wid=nil, data=01234567890$/ 87 | 88 | 89 | 90 | === TEST 2: worker.events handling remote events 91 | --- http_config 92 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 93 | init_by_lua_block { 94 | local opts = { 95 | --broker_id = 0, 96 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 97 | testing = true, 98 | } 99 | 100 | local ev = require("resty.events").new(opts) 101 | if not ev then 102 | ngx.log(ngx.ERR, "failed to new events") 103 | end 104 | 105 | _G.ev = ev 106 | } 107 | init_worker_by_lua_block { 108 | local ev = _G.ev 109 | local ok, err = ev:init_worker() 110 | if not ok then 111 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 112 | end 113 | 114 | assert(not ev:is_ready()) 115 | 116 | ev:subscribe("*", "*", function(data, event, source, wid) 117 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 118 | ", data=", tostring(data)) 119 | end) 120 | 121 | _G.ev = ev 122 | } 123 | --- config 124 | location = /test { 125 | content_by_lua_block { 126 | local ev = _G.ev 127 | 128 | assert(ev:is_ready()) 129 | 130 | ev:publish("all", "content_by_lua", "request1", "01234567890") 131 | ev:publish("current", "content_by_lua", "request2", "01234567890") 132 | ev:publish("all", "content_by_lua", "request3", "01234567890") 133 | 134 | ngx.say("ok") 135 | } 136 | } 137 | --- request 138 | GET /test 139 | --- response_body 140 | ok 141 | --- error_log 142 | event published to 1 workers 143 | --- no_error_log 144 | [error] 145 | [crit] 146 | [alert] 147 | --- grep_error_log eval: qr/worker-events: .*/ 148 | --- grep_error_log_out eval 149 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=nil 150 | worker-events: handler event; source=content_by_lua, event=request1, wid=nil, data=01234567890 151 | worker-events: handling event; source=content_by_lua, event=request2, wid=nil 152 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 153 | worker-events: handling event; source=content_by_lua, event=request3, wid=nil 154 | worker-events: handler event; source=content_by_lua, event=request3, wid=nil, data=01234567890$/ 155 | 156 | 157 | 158 | === TEST 3: worker.events 'one' being done, and only once 159 | --- http_config 160 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 161 | init_by_lua_block { 162 | local opts = { 163 | unique_timeout = 0.04, 164 | --broker_id = 0, 165 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 166 | testing = true, 167 | } 168 | 169 | local ev = require("resty.events").new(opts) 170 | if not ev then 171 | ngx.log(ngx.ERR, "failed to new events") 172 | end 173 | 174 | _G.ev = ev 175 | } 176 | init_worker_by_lua_block { 177 | local ev = _G.ev 178 | local ok, err = ev:init_worker() 179 | if not ok then 180 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 181 | end 182 | 183 | assert(not ev:is_ready()) 184 | 185 | ev:subscribe("*", "*", function(data, event, source, wid) 186 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 187 | ", data=", tostring(data)) 188 | end) 189 | 190 | _G.ev = ev 191 | } 192 | --- config 193 | location = /test { 194 | content_by_lua_block { 195 | local ev = _G.ev 196 | 197 | assert(ev:is_ready()) 198 | 199 | ev:publish("all", "content_by_lua", "request1", "01234567890") 200 | ev:publish("unique_value", "content_by_lua", "request2", "01234567890") 201 | ev:publish("unique_value", "content_by_lua", "request3", "01234567890") 202 | 203 | ngx.sleep(0.1) -- wait for unique timeout to expire 204 | 205 | ev:publish("unique_value", "content_by_lua", "request4", "01234567890") 206 | ev:publish("unique_value", "content_by_lua", "request5", "01234567890") 207 | ev:publish("all", "content_by_lua", "request6", "01234567890") 208 | 209 | ngx.say("ok") 210 | } 211 | } 212 | --- request 213 | GET /test 214 | --- response_body 215 | ok 216 | --- error_log 217 | event published to 1 workers 218 | --- no_error_log 219 | [error] 220 | [crit] 221 | [alert] 222 | --- grep_error_log eval: qr/worker-events: .*/ 223 | --- grep_error_log_out eval 224 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=nil 225 | worker-events: handler event; source=content_by_lua, event=request1, wid=nil, data=01234567890 226 | worker-events: handling event; source=content_by_lua, event=request2, wid=nil 227 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 228 | worker-events: handling event; source=content_by_lua, event=request3, wid=nil 229 | worker-events: handler event; source=content_by_lua, event=request3, wid=nil, data=01234567890 230 | worker-events: handling event; source=content_by_lua, event=request4, wid=nil 231 | worker-events: handler event; source=content_by_lua, event=request4, wid=nil, data=01234567890 232 | worker-events: handling event; source=content_by_lua, event=request5, wid=nil 233 | worker-events: handler event; source=content_by_lua, event=request5, wid=nil, data=01234567890 234 | worker-events: handling event; source=content_by_lua, event=request6, wid=nil 235 | worker-events: handler event; source=content_by_lua, event=request6, wid=nil, data=01234567890$/ 236 | 237 | 238 | 239 | === TEST 4: publish events at anywhere 240 | --- http_config 241 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 242 | init_by_lua_block { 243 | local opts = { 244 | --broker_id = 0, 245 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 246 | testing = true, 247 | } 248 | 249 | local ev = require("resty.events").new(opts) 250 | if not ev then 251 | ngx.log(ngx.ERR, "failed to new events") 252 | end 253 | 254 | _G.ev = ev 255 | } 256 | init_worker_by_lua_block { 257 | local ev = _G.ev 258 | 259 | ev:subscribe("*", "*", function(data, event, source, wid) 260 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 261 | ", data=", tostring(data)) 262 | end) 263 | 264 | ev:publish("all", "content_by_lua", "request1", "01234567890") 265 | 266 | local ok, err = ev:init_worker() 267 | if not ok then 268 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 269 | end 270 | 271 | assert(not ev:is_ready()) 272 | 273 | ev:publish("current", "content_by_lua", "request2", "01234567890") 274 | 275 | ev:publish("all", "content_by_lua", "request3", "01234567890") 276 | 277 | _G.ev = ev 278 | } 279 | --- config 280 | location = /test { 281 | content_by_lua_block { 282 | ngx.say("ok") 283 | } 284 | } 285 | --- request 286 | GET /test 287 | --- response_body 288 | ok 289 | --- error_log 290 | event published to 1 workers 291 | --- no_error_log 292 | [error] 293 | [crit] 294 | [alert] 295 | --- grep_error_log eval: qr/worker-events: .*/ 296 | --- grep_error_log_out eval 297 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=nil 298 | worker-events: handler event; source=content_by_lua, event=request1, wid=nil, data=01234567890 299 | worker-events: handling event; source=content_by_lua, event=request2, wid=nil 300 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 301 | worker-events: handling event; source=content_by_lua, event=request3, wid=nil 302 | worker-events: handler event; source=content_by_lua, event=request3, wid=nil, data=01234567890$/ 303 | -------------------------------------------------------------------------------- /t/privileged.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 15) + 2; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | master_on(); 17 | workers(3); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: posting events and handling events, broadcast 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 25 | init_by_lua_block { 26 | local process = require "ngx.process" 27 | process.enable_privileged_agent(100) 28 | 29 | local opts = { 30 | broker_id = 2, 31 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 32 | } 33 | 34 | local ev = require("resty.events").new(opts) 35 | if not ev then 36 | ngx.log(ngx.ERR, "failed to new events") 37 | end 38 | 39 | _G.ev = ev 40 | } 41 | init_worker_by_lua_block { 42 | local ev = _G.ev 43 | local ok, err = ev:init_worker() 44 | if not ok then 45 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 46 | end 47 | 48 | local i = 0 49 | 50 | ev:subscribe("*", "*", function(data, event, source, wid) 51 | i = i + 1 52 | ngx.log(ngx.DEBUG, i, " worker-events: handler event; source=", source, ", event=", event, 53 | ", wid=", wid, ", by=", (ngx.worker.id() or "nil"), ", data=", data) 54 | end) 55 | 56 | _G.ev = ev 57 | } 58 | 59 | server { 60 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 61 | location / { 62 | content_by_lua_block { 63 | _G.ev:run() 64 | } 65 | } 66 | } 67 | --- config 68 | location = /test { 69 | content_by_lua_block { 70 | local ev = _G.ev 71 | 72 | ev:publish("all", "content_by_lua", "request1", "01234567890") 73 | 74 | ngx.say("ok") 75 | } 76 | } 77 | --- request 78 | GET /test 79 | --- response_body 80 | ok 81 | --- error_log eval 82 | [ 83 | qr/privileged agent process/, 84 | qr/event published to 4 workers/, 85 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=0, data=01234567890/, 86 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=1, data=01234567890/, 87 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=2, data=01234567890/, 88 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=nil, data=01234567890/ 89 | ] 90 | --- no_error_log 91 | [error] 92 | [crit] 93 | [alert] 94 | --- grep_error_log eval: qr/worker-events: handling event; .*/ 95 | --- grep_error_log_out eval 96 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 97 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 98 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 99 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+$/ 100 | 101 | 102 | 103 | === TEST 2: posting events and handling events, local 104 | --- http_config 105 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 106 | init_by_lua_block { 107 | local process = require "ngx.process" 108 | process.enable_privileged_agent(100) 109 | 110 | local opts = { 111 | broker_id = 2, 112 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 113 | } 114 | 115 | local ev = require("resty.events").new(opts) 116 | if not ev then 117 | ngx.log(ngx.ERR, "failed to new events") 118 | end 119 | 120 | _G.ev = ev 121 | } 122 | init_worker_by_lua_block { 123 | local ev = _G.ev 124 | local ok, err = ev:init_worker() 125 | if not ok then 126 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 127 | end 128 | 129 | local i = 0 130 | 131 | ev:subscribe("*", "*", function(data, event, source, wid) 132 | if wid then 133 | i = 3 134 | else 135 | i = i + 1 136 | end 137 | 138 | ngx.log(ngx.DEBUG, i, " worker-events: handler event; source=", source, ", event=", event, 139 | ", wid=", wid, ", by=", (ngx.worker.id() or "nil"), ", data=", data) 140 | end) 141 | 142 | _G.ev = ev 143 | } 144 | 145 | server { 146 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 147 | location / { 148 | content_by_lua_block { 149 | _G.ev:run() 150 | } 151 | } 152 | } 153 | --- config 154 | location = /test { 155 | content_by_lua_block { 156 | local ev = _G.ev 157 | 158 | ev:publish("current", "content_by_lua", "request1", "ABCDEFGHIJK") 159 | ev:publish("current", "content_by_lua", "request2", "LMNOPQRSTUV") 160 | ev:publish("all", "content_by_lua", "request3", "01234567890") 161 | 162 | ngx.say("ok") 163 | } 164 | } 165 | --- request 166 | GET /test 167 | --- response_body 168 | ok 169 | --- error_log eval 170 | [ 171 | qr/privileged agent process/, 172 | qr/event published to 4 workers/, 173 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=nil, by=\d+, data=ABCDEFGHIJK/, 174 | qr/2 worker-events: handler event; source=content_by_lua, event=request2, wid=nil, by=\d+, data=LMNOPQRSTUV/, 175 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=0, data=01234567890/, 176 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=1, data=01234567890/, 177 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=2, data=01234567890/, 178 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=nil, data=01234567890/ 179 | ] 180 | --- no_error_log 181 | [error] 182 | [crit] 183 | [alert] 184 | --- grep_error_log eval: qr/worker-events: handling event; .*/ 185 | --- grep_error_log_out eval 186 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=nil 187 | worker-events: handling event; source=content_by_lua, event=request2, wid=nil 188 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 189 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 190 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 191 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+$/ 192 | 193 | 194 | 195 | === TEST 3: worker.events 'one' being done, and only once 196 | --- http_config 197 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 198 | init_by_lua_block { 199 | local process = require "ngx.process" 200 | process.enable_privileged_agent(100) 201 | 202 | local opts = { 203 | unique_timeout = 0.04, 204 | --broker_id = 0, 205 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 206 | } 207 | 208 | local ev = require("resty.events").new(opts) 209 | if not ev then 210 | ngx.log(ngx.ERR, "failed to new events") 211 | end 212 | 213 | _G.ev = ev 214 | } 215 | init_worker_by_lua_block { 216 | local ev = _G.ev 217 | local ok, err = ev:init_worker() 218 | if not ok then 219 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 220 | end 221 | 222 | ev:subscribe("*", "*", function(data, event, source, wid) 223 | ngx.log(ngx.DEBUG, "worker-events: handler event; source=", source, ", event=", event, 224 | ", wid=", wid, ", by=", (ngx.worker.id() or "nil"), ", data=", data) 225 | end) 226 | 227 | _G.ev = ev 228 | } 229 | 230 | server { 231 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 232 | location / { 233 | content_by_lua_block { 234 | _G.ev:run() 235 | } 236 | } 237 | } 238 | --- config 239 | location = /test { 240 | content_by_lua_block { 241 | local ev = _G.ev 242 | 243 | ev:publish("all", "content_by_lua", "request1", "01234567890") 244 | 245 | ev:publish("unique_value", "content_by_lua", "request2", "ABCDEFGHIJK") 246 | ev:publish("unique_value", "content_by_lua", "request3", "LMNOPQRSTUV") 247 | 248 | ngx.sleep(0.1) -- wait for unique timeout to expire 249 | 250 | ev:publish("unique_value", "content_by_lua", "request4", "WXYZABCDEFG") 251 | ev:publish("unique_value", "content_by_lua", "request5", "HIJKLMNOPQR") 252 | 253 | ev:publish("all", "content_by_lua", "request6", "STUVWXYZABC") 254 | 255 | ngx.say("ok") 256 | } 257 | } 258 | --- request 259 | GET /test 260 | --- response_body 261 | ok 262 | --- error_log eval 263 | [ 264 | qr/privileged agent process/, 265 | qr/event published to 1 workers/, 266 | qr/unique event is duplicate on worker #\d+: unique_value/, 267 | qr/event published to 4 workers/, 268 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=0, data=01234567890/, 269 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=1, data=01234567890/, 270 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=2, data=01234567890/, 271 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=nil, data=01234567890/, 272 | qr/worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, by=(\d+|nil), data=ABCDEFGHIJK/, 273 | qr/worker-events: handler event; source=content_by_lua, event=request4, wid=\d+, by=(\d+|nil), data=WXYZABCDEFG/, 274 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=0, data=STUVWXYZABC/, 275 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=1, data=STUVWXYZABC/, 276 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=2, data=STUVWXYZABC/, 277 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=nil, data=STUVWXYZABC/ 278 | ] 279 | 280 | --- no_error_log 281 | [error] 282 | [crit] 283 | [alert] 284 | LMNOPQRSTUV 285 | HIJKLMNOPQR 286 | -------------------------------------------------------------------------------- /t/protocol-privileged.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 11); 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | check_accum_error_log(); 19 | run_tests(); 20 | 21 | __DATA__ 22 | 23 | === TEST 1: sanity: send_frame, recv_frame (with privileged agent) 24 | --- http_config 25 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 26 | init_by_lua_block { 27 | local process = require "ngx.process" 28 | process.enable_privileged_agent(100) 29 | } 30 | init_worker_by_lua_block { 31 | local process = require "ngx.process" 32 | if process.type() ~= "privileged agent" then 33 | return 34 | end 35 | ngx.timer.at(0, function() 36 | local conn = require("resty.events.protocol").client.new() 37 | 38 | local ok, err = conn:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") 39 | if not ok then 40 | ngx.log(ngx.ERR, "failed to connect: ", err) 41 | return 42 | end 43 | 44 | local bytes, err = conn:send_frame("hello") 45 | if err then 46 | ngx.log(ngx.ERR, "failed to send data: ", err) 47 | end 48 | 49 | local data, err = conn:recv_frame() 50 | if not data or err then 51 | ngx.log(ngx.ERR, "failed to recv data: ", err) 52 | return 53 | end 54 | 55 | ngx.log(ngx.DEBUG, data) 56 | ngx.log(ngx.DEBUG, "cli recv len: ", #data) 57 | end) 58 | } 59 | server { 60 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 61 | location / { 62 | content_by_lua_block { 63 | local conn, err = require("resty.events.protocol").server.new() 64 | if not conn then 65 | ngx.say("failed to init socket: ", err) 66 | return 67 | end 68 | 69 | ngx.log(ngx.DEBUG, "Upgrade: ", ngx.var.http_upgrade) 70 | ngx.log(ngx.DEBUG, "Worker ID: ", conn.info.id) 71 | 72 | local data, err = conn:recv_frame() 73 | if not data or err then 74 | ngx.say("failed to recv data: ", err) 75 | return 76 | end 77 | ngx.log(ngx.DEBUG, "srv recv data: ", data) 78 | 79 | local bytes, err = conn:send_frame("world") 80 | if err then 81 | ngx.say("failed to send data: ", err) 82 | return 83 | end 84 | 85 | ngx.log(ngx.DEBUG, "srv send data: world") 86 | } 87 | } 88 | } 89 | --- config 90 | location = /test { 91 | content_by_lua_block { 92 | ngx.say("world") 93 | } 94 | } 95 | --- request 96 | GET /test 97 | --- response_body 98 | world 99 | --- error_log 100 | Upgrade: Kong-Worker-Events/1 101 | Worker ID: -1 102 | srv recv data: hello 103 | srv send data: world 104 | world 105 | cli recv len: 5 106 | --- no_error_log 107 | [error] 108 | [crit] 109 | [alert] 110 | -------------------------------------------------------------------------------- /t/protocol.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 9) - 4; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: sanity: send_frame, recv_frame 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 25 | server { 26 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 27 | location / { 28 | content_by_lua_block { 29 | local conn, err = require("resty.events.protocol").server.new() 30 | if not conn then 31 | ngx.say("failed to init socket: ", err) 32 | return 33 | end 34 | 35 | ngx.log(ngx.DEBUG, "Upgrade: ", ngx.var.http_upgrade) 36 | ngx.log(ngx.DEBUG, "Worker ID: ", conn.info.id) 37 | 38 | local data, err = conn:recv_frame() 39 | if not data or err then 40 | ngx.say("failed to recv data: ", err) 41 | return 42 | end 43 | ngx.log(ngx.DEBUG, "srv recv data: ", data) 44 | 45 | local bytes, err = conn:send_frame("world") 46 | if err then 47 | ngx.say("failed to send data: ", err) 48 | return 49 | end 50 | 51 | ngx.log(ngx.DEBUG, "srv send data: world") 52 | } 53 | } 54 | } 55 | 56 | --- config 57 | location = /test { 58 | content_by_lua_block { 59 | local conn = require("resty.events.protocol").client.new() 60 | 61 | local ok, err = conn:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") 62 | if not ok then 63 | ngx.say("failed to connect: ", err) 64 | return 65 | end 66 | 67 | local bytes, err = conn:send_frame("hello") 68 | if err then 69 | ngx.say("failed to send data: ", err) 70 | end 71 | 72 | local data, err = conn:recv_frame() 73 | if not data or err then 74 | ngx.say("failed to recv data: ", err) 75 | return 76 | end 77 | 78 | ngx.say(data) 79 | ngx.log(ngx.DEBUG, "cli recv len: ", #data) 80 | } 81 | } 82 | --- request 83 | GET /test 84 | --- response_body 85 | world 86 | --- error_log 87 | Upgrade: Kong-Worker-Events/1 88 | Worker ID: 0 89 | srv recv data: hello 90 | srv send data: world 91 | cli recv len: 5 92 | --- no_error_log 93 | [error] 94 | [crit] 95 | [alert] 96 | 97 | 98 | 99 | === TEST 2: client checks unix prefix 100 | --- http_config 101 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 102 | --- config 103 | location = /test { 104 | content_by_lua_block { 105 | local conn = require("resty.events.protocol").client.new() 106 | 107 | ngx.log(ngx.DEBUG, "addr is nginx.sock") 108 | 109 | local ok, err = conn:connect("nginx.sock") 110 | if not ok then 111 | ngx.say("failed to connect: ", err) 112 | return 113 | end 114 | 115 | ngx.say("ok") 116 | } 117 | } 118 | --- request 119 | GET /test 120 | --- response_body 121 | failed to connect: addr must start with "unix:" 122 | --- error_log 123 | addr is nginx.sock 124 | --- no_error_log 125 | [error] 126 | [crit] 127 | [alert] 128 | -------------------------------------------------------------------------------- /t/queue.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | plan tests => repeat_each() * (blocks() * 5); 5 | 6 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 7 | 8 | workers(1); 9 | run_tests(); 10 | 11 | __DATA__ 12 | 13 | === TEST 1: queue works correctly 14 | --- http_config 15 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 16 | --- config 17 | location = /test { 18 | content_by_lua_block { 19 | local queue = require("resty.events.queue").new(10240) 20 | local value, err = queue:pop() 21 | ngx.say(err) 22 | assert(queue:push_front("first")) 23 | ngx.say((queue:pop())) 24 | assert(queue:push("second")) 25 | assert(queue:push_front("first")) 26 | ngx.say((queue:pop())) 27 | ngx.say((queue:pop())) 28 | value, err = queue:pop() 29 | ngx.say(err) 30 | assert(queue:push("first")) 31 | assert(queue:push("second")) 32 | ngx.say((queue:pop())) 33 | ngx.say((queue:pop())) 34 | assert(queue:push_front("third")) 35 | assert(queue:push_front("second")) 36 | assert(queue:push_front("first")) 37 | ngx.say((queue:pop())) 38 | ngx.say((queue:pop())) 39 | ngx.say((queue:pop())) 40 | } 41 | } 42 | --- request 43 | GET /test 44 | --- response_body 45 | timeout 46 | first 47 | first 48 | second 49 | timeout 50 | first 51 | second 52 | first 53 | second 54 | third 55 | --- no_error_log 56 | [crit] 57 | [error] 58 | [warn] 59 | -------------------------------------------------------------------------------- /t/retain-events.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 6); 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | master_on(); 17 | workers(1); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: retains events on connection failure 23 | --- http_config 24 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 25 | init_by_lua_block { 26 | _G.protocol = require("resty.events.protocol") 27 | local opts = { 28 | broker_id = 0, 29 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 30 | } 31 | 32 | local ev = require("resty.events").new(opts) 33 | if not ev then 34 | ngx.log(ngx.ERR, "failed to new events") 35 | end 36 | 37 | _G.ev = ev 38 | } 39 | init_worker_by_lua_block { 40 | local ev = _G.ev 41 | local ok, err = ev:init_worker() 42 | if not ok then 43 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 44 | end 45 | 46 | ev:subscribe("*", "*", function(data, event, source, wid) 47 | ngx.log(ngx.DEBUG, data) 48 | end) 49 | } 50 | server { 51 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 52 | location / { 53 | content_by_lua_block { 54 | _G.ev:run() 55 | } 56 | } 57 | } 58 | --- config 59 | location = /test { 60 | content_by_lua_block { 61 | local ev = _G.ev 62 | ev:publish("all", "source", "event", "eventdata#1") 63 | ngx.sleep(2) 64 | local protocol = _G.protocol 65 | local send_frame = protocol.client.send_frame 66 | protocol.client.send_frame = function() 67 | return nil, "lost the broker connection" 68 | end 69 | ev:publish("all", "source", "event", "eventdata#2") 70 | ngx.sleep(0) 71 | protocol.client.send_frame = send_frame 72 | } 73 | } 74 | --- request 75 | GET /test 76 | --- wait: 10 77 | --- error_log eval 78 | [ 79 | qr/eventdata#1/, 80 | qr/event worker failed to communicate with broker \(failed to send event: lost the broker connection\)/, 81 | qr/eventdata#2/, 82 | ] 83 | --- no_error_log 84 | [crit] 85 | [alert] 86 | -------------------------------------------------------------------------------- /t/slow-client-on-startup.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | plan tests => repeat_each() * (blocks() * 7); 5 | 6 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 7 | 8 | check_accum_error_log(); 9 | master_on(); 10 | workers(3); 11 | run_tests(); 12 | 13 | __DATA__ 14 | 15 | === TEST 1: slow client on node start won't miss the events 16 | --- http_config 17 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 18 | lua_shared_dict dict 1m; 19 | init_by_lua_block { 20 | require("ngx.process").enable_privileged_agent(4096) 21 | local opts = { 22 | broker_id = 2, 23 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 24 | enable_privileged_agent = true, 25 | } 26 | 27 | local ev = require("resty.events").new(opts) 28 | if not ev then 29 | ngx.log(ngx.ERR, "failed to new events") 30 | end 31 | 32 | _G.ev = ev 33 | } 34 | init_worker_by_lua_block { 35 | local ev = _G.ev 36 | local id = ngx.worker.id() or "privileged" 37 | local function initialize() 38 | local ok, err = ev:init_worker() 39 | if not ok then 40 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 41 | end 42 | ev:subscribe("content_by_lua", "first", function() 43 | ngx.log(ngx.INFO, "#", id, " first handler") 44 | end) 45 | end 46 | 47 | if id == 1 then 48 | -- need to disable listening here as we are postponing the initialization 49 | require("resty.events.disable_listening")(ev.opts.listening) 50 | -- slow client 51 | ngx.timer.at(5, initialize) 52 | else 53 | initialize() 54 | end 55 | 56 | _G.ev = ev 57 | } 58 | 59 | server { 60 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 61 | location / { 62 | content_by_lua_block { 63 | _G.ev:run() 64 | } 65 | } 66 | } 67 | --- config 68 | location = /test { 69 | access_by_lua_block { 70 | local ev = _G.ev 71 | ev:publish("all", "content_by_lua", "first") 72 | ngx.sleep(0) 73 | ngx.say("ok") 74 | } 75 | } 76 | --- request 77 | GET /test 78 | --- wait: 10 79 | --- response_body 80 | ok 81 | --- no_error_log 82 | [crit] 83 | --- error_log eval 84 | [ 85 | qr/#privileged first handler/, 86 | qr/#0 first handler/, 87 | qr/#1 first handler/, 88 | qr/#2 first handler/, 89 | ] 90 | --- skip_nginx 91 | 7: < 1.21.4 92 | -------------------------------------------------------------------------------- /t/stream-broadcast.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 14) + 2; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | master_on(); 17 | workers(4); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: posting events and handling events, broadcast 23 | --- main_config 24 | stream { 25 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 26 | init_by_lua_block { 27 | local opts = { 28 | broker_id = 3, 29 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 30 | } 31 | 32 | local ev = require("resty.events").new(opts) 33 | if not ev then 34 | ngx.log(ngx.ERR, "failed to new events") 35 | end 36 | 37 | _G.ev = ev 38 | } 39 | init_worker_by_lua_block { 40 | local ev = _G.ev 41 | local ok, err = ev:init_worker() 42 | if not ok then 43 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 44 | end 45 | 46 | local i = 0 47 | 48 | ev:subscribe("*", "*", function(data, event, source, wid) 49 | i = i + 1 50 | ngx.log(ngx.DEBUG, i, " worker-events: handler event; source=", source, ", event=", event, 51 | ", wid=", wid, ", by=", ngx.worker.id(), ", data=", data) 52 | end) 53 | 54 | _G.ev = ev 55 | } 56 | 57 | server { 58 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 59 | content_by_lua_block { 60 | _G.ev:run() 61 | } 62 | } 63 | server { 64 | listen 1985; 65 | content_by_lua_block { 66 | local ev = _G.ev 67 | 68 | ev:publish("all", "content_by_lua", "request1", "01234567890") 69 | 70 | ngx.say("ok") 71 | } 72 | } 73 | } 74 | --- config 75 | location = /test { 76 | content_by_lua_block { 77 | local sock, err = ngx.socket.tcp() 78 | assert(sock, err) 79 | 80 | local ok, err = sock:connect("127.0.0.1", 1985) 81 | if not ok then 82 | ngx.say("connect to stream server error: ", err) 83 | return 84 | end 85 | 86 | local data, err = sock:receive("*a") 87 | if not data then 88 | sock:close() 89 | ngx.say("receive stream response error: ", err) 90 | return 91 | end 92 | 93 | ngx.print(data) 94 | } 95 | } 96 | --- request 97 | GET /test 98 | --- response_body 99 | ok 100 | --- error_log eval 101 | [ 102 | qr/event published to 4 workers/, 103 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=0, data=01234567890/, 104 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=1, data=01234567890/, 105 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=2, data=01234567890/, 106 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=3, data=01234567890/ 107 | ] 108 | --- no_error_log 109 | [error] 110 | [crit] 111 | [alert] 112 | --- grep_error_log eval: qr/worker-events: handling event; .*/ 113 | --- grep_error_log_out eval 114 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 115 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 116 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 117 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+$/ 118 | 119 | 120 | 121 | === TEST 2: posting events and handling events, local 122 | --- main_config 123 | stream { 124 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 125 | init_by_lua_block { 126 | local opts = { 127 | broker_id = 3, 128 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 129 | } 130 | 131 | local ev = require("resty.events").new(opts) 132 | if not ev then 133 | ngx.log(ngx.ERR, "failed to new events") 134 | end 135 | 136 | _G.ev = ev 137 | } 138 | init_worker_by_lua_block { 139 | local ev = _G.ev 140 | local ok, err = ev:init_worker() 141 | if not ok then 142 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 143 | end 144 | 145 | local i = 0 146 | 147 | ev:subscribe("*", "*", function(data, event, source, wid) 148 | if wid then 149 | i = 3 150 | else 151 | i = i + 1 152 | end 153 | 154 | ngx.log(ngx.DEBUG, i, " worker-events: handler event; source=", source, ", event=", event, 155 | ", wid=", wid, ", by=", ngx.worker.id(), ", data=", data) 156 | end) 157 | 158 | _G.ev = ev 159 | } 160 | 161 | server { 162 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 163 | content_by_lua_block { 164 | _G.ev:run() 165 | } 166 | } 167 | server { 168 | listen 1985; 169 | content_by_lua_block { 170 | local ev = _G.ev 171 | 172 | ev:publish("current", "content_by_lua", "request1", "ABCDEFGHIJK") 173 | ev:publish("current", "content_by_lua", "request2", "LMNOPQRSTUV") 174 | ev:publish("all", "content_by_lua", "request3", "01234567890") 175 | 176 | ngx.say("ok") 177 | } 178 | } 179 | } 180 | --- config 181 | location = /test { 182 | content_by_lua_block { 183 | local sock, err = ngx.socket.tcp() 184 | assert(sock, err) 185 | 186 | local ok, err = sock:connect("127.0.0.1", 1985) 187 | if not ok then 188 | ngx.say("connect to stream server error: ", err) 189 | return 190 | end 191 | 192 | local data, err = sock:receive("*a") 193 | if not data then 194 | sock:close() 195 | ngx.say("receive stream response error: ", err) 196 | return 197 | end 198 | 199 | ngx.print(data) 200 | } 201 | } 202 | --- request 203 | GET /test 204 | --- response_body 205 | ok 206 | --- error_log eval 207 | [ 208 | qr/event published to 4 workers/, 209 | qr/1 worker-events: handler event; source=content_by_lua, event=request1, wid=nil, by=\d+, data=ABCDEFGHIJK/, 210 | qr/2 worker-events: handler event; source=content_by_lua, event=request2, wid=nil, by=\d+, data=LMNOPQRSTUV/, 211 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=0, data=01234567890/, 212 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=1, data=01234567890/, 213 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=2, data=01234567890/, 214 | qr/3 worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, by=3, data=01234567890/ 215 | ] 216 | --- no_error_log 217 | [error] 218 | [crit] 219 | [alert] 220 | --- grep_error_log eval: qr/worker-events: handling event; .*/ 221 | --- grep_error_log_out eval 222 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=nil 223 | worker-events: handling event; source=content_by_lua, event=request2, wid=nil 224 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 225 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 226 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 227 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+$/ 228 | 229 | 230 | 231 | === TEST 3: worker.events 'one' being done, and only once 232 | --- main_config 233 | stream { 234 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 235 | init_by_lua_block { 236 | local opts = { 237 | unique_timeout = 0.04, 238 | --broker_id = 0, 239 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 240 | } 241 | 242 | local ev = require("resty.events").new(opts) 243 | if not ev then 244 | ngx.log(ngx.ERR, "failed to new events") 245 | end 246 | 247 | _G.ev = ev 248 | } 249 | init_worker_by_lua_block { 250 | local ev = _G.ev 251 | local ok, err = ev:init_worker() 252 | if not ok then 253 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 254 | end 255 | 256 | ev:subscribe("*", "*", function(data, event, source, wid) 257 | ngx.log(ngx.DEBUG, "worker-events: handler event; source=", source, ", event=", event, 258 | ", wid=", wid, ", by=", ngx.worker.id(), ", data=", data) 259 | end) 260 | 261 | _G.ev = ev 262 | } 263 | 264 | server { 265 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 266 | content_by_lua_block { 267 | _G.ev:run() 268 | } 269 | } 270 | server { 271 | listen 1985; 272 | content_by_lua_block { 273 | local ev = _G.ev 274 | 275 | ev:publish("all", "content_by_lua", "request1", "01234567890") 276 | 277 | ev:publish("unique_value", "content_by_lua", "request2", "ABCDEFGHIJK") 278 | ev:publish("unique_value", "content_by_lua", "request3", "LMNOPQRSTUV") 279 | 280 | ngx.sleep(0.1) -- wait for unique timeout to expire 281 | 282 | ev:publish("unique_value", "content_by_lua", "request4", "WXYZABCDEFG") 283 | ev:publish("unique_value", "content_by_lua", "request5", "HIJKLMNOPQR") 284 | 285 | ev:publish("all", "content_by_lua", "request6", "STUVWXYZABC") 286 | 287 | ngx.say("ok") 288 | } 289 | } 290 | } 291 | --- config 292 | location = /test { 293 | content_by_lua_block { 294 | local sock, err = ngx.socket.tcp() 295 | assert(sock, err) 296 | 297 | local ok, err = sock:connect("127.0.0.1", 1985) 298 | if not ok then 299 | ngx.say("connect to stream server error: ", err) 300 | return 301 | end 302 | 303 | local data, err = sock:receive("*a") 304 | if not data then 305 | sock:close() 306 | ngx.say("receive stream response error: ", err) 307 | return 308 | end 309 | 310 | ngx.print(data) 311 | } 312 | } 313 | --- request 314 | GET /test 315 | --- response_body 316 | ok 317 | --- error_log eval 318 | [ 319 | qr/event published to 1 workers/, 320 | qr/unique event is duplicate on worker #\d+: unique_value/, 321 | qr/event published to 4 workers/, 322 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=0, data=01234567890/, 323 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=1, data=01234567890/, 324 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=2, data=01234567890/, 325 | qr/worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, by=3, data=01234567890/, 326 | qr/worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, by=\d+, data=ABCDEFGHIJK/, 327 | qr/worker-events: handler event; source=content_by_lua, event=request4, wid=\d+, by=\d+, data=WXYZABCDEFG/, 328 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=0, data=STUVWXYZABC/, 329 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=1, data=STUVWXYZABC/, 330 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=2, data=STUVWXYZABC/, 331 | qr/worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, by=3, data=STUVWXYZABC/ 332 | ] 333 | --- no_error_log 334 | [error] 335 | [crit] 336 | [alert] 337 | LMNOPQRSTUV 338 | HIJKLMNOPQR 339 | -------------------------------------------------------------------------------- /t/stream-events.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | #repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 7) + 1; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: posting events and handling events, broadcast and local 23 | --- main_config 24 | stream { 25 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 26 | init_by_lua_block { 27 | local opts = { 28 | --broker_id = 0, 29 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 30 | } 31 | 32 | local ev = require("resty.events").new(opts) 33 | if not ev then 34 | ngx.log(ngx.ERR, "failed to new events") 35 | end 36 | 37 | _G.ev = ev 38 | } 39 | init_worker_by_lua_block { 40 | local ev = _G.ev 41 | local ok, err = ev:init_worker() 42 | if not ok then 43 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 44 | end 45 | 46 | ev:subscribe("*", "*", function(data, event, source, wid) 47 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 48 | ", data=", data) 49 | end) 50 | 51 | _G.ev = ev 52 | } 53 | 54 | server { 55 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 56 | content_by_lua_block { 57 | _G.ev:run() 58 | } 59 | } 60 | server { 61 | listen 1985; 62 | content_by_lua_block { 63 | local ev = _G.ev 64 | 65 | ev:publish("all", "content_by_lua", "request1", "01234567890") 66 | ev:publish("current", "content_by_lua", "request2", "01234567890") 67 | ev:publish("all", "content_by_lua", "request3", "01234567890") 68 | 69 | ngx.say("ok") 70 | } 71 | } 72 | } 73 | --- config 74 | location = /test { 75 | content_by_lua_block { 76 | local sock, err = ngx.socket.tcp() 77 | assert(sock, err) 78 | 79 | local ok, err = sock:connect("127.0.0.1", 1985) 80 | if not ok then 81 | ngx.say("connect to stream server error: ", err) 82 | return 83 | end 84 | 85 | local data, err = sock:receive("*a") 86 | if not data then 87 | sock:close() 88 | ngx.say("receive stream response error: ", err) 89 | return 90 | end 91 | 92 | ngx.print(data) 93 | } 94 | } 95 | --- request 96 | GET /test 97 | --- response_body 98 | ok 99 | --- error_log 100 | event published to 1 workers 101 | --- no_error_log 102 | [error] 103 | [crit] 104 | [alert] 105 | --- grep_error_log eval: qr/worker-events: .*/ 106 | --- grep_error_log_out eval 107 | qr/^worker-events: handling event; source=content_by_lua, event=request2, wid=nil 108 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 109 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 110 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 111 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 112 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=01234567890$/ 113 | 114 | 115 | 116 | === TEST 2: worker.events handling remote events 117 | --- main_config 118 | stream { 119 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 120 | init_by_lua_block { 121 | local opts = { 122 | --broker_id = 0, 123 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 124 | } 125 | 126 | local ev = require("resty.events").new(opts) 127 | if not ev then 128 | ngx.log(ngx.ERR, "failed to new events") 129 | end 130 | 131 | _G.ev = ev 132 | } 133 | init_worker_by_lua_block { 134 | local ev = _G.ev 135 | local ok, err = ev:init_worker() 136 | if not ok then 137 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 138 | end 139 | 140 | ev:subscribe("*", "*", function(data, event, source, wid) 141 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 142 | ", data=", tostring(data)) 143 | end) 144 | 145 | _G.ev = ev 146 | } 147 | 148 | server { 149 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 150 | content_by_lua_block { 151 | _G.ev:run() 152 | } 153 | } 154 | 155 | server { 156 | listen 1985; 157 | content_by_lua_block { 158 | local ev = _G.ev 159 | 160 | ev:publish("all", "content_by_lua", "request1", "01234567890") 161 | ev:publish("current", "content_by_lua", "request2", "01234567890") 162 | ev:publish("all", "content_by_lua", "request3", "01234567890") 163 | 164 | ngx.say("ok") 165 | } 166 | } 167 | } 168 | --- config 169 | location = /test { 170 | content_by_lua_block { 171 | local sock, err = ngx.socket.tcp() 172 | assert(sock, err) 173 | 174 | local ok, err = sock:connect("127.0.0.1", 1985) 175 | if not ok then 176 | ngx.say("connect to stream server error: ", err) 177 | return 178 | end 179 | 180 | local data, err = sock:receive("*a") 181 | if not data then 182 | sock:close() 183 | ngx.say("receive stream response error: ", err) 184 | return 185 | end 186 | 187 | ngx.print(data) 188 | } 189 | } 190 | --- request 191 | GET /test 192 | --- response_body 193 | ok 194 | --- error_log 195 | event published to 1 workers 196 | --- no_error_log 197 | [error] 198 | [crit] 199 | [alert] 200 | --- grep_error_log eval: qr/worker-events: .*/ 201 | --- grep_error_log_out eval 202 | qr/^worker-events: handling event; source=content_by_lua, event=request2, wid=nil 203 | worker-events: handler event; source=content_by_lua, event=request2, wid=nil, data=01234567890 204 | worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 205 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 206 | worker-events: handling event; source=content_by_lua, event=request3, wid=\d+ 207 | worker-events: handler event; source=content_by_lua, event=request3, wid=\d+, data=01234567890$/ 208 | 209 | 210 | 211 | === TEST 3: worker.events 'one' being done, and only once 212 | --- main_config 213 | stream { 214 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?/init.lua;lualib/?.lua;;"; 215 | init_by_lua_block { 216 | local opts = { 217 | unique_timeout = 0.04, 218 | --broker_id = 0, 219 | listening = "unix:$TEST_NGINX_HTML_DIR/nginx.sock", 220 | } 221 | 222 | local ev = require("resty.events").new(opts) 223 | if not ev then 224 | ngx.log(ngx.ERR, "failed to new events") 225 | end 226 | 227 | _G.ev = ev 228 | } 229 | init_worker_by_lua_block { 230 | local ev = _G.ev 231 | local ok, err = ev:init_worker() 232 | if not ok then 233 | ngx.log(ngx.ERR, "failed to init_worker events: ", err) 234 | end 235 | 236 | ev:subscribe("*", "*", function(data, event, source, wid) 237 | ngx.log(ngx.DEBUG, "worker-events: handler event; ", "source=",source,", event=",event, ", wid=", wid, 238 | ", data=", tostring(data)) 239 | end) 240 | 241 | _G.ev = ev 242 | } 243 | 244 | server { 245 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 246 | content_by_lua_block { 247 | _G.ev:run() 248 | } 249 | } 250 | server { 251 | listen 1985; 252 | content_by_lua_block { 253 | local ev = _G.ev 254 | 255 | ev:publish("all", "content_by_lua", "request1", "01234567890") 256 | ev:publish("unique_value", "content_by_lua", "request2", "01234567890") 257 | ev:publish("unique_value", "content_by_lua", "request3", "01234567890") 258 | 259 | ngx.sleep(0.1) -- wait for unique timeout to expire 260 | 261 | ev:publish("unique_value", "content_by_lua", "request4", "01234567890") 262 | ev:publish("unique_value", "content_by_lua", "request5", "01234567890") 263 | ev:publish("all", "content_by_lua", "request6", "01234567890") 264 | 265 | ngx.say("ok") 266 | } 267 | } 268 | } 269 | --- config 270 | location = /test { 271 | content_by_lua_block { 272 | local sock, err = ngx.socket.tcp() 273 | assert(sock, err) 274 | 275 | local ok, err = sock:connect("127.0.0.1", 1985) 276 | if not ok then 277 | ngx.say("connect to stream server error: ", err) 278 | return 279 | end 280 | 281 | local data, err = sock:receive("*a") 282 | if not data then 283 | sock:close() 284 | ngx.say("receive stream response error: ", err) 285 | return 286 | end 287 | 288 | ngx.print(data) 289 | } 290 | } 291 | --- request 292 | GET /test 293 | --- response_body 294 | ok 295 | --- error_log 296 | event published to 1 workers 297 | unique event is duplicate on worker #0: unique_value 298 | --- no_error_log 299 | [error] 300 | [crit] 301 | [alert] 302 | --- grep_error_log eval: qr/worker-events: .*/ 303 | --- grep_error_log_out eval 304 | qr/^worker-events: handling event; source=content_by_lua, event=request1, wid=\d+ 305 | worker-events: handler event; source=content_by_lua, event=request1, wid=\d+, data=01234567890 306 | worker-events: handling event; source=content_by_lua, event=request2, wid=\d+ 307 | worker-events: handler event; source=content_by_lua, event=request2, wid=\d+, data=01234567890 308 | worker-events: handling event; source=content_by_lua, event=request4, wid=\d+ 309 | worker-events: handler event; source=content_by_lua, event=request4, wid=\d+, data=01234567890 310 | worker-events: handling event; source=content_by_lua, event=request6, wid=\d+ 311 | worker-events: handler event; source=content_by_lua, event=request6, wid=\d+, data=01234567890$/ 312 | -------------------------------------------------------------------------------- /t/stream-protocol-privileged.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 10); 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | check_accum_error_log(); 19 | run_tests(); 20 | 21 | __DATA__ 22 | 23 | === TEST 1: sanity: send_frame, recv_frame (with privileged agent) 24 | --- main_config 25 | stream { 26 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 27 | init_by_lua_block { 28 | local process = require "ngx.process" 29 | process.enable_privileged_agent(100) 30 | } 31 | init_worker_by_lua_block { 32 | local process = require "ngx.process" 33 | if process.type() ~= "privileged agent" then 34 | return 35 | end 36 | ngx.timer.at(0, function() 37 | local conn = require("resty.events.protocol").client.new() 38 | 39 | local ok, err = conn:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") 40 | if not ok then 41 | ngx.log(ngx.ERR, "failed to connect: ", err) 42 | return 43 | end 44 | 45 | local bytes, err = conn:send_frame("hello") 46 | if err then 47 | ngx.log(ngx.ERR, "failed to send data: ", err) 48 | end 49 | 50 | local data, err = conn:recv_frame() 51 | if not data or err then 52 | ngx.log(ngx.ERR, "failed to recv data: ", err) 53 | return 54 | end 55 | 56 | ngx.log(ngx.DEBUG, data) 57 | ngx.log(ngx.DEBUG, "cli recv len: ", #data) 58 | end) 59 | } 60 | server { 61 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 62 | content_by_lua_block { 63 | local conn, err = require("resty.events.protocol").server.new() 64 | if not conn then 65 | ngx.say("failed to init socket: ", err) 66 | return 67 | end 68 | 69 | ngx.log(ngx.DEBUG, "Worker ID: ", conn.info.id) 70 | 71 | local data, err = conn:recv_frame() 72 | if not data or err then 73 | ngx.say("failed to recv data: ", err) 74 | return 75 | end 76 | ngx.log(ngx.DEBUG, "srv recv data: ", data) 77 | 78 | local bytes, err = conn:send_frame("world") 79 | if err then 80 | ngx.say("failed to send data: ", err) 81 | return 82 | end 83 | 84 | ngx.log(ngx.DEBUG, "srv send data: world") 85 | } 86 | } 87 | } 88 | --- config 89 | location = /test { 90 | content_by_lua_block { 91 | ngx.say("world") 92 | } 93 | } 94 | --- request 95 | GET /test 96 | --- response_body 97 | world 98 | --- error_log 99 | Worker ID: -1 100 | srv recv data: hello 101 | srv send data: world 102 | world 103 | cli recv len: 5 104 | --- no_error_log 105 | [error] 106 | [crit] 107 | [alert] 108 | -------------------------------------------------------------------------------- /t/stream-protocol.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et fdm=marker: 2 | use Test::Nginx::Socket::Lua; 3 | 4 | #worker_connections(1014); 5 | #master_process_enabled(1); 6 | #log_level('warn'); 7 | 8 | repeat_each(2); 9 | 10 | plan tests => repeat_each() * (blocks() * 9) - 4; 11 | 12 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 13 | 14 | #no_diff(); 15 | #no_long_string(); 16 | #master_on(); 17 | #workers(2); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: sanity: send_frame, recv_frame 23 | --- main_config 24 | stream { 25 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 26 | server { 27 | listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; 28 | content_by_lua_block { 29 | local conn, err = require("resty.events.protocol").server.new() 30 | if not conn then 31 | ngx.say("failed to init socket: ", err) 32 | return 33 | end 34 | 35 | ngx.log(ngx.DEBUG, "Worker ID: ", conn.info.id) 36 | ngx.log(ngx.DEBUG, "stream connect ok") 37 | 38 | local data, err = conn:recv_frame() 39 | if not data or err then 40 | ngx.say("failed to recv data: ", err) 41 | return 42 | end 43 | ngx.log(ngx.DEBUG, "srv recv data: ", data) 44 | 45 | local bytes, err = conn:send_frame("world") 46 | if err then 47 | ngx.say("failed to send data: ", err) 48 | return 49 | end 50 | 51 | ngx.log(ngx.DEBUG, "srv send data: world") 52 | } 53 | } 54 | 55 | server { 56 | listen 1985; 57 | content_by_lua_block { 58 | local conn = require("resty.events.protocol").client.new() 59 | 60 | local ok, err = conn:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") 61 | if not ok then 62 | ngx.say("failed to connect: ", err) 63 | return 64 | end 65 | 66 | local bytes, err = conn:send_frame("hello") 67 | if err then 68 | ngx.say("failed to send data: ", err) 69 | end 70 | 71 | local data, err = conn:recv_frame() 72 | if not data or err then 73 | ngx.say("failed to recv data: ", err) 74 | return 75 | end 76 | 77 | ngx.say(data) 78 | ngx.log(ngx.DEBUG, "cli recv len: ", #data) 79 | } 80 | } 81 | } 82 | --- config 83 | location = /test { 84 | content_by_lua_block { 85 | local sock, err = ngx.socket.tcp() 86 | assert(sock, err) 87 | 88 | local ok, err = sock:connect("127.0.0.1", 1985) 89 | if not ok then 90 | ngx.say("connect to stream server error: ", err) 91 | return 92 | end 93 | 94 | local data, err = sock:receive("*a") 95 | if not data then 96 | sock:close() 97 | ngx.say("receive stream response error: ", err) 98 | return 99 | end 100 | 101 | ngx.print(data) 102 | } 103 | } 104 | --- timeout: 1 105 | --- request 106 | GET /test 107 | --- response_body 108 | world 109 | --- error_log 110 | Worker ID: 0 111 | stream connect ok 112 | srv recv data: hello 113 | srv send data: world 114 | cli recv len: 5 115 | --- no_error_log 116 | [error] 117 | [crit] 118 | [alert] 119 | 120 | 121 | 122 | === TEST 2: client checks unix prefix 123 | --- main_config 124 | stream { 125 | lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; 126 | server { 127 | listen 1985; 128 | content_by_lua_block { 129 | local conn = require("resty.events.protocol").client.new() 130 | 131 | ngx.log(ngx.DEBUG, "addr is nginx.sock") 132 | 133 | local ok, err = conn:connect("nginx.sock") 134 | if not ok then 135 | ngx.say("failed to connect: ", err) 136 | return 137 | end 138 | 139 | ngx.say("ok") 140 | } 141 | } 142 | } 143 | --- config 144 | location = /test { 145 | content_by_lua_block { 146 | local sock, err = ngx.socket.tcp() 147 | assert(sock, err) 148 | 149 | local ok, err = sock:connect("127.0.0.1", 1985) 150 | if not ok then 151 | ngx.say("connect to stream server error: ", err) 152 | return 153 | end 154 | 155 | local data, err = sock:receive("*a") 156 | if not data then 157 | sock:close() 158 | ngx.say("receive stream response error: ", err) 159 | return 160 | end 161 | 162 | ngx.print(data) 163 | } 164 | } 165 | --- request 166 | GET /test 167 | --- response_body 168 | failed to connect: addr must start with "unix:" 169 | --- error_log 170 | addr is nginx.sock 171 | --- no_error_log 172 | [error] 173 | [crit] 174 | [alert] 175 | --------------------------------------------------------------------------------