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

queueitem.cancelled flag; true when the element has been cancelled.
queueitem.completed flag; true when the element has been completed or cancelled.
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 .
queueitem:complete () Marks the queueitem as completed.

Worker functions

copas.addworker ([obj], func[, errhandler]) Creates a worker and adds it to the Copas scheduler.
copas.getworker (t) Returns a background worker .
copas.removeworker (t) Removes a worker from the Copas scheduler.

Class worker

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.
worker:pop () Retrieves data from the worker queue (and implicitly yields control).
worker:push (data) Adds data to the worker queue.

Timer functions

copas.cancelall () Cancels all currently armed timer objects.
copas.newtimer (f_arm, f_expire, f_cancel, recurring, f_error) Creates a new timer .

Class timer

timer:arm (interval) Arms a previously created timer .
timer:cancel () Cancels a previously armed timer.

Copas core

copas.exitloop (timeout, keeptimers) Instructs Copas to exit the loop.
copas.isexiting () Indicator of the loop running or exiting.
copas.loop (timeout, precision) Executes an endless loop handling Copas steps and timers (it replaces the original copas.loop ).
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.

Utility functions

copas.delayedexecutioner (delay, func, ...) Calls a function delayed, after the specified amount of time.
copas.waitforcondition (interval, timeout, condition, handler, ...) Executes a handler function after a specific condition has been met (non-blocking wait).


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.
  • cancelled boolean
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
  • completed boolean
queueitem.data
The actual data contained within the queueitem .
  • data the actual data
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) -- object ':' notation
        -- do some initializing here... will be run immediately upon
        -- adding the worker
        while true do
            data = worker:pop()   -- fetch data from queue, implicitly yields the coroutine
            self:display(data)    -- call on 'self'
        end
     end
     obj.worker = copas.addworker(obj, obj.handler)
     -- enqueue data for the new worker
     obj.worker:push("here is some data")
    
     -- alternative without an object, demonstrating 'pause' for long operations
     local w = copas.addworker(function(queue)
             -- 'queue' is the worker object (named 'queue' for readability)
             -- do some initializing here... will be run immediately upon
             -- adding the worker
             while true do
                 data = queue:pop()    -- fetch data from queue, implicitly yields the coroutine
                 -- handle the retrieved data here
                 print(data)
                 -- do some lengthy stuff
                 queue:pause()         -- implicitly yields
                 -- do more lengthy stuff
             end
         end)
     -- enqueue data for the new worker
     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
  • errhandler
worker.queue
Holds the list of queueitem objects waiting to be processed by the worker
  • queue
worker.thread
Holds the coroutine for this worker
  • thread
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

  1. nil
  2. 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:

     -- Create a new timer
     local t = copas.newtimer(nil, function () print("hello world") end, nil, false, nil)
    
     -- Create timer and arm it immediately, to be run in 5 seconds
     copas.newtimer(nil, function () print("hello world") end, nil, false, nil):arm(5)
    
     -- Create timer and arm it immediately, to be run now (function f is provide twice!) and again every 5 seconds
     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:

     -- Create a new timer
     local f = function() print("hello world") end
     local t = copas.newtimer(nil, f, nil, false)
     t:arm(5)              -- arm it at 5 seconds
     -- which is equivalent to chaining the arm() call
     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:

     -- Create a new timer
     local t = copas.newtimer(nil, function () print("hello world") end, nil, false)
     t:arm(5)              -- arm it at 5 seconds
     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
         -- loop is currently running, make it exit after the worker queue is empty and cancel any timers
         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")
generated by LDoc 1.4.2