Skip to content

feat(core): optional per-provider deadline for commands - #7753

Open
artcashin wants to merge 1 commit into
openbq-org:developfrom
artcashin:feat/core-command-deadline
Open

artcashin wants to merge 1 commit into
openbq-org:developfrom
artcashin:feat/core-command-deadline

Conversation

@artcashin

@artcashin artcashin commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Problem

StaticCommandRunner._command awaits a command with no limit. If a command waits on an upstream that never answers, its request stays open until the client gives up, and nothing on the server ends the command.

Change

Two optional environment variables, read through Env like the other OPENBB_* settings:

  • OPENBB_COMMAND_TIMEOUT sets a deadline in seconds for every command.
  • OPENBB_COMMAND_TIMEOUT_<PROVIDER> overrides it for one provider. The provider comes from provider_choices, else from the command's top-level package with the openbb_ prefix dropped.

Unset, which is the default, commands run exactly as before. With a deadline, _command runs the command under asyncio.wait_for. Past the deadline, the command is cancelled at its next await and raises TimeoutError("The <provider> provider did not answer within <n> seconds."). A TimeoutError that the command raises itself passes through unchanged. The values are read once at import, so a value that is not a positive number stops the server at startup.

Why this is written against develop and not on top of #7749. #7749 runs each provider's commands on its own event loop. The deadline doesn't depend on it: on develop's runner, wait_for already ends any command that is waiting on I/O, which is the hang this PR addresses. What #7749 adds is timeliness for CPU-bound commands. Without it, such a command blocks the server loop, so its 504 can only go out once the command reaches an await. With #7749, the server loop stays free and the 504 goes out on time. The two PRs touch the same lines of _command. Whichever merges second needs a small rebase: the deadline then wraps the future returned from the provider's loop. I'll do that rebase.

Tests

  • tests/test_env.py: unset or empty gives no deadlines; a default plus a per-provider override parse correctly; abc, 0, -5 and nan raise ValueError naming the variable, for both the default and a per-provider name.
  • tests/app/test_command_runner.py: a command past its provider's 0.2 s deadline is cancelled (it sees CancelledError) and raises a TimeoutError naming the provider in under 5 s. A separate test checks that a command's own TimeoutError("EIA timed out") passes through unchanged.

All seven new tests fail without the change. The pass-through test is a guard; without the change it fails only because the deadline setting doesn't exist yet. The two files pass 75/75, and openbb_platform/core/tests passes 2,255 with 16 skipped (Python 3.12).

Results from a deployment

OpenBQ's Docker image runs this deadline on top of #7749's per-provider loops, with deployment-specific names: a 55 s default and 90 s for CFTC, whose forex swap_trades takes about 70 s. Here that is OPENBB_COMMAND_TIMEOUT=55 and OPENBB_COMMAND_TIMEOUT_CFTC=90.

  • In the deployment's tests, a 70 s command answered 504 at about 55.5 s, while /widgets.json answered every one-second poll in at most 0.03 s.
  • This branch, with OPENBB_COMMAND_TIMEOUT=55 and the same 70 s command:
    • Alone, it ends the command at 55.4 s with 500 Unexpected Error -> TimeoutError -> The slowfake provider did not answer within 55 seconds., and /widgets.json answers all 55 polls in at most 0.012 s.
    • Combined with fix(core): map missing inputs, timeouts and non-JSON responses to 4xx/5xx #7751, which maps TimeoutError to 504, the same request answers 504 at 55.3 s, and /widgets.json answers in at most 0.007 s.

Not included

🤖 Generated with Claude Code

A command that waits on an upstream which never answers holds its request
open until the client gives up; nothing on the server ends it.

OPENBB_COMMAND_TIMEOUT sets a deadline in seconds for every command and
OPENBB_COMMAND_TIMEOUT_<PROVIDER> overrides it for one provider. Unset, as
by default, commands run as before. Past the deadline the command is
cancelled at its next await and raises TimeoutError naming the provider; a
TimeoutError the command raises itself passes through unchanged. Values are
read once at import, so a value that is not a positive number stops the
server at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant