← Problem Management: investigate and prevent
09 / 12 · 70 MIN

Retained connections and cancellation

Reproduce a lease leak with asyncio tasks and SQLite connections, distinguishing acquisition, use and return.

Distinguish the waiting point

A timeout describes an exceeded deadline but does not independently identify the stage consuming it. The lab creates a synthetic SQLite table and three local connections leased through asyncio.Queue. Each operation acquires a connection, reads a known value and returns the lease. In the faulty version, an application exception after the query bypasses return. After three failures, no connection is free and the next attempt expires before calling the driver. Relevant evidence is the sequence across acquisition, query and exit, together with free and leased resources. Low CPU and no active SQL are compatible with this wait. They establish neither healthy service nor a table lock.

Normal exits do not cover cancellation

A test that only completes reads can pass even when code loses resources on abnormal exits. The guide uses one event to confirm acquisition and another to keep the task suspended. Only then does it cancel and await completion, observing CancelledError and pool state. In the corrected version, finally returns the connection and cancellation remains visible to the caller. Catching an exception and returning success would change the operation contract. Another variant enters finally protection too late: the task acquires, waits outside try and only then enters the protected region. Cancellation during that wait leaves a lease retained. Review must check protection placement, not merely the presence of the word finally.

Ownership, concurrency and exercise limits

Cancelling a task still waiting on queue.get does not give it a connection to return. The guide holds three leases in other tasks, cancels the waiting consumer and confirms accounting does not change incorrectly. In another group, eight tasks share three connections: three remain leased while an event prevents completion, and the others wait. After releasing the event, every task finishes and capacity returns. This observes bounded concurrency and reuse in this code. It does not measure eight physically parallel queries: sqlite3 is synchronous and tasks use one event loop. The short timeout terminates a negative test; it is neither an SLA nor a performance measurement. No WebSphere, JDBC or remote database runs here.

Transaction, lease and usable object

Accounting is necessary but insufficient. A free slot can contain an already closed connection and fail on the next query. The exercise reproduces that case and replaces the unusable resource before demonstrating another read. It also shows that leaving with connection in sqlite3 does not close the connection: that context manager handles transactions, while close has another responsibility. Do not transfer this behavior without reading the target driver contract. A library may return a proxy to its pool when the client calls close; this teaching pool uses an explicit return function. Finally, the guide terminates its tasks, closes connections it created and removes only its temporary directory. That cleanup does not constitute enterprise-pool acceptance.

python3 content/labs/problem-runtime/run.py --output /tmp/dr-problem-pool.json
# Teaching pattern: synchronous return with no intervening await.
# lease = await pool.acquire
# try:
# return await body(lease)
# finally:
# pool.release(lease)
IN PRACTICE

Three abnormal exits retain three connections; the next attempt waits in the pool and executes no new query.

Common pitfalls

Confusing resource waiting with slow SQL, protecting only the normal path or entering finally after a cancellable wait.

Related topics: Investigation and evidence · Workarounds and correction acceptance

Take this idea with you

A task that acquired a resource must return it on applicable exits; a task still waiting does not own that lease.

Create account

Reference: Effective Troubleshooting · Problem management practices 2026-09; ServiceNow Brazil examples with scoped plugins and properties