You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: asyncio.run() works in sync tasks; sync-task calls from async must use .aio (#1472)
Two related changes to how sync tasks and sync-task calls behave.
## 1. `asyncio.run()` now works inside sync tasks
Sync task bodies used to run inside a hidden coroutine on a per-call
helper event loop (`run_sync_with_loop`, from #565). That loop was never
usable — `asyncio.run()` raised "cannot be called from a running event
loop" — it just made the thread look async. Sync bodies now run as plain
sync code on a dedicated daemon thread (`run_sync_in_thread`).
`asyncio.run()` and third-party sync wrappers (e.g. pydantic_ai
`run_sync`) work there now.
## 2. Calling a sync task from async code requires `.aio`
```python
@env.task
def child() -> str: ...
@env.task
async def parent() -> str:
return child() # raises SyncTaskCallInAsyncContextError
return await child.aio() # correct
```
A blocking call from async code parks the event loop that also runs the
controller failure watch. If the actions service goes down, the process
can never observe it and the pod hangs forever. It now raises
immediately with the fix in the message.
## What breaks
- **Blocking sync-task calls from async code** now raise. Migrate to
`await task.aio(...)`. (In-repo callers updated: one test, two
examples.)
- **Sync task bodies no longer see a running event loop** —
`asyncio.get_running_loop()` raises there. Nothing in-repo relied on it;
the agents plugin bridge doesn't depend on it (docstring updated).
- **`flyte._utils.asyncify.run_sync_with_loop` → `run_sync_in_thread`**
(private; papermill and docker_builder callers updated — plugin releases
should pair with this SDK version).
Sync → sync calls, `flyte.map`, sandbox orchestrators, and syncify-based
sync APIs (`download_sync`, `storage.*`, `flyte.run`) are unaffected:
they never used the caller's loop.
## Verified on a real cluster
Remote run on demo with this branch baked into the image:
https://demo.hosted.unionai.cloud/v2/domain/development/project/ketan/runs/ukf2hnfjhtrpvpbc4dqc
— sync body sees no running loop, `asyncio.run()` returns, sync→sync
blocking call works, `await leaf.aio()` from async works, and a blocking
call from an async body raises `SyncTaskCallInAsyncContextError` with
the `.aio` hint.
`tests/flyte/test_sync_tasks.py::test_sync_task_through_runtime_taskrunner`
covers the same through the real runtime entrypoint (local runs don't go
through taskrunner).
## Companion
`flyteplugins-union` is the only external importer of the renamed
helper: unionai/flyteplugins-union#100 adopts `run_sync_in_thread` with
an `ImportError` fallback, so it loads on both SDK versions.
## Tests
`tests/flyte/test_sync_call_guard.py` (raises from async, `.aio` ok,
sync→sync ok, end-to-end local run),
`tests/flyte/utils/test_asyncify.py` updated for the new semantics. Full
sweep: 4577 passed; 3 environment-dependent failures reproduce without
this diff.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Ketan Umare <kumare3@users.noreply.github.com>
Co-authored-by: Ketan Umare <kumare3@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
0 commit comments