TrioExecutor shares the limiter with the underlying trio.to_thread.run_sync call, so more tokens are consumed than are intended.
Reproducible example:
import asyncio
import trio_asyncio
async def amain():
loop = asyncio.get_event_loop()
executor = trio_asyncio.TrioExecutor(max_workers=1)
await loop.run_in_executor(executor, print, "hello")
trio_asyncio.run(trio_asyncio.aio_as_trio(amain))
This call will always block, as TrioExecutor acquires the only token available and the inner trio.to_thread.run_sync cannot acquire its own token.