Coroutines

Coroutines are an integral part of cloud functions in Elements. Every invoked function (even in endpoints) is a coroutine. This allows for a tremendous amount of flexibility.

There are two ways to control coroutines in Elements

-- Helper class for gathering info and controlling 
-- coroutines from outside of the current coroutine

local namazu_coroutine = require "namazu.coroutine"

-- The native Lua coroutine module

local coroutine = require "coroutine"

coroutine

In this example, we can use the native coroutine module to yield the current coroutine with a variety of commands:

    /**
     * Indicates the coroutine should yield and 
     * be rescheduled as soon as absolutely possible.
     */
    IMMEDIATE,

    /**
     * Indicates that the coroutine should yield and be rescheduled until the
     * provided absolute time.  The time is expressed in the time since the
     * unix epoch: January 1, 1970.  The units may be specified, but the
     * default value is in seconds.
     */
    UNTIL_TIME,

    /**
     * Indicates that the should parse the second argument as a cron string.
     * The coroutine will resume when at the next time the cron expression
     * will hold true.
     */
    UNTIL_NEXT,

    /**
     * Indicates that the coroutine should yield for the specified amount of
     * time.  The units may be specified but the default is is in seconds.
     */
    FOR,

    /**
     * Indicates that the coroutine should yield indefinitely.  Typically this
     * means the the calling code will manually resume the coroutine at some
     * point later.  Note that under some circumstances, the container may opt
     * to forcibly kill the running Resource.
     */
    INDEFINITELY,

    /***
     * Indicates that the Resource should yield immediately just to
     * save the state.  This will not release the lock on the Resource
     * and will ensure that the contents are written to disk before returning.
     */
    COMMIT

Valid time units that can be used for yielding are as follows:

"NANOSECONDS"
"MICROSECONDS"
"MILLISECONDS"
"SECONDS"
"MINUTES"
"HOURS"
"DAYS"

for example:

    print "Yielding immediately"
    reason, elapsed, units = coroutine.yield("IMMEDIATE")
    print(reason, " yielded for ", elapsed, " ", units)

    print "Yielding for one second"
    reason, elapsed, units = coroutine.yield("FOR", 1, "SECONDS")
    print(reason, " yielded for ", elapsed, " ", units)

    time = os.time() + 1
    print("Yielding until ", time, " (in seconds)")
    reason, elapsed, units = coroutine.yield("UNTIL_TIME", time, "SECONDS")
    print(reason, " yielded for ", elapsed, " ", units)

    print("Yielding until cron next cron for \"* * * ? * *\" (once every second)")
    reason, elapsed, units = coroutine.yield("UNTIL_NEXT", "* * * ? * *")
    print(reason, " yielded for ", elapsed, " ", units)

namazu.coroutine

The namazu_coroutine provides three functions:

  • current_task_id()
  • start(coroutine)
  • resume(task_id)

Knowing the current task id is useful primarily for managing the state of the task externally. For example:

local test_coroutine = {}

local coroutine = require "coroutine"
local namazu_coroutine = require "namazu.coroutine"

local task_id = nil

function test_coroutine.block()

    task_id = namazu_coroutine.current_task_id()

    -- Yielding indefinitely will suspend the current task until resume 
    -- is called (see awake below)

    print ("Blocking coroutine " .. tostring(task_id) .. " indefinitely.")
    coroutine.yield("INDEFINITELY")

    print ("Got wake signal.  Exiting.")
    return "OK"

end

function test_coroutine.awake()

    while task_id == nil do
        print("No corresponding task.  Waiting ...")
        coroutine.yield("FOR", 1, "SECONDS")
    end

    print ("Waking up " .. tostring(task_id))
    namazu_coroutine.resume(task_id)

    return "OK"

end

return test_coroutine

Sometimes, you might want to run several tasks asynchronously. To do this, you can create any number of other coroutines. Take this endpoint code for example:

local index = require ("namazu.index")
local resource = require ("namazu.resource")
local auth = require ("namazu.elements.auth")
local namazu_response = require ("namazu.response")

local test_endpoints = {}

function test_endpoint.update_resources(payload, request, session)

    -- In this example, this is an authenticated request, so we can use auth
    -- to get the profile info of the requestor.

    local profile = auth.profile()
    local profile_id = profile.id

    local update_info = payload

    -- This would be the path to all of the resources pertaining to the current
    -- profile id of the user making this request.
    -- Wildcards are useful for searching the entire contents of a directory.

    local resources_path = string.format("resources/%s/*", profile_id)  
    local resource_listings = index.list(path_pattern)
    local active_tasks = 0

    for path, resource_id in pairs(resource_listings) do

        -- Creates a coroutine to update each resource asynchronously

        local co = coroutine.create(function ()

            -- Coroutines accept upvalues, which can be very useful!

            active_tasks = active_tasks + 1

            local result, code = resource.invoke(resource_id, "update_resource", update_info)

            active_tasks = active_tasks - 1

        end)

        namazu_coroutine.start(co)
    end

    while(active_tasks > 0) do
        coroutine.yield("IMMEDIATE")
    end

    -- Just return okay for the purpose of this example

    return namazu_response.formulate(200)
end

return test_endpoints