Changelog

v0.26.4 (2026-05-21)

  • V0.26.4. [tobymao]

  • KeyError on concurrent same-key job attempts in job_task_contexts. [Timothy Redaelli]

    Job.hash is derived from .key, so two in-flight attempts of the same-key job (e.g. a sweep-driven retry of a job whose previous attempt is still winding down) share one slot in Worker.job_task_contexts. Whichever attempt’s finally pops the slot first leaves the other to raise KeyError at the completion check:

    File ".../saq/worker.py", line 374, in process
        if self.job_task_contexts[job]["aborted"] is None:
    KeyError: Job<... key: chat:1, attempts: 2, error: cancelled, status: active>
    

    Keep the dict keyed by Job (i.e. by .key), but hold a local reference to the JobTaskContext inside process() and read “aborted” through it. In the finally, only remove the slot if it still holds our ctx – a successor attempt’s slot is preserved. abort() is unchanged.

    Adds a regression test that reproduces the race by running two process() coroutines with same-key jobs and asserting both complete.

  • Allow redis-py 7.x (bump cap from <7.0 to <8.0) [taaacse4]

    redis-py 7.0.0 breaking changes (type annotations, RLock, removed parse_list_to_dict) do not affect SAQ’s usage of the redis library. SAQ uses redis.asyncio, pipelines, basic commands, and pub/sub — all unchanged in 7.x.

    Same analysis as the prior bump from <6.0 to <7.0 (cc8bef9).

    Closes #291

  • V0.26.3. [tobymao]

  • Pandas serde closes #287. [tobymao]

  • V0.26.2. [tobymao]

  • Again. [tobymao]

  • Fix again. [tobymao]

  • 3.9. [tobymao]

  • Support newer psycopg. [tobymao]

  • V0.26.1. [tobymao]

  • Remove signal handlers after stop. [Vikash]

  • Allow redis package version up to 6.x. [Malthe Jørgensen]

    Breaking changes and deprecations in 6.0.0 don’t affect how SAQ uses the redis library, so we can allow its use. Similarly the later available redis 6.x versions: 6.1.0, 6.1.1, 6.2.0, 6.3.0, 6.4.0 adhere to semantic versioning (double checked, because packages don’t always follow semantic versioning) and don’t have breaking changes nor deprecations.

    redis 6.x supports Python 3.9+ similarly to SAQ. Python 3.8 support was dropped in 6.2.0.

    Reference:

    • https://github.com/redis/redis-py/releases/tag/v6.0.0

    • https://github.com/redis/redis-py/tags

  • Style fix. [Simone Caldana]

  • Record worker_id in job metadata once a job is picked up. [Simone Caldana]

  • Try again. [tobymao]

  • Fix tests. [tobymao]

  • V0.26.0. [tobymao]

  • Global timeout for map function closes #270. [tobymao]

  • Chore: Avoid high cardinality job id in logger message. [ditsuke]

  • Do wait to cancel task in case of timeout. [ditsuke]

  • Avoid race in setting task status b/w process and abort. [ditsuke]

  • Tests: Small wait to let process() end. [ditsuke]

  • Chore: None for cancellation timeout (noop) [ditsuke]

  • Tests: Assert that .abort blocks until the task stops. [ditsuke]

  • Chore: Log on failure to cancel in time. [ditsuke]

  • Chore: Remove job-level completion allowance. [ditsuke]

  • Feat: Graceful shutdown and cancellation controls. [ditsuke]

  • Fix(tests): Bump wait for task to get picked. [ditsuke]

  • Test: Add testcase for shutdown hook order. [ditsuke]

  • Refactor: Remove AsyncExitStack. [ditsuke]

  • Avoid race in stop() [ditsuke]

  • Allow tasks to stop before cleanup. [ditsuke]

  • Py39 type compat. [ditsuke]

  • Type qualifiers. [ditsuke]

  • Chore: Remove redundant type alias. [ditsuke]

  • Chore: Improve worker context typing. [ditsuke]

  • Style: add generic for context. [euri10]

  • Docs: Complete and expand documentation for Tasks section. [Rakesh Bhatia]

  • Refactor: extract runner from main function. [Ivan Koldakov]

    Move the main execution logic into a separate run() function in saq.runner. It allows running the application programmatically without modifying sys.argv.

  • Set default port value. [Ivan Koldakov]

    Avoid passing None to start() as it violates the signature.

  • Remove all signal handlers for windows as they don’t work. [tobymao]

  • V0.25.2. [tobymao]

  • Feat: add optional polling to postgres. [tobymao]

  • Drop 3.8/add 3.13. [eakmanrq]

  • Chore: type ignore aiosignal 1.4.0. [eakmanrq]

  • V0.25.1. [tobymao]

  • Feat: add retry to http calls. [eakmanrq]

  • V0.25.0. [tobymao]

  • Add test. [tobymao]

  • Refactor!: remove pg locks for stability. [tobymao]

  • V0.24.13. [tobymao]

  • Handle case where connection pool closes connection. [eakmanrq]

  • V0.24.12. [tobymao]

  • Ensure there’s no race condition in between dequeue and processing for sweeper. [tobymao]

  • V0.24.11. [tobymao]

  • Formatting. [Haukur Páll]

  • Add poll_interval test. [Haukur Páll]

  • Add poll_interval example to readme. [Haukur Páll]

  • Formatting. [Haukur Páll]

  • Making the poll_interval docstring consistent betwen methods. [Haukur Páll]

  • Add poll_interval to apply, edit docstring. [Haukur Páll]

  • V0.24.10. [tobymao]

  • Ensure db status is source of truth for pg jobs. [tobymao]

  • V0.24.9. [Iaroslav Zeigerman]

  • Acquiring an advisory lock for a job as part of a predicate. [Iaroslav Zeigerman]

  • V0.24.8. [tobymao]

  • Ensure retried jobs are unlocked. [tobymao]

  • V0.24.7. [tobymao]

  • Ensure that we filter locked objects before limits so that we actually return jobs. [tobymao]

  • V0.24.6. [tobymao]

  • Race condition when refreshing and updating simultaneously, the update will be overriden mid task causing unexpected behavior. [tobymao]

  • V0.24.5. [tobymao]

  • Merge pull request #233 from eakmanrq/eakmanrq/add_done_callback_web_worker. [Toby Mao]

    fix: prevent zombie web worker