Skip to content

refactor: adopt asyncio.TaskGroup for structured concurrency#909

Draft
vdusek wants to merge 3 commits into
drop-python-3.10from
feat/asyncio-task-group
Draft

refactor: adopt asyncio.TaskGroup for structured concurrency#909
vdusek wants to merge 3 commits into
drop-python-3.10from
feat/asyncio-task-group

Conversation

@vdusek
Copy link
Copy Markdown
Contributor

@vdusek vdusek commented May 26, 2026

Stacked on top of #908 — base will retarget to master automatically once #908 merges.

Adopts asyncio.TaskGroup (Python 3.11+) in four places:

  • ApifyRequestList._fetch_requests_from_url — structured task scope for concurrent remote-list fetches; errors aggregate as ExceptionGroup.
  • Actor.reboot pre-reboot dispatchasyncio.timeout(...) + TaskGroup, each listener wrapped in safe_dispatch so one failure no longer aborts siblings (preserves test_reboot_runs_all_listeners_even_when_one_fails).
  • Actor._save_actor_state — per-store persists fan out instead of running sequentially; per-task try/except keeps cleanup best-effort.
  • ApifyFileSystemKeyValueStoreClient.purgeunlink calls fan out instead of running sequentially.

Skipped: _apify_event_manager.py (Low Priority — #765 itself flags it as needing class restructuring) and Actor.__aenter__ (event/charging manager init) — the charging manager's __aenter__ sets a ContextVar that doesn't propagate out of a child task, so parallelizing silently breaks PPE charging.

Closes #765.

Switch from asyncio.gather to asyncio.TaskGroup in two places enabled by
the Python 3.11+ baseline:

- ApifyRequestList._fetch_requests_from_url: structured task scope so any
  fetch failure cancels siblings cleanly and aggregates errors in an
  ExceptionGroup instead of swallowing all but the first.
- Actor.reboot pre-reboot dispatch: combine asyncio.timeout + TaskGroup,
  with each listener wrapped to preserve the existing best-effort
  semantics (one failing listener does not abort the others).

Closes #765.
@vdusek vdusek added adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team. labels May 26, 2026
@vdusek vdusek self-assigned this May 26, 2026
@vdusek vdusek added adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team. labels May 26, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 26, 2026

Codecov Report

❌ Patch coverage is 93.10345% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.00%. Comparing base (b9578dc) to head (649272e).

Files with missing lines Patch % Lines
src/apify/_actor.py 90.00% 2 Missing ⚠️
Additional details and impacted files
@@                 Coverage Diff                  @@
##           drop-python-3.10     #909      +/-   ##
====================================================
- Coverage             87.05%   87.00%   -0.05%     
====================================================
  Files                    48       48              
  Lines                  2943     2956      +13     
====================================================
+ Hits                   2562     2572      +10     
- Misses                  381      384       +3     
Flag Coverage Δ
e2e 37.58% <0.00%> (-0.17%) ⬇️
integration 58.89% <24.13%> (-0.20%) ⬇️
unit 75.81% <93.10%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

vdusek added 2 commits May 26, 2026 16:08
`_save_actor_state` previously iterated `_use_state_stores` and awaited each `persist_autosaved_values`
call sequentially. The persists are independent — fan them out with `asyncio.TaskGroup` so cleanup
latency is bounded by the slowest store instead of summed across stores. A per-task try/except keeps
the cleanup best-effort: one failing store no longer prevents siblings from persisting.
`ApifyFileSystemKeyValueStoreClient.purge` previously awaited each `unlink` sequentially. The
`asyncio.to_thread` calls are independent — fan them out with `asyncio.TaskGroup` so purge latency
scales with the slowest deletion instead of the total file count.
@vdusek vdusek requested review from janbuchar and removed request for janbuchar May 26, 2026 14:23
@vdusek vdusek marked this pull request as draft May 26, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adhoc Ad-hoc unplanned task added during the sprint. t-tooling Issues with this label are in the ownership of the tooling team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants