664 lines
22 KiB
Lua
664 lines
22 KiB
Lua
-- Queue with retryable processing
|
|
--
|
|
-- This is a queue of "entries". Entries can be any Lua value. A handler
|
|
-- function is called asynchronously to consume the entries and process them
|
|
-- (i.e. send them to an upstream server).
|
|
--
|
|
-- The maximum size of each batch of entries that is passed to the `handler`
|
|
-- function can be configured using the `max_batch_size` parameter.
|
|
--
|
|
-- The handler function can either return true if the entries
|
|
-- were successfully processed, or false and an error message to indicate that
|
|
-- processing has failed. If processing has failed, the queue library will
|
|
-- automatically retry.
|
|
--
|
|
-- Usage:
|
|
--
|
|
-- local Queue = require "kong.tools.queue"
|
|
--
|
|
-- local handler = function(conf, entries)
|
|
-- -- must return true if ok, or false + error otherwise
|
|
-- return true
|
|
-- end
|
|
--
|
|
-- local handler_conf = {...} -- configuration for queue handler
|
|
-- local queue_conf = -- configuration for the queue itself (defaults shown unless noted)
|
|
-- {
|
|
-- name = "example", -- name of the queue (required)
|
|
-- log_tag = "identifyme", -- tag string to identify plugin or application area in logs
|
|
-- max_batch_size = 10, -- maximum number of entries in one batch (default 1)
|
|
-- max_coalescing_delay = 1, -- maximum number of seconds after first entry before a batch is sent
|
|
-- max_entries = 10, -- maximum number of entries on the queue (default 10000)
|
|
-- max_bytes = 100, -- maximum number of bytes on the queue (default nil)
|
|
-- initial_retry_delay = 0.01, -- initial delay when retrying a failed batch, doubled for each subsequent retry
|
|
-- max_retry_time = 60, -- maximum number of seconds before a failed batch is dropped
|
|
-- max_retry_delay = 60, -- maximum delay between send attempts, caps exponential retry
|
|
-- }
|
|
--
|
|
-- Queue.enqueue(queue_conf, handler, handler_conf, "Some value")
|
|
-- Queue.enqueue(queue_conf, handler, handler_conf, "Another value")
|
|
--
|
|
-- Given the example above,
|
|
--
|
|
-- * If the two `enqueue()` invocations are done within `max_coalescing_delay` seconds, they will be passed to the
|
|
-- handler function together in one batch. The maximum number of entries in one batch is defined
|
|
-- by the `max_batch_size` parameter.
|
|
-- * The `max_entries` parameter defines how many entries can be waiting on the queue for transmission. If
|
|
-- it is exceeded, the oldest entries on the queue will be discarded when new entries are queued. Error
|
|
-- messages describing how many entries were lost will be logged.
|
|
-- * The `max_bytes` parameter, if set, indicates that no more than that number of bytes can be
|
|
-- waiting on a queue for transmission. If it is set, only string entries must be queued and an error
|
|
-- message will be logged if an attempt is made to queue a non-string entry.
|
|
-- * When the `handler` function does not return a true value for a batch, it is retried for up to
|
|
-- `max_retry_time` seconds before the batch is deleted and an error is logged. Retries are organized
|
|
-- by the queue library with the initial delay before retrying being defined by `initial_retry_delay` and
|
|
-- the maximum time between retries defined by `max_retry_delay` seconds. For each subsequent retry, the
|
|
-- previous delay is doubled to yield an exponential back-off strategy - The first retry will be made quickly,
|
|
-- and each subsequent retry will be delayed longer.
|
|
|
|
local workspaces = require("kong.workspaces")
|
|
local semaphore = require("ngx.semaphore")
|
|
local table_new = require("table.new")
|
|
|
|
|
|
-- Minimum interval to warn about usage of legacy queueing related parameters
|
|
local MIN_WARNING_INTERVAL_SECONDS = 60
|
|
|
|
|
|
|
|
local assert = assert
|
|
local select = select
|
|
local pairs = pairs
|
|
local type = type
|
|
local setmetatable = setmetatable
|
|
local semaphore_new = semaphore.new
|
|
local math_min = math.min
|
|
local now = ngx.now
|
|
local sleep = ngx.sleep
|
|
local null = ngx.null
|
|
|
|
|
|
local Queue = {}
|
|
|
|
|
|
-- Threshold to warn that the queue max_entries limit is reached
|
|
local CAPACITY_WARNING_THRESHOLD = 0.8
|
|
-- Time in seconds to poll for worker shutdown when coalescing entries
|
|
local COALESCE_POLL_TIME = 1.0
|
|
-- If remaining coalescing wait budget is less than this number of seconds,
|
|
-- then just send the batch without waiting any further
|
|
local COALESCE_MIN_TIME = 0.05
|
|
|
|
|
|
local Queue_mt = {
|
|
__index = Queue
|
|
}
|
|
|
|
|
|
local function _make_queue_key(name)
|
|
return (workspaces.get_workspace_id() or "") .. "." .. name
|
|
end
|
|
|
|
|
|
local function _remaining_capacity(self)
|
|
local remaining_entries = self.max_entries - self:count()
|
|
local max_bytes = self.max_bytes
|
|
|
|
-- we enqueue entries one by one,
|
|
-- so it is impossible to have a negative value
|
|
assert(remaining_entries >= 0, "queue should not be over capacity")
|
|
|
|
if not max_bytes then
|
|
return remaining_entries
|
|
end
|
|
|
|
local remaining_bytes = max_bytes - self.bytes_queued
|
|
|
|
-- we check remaining_bytes before enqueueing an entry,
|
|
-- so it is impossible to have a negative value
|
|
assert(remaining_bytes >= 0, "queue should not be over capacity")
|
|
|
|
return remaining_entries, remaining_bytes
|
|
end
|
|
|
|
|
|
local function _is_reaching_max_entries(self)
|
|
-- `()` is used to get the first return value only
|
|
return (_remaining_capacity(self)) == 0
|
|
end
|
|
|
|
|
|
local function _will_exceed_max_entries(self)
|
|
-- `()` is used to get the first return value only
|
|
return (_remaining_capacity(self)) - 1 < 0
|
|
end
|
|
|
|
|
|
local function _is_entry_too_large(self, entry)
|
|
local max_bytes = self.max_bytes
|
|
|
|
if not max_bytes then
|
|
return false
|
|
end
|
|
|
|
if type(entry) ~= "string" then
|
|
-- handle non-string entry, including `nil`
|
|
return false
|
|
end
|
|
|
|
return #entry > max_bytes
|
|
end
|
|
|
|
|
|
local function _is_reaching_max_bytes(self)
|
|
if not self.max_bytes then
|
|
return false
|
|
end
|
|
|
|
local _, remaining_bytes = _remaining_capacity(self)
|
|
return remaining_bytes == 0
|
|
end
|
|
|
|
|
|
local function _will_exceed_max_bytes(self, entry)
|
|
if not self.max_bytes then
|
|
return false
|
|
end
|
|
|
|
if type(entry) ~= "string" then
|
|
-- handle non-string entry, including `nil`
|
|
return false
|
|
end
|
|
|
|
local _, remaining_bytes = _remaining_capacity(self)
|
|
return #entry > remaining_bytes
|
|
end
|
|
|
|
|
|
local function _is_full(self)
|
|
return _is_reaching_max_entries(self) or _is_reaching_max_bytes(self)
|
|
end
|
|
|
|
|
|
local function _can_enqueue(self, entry)
|
|
return not (
|
|
_is_full(self) or
|
|
_is_entry_too_large(self, entry) or
|
|
_will_exceed_max_entries(self) or
|
|
_will_exceed_max_bytes(self, entry)
|
|
)
|
|
end
|
|
|
|
|
|
local queues = {}
|
|
|
|
|
|
function Queue.exists(name)
|
|
return queues[_make_queue_key(name)] and true or false
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- Initialize a queue with background retryable processing
|
|
-- @param process function, invoked to process every payload generated
|
|
-- @param opts table, requires `name`, optionally includes `retry_count`, `max_coalescing_delay` and `max_batch_size`
|
|
-- @return table: a Queue object.
|
|
local function get_or_create_queue(queue_conf, handler, handler_conf)
|
|
|
|
local name = assert(queue_conf.name)
|
|
local key = _make_queue_key(name)
|
|
|
|
local queue = queues[key]
|
|
if queue then
|
|
queue:log_trace("queue exists")
|
|
-- We always use the latest configuration that we have seen for a queue and handler.
|
|
queue.handler_conf = handler_conf
|
|
return queue
|
|
end
|
|
|
|
queue = {
|
|
-- Queue parameters from the enqueue call
|
|
name = name,
|
|
key = key,
|
|
handler = handler,
|
|
handler_conf = handler_conf,
|
|
|
|
-- semaphore to count the number of items on the queue and synchronize between enqueue and handler
|
|
semaphore = semaphore_new(),
|
|
|
|
-- `bytes_queued` holds the number of bytes on the queue. It will be used only if max_bytes is set.
|
|
bytes_queued = 0,
|
|
-- `entries` holds the actual queue items.
|
|
entries = table_new(32, 0),
|
|
-- Pointers into the table that holds the actual queued entries. `front` points to the oldest, `back` points to
|
|
-- the newest entry.
|
|
front = 1,
|
|
back = 1,
|
|
}
|
|
for option, value in pairs(queue_conf) do
|
|
queue[option] = value
|
|
end
|
|
|
|
queue = setmetatable(queue, Queue_mt)
|
|
|
|
if queue.concurrency_limit == 1 then
|
|
kong.timer:named_at("queue " .. key, 0, function(_, q)
|
|
while q:count() > 0 do
|
|
q:log_trace("processing queue")
|
|
q:process_once()
|
|
end
|
|
q:log_trace("done processing queue")
|
|
queues[key] = nil
|
|
end, queue)
|
|
queues[key] = queue
|
|
end
|
|
|
|
|
|
queue:log_trace("queue created")
|
|
|
|
return queue
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- Log a message that includes the name of the queue for identification purposes
|
|
-- @param self Queue
|
|
-- @param level: log level
|
|
-- @param formatstring: format string, will get the queue name and ": " prepended
|
|
-- @param ...: formatter arguments
|
|
function Queue:log(handler, formatstring, ...)
|
|
local message = "[" .. (self.log_tag or "") .. "] queue " .. self.name .. ": " .. formatstring
|
|
if select('#', ...) > 0 then
|
|
return handler(string.format(message, unpack({...})))
|
|
else
|
|
return handler(message)
|
|
end
|
|
end
|
|
|
|
function Queue:log_trace(...) self:log(kong.log.trace, ...) end
|
|
function Queue:log_debug(...) self:log(kong.log.debug, ...) end
|
|
function Queue:log_info(...) self:log(kong.log.info, ...) end
|
|
function Queue:log_warn(...) self:log(kong.log.warn, ...) end
|
|
function Queue:log_err(...) self:log(kong.log.err, ...) end
|
|
|
|
|
|
function Queue:count()
|
|
return self.back - self.front
|
|
end
|
|
|
|
|
|
function Queue.is_full(queue_conf)
|
|
local queue = queues[_make_queue_key(queue_conf.name)]
|
|
if not queue then
|
|
-- treat non-existing queues as not full as they will be created on demand
|
|
return false
|
|
end
|
|
|
|
return _is_full(queue)
|
|
end
|
|
|
|
|
|
function Queue.can_enqueue(queue_conf, entry)
|
|
local queue = queues[_make_queue_key(queue_conf.name)]
|
|
if not queue then
|
|
-- treat non-existing queues having enough capacity.
|
|
-- WARNING: The limitation is that if the `entry` is a string and the `queue.max_bytes` is set,
|
|
-- and also the `#entry` is larger than `queue.max_bytes`,
|
|
-- this function will incorrectly return `true` instead of `false`.
|
|
-- This is a limitation of the current implementation.
|
|
-- All capacity checking functions need a Queue instance to work correctly.
|
|
-- constructing a Queue instance just for this function is not efficient,
|
|
-- so we just return `true` here.
|
|
-- This limitation should not happen in normal usage,
|
|
-- as user should be aware of the queue capacity settings
|
|
-- to avoid such situation.
|
|
return true
|
|
end
|
|
|
|
return _can_enqueue(queue, entry)
|
|
end
|
|
|
|
local function handle(self, entries)
|
|
local entry_count = #entries
|
|
|
|
local start_time = now()
|
|
local retry_count = 0
|
|
while true do
|
|
self:log_trace("passing %d entries to handler", entry_count)
|
|
local status, ok, err = pcall(self.handler, self.handler_conf, entries)
|
|
if status and ok == true then
|
|
self:log_trace("handler processed %d entries successfully", entry_count)
|
|
break
|
|
end
|
|
|
|
if not status then
|
|
-- protected call failed, ok is the error message
|
|
err = ok
|
|
end
|
|
|
|
self:log_warn("handler could not process entries: %s", tostring(err or "no error details returned by handler"))
|
|
|
|
if not err then
|
|
self:log_err("handler returned falsy value but no error information")
|
|
end
|
|
|
|
if (now() - start_time) > self.max_retry_time then
|
|
self:log_err(
|
|
"could not send entries due to max_retry_time exceeded. %d queue entries were lost",
|
|
entry_count)
|
|
break
|
|
end
|
|
|
|
-- Delay before retrying. The delay time is calculated by multiplying the configured initial_retry_delay with
|
|
-- 2 to the power of the number of retries, creating an exponential increase over the course of each retry.
|
|
-- The maximum time between retries is capped by the max_retry_delay configuration parameter.
|
|
sleep(math_min(self.max_retry_delay, 2 ^ retry_count * self.initial_retry_delay))
|
|
retry_count = retry_count + 1
|
|
end
|
|
end
|
|
|
|
|
|
-- Delete the frontmost entry from the queue and adjust the current utilization variables.
|
|
function Queue:delete_frontmost_entry()
|
|
if self.max_bytes then
|
|
-- If max_bytes is set, reduce the currently queued byte count by the
|
|
self.bytes_queued = self.bytes_queued - #self.entries[self.front]
|
|
end
|
|
self.entries[self.front] = nil
|
|
self.front = self.front + 1
|
|
if self.front == self.back then
|
|
self.front = 1
|
|
self.back = 1
|
|
end
|
|
end
|
|
|
|
|
|
-- Drop the oldest entry, adjusting the semaphore value in the process. This is
|
|
-- called when the queue runs out of space and needs to make space.
|
|
function Queue:drop_oldest_entry()
|
|
assert(self.semaphore:count() > 0)
|
|
self.semaphore:wait(0)
|
|
self:delete_frontmost_entry()
|
|
end
|
|
|
|
|
|
-- Process one batch of entries from the queue. Returns truthy if entries were processed, falsy if there was an
|
|
-- error or no items were on the queue to be processed.
|
|
function Queue:process_once()
|
|
local ok, err = self.semaphore:wait(0)
|
|
if not ok then
|
|
if err ~= "timeout" then
|
|
-- We can't do anything meaningful to recover here, so we just log the error.
|
|
self:log_err('error waiting for semaphore: %s', err)
|
|
end
|
|
return
|
|
end
|
|
local data_started = now()
|
|
|
|
local entry_count = 1
|
|
|
|
-- We've got our first entry from the queue. Collect more entries until max_coalescing_delay expires or we've collected
|
|
-- max_batch_size entries to send
|
|
while entry_count < self.max_batch_size
|
|
and self.max_coalescing_delay - (now() - data_started) >= COALESCE_MIN_TIME
|
|
do
|
|
-- Instead of waiting for the coalesce time to expire, we cap the semaphore wait to COALESCE_POLL_TIME
|
|
-- so that we can check for worker shutdown periodically.
|
|
local wait_time = math_min(self.max_coalescing_delay - (now() - data_started), COALESCE_POLL_TIME)
|
|
|
|
if ngx.worker.exiting() then
|
|
-- minimize coalescing delay during shutdown to quickly process remaining entries
|
|
self.max_coalescing_delay = COALESCE_MIN_TIME
|
|
wait_time = COALESCE_MIN_TIME
|
|
end
|
|
|
|
ok, err = self.semaphore:wait(wait_time)
|
|
if not ok and err ~= "timeout" then
|
|
self:log_err("could not wait for semaphore: %s", err)
|
|
break
|
|
elseif ok then
|
|
entry_count = entry_count + 1
|
|
end
|
|
end
|
|
|
|
local batch = {unpack(self.entries, self.front, self.front + entry_count - 1)}
|
|
for _ = 1, entry_count do
|
|
self:delete_frontmost_entry()
|
|
end
|
|
if self.already_dropped_entries then
|
|
self:log_info('queue resumed processing')
|
|
self.already_dropped_entries = false
|
|
end
|
|
|
|
handle(self, batch)
|
|
end
|
|
|
|
|
|
local legacy_params_warned = {}
|
|
|
|
local function maybe_warn(name, message)
|
|
local key = name .. "/" .. message
|
|
if ngx.now() - (legacy_params_warned[key] or 0) >= MIN_WARNING_INTERVAL_SECONDS then
|
|
kong.log.warn(message)
|
|
legacy_params_warned[key] = ngx.now()
|
|
end
|
|
end
|
|
|
|
|
|
-- This function retrieves the queue parameters from a plugin configuration, converting legacy parameters
|
|
-- to their new locations.
|
|
function Queue.get_plugin_params(plugin_name, config, queue_name)
|
|
local queue_config = config.queue or table_new(0, 5)
|
|
|
|
-- create a tag to put into log files that identifies the plugin instance
|
|
local log_tag = plugin_name .. " plugin " .. kong.plugin.get_id()
|
|
if config.plugin_instance_name then
|
|
log_tag = log_tag .. " (" .. config.plugin_instance_name .. ")"
|
|
end
|
|
queue_config.log_tag = log_tag
|
|
|
|
if not queue_config.name then
|
|
queue_config.name = queue_name or kong.plugin.get_id()
|
|
end
|
|
|
|
-- It is planned to remove the legacy parameters in Kong Gateway 4.0, removing
|
|
-- the need for the checks below. ({ after = "4.0", })
|
|
if (config.retry_count or null) ~= null and config.retry_count ~= 10 then
|
|
maybe_warn(
|
|
queue_config.name,
|
|
"the retry_count parameter no longer works, please update "
|
|
.. "your configuration to use initial_retry_delay and max_retry_time instead")
|
|
end
|
|
|
|
if (config.queue_size or null) ~= null and config.queue_size ~= 1 then
|
|
queue_config.max_batch_size = config.queue_size
|
|
maybe_warn(
|
|
queue_config.name,
|
|
"the queue_size parameter is deprecated, please update your "
|
|
.. "configuration to use queue.max_batch_size instead")
|
|
end
|
|
|
|
if (config.flush_timeout or null) ~= null and config.flush_timeout ~= 2 then
|
|
queue_config.max_coalescing_delay = config.flush_timeout
|
|
maybe_warn(
|
|
queue_config.name,
|
|
"the flush_timeout parameter is deprecated, please update your "
|
|
.. "configuration to use queue.max_coalescing_delay instead")
|
|
end
|
|
|
|
if (config.batch_span_count or null) ~= null and config.batch_span_count ~= 200 then
|
|
queue_config.max_batch_size = config.batch_span_count
|
|
maybe_warn(
|
|
queue_config.name,
|
|
"the batch_span_count parameter is deprecated, please update your "
|
|
.. "configuration to use queue.max_batch_size instead")
|
|
end
|
|
|
|
if (config.batch_flush_delay or null) ~= null and config.batch_flush_delay ~= 3 then
|
|
queue_config.max_coalescing_delay = config.batch_flush_delay
|
|
maybe_warn(
|
|
queue_config.name,
|
|
"the batch_flush_delay parameter is deprecated, please update your "
|
|
.. "configuration to use queue.max_coalescing_delay instead")
|
|
end
|
|
return queue_config
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- Add entry to the queue
|
|
-- @param conf plugin configuration of the plugin instance that caused the item to be queued
|
|
-- @param entry the value included in the queue. It can be any Lua value besides nil.
|
|
-- @return true, or nil and an error message.
|
|
local function enqueue(self, entry)
|
|
if entry == nil then
|
|
return nil, "entry must be a non-nil Lua value"
|
|
end
|
|
|
|
if self.concurrency_limit == -1 then -- unlimited concurrency
|
|
-- do not enqueue when concurrency_limit is unlimited
|
|
local ok, err = kong.timer:at(0, function(premature)
|
|
if premature then
|
|
return
|
|
end
|
|
handle(self, { entry })
|
|
end)
|
|
if not ok then
|
|
return nil, "failed to crete timer: " .. err
|
|
end
|
|
return true
|
|
end
|
|
|
|
|
|
if self:count() >= self.max_entries * CAPACITY_WARNING_THRESHOLD then
|
|
if not self.warned then
|
|
self:log_warn('queue at %s%% capacity', CAPACITY_WARNING_THRESHOLD * 100)
|
|
self.warned = true
|
|
end
|
|
else
|
|
self.warned = nil
|
|
end
|
|
|
|
if _is_reaching_max_entries(self) then
|
|
self:log_err("queue full, dropping old entries until processing is successful again")
|
|
self:drop_oldest_entry()
|
|
self.already_dropped_entries = true
|
|
end
|
|
|
|
if _is_entry_too_large(self, entry) then
|
|
local err_msg = string.format(
|
|
"string to be queued is longer (%d bytes) than the queue's max_bytes (%d bytes)",
|
|
#entry,
|
|
self.max_bytes
|
|
)
|
|
self:log_err(err_msg)
|
|
|
|
return nil, err_msg
|
|
end
|
|
|
|
if _will_exceed_max_bytes(self, entry) then
|
|
local dropped = 0
|
|
|
|
repeat
|
|
self:drop_oldest_entry()
|
|
dropped = dropped + 1
|
|
self.already_dropped_entries = true
|
|
until not _will_exceed_max_bytes(self, entry)
|
|
|
|
self:log_err("byte capacity exceeded, %d queue entries were dropped", dropped)
|
|
end
|
|
|
|
-- safety guard
|
|
-- The queue should not be full if we are running into this situation.
|
|
-- Since the dropping logic is complicated,
|
|
-- further maintenancers might introduce bugs,
|
|
-- so I added this assertion to detect this kind of bug early.
|
|
-- It's better to crash early than leak memory
|
|
-- as analyze memory leak is hard.
|
|
assert(
|
|
-- assert that enough space is available on the queue now
|
|
_can_enqueue(self, entry),
|
|
"queue should not be full after dropping entries"
|
|
)
|
|
|
|
if self.max_bytes then
|
|
if type(entry) ~= "string" then
|
|
self:log_err("queuing non-string entry to a queue that has queue.max_bytes set, capacity monitoring will not be correct")
|
|
end
|
|
|
|
self.bytes_queued = self.bytes_queued + #entry
|
|
end
|
|
|
|
self.entries[self.back] = entry
|
|
self.back = self.back + 1
|
|
self.semaphore:post()
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
function Queue.enqueue(queue_conf, handler, handler_conf, value)
|
|
|
|
assert(type(queue_conf) == "table",
|
|
"arg #1 (queue_conf) must be a table")
|
|
assert(type(handler) == "function",
|
|
"arg #2 (handler) must be a function")
|
|
assert(handler_conf == nil or type(handler_conf) == "table",
|
|
"arg #3 (handler_conf) must be a table or nil")
|
|
assert(type(queue_conf.name) == "string",
|
|
"arg #1 (queue_conf) must include a name")
|
|
|
|
assert(
|
|
type(queue_conf.max_batch_size) == "number",
|
|
"arg #1 (queue_conf) max_batch_size must be a number"
|
|
)
|
|
assert(
|
|
type(queue_conf.max_coalescing_delay) == "number",
|
|
"arg #1 (queue_conf) max_coalescing_delay must be a number"
|
|
)
|
|
assert(
|
|
type(queue_conf.max_entries) == "number",
|
|
"arg #1 (queue_conf) max_entries must be a number"
|
|
)
|
|
assert(
|
|
type(queue_conf.max_retry_time) == "number",
|
|
"arg #1 (queue_conf) max_retry_time must be a number"
|
|
)
|
|
assert(
|
|
type(queue_conf.initial_retry_delay) == "number",
|
|
"arg #1 (queue_conf) initial_retry_delay must be a number"
|
|
)
|
|
assert(
|
|
type(queue_conf.max_retry_delay) == "number",
|
|
"arg #1 (queue_conf) max_retry_delay must be a number"
|
|
)
|
|
|
|
local max_bytes_type = type(queue_conf.max_bytes)
|
|
assert(
|
|
max_bytes_type == "nil" or max_bytes_type == "number",
|
|
"arg #1 (queue_conf) max_bytes must be a number or nil"
|
|
)
|
|
|
|
assert(
|
|
type(queue_conf.concurrency_limit) == "number",
|
|
"arg #1 (queue_conf) concurrency_limit must be a number"
|
|
)
|
|
|
|
local queue = get_or_create_queue(queue_conf, handler, handler_conf)
|
|
return enqueue(queue, value)
|
|
end
|
|
|
|
-- For testing, the _exists() function is provided to allow a test to wait for the
|
|
-- queue to have been completely processed.
|
|
function Queue._exists(name)
|
|
local queue = queues[_make_queue_key(name)]
|
|
return queue and queue:count() > 0
|
|
end
|
|
|
|
|
|
-- [[ For testing purposes only
|
|
Queue._CAPACITY_WARNING_THRESHOLD = CAPACITY_WARNING_THRESHOLD
|
|
-- ]]
|
|
|
|
|
|
return Queue
|