Skip to content

luci-base (ucode): dispatcher.uc opens ubus at import time, so forked uhttpd workers share one ubus socket and concurrent requests hang #9091

Open

Description

@sla-te

Summary

With uhttpd.main.max_requests > 1, several concurrent requests to /cgi-bin/luci/... hang until the client times out. Each uhttpd worker stays idle (no CPU) inside controller/admin/index.uc. With max_requests=1 the same client works reliably.

Likely cause

  • /usr/share/ucode/luci/uhttpd.uc imports luci.dispatcher when uhttpd loads the ucode handler (-O), which happens in the main uhttpd process.
  • modules/luci-base/ucode/dispatcher.uc line 16: let ubus = connect(); runs at module scope, so the ubus connection is created once in the parent.
  • uhttpd forks one worker per request. All workers inherit the same ubus socket. On the router, every worker held the same two socket inodes as the parent.
  • My hypothesis: concurrent workers on one ubus socket consume each other's replies, and the remaining workers wait forever. I haven't traced this at the ubus protocol level.

Environment

  • OpenWrt 25.x snapshot, aarch64_cortex-a76
  • luci-base 26.183.16146~6aacb73
  • uhttpd / uhttpd-mod-ucode / uhttpd-mod-ubus 2026.06.16~7b1bec45-r1
  • ucode + ucode-mod-ubus 2026.01.16~85922056-r1
  • ubus / ubusd 2026.06.2824864e78-r2, rpcd 2026.06.0428faf640-r1

Reproduction

  1. Set max_requests to 20: uci set uhttpd.main.max_requests=20; uci commit uhttpd; /etc/init.d/uhttpd restart.
  2. Log in from a client that sends several requests to the LuCI dispatcher in parallel. I used the LuCI Mobile Android app, which opens about 8 parallel HTTPS connections right after login.
  3. The login succeeds: logread shows dispatcher.uc: luci: accepted login on / for root.
  4. The follow-up requests get no response. 8-11 uhttpd workers pile up with load near 0 and disappear only when the client disconnects 15-30 s later.
  5. The same ubus methods called from a shell (ubus call luci-rpc getNetworkDevices, system board, network.interface dump, ...) return instantly.
  6. With max_requests=1 the requests run one after another, and the client works.

Suggested fix

Connect to ubus lazily, per worker: for example, inside dispatch(), or on first use after the fork, instead of at module scope. The same pattern may apply to let uci = cursor(); on the next line.

Related

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions