LAP: Lua Asynchronous Protocol

Write libraries to work for either blocking or non blocking IO.

Lua has one of the coolest yet most underutilized asynchronous programming tools: the coroutine module, specifically coroutine.yield. Lua's yield can be called from any depth, resuming execution at the callsite upon coroutine.resume. This means that if we swap out traditionally blocking APIs like file:read with ones that are non-blocking and yielding (i.e. by running IO in a separate thread or using unix's aio interface) we use most libraries asynchronously without changing a single line of code.

For an example of implementing the LAP protocol see #Package_fd

LAP is a lightweight zero-dependency asynchronous protocol which aims to take advantage of Lua's awesomeness. It is architected to allow libraries to provide a lightweight "asynchronous mode" so that they can be used asynchronously by a coroutine executor. This allows users and library authors to write code that looks synchronous but which can be executed asynchronously at the application author's discression.

This folder also contains the lap.lua library, see the Library section. Library authors do not need to depend on this library to work with the LAP protocol.

The LAP protocol has two components:

Library authors can fully support the protocol by following the Yielding Protocol below and copy/pasting the following:

LAP_FNS_SYNC  = LAP_FNS_SYNC  or {}
LAP_FNS_ASYNC = LAP_FNS_ASYNC or {}

// register functions to switch modes, see end of lap.lua for example
table.insert(LAP_FNS_SYNC,  function() ... end)
table.insert(LAP_FNS_ASYNC, function() ... end)

// implement your asynchronous functions by following the protocol.

Library authors should make their default API synchronous (blocking) by default, except for items that cannot be used synchronously.

LAP_READY Global Table

LAP_READY is a global key/value table where the keys are the coroutines which should be run at some later time (by the executor). The values are arbitrary (typically a string identifier for debugging).

This means that a coroutine can schedule another coroutine cor by simply doing LAP_READY[cor] = "my_identifier". This simple feature can be used for many purposes such as creating Channel datastructures as well as handling any/all behavior. See the Library section for details.

Yielding Protocol

LAP's yielding protocol makes it trivial for Lua libraries to interface with executors. Libraries can simply call coroutine.yield with one of the following and a compliant executor will perform the behavior specified if it is supported (else it will run the coroutine on the next loop).

Global Variables

There are four global variables defined by the LAP protocol:

The sync/async tables allows a user to write code in a blocking style yet it can be run asynchronously, such as the following. You can even switch back and forth so that tests can be run in both modes.

function getLines(path, fn)
  local lines = {}
  -- File API can be configured to either block or yield.
  for line in io.lines(path) do
    table.insert(lines, line)
  end
  return lines
end

Recomendation: use #lap.async and #lap.sync to switch modes

Mod lap

Lua module importing the LAP protocol so that libraries can

support both blocking and non-blocking IO.

Types: Recv Send Any Lap

Functions

Record Recv

Create the receive side of a channel.

Fields:

Methods

Record Send

Sender, created through recv:sender()
This is considered closed if the receiver is closed. The receiver will automatically close if it is garbage collected.

Methods

Record Any

Fields: Methods

Record Lap

Default implementation of an executor, see civix.Lap for a more complete one.

"A single lap of the executor loop"

Example

  -- schedule your main fn, which may schedule other fns
  lap.schedule(myMainFn)

  -- create a Lap instance with the necessary configs
  local Lap = lap.Lap{
    sleepFn=civix.sleep, monoFn=civix.monoSecs, pollList=fd.PollList()
  }

  -- run repeatedly while there are coroutines to run
  while next(LAP_READY) do
    errors = Lap(); if errors then
      -- handle errors
    end
    -- do other things in your application's executor loop
  end

Fields:

Methods