Module timer
Copas Timer is a module that adds a timer capability to the Copas scheduler.
It provides the same base functions copas.step and copas.loop
as Copas (it actually replaces them) except that it will also check for (and
run) timer expiring and run background worker if there is no IO or timer to
handle.
To use the module it should be required as; local copas =
require("copas.timer")
because it returns the global copas
table and not a separate timer table.
There is a difference between the 2 background mechanisms provided; the
timer runs on the main loop, and hence should never yield. A worker runs in
its own thread (coroutine) and can be yielded (worker:pause ) if they take too long.
The workers are dispatched from a rotating queue, so when a worker is up to run
it will be removed from the queue, resumed, and (if its queue isn't empty) added at the end
of the queue again.
Info:
- Copyright: 2011-2014 Thijs Schreijer
- Release: Version 1.0, Timer module to extend Copas with a timer, worker and event capabilities
- License: Copas Timer is free software under the MIT/X11 license.
- Author: Thijs Schreijer, http://www.thijsschreijer.nl
Class queueitem
Object created by
worker:push when data is pushed into the
worker queue. This
object can be tracked for progress or cancellation.
-
queueitem.cancelled
-
flag;
true
when the element has been cancelled.
-
queueitem.completed
-
flag;
true
when the element has been completed or cancelled.
'completed' is when the worker is finished with it and requests the next element by calling its worker:pop function
-
queueitem.data
-
The actual data contained within the queueitem .
-
queueitem.worker
-
The worker object for which this queueitem has been enqueued.
-
queueitem:cancel ()
-
Cancels the queueitem .
When cancelling both the cancelled and completed
flag will be set. The cancelled flag will prevent the data from being executed
when it is being popped from the queue.
-
queueitem:complete ()
-
Marks the queueitem as completed.
The completed flag will be set. Generally there is no need to call this method, it will
be called when the worker handling this element pops the next element from its queue.
Worker functions
-
copas.addworker ([obj], func[, errhandler])
-
Creates a worker and adds it to the Copas scheduler. The workers will be executed when there is no I/O nor
any expiring timer to run. The function will be started immediately upon
creating the coroutine for the worker. Calling worker:push on
the returned worker table will enqueue data to be handled. The function can
fetch data from the queue through worker:pop which will
pop a new element from the workers queue. For lengthy operations where the code needs
to yield without popping a new element from the queue, call worker:pause .
Parameters:
- obj
object the worker relates to
- func
function
function to execute as the coroutine. It takes as arguments
obj
and worker , where obj
is only provided when it is passed to addworker . It allows to use obj
as self
in the worker function. See both examples below.
- errhandler
function
function to handle any errors returned or
nil
(should be a function taking 2 arguments; 1 - coroutine generating the error, 2 - returned error)
Returns:
worker
See also:
Usage:
local obj = {}
function obj:display(data) print(data) end
function obj:handler(worker) while true do
data = worker:pop() self:display(data) end
end
obj.worker = copas.addworker(obj, obj.handler)
obj.worker:push("here is some data")
local w = copas.addworker(function(queue)
while true do
data = queue:pop() print(data)
queue:pause() end
end)
w:push("here is some data")
-
copas.getworker (t)
-
Returns a background worker .
Parameters:
Returns:
the worker table (as earlier returned by copas.addworker )
Or
nil
if the coroutine wasn't found
See also:
Usage:
if copas.getworker(coroutine.running()) then
print ("I'm running as a background worker")
else
print ("No background worker found, so I'm on my own")
end
-
copas.removeworker (t)
-
Removes a worker from the Copas scheduler.
Parameters:
- t
coroutine or worker object to be removed
Returns:
worker
Or
nil
if the coroutine/worker wasn't found
Class worker
This class represents a
worker and its queue with
queueitem objects. It can be used to manipulate the worker and push data to it.
-
worker.errhandler
-
Holds the errorhandler for this worker
-
worker.queue
-
Holds the list of queueitem objects waiting to be processed by the worker
-
worker.thread
-
Holds the coroutine for this worker
-
worker:pause ()
-
Yields control in case of lengthy operations. Similar to worker:pop except
that this method does not pop a new element from the worker queue.
Returns:
true
-
worker:pop ()
-
Retrieves data from the worker queue (and implicitly yields control). Note that this method
implicitly yields the coroutine until new data has been pushed in the worker queue.
Returns:
data field of the next queueitem popped from the queue
-
worker:push (data)
-
Adds data to the worker queue. If the worker has died, it will return an error
and nothing will be enqueued.
Parameters:
- data
Data (any type) to be added to the queue of the worker
Returns:
queueitem that was added to the worker queue
Or
-
nil
-
error message
Usage:
local w = copas.addworker(myfunc)
w:push("some data")
Timer functions
Creating and handling timers
-
copas.cancelall ()
-
Cancels all currently armed timer objects.
See also:
-
copas.newtimer (f_arm, f_expire, f_cancel, recurring, f_error)
-
Creates a new timer .
After creating call the arm method of the new timer to actually
schedule it. REMARK: the background worker run on their own coroutine
and hence need to yield control when their operation takes too long, but
the timer run on the main loop, and hence the callbacks should never
yield, in those cases consider adding a worker through
copas.addworker from the timer callback.
Parameters:
- f_arm
function
callback function to execute when the timer is armed, or
nil
- f_expire
function
callback function to execute when the timer expires
- f_cancel
function
callback function to execute when the timer is cancelled, or
nil
- recurring
boolean
(boolean) should the timer automatically be re-armed with
the same interval after it expired
- f_error
function
callback function to execute (in a xpcall call), or
nil
when any of the other callbacks generates an error
See also:
Usage:
local t = copas.newtimer(nil, function () print("hello world") end, nil, false, nil)
copas.newtimer(nil, function () print("hello world") end, nil, false, nil):arm(5)
local f = function () print("hello world") end
copas.newtimer(f, f, nil, true, nil):arm(5)
Class timer
-
timer:arm (interval)
-
Arms a previously created timer . When arm is called on an already
armed timer then the timer will be rescheduled, the cancel handler
will not be called in this case, but the arm handler will run.
Parameters:
- interval
number
the interval after which the timer expires (in seconds). This must
be set with the first call to arm any additional calls will reuse
the existing interval if no new interval is provided.
Returns:
timer
See also:
Usage:
local f = function() print("hello world") end
local t = copas.newtimer(nil, f, nil, false)
t:arm(5) local t = copas.newtimer(nil, f, nil, false):arm(5)
-
timer:cancel ()
-
Cancels a previously armed timer. This will run the cancel handler
provided when creating the timer .
See also:
Usage:
local t = copas.newtimer(nil, function () print("hello world") end, nil, false)
t:arm(5) t:cancel() -- cancel it again
Copas core
Adapted base functions and some additional ones.
-
copas.exitloop (timeout, keeptimers)
-
Instructs Copas to exit the loop. It will wait for any background worker to complete their queue.
If the
copas.eventer
is used then the timeout will only start after the
copas.events.loopstopping
event has been completely handled.
Parameters:
- timeout
number
Timeout (in seconds) after which to forcefully exit the loop,
abandoning any worker still running.
nil
: no timeout, continue running until all workers have emptied their queues
< 0
: exit immediately after next loop iteration, do not
wait for workers nor the copas.events.loopstopping/loopstopped
events to complete
(timers will still be cancelled if set to do so)
- keeptimers
boolean
if
true
then the active timers will NOT be cancelled, otherwise
copas.cancelall will be called to properly cancel all running timers.
See also:
-
copas.isexiting ()
-
Indicator of the loop running or exiting.
Returns:
nil
when the loop is not running
Or
false
when the loop is running
Or
true
when the loop is scheduled to stop
See also:
Usage:
if copas.isexiting() ~= nil then
copas.exitloop(nil, false)
end
-
copas.loop (timeout, precision)
-
Executes an endless loop handling Copas steps and timers (it replaces the original copas.loop ).
The loop can be terminated by calling copas.exitloop .
Parameters:
- timeout
number
time out (in seconds) to be used. The timer list
will be checked at least every
timeout
period for expired timers. The
actual interval will be between 0
and timeout
based on the next
timers expire time or worker threads being available. If not provided, it defaults to 5 seconds.
- precision
number
the precision of the timer (in seconds). Whenever the timer
list is checked for expired timers, a timer is considered expired when the exact
expire time is in the past, or up to
precision
seconds in the future.
It defaults to 0.02
if not provided.
See also:
-
copas.step (timeout, precision)
-
Executes a single Copas step followed by the execution of the first expired
(if any) timer in the timers list (it replaces the original copas.step )
if there is no timer that expires then it will try to execute a worker if available.
Parameters:
- timeout
number
timeout value (in seconds) to pass to the original Copas step handler
- precision
number
see parameter
precision
at function copas.loop
Returns:
time in seconds until the next timer in the list expires
Or
0 if there are worker objects with non-empty queues
Or
nil
if there is no timer nor any worker with work to do
See also:
Utility functions
Some useful functions using timers, for timed actions and checks.
-
copas.delayedexecutioner (delay, func, ...)
-
Calls a function delayed, after the specified amount of time.
An example use is a call that requires communications to be running already,
but if you start the Copas loop, it basically blocks; classic chicken-egg. In this case use the
delayedexecutioner to call the method in 0.5 seconds, just before
starting the CopasTimer loop. Now when the method actually executes, communications
will be online already. The internals use a timer , so it is executed on the main
loop and should not be suspended by calling
yield()
.
Parameters:
- delay
number
delay in seconds before calling the function
- func
function
function to call
- ...
any arguments to be passed to the function
Returns:
timer implementing the delay
See also:
Usage:
local t = socket.gettime()
copas.delayedexecutioner(5, function(txt)
print(txt .. " and it was " .. socket.gettime() - t .. " to be precise.")
end, "This should display in 5 seconds from now.")
-
copas.waitforcondition (interval, timeout, condition, handler, ...)
-
Executes a handler function after a specific condition has been met
(non-blocking wait). This is implemented using a timer , hence both the
condition
and handler
functions run on the main
thread and should never yield.
Parameters:
- interval
number
interval (in seconds) for checking the condition
- timeout
number
timeout value (in seconds) after which the operation fails
(note that the
handler()
will still be called)
- condition
function
a function that is called repeatedly. It will get the
additional parameters specified to waitforcondition . The
function should return
true
or false
depending on
whether the condition was met.
- handler
function
the handler function that will be executed. It will
always be executed. The first argument to the handler will
be
true
if the condition was met, or false
if the
operation timed-out, any additional parameters provided to
waitforcondition will be passed after that.
- ...
additional parameters passed on to both the
condition
and handler
functions.
Returns:
timer that verifies the condition.
Usage:
local count = 1
function check(param)
print("Check count ", count, ". Called using param = ", param)
count = count + 1
return (count == 10)
end
function done(conditionmet, param)
if conditionmet then
print("The condition was met when count reached ", count - 1,". Called using param = ", param)
else
print("Failed, condition was not met. Called using param = ", param)
end
end
copas.waitforcondition(0.1, 5, check, done, "1234567890")