🎓 A1Learner
🔍
0 of 49 lessons completed

Lua Error Recovery

Error recovery is what a program does after a failure so the job can still finish. It retries a temporary failure, uses a safe value when retries run out, or restarts from the last good state.

Error objects tell retryable failures from fatal ones

Two guarded calls return two kinds, and the code reads each kind.

local function fail(kind, message) error({kind = kind, message = message}, 0) end
local ok, err = pcall(fail, "retryable", "busy")
print(ok)
print(err.kind)
print(err.message)
local ok2, err2 = pcall(fail, "fatal", "missing")
print(err2.kind)

Retries run out, then the fallback value stands

Three tries fail, and the safe value stays in place.

local tries = 0
local function fetch()
  tries = tries + 1
  error({kind = "retryable", message = "busy"}, 0)
end
local value = "cached"
for attempt = 1, 3 do
  local ok, result = pcall(fetch)
  if ok then value = result break end
end
print(value)
print(tries)

A job resumes from its last good place

The failed item is tried again, and no finished item repeats.

local items = {"one", "two", "three"}
local busy = true
local done = {}
local at = 1
while at <= #items do
  local item = items[at]
  local ok, result = pcall(function()
    if busy and item == "two" then error({kind = "retryable", message = "busy"}, 0) end
    return item
  end)
  if ok then done[#done + 1] = result at = at + 1 else busy = false end
end
print(table.concat(done, ","))

Example

local tries = 0
local function fetch()
  tries = tries + 1
  if tries < 3 then error({kind = "retryable", message = "busy"}, 0) end
  return "data"
end
local value = "cached"
for attempt = 1, 3 do
  local ok, result = pcall(fetch)
  if ok then value = result break end
end
print(value)
print(tries)
local items = {"one", "two"}
local busy = true
local done = {}
local at = 1
while at <= #items do
  local item = items[at]
  local ok, result = pcall(function()
    if busy and item == "two" then error({kind = "retryable", message = "busy"}, 0) end
    return item
  end)
  if ok then done[#done + 1] = result at = at + 1 else busy = false end
end
print(table.concat(done, ","))
local ok, err = pcall(function() error({kind = "fatal", message = "missing"}, 0) end)
local outcome = "kept"
if not ok and err.kind == "fatal" then outcome = "stopped" end
print(outcome)

Key points

  • Retryable error: a temporary failure, like a busy resource, that may succeed on a later try.
  • Fatal error: a permanent failure, like a missing value, that retrying cannot fix.
  • Error object: a table carrying a kind and a message, so code can tell failures apart.
  • Fallback value: a safe value used when the real value cannot be produced.
  • Resume state: the last good result kept aside, so a job restarts from there instead of from the start.
  • Retry limit: a fixed number of tries, so a retryable failure cannot loop forever.

In this example, the fetch fails twice and succeeds on the third try, so the value is data. The job then hits a busy item, keeps its place, and finishes without repeating an item. The fatal error is told apart by its kind, so the job stops instead of retrying.

Note

pcall returns the error object itself when a function raises a table, so the kind survives the guarded call.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

An error object has kind fatal. What should recovery do?