fix(dashboard): run residual cron profile I/O off the event loop

Two async handlers still called the cron profile-walk helpers directly on
the FastAPI event loop after the 49fa04a23/346e5673d threadpool migration:

- POST /api/cron/fire called _find_cron_job_profile() inline — it walks
  every profile and lists its jobs (file I/O per profile), stalling the
  loop before the 202 is returned.
- POST /api/cron/blueprints/instantiate called _call_cron_for_profile()
  inline for create_job.

Route both through the existing _run_cron_dashboard_io threadpool wrapper
like every other cron dashboard endpoint.

Credit: @riceharvest (#50948) originally identified the sync-I/O-in-async-
handlers bug class for the desktop boot endpoints; 49fa04a23, 346e5673d,
7d0ddbb2f, d5eee133e and 24d5bda1e have since fixed most of that PR's scope
via the managed threadpool + PID cache + alias-map surfaces. This covers
the two cron handlers those merges missed.
This commit is contained in:
kshitijk4poor 2026-07-09 14:27:35 +05:30
parent f556edc10d
commit 74609f926c

View file

@ -10481,7 +10481,10 @@ async def cron_fire_webhook(request: Request):
if not job_id:
return JSONResponse({"error": "missing job_id"}, status_code=400)
profile = _find_cron_job_profile(job_id)
# _find_cron_job_profile walks every profile and lists its jobs (file
# I/O per profile) — run it off the event loop like the other cron
# dashboard endpoints.
profile = await _run_cron_dashboard_io(_find_cron_job_profile, job_id)
if not profile:
# Job is gone (cancelled / completed) — nothing to fire. 200 so NAS
# does not retry a fire that is intentionally absent.
@ -10556,7 +10559,11 @@ async def instantiate_blueprint(body: AutomationBlueprintInstantiate, profile: s
# Blueprint-created jobs deliver to the dashboard's configured target by
# default; the form's deliver slot overrides via spec["deliver"].
spec.pop("origin", None)
return _call_cron_for_profile(profile, "create_job", **spec)
# create_job does per-profile file I/O — keep it off the event loop
# like the sibling cron endpoints (partial avoids **spec keys ever
# colliding with the wrapper's own parameters).
_create = functools.partial(_call_cron_for_profile, profile, "create_job", **spec)
return await _run_cron_dashboard_io(_create)
except HTTPException:
raise
except Exception as e: