2 @message |
assert
checked_mode: table, STDLOG: Records affected: 10
checked_mode: table, STDLOG: ID
checked_mode: table, STDLOG:
checked_mode: table, STDLOG: -140
checked_mode: table, STDLOG: -130
checked_mode: table, STDLOG: -120
checked_mode: table, STDLOG: -110
checked_mode: table, STDLOG: -2
checked_mode: table, STDLOG: -1
checked_mode: table, STDLOG: 11
checked_mode: table, STDLOG: 12
checked_mode: table, STDLOG: 13
checked_mode: table, STDLOG: 14
checked_mode: table, STDLOG: Records affected: 10
checked_mode: table, STDLOG: OLD_ID OP SNAP_NO_RANK
checked_mode: table, STDLOG:
- checked_mode: table, STDLOG: 1 UPD 1
+ checked_mode: table, STDLOG: 1UPD 1
- checked_mode: table, STDLOG: -14 UPD 2
+ checked_mode: table, STDLOG: -14UPD 2
- checked_mode: table, STDLOG: -13 UPD 2
+ checked_mode: table, STDLOG: -13UPD 2
- checked_mode: table, STDLOG: -12 UPD 2
+ checked_mode: table, STDLOG: -12UPD 2
- checked_mode: table, STDLOG: -11 UPD 2
+ checked_mode: table, STDLOG: -11UPD 2
- checked_mode: table, STDLOG: 1 UPD 2
+ checked_mode: table, STDLOG: 1UPD 2
- checked_mode: table, STDLOG: 2 UPD 2
+ checked_mode: table, STDLOG: 2UPD 2
- checked_mode: table, STDLOG: 110 UPD 2
+ checked_mode: table, STDLOG: 110UPD 2
- checked_mode: table, STDLOG: 120 UPD 2
+ checked_mode: table, STDLOG: 120UPD 2
- checked_mode: table, STDLOG: 130 UPD 2
+ checked_mode: table, STDLOG: 130UPD 2
- checked_mode: table, STDLOG: 140 UPD 2
+ checked_mode: table, STDLOG: 140UPD 2
checked_mode: table, STDLOG: Records affected: 11
LOG DETAILS:
2025-07-01 06:05:11.342
2025-07-01 06:05:11.347 act = <firebird.qa.plugin.Action object at [hex]>
2025-07-01 06:05:11.351 fn_worker_sql = PosixPath('/var/tmp/qa_2024/test_12486/tmp_worker.sql')
2025-07-01 06:05:11.356 fn_worker_log = PosixPath('/var/tmp/qa_2024/test_12486/tmp_worker.log')
2025-07-01 06:05:11.360 fn_worker_err = PosixPath('/var/tmp/qa_2024/test_12486/tmp_worker.err')
2025-07-01 06:05:11.365 capsys = <_pytest.capture.CaptureFixture object at [hex]>
2025-07-01 06:05:11.369
2025-07-01 06:05:11.374 @pytest.mark.trace
2025-07-01 06:05:11.378 @pytest.mark.version('>=4.0.2')
2025-07-01 06:05:11.383 def test_1(act: Action, fn_worker_sql: Path, fn_worker_log: Path, fn_worker_err: Path, capsys):
2025-07-01 06:05:11.387 sql_init = (act.files_dir / 'read-consist-sttm-restart-DDL.sql').read_text()
2025-07-01 06:05:11.392
2025-07-01 06:05:11.397 for checked_mode in('table', 'view'):
2025-07-01 06:05:11.401 target_obj = 'test' if checked_mode == 'table' else 'v_test'
2025-07-01 06:05:11.406
2025-07-01 06:05:11.410 SQL_TO_BE_RESTARTED = f"update /* {SQL_TAG_THAT_WE_WAITING_FOR} */ {target_obj} set id = -id order by id ROWS 10"
2025-07-01 06:05:11.415
2025-07-01 06:05:11.419 # add rows with ID = 1, 2:
2025-07-01 06:05:11.424 sql_addi = f'''
2025-07-01 06:05:11.428 set term ^;
2025-07-01 06:05:11.433 execute block as
2025-07-01 06:05:11.437 begin
2025-07-01 06:05:11.442 rdb$set_context('USER_SESSION', 'WHO', 'INIT_DATA');
2025-07-01 06:05:11.447 end
2025-07-01 06:05:11.455 ^
2025-07-01 06:05:11.463 set term ;^
2025-07-01 06:05:11.472 insert into {target_obj}(id, x) select row_number()over(),row_number()over() from rdb$types rows 2;
2025-07-01 06:05:11.479 commit;
2025-07-01 06:05:11.484 '''
2025-07-01 06:05:11.489
2025-07-01 06:05:11.494 act.isql(switches=['-q'], input = ''.join( (sql_init, sql_addi) ) )
2025-07-01 06:05:11.499 # ::: NOTE ::: We have to immediately quit if any error raised in prepare phase.
2025-07-01 06:05:11.504 # See also letter from dimitr, 01-feb-2022 14:46
2025-07-01 06:05:11.509 assert act.stderr == ''
2025-07-01 06:05:11.515 act.reset()
2025-07-01 06:05:11.520
2025-07-01 06:05:11.525 trace_cfg_items = [
2025-07-01 06:05:11.530 'time_threshold = 0',
2025-07-01 06:05:11.535 'log_errors = true',
2025-07-01 06:05:11.540 'log_statement_start = true',
2025-07-01 06:05:11.544 'log_statement_finish = true',
2025-07-01 06:05:11.549 ]
2025-07-01 06:05:11.554
2025-07-01 06:05:11.560 with act.trace(db_events = trace_cfg_items, encoding=locale.getpreferredencoding()):
2025-07-01 06:05:11.566
2025-07-01 06:05:11.573 with act.db.connect() as con_lock_1, act.db.connect() as con_lock_2, act.db.connect() as con_monitoring:
2025-07-01 06:05:11.580
2025-07-01 06:05:11.585 tpb_monitoring = tpb(isolation=Isolation.READ_COMMITTED_RECORD_VERSION, lock_timeout=0)
2025-07-01 06:05:11.590 tx_monitoring = con_monitoring.transaction_manager(tpb_monitoring)
2025-07-01 06:05:11.595 cur_monitoring = tx_monitoring.cursor()
2025-07-01 06:05:11.600
2025-07-01 06:05:11.605 for i,c in enumerate((con_lock_1,con_lock_2)):
2025-07-01 06:05:11.613 sttm = f"execute block as begin rdb$set_context('USER_SESSION', 'WHO', 'LOCKER #{i+1}'); end"
2025-07-01 06:05:11.620 c.execute_immediate(sttm)
2025-07-01 06:05:11.625
2025-07-01 06:05:11.631 #########################
2025-07-01 06:05:11.638 ### L O C K E R - 1 ###
2025-07-01 06:05:11.645 #########################
2025-07-01 06:05:11.651
2025-07-01 06:05:11.657 con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = 2' )
2025-07-01 06:05:11.662
2025-07-01 06:05:11.673 worker_sql = f'''
2025-07-01 06:05:11.683 set list on;
2025-07-01 06:05:11.691 set autoddl off;
2025-07-01 06:05:11.698 set term ^;
2025-07-01 06:05:11.709 execute block returns (whoami varchar(30)) as
2025-07-01 06:05:11.722 begin
2025-07-01 06:05:11.731 whoami = 'WORKER'; -- , ATT#' || current_connection;
2025-07-01 06:05:11.738 rdb$set_context('USER_SESSION','WHO', whoami);
2025-07-01 06:05:11.745 -- suspend;
2025-07-01 06:05:11.750 end
2025-07-01 06:05:11.756 ^
2025-07-01 06:05:11.761 set term ;^
2025-07-01 06:05:11.774 commit;
2025-07-01 06:05:11.785 SET KEEP_TRAN_PARAMS ON;
2025-07-01 06:05:11.796 set transaction read committed read consistency;
2025-07-01 06:05:11.810 set list off;
2025-07-01 06:05:11.820 set wng off;
2025-07-01 06:05:11.831 set count on;
2025-07-01 06:05:11.844
2025-07-01 06:05:11.854 -- WORKER. THIS MUST BE LOCKED NOW BY LOCKER_i:
2025-07-01 06:05:11.863 {SQL_TO_BE_RESTARTED};
2025-07-01 06:05:11.872
2025-07-01 06:05:11.884 -- check results:
2025-07-01 06:05:11.892 -- ###############
2025-07-01 06:05:11.902 select id from {target_obj} order by id; -- this will produce output only after all lockers do their commit/rollback
2025-07-01 06:05:11.909
2025-07-01 06:05:11.915 select v.old_id, v.op, v.snap_no_rank from v_worker_log v where v.op = 'upd';
2025-07-01 06:05:11.922
2025-07-01 06:05:11.934 set width who 10;
2025-07-01 06:05:11.943 -- DO NOT check this! Values can differ here from one run to another!
2025-07-01 06:05:11.949 -- select id, trn, who, old_id, new_id, op, rec_vers, global_cn, snap_no from tlog_done order by id;
2025-07-01 06:05:11.954
2025-07-01 06:05:11.959 rollback;
2025-07-01 06:05:11.964
2025-07-01 06:05:11.968 '''
2025-07-01 06:05:11.973
2025-07-01 06:05:11.979 fn_worker_sql.write_text(worker_sql)
2025-07-01 06:05:11.985
2025-07-01 06:05:11.991 with fn_worker_log.open(mode='w') as hang_out, fn_worker_err.open(mode='w') as hang_err:
2025-07-01 06:05:11.997
2025-07-01 06:05:12.004 ############################################################################
2025-07-01 06:05:12.011 ### L A U N C H W O R K E R U S I N G I S Q L, A S Y N C. ###
2025-07-01 06:05:12.019 ############################################################################
2025-07-01 06:05:12.026 p_worker = subprocess.Popen([act.vars['isql'], '-i', str(fn_worker_sql),
2025-07-01 06:05:12.034 '-user', act.db.user,
2025-07-01 06:05:12.041 '-password', act.db.password,
2025-07-01 06:05:12.049 '-pag', '999999',
2025-07-01 06:05:12.057 act.db.dsn
2025-07-01 06:05:12.064 ],
2025-07-01 06:05:12.071 stdout = hang_out,
2025-07-01 06:05:12.078 stderr = hang_err
2025-07-01 06:05:12.085 )
2025-07-01 06:05:12.097 # NB: when ISQL will establish attach, first record that it must lock is ID = 1 -- see above SQL_TO_BE_RESTARTED
2025-07-01 06:05:12.107 # We must to ensure that this (worker) attachment has been really created and LOCKS this record:
2025-07-01 06:05:12.114 #
2025-07-01 06:05:12.120 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=1', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-01 06:05:12.125
2025-07-01 06:05:12.130
2025-07-01 06:05:12.135 #########################
2025-07-01 06:05:12.140 ### L O C K E R - 2 ###
2025-07-01 06:05:12.146 #########################
2025-07-01 06:05:12.153 con_lock_2.execute_immediate( f'insert /* LOCKER-2 */ into {target_obj}(id) values(110)' )
2025-07-01 06:05:12.166 con_lock_2.execute_immediate( f'insert /* LOCKER-2 */ into {target_obj}(id) values(-11)' )
2025-07-01 06:05:12.174 con_lock_2.commit()
2025-07-01 06:05:12.182 con_lock_2.execute_immediate( f'update /* LOCKER-2 */ {target_obj} set id=id where id = 110' )
2025-07-01 06:05:12.189 con_lock_2.execute_immediate( f'update /* LOCKER-2 */ {target_obj} set id=id where id = -11' )
2025-07-01 06:05:12.195
2025-07-01 06:05:12.207 #########################
2025-07-01 06:05:12.217 ### L O C K E R - 1 ###
2025-07-01 06:05:12.226 #########################
2025-07-01 06:05:12.235 con_lock_1.commit() # releases record with ID = 2 ==> now it can be locked by worker.
2025-07-01 06:05:12.245
2025-07-01 06:05:12.259 # We have to WAIT HERE until worker will actually 'catch' just released record with ID = 2.
2025-07-01 06:05:12.268 #
2025-07-01 06:05:12.276 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=2', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-01 06:05:12.284 # If we come here then it means that record with ID = 2 for sure is locked by WORKER.
2025-07-01 06:05:12.292
2025-07-01 06:05:12.300 con_lock_1.execute_immediate( f'insert /* LOCKER-1 */ into {target_obj}(id) values(120)' )
2025-07-01 06:05:12.308 con_lock_1.execute_immediate( f'insert /* LOCKER-1 */ into {target_obj}(id) values(-12)' )
2025-07-01 06:05:12.317 con_lock_1.commit()
2025-07-01 06:05:12.330 con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = 120' )
2025-07-01 06:05:12.341 con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = -12' )
2025-07-01 06:05:12.353
2025-07-01 06:05:12.364
2025-07-01 06:05:12.373 #########################
2025-07-01 06:05:12.381 ### L O C K E R - 2 ###
2025-07-01 06:05:12.388 #########################
2025-07-01 06:05:12.394 con_lock_2.commit() # releases TWO records with ID = -11 and 110.
2025-07-01 06:05:12.400
2025-07-01 06:05:12.407 # We have to WAIT HERE until worker will actually 'catch' just released records, in the order of its cursor: first with ID = -11, then with ID = 110.
2025-07-01 06:05:12.413 #
2025-07-01 06:05:12.419 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 110', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-01 06:05:12.427 # If we come here then it means that TWO records with ID = -11 and 110 for sure are locked by WORKER.
2025-07-01 06:05:12.440
2025-07-01 06:05:12.448
2025-07-01 06:05:12.462 con_lock_2.execute_immediate( f'insert into /* LOCKER-2 */ {target_obj}(id) values(130)' )
2025-07-01 06:05:12.471 con_lock_2.execute_immediate( f'insert into /* LOCKER-2 */ {target_obj}(id) values(-13)' )
2025-07-01 06:05:12.480 con_lock_2.commit()
2025-07-01 06:05:12.488 con_lock_2.execute_immediate( f'update /* LOCKER-2 */ {target_obj} set id=id where id = 130' )
2025-07-01 06:05:12.495 con_lock_2.execute_immediate( f'update /* LOCKER-2 */ {target_obj} set id=id where id = -13' )
2025-07-01 06:05:12.502
2025-07-01 06:05:12.509 #########################
2025-07-01 06:05:12.515 ### L O C K E R - 1 ###
2025-07-01 06:05:12.522 #########################
2025-07-01 06:05:12.533 con_lock_1.commit() # releases TWO records with ID = -12 and 120.
2025-07-01 06:05:12.540
2025-07-01 06:05:12.546 # We have to WAIT HERE until worker will actually 'catch' just released records, in the order of its cursor: first with ID = -12, then with ID = 120.
2025-07-01 06:05:12.551 #
2025-07-01 06:05:12.557 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 120', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-01 06:05:12.565 # If we come here then it means that TWO records with ID = -12 and 120 for sure are locked by WORKER.
2025-07-01 06:05:12.571
2025-07-01 06:05:12.577
2025-07-01 06:05:12.584 con_lock_1.execute_immediate( f'insert /* LOCKER-1 */ into {target_obj}(id) values(140)' )
2025-07-01 06:05:12.592 con_lock_1.execute_immediate( f'insert /* LOCKER-1 */ into {target_obj}(id) values(-14)' )
2025-07-01 06:05:12.599 con_lock_1.commit()
2025-07-01 06:05:12.611 con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = 140' )
2025-07-01 06:05:12.626 con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = -14' )
2025-07-01 06:05:12.637
2025-07-01 06:05:12.645 #########################
2025-07-01 06:05:12.652 ### L O C K E R - 2 ###
2025-07-01 06:05:12.658 #########################
2025-07-01 06:05:12.664 con_lock_2.commit()
2025-07-01 06:05:12.670
2025-07-01 06:05:12.676 # We have to WAIT HERE until worker will actually 'catch' just released records, in the order of its cursor: first with ID = -13, then with ID = 130.
2025-07-01 06:05:12.683 #
2025-07-01 06:05:12.690 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 130', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-01 06:05:12.701 # If we come here then it means that TWO records with ID = -13 and 130 for sure are locked by WORKER.
2025-07-01 06:05:12.709
2025-07-01 06:05:12.715 #########################
2025-07-01 06:05:12.722 ### L O C K E R - 1 ###
2025-07-01 06:05:12.728 #########################
2025-07-01 06:05:12.735 con_lock_1.commit() # WORKER will complete his job after this
2025-07-01 06:05:12.743
2025-07-01 06:05:12.754
2025-07-01 06:05:12.762 # Here we wait for ISQL complete its mission:
2025-07-01 06:05:12.769 p_worker.wait()
2025-07-01 06:05:12.776
2025-07-01 06:05:12.783 #< with act.db.connect()
2025-07-01 06:05:12.791
2025-07-01 06:05:12.803 for g in (fn_worker_log, fn_worker_err):
2025-07-01 06:05:12.814 with g.open() as f:
2025-07-01 06:05:12.825 for line in f:
2025-07-01 06:05:12.835 if line.split():
2025-07-01 06:05:12.844 if g == fn_worker_log:
2025-07-01 06:05:12.855 print(f'checked_mode: {checked_mode}, STDLOG: {line}')
2025-07-01 06:05:12.864 else:
2025-07-01 06:05:12.872 print(f'UNEXPECTED STDERR {line}')
2025-07-01 06:05:12.878
2025-07-01 06:05:12.884 expected_stdout_worker = f"""
2025-07-01 06:05:12.890 checked_mode: {checked_mode}, STDLOG: Records affected: 10
2025-07-01 06:05:12.897 checked_mode: {checked_mode}, STDLOG: ID
2025-07-01 06:05:12.905 checked_mode: {checked_mode}, STDLOG:
2025-07-01 06:05:12.912 checked_mode: {checked_mode}, STDLOG: -140
2025-07-01 06:05:12.925 checked_mode: {checked_mode}, STDLOG: -130
2025-07-01 06:05:12.933 checked_mode: {checked_mode}, STDLOG: -120
2025-07-01 06:05:12.939 checked_mode: {checked_mode}, STDLOG: -110
2025-07-01 06:05:12.945 checked_mode: {checked_mode}, STDLOG: -2
2025-07-01 06:05:12.950 checked_mode: {checked_mode}, STDLOG: -1
2025-07-01 06:05:12.955 checked_mode: {checked_mode}, STDLOG: 11
2025-07-01 06:05:12.959 checked_mode: {checked_mode}, STDLOG: 12
2025-07-01 06:05:12.966 checked_mode: {checked_mode}, STDLOG: 13
2025-07-01 06:05:12.972 checked_mode: {checked_mode}, STDLOG: 14
2025-07-01 06:05:12.978 checked_mode: {checked_mode}, STDLOG: Records affected: 10
2025-07-01 06:05:12.984 checked_mode: {checked_mode}, STDLOG: OLD_ID OP SNAP_NO_RANK
2025-07-01 06:05:12.990 checked_mode: {checked_mode}, STDLOG:
2025-07-01 06:05:12.997 checked_mode: {checked_mode}, STDLOG: 1 UPD 1
2025-07-01 06:05:13.004 checked_mode: {checked_mode}, STDLOG: -14 UPD 2
2025-07-01 06:05:13.011 checked_mode: {checked_mode}, STDLOG: -13 UPD 2
2025-07-01 06:05:13.018 checked_mode: {checked_mode}, STDLOG: -12 UPD 2
2025-07-01 06:05:13.025 checked_mode: {checked_mode}, STDLOG: -11 UPD 2
2025-07-01 06:05:13.033 checked_mode: {checked_mode}, STDLOG: 1 UPD 2
2025-07-01 06:05:13.040 checked_mode: {checked_mode}, STDLOG: 2 UPD 2
2025-07-01 06:05:13.048 checked_mode: {checked_mode}, STDLOG: 110 UPD 2
2025-07-01 06:05:13.055 checked_mode: {checked_mode}, STDLOG: 120 UPD 2
2025-07-01 06:05:13.062 checked_mode: {checked_mode}, STDLOG: 130 UPD 2
2025-07-01 06:05:13.071 checked_mode: {checked_mode}, STDLOG: 140 UPD 2
2025-07-01 06:05:13.082 checked_mode: {checked_mode}, STDLOG: Records affected: 11
2025-07-01 06:05:13.090 """
2025-07-01 06:05:13.097
2025-07-01 06:05:13.103 act.expected_stdout = expected_stdout_worker
2025-07-01 06:05:13.108 act.stdout = capsys.readouterr().out
2025-07-01 06:05:13.113 > assert act.clean_stdout == act.clean_expected_stdout
2025-07-01 06:05:13.122 E assert
2025-07-01 06:05:13.133 E checked_mode: table, STDLOG: Records affected: 10
2025-07-01 06:05:13.140 E checked_mode: table, STDLOG: ID
2025-07-01 06:05:13.146 E checked_mode: table, STDLOG:
2025-07-01 06:05:13.151 E checked_mode: table, STDLOG: -140
2025-07-01 06:05:13.156 E checked_mode: table, STDLOG: -130
2025-07-01 06:05:13.160 E checked_mode: table, STDLOG: -120
2025-07-01 06:05:13.165 E checked_mode: table, STDLOG: -110
2025-07-01 06:05:13.170 E checked_mode: table, STDLOG: -2
2025-07-01 06:05:13.175 E checked_mode: table, STDLOG: -1
2025-07-01 06:05:13.180 E checked_mode: table, STDLOG: 11
2025-07-01 06:05:13.185 E checked_mode: table, STDLOG: 12
2025-07-01 06:05:13.191 E checked_mode: table, STDLOG: 13
2025-07-01 06:05:13.197 E checked_mode: table, STDLOG: 14
2025-07-01 06:05:13.203 E checked_mode: table, STDLOG: Records affected: 10
2025-07-01 06:05:13.210 E checked_mode: table, STDLOG: OLD_ID OP SNAP_NO_RANK
2025-07-01 06:05:13.220 E checked_mode: table, STDLOG:
2025-07-01 06:05:13.231 E - checked_mode: table, STDLOG: 1 UPD 1
2025-07-01 06:05:13.252 E + checked_mode: table, STDLOG: 1UPD 1
2025-07-01 06:05:13.261 E - checked_mode: table, STDLOG: -14 UPD 2
2025-07-01 06:05:13.275 E + checked_mode: table, STDLOG: -14UPD 2
2025-07-01 06:05:13.281 E - checked_mode: table, STDLOG: -13 UPD 2
2025-07-01 06:05:13.295 E + checked_mode: table, STDLOG: -13UPD 2
2025-07-01 06:05:13.305 E - checked_mode: table, STDLOG: -12 UPD 2
2025-07-01 06:05:13.321 E + checked_mode: table, STDLOG: -12UPD 2
2025-07-01 06:05:13.328 E - checked_mode: table, STDLOG: -11 UPD 2
2025-07-01 06:05:13.351 E + checked_mode: table, STDLOG: -11UPD 2
2025-07-01 06:05:13.359 E - checked_mode: table, STDLOG: 1 UPD 2
2025-07-01 06:05:13.379 E + checked_mode: table, STDLOG: 1UPD 2
2025-07-01 06:05:13.389 E - checked_mode: table, STDLOG: 2 UPD 2
2025-07-01 06:05:13.410 E + checked_mode: table, STDLOG: 2UPD 2
2025-07-01 06:05:13.420 E - checked_mode: table, STDLOG: 110 UPD 2
2025-07-01 06:05:13.442 E + checked_mode: table, STDLOG: 110UPD 2
2025-07-01 06:05:13.452 E - checked_mode: table, STDLOG: 120 UPD 2
2025-07-01 06:05:13.470 E + checked_mode: table, STDLOG: 120UPD 2
2025-07-01 06:05:13.481 E - checked_mode: table, STDLOG: 130 UPD 2
2025-07-01 06:05:13.498 E + checked_mode: table, STDLOG: 130UPD 2
2025-07-01 06:05:13.510 E - checked_mode: table, STDLOG: 140 UPD 2
2025-07-01 06:05:13.528 E + checked_mode: table, STDLOG: 140UPD 2
2025-07-01 06:05:13.539 E checked_mode: table, STDLOG: Records affected: 11
2025-07-01 06:05:13.551
2025-07-01 06:05:13.560 tests/functional/transactions/test_read_consist_sttm_restart_on_update_03.py:480: AssertionError
2025-07-01 06:05:13.567 ---------------------------- Captured stdout setup -----------------------------
2025-07-01 06:05:13.575 Creating db: localhost:/var/tmp/qa_2024/test_12486/test.fdb [page_size=None, sql_dialect=None, charset='NONE', user=SYSDBA, password=masterkey]
|
3 #text |
act = <firebird.qa.plugin.Action pytest object at [hex]>
fn_worker_sql = PosixPath('/var/tmp/qa_2024/test_12486/tmp_worker.sql')
fn_worker_log = PosixPath('/var/tmp/qa_2024/test_12486/tmp_worker.log')
fn_worker_err = PosixPath('/var/tmp/qa_2024/test_12486/tmp_worker.err')
capsys = <_pytest.capture.CaptureFixture pytest object at [hex]>
@pytest.mark.trace
@pytest.mark.version('>=4.0.2')
def test_1(act: Action, fn_worker_sql: Path, fn_worker_log: Path, fn_worker_err: Path, capsys):
sql_init = (act.files_dir / 'read-consist-sttm-restart-DDL.sql').read_text()
for checked_mode in('table', 'view'):
target_obj = 'test' if checked_mode == 'table' else 'v_test'
SQL_TO_BE_RESTARTED = f"update /* {SQL_TAG_THAT_WE_WAITING_FOR} */ {target_obj} set id = -id order by id ROWS 10"
# add rows with ID = 1, 2:
sql_addi = f'''
set term ^;
execute block as
begin
rdb$set_context('USER_SESSION', 'WHO', 'INIT_DATA');
end
^
set term ;^
insert into {target_obj}(id, x) select row_number()over(),row_number()over() from rdb$types rows 2;
commit;
'''
act.isql(switches=['-q'], input = ''.join( (sql_init, sql_addi) ) )
# ::: NOTE ::: We have to immediately quit if any error raised in prepare phase.
# See also letter from dimitr, 01-feb-2022 14:46
assert act.stderr == ''
act.reset()
trace_cfg_items = [
'time_threshold = 0',
'log_errors = true',
'log_statement_start = true',
'log_statement_finish = true',
]
with act.trace(db_events = trace_cfg_items, encoding=locale.getpreferredencoding()):
with act.db.connect() as con_lock_1, act.db.connect() as con_lock_2, act.db.connect() as con_monitoring:
tpb_monitoring = tpb(isolation=Isolation.READ_COMMITTED_RECORD_VERSION, lock_timeout=0)
tx_monitoring = con_monitoring.transaction_manager(tpb_monitoring)
cur_monitoring = tx_monitoring.cursor()
for i,c in enumerate((con_lock_1,con_lock_2)):
sttm = f"execute block as begin rdb$set_context('USER_SESSION', 'WHO', 'LOCKER #{i+1}'); end"
c.execute_immediate(sttm)
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = 2' )
worker_sql = f'''
set list on;
set autoddl off;
set term ^;
execute block returns (whoami varchar(30)) as
begin
whoami = 'WORKER'; -- , ATT#' || current_connection;
rdb$set_context('USER_SESSION','WHO', whoami);
-- suspend;
end
^
set term ;^
commit;
SET KEEP_TRAN_PARAMS ON;
set transaction read committed read consistency;
set list off;
set wng off;
set count on;
-- WORKER. THIS MUST BE LOCKED NOW BY LOCKER_i:
{SQL_TO_BE_RESTARTED};
-- check results:
-- ###############
select id from {target_obj} order by id; -- this will produce output only after all lockers do their commit/rollback
select v.old_id, v.op, v.snap_no_rank from v_worker_log v where v.op = 'upd';
set width who 10;
-- DO NOT check this! Values can differ here from one run to another!
-- select id, trn, who, old_id, new_id, op, rec_vers, global_cn, snap_no from tlog_done order by id;
rollback;
'''
fn_worker_sql.write_text(worker_sql)
with fn_worker_log.open(mode='w') as hang_out, fn_worker_err.open(mode='w') as hang_err:
############################################################################
### L A U N C H W O R K E R U S I N G I S Q L, A S Y N C. ###
############################################################################
p_worker = subprocess.Popen([act.vars['isql'], '-i', str(fn_worker_sql),
'-user', act.db.user,
'-password', act.db.password,
'-pag', '999999',
act.db.dsn
],
stdout = hang_out,
stderr = hang_err
)
# NB: when ISQL will establish attach, first record that it must lock is ID = 1 -- see above SQL_TO_BE_RESTARTED
# We must to ensure that this (worker) attachment has been really created and LOCKS this record:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=1', SQL_TAG_THAT_WE_WAITING_FOR)
#########################
### L O C K E R - 2 ###
#########################
con_lock_2.execute_immediate( f'insert /* LOCKER-2 */ into {target_obj}(id) values(110)' )
con_lock_2.execute_immediate( f'insert /* LOCKER-2 */ into {target_obj}(id) values(-11)' )
con_lock_2.commit()
con_lock_2.execute_immediate( f'update /* LOCKER-2 */ {target_obj} set id=id where id = 110' )
con_lock_2.execute_immediate( f'update /* LOCKER-2 */ {target_obj} set id=id where id = -11' )
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit() # releases record with ID = 2 ==> now it can be locked by worker.
# We have to WAIT HERE until worker will actually 'catch' just released record with ID = 2.
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=2', SQL_TAG_THAT_WE_WAITING_FOR)
# If we come here then it means that record with ID = 2 for sure is locked by WORKER.
con_lock_1.execute_immediate( f'insert /* LOCKER-1 */ into {target_obj}(id) values(120)' )
con_lock_1.execute_immediate( f'insert /* LOCKER-1 */ into {target_obj}(id) values(-12)' )
con_lock_1.commit()
con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = 120' )
con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = -12' )
#########################
### L O C K E R - 2 ###
#########################
con_lock_2.commit() # releases TWO records with ID = -11 and 110.
# We have to WAIT HERE until worker will actually 'catch' just released records, in the order of its cursor: first with ID = -11, then with ID = 110.
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 110', SQL_TAG_THAT_WE_WAITING_FOR)
# If we come here then it means that TWO records with ID = -11 and 110 for sure are locked by WORKER.
con_lock_2.execute_immediate( f'insert into /* LOCKER-2 */ {target_obj}(id) values(130)' )
con_lock_2.execute_immediate( f'insert into /* LOCKER-2 */ {target_obj}(id) values(-13)' )
con_lock_2.commit()
con_lock_2.execute_immediate( f'update /* LOCKER-2 */ {target_obj} set id=id where id = 130' )
con_lock_2.execute_immediate( f'update /* LOCKER-2 */ {target_obj} set id=id where id = -13' )
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit() # releases TWO records with ID = -12 and 120.
# We have to WAIT HERE until worker will actually 'catch' just released records, in the order of its cursor: first with ID = -12, then with ID = 120.
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 120', SQL_TAG_THAT_WE_WAITING_FOR)
# If we come here then it means that TWO records with ID = -12 and 120 for sure are locked by WORKER.
con_lock_1.execute_immediate( f'insert /* LOCKER-1 */ into {target_obj}(id) values(140)' )
con_lock_1.execute_immediate( f'insert /* LOCKER-1 */ into {target_obj}(id) values(-14)' )
con_lock_1.commit()
con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = 140' )
con_lock_1.execute_immediate( f'update /* LOCKER-1 */ {target_obj} set id=id where id = -14' )
#########################
### L O C K E R - 2 ###
#########################
con_lock_2.commit()
# We have to WAIT HERE until worker will actually 'catch' just released records, in the order of its cursor: first with ID = -13, then with ID = 130.
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 130', SQL_TAG_THAT_WE_WAITING_FOR)
# If we come here then it means that TWO records with ID = -13 and 130 for sure are locked by WORKER.
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit() # WORKER will complete his job after this
# Here we wait for ISQL complete its mission:
p_worker.wait()
#< with act.db.connect()
for g in (fn_worker_log, fn_worker_err):
with g.open() as f:
for line in f:
if line.split():
if g == fn_worker_log:
print(f'checked_mode: {checked_mode}, STDLOG: {line}')
else:
print(f'UNEXPECTED STDERR {line}')
expected_stdout_worker = f"""
checked_mode: {checked_mode}, STDLOG: Records affected: 10
checked_mode: {checked_mode}, STDLOG: ID
checked_mode: {checked_mode}, STDLOG:
checked_mode: {checked_mode}, STDLOG: -140
checked_mode: {checked_mode}, STDLOG: -130
checked_mode: {checked_mode}, STDLOG: -120
checked_mode: {checked_mode}, STDLOG: -110
checked_mode: {checked_mode}, STDLOG: -2
checked_mode: {checked_mode}, STDLOG: -1
checked_mode: {checked_mode}, STDLOG: 11
checked_mode: {checked_mode}, STDLOG: 12
checked_mode: {checked_mode}, STDLOG: 13
checked_mode: {checked_mode}, STDLOG: 14
checked_mode: {checked_mode}, STDLOG: Records affected: 10
checked_mode: {checked_mode}, STDLOG: OLD_ID OP SNAP_NO_RANK
checked_mode: {checked_mode}, STDLOG:
checked_mode: {checked_mode}, STDLOG: 1 UPD 1
checked_mode: {checked_mode}, STDLOG: -14 UPD 2
checked_mode: {checked_mode}, STDLOG: -13 UPD 2
checked_mode: {checked_mode}, STDLOG: -12 UPD 2
checked_mode: {checked_mode}, STDLOG: -11 UPD 2
checked_mode: {checked_mode}, STDLOG: 1 UPD 2
checked_mode: {checked_mode}, STDLOG: 2 UPD 2
checked_mode: {checked_mode}, STDLOG: 110 UPD 2
checked_mode: {checked_mode}, STDLOG: 120 UPD 2
checked_mode: {checked_mode}, STDLOG: 130 UPD 2
checked_mode: {checked_mode}, STDLOG: 140 UPD 2
checked_mode: {checked_mode}, STDLOG: Records affected: 11
"""
act.expected_stdout = expected_stdout_worker
act.stdout = capsys.readouterr().out
> assert act.clean_stdout == act.clean_expected_stdout
E assert
E checked_mode: table, STDLOG: Records affected: 10
E checked_mode: table, STDLOG: ID
E checked_mode: table, STDLOG:
E checked_mode: table, STDLOG: -140
E checked_mode: table, STDLOG: -130
E checked_mode: table, STDLOG: -120
E checked_mode: table, STDLOG: -110
E checked_mode: table, STDLOG: -2
E checked_mode: table, STDLOG: -1
E checked_mode: table, STDLOG: 11
E checked_mode: table, STDLOG: 12
E checked_mode: table, STDLOG: 13
E checked_mode: table, STDLOG: 14
E checked_mode: table, STDLOG: Records affected: 10
E checked_mode: table, STDLOG: OLD_ID OP SNAP_NO_RANK
E checked_mode: table, STDLOG:
E - checked_mode: table, STDLOG: 1 UPD 1
E + checked_mode: table, STDLOG: 1UPD 1
E - checked_mode: table, STDLOG: -14 UPD 2
E + checked_mode: table, STDLOG: -14UPD 2
E - checked_mode: table, STDLOG: -13 UPD 2
E + checked_mode: table, STDLOG: -13UPD 2
E - checked_mode: table, STDLOG: -12 UPD 2
E + checked_mode: table, STDLOG: -12UPD 2
E - checked_mode: table, STDLOG: -11 UPD 2
E + checked_mode: table, STDLOG: -11UPD 2
E - checked_mode: table, STDLOG: 1 UPD 2
E + checked_mode: table, STDLOG: 1UPD 2
E - checked_mode: table, STDLOG: 2 UPD 2
E + checked_mode: table, STDLOG: 2UPD 2
E - checked_mode: table, STDLOG: 110 UPD 2
E + checked_mode: table, STDLOG: 110UPD 2
E - checked_mode: table, STDLOG: 120 UPD 2
E + checked_mode: table, STDLOG: 120UPD 2
E - checked_mode: table, STDLOG: 130 UPD 2
E + checked_mode: table, STDLOG: 130UPD 2
E - checked_mode: table, STDLOG: 140 UPD 2
E + checked_mode: table, STDLOG: 140UPD 2
E checked_mode: table, STDLOG: Records affected: 11
tests/functional/transactions/test_read_consist_sttm_restart_on_update_03.py:480: AssertionError
|