2 @message |
assert
checked_mode: table, STDLOG: Records affected: 0
checked_mode: table, STDLOG: ID
checked_mode: table, STDLOG:
checked_mode: table, STDLOG: 1
checked_mode: table, STDLOG: 2
checked_mode: table, STDLOG: 3
checked_mode: table, STDLOG: 4
checked_mode: table, STDLOG: 5
checked_mode: table, STDLOG: 6
checked_mode: table, STDLOG: 7
checked_mode: table, STDLOG: 8
checked_mode: table, STDLOG: 9
checked_mode: table, STDLOG: 10
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: 5 DEL 1
+ checked_mode: table, STDLOG: 5DEL 1
- checked_mode: table, STDLOG: 4 DEL 1
+ checked_mode: table, STDLOG: 4DEL 1
- checked_mode: table, STDLOG: 3 DEL 1
+ checked_mode: table, STDLOG: 3DEL 1
- checked_mode: table, STDLOG: 2 DEL 1
+ checked_mode: table, STDLOG: 2DEL 1
checked_mode: table, STDLOG: Records affected: 4
LOG DETAILS:
2025-07-02 06:07:24.771
2025-07-02 06:07:24.779 act = <firebird.qa.plugin.Action object at [hex]>
2025-07-02 06:07:24.786 fn_worker_sql = PosixPath('/var/tmp/qa_2024/test_12473/tmp_worker.sql')
2025-07-02 06:07:24.793 fn_worker_log = PosixPath('/var/tmp/qa_2024/test_12473/tmp_worker.log')
2025-07-02 06:07:24.804 fn_worker_err = PosixPath('/var/tmp/qa_2024/test_12473/tmp_worker.err')
2025-07-02 06:07:24.815 capsys = <_pytest.capture.CaptureFixture object at [hex]>
2025-07-02 06:07:24.824
2025-07-02 06:07:24.832 @pytest.mark.trace
2025-07-02 06:07:24.840 @pytest.mark.version('>=4.0.3')
2025-07-02 06:07:24.853 def test_1(act: Action, fn_worker_sql: Path, fn_worker_log: Path, fn_worker_err: Path, capsys):
2025-07-02 06:07:24.866 sql_init = (act.files_dir / 'read-consist-sttm-restart-DDL.sql').read_text()
2025-07-02 06:07:24.878
2025-07-02 06:07:24.887 for checked_mode in('table', 'view'):
2025-07-02 06:07:24.894 target_obj = 'test' if checked_mode == 'table' else 'v_test'
2025-07-02 06:07:24.901
2025-07-02 06:07:24.908 SQL_TO_BE_RESTARTED = f'delete /* {SQL_TAG_THAT_WE_WAITING_FOR} */ from {target_obj} where not exists(select * from {target_obj} where id >= 10) order by id desc'
2025-07-02 06:07:24.915
2025-07-02 06:07:24.922 # add rows with ID = 1,2,3,4,5:
2025-07-02 06:07:24.929 sql_addi='''
2025-07-02 06:07:24.934 set term ^;
2025-07-02 06:07:24.939 execute block as
2025-07-02 06:07:24.946 begin
2025-07-02 06:07:24.953 rdb$set_context('USER_SESSION', 'WHO', 'INIT_DATA');
2025-07-02 06:07:24.958 end
2025-07-02 06:07:24.965 ^
2025-07-02 06:07:24.970 set term ;^
2025-07-02 06:07:24.981 insert into test(id, x)
2025-07-02 06:07:24.991 select row_number()over(),row_number()over()
2025-07-02 06:07:24.998 from rdb$types rows 5;
2025-07-02 06:07:25.008 commit;
2025-07-02 06:07:25.019 '''
2025-07-02 06:07:25.031 act.isql(switches=['-q'], input = ''.join( (sql_init, sql_addi) ) )
2025-07-02 06:07:25.042 # ::: NOTE ::: We have to immediately quit if any error raised in prepare phase.
2025-07-02 06:07:25.051 # See also letter from dimitr, 01-feb-2022 14:46
2025-07-02 06:07:25.060 assert act.stderr == ''
2025-07-02 06:07:25.073 act.reset()
2025-07-02 06:07:25.083
2025-07-02 06:07:25.097 trace_cfg_items = [
2025-07-02 06:07:25.108 'time_threshold = 0',
2025-07-02 06:07:25.119 'log_errors = true',
2025-07-02 06:07:25.133 'log_statement_start = true',
2025-07-02 06:07:25.143 'log_statement_finish = true',
2025-07-02 06:07:25.151 ]
2025-07-02 06:07:25.164
2025-07-02 06:07:25.175 with act.trace(db_events = trace_cfg_items, encoding=locale.getpreferredencoding()):
2025-07-02 06:07:25.183
2025-07-02 06:07:25.191 with act.db.connect() as con_lock_1, act.db.connect() as con_lock_2, act.db.connect() as con_monitoring:
2025-07-02 06:07:25.202
2025-07-02 06:07:25.214 tpb_monitoring = tpb(isolation=Isolation.READ_COMMITTED_RECORD_VERSION, lock_timeout=0)
2025-07-02 06:07:25.228 tx_monitoring = con_monitoring.transaction_manager(tpb_monitoring)
2025-07-02 06:07:25.240 cur_monitoring = tx_monitoring.cursor()
2025-07-02 06:07:25.250
2025-07-02 06:07:25.261 for i,c in enumerate((con_lock_1,con_lock_2)):
2025-07-02 06:07:25.270 sttm = f"execute block as begin rdb$set_context('USER_SESSION', 'WHO', 'LOCKER #{i+1}'); end"
2025-07-02 06:07:25.280 c.execute_immediate(sttm)
2025-07-02 06:07:25.288
2025-07-02 06:07:25.296 #########################
2025-07-02 06:07:25.304 ### L O C K E R - 1 ###
2025-07-02 06:07:25.310 #########################
2025-07-02 06:07:25.315 con_lock_1.execute_immediate( 'update test set id=id where id = 1' )
2025-07-02 06:07:25.320
2025-07-02 06:07:25.325 worker_sql = f'''
2025-07-02 06:07:25.329 set list on;
2025-07-02 06:07:25.334 set autoddl off;
2025-07-02 06:07:25.339 set term ^;
2025-07-02 06:07:25.345 execute block returns (whoami varchar(30)) as
2025-07-02 06:07:25.352 begin
2025-07-02 06:07:25.359 whoami = 'WORKER'; -- , ATT#' || current_connection;
2025-07-02 06:07:25.365 rdb$set_context('USER_SESSION','WHO', whoami);
2025-07-02 06:07:25.370 -- suspend;
2025-07-02 06:07:25.375 end
2025-07-02 06:07:25.380 ^
2025-07-02 06:07:25.385 set term ;^
2025-07-02 06:07:25.389 commit;
2025-07-02 06:07:25.394 SET KEEP_TRAN_PARAMS ON;
2025-07-02 06:07:25.399 set transaction read committed read consistency;
2025-07-02 06:07:25.403 set list off;
2025-07-02 06:07:25.410 set wng off;
2025-07-02 06:07:25.419 set count on;
2025-07-02 06:07:25.430
2025-07-02 06:07:25.439 -- this must hang because of locker-1:
2025-07-02 06:07:25.449 {SQL_TO_BE_RESTARTED};
2025-07-02 06:07:25.463
2025-07-02 06:07:25.478 -- check results:
2025-07-02 06:07:25.486 -- ###############
2025-07-02 06:07:25.494 select id from test order by id; -- this will produce output only after all lockers do their commit/rollback
2025-07-02 06:07:25.504
2025-07-02 06:07:25.512 select v.old_id, v.op, v.snap_no_rank
2025-07-02 06:07:25.520 from v_worker_log v
2025-07-02 06:07:25.528 where v.op = 'del';
2025-07-02 06:07:25.534
2025-07-02 06:07:25.546 set width who 10;
2025-07-02 06:07:25.557 -- DO NOT check this! Values can differ here from one run to another!
2025-07-02 06:07:25.565 --select id, trn, who, old_id, new_id, op, rec_vers, global_cn, snap_no from tlog_done order by id;
2025-07-02 06:07:25.572
2025-07-02 06:07:25.585 rollback;
2025-07-02 06:07:25.596 '''
2025-07-02 06:07:25.604 fn_worker_sql.write_text(worker_sql)
2025-07-02 06:07:25.611
2025-07-02 06:07:25.623 with fn_worker_log.open(mode='w') as hang_out, fn_worker_err.open(mode='w') as hang_err:
2025-07-02 06:07:25.631
2025-07-02 06:07:25.639 ############################################################################
2025-07-02 06:07:25.652 ### 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-02 06:07:25.663 ############################################################################
2025-07-02 06:07:25.673 p_worker = subprocess.Popen([act.vars['isql'], '-i', str(fn_worker_sql),
2025-07-02 06:07:25.681 '-user', act.db.user,
2025-07-02 06:07:25.688 '-password', act.db.password,
2025-07-02 06:07:25.694 act.db.dsn
2025-07-02 06:07:25.705 ],
2025-07-02 06:07:25.715 stdout = hang_out,
2025-07-02 06:07:25.724 stderr = hang_err
2025-07-02 06:07:25.731 )
2025-07-02 06:07:25.738
2025-07-02 06:07:25.749 # NB: when ISQL will establish attach, first record that it must lock is ID = 5 -- see above SQL_TO_BE_RESTARTED
2025-07-02 06:07:25.759 # We must to ensure that this (worker) attachment has been really created and LOCKS this record:
2025-07-02 06:07:25.766 #
2025-07-02 06:07:25.774 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id=5', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:25.783
2025-07-02 06:07:25.790
2025-07-02 06:07:25.798 #########################
2025-07-02 06:07:25.809 ### L O C K E R - 2 ###
2025-07-02 06:07:25.822 #########################
2025-07-02 06:07:25.833 # Add record so that it **will* be included in the set of rows that must be affected by session-worker:
2025-07-02 06:07:25.844 con_lock_2.execute_immediate( 'insert into test(id, x) values(6, 6);' )
2025-07-02 06:07:25.855 con_lock_2.commit()
2025-07-02 06:07:25.865 con_lock_2.execute_immediate( 'update test set id = id where id = 6;' )
2025-07-02 06:07:25.876
2025-07-02 06:07:25.886 #########################
2025-07-02 06:07:25.895 ### L O C K E R - 1 ###
2025-07-02 06:07:25.902 #########################
2025-07-02 06:07:25.912 con_lock_1.commit() # releases record with ID=1 ==> now it can be locked by worker.
2025-07-02 06:07:25.924
2025-07-02 06:07:25.935 # We have to WAIT HERE until worker will actually 'catch' just released record with ID = 1.
2025-07-02 06:07:25.943 #
2025-07-02 06:07:25.952 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-02 06:07:25.963
2025-07-02 06:07:25.972 # If we come here then it means that record with ID = 1 for sure is locked by WORKER.
2025-07-02 06:07:25.979
2025-07-02 06:07:25.993 # Add record so that it **will* be included in the set of rows that must be affected by session-worker:
2025-07-02 06:07:26.006 con_lock_1.execute_immediate( 'insert into test(id, x) values(7, 7);' )
2025-07-02 06:07:26.017 con_lock_1.commit()
2025-07-02 06:07:26.026 con_lock_1.execute_immediate( 'update test set id = id where id = 7;' )
2025-07-02 06:07:26.035
2025-07-02 06:07:26.042 #########################
2025-07-02 06:07:26.052 ### L O C K E R - 2 ###
2025-07-02 06:07:26.059 #########################
2025-07-02 06:07:26.068 con_lock_2.commit() # releases record with ID = 6, but session-worker is waiting for record with ID = 7 (that was added by locker-1).
2025-07-02 06:07:26.074
2025-07-02 06:07:26.089 # We have to WAIT HERE until worker will actually 'catch' just released record with ID = 6:
2025-07-02 06:07:26.102 #
2025-07-02 06:07:26.110 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 6', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:26.117
2025-07-02 06:07:26.124 # If we come here then it means that record with ID = 6 for sure is locked by WORKER.
2025-07-02 06:07:26.130
2025-07-02 06:07:26.141
2025-07-02 06:07:26.149 con_lock_2.execute_immediate( 'insert into test(id, x) values(8, 8);' )
2025-07-02 06:07:26.159 con_lock_2.commit()
2025-07-02 06:07:26.168 con_lock_2.execute_immediate( 'update test set id = id where id = 8;' )
2025-07-02 06:07:26.175
2025-07-02 06:07:26.182
2025-07-02 06:07:26.194 #########################
2025-07-02 06:07:26.205 ### L O C K E R - 1 ###
2025-07-02 06:07:26.214 #########################
2025-07-02 06:07:26.222 con_lock_1.commit() # releases record with ID = 7, but session-worker is waiting for record with ID = 8 (that was added by locker-2).
2025-07-02 06:07:26.228
2025-07-02 06:07:26.235 # We have to WAIT HERE until worker will actually 'catch' just released record with ID = 7:
2025-07-02 06:07:26.245 #
2025-07-02 06:07:26.257 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 7', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:26.265
2025-07-02 06:07:26.275 # If we come here then it means that record with ID = 7 for sure is locked by WORKER.
2025-07-02 06:07:26.283
2025-07-02 06:07:26.291 con_lock_1.execute_immediate( 'insert into test(id, x) values(9, 9);' )
2025-07-02 06:07:26.298 con_lock_1.commit()
2025-07-02 06:07:26.305 con_lock_1.execute_immediate( 'update test set id = id where id = 9;' )
2025-07-02 06:07:26.311
2025-07-02 06:07:26.321
2025-07-02 06:07:26.327 #########################
2025-07-02 06:07:26.335 ### L O C K E R - 2 ###
2025-07-02 06:07:26.348 #########################
2025-07-02 06:07:26.366 con_lock_2.commit() # releases record with ID = 8, but session-worker is waiting for record with ID = 9 (that was added by locker-1).
2025-07-02 06:07:26.379
2025-07-02 06:07:26.387 # We have to WAIT HERE until worker will actually 'catch' just released record with ID = 8:
2025-07-02 06:07:26.395 #
2025-07-02 06:07:26.402 wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 8', SQL_TAG_THAT_WE_WAITING_FOR)
2025-07-02 06:07:26.408
2025-07-02 06:07:26.414 # If we come here then it means that record with ID = 8 for sure is locked by WORKER.
2025-07-02 06:07:26.421
2025-07-02 06:07:26.426 con_lock_2.execute_immediate( 'insert into test(id, x) values(10, 10);' )
2025-07-02 06:07:26.432 con_lock_2.commit()
2025-07-02 06:07:26.437 con_lock_2.execute_immediate( 'update test set id = id where id = 10;' )
2025-07-02 06:07:26.443
2025-07-02 06:07:26.448
2025-07-02 06:07:26.453 #########################
2025-07-02 06:07:26.461 ### L O C K E R - 1 ###
2025-07-02 06:07:26.469 #########################
2025-07-02 06:07:26.477 con_lock_1.commit() # <<< THIS MUST CANCEL ALL PERFORMED DELETIONS OF SESSION-WORKER: record with ID = 10 become visible to it and its "NOT EXISTS()" query predicate return FAILSE on that.
2025-07-02 06:07:26.482 con_lock_2.commit()
2025-07-02 06:07:26.492
2025-07-02 06:07:26.506 # Here we wait until ISQL complete its mission:
2025-07-02 06:07:26.519 p_worker.wait()
2025-07-02 06:07:26.528
2025-07-02 06:07:26.536 #< with act.db.connect()
2025-07-02 06:07:26.543
2025-07-02 06:07:26.550 for g in (fn_worker_log, fn_worker_err):
2025-07-02 06:07:26.556 with g.open() as f:
2025-07-02 06:07:26.563 for line in f:
2025-07-02 06:07:26.571 if line.split():
2025-07-02 06:07:26.583 if g == fn_worker_log:
2025-07-02 06:07:26.596 print(f'checked_mode: {checked_mode}, STDLOG: {line}')
2025-07-02 06:07:26.608 else:
2025-07-02 06:07:26.620 print(f'UNEXPECTED STDERR {line}')
2025-07-02 06:07:26.631
2025-07-02 06:07:26.645 expected_stdout_worker = f"""
2025-07-02 06:07:26.657 checked_mode: {checked_mode}, STDLOG: Records affected: 0
2025-07-02 06:07:26.665
2025-07-02 06:07:26.677 checked_mode: {checked_mode}, STDLOG: ID
2025-07-02 06:07:26.690 checked_mode: {checked_mode}, STDLOG: =======
2025-07-02 06:07:26.699 checked_mode: {checked_mode}, STDLOG: 1
2025-07-02 06:07:26.708 checked_mode: {checked_mode}, STDLOG: 2
2025-07-02 06:07:26.715 checked_mode: {checked_mode}, STDLOG: 3
2025-07-02 06:07:26.724 checked_mode: {checked_mode}, STDLOG: 4
2025-07-02 06:07:26.736 checked_mode: {checked_mode}, STDLOG: 5
2025-07-02 06:07:26.745 checked_mode: {checked_mode}, STDLOG: 6
2025-07-02 06:07:26.753 checked_mode: {checked_mode}, STDLOG: 7
2025-07-02 06:07:26.760 checked_mode: {checked_mode}, STDLOG: 8
2025-07-02 06:07:26.766 checked_mode: {checked_mode}, STDLOG: 9
2025-07-02 06:07:26.771 checked_mode: {checked_mode}, STDLOG: 10
2025-07-02 06:07:26.783 checked_mode: {checked_mode}, STDLOG: Records affected: 10
2025-07-02 06:07:26.793
2025-07-02 06:07:26.805 checked_mode: {checked_mode}, STDLOG: OLD_ID OP SNAP_NO_RANK
2025-07-02 06:07:26.815 checked_mode: {checked_mode}, STDLOG: ======= ====== =====================
2025-07-02 06:07:26.826 checked_mode: {checked_mode}, STDLOG: 5 DEL 1
2025-07-02 06:07:26.835 checked_mode: {checked_mode}, STDLOG: 4 DEL 1
2025-07-02 06:07:26.844 checked_mode: {checked_mode}, STDLOG: 3 DEL 1
2025-07-02 06:07:26.852 checked_mode: {checked_mode}, STDLOG: 2 DEL 1
2025-07-02 06:07:26.859
2025-07-02 06:07:26.867 checked_mode: {checked_mode}, STDLOG: Records affected: 4
2025-07-02 06:07:26.876 """
2025-07-02 06:07:26.889 act.expected_stdout = expected_stdout_worker
2025-07-02 06:07:26.900 act.stdout = capsys.readouterr().out
2025-07-02 06:07:26.910 > assert act.clean_stdout == act.clean_expected_stdout
2025-07-02 06:07:26.921 E assert
2025-07-02 06:07:26.931 E checked_mode: table, STDLOG: Records affected: 0
2025-07-02 06:07:26.938 E checked_mode: table, STDLOG: ID
2025-07-02 06:07:26.947 E checked_mode: table, STDLOG:
2025-07-02 06:07:26.957 E checked_mode: table, STDLOG: 1
2025-07-02 06:07:26.966 E checked_mode: table, STDLOG: 2
2025-07-02 06:07:26.975 E checked_mode: table, STDLOG: 3
2025-07-02 06:07:26.986 E checked_mode: table, STDLOG: 4
2025-07-02 06:07:26.994 E checked_mode: table, STDLOG: 5
2025-07-02 06:07:27.003 E checked_mode: table, STDLOG: 6
2025-07-02 06:07:27.015 E checked_mode: table, STDLOG: 7
2025-07-02 06:07:27.023 E checked_mode: table, STDLOG: 8
2025-07-02 06:07:27.030 E checked_mode: table, STDLOG: 9
2025-07-02 06:07:27.039 E checked_mode: table, STDLOG: 10
2025-07-02 06:07:27.050 E checked_mode: table, STDLOG: Records affected: 10
2025-07-02 06:07:27.061 E checked_mode: table, STDLOG: OLD_ID OP SNAP_NO_RANK
2025-07-02 06:07:27.068 E checked_mode: table, STDLOG:
2025-07-02 06:07:27.076 E - checked_mode: table, STDLOG: 5 DEL 1
2025-07-02 06:07:27.088 E + checked_mode: table, STDLOG: 5DEL 1
2025-07-02 06:07:27.095 E - checked_mode: table, STDLOG: 4 DEL 1
2025-07-02 06:07:27.107 E + checked_mode: table, STDLOG: 4DEL 1
2025-07-02 06:07:27.113 E - checked_mode: table, STDLOG: 3 DEL 1
2025-07-02 06:07:27.130 E + checked_mode: table, STDLOG: 3DEL 1
2025-07-02 06:07:27.140 E - checked_mode: table, STDLOG: 2 DEL 1
2025-07-02 06:07:27.153 E + checked_mode: table, STDLOG: 2DEL 1
2025-07-02 06:07:27.158 E checked_mode: table, STDLOG: Records affected: 4
2025-07-02 06:07:27.163
2025-07-02 06:07:27.168 tests/functional/transactions/test_read_consist_statement_delete_undone_01.py:449: AssertionError
2025-07-02 06:07:27.173 ---------------------------- Captured stdout setup -----------------------------
2025-07-02 06:07:27.178 Creating db: localhost:/var/tmp/qa_2024/test_12473/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_12473/tmp_worker.sql')
fn_worker_log = PosixPath('/var/tmp/qa_2024/test_12473/tmp_worker.log')
fn_worker_err = PosixPath('/var/tmp/qa_2024/test_12473/tmp_worker.err')
capsys = <_pytest.capture.CaptureFixture pytest object at [hex]>
@pytest.mark.trace
@pytest.mark.version('>=4.0.3')
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'delete /* {SQL_TAG_THAT_WE_WAITING_FOR} */ from {target_obj} where not exists(select * from {target_obj} where id >= 10) order by id desc'
# add rows with ID = 1,2,3,4,5:
sql_addi='''
set term ^;
execute block as
begin
rdb$set_context('USER_SESSION', 'WHO', 'INIT_DATA');
end
^
set term ;^
insert into test(id, x)
select row_number()over(),row_number()over()
from rdb$types rows 5;
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( 'update test set id=id where id = 1' )
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;
-- this must hang because of locker-1:
{SQL_TO_BE_RESTARTED};
-- check results:
-- ###############
select id from test 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 = 'del';
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,
act.db.dsn
],
stdout = hang_out,
stderr = hang_err
)
# NB: when ISQL will establish attach, first record that it must lock is ID = 5 -- 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=5', SQL_TAG_THAT_WE_WAITING_FOR)
#########################
### L O C K E R - 2 ###
#########################
# Add record so that it **will* be included in the set of rows that must be affected by session-worker:
con_lock_2.execute_immediate( 'insert into test(id, x) values(6, 6);' )
con_lock_2.commit()
con_lock_2.execute_immediate( 'update test set id = id where id = 6;' )
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit() # releases record with ID=1 ==> now it can be locked by worker.
# We have to WAIT HERE until worker will actually 'catch' just released record with ID = 1.
#
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)
# If we come here then it means that record with ID = 1 for sure is locked by WORKER.
# Add record so that it **will* be included in the set of rows that must be affected by session-worker:
con_lock_1.execute_immediate( 'insert into test(id, x) values(7, 7);' )
con_lock_1.commit()
con_lock_1.execute_immediate( 'update test set id = id where id = 7;' )
#########################
### L O C K E R - 2 ###
#########################
con_lock_2.commit() # releases record with ID = 6, but session-worker is waiting for record with ID = 7 (that was added by locker-1).
# We have to WAIT HERE until worker will actually 'catch' just released record with ID = 6:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 6', SQL_TAG_THAT_WE_WAITING_FOR)
# If we come here then it means that record with ID = 6 for sure is locked by WORKER.
con_lock_2.execute_immediate( 'insert into test(id, x) values(8, 8);' )
con_lock_2.commit()
con_lock_2.execute_immediate( 'update test set id = id where id = 8;' )
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit() # releases record with ID = 7, but session-worker is waiting for record with ID = 8 (that was added by locker-2).
# We have to WAIT HERE until worker will actually 'catch' just released record with ID = 7:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 7', SQL_TAG_THAT_WE_WAITING_FOR)
# If we come here then it means that record with ID = 7 for sure is locked by WORKER.
con_lock_1.execute_immediate( 'insert into test(id, x) values(9, 9);' )
con_lock_1.commit()
con_lock_1.execute_immediate( 'update test set id = id where id = 9;' )
#########################
### L O C K E R - 2 ###
#########################
con_lock_2.commit() # releases record with ID = 8, but session-worker is waiting for record with ID = 9 (that was added by locker-1).
# We have to WAIT HERE until worker will actually 'catch' just released record with ID = 8:
#
wait_for_record_become_locked(tx_monitoring, cur_monitoring, f'update {target_obj} set id=id where id = 8', SQL_TAG_THAT_WE_WAITING_FOR)
# If we come here then it means that record with ID = 8 for sure is locked by WORKER.
con_lock_2.execute_immediate( 'insert into test(id, x) values(10, 10);' )
con_lock_2.commit()
con_lock_2.execute_immediate( 'update test set id = id where id = 10;' )
#########################
### L O C K E R - 1 ###
#########################
con_lock_1.commit() # <<< THIS MUST CANCEL ALL PERFORMED DELETIONS OF SESSION-WORKER: record with ID = 10 become visible to it and its "NOT EXISTS()" query predicate return FAILSE on that.
con_lock_2.commit()
# Here we wait until 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: 0
checked_mode: {checked_mode}, STDLOG: ID
checked_mode: {checked_mode}, STDLOG: =======
checked_mode: {checked_mode}, STDLOG: 1
checked_mode: {checked_mode}, STDLOG: 2
checked_mode: {checked_mode}, STDLOG: 3
checked_mode: {checked_mode}, STDLOG: 4
checked_mode: {checked_mode}, STDLOG: 5
checked_mode: {checked_mode}, STDLOG: 6
checked_mode: {checked_mode}, STDLOG: 7
checked_mode: {checked_mode}, STDLOG: 8
checked_mode: {checked_mode}, STDLOG: 9
checked_mode: {checked_mode}, STDLOG: 10
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: 5 DEL 1
checked_mode: {checked_mode}, STDLOG: 4 DEL 1
checked_mode: {checked_mode}, STDLOG: 3 DEL 1
checked_mode: {checked_mode}, STDLOG: 2 DEL 1
checked_mode: {checked_mode}, STDLOG: Records affected: 4
"""
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: 0
E checked_mode: table, STDLOG: ID
E checked_mode: table, STDLOG:
E checked_mode: table, STDLOG: 1
E checked_mode: table, STDLOG: 2
E checked_mode: table, STDLOG: 3
E checked_mode: table, STDLOG: 4
E checked_mode: table, STDLOG: 5
E checked_mode: table, STDLOG: 6
E checked_mode: table, STDLOG: 7
E checked_mode: table, STDLOG: 8
E checked_mode: table, STDLOG: 9
E checked_mode: table, STDLOG: 10
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: 5 DEL 1
E + checked_mode: table, STDLOG: 5DEL 1
E - checked_mode: table, STDLOG: 4 DEL 1
E + checked_mode: table, STDLOG: 4DEL 1
E - checked_mode: table, STDLOG: 3 DEL 1
E + checked_mode: table, STDLOG: 3DEL 1
E - checked_mode: table, STDLOG: 2 DEL 1
E + checked_mode: table, STDLOG: 2DEL 1
E checked_mode: table, STDLOG: Records affected: 4
tests/functional/transactions/test_read_consist_statement_delete_undone_01.py:449: AssertionError
|