Repository navigation
Conversation
StaticCommandRunner._command awaited every command on the server's event
loop. Provider code does CPU-bound work in sync and async functions alike
(pandas strategy pricing, curve bootstraps over large daily files), and one
such command blocked every route, /widgets.json included, until it ended.
Each provider now gets one persistent event loop in its own daemon thread,
created on first use; the command runs there and the server awaits the
result. A slow provider blocks only itself. Provider state bound to a loop
(asyncio locks, cached aiohttp or SQLite backends) keeps working, because a
provider's commands always run on the same loop; a fresh loop per command
breaks such providers ("... is bound to a different event loop").
The provider comes from provider_choices, else the command's top-level
package (openbb_ prefix dropped), so a provider's standard-model and custom
router commands share a loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Could a maintainer approve the workflow runs for this PR? They're waiting for approval because it comes from a fork ( |
|
@dlee-bq, a gentle reminder on this one when you have a moment. It's ready for review and still merges cleanly into The workflow runs are still waiting for maintainer approval, because the PR comes from a fork. Locally, |
|
Hi @artcashin. Thanks for the PR, however I do not believe this is a viable solution because it defeats the Python interface's behavior that runs functions in a new event loop - effectively making concurrency not possible. In a process pool, the same load runs 3.5× faster, and light same-provider requests are served by free workers. This works with the REST API and the Python interface. |
Problem
StaticCommandRunner._commandawaits every command on the server's event loop. Provider code does CPU-bound work in sync and async functions alike, so one heavy command blocks every route until it finishes,/widgets.jsonincluded. Clients time out, health checks fail, and the API looks wedged. Examples we hit: CBOE options strategy pricing (pandas), CFTC curve bootstraps over whole DTCC day files, FFIEC peer-group averages.Change
Each provider gets one persistent event loop in its own daemon thread, created on first use (
_provider_loop)._commandruns the command there and awaits the result from the server loop.asyncio.runin a thread) breaks such providers: in openbb-cboe, openbb-deribit, openbb-nasdaq and openbb-tmx it raised "bound to a different event loop". It also leaked native memory per call in our tests.provider_choices, else the command's top-level package with theopenbb_prefix dropped, so a provider's standard-model and custom router commands share a loop.Tests
Four new tests in
tests/app/test_command_runner.py:provider_choicespicks the loop;All four fail without the change; the file passes 69/69 with it.
tests/appandtests/apipass: 1,401 tests and 1,213 subtests. The one failure,test_version::test_is_git_repo_true_for_repo_root, comes from running in a container against a git worktree and is unrelated.Results from a deployment
We've run this as a patched
command_runner.pyin OpenBQ's Docker image, calling all 995 widgets one at a time. The number of widgets during which/widgets.jsontook over 1 s fell from 34 (worst 88.7 s) to 7 (worst 2.3 s), with the same response statuses.Not included
Commands of the same provider still interleave on that provider's loop as before, so one provider's heavy command can still delay its own other commands. We serialize CBOE and CFTC commands in our deployment, but that's tuning, so it isn't in this PR.
🤖 Generated with Claude Code