6
0
mirror of https://github.com/FirebirdSQL/firebird-qa.git synced 2025-01-22 21:43:06 +01:00
firebird-qa/tests/bugs/core_6141_test.py

294 lines
11 KiB
Python

#coding:utf-8
#
# id: bugs.core_6141
# title: fbsvcmgr action_repair rpr_list_limbo_trans does not show list of transactions in LIMBO state
# decription:
# Test creates two databases with the same DDL (single table with single field): DBNAME_A, DBNAME_B.
# Then it makes instance of fdb.ConnectionGroup() for adding to it two connections (start distibuted work).
# First connection adds bulk of records, each in separate transaction. Second connection adds only one record.
# Number of separate transactions which are used for inserting records see in variable LIMBO_COUNT,
# and it must be not less then 150 (at least for the moment when this test is written: dec-2019).
# Then we change state of DBNAME_A to full shutdown, without doing commit or retain before this.
# Finally, we return this database state to online.
# Since that point header of DBNAME_A contains some data about limbo transactions.
# We make output of them using two ways: gfix -list and fbsvcmgr rpr_list_limbo_trans.
# Output should contain lines with ID of transactions in limbo state.
# NOTE: NOT ALL TRANSACTIONS CAN BE SHOWN BECAUSE THEIR LIST CAN BE EXTREMELY LONG.
# We count number of lines with limbo info using regexp and check that number of these lines equal to expected,
# ignoring concrete values of transaction IDs.
#
# NB-1.
# Output from gfix and fbsvcmgr differs, see pattern_for_limbo_in_gfix_output and pattern_for_limbo_in_fsvc_output.
#
# NB-2.
# Only 'gfix -list' produces output which final row is: "More limbo transactions than fit. Try again".
# No such message in the output of fbsvcmgr, it just show some Tx ID (last that it can display).
#
# Checked on:
# 4.0.0.1691 SS: 6.718s.
# 4.0.0.1691 CS: 6.532s.
# 3.0.5.33212 SS: 4.152s.
# 3.0.5.33212 CS: 5.770s.
#
# tracker_id: CORE-6141
# min_versions: ['3.0']
# versions: 3.0
# qmid: None
import pytest
import re
from firebird.qa import db_factory, python_act, Action, Database
from firebird.driver import tpb, Isolation, DistributedTransactionManager, ShutdownMode, \
ShutdownMethod
# version: 3.0
# resources: None
substitutions_1 = [('[ \t]+', ' ')]
init_script_1 = """
create table test(id int, x int, constraint test_pk primary key(id) using index test_pk) ;
"""
db_1_a = db_factory(sql_dialect=3, init=init_script_1, filename='core_6141_a.fdb')
db_1_b = db_factory(sql_dialect=3, init=init_script_1, filename='core_6141_b.fdb')
# test_script_1
#---
# import os
# import sys
# import time
# import subprocess
# import re
# from fdb import services
#
# os.environ["ISC_USER"] = user_name
# os.environ["ISC_PASSWORD"] = user_password
# db_conn.close()
#
# #--------------------------------------------
#
# def flush_and_close( file_handle ):
# # https://docs.python.org/2/library/os.html#os.fsync
# # If you're starting with a Python file object f,
# # first do f.flush(), and
# # then do os.fsync(f.fileno()), to ensure that all internal buffers associated with f are written to disk.
# global os
#
# file_handle.flush()
# if file_handle.mode not in ('r', 'rb') and file_handle.name != os.devnull:
# # otherwise: "OSError: [Errno 9] Bad file descriptor"!
# os.fsync(file_handle.fileno())
# file_handle.close()
#
# #--------------------------------------------
#
# def cleanup( f_names_list ):
# global os
# for f in f_names_list:
# if type(f) == file:
# del_name = f.name
# elif type(f) == str:
# del_name = f
# else:
# print('Unrecognized type of element:', f, ' - can not be treated as file.')
# del_name = None
#
# if del_name and os.path.isfile( del_name ):
# os.remove( del_name )
#
# #--------------------------------------------
#
#
# DBNAME_A = os.path.join(context['temp_directory'],'tmp_6141_a.fdb')
# DBNAME_B = os.path.join(context['temp_directory'],'tmp_6141_b.fdb')
#
# LIMBO_COUNT = 255
# cleanup( (DBNAME_A,DBNAME_B) )
#
# con1 = fdb.create_database( dsn = 'localhost:' + DBNAME_A)
# con2 = fdb.create_database( dsn = 'localhost:' + DBNAME_B)
# con1.execute_immediate( 'create table test(id int, x int, constraint test_pk primary key(id) using index test_pk)' )
# con1.commit()
#
# con2.execute_immediate( 'create table test(id int, x int, constraint test_pk primary key(id) using index test_pk)' )
# con2.commit()
#
# con1.close()
# con2.close()
#
# cgr = fdb.ConnectionGroup()
#
# con1 = fdb.connect( dsn = 'localhost:' + DBNAME_A)
# con2 = fdb.connect( dsn = 'localhost:' + DBNAME_B)
#
# cgr.add(con1)
# cgr.add(con2)
#
# # https://pythonhosted.org/fdb/reference.html#fdb.TPB
# # https://pythonhosted.org/fdb/reference.html#fdb.Connection.trans
#
# custom_tpb = fdb.TPB()
# custom_tpb.access_mode = fdb.isc_tpb_write
# custom_tpb.isolation_level = (fdb.isc_tpb_read_committed, fdb.isc_tpb_rec_version)
# custom_tpb.lock_resolution = fdb.isc_tpb_nowait
#
# tx1_list=[]
# for i in range(0, LIMBO_COUNT):
# tx1_list += [ con1.trans( default_tpb = custom_tpb ) ]
#
# cur_list=[]
# for i, tx1 in enumerate(tx1_list):
# tx1.begin()
# cur=tx1.cursor()
# cur.execute( "insert into test(id, x) values( ?, ? )", ( i, i*11111 ) )
# cur.close()
# tx1.prepare()
#
#
# tx2 = con2.trans( default_tpb = custom_tpb )
# cur2=tx2.cursor()
# cur2.execute( "insert into test(id, x) values( ?, ? )", (-2, -2222222) )
# cur2.close()
#
# tx2.prepare()
# tx2.commit()
#
# svc = services.connect(host='localhost', user=user_name, password=user_password)
# svc.shutdown( DBNAME_A, services.SHUT_FULL, services.SHUT_FORCE, 0)
#
# print('Database <DBNAME_A> is in full shutdown state now.')
#
# for tx1 in tx1_list:
# try:
# tx1.close()
# except:
# pass
#
# # Result for DBNAME_A when it will be returned online
# # and we query table TEST:
# # Statement failed, SQLSTATE = HY000
# # record from transaction <N> is stuck in limbo
# # See also "gfix -list <disk>\\path o\\dbname_a"
#
# cgr.clear()
# print('ConnectionGroup instance has been cleared.')
#
# svc.bring_online( DBNAME_A, services.SHUT_NORMAL )
# print('Database <DBNAME_A> has been brought ONLINE.')
#
# svc.close()
# con2.close()
#
# try:
# # This will fail for sure because DB state was changed to full shutdown
# con1.close()
# except:
# pass
#
# f_gfix_list_log=open( os.path.join(context['temp_directory'],'tmp_6141_gfix_list.log'), 'w', buffering = 0)
# subprocess.call( [ context['gfix_path'], '-list', DBNAME_A ], stdout=f_gfix_list_log, stderr=subprocess.STDOUT )
# flush_and_close( f_gfix_list_log )
#
#
# f_svc_list_log=open( os.path.join(context['temp_directory'],'tmp_6141_fbsvc_list.log'), 'w', buffering = 0)
# subprocess.call( [ context['fbsvcmgr_path'], 'localhost:service_mgr', 'action_repair', 'rpr_list_limbo_trans', 'dbname', DBNAME_A ], stdout=f_svc_list_log, stderr=subprocess.STDOUT )
# flush_and_close( f_svc_list_log )
#
# pattern_for_limbo_in_gfix_output=re.compile('Transaction\\s+\\d+\\s+.*limbo', re.IGNORECASE)
# pattern_for_limbo_in_fsvc_output=re.compile('Transaction\\s+in\\s+limbo:\\s+\\d+', re.IGNORECASE)
#
# for i,g in enumerate( (f_gfix_list_log, f_svc_list_log) ):
# lines_with_limbo_info=0
# log_name = 'gfix -list' if i== 0 else 'fbsvcmgr rpr_list_limbo_trans'
# pattern_for_match = pattern_for_limbo_in_gfix_output if i==0 else pattern_for_limbo_in_fsvc_output
# msg = "Number of lines related to limbo Tx in '%(log_name)s' output: " % locals()
# with open( g.name,'r') as f:
# for line in f:
# if pattern_for_match.search(line):
# lines_with_limbo_info += 1
# else:
# print( 'Additional output from ' + ('GFIX: ' if i==0 else 'FBSVCMGR: ') + line )
# print( msg + str(lines_with_limbo_info) )
#
# # CLEANUP
# #########
# time.sleep(1)
# cleanup( (DBNAME_A, DBNAME_B, f_gfix_list_log.name, f_svc_list_log.name) )
#
#
#---
act_1 = python_act('db_1_a', substitutions=substitutions_1)
expected_stdout_1 = """
Number of lines related to limbo Tx in 'gfix -list' output: 146
Number of lines related to limbo Tx in 'fbsvcmgr rpr_list_limbo_trans' output: 146
"""
LIMBO_COUNT = 255
@pytest.mark.version('>=3.0')
def test_1(act_1: Action, db_1_b: Database, capsys):
#pytest.skip('PROBLEM WITH TEST')
# On Firebird 4, the fbsvcmngr reports error:
# unavailable database
# gfix works fine, although the outpout is more verbose than original test expected
dt_list = []
custom_tpb = tpb(isolation=Isolation.READ_COMMITTED_RECORD_VERSION, lock_timeout=0)
with act_1.db.connect() as con1, db_1_b.connect() as con2:
for i in range(LIMBO_COUNT):
dt = DistributedTransactionManager([con1, con2], custom_tpb)
dt_list.append(dt)
cur1 = dt.cursor(con1)
cur1.execute("insert into test (id, x) values (?, ?)", [i, i * 11111])
cur1.close()
cur2 = dt.cursor(con2)
cur2.execute("insert into test (id, x) values (?, ?)", [-i, i * -2222])
cur2.close()
for dtc in dt_list:
# Initiate distributed commit: phase-1
dtc.prepare()
# Shut down the first database
with act_1.connect_server() as srv:
srv.database.shutdown(database=act_1.db.db_path, mode=ShutdownMode.FULL,
method=ShutdownMethod.FORCED, timeout=0)
#
while dt_list:
dtc = dt_list.pop()
dtc._tra = None # Needed hack to bypass commit and exception
dtc.close()
#
with act_1.connect_server() as srv:
srv.database.bring_online(database=act_1.db.db_path)
#
act_1.gfix(switches=['-list', act_1.db.dsn])
gfix_log = act_1.stdout
#
act_1.reset()
# Set EXPECTED_STDERR so we can get over "unavailable database" error and fail on assert
# Remove when svcmgr issue is resolved
act_1.expected_stderr = "We expect errors"
act_1.svcmgr(switches=['action_repair', 'rpr_list_limbo_trans', 'dbname', act_1.db.dsn])
mngr_log = act_1.stdout
#print(act_1.stderr)
#
pattern_for_gfix_output = re.compile('Transaction\\s+\\d+\\s+.*limbo', re.IGNORECASE)
pattern_for_fsvc_output = re.compile('Transaction\\s+in\\s+limbo:\\s+\\d+', re.IGNORECASE)
#
for log_name, limbo_log, pattern in [('gfix -list', gfix_log, pattern_for_gfix_output),
('fbsvcmgr rpr_list_limbo_trans', mngr_log, pattern_for_fsvc_output)]:
lines_with_limbo_info = 0
msg = f"Number of lines related to limbo Tx in '{log_name}' output: "
for line in limbo_log.splitlines():
if pattern.search(line):
lines_with_limbo_info += 1
#else:
#print(f'Additional output from {log_name}: {line}')
print(msg + str(lines_with_limbo_info))
# Check
act_1.reset()
act_1.expected_stdout = expected_stdout_1
act_1.stdout = capsys.readouterr().out
assert act_1.clean_stdout == act_1.clean_expected_stdout