├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── lib └── tablepool.lua └── t ├── max-size.t └── sanity.t /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | *.swo 4 | *.bak 5 | t/servroot/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | branches: 5 | only: 6 | - "master" 7 | 8 | os: linux 9 | 10 | language: c 11 | 12 | compiler: 13 | - gcc 14 | 15 | addons: 16 | apt: 17 | packages: 18 | - axel 19 | - cpanminus 20 | - libluajit-5.1-dev 21 | 22 | cache: 23 | apt: true 24 | directories: 25 | - download-cache 26 | 27 | env: 28 | global: 29 | - JOBS=2 30 | - LUAJIT_PREFIX=/opt/luajit21 31 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 32 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 33 | - LUA_INCLUDE_DIR=$LUAJIT_INC 34 | - LUA_CMODULE_DIR=/lib 35 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 36 | - TEST_NGINX_SLEEP=0.006 37 | matrix: 38 | - NGINX_VERSION=1.27.1 39 | 40 | install: 41 | - sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 42 | - git clone https://github.com/openresty/openresty.git ../openresty 43 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 44 | - git clone https://github.com/openresty/nginx-devel-utils.git 45 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 46 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 47 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 48 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 49 | 50 | script: 51 | # compile luajit 52 | - cd luajit2/ 53 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC 54 | XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 55 | || (cat build.log && exit 1) 56 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || 57 | (cat build.log && exit 1) 58 | - cd .. 59 | - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 60 | - ngx-build $NGINX_VERSION --without-pcre2 --with-debug --with-cc-opt="-DDEBUG_MALLOC" 61 | --with-ipv6 --add-module=../lua-nginx-module > build.log 2>&1 || 62 | (cat build.log && exit 1) 63 | - prove -I. -r t 64 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty 2 | 3 | #LUA_VERSION := 5.1 4 | PREFIX ?= /usr/local 5 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 6 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 7 | INSTALL ?= install 8 | 9 | test ?= t 10 | 11 | .PHONY: all test install 12 | 13 | all: ; 14 | 15 | install: all 16 | $(INSTALL) lib/*.lua $(DESTDIR)$(LUA_LIB_DIR)/ 17 | 18 | test: all 19 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r $(test) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-tablepool - Lua table recycling pools for LuaJIT 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Synopsis](#synopsis) 11 | * [Description](#description) 12 | * [Methods](#methods) 13 | * [fetch](#fetch) 14 | * [release](#release) 15 | * [Prerequisites](#prerequisites) 16 | * [Community](#community) 17 | * [English Mailing List](#english-mailing-list) 18 | * [Chinese Mailing List](#chinese-mailing-list) 19 | * [Bugs and Patches](#bugs-and-patches) 20 | * [Author](#author) 21 | * [Copyright and License](#copyright-and-license) 22 | 23 | Synopsis 24 | ======== 25 | 26 | ```nginx 27 | local tablepool = require "tablepool" 28 | 29 | local pool_name = "some_tag" 30 | 31 | local my_tb = tablepool.fetch(pool_name, 0, 10) 32 | 33 | -- using my_tb for some purposes... 34 | 35 | tablepool.release(pool_name, my_tb) 36 | ``` 37 | 38 | Description 39 | =========== 40 | 41 | This Lua library implements a pool mechanism to recycle small Lua tables. This can 42 | avoid frequent allocations and de-allocation of many small Lua tables for temporary use. 43 | Recycling tables can also reduce the overhead of garbage collection in general. 44 | 45 | The Lua table pools are shared across all the requests handled by the current NGINX 46 | worker process unless `lua_code_cache` is turned off in your `nginx.conf`. 47 | 48 | Methods 49 | ======= 50 | 51 | To load the `tablepool` module, 52 | 53 | ``` 54 | local tablepool = require "tablepool" 55 | ``` 56 | 57 | [Back to TOC](#table-of-contents) 58 | 59 | fetch 60 | ----- 61 | `syntax: tb = tablepool.fetch(pool_name, narr, nrec)` 62 | 63 | Fetches a (free) Lua table from the table pool of the specified name `pool_name`. 64 | If the pool 65 | does not exist or the pool is empty, simply create a Lua table whose array part has 66 | `narr` elements and whose hash table part has `nrec` elements. 67 | 68 | [Back to TOC](#table-of-contents) 69 | 70 | release 71 | ------- 72 | `syntax: cache, err = tablepool.release(pool_name, tb, [no_clear])` 73 | 74 | Releases the already used Lua table, `tb`, into the table pool named `pool_name`. If the specified 75 | table pool does not exist, create it right away. 76 | 77 | The caller must *not* continue using the released Lua table, `tb`, after this call. Otherwise 78 | random data corruption is expected. 79 | 80 | The optional `no_clear` parameter specifies whether to clear the contents in the Lua table 81 | `tb` before putting it into the pool. Defaults to `false`, that is, always clearing the Lua table. 82 | If you always initialize all the elements in the Lua table and always use the exactly same number of elements in the Lua table, then you can set this argument to `true` to 83 | avoid the overhead of explicit table clearing. 84 | 85 | According to the current implementation, for maximum 200 Lua tables can be cached in 86 | an individual pool. We may make this configurable in the future. If the specified table 87 | pool already exceeds its size limit, then the `tb` table is subject to garbage collection. This behavior is to avoid potential memory leak due to unbalanced `fetch` and `release` method calls. 88 | 89 | [Back to TOC](#table-of-contents) 90 | 91 | Caveats 92 | ======= 93 | 94 | Large tables requiring clearing 95 | ------------------------------- 96 | 97 | If you always need to clear out the recycled Lua tables, then you should avoid recycling 98 | relatively large Lua tables (like those of hundreds or even thousands of elements in a single table). 99 | This is because clearing large Lua tables may offset the benefit of recycling tables. 100 | 101 | It is recommended to always watch for the `lj_tab_clear` function frames in the C-land on-CPU 102 | flame graphs of your busy nginx worker processes. 103 | 104 | Prerequisites 105 | ============= 106 | 107 | This Lua library depends on the new `table.new` and `table.clear` API functions first introduced since LuaJIT 2.1. 108 | Older versions of LuaJIT or the standard Lua interpreters will *not* work at all. 109 | 110 | [Back to TOC](#table-of-contents) 111 | 112 | Community 113 | ========= 114 | 115 | [Back to TOC](#table-of-contents) 116 | 117 | English Mailing List 118 | -------------------- 119 | 120 | The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. 121 | 122 | [Back to TOC](#table-of-contents) 123 | 124 | Chinese Mailing List 125 | -------------------- 126 | 127 | The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. 128 | 129 | [Back to TOC](#table-of-contents) 130 | 131 | Bugs and Patches 132 | ================ 133 | 134 | Please report bugs or submit patches by 135 | 136 | 1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-lrucache/issues), 137 | 1. or posting to the [OpenResty community](#community). 138 | 139 | [Back to TOC](#table-of-contents) 140 | 141 | Author 142 | ====== 143 | 144 | Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. 145 | 146 | [Back to TOC](#table-of-contents) 147 | 148 | Copyright and License 149 | ===================== 150 | 151 | This module is licensed under the BSD license. 152 | 153 | Copyright (C) 2016-2021, by Yichun "agentzh" Zhang, OpenResty Inc. 154 | 155 | All rights reserved. 156 | 157 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 158 | 159 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 160 | 161 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 162 | 163 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 164 | 165 | [Back to TOC](#table-of-contents) 166 | -------------------------------------------------------------------------------- /lib/tablepool.lua: -------------------------------------------------------------------------------- 1 | local newtab = require "table.new" 2 | local cleartab = require "table.clear" 3 | local setmetatable = setmetatable 4 | 5 | 6 | local _M = newtab(0, 2) 7 | local max_pool_size = 200 8 | local pools = newtab(0, 4) 9 | 10 | 11 | function _M.fetch(tag, narr, nrec) 12 | local pool = pools[tag] 13 | if not pool then 14 | pool = newtab(4, 1) 15 | pools[tag] = pool 16 | pool.c = 0 17 | pool[0] = 0 18 | 19 | else 20 | local len = pool[0] 21 | if len > 0 then 22 | local obj = pool[len] 23 | pool[len] = nil 24 | pool[0] = len - 1 25 | -- ngx.log(ngx.ERR, "HIT") 26 | return obj 27 | end 28 | end 29 | 30 | return newtab(narr, nrec) 31 | end 32 | 33 | 34 | function _M.release(tag, obj, noclear) 35 | if not obj then 36 | error("object empty", 2) 37 | end 38 | 39 | local pool = pools[tag] 40 | if not pool then 41 | pool = newtab(4, 1) 42 | pools[tag] = pool 43 | pool.c = 0 44 | pool[0] = 0 45 | end 46 | 47 | do 48 | local cnt = pool.c + 1 49 | if cnt >= 20000 then 50 | pool = newtab(4, 1) 51 | pools[tag] = pool 52 | pool.c = 0 53 | pool[0] = 0 54 | return 55 | end 56 | pool.c = cnt 57 | end 58 | 59 | local len = pool[0] + 1 60 | if len > max_pool_size then 61 | -- discard it simply 62 | return 63 | end 64 | 65 | if not noclear then 66 | setmetatable(obj, nil) 67 | cleartab(obj) 68 | end 69 | 70 | pool[len] = obj 71 | pool[0] = len 72 | end 73 | 74 | 75 | return _M 76 | 77 | -- vi: ft=lua ts=4 sw=4 et 78 | -------------------------------------------------------------------------------- /t/max-size.t: -------------------------------------------------------------------------------- 1 | # vi:ft= 2 | 3 | use Test::Nginx::Socket::Lua; 4 | 5 | repeat_each(2); 6 | #no_long_string(); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = `pwd`; 11 | chomp $pwd; 12 | 13 | our $HttpConfig = <<_EOC_; 14 | lua_package_path '$pwd/lib/?.lua;lib/?.lua;;'; 15 | _EOC_ 16 | 17 | #log_level 'warn'; 18 | 19 | run_tests(); 20 | 21 | __DATA__ 22 | 23 | === TEST 1: max pool size is 200 24 | --- http_config eval: $::HttpConfig 25 | --- config 26 | location /t { 27 | content_by_lua_block { 28 | local tablepool = require "tablepool" 29 | 30 | local arr = {} 31 | for i = 1, 201 do 32 | local t = tablepool.fetch("tag", 0, 1) 33 | t.a = "foo" 34 | 35 | arr[i] = t 36 | end 37 | 38 | for i = 1, #arr do 39 | tablepool.release("tag", arr[i], true) 40 | end 41 | 42 | for i = 1, 2 do 43 | local t = tablepool.fetch("tag", 0, 10) 44 | ngx.say("old value: ", t.a) 45 | end 46 | } 47 | } 48 | --- request 49 | GET /t 50 | --- response_body 51 | old value: foo 52 | old value: foo 53 | --- no_error_log 54 | [error] 55 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vi:ft= 2 | 3 | use Test::Nginx::Socket::Lua; 4 | 5 | repeat_each(2); 6 | #no_long_string(); 7 | 8 | plan tests => repeat_each() * (3 * blocks()); 9 | 10 | my $pwd = `pwd`; 11 | chomp $pwd; 12 | 13 | our $HttpConfig = <<_EOC_; 14 | lua_package_path '$pwd/lib/?.lua;lib/?.lua;;'; 15 | _EOC_ 16 | 17 | #log_level 'warn'; 18 | 19 | run_tests(); 20 | 21 | __DATA__ 22 | 23 | === TEST 1: sanity 24 | --- http_config eval: $::HttpConfig 25 | --- config 26 | location /t { 27 | content_by_lua_block { 28 | local tablepool = require "tablepool" 29 | 30 | local old_tb = tablepool.fetch("tag", 0, 10) 31 | if not old_tb then 32 | ngx.say("failed to fetch table") 33 | return 34 | end 35 | 36 | old_tb.a = "test" 37 | tablepool.release("tag", old_tb) 38 | 39 | local new_tb = tablepool.fetch("tag", 0, 10) 40 | ngx.say("equal: ", new_tb == old_tb, " old value:", new_tb.a) 41 | } 42 | } 43 | --- request 44 | GET /t 45 | --- response_body 46 | equal: true old value:nil 47 | --- no_error_log 48 | [error] 49 | 50 | 51 | 52 | === TEST 2: release without clear 53 | --- http_config eval: $::HttpConfig 54 | --- config 55 | location /t { 56 | content_by_lua_block { 57 | local tablepool = require "tablepool" 58 | 59 | local old_tb = tablepool.fetch("tag", 3, 10) 60 | if not old_tb then 61 | ngx.say("failed to fetch table") 62 | return 63 | end 64 | 65 | old_tb.a = "test" 66 | tablepool.release("tag", old_tb, true) 67 | 68 | new_tb = tablepool.fetch("tag", 3, 10) 69 | ngx.say("equal: ", new_tb == old_tb, " old value:", new_tb.a) 70 | } 71 | } 72 | --- request 73 | GET /t 74 | --- response_body 75 | equal: true old value:test 76 | --- no_error_log 77 | [error] 78 | 79 | 80 | 81 | === TEST 3: release without clear 82 | --- http_config eval: $::HttpConfig 83 | --- config 84 | location /t { 85 | content_by_lua_block { 86 | local tablepool = require "tablepool" 87 | 88 | tablepool.release("tag", nil) 89 | } 90 | } 91 | --- request 92 | GET /t 93 | --- response_body_like: 500 Internal Server Error 94 | --- error_code: 500 95 | --- error_log eval 96 | qr/content_by_lua\(nginx.conf:\d+\):4: object empty/ 97 | 98 | 99 | 100 | === TEST 4: clear metatable 101 | --- http_config eval: $::HttpConfig 102 | --- config 103 | location /t { 104 | content_by_lua_block { 105 | local tablepool = require "tablepool" 106 | 107 | local old_tb = tablepool.fetch("tag", 0, 10) 108 | if not old_tb then 109 | ngx.say("failed to fetch table") 110 | return 111 | end 112 | 113 | local meta = { foo = 1 } 114 | meta.__index = meta 115 | 116 | setmetatable(old_tb, meta) 117 | 118 | tablepool.release("tag", old_tb) 119 | 120 | local new_tb = tablepool.fetch("tag", 0, 10) 121 | ngx.say("equal: ", new_tb == old_tb, " old value:", new_tb.foo) 122 | } 123 | } 124 | --- request 125 | GET /t 126 | --- response_body 127 | equal: true old value:nil 128 | --- no_error_log 129 | [error] 130 | --------------------------------------------------------------------------------