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