diff --git a/tests/bugs/gh_3218_test.py b/tests/bugs/gh_3218_test.py index 3f148ef9..1fef04ed 100644 --- a/tests/bugs/gh_3218_test.py +++ b/tests/bugs/gh_3218_test.py @@ -13,11 +13,20 @@ NOTES: [20.01.2024] pzotov Confirmed problem on 5.0.0.442: number of indexed reads was equal to the total count of records in rdb$relation_fields. 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 = """ set term ^; @@ -73,22 +82,41 @@ def test_1(act: Action, capsys): result_map = {} chk_sql = 'select 1 from sp_get_relations r join rdb$relation_fields f on f.rdb$relation_name = r.rdb$relation_name where r.rdb$relation_id < 10' - with cur.prepare(chk_sql) as ps: - cur.execute(chk_sql) + ps, rs = None, None + try: + ps = cur.prepare(chk_sql) + + # ::: 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) + tabstat1 = [ p for p in con.info.get_table_access_stats() if p.table_id == rf_rel_id ] cur.fetchall() tabstat2 = [ p for p in con.info.get_table_access_stats() if p.table_id == rf_rel_id ] print( '\n'.join([replace_leading(s) for s in ps.detailed_plan.split('\n')]) ) # explained plan, with preserving indents by replacing leading spaces with '#' - idx_reads = (tabstat2[0].indexed if tabstat2[0].indexed else 0) - if tabstat1: - idx_reads -= (tabstat1[0].indexed if tabstat1[0].indexed else 0) + idx_reads = (tabstat2[0].indexed if tabstat2[0].indexed else 0) + if tabstat1: + idx_reads -= (tabstat1[0].indexed if tabstat1[0].indexed else 0) - print('Result:') - if idx_reads == 0 or idx_reads > cnt_chk: - print(f'POOR! Number of records in rdb$relation_fields: 1) to be filtered: {cnt_chk}, 2) total: {cnt_all}. Number of indexed_reads: {idx_reads}') - else: - print('Acceptable.') + print('Result:') + if idx_reads == 0 or idx_reads > cnt_chk: + print(f'POOR! Number of records in rdb$relation_fields: 1) to be filtered: {cnt_chk}, 2) total: {cnt_all}. Number of indexed_reads: {idx_reads}') + else: + print('Acceptable.') + + except DatabaseError as e: + print( e.__str__() ) + print(e.gds_codes) + finally: + if rs: + rs.close() # <<< EXPLICITLY CLOSING CURSOR RESULTS + if ps: + ps.free() act.expected_stdout = """ Select Expression