mirror of
https://github.com/FirebirdSQL/firebird-qa.git
synced 2025-01-23 05:53:06 +01:00
720 lines
28 KiB
Python
720 lines
28 KiB
Python
#coding:utf-8
|
|
#
|
|
# id: bugs.core_5087
|
|
# title: Database shutdown can cause server crash if multiple attachments run EXECUTE STATEMENT
|
|
# decription:
|
|
# Test makes two copies of current DB file (and closes db_conn after this):
|
|
# 1) to <db_dml_sessions> - for use by launching ISQL sessions which will perform "heavy DML" (insert rows in the table with wide-key index);
|
|
# 2) to <db_verify_alive> - for use to verify that FB is alive after we change state of <db_dml_sessions> while ISQL sessions running.
|
|
#
|
|
# Then we establish connect to <db_verify_alive> database (see 'att_chk').
|
|
# If FB is alive after DML and shutdown then we have to see alive connection to <db_verify_alive>.
|
|
# If FB works as SuperServer/SuperClassic and crashed then this connect will be lost and test fails with Python exception.
|
|
# If FB works as Classic then this connect will alive but we can check firebird.log for presense of phrases which testify about crash:
|
|
# "code attempted to access" and/or "terminate(d) abnormally". We analyze the DIFFERENCE file between old and new firebird.log for this.
|
|
#
|
|
# After this, we establish connection to <db_dml_sessions> (see 'att1') and make INSERT statement to special table 't_lock' which has PK-field.
|
|
# Futher, we launch <PLANNED_DML_ATTACHMENTS> ISQL sessions which also must insert the same (duplicated) key into this table - and they hang.
|
|
# Table 't_lock' serves as "semaphore": when we close attachment 'att1' then all running ISQL sessions will immediately do their DML job.
|
|
#
|
|
# We check that all required number connections of ISQLs present by loop in which we query mon$attachments table and count records from it.
|
|
# When result of this count will be equal to <PLANNED_DML_ATTACHMENTS> then we release lock from T_LOCK table by closing 'att1'.
|
|
#
|
|
# Futher, we allow ISQL sessions to perform their job by waiting for several seconds and then run command to change DB state to full shutdown.
|
|
# This command works without returning to caller until all activity in the DB will stop.
|
|
#
|
|
# When control will return here, we check that attachment 'att_chk' is alive. Also, we get difference of FB log content and parse by searching
|
|
# phrases which can relate to FB crash.
|
|
#
|
|
# We also do additional check: all ISQL sessions should be disconnected with writing to logs appropriate messages about shutdown.
|
|
#
|
|
# Confirmed crash on 3.0.0.31374 Beta1 (Classic).
|
|
# Hanged on 3.0.0.31896 Beta2 and 3.0.0.32136 RC-1 (no return from SHUTDOWN process).
|
|
# Parameter 'min_version' was changed to 2.5.9: now is PASSES for normal time (it seems that issues described in CORE-5106 was actually fixed).
|
|
# Checked 20.08.2020 on:
|
|
# 4.0.0.2170 SS: 13.911s.
|
|
# 4.0.0.2170 CS: 15.915s.
|
|
# 3.0.7.33356 SS: 12.210s.
|
|
# 3.0.7.33356 CS: 15.004s.
|
|
# 2.5.9.27152 SS: 12.690s.
|
|
# 2.5.9.27152 CS: 11.550s.
|
|
#
|
|
# OLD COMMENT ABOUT 2.5:
|
|
# On 2.5 no crash but works VERY slow, even for 5 attachments (checked on WI-V2.5.6.26970, 255 seconds!).
|
|
# Setting min_version = 2.6.6 is deferred again until CORE-5106 will not be fixed.
|
|
#
|
|
# tracker_id: CORE-5087
|
|
# min_versions: ['2.5.9']
|
|
# versions: 2.5.9
|
|
# qmid: None
|
|
|
|
from __future__ import annotations
|
|
from typing import List
|
|
import pytest
|
|
import subprocess
|
|
import time
|
|
import re
|
|
from difflib import unified_diff
|
|
from pathlib import Path
|
|
from firebird.qa import db_factory, python_act, Action, Database, temp_files, temp_file
|
|
from firebird.driver import ShutdownMode, ShutdownMethod
|
|
|
|
# version: 2.5.9
|
|
# resources: None
|
|
|
|
substitutions_1 = []
|
|
|
|
init_script_1 = """
|
|
create sequence g;
|
|
recreate table test(
|
|
id int,
|
|
s varchar(500) unique,
|
|
att bigint default current_connection
|
|
);
|
|
recreate table log4attach(
|
|
att bigint default current_connection
|
|
,dts timestamp default 'now'
|
|
,process varchar(255)
|
|
,protocol varchar(255)
|
|
,address varchar(255)
|
|
);
|
|
commit;
|
|
|
|
set term ^;
|
|
execute block as
|
|
begin
|
|
rdb$set_context('USER_SESSION','INITIAL_DDL','1');
|
|
end
|
|
^
|
|
|
|
create or alter trigger trg_attach active on connect position 0 as
|
|
begin
|
|
if ( rdb$get_context('USER_SESSION','INITIAL_DDL') is null )
|
|
then
|
|
in autonomous transaction do
|
|
insert into log4attach(process,protocol, address)
|
|
values( rdb$get_context('SYSTEM', 'CLIENT_PROCESS')
|
|
,rdb$get_context('SYSTEM', 'NETWORK_PROTOCOL')
|
|
,rdb$get_context('SYSTEM', 'CLIENT_ADDRESS')
|
|
);
|
|
end
|
|
^ -- trg_attach
|
|
set term ;^
|
|
commit;
|
|
|
|
create index test_att on test(att);
|
|
create index test_id on test(id);
|
|
create index log4attach_att on log4attach(att);
|
|
commit;
|
|
"""
|
|
|
|
db_verify_alive_1 = db_factory(sql_dialect=3, init=init_script_1, filename='tmp_5087_chk4alive.fdb')
|
|
db_dml_sessions_1 = db_factory(sql_dialect=3, init=init_script_1, filename='tmp_5087_dml_heavy.fdb')
|
|
|
|
# test_script_1
|
|
#---
|
|
# import os
|
|
# import sys
|
|
# import subprocess
|
|
# from subprocess import Popen
|
|
# import time
|
|
# import datetime
|
|
# import shutil
|
|
# import difflib
|
|
# import re
|
|
# import fdb
|
|
#
|
|
# PLANNED_DML_ATTACHMENTS = 30
|
|
#
|
|
# #-----------------------------------
|
|
#
|
|
# 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()
|
|
# os.fsync(file_handle.fileno())
|
|
#
|
|
# file_handle.close()
|
|
#
|
|
# #--------------------------------------------
|
|
#
|
|
# def cleanup( f_names_list ):
|
|
# global os
|
|
# for i in range(len( f_names_list )):
|
|
# if os.path.isfile( f_names_list[i]):
|
|
# os.remove( f_names_list[i] )
|
|
# if os.path.isfile( f_names_list[i]):
|
|
# print('ERROR: can not remove file ' + f_names_list[i])
|
|
#
|
|
# #---------------------------------------------
|
|
#
|
|
# def svc_get_fb_log( engine, f_fb_log ):
|
|
#
|
|
# global subprocess
|
|
#
|
|
# if engine.startswith('2.5'):
|
|
# get_firebird_log_key='action_get_ib_log'
|
|
# else:
|
|
# get_firebird_log_key='action_get_fb_log'
|
|
#
|
|
# subprocess.call([ context['fbsvcmgr_path'],
|
|
# "localhost:service_mgr",
|
|
# get_firebird_log_key
|
|
# ],
|
|
# stdout=f_fb_log, stderr=subprocess.STDOUT
|
|
# )
|
|
#
|
|
# return
|
|
#
|
|
# #--------------------------------------------
|
|
#
|
|
# os.environ["ISC_USER"] = user_name
|
|
# os.environ["ISC_PASSWORD"] = user_password
|
|
#
|
|
# db_file=db_conn.database_name
|
|
# engine = str(db_conn.engine_version)
|
|
#
|
|
# db_conn.close()
|
|
#
|
|
# db_verify_alive="$(DATABASE_LOCATION)tmp_5087_chk4alive.fdb"
|
|
# db_dml_sessions="$(DATABASE_LOCATION)tmp_5087_dml_heavy.fdb"
|
|
#
|
|
#
|
|
# # Copy database to another file in order to make connect to this copy
|
|
# # and check that this connection is alive after we'll do DML and shutdown
|
|
# # with source database (i.e. with "bugs.core_5087.fdb"):
|
|
# shutil.copy2( db_file, db_verify_alive )
|
|
# shutil.copy2( db_file, db_dml_sessions )
|
|
#
|
|
#
|
|
# f_chk_log = open( os.path.join(context['temp_directory'],'tmp_chk_5087.log'), 'w', buffering=0)
|
|
#
|
|
# att_chk = fdb.connect(dsn='localhost:'+db_verify_alive) # This leads to adding 1 row into table 'log4attach'
|
|
# att_chk.execute_immediate('delete from log4attach where att<>current_connection')
|
|
# att_chk.commit()
|
|
#
|
|
# att_chk.begin()
|
|
# cur_chk = att_chk.cursor()
|
|
#
|
|
# cur_chk.execute("select 'check_point_1: established connection to <db_verify_alive>' as msg from rdb$database")
|
|
# f_chk_log.seek(0,os.SEEK_END)
|
|
# for row in cur_chk:
|
|
# f_chk_log.write(row[0])
|
|
#
|
|
# att_chk.commit()
|
|
#
|
|
# att1 = fdb.connect(dsn='localhost:' + db_dml_sessions)
|
|
# att2 = fdb.connect(dsn='localhost:' + db_dml_sessions)
|
|
#
|
|
# att1.execute_immediate('recreate table t_lock(id int primary key)')
|
|
# att1.commit()
|
|
# att1.execute_immediate('insert into t_lock(id) values(1)')
|
|
#
|
|
# # do NOT: att1.close() -- it will be done only after all ISQL sessions will establish their attachments.
|
|
#
|
|
# f_fblog_before=open( os.path.join(context['temp_directory'],'tmp_5087_fblog_before.txt'), 'w', buffering=0)
|
|
# svc_get_fb_log( engine, f_fblog_before )
|
|
# f_fblog_before.close()
|
|
#
|
|
# sql_dml='''
|
|
# commit;
|
|
# set transaction wait;
|
|
# set term ^;
|
|
# execute block as
|
|
# begin
|
|
# insert into t_lock(id) values(1);
|
|
# when any do
|
|
# begin
|
|
# -- nop --
|
|
# end
|
|
# end
|
|
# ^
|
|
# set term ;^
|
|
# commit;
|
|
#
|
|
# set bail on;
|
|
#
|
|
# set transaction read committed;
|
|
# set term ^;
|
|
# execute block as
|
|
# declare n_limit int = 100000;
|
|
# declare s_length smallint;
|
|
# begin
|
|
# select ff.rdb$field_length
|
|
# from rdb$fields ff
|
|
# join rdb$relation_fields rf on ff.rdb$field_name = rf.rdb$field_source
|
|
# where rf.rdb$relation_name=upper('test') and rf.rdb$field_name=upper('s')
|
|
# into s_length;
|
|
#
|
|
# while (n_limit > 0) do
|
|
# begin
|
|
# execute statement ('insert into test(id, s) values( ?, ?)')
|
|
# ( gen_id(g,1), rpad('', :s_length, uuid_to_char(gen_uuid())) )
|
|
# with autonomous transaction
|
|
# ;
|
|
#
|
|
# n_limit = n_limit - 1;
|
|
# end
|
|
# insert into test( id ) values( -current_connection );
|
|
# end
|
|
# ^
|
|
# set term ;^
|
|
# commit;
|
|
# '''
|
|
#
|
|
# f_dml_sql = open( os.path.join(context['temp_directory'],'tmp_dml_5087.sql'), 'w')
|
|
# f_dml_sql.write(sql_dml)
|
|
# flush_and_close( f_dml_sql )
|
|
#
|
|
# f_list = []
|
|
# p_list = []
|
|
#
|
|
# ########################################################
|
|
# # Launching dozen of child ISQL processes with doing ES
|
|
# ########################################################
|
|
#
|
|
# for i in range(0, PLANNED_DML_ATTACHMENTS):
|
|
# sqllog=open( os.path.join(context['temp_directory'],'tmp_dml_5087_'+str(i)+'.log'), 'w')
|
|
# f_list.append(sqllog)
|
|
#
|
|
# for i in range(len(f_list)):
|
|
# ###########################################
|
|
# # a s y n c h r o n o u s l a u n c h
|
|
# ###########################################
|
|
# p_isql=Popen( [ context['isql_path'], 'localhost:' + db_dml_sessions, "-i", f_dml_sql.name ],
|
|
# stdout=f_list[i],
|
|
# stderr=subprocess.STDOUT
|
|
# )
|
|
# p_list.append(p_isql)
|
|
#
|
|
# cur2 = att2.cursor()
|
|
# while True:
|
|
# att2.begin()
|
|
# cur2.execute("select count(*) from mon$attachments a where a.mon$attachment_id<>current_connection and a.mon$remote_process containing 'isql'")
|
|
# for r in cur2:
|
|
# established_dml_attachments = r[0]
|
|
# att2.commit()
|
|
#
|
|
# if established_dml_attachments < PLANNED_DML_ATTACHMENTS:
|
|
# # do not delete, leave it for debug:
|
|
# ####################################
|
|
# #msg = datetime.datetime.now().strftime("%H:%M:%S.%f") + ' LOOP: only %(established_dml_attachments)s attachments exist of planned %(PLANNED_DML_ATTACHMENTS)s' % locals()
|
|
# #print( msg )
|
|
# #f_chk_log.write('\\n')
|
|
# #f_chk_log.write(msg)
|
|
# time.sleep(0.2)
|
|
# continue
|
|
# else:
|
|
# # do not delete, leave it for debug:
|
|
# ####################################
|
|
# #msg = datetime.datetime.now().strftime("%H:%M:%S.%f") + ' Found all planned %(PLANNED_DML_ATTACHMENTS)s attachments. Can release lock.' % locals()
|
|
# msg = 'Found all attachments of planned number to <db_dml_sessions>. No we can release lock for allow them to start DML.'
|
|
# #print( msg )
|
|
# f_chk_log.write('\\n\\n')
|
|
# f_chk_log.write(msg)
|
|
# break
|
|
#
|
|
# cur2.close()
|
|
# att2.close()
|
|
#
|
|
# #-----------------------------------
|
|
#
|
|
# # release t_lock record.
|
|
# # All launched ISQL sessions can do useful job (INSERT statements) since this point.
|
|
# att1.close()
|
|
#
|
|
# # Let ISQL sessions do some work:
|
|
# time.sleep( 3 )
|
|
#
|
|
# cur_chk.execute("select 'check_point_2: before shutdown <db_dml_sessions>' as msg from rdb$database")
|
|
# f_chk_log.seek(0,os.SEEK_END)
|
|
# for row in cur_chk:
|
|
# f_chk_log.write('\\n')
|
|
# f_chk_log.write(row[0])
|
|
#
|
|
# f_shutdown_log = open( os.path.join(context['temp_directory'],'tmp_shutdown_5087.log'), 'w', buffering=0)
|
|
#
|
|
# ####################################
|
|
# # S H U T D O W N D A T A B A S E
|
|
# ####################################
|
|
#
|
|
# subprocess.call( [context['fbsvcmgr_path'],"localhost:service_mgr",
|
|
# "action_properties",
|
|
# "dbname", db_dml_sessions,
|
|
# "prp_shutdown_mode", "prp_sm_full", "prp_force_shutdown", "0"
|
|
# ],
|
|
# stdout=f_shutdown_log,
|
|
# stderr=subprocess.STDOUT
|
|
# )
|
|
# flush_and_close( f_shutdown_log )
|
|
#
|
|
# cur_chk.execute("select 'check_point_3: after shutdown <db_dml_sessions>' as msg from rdb$database")
|
|
# f_chk_log.seek(0,os.SEEK_END)
|
|
# for row in cur_chk:
|
|
# f_chk_log.write('\\n')
|
|
# f_chk_log.write(row[0])
|
|
#
|
|
# #------------------------------------------
|
|
# for i in range(len(f_list)):
|
|
# flush_and_close( f_list[i] )
|
|
#
|
|
# for i in range(len(p_list)):
|
|
# p_list[i].terminate()
|
|
#
|
|
# #------------------------------------------
|
|
#
|
|
# cur_chk.execute("select 'check_point_4: killed all DML sessions.' as msg from rdb$database")
|
|
# f_chk_log.seek(0,os.SEEK_END)
|
|
# for row in cur_chk:
|
|
# f_chk_log.write('\\n')
|
|
# f_chk_log.write(row[0])
|
|
#
|
|
# # Here we must wait a little because firebird.log will get new messages NOT instantly.
|
|
# time.sleep(3)
|
|
#
|
|
# crashes_in_worker_logs = 0
|
|
# for i in range(len(f_list)):
|
|
# dml_worker_log=open(f_list[i].name).read()
|
|
# if 'SQLSTATE = 08004' in dml_worker_log: #### do NOT add >>> or 'SQLSTATE = 08006' in dml_worker_log:
|
|
# crashes_in_worker_logs += 1
|
|
#
|
|
# f_fblog_after=open( os.path.join(context['temp_directory'],'tmp_5087_fblog_after.txt'), 'w', buffering=0)
|
|
# svc_get_fb_log( engine, f_fblog_after )
|
|
# flush_and_close( f_fblog_after )
|
|
#
|
|
# att_chk.begin()
|
|
# cur_chk.execute("select 'check_point_5: get firebird.log' as msg from rdb$database")
|
|
# f_chk_log.seek(0,os.SEEK_END)
|
|
# for row in cur_chk:
|
|
# f_chk_log.write('\\n')
|
|
# f_chk_log.write(row[0])
|
|
#
|
|
# cur_chk.execute('select count(distinct att) from log4attach;')
|
|
# for row in cur_chk:
|
|
# f_chk_log.write('\\n')
|
|
# f_chk_log.write('distinct attachments to <db_verify_alive>: '+str(row[0])) # must be 1
|
|
#
|
|
# cur_chk.close()
|
|
# att_chk.close()
|
|
#
|
|
# #os.remove(db_verify_alive)
|
|
#
|
|
# #------------------------------------------
|
|
#
|
|
# f_chk_log.seek(0,os.SEEK_END)
|
|
# f_chk_log.write('\\n')
|
|
# f_chk_log.write('Found crash messages in DML worker logs: %(crashes_in_worker_logs)s' % locals() ) # must be 0.
|
|
#
|
|
# sql_chk='''
|
|
# set list on;
|
|
# select
|
|
# iif( count(distinct t.att) = %(PLANNED_DML_ATTACHMENTS)s
|
|
# ,'YES'
|
|
# ,'NO. Only ' || count(distinct t.att) || ' attachments of planned %(PLANNED_DML_ATTACHMENTS)s established.' )
|
|
# as "All DML sessions found in DB ?"
|
|
# from rdb$database r
|
|
# left join (
|
|
# select
|
|
# att
|
|
# ,count( iif(id >=0, id, null) ) as cnt_in_autonom_tx
|
|
# ,count( iif(id < 0, id, null) ) as cnt_in_common_tx
|
|
# from test
|
|
# group by att
|
|
# ) t on t.cnt_in_common_tx = 0 and t.cnt_in_autonom_tx > 0
|
|
# ;
|
|
# ''' % locals()
|
|
#
|
|
# f_chk_sql = open( os.path.join(context['temp_directory'],'tmp_chk_5087.sql'), 'w', buffering=0)
|
|
# f_chk_sql.write(sql_chk)
|
|
# f_chk_sql.close()
|
|
#
|
|
# f_chk_log.seek(0,os.SEEK_END)
|
|
#
|
|
# f_chk_log.write('\\n')
|
|
# f_chk_log.write('Point before bring DML database online.' )
|
|
#
|
|
# subprocess.call( [context['fbsvcmgr_path'], "localhost:service_mgr",
|
|
# "action_properties", "prp_db_online",
|
|
# "dbname", db_dml_sessions,
|
|
# ]
|
|
# )
|
|
#
|
|
# f_chk_log.write('\\n')
|
|
# f_chk_log.write('Point after bring DML database online.' )
|
|
#
|
|
# subprocess.call( [ context['isql_path'], "localhost:"+db_dml_sessions, "-nod", "-i", f_chk_sql.name ],
|
|
# stdout=f_chk_log,
|
|
# stderr=subprocess.STDOUT
|
|
# )
|
|
# flush_and_close( f_chk_log )
|
|
#
|
|
# #-------------------------------------------
|
|
#
|
|
# # Now we can compare two versions of firebird.log and check their difference.
|
|
#
|
|
# oldfb=open(f_fblog_before.name, 'r')
|
|
# newfb=open(f_fblog_after.name, 'r')
|
|
#
|
|
# difftext = ''.join(difflib.unified_diff(
|
|
# oldfb.readlines(),
|
|
# newfb.readlines()
|
|
# ))
|
|
# oldfb.close()
|
|
# newfb.close()
|
|
#
|
|
# f_diff_txt=open( os.path.join(context['temp_directory'],'tmp_5087_diff.txt'), 'w')
|
|
# f_diff_txt.write(difftext)
|
|
# flush_and_close( f_diff_txt )
|
|
#
|
|
# allowed_patterns = (
|
|
# re.compile('code attempt\\S+.*\\s+access',re.IGNORECASE),
|
|
# re.compile('terminate\\S+ abnormally',re.IGNORECASE),
|
|
# re.compile('Error writing data',re.IGNORECASE)
|
|
# )
|
|
#
|
|
# with open( f_diff_txt.name,'r') as f:
|
|
# for line in f:
|
|
# if line.startswith('+'):
|
|
# match2some = filter( None, [ p.search(line) for p in allowed_patterns ] )
|
|
# if match2some:
|
|
# print('Crash message in firebird.log detected: '+line.upper())
|
|
#
|
|
# # This should be empty:
|
|
# #######################
|
|
# with open( f_shutdown_log.name,'r') as f:
|
|
# for line in f:
|
|
# print(line.upper())
|
|
#
|
|
#
|
|
# # Print check info:
|
|
# with open( f_chk_log.name,'r') as f:
|
|
# print( f.read() )
|
|
#
|
|
#
|
|
# ###############################
|
|
# # Cleanup.
|
|
# k_list = [ x.name for x in [ f_shutdown_log, f_dml_sql, f_chk_sql, f_fblog_before, f_fblog_after, f_diff_txt, f_chk_log] + f_list ]
|
|
# k_list += [db_verify_alive, db_dml_sessions]
|
|
# cleanup(k_list)
|
|
#
|
|
#
|
|
#---
|
|
|
|
act_1 = python_act('db_verify_alive_1', substitutions=substitutions_1)
|
|
|
|
expected_stdout_1 = """
|
|
check_point_1: established connection to <db_verify_alive>
|
|
Found all attachments of planned number to <db_dml_sessions>. Now we can release lock for allow them to start DML.
|
|
check_point_2: before shutdown <db_dml_sessions>
|
|
check_point_3: after shutdown <db_dml_sessions>
|
|
check_point_4: killed all DML sessions.
|
|
check_point_5: get firebird.log
|
|
distinct attachments to <db_verify_alive>: 1
|
|
Found crash messages in DML worker logs: 0
|
|
Point before bring DML database online.
|
|
Point after bring DML database online.
|
|
All DML sessions found in DB ? YES
|
|
"""
|
|
|
|
PLANNED_DML_ATTACHMENTS = 30
|
|
TIME_FOR_DML_WORK = 12 # Seconds to sleep. Was 4, but 12 required on my system for all DML to show up
|
|
|
|
dml_logs_1 = temp_files([f'tmp_dml_5087_{i+1}' for i in range(PLANNED_DML_ATTACHMENTS)])
|
|
check_log = temp_file('tmp_chk_5087.log')
|
|
dml_script_1 = temp_file('dml-script.sql')
|
|
|
|
@pytest.mark.version('>=3.0')
|
|
def test_1(act_1: Action, db_dml_sessions_1: Database, dml_logs_1: List[Path],
|
|
check_log: Path, dml_script_1: Path, capsys):
|
|
#
|
|
dml_script_1.write_text("""
|
|
commit;
|
|
set transaction wait;
|
|
set term ^;
|
|
execute block as
|
|
begin
|
|
insert into t_lock(id) values(1);
|
|
when any do
|
|
begin
|
|
-- nop --
|
|
end
|
|
end
|
|
^
|
|
set term ;^
|
|
commit;
|
|
|
|
set bail on;
|
|
|
|
set transaction read committed;
|
|
set term ^;
|
|
execute block as
|
|
declare n_limit int = 100000;
|
|
declare s_length smallint;
|
|
begin
|
|
select ff.rdb$field_length
|
|
from rdb$fields ff
|
|
join rdb$relation_fields rf on ff.rdb$field_name = rf.rdb$field_source
|
|
where rf.rdb$relation_name=upper('test') and rf.rdb$field_name=upper('s')
|
|
into s_length;
|
|
|
|
while (n_limit > 0) do
|
|
begin
|
|
execute statement ('insert into test(id, s) values( ?, ?)')
|
|
( gen_id(g,1), rpad('', :s_length, uuid_to_char(gen_uuid())) )
|
|
with autonomous transaction
|
|
;
|
|
|
|
n_limit = n_limit - 1;
|
|
end
|
|
insert into test( id ) values( -current_connection );
|
|
end
|
|
^
|
|
set term ;^
|
|
commit;
|
|
""")
|
|
#
|
|
with open(check_log, mode='w') as f_check_log:
|
|
with act_1.db.connect() as att_chk:
|
|
att_chk.execute_immediate('delete from log4attach where att<>current_connection')
|
|
att_chk.commit()
|
|
att_chk.begin()
|
|
cur_chk = att_chk.cursor()
|
|
cur_chk.execute("select 'check_point_1: established connection to <db_verify_alive>' as msg from rdb$database")
|
|
for row in cur_chk:
|
|
f_check_log.write(f'{row[0]}\n')
|
|
att_chk.commit()
|
|
#
|
|
f_logs = []
|
|
p_dml = []
|
|
try:
|
|
with db_dml_sessions_1.connect() as att1, db_dml_sessions_1.connect() as att2:
|
|
att1.execute_immediate('recreate table t_lock(id int primary key)')
|
|
att1.commit()
|
|
att1.execute_immediate('insert into t_lock(id) values(1)')
|
|
with act_1.connect_server() as srv:
|
|
srv.info.get_log()
|
|
log_before = srv.readlines()
|
|
# Launch isql processes for DML
|
|
for dml_log in dml_logs_1: # Contains PLANNED_DML_ATTACHMENTS items
|
|
f = open(dml_log, mode='w')
|
|
f_logs.append(f)
|
|
p_dml.append(subprocess.Popen([act_1.vars['isql'],
|
|
'-i', str(dml_script_1),
|
|
'-user', act_1.db.user,
|
|
'-password', act_1.db.password,
|
|
db_dml_sessions_1.dsn],
|
|
stdout=f, stderr=subprocess.STDOUT))
|
|
#
|
|
cur2 = att2.cursor()
|
|
while True:
|
|
att2.begin()
|
|
cur2.execute("select count(*) from mon$attachments a where a.mon$attachment_id<>current_connection and a.mon$remote_process containing 'isql'")
|
|
established_dml_attachments = cur2.fetchone()[0]
|
|
att2.commit()
|
|
#
|
|
if established_dml_attachments < PLANNED_DML_ATTACHMENTS:
|
|
# do not delete, leave it for debug:
|
|
####################################
|
|
#msg = datetime.datetime.now().strftime("%H:%M:%S.%f") + ' LOOP: only %(established_dml_attachments)s attachments exist of planned %(PLANNED_DML_ATTACHMENTS)s' % locals()
|
|
#print( msg )
|
|
#f_check_log.write(f'{msg}\n')
|
|
time.sleep(1)
|
|
continue
|
|
else:
|
|
# do not delete, leave it for debug:
|
|
####################################
|
|
#msg = datetime.datetime.now().strftime("%H:%M:%S.%f") + ' Found all planned %(PLANNED_DML_ATTACHMENTS)s attachments. Can release lock.' % locals()
|
|
msg = 'Found all attachments of planned number to <db_dml_sessions>. Now we can release lock for allow them to start DML.'
|
|
#print( msg )
|
|
f_check_log.write(f'{msg}\n')
|
|
break
|
|
# All launched ISQL sessions can do useful job (INSERT statements) since this point.
|
|
# Let ISQL sessions do some work:
|
|
time.sleep(TIME_FOR_DML_WORK)
|
|
cur_chk.execute("select 'check_point_2: before shutdown <db_dml_sessions>' as msg from rdb$database")
|
|
for row in cur_chk:
|
|
f_check_log.write(f'{row[0]}\n')
|
|
att_chk.commit()
|
|
# Shutdown database
|
|
with act_1.connect_server() as srv:
|
|
srv.database.shutdown(database=db_dml_sessions_1.db_path,
|
|
mode=ShutdownMode.FULL, method=ShutdownMethod.FORCED,
|
|
timeout=0)
|
|
cur_chk.execute("select 'check_point_3: after shutdown <db_dml_sessions>' as msg from rdb$database")
|
|
for row in cur_chk:
|
|
f_check_log.write(f'{row[0]}\n')
|
|
att_chk.commit()
|
|
finally:
|
|
for f in f_logs:
|
|
f.close()
|
|
for p in p_dml:
|
|
p.terminate()
|
|
#
|
|
cur_chk.execute("select 'check_point_4: killed all DML sessions.' as msg from rdb$database")
|
|
for row in cur_chk:
|
|
f_check_log.write(f'{row[0]}\n')
|
|
att_chk.commit()
|
|
#
|
|
# Here we must wait a little because firebird.log will get new messages NOT instantly.
|
|
time.sleep(3)
|
|
#
|
|
crashes_in_worker_logs = 0
|
|
for dml_log in dml_logs_1:
|
|
if 'SQLSTATE = 08004' in dml_log.read_text():
|
|
crashes_in_worker_logs += 1
|
|
#
|
|
with act_1.connect_server() as srv:
|
|
srv.info.get_log()
|
|
log_after = srv.readlines()
|
|
att_chk.begin()
|
|
cur_chk.execute("select 'check_point_5: get firebird.log' as msg from rdb$database")
|
|
for row in cur_chk:
|
|
f_check_log.write(f'{row[0]}\n')
|
|
att_chk.commit()
|
|
#
|
|
cur_chk.execute('select count(distinct att) from log4attach;')
|
|
for row in cur_chk:
|
|
f_check_log.write(f'distinct attachments to <db_verify_alive>: {row[0]}\n') # must be 1
|
|
#
|
|
f_check_log.write(f'Found crash messages in DML worker logs: {crashes_in_worker_logs}\n') # must be 0.
|
|
f_check_log.write('Point before bring DML database online.\n')
|
|
with act_1.connect_server() as srv:
|
|
srv.database.bring_online(database=db_dml_sessions_1.db_path)
|
|
f_check_log.write('Point after bring DML database online.\n')
|
|
chk_script = f"""
|
|
set list on;
|
|
select
|
|
iif( count(distinct t.att) = {PLANNED_DML_ATTACHMENTS}
|
|
,'YES'
|
|
,'NO. Only ' || count(distinct t.att) || ' attachments of planned {PLANNED_DML_ATTACHMENTS} established.')
|
|
as "All DML sessions found in DB ?"
|
|
from rdb$database r
|
|
left join (
|
|
select
|
|
att
|
|
,count(iif(id >=0, id, null)) as cnt_in_autonom_tx
|
|
,count(iif(id < 0, id, null)) as cnt_in_common_tx
|
|
from test
|
|
group by att
|
|
) t on t.cnt_in_common_tx = 0 and t.cnt_in_autonom_tx > 0;
|
|
"""
|
|
act_1.isql(switches=['-nod', str(db_dml_sessions_1.dsn)], input=chk_script, connect_db=False)
|
|
f_check_log.write(act_1.stdout)
|
|
# Now we can compare two versions of firebird.log and check their difference.
|
|
allowed_patterns = [re.compile('code attempt\\S+.*\\s+access',re.IGNORECASE),
|
|
re.compile('terminate\\S+ abnormally',re.IGNORECASE),
|
|
re.compile('Error writing data',re.IGNORECASE)
|
|
]
|
|
for line in unified_diff(log_before, log_after):
|
|
if line.startswith('+'):
|
|
if filter(None, [p.search(line) for p in allowed_patterns]):
|
|
f_check_log.write(f'Crash message in firebird.log detected: {line.upper()}\n')
|
|
# Final check
|
|
act_1.expected_stdout = expected_stdout_1
|
|
act_1.stdout = check_log.read_text()
|
|
assert act_1.clean_stdout == act_1.clean_expected_stdout
|