Skip to content

Resoling comments

9f644b1
Select commit
Loading
Failed to load commit list.
Open

FIX: ODBC Catalog method fetchone() issue #520

Resoling comments
9f644b1
Select commit
Loading
Failed to load commit list.
Azure Pipelines / MSSQL-Python-PR-Validation succeeded Apr 21, 2026 in 30m 16s

Build #pr-validation-pipeline had test failures

Details

Tests

  • Failed: 1 (0.00%)
  • Passed: 28,538 (97.26%)
  • Other: 803 (2.74%)
  • Total: 29,342
Code coverage

  • 6672 of 8434 lines covered (79.11%)

Annotations

Check failure on line 1 in test_aggressive_dbc_segfault_reproduction

See this annotation in the file changed.

@azure-pipelines azure-pipelines / MSSQL-Python-PR-Validation

test_aggressive_dbc_segfault_reproduction

subprocess.TimeoutExpired: Command '['/Library/Frameworks/Python.framework/Versions/3.13/bin/python', '-c', '\nimport sys\nimport gc\nfrom mssql_python import connect\n\nprint("=== AGGRESSIVE DBC SEGFAULT TEST ===")\nprint("Creating many DBC handles and forcing shutdown...")\n\n# Create many connections without closing them\n# This maximizes the chance of DBC handles being finalized\n# AFTER the static ENV handle has destructed\nconnections = []\nfor i in range(5):  # Reduced for faster execution\n    conn = connect("Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=Azure@123!;TrustServerCertificate=yes")\n    # Don\'t even create cursors - just DBC handles\n    connections.append(conn)\n\nprint(f"Created {len(connections)} DBC handles")\nprint("Forcing GC to ensure objects are tracked...")\ngc.collect()\n\n# Delete the list but objects are still alive in GC\ndel connections\n\nprint("WARNING: About to exit with unclosed DBC handles")\nprint("If Type 2 (DBC) handles are not protected, this may SEGFAULT")\nprint("Stack trace will show: SQLFreeHandle -> SqlHandle::free() -> finalize_garbage")\n\n# Force immediate exit - this triggers finalize_garbage\nsys.exit(0)\n']' timed out after 5 seconds
Raw output
self = <test_013_SqlHandle_free_shutdown.TestHandleFreeShutdown object at 0x108f12210>
conn_str = 'Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=Azure@123!;TrustServerCertificate=yes'

    def test_aggressive_dbc_segfault_reproduction(self, conn_str):
        """
        AGGRESSIVE TEST: Try to reproduce DBC handle segfault during shutdown.
    
        This test aggressively attempts to trigger the segfault described in the stack trace
        by creating many DBC handles and forcing Python to shut down while they're still alive.
    
        Current vulnerability: DBC handles (Type 2) are NOT protected during shutdown,
        so they will call SQLFreeHandle during finalization, potentially accessing
        the already-destructed static ENV handle.
    
        Expected with CURRENT CODE: May segfault (this is the bug we're testing for)
        Expected with FIXED CODE: No segfault
        """
        script = textwrap.dedent(f"""
            import sys
            import gc
            from mssql_python import connect
    
            print("=== AGGRESSIVE DBC SEGFAULT TEST ===")
            print("Creating many DBC handles and forcing shutdown...")
    
            # Create many connections without closing them
            # This maximizes the chance of DBC handles being finalized
            # AFTER the static ENV handle has destructed
            connections = []
            for i in range(5):  # Reduced for faster execution
                conn = connect("{conn_str}")
                # Don't even create cursors - just DBC handles
                connections.append(conn)
    
            print(f"Created {{len(connections)}} DBC handles")
            print("Forcing GC to ensure objects are tracked...")
            gc.collect()
    
            # Delete the list but objects are still alive in GC
            del connections
    
            print("WARNING: About to exit with unclosed DBC handles")
            print("If Type 2 (DBC) handles are not protected, this may SEGFAULT")
            print("Stack trace will show: SQLFreeHandle -> SqlHandle::free() -> finalize_garbage")
    
            # Force immediate exit - this triggers finalize_garbage
            sys.exit(0)
        """)
    
>       result = subprocess.run(
            [sys.executable, "-c", script], capture_output=True, text=True, timeout=5
        )

tests/test_013_SqlHandle_free_shutdown.py:87: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/subprocess.py:556: in run
    stdout, stderr = process.communicate(input, timeout=timeout)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/subprocess.py:1222: in communicate
    stdout, stderr = self._communicate(input, endtime, timeout)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/subprocess.py:2155: in _communicate
    self._check_timeout(endtime, orig_timeout, stdout, stderr)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Popen: returncode: -9 args: ['/Library/Frameworks/Python.framework/Versions...>
endtime = 1995.637093599, orig_timeout = 5, stdout_seq = [], stderr_seq = []
skip_check_and_raise = False

    def _check_timeout(self, endtime, orig_timeout, stdout_seq, stderr_seq,
                       skip_check_and_raise=False):
        """Convenience for checking if a timeout has expired."""
        if endtime is None:
            return
        if skip_check_and_raise or _time() > endtime:
>           raise TimeoutExpired(
                    self.args, orig_timeout,
                    output=b''.join(stdout_seq) if stdout_seq else None,
                    stderr=b''.join(stderr_seq) if stderr_seq else None)
E           subprocess.TimeoutExpired: Command '['/Library/Frameworks/Python.framework/Versi