Accessing Long-Running Apache OpenWhisk Actions Results

Apache OpenWhisk actions are invoked by sending HTTP POST requests to the platform API. Invocation requests have two different modes: blocking and non-blocking.

Blocking invocations mean the platform won’t send the HTTP response until the action finishes. This allows it to include the action result in the response. Blocking invocations are used when you want to invoke an action and wait for the result.

$ wsk action invoke my_action --blocking
ok: invoked /_/my_action with id db70ef682fae4f8fb0ef682fae2f8fd5
{
    "activationId": "db70ef682fae4f8fb0ef682fae2f8fd5",
    ...
    "response": {
        "result": { ... },
        "status": "success",
        "success": true
    },
    ...
}

Non-blocking invocations return as soon as the platform processes the invocation request. This is before the action has finished executing. HTTP responses from non-blocking invocations only include activation identifiers, as the action result is not available.

$ wsk action invoke my_action
ok: invoked /_/my_action with id d2728aaa75394411b28aaa7539341195

HTTP responses from a blocking invocation will only wait for a limited amount of time before returning. This defaults to 65 seconds in the platform configuration file. If an action invocation has not finished before this timeout limit, a HTTP 5xx status response is returned.

Hmmm… 🤔

“So, how can you invoke an action and wait for the result when actions take longer than this limit?"

This question comes up regularly from developers building applications using the platform. I’ve decided to turn my answer into a blog post to help others struggling with this issue (after answering this question again this week 😎).

solution

  • Invoke the action using a non-blocking invocation.
  • Use the returned activation identifier to poll the activation result API.
  • The HTTP response for the activation result will return a HTTP 404 response until the action finishes.

When polling for activation results from non-blocking invocations, you should enforce a limit on the maximum polling time allowed. This is because HTTP 404s can be returned due to other scenarios (e.g. invalid activation identifiers). Enforcing a time limit ensures that, in the event of issues in the application code or the platform, the polling loop with eventually stop!

Setting the maximum polling time to the action timeout limit (plus a small offset) is a good approach.

An action cannot run for longer than its timeout limit. If the activation record is not available after this duration has elapsed (plus a small offset to handle internal platform delays), something has gone wrong. Continuing to poll after this point runs the risk of turning the polling operation into an infinite loop…

example code

This example provides an implementation of this approach for Node.js using the JavaScript Client SDK.

"use strict";

const openwhisk = require('openwhisk')

const options = { apihost: <API_HOST>, api_key: <API_KEY> }
const ow = openwhisk(options)

// action duration limit (+ small offset)
const timeout_ms = 85000
// delay between polling requests
const polling_delay = 1000
// action to invoke
const action = 'delay'

const now = () => (new Date().getTime())
const max_polling_time = now() + timeout_ms

const delay = async ms => new Promise(resolve => setTimeout(resolve, ms))

const activation = await ow.actions.invoke({name: action})
console.log(`new activation id: ${activation.activationId}`)

let result = null

do {
  try {
    result = await ow.activations.get({ name: activation.activationId })
    console.log(`activation result (${activation.activationId}) now available!`)
  } catch (err) {
    if (err.statusCode !== 404) {
      throw err
    }
    console.log(`activation result (${activation.activationId}) not available yet`)
  }

  await delay(polling_delay)
} while (!result && now() < max_polling_time) 

console.log(`activation result (${activation.activationId})`, result)

testing it out

Here is the source code for an action which will not return until 70 seconds have passed. Blocking invocations firing this action will result in a HTTP timeout before the response is returned.

const delay = async ms => new Promise(resolve => setTimeout(resolve, ms))

function main() {
  return delay(70*1000)
}

Using the script above, the action result will be retrieved from a non-blocking invocation.

  • Create an action from the source file in the example above.
wsk action create delay delay.js --timeout 80000 --kind nodejs:10
  • Run the Node.js script to invoke this action and poll for the activation result.
node script.js

If the script runs correctly, log messages will display the polling status and then the activation result.

$ node script.js
new activation id: d4efc4641b544320afc4641b54132066
activation result (d4efc4641b544320afc4641b54132066) not available yet
activation result (d4efc4641b544320afc4641b54132066) not available yet
activation result (d4efc4641b544320afc4641b54132066) not available yet
...
activation result (d4efc4641b544320afc4641b54132066) now available!
activation result (d4efc4641b544320afc4641b54132066) { ... }