Class Models for Lua

# Thread Scheduler

## `loop.thread.Scheduler`

Class of objects that provides a scheduling policy for management of a collection of co-routines that represent independent threads of execution. This class also provides some basic operations for synchronization of the scheduled threads. This class is useful for implementation of multi-threading support in Lua applications.

Each class maintains two lists of threads, one for the ones ready for execution (i.e. running) and another for those suspended for some time (i.e. sleeping). The scheduling policy is round-robin, resuming all threads ready for running one after the other. When a thread finishes it is removed from the scheduler and an optional trap function is called to handle its last results or any error raised during its execution. The scheduler also automatically moves threads from the list of sleeping threads to the running list when their sleeping time are finished.

## Behavior

### Initialization

`Scheduler([object])`
  Creates a new instance using the table `object` adding to it all proper data strucutures used to store the lists of running and sleeping threads and the set of optional trap functions of each scheduled thread. Additionally, it also initialize the internal state of the new scheduler instance. If no `object` is provided then a new table is used.

### Fields

`current`
  Co-routine of the thread that currenly holds the right to execute. This co-routine is not always the result of `coroutine.running()` because in some circustances other internal co-routines related to it are acctually executing. That is the case when a thread executes the `pcall` function.
`pcall`
  Function that behaves like `pcall` function of the Lua base library but allows calls of `coroutine.yield()`.
`traps`
  Table that maps registered co-routine to its trap function. A trap function is a function called when a co-routine finishes. The trap function receives a boolean value indicating if the call ended normally or not, i.e. due to an error raised. As additional argument, the trap function receives the results of the co-routine. However, if the co-routine ended with an error, the error message is the only additional parameter of the trap function.

### Methods

`error(coroutine, error)`
  Method used to handle the error message `error` of co-routine `coroutine`. By default, this method raises an ordinary Lua error with message `error`. If the `debug.traceback` function is available at the time this class is first required, then the error message raised contains the original stack of the error produced. This method may be redefined for produce better error messages.
`halt()`
  Method used to stop the scheduling loop performed by method `run`. The scheduling then may be resumed by future calls to `step` or `run`.
`idle([timeout])`
  Method called when there are no running threads available for scheduling for until the moment specified by `timeout`. If no `timeout` is available, then the this method should return immediatly. By default this method execute a busy waiting at least until the moment `timeout` is reached. It is strongly recommended that such implementation be replaced by a more efficient one, like the following code in Linux environments:

      funtion scheduler:idle(timeout)
        if timeout then
          os.execute("sleep "..(timeout - self:time()))
        end
      end

`register(coroutine)`
  Register a co-routine to be scheduled for execution. This method simply inserts the co-routine `co-routine` at the end of the running list of the scheduler and return `true`. However, if the co-routine is already registered then this method has no effect and returns no value.
`remove(coroutine)`
  Removes a co-routine from the scheduler, therefore it is no more scheduled for execution. This method simply removes the co-routine `co-routine` from the lists of the scheduler (i.e. running or sleeping) and return the removed co-routine plus the time it was scheduled to be waken if it was sleeping. However, if the co-routine is not registered then this method has no effect and returns no value.
`resume(coroutine, ...)`
  This method can only be called inside a scheduled co-routine. It immediatetly switches the execution to the co-routine `coroutine`. The calling co-routine is suspended until the next resuming cycle (see method `step()`). All its extra arguments are returned by the operation that suspended the co-routine `coroutine`. Such suspending operations are the methods `start`, `resume`, `suspend` and the function `coroutine.yield` of the Lua Base Library. If the resumed co-routine is already scheduled for execution then its execution is advanced in the running thread list but it is not scheduled again until the next resuming cycle. If the resumed co-routine was already executed in the current resuming cycle, then it is executed once more in the same cycle. This operation simulates an API for symetric co-routines using the asymetric co-routines of Lua.
`run([timeout])`
  Starts to schedule registered co-routines until the moment given by `timeout` is reached by performing continuous resuming cycles (see method `step()`). If no `timeout` is provided, it schedules all co-routines until they all finish. If there are no registered co-routines, the call has no effect.
`start(function, ...)`
  This method can only be called inside a scheduled co-routine. It creates a new co-routine for execution of function `function` with the arguments provided and immediatetly starts it. The calling co-routine is suspended until the next resuming cycle (see method `step()`).
`step()`
  Performs a resuming cycle that consists of executing each scheduled co-routine ready for execution (i.e. running) at least once. Since, each co-routine is responsible to notify the moment for execution switch, a resuming cycle may last for an arbitrary long time, even forever. Additionally, a co-routine may be resumed more than once if resumed by method `resume`. Specifically, two co-routines may alternate execution forever inside a single resuming cycle if they resume each other mutually indefinetly. In such case, no other co-routine is resumed.
`suspend([time])`
  This method can only be called inside a scheduled co-routine. It suspends the execution of the current co-routine for at least `time` seconds. If no `time` is provided, the co-routine is suspended until it is registered again in the scheduler by operation `register` or resumed by method `resume`.
`time()`
  Method used to get the number of seconds counted by the system. This counting may be relative to any point in the past. This method is used to check the current moment of the system and therefore percieve time slapses. To define relative timouts use the formula `scheduler:time() + seconds`. By default, this method is based in the `os.time` function of Lua and therefore has only precision of seconds. To use the scheduler with a higher precision, redefine this method.

## Remarks

- This class can be used as an instance of itself, therefore all methods can be executed over the class itself.
- Since standard Lua does not provide operations to suspend execution for some time, the behavior of method `idle` is to perform a busy-waiting until the time specifield. This is extremelly undesired in time-shared systems. Therefore, it is strongly recommended to replace this implementation for a more efficient one. For example, using the `socket.sleep` function provided by LuaSocket package.

      function scheduler:idle(timeout)
        socket.sleep(timeout - self:time())
      end

- This class provides a verbose message manager (instance of `Verbose`) in field `verbose` that offers the following flags that can be activated to generate messages about the internal actions performed by the scheduler.

  `concurrency`
    Group of all message flags described below.
  `copcall`
    Messages describing the actions related to the management of the co-routine-compatiable pcall implementation.
  `threads`
    Messages describing the actions related to the management of scheduled threads, e.g. resuming, suspending, registration, etc.
  `scheduler`
    Messages describing the actions related to the internal control of the scheduling mechanism, e.g. resuming cycles, idle time, etc.
  The verbose messages are very useful to understand the behavior of the scheduler as well as for debugging multi-threaded applications, however it is very expensive for some performance-critical applications. In order to remove the verbose support and improve performance, remove all the code after the occurences of `--[[VERBOSE]]` in the implementation of this class. An easy way to turn the verbose support on or off is to replace all occourences of "`--[[VERBOSE]]`" by "`-- [[VERBOSE]]`" and vice-versa.

## Examples

### Light control system

    local scheduler = require "loop.thread.Scheduler"

    function control(room)
      local c = 0
      while true do
        if room.light and not room.presence then
          c = c + 1
          if c > 5 then
            c = 0
            room.light = false
          else
            scheduler:suspend(1)
          end
        else
          c = 0
          scheduler:suspend(6)
        end
      end
    end

    function person(rooms)
      local room = 0
      while true do
        room = 1 + (room + math.random(#rooms-1)) % #rooms
        rooms[room].presence = true
        rooms[room].light = true
        scheduler:suspend(math.random(6))
        rooms[room].presence = false
      end
    end

    function printer(rooms)
      while true do
        for _, room in ipairs(rooms) do
          io.write("  ", room.presence and "*" or room.light and "." or  " ")
        end
        print()
        scheduler:suspend(1)
      end
    end

    scheduler:register(coroutine.create(function()
      local rooms = {}
      for i = 1, 9 do rooms[i] = {} end

      scheduler:start(printer, rooms)
      scheduler:start(person, rooms)
      scheduler:suspend(60)

      print "starting light control ..."
      for _, room in ipairs(rooms) do
        scheduler:start(control, room)
      end
    end))

    scheduler:run()

Copyright (C) 2004-2008 Tecgraf, PUC-Rio

This project is currently being maintained by Tecgraf at PUC-Rio.