236 lines
7.5 KiB
Lua
236 lines
7.5 KiB
Lua
-- make sure we are pointing to the local copas first
|
|
package.path = string.format("../src/?.lua;%s", package.path)
|
|
|
|
|
|
|
|
local copas = require "copas"
|
|
local socket = require "socket"
|
|
local gettime = copas.gettime
|
|
local timer = copas.timer
|
|
|
|
local successes = 0
|
|
|
|
copas.loop(function()
|
|
|
|
local count_t1 = 0
|
|
local t1 = timer.new({
|
|
delay = 0.5,
|
|
recurring = true,
|
|
params = "hello world",
|
|
callback = function(timer_obj, params)
|
|
-- let's ensure parameters get passed
|
|
assert(params == "hello world", "expected: hello world")
|
|
successes = successes + 1 -- 6 to come
|
|
count_t1 = count_t1 + 1
|
|
print(params .. " " .. count_t1)
|
|
end,
|
|
})
|
|
-- succes count = 6
|
|
|
|
local t2 = timer.new({
|
|
delay = 0.2, -- we'll override this with 0.1 below
|
|
recurring = false,
|
|
params = {
|
|
start_time = gettime()
|
|
},
|
|
initial_delay = 0.1, -- initial delay, only 0.1
|
|
callback = function(timer_obj, params)
|
|
assert(gettime() - params.start_time < 0.11, "didn't honour initial delay, or recurred")
|
|
print("this seems to go well, and should print only once")
|
|
successes = successes + 1 -- 1 to come
|
|
end,
|
|
})
|
|
-- succes count = 7
|
|
|
|
timer.new({
|
|
delay = 3.3, --> allows T1 to run 6 times
|
|
callback = function(timer_obj, params)
|
|
t1:cancel()
|
|
local _, err = t2:cancel()
|
|
assert(err == "not armed", "expected t2 to already be stopped")
|
|
successes = successes + 1 -- 1 to come
|
|
assert(count_t1 == 6, "expected t1 to run 6 times!")
|
|
successes = successes + 1 -- 1 to come
|
|
timer_obj:cancel() -- cancel myself
|
|
end,
|
|
})
|
|
-- succes count = 9
|
|
|
|
timer.new({
|
|
delay = 0.1,
|
|
recurring = true,
|
|
callback = function(timer_obj, params)
|
|
-- re-arm myself (recurring), should not be possible
|
|
local ok, err = timer_obj:arm(1)
|
|
assert(err == "already armed", "expected myself to be already armed")
|
|
assert(ok == nil, "expected 'ok' to be nil")
|
|
print("failed to re-arm a recurring timer, so that's ok")
|
|
successes = successes + 1 -- 1 to come
|
|
assert(timer_obj:cancel()) -- cancel myself
|
|
end,
|
|
})
|
|
-- succes count = 10
|
|
|
|
local touched = false
|
|
timer.new({
|
|
delay = 0.1,
|
|
recurring = false,
|
|
callback = function(timer_obj, params)
|
|
if touched == false then
|
|
-- re-arm myself (non-recurring), should be possible
|
|
local ok, err = timer_obj:arm(3)
|
|
assert(ok == timer_obj)
|
|
assert(err == nil, "expected 'err' to be nil")
|
|
touched = gettime()
|
|
print("re-armed a non-recurring timer, so that's ok")
|
|
successes = successes + 1 -- 1 to come
|
|
else
|
|
print("a re-armed non-recurring timer executed, so that's ok")
|
|
successes = successes + 1 -- 1 to come
|
|
local t = math.abs(gettime() - touched - 3)
|
|
assert(t < 0.01, "expected a 3 second delay for the rearmed timer. Got: "..(gettime() - touched))
|
|
successes = successes + 1 -- 1 to come
|
|
end
|
|
end,
|
|
})
|
|
-- succes count = 13
|
|
|
|
local count = 0
|
|
local params_in = {}
|
|
-- timer shouldn't be cancelled if its handler errors
|
|
timer.new({
|
|
name = "error-test",
|
|
delay = 0.1,
|
|
recurring = true,
|
|
params = params_in,
|
|
errorhandler = function(msg, co, skt)
|
|
local errmsg = copas.gettraceback(msg, co, skt)
|
|
assert(errmsg:find("error%-test"), "the threadname wasn't found")
|
|
assert(errmsg:find("error 1!") or errmsg:find("error 2!"), "the error message wasn't found")
|
|
--print(errmsg)
|
|
successes = successes + 1
|
|
end,
|
|
callback = function(timer_obj, params)
|
|
assert(params == params_in, "Params table wasn't passed along")
|
|
count = count + 1
|
|
if count == 2 then
|
|
-- 2nd call, so we're done
|
|
timer_obj:cancel()
|
|
end
|
|
error("error "..count.."!")
|
|
end,
|
|
})
|
|
-- succes count = 15
|
|
|
|
-- Regression test for https://github.com/lunarmodules/copas/issues/206
|
|
-- Cancelling a recurring timer while its callback is yielded on socket I/O
|
|
-- must let the in-progress callback resume and finish (just without
|
|
-- rescheduling), not abandon it mid-flight.
|
|
do
|
|
local io_invocations = 0
|
|
local io_callback_finished = false
|
|
|
|
local receiver = socket.udp()
|
|
receiver:setsockname("127.0.0.1", 0)
|
|
local ip, port = receiver:getsockname()
|
|
receiver = copas.wrap(receiver)
|
|
|
|
local io_timer
|
|
io_timer = timer.new({
|
|
delay = 0.1,
|
|
recurring = true,
|
|
callback = function()
|
|
io_invocations = io_invocations + 1
|
|
local data = receiver:receive() -- yields on socket I/O
|
|
assert(data == "wakeup", "expected to receive 'wakeup', got: "..tostring(data))
|
|
io_callback_finished = true
|
|
end,
|
|
})
|
|
|
|
copas.addthread(function()
|
|
-- wait until the timer callback is parked waiting on the socket read
|
|
while io_invocations == 0 do
|
|
copas.pause(0.01)
|
|
end
|
|
copas.pause(0.05) -- make sure it actually reached the yield point
|
|
|
|
assert(io_timer:cancel())
|
|
|
|
local sender = copas.wrap(socket.udp())
|
|
sender:sendto("wakeup", ip, port)
|
|
|
|
copas.pause(0.2) -- allow the in-progress callback to resume and finish
|
|
assert(io_callback_finished, "in-progress callback was abandoned after cancel()")
|
|
successes = successes + 1 -- 1 to come
|
|
|
|
assert(io_invocations == 1, "timer rescheduled after being cancelled")
|
|
successes = successes + 1 -- 1 to come
|
|
end)
|
|
end
|
|
-- succes count = 17
|
|
|
|
end)
|
|
|
|
assert(successes == 17, "number of successes didn't match! got: "..successes)
|
|
|
|
-- Regression test for https://github.com/lunarmodules/copas/issues/213
|
|
-- A recurring timer that cancels-and-rearms itself from within its own
|
|
-- callback must not leak the cancelled (old-generation) coroutine into the
|
|
-- sleeping heap. Each leaked coroutine would sit there for its full delay,
|
|
-- consuming a slot until it (eventually) expires -- forever, for a delay of
|
|
-- math.huge, as in the original report.
|
|
-- Run in its own isolated loop so the sleeping-heap count isn't affected by
|
|
-- other tests' timers.
|
|
local leaktest_done = false
|
|
|
|
copas.loop(function()
|
|
local rearm_count = 0
|
|
local max_rearms = 5
|
|
local baseline = copas.status().timer
|
|
|
|
local leak_timer
|
|
leak_timer = timer.new({
|
|
delay = 2, -- long enough that a leaked old-generation coroutine is still parked when checked
|
|
initial_delay = 0,
|
|
recurring = true,
|
|
callback = function(timer_obj)
|
|
rearm_count = rearm_count + 1
|
|
if rearm_count <= max_rearms then
|
|
assert(timer_obj:cancel())
|
|
assert(timer_obj:arm(0))
|
|
else
|
|
assert(timer_obj:cancel())
|
|
end
|
|
end,
|
|
})
|
|
|
|
-- watchdog: without the fix, the leaked coroutines keep the loop alive
|
|
-- until their delay expires (or forever, for math.huge). Bound the
|
|
-- failure mode instead of letting the test suite hang.
|
|
local watchdog
|
|
watchdog = timer.new({
|
|
delay = 5,
|
|
callback = function()
|
|
print("timer leak regression test (issue #213) did not complete within 5 seconds")
|
|
os.exit(1)
|
|
end,
|
|
})
|
|
|
|
copas.addthread(function()
|
|
while rearm_count <= max_rearms do
|
|
copas.pause(0.01)
|
|
end
|
|
copas.pause(0.05) -- let everything settle
|
|
|
|
assert(leak_timer.cancelled, "expected the timer to be cancelled")
|
|
local timer_count = copas.status().timer
|
|
assert(timer_count <= baseline + 1,
|
|
"leaked timer coroutines detected: expected around "..baseline..", got "..timer_count)
|
|
|
|
watchdog:cancel()
|
|
leaktest_done = true
|
|
end)
|
|
end)
|
|
|
|
assert(leaktest_done, "timer leak regression test (issue #213) did not complete")
|
|
print("test success!")
|