Skip to content

bug: workflow plugin log phase may fail when ctx._workflow_cache is nil #13479

@Ethan-Zhang-ZH

Description

@Ethan-Zhang-ZH

Current Behavior

After upgrading Apache APISIX from 3.10 to 3.15, we started seeing errors from the workflow plugin during the log phase:

failed to run log_by_lua*: /usr/local/apisix/apisix/plugins/workflow.lua:182: attempt to index field '_workflow_cache' (a nil value)
stack traceback:
        /usr/local/apisix/apisix/plugins/workflow.lua:182: in function 'phase_func'
        /usr/local/apisix/apisix/plugin.lua:1231: in function 'run_plugin'
        /usr/local/apisix/apisix/plugin.lua:1331: in function 'run_global_rules'
        /usr/local/apisix/apisix/init.lua:472: in function 'common_phase'
        /usr/local/apisix/apisix/init.lua:1064: in function 'http_log_phase'

The affected requests are mainly OPTIONS preflight requests.

Expected Behavior

The workflow plugin should not throw an error in the log phase when ctx._workflow_cache is not initialized.

Error Analysis

In APISIX 3.15, workflow.lua added a log phase handler introduced by:

The current logic initializes ctx._workflow_cache only in the access phase:

function _M.access(conf, ctx)
    ctx._workflow_cache = ctx._workflow_cache or {}
    ...
    ctx._workflow_cache[idx] = match_result
end

However, the log phase assumes the field always exists:

function _M.log(conf, ctx)
    for idx, rule in ipairs(conf.rules) do
        local match_result = ctx._workflow_cache[idx]
        ...
    end
end

If a request is finalized before workflow.access() runs, for example an OPTIONS request handled earlier by CORS or another plugin, the log phase can still execute while ctx._workflow_cache is nil.

Possible Fix

Add a nil guard at the beginning of workflow.log():

function _M.log(conf, ctx)
    if not ctx._workflow_cache then
        return
    end

    for idx, rule in ipairs(conf.rules) do
        local match_result = ctx._workflow_cache[idx]
        ...
    end
end

Environment

  • APISIX version: 3.15
  • Previous version without this issue: 3.10
  • Plugin involved: workflow
  • Request type: mainly OPTIONS preflight requests

Expected Behavior

No response

Error Logs

No response

Steps to Reproduce

  1. Start APISIX 3.15 with the workflow, cors, and limit-conn plugins enabled.

  2. Configure a global rule that uses the workflow plugin with a limit-conn action.

Example:

{
  "plugins": {
    "workflow": {
      "rules": [
        {
          "case": [
            ["http_method", "==", "GET"]
          ],
          "actions": [
            [
              "limit-conn",
              {
                "conn": 1,
                "burst": 0,
                "default_conn_delay": 0,
                "key": "remote_addr",
                "rejected_code": 503
              }
            ]
          ]
        }
      ]
    }
  }
}
  1. Configure a route with the cors plugin enabled so that OPTIONS preflight requests are handled before the access phase.

Example:

{
  "uri": "/test",
  "plugins": {
    "cors": {}
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "httpbin.org:80": 1
    }
  }
}
  1. Send an OPTIONS preflight request:
curl -i -X OPTIONS "http://127.0.0.1:9080/test" \
  -H "Origin: https://example.com" \
  -H "Access-Control-Request-Method: GET"
  1. Check the APISIX error log.

Actual result:
failed to run log_by_lua*: /usr/local/apisix/apisix/plugins/workflow.lua:182: attempt to index field '_workflow_cache' (a nil value)
Expected result:

APISIX should handle the request without logging an error. The workflow plugin should skip the log phase safely when ctx._workflow_cache was not initialized in the access phase.

The core trigger is: workflow.log() runs, but workflow.access() did not run for that request.

Environment

  • APISIX version (run apisix version):
  • Operating system (run uname -a):
  • OpenResty / Nginx version (run openresty -V or nginx -V):
  • etcd version, if relevant (run curl http://127.0.0.1:9090/v1/server_info):
  • APISIX Dashboard version, if relevant:
  • Plugin runner version, for issues related to plugin runners:
  • LuaRocks version, for installation issues (run luarocks --version):

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingplugin

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    📋 Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions