CHORE: Add OneBranch release pipelines for mssql-python-odbc#664
CHORE: Add OneBranch release pipelines for mssql-python-odbc#664jahnvi480 wants to merge 33 commits into
Build #pr-validation-pipeline had test failures
Details
- Failed: 1 (0.00%)
- Passed: 28,234 (96.92%)
- Other: 895 (3.07%)
- Total: 29,130
- 6743 of 8343 line covered (80.82%)
Annotations
Check failure on line 123 in Build log
azure-pipelines / MSSQL-Python-PR-Validation
Build log #L123
Bash exited with code '1'.
Check failure on line 135 in Build log
azure-pipelines / MSSQL-Python-PR-Validation
Build log #L135
The task has timed out.
Check failure on line 2368 in Build log
azure-pipelines / MSSQL-Python-PR-Validation
Build log #L2368
Bash exited with code '1'.
Check failure on line 15 in Build log
azure-pipelines / MSSQL-Python-PR-Validation
Build log #L15
Bash exited with code '1'.
Check failure on line 1 in test_cleanup_connections_weakset_modification_during_iteration
azure-pipelines / MSSQL-Python-PR-Validation
test_cleanup_connections_weakset_modification_during_iteration
AssertionError: Test failed. stderr:
assert -11 == 0
+ where -11 = CompletedProcess(args=['/opt/venv/bin/python', '-c', '\nimport mssql_python\nimport weakref\nimport gc\n\nclass TestConnection:\n def __init__(self, conn_id):\n self.conn_id = conn_id\n self._closed = False\n\n def close(self):\n self._closed = True\n\n# Create connections with only weak references so they can be GC\'d easily\nweak_refs = []\nfor i in range(10):\n conn = TestConnection(i)\n mssql_python._register_connection(conn)\n weak_refs.append(weakref.ref(conn))\n # Don\'t keep strong reference - only weak_refs list has refs\n\ninitial_size = len(mssql_python._active_connections)\nprint(f"Initial WeakSet size: {initial_size}")\n\n# TEST 1: Demonstrate that direct iteration would be unsafe\n# (We can\'t actually do this in the real code, but we can show the principle)\nprint("TEST 1: Verifying list copy is necessary...")\n\n# Force some garbage collection\ngc.collect()\nafter_gc_size = len(mssql_python._active_connections)\nprint(f"WeakSet size after GC: {after_gc_size}")\n\n# TEST 2: Verify list copy allows safe iteration\nprint("TEST 2: Testing list copy creates stable snapshot...")\n\n# This is what _cleanup_connections does - creates a list copy\nwith mssql_python._connections_lock:\n connections_to_close = list(mssql_python._active_connections)\n\nsnapshot_size = len(connections_to_close)\nprint(f"Snapshot list size: {snapshot_size}")\n\n# Now cause more GC while we iterate the snapshot\ngc.collect()\n\n# Iterate the snapshot - this should work even though WeakSet may have changed\nprocessed = 0\nfor conn in connections_to_close:\n try:\n if hasattr(conn, "_closed") and not conn._closed:\n conn.close()\n processed += 1\n except Exception:\n # Connection may have been GC\'d, that\'s OK\n pass\n\nfinal_size = len(mssql_python._active_connections)\nprint(f"Final WeakSet size: {final_size}")\nprint(f"Processed {processed} connections from snapshot")\n\n# Key assertion: We could iterate the full snapshot even if WeakSet changed\nassert processed == snapshot_size, f"Should process all snapshot items: {processed} == {snapshot_size}"\n\nprint("WeakSet modification during iteration: PASSED")\nprint("[OK] list() copy prevents \'set changed size during iteration\' errors")\n'], returncode=-11, stdout='', stderr='').returncode
Raw output
self = <test_013_SqlHandle_free_shutdown.TestHandleFreeShutdown object at 0x5508161110>
conn_str = 'Server=172.17.0.4;Database=TestDB;Uid=SA;Pwd=Azure@123!;TrustServerCertificate=yes'
def test_cleanup_connections_weakset_modification_during_iteration(self, conn_str):
"""
Test that list copy prevents RuntimeError when WeakSet is modified during iteration.
This is a more aggressive test of the connections_to_close = list(_active_connections) line.
Validates that:
1. Without the list copy, iterating WeakSet directly would fail if modified
2. With the list copy, iteration is safe even if WeakSet shrinks due to GC
3. The pattern prevents "dictionary changed size during iteration" type errors
"""
script = textwrap.dedent(f"""
import mssql_python
import weakref
import gc
class TestConnection:
def __init__(self, conn_id):
self.conn_id = conn_id
self._closed = False
def close(self):
self._closed = True
# Create connections with only weak references so they can be GC'd easily
weak_refs = []
for i in range(10):
conn = TestConnection(i)
mssql_python._register_connection(conn)
weak_refs.append(weakref.ref(conn))
# Don't keep strong reference - only weak_refs list has refs
initial_size = len(mssql_python._active_connections)
print(f"Initial WeakSet size: {{initial_size}}")
# TEST 1: Demonstrate that direct iteration would be unsafe
# (We can't actually do this in the real code, but we can show the principle)
print("TEST 1: Verifying list copy is necessary...")
# Force some garbage collection
gc.collect()
after_gc_size = len(mssql_python._active_connections)
print(f"WeakSet size after GC: {{after_gc_size}}")
# TEST 2: Verify list copy allows safe iteration
print("TEST 2: Testing list copy creates stable snapshot...")
# This is what _cleanup_connections does - creates a list copy
with mssql_python._connections_lock:
connections_to_close = list(mssql_python._active_connections)
snapshot_size = len(connections_to_close)
print(f"Snapshot list size: {{snapshot_size}}")
# Now cause more GC while we iterate the snapshot
gc.collect()
# Iterate the snapshot - this should work even though WeakSet may have changed
processed = 0
for conn in connections_to_close:
try:
if hasattr(conn, "_closed") and not conn._closed:
conn.close()
processed += 1
except Exception:
# Connection may have been GC'd, that's OK
pass
final_size = len(mssql_python._active_connections)
print(f"Final WeakSet size: {{final_size}}")
print(f"Processed {{processed}} connections from snapshot")
# Key assertion: We could iterate the full snapshot even if WeakSet changed
assert processed == snapshot_size, f"Should process all snapshot items: {{processed}} == {{snapshot_size}}"
print("WeakSet modification during iteration: PASSED")
print("[OK] list() copy prevents 'set changed size during iteration' errors")
""")
result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
)
>
... [The stack trace has been truncated as it exceeded the maximum allowed size. Please refer to the complete log available in the Test Run attachments for full details.]