Take advantage of coroutines for a huge amount of flexibility when it comes to managing Elements' API functionality.
Coroutines are an integral part of cloud functions in Elements. Every invoked function (even in endpoints) is a coroutine, which allows for a tremendous amount of flexibility.
There are two ways to control coroutines in Elements:
-- The native Lua coroutine module
local coroutine = require "coroutine"
-- Helper class for gathering info and controlling
-- coroutines from outside of the current coroutine
local namazu_coroutine = require "namazu.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:
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