Skip to content

Conversation

@russellromney
Copy link

Description

Add anyio support to the Python bindings. Closes #4239

This PR includes a benchmark script for validation - before merge, it should be deleted in a final commit.

Summary

Migrate Python async bindings from asyncio to anyio for Trio compatibility.

  • Replace asyncio primitives with anyio equivalents (to_thread.run_sync)
  • Use threading.Event (not anyio.Event) to preserve sync-context initialization
  • Add abandon_on_cancel=True for clean task cancellation
  • Add internal timeout parameter on worker operations to work with anyio
  • Test on all three backends: asyncio, trio, asyncio+uvloop

All original behaviors preserved:

  • Dedicated worker thread per connection
  • Eager connection/cursor initialization in __init__
  • Fire-and-forget property setters
  • Pre-execution cancellation check

My goal here was to maintain existing behavior. However, I did add two things relevant to anyio semantics:

  1. Cancellation - allows the async caller to be unblocked immediately when cancelled (e.g. via anyio.move_on_after), even if the worker thread is still running. Original asyncio code: Caller blocks until worker completes. With anyio: Caller can abandon the wait, worker continues independently.
  2. Timeout - adds an optional internal timeout parameter on _run() for fine-grained control.
    Users wanting timeouts should use anyio.fail_after() or move_on_after() (documented in
    module docstring). The internal parameter exists for subclasses or edge cases.

Testing

  • all (84) tests pass across asyncio, trio, and asyncio+uvloop backends
  • added new tests to make sure the behavior is the same across more complex scenarios
  • changed testing dependency from pytest-asyncio to pytest-anyio
  • uvx ruff check passes
  • uvx ruff format --check passes

Of course this still needs to be tested with Turso server.

Performance Benchmark

Measured anyio overhead vs original asyncio implementation on Apple Silicon M3.

Results

Metric Original asyncio Anyio (asyncio) Anyio (uvloop)
Connect (hot) 0.238 ms 0.320 ms (+34%) 0.290 ms (+22%)
INSERT tiny 60.45 μs 103.65 μs (+71%) 93.54 μs (+55%)
SELECT 1 (pure overhead) 80.01 μs 172.30 μs (+115%) 145.94 μs (+82%)
Select 1KB row 90.87 μs 184.22 μs (+103%) 159.28 μs (+75%)
Select 100 rows 155.56 μs 238.73 μs (+53%) 218.47 μs (+40%)

Analysis

  • Overhead is constant (~90μs extra per operation), not proportional to data size
  • For real network I/O (10-100ms Turso round trips), this is <1% overhead
  • uvloop reduces overhead by ~15% for asyncio users who want better performance

Trio compatibility is worth the tradeoff in my opinion; local-only workloads can use uvloop if optimization is needed.

Reproducing

# Test anyio version
python benchmark_anyio.py

# Test original asyncio version

# make sure you're on this branch, then: 
git stash
git checkout 731b9c1ec -- turso/lib_aio.py turso/lib_sync_aio.py turso/worker.py
uv run python benchmark_anyio.py
git checkout HEAD -- turso/lib_aio.py turso/lib_sync_aio.py turso/worker.py
git stash pop

Why anyio? Justifying a depenency

Adding dependencies to any project must require a justification. Trio is well-loved for IO-bound concurrency. So mainly this PR expands the userbase of Turso to Python projects that depend on Trio.

Point Argument
Widely adopted FastAPI, Starlette, httpx all use it
Minimal Thin abstraction, ~1 dependency
Well-maintained Core Python async ecosystem
No runtime cost Just wraps existing primitives

Why to not merge this PR

I would totally understand not wanting to add a dependency to a lightweight bindings package. However, I think it is justified given anyio's fundamental role in the ecosystem, backward compatibility, and improvements in cancel-ability.

Description AI Usage

I used Claude Code with Opus 4.5 (with thinking) as an assistant in making a coding plan, figuring out how to maintain exact behavior with asyncio, writing a benchmark, and coding some of the actual changes. I also used it to generate more test cases and write the PR text.

Copy link

@turso-bot turso-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please review @pereman2

@PThorpe92
Copy link
Collaborator

Hey @russellromney, thanks for the PR!

I personally don't really have enough context about the python ecosystem and whether or not this is the direction we want to go in. If you are interested in discussing this with maintainers on discord here is an invite to the turso developers discord: https://discord.gg/yyWpsqAB I started a discussion in the #review channel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants