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:
- 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.
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.
- 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:
- 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).
- In
AutoImport.close(), iterate over and close all registered connections across all threads.
- Optionally expose a
close_thread_connection() method to clean up resources when a worker thread terminates.
Component:
rope.contrib.autoimport.sqlite.AutoImportDescribe the bug
1. Root Cause in
ropeIn
rope.contrib.autoimport.sqlite.AutoImport:Why it leaks:
autoimport.connection(such as background indexing inrolsor thread pools) dynamically creates a distinctsqlite3.Connectionstored inside that thread'sself.thread_local.close()only sees the calling thread:AutoImportdoes not maintain a central registry of connections created across threads. Whenclose()is invoked (normally on the main/server thread),self.connectionresolves to the calling thread's connection.AutoImportdynamically instantiates per-thread SQLite connections usingself.thread_local = threading.local(). However,AutoImportdoes not track these connections centrally. WhenAutoImport.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 inResourceWarning: unclosed databaseupon garbage collection.To Reproduce
Run the complete
ropetest suite. Warnings happen.Proposed upstream fix:
AutoImportinstance (e.g., aweakref.WeakSetor asetguarded by athreading.Lock).AutoImport.close(), iterate over and close all registered connections across all threads.close_thread_connection()method to clean up resources when a worker thread terminates.