This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new d2d826e  fix(log-rotate): avoid creating multiple timers (#2596)
d2d826e is described below

commit d2d826e6ab39980a757bdeb48b76960765509c7c
Author: 罗泽轩 <[email protected]>
AuthorDate: Mon Nov 9 09:09:33 2020 +0800

    fix(log-rotate): avoid creating multiple timers (#2596)
---
 apisix/init.lua                   |  2 +
 apisix/plugin.lua                 | 24 +++++++++-
 apisix/plugins/example-plugin.lua | 10 +++++
 apisix/plugins/log-rotate.lua     | 21 +++------
 apisix/timers.lua                 | 88 ++++++++++++++++++++++++++++++++++++
 t/plugin/log-rotate.t             | 94 +++++++++++++++++++++++++++++++++++----
 6 files changed, 214 insertions(+), 25 deletions(-)

diff --git a/apisix/init.lua b/apisix/init.lua
index b2a795f..05a708f 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -98,6 +98,8 @@ function _M.http_init_worker()
     load_balancer = require("apisix.balancer").run
     require("apisix.admin.init").init_worker()
 
+    require("apisix.timers").init_worker()
+
     router.http_init_worker()
     require("apisix.http.service").init_worker()
     plugin.init_worker()
diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index 5936288..ae1aedd 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -56,12 +56,26 @@ local function sort_plugin(l, r)
 end
 
 
-local function load_plugin(name, plugins_list, is_stream_plugin)
+local function unload_plugin(name, is_stream_plugin)
     local pkg_name = "apisix.plugins." .. name
     if is_stream_plugin then
         pkg_name = "apisix.stream.plugins." .. name
     end
+
+    local old_plugin = pkg_loaded[pkg_name]
+    if old_plugin and type(old_plugin.destory) == "function" then
+        old_plugin.destory()
+    end
+
     pkg_loaded[pkg_name] = nil
+end
+
+
+local function load_plugin(name, plugins_list, is_stream_plugin)
+    local pkg_name = "apisix.plugins." .. name
+    if is_stream_plugin then
+        pkg_name = "apisix.stream.plugins." .. name
+    end
 
     local ok, plugin = pcall(require, pkg_name)
     if not ok then
@@ -115,6 +129,10 @@ local function load(plugin_names)
 
     core.log.warn("new plugins: ", core.json.delay_encode(processed))
 
+    for name in pairs(local_plugins_hash) do
+        unload_plugin(name)
+    end
+
     core.table.clear(local_plugins)
     core.table.clear(local_plugins_hash)
 
@@ -159,6 +177,10 @@ local function load_stream(plugin_names)
 
     core.log.warn("new plugins: ", core.json.delay_encode(processed))
 
+    for name in pairs(stream_local_plugins_hash) do
+        unload_plugin(name, true)
+    end
+
     core.table.clear(stream_local_plugins)
     core.table.clear(stream_local_plugins_hash)
 
diff --git a/apisix/plugins/example-plugin.lua 
b/apisix/plugins/example-plugin.lua
index 9944929..a590f43 100644
--- a/apisix/plugins/example-plugin.lua
+++ b/apisix/plugins/example-plugin.lua
@@ -61,6 +61,16 @@ function _M.check_schema(conf)
 end
 
 
+function _M.init()
+    -- call this function when plugin is loaded
+end
+
+
+function _M.destory()
+    -- call this function when plugin is unloaded
+end
+
+
 function _M.rewrite(conf, ctx)
     core.log.warn("plugin rewrite phase, conf: ", core.json.encode(conf))
     -- core.log.warn(" ctx: ", core.json.encode(ctx, true))
diff --git a/apisix/plugins/log-rotate.lua b/apisix/plugins/log-rotate.lua
index 6287b55..de2266c 100644
--- a/apisix/plugins/log-rotate.lua
+++ b/apisix/plugins/log-rotate.lua
@@ -16,6 +16,7 @@
 --
 
 local core = require("apisix.core")
+local timers = require("apisix.timers")
 local process = require("ngx.process")
 local signal = require("resty.signal")
 local ngx = ngx
@@ -27,7 +28,6 @@ local string = string
 local local_conf
 
 
-local timer
 local plugin_name = "log-rotate"
 local INTERVAL = 60 * 60    -- rotate interval (unit: second)
 local MAX_KEPT = 24 * 7     -- max number of log files will be kept
@@ -205,23 +205,12 @@ end
 
 
 function _M.init()
-    core.log.info("enter log-rotate plugin, process type: ", process.type())
-
-    if process.type() ~= "privileged agent" and process.type() ~= "single" then
-        return
-    end
+    timers.register_timer("plugin#log-rotate", rotate, true)
+end
 
-    if timer then
-        return
-    end
 
-    local err
-    timer, err = core.timer.new("logrotate", rotate, {check_interval = 0.5})
-    if not timer then
-        core.log.error("failed to create timer log rotate: ", err)
-    else
-        core.log.notice("succeed to create timer: log rotate")
-    end
+function _M.destory()
+    timers.unregister_timer("plugin#log-rotate", true)
 end
 
 
diff --git a/apisix/timers.lua b/apisix/timers.lua
new file mode 100644
index 0000000..51b0271
--- /dev/null
+++ b/apisix/timers.lua
@@ -0,0 +1,88 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core = require("apisix.core")
+local process = require("ngx.process")
+local pairs = pairs
+local unpack = unpack
+local thread_spawn = ngx.thread.spawn
+local thread_wait = ngx.thread.wait
+
+
+local timers = {}
+
+
+local _M = {}
+
+
+local function background_timer()
+    local threads = {}
+    for name, timer in pairs(timers) do
+        core.log.info("run timer[", name, "]")
+
+        local th, err = thread_spawn(timer)
+        if not th then
+            core.log.error("failed to spawn thread for timer [", name, "]: ", 
err)
+            goto continue
+        end
+
+        core.table.insert(threads, th)
+
+::continue::
+    end
+
+    local ok, err = thread_wait(unpack(threads))
+    if not ok then
+        core.log.error("failed to wait threads: ", err)
+    end
+end
+
+
+local function is_privileged()
+    return process.type() == "privileged agent" or process.type() == "single"
+end
+
+
+function _M.init_worker()
+    local timer, err = core.timer.new("background", background_timer, 
{check_interval = 0.5})
+    if not timer then
+        core.log.error("failed to create background timer: ", err)
+        return
+    end
+
+    core.log.notice("succeed to create background timer")
+end
+
+
+function _M.register_timer(name, f, privileged)
+    if privileged and not is_privileged() then
+        return
+    end
+
+    timers[name] = f
+end
+
+
+function _M.unregister_timer(name, privileged)
+    if privileged and not is_privileged() then
+        return
+    end
+
+    timers[name] = nil
+end
+
+
+return _M
diff --git a/t/plugin/log-rotate.t b/t/plugin/log-rotate.t
index 3cf08cb..29b66ef 100644
--- a/t/plugin/log-rotate.t
+++ b/t/plugin/log-rotate.t
@@ -28,6 +28,7 @@ add_block_preprocessor(sub {
     my $user_yaml_config = <<_EOC_;
 apisix:
   node_listen: 1984
+  admin_key: null
 
 plugins:                          # plugin list
   - log-rotate
@@ -39,6 +40,7 @@ plugin_attr:
 _EOC_
 
     $block->set_value("yaml_config", $user_yaml_config);
+    $block->set_value("request", "GET /t");
 });
 
 run_tests;
@@ -64,21 +66,19 @@ __DATA__
                     local content = f:read("*all")
                     f:close()
                     local index = string.find(content, "start xxxxxx")
-                    if index then    
+                    if index then
                         has_split_error_file = true
                     end
                 end
             end
-            
-            if not has_split_error_file or not has_split_error_file then 
+
+            if not has_split_error_file or not has_split_error_file then
                ngx.status = 500
             else
                ngx.status = 200
-            end        
+            end
         }
     }
---- request
-GET /t
 --- error_code eval
 [200]
 --- no_error_log
@@ -95,11 +95,89 @@ GET /t
             ngx.say("done")
         }
     }
---- request
-GET /t
 --- response_body
 done
 --- no_error_log
 [error]
 --- error_log
 start xxxxxx
+
+
+
+=== TEST 3: fix: ensure only one timer is running
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.5)
+            local t = require("lib.test_admin").test
+            local code, _, org_body = t('/apisix/admin/plugins/reload',
+                                        ngx.HTTP_PUT)
+
+            ngx.status = code
+            ngx.say(org_body)
+
+            ngx.sleep(1)
+
+            local lfs = require("lfs")
+            for file_name in lfs.dir(ngx.config.prefix() .. "/logs/") do
+                if string.match(file_name, "__error.log$") then
+                    local f = assert(io.open(ngx.config.prefix() .. "/logs/" 
.. file_name, "r"))
+                    local content = f:read("*all")
+                    f:close()
+                    local counter = 0
+                    ngx.re.gsub(content, [=[run timer\[plugin#log-rotate\]]=], 
function()
+                        counter = counter + 1
+                        return ""
+                    end)
+
+                    if counter ~= 1 then
+                        ngx.say("not a single rotater run at the same time: ", 
file_name)
+                    end
+                end
+            end
+        }
+    }
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: disable log-rotate via hot reload
+--- config
+    location /t {
+        content_by_lua_block {
+            local data = [[
+apisix:
+  node_listen: 1984
+  admin_key: null
+plugins:
+  - prometheus
+            ]]
+            require("lib.test_admin").set_config_yaml(data)
+            local t = require("lib.test_admin").test
+            local code, _, org_body = t('/apisix/admin/plugins/reload',
+                                        ngx.HTTP_PUT)
+
+            ngx.status = code
+            ngx.say(org_body)
+
+            ngx.sleep(1.5)
+
+            local n_split_error_file = 0
+            local lfs = require("lfs")
+            for file_name in lfs.dir(ngx.config.prefix() .. "/logs/") do
+                if string.match(file_name, "__error.log$") then
+                    n_split_error_file = n_split_error_file + 1
+                end
+            end
+
+            ngx.say(n_split_error_file)
+        }
+    }
+--- response_body
+done
+1
+--- no_error_log
+[error]

Reply via email to