diff --git a/tests/bugs/gh_7388_test.py b/tests/bugs/gh_7388_test.py index 7fa40747..89735d7e 100644 --- a/tests/bugs/gh_7388_test.py +++ b/tests/bugs/gh_7388_test.py @@ -13,11 +13,20 @@ NOTES: [20.01.2024] pzotov Confirmed problem on 5.0.0.871. Checked on 6.0.0.218, 5.0.1.1318. + + [18.01.2025] pzotov + Resultset of cursor that executes using instance of selectable PreparedStatement must be stored + in some variable in order to have ability close it EXPLICITLY (before PS will be freed). + Otherwise access violation raises during Python GC and pytest hangs at final point (does not return control to OS). + This occurs at least for: Python 3.11.2 / pytest: 7.4.4 / firebird.driver: 1.10.6 / Firebird.Qa: 0.19.3 + The reason of that was explained by Vlad, 26.10.24 17:42 ("oddities when use instances of selective statements"). """ -import pytest from pathlib import Path + +import pytest from firebird.qa import * +from firebird.driver import DatabaseError init_sql = """ create view v1 @@ -37,7 +46,7 @@ SUCCESS_MSG = "Expected: table statistics are identical." #---------------------------------------------------------- -def replace_leading(source, char="#"): +def replace_leading(source, char="."): stripped = source.lstrip() return char * (len(source) - len(stripped)) + stripped @@ -78,12 +87,21 @@ def test_1(act: Action, capsys): result_map = {} for qry_txt in q_map.keys(): - with cur.prepare(qry_txt) as ps: + ps, rs = None, None + try: + ps = cur.prepare(qry_txt) q_map[qry_txt] = ps.detailed_plan for tab_nm,tab_id in t_map.items(): tabstat1 = [ p for p in con.info.get_table_access_stats() if p.table_id == tab_id ] - cur.execute(qry_txt) - for r in cur: + + # ::: NB ::: 'ps' returns data, i.e. this is SELECTABLE expression. + # We have to store result of cur.execute() in order to + # close it explicitly. + # Otherwise AV can occur during Python garbage collection and this + # causes pytest to hang on its final point. + # Explained by hvlad, email 26.10.24 17:42 + rs = cur.execute(ps) + for r in rs: pass tabstat2 = [ p for p in con.info.get_table_access_stats() if p.table_id == tab_id ] @@ -98,6 +116,15 @@ def test_1(act: Action, capsys): idx -= (tabstat1[0].indexed if tabstat1[0].indexed else 0) result_map[qry_txt, tab_nm] = (seq, idx) + except DatabaseError as e: + print( e.__str__() ) + print(e.gds_codes) + finally: + if rs: + rs.close() # <<< EXPLICITLY CLOSING CURSOR RESULTS + if ps: + ps.free() + ''' print('q_map.items():') for k,v in q_map.items(): @@ -133,7 +160,8 @@ def test_1(act: Action, capsys): print(qry_txt) print('-' * 22) print('Plan:') - print( '\n'.join([replace_leading(s) for s in q_map[qry_txt].split('\n')]) ) # explained plan, with preserving indents by replacing leading spaces with '#' + # Show explained plan, with preserving indents by replacing leading spaces with '.': + print( '\n'.join([replace_leading(s) for s in q_map[qry_txt].split('\n')]) ) print('') else: print(SUCCESS_MSG)