Shared resources (connections, clients, caches) must be concurrency-safe across async tool invocations.
Standard
await asyncio.to_thread(...) / run_in_executor.create_task, you must either await/drain it (or guarantee the loop won’t stop before it runs) or log which resources were scheduled and why they may not complete.asyncio.run() from the wrong loop; prefer clearing/discarding pools when loop ownership would conflict.asyncio.Lock and perform an atomic swap of the fully built data so readers never observe partial state.contextvars (or otherwise ensure no process-global mutation without isolation).Practical patterns
map[key] = conn conn.validate() # another coroutine may grab conn while invalid
conn.validate() with lock: map[key] = conn
- **Offload blocking work**:
```py
# In async tool
result = await asyncio.to_thread(sync_fn, *args)
options = {SQL_ATTR_LOGIN_TIMEOUT: login_timeout_s}
# so reconnect can't hold a lock for OS TCP/TLS timeouts
# If loop may be stopping, log scheduling info and/or drain tasks.
for key, conn in snapshot:
coro = conn.close()
if isawaitable(coro):
tasks.append(asyncio.create_task(coro))
logger.warning(f"Scheduled close for {key}")
# Prefer: await asyncio.gather(*tasks, return_exceptions=True)
If you adopt these rules for every shared connection/client/cache and every async entrypoint, you eliminate the majority of concurrency defects shown in the discussions: race windows, lock starvation, event-loop pinning, silent leaks, and partially built shared state.