Skip to content

AutoImport.close() leaks SQLite connections created in worker threads (ResourceWarning: unclosed database) #886

Description

@mcepl

Component:

rope.contrib.autoimport.sqlite.AutoImport

Describe the bug

1. Root Cause in rope

In rope.contrib.autoimport.sqlite.AutoImport:

# rope/contrib/autoimport/sqlite.py
def __init__(self, ...):
    self.thread_local = local()  # threading.local()
    ...

@property
def connection(self) -> sqlite3.Connection:
    """Creates a new connection if called from a new thread."""
    if not hasattr(self.thread_local, "connection"):
        self.thread_local.connection = self.create_database_connection(...)
    return self.thread_local.connection

def close(self):
    """Close the autoimport database."""
    self.connection.commit()
    self.connection.close()

Why it leaks:

  1. Thread-local isolation: Any worker thread accessing autoimport.connection (such as background indexing in rols or thread pools) dynamically creates a distinct sqlite3.Connection stored inside that thread's self.thread_local.
  2. close() only sees the calling thread: AutoImport does not maintain a central registry of connections created across threads. When close() is invoked (normally on the main/server thread), self.connection resolves to the calling thread's connection.
  3. Orphaned worker connections: The connections allocated inside worker threads are never closed. When the worker thread terminates or Python's garbage collector sweeps the objects, Python 3.12+ emits:
ResourceWarning: unclosed database in <sqlite3.Connection object at ...>

AutoImport dynamically instantiates per-thread SQLite connections using self.thread_local = threading.local(). However, AutoImport does not track these connections centrally. When AutoImport.close() is called from the main thread, it only commits and closes the connection belonging to that thread. Any connection created by worker threads is never closed explicitly, resulting in ResourceWarning: unclosed database upon garbage collection.

To Reproduce

Run the complete rope test suite. Warnings happen.

Proposed upstream fix:

  1. Maintain a thread-safe registry of all active connections on the AutoImport instance (e.g., a weakref.WeakSet or a set guarded by a threading.Lock).
  2. In AutoImport.close(), iterate over and close all registered connections across all threads.
  3. Optionally expose a close_thread_connection() method to clean up resources when a worker thread terminates.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugUnexpected or incorrect user-visible behavior

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions