|
| 1 | +use strict; |
| 2 | +use warnings; |
| 3 | +use Test::More; |
| 4 | +use lib '.'; |
| 5 | +use SpockTest qw( |
| 6 | + create_cluster destroy_cluster |
| 7 | + get_test_config scalar_query psql_or_bail |
| 8 | + wait_for_sub_status |
| 9 | +); |
| 10 | + |
| 11 | +# A connection-class error after BEGIN aborts the local transaction and makes |
| 12 | +# the provider retransmit it. The exception marker recorded by handle_begin() |
| 13 | +# must not make the replacement worker treat that valid retransmission as |
| 14 | +# exception replay under SUB_DISABLE. |
| 15 | + |
| 16 | +create_cluster(2, 'Create 2-node reconnect retransmission test cluster'); |
| 17 | + |
| 18 | +my $config = get_test_config(); |
| 19 | +my $p1 = $config->{node_ports}->[0]; |
| 20 | +my $p2 = $config->{node_ports}->[1]; |
| 21 | +my $conn = "host=$config->{host} dbname=$config->{db_name} port=$p1 " . |
| 22 | + "user=$config->{db_user} password=$config->{db_password}"; |
| 23 | +my $subscriber_log = "$config->{log_dir}/00${p2}.log"; |
| 24 | + |
| 25 | +psql_or_bail(2, "ALTER SYSTEM SET spock.exception_behaviour = sub_disable"); |
| 26 | +psql_or_bail(2, "SELECT pg_reload_conf()"); |
| 27 | + |
| 28 | +psql_or_bail(1, "CREATE TABLE reconnect_retransmit (id int PRIMARY KEY, val text)"); |
| 29 | +psql_or_bail(2, "CREATE TABLE reconnect_retransmit (id int PRIMARY KEY, val text)"); |
| 30 | + |
| 31 | +# Sequence increments are non-transactional, so SQLSTATE 08006 is raised only |
| 32 | +# on the first apply attempt. ENABLE REPLICA restricts the trigger to apply. |
| 33 | +psql_or_bail(2, q{ |
| 34 | + CREATE SEQUENCE reconnect_fail_once; |
| 35 | + CREATE FUNCTION reconnect_fail_once() RETURNS trigger LANGUAGE plpgsql AS $$ |
| 36 | + BEGIN |
| 37 | + IF nextval('reconnect_fail_once') = 1 THEN |
| 38 | + RAISE EXCEPTION 'injected provider connection loss' |
| 39 | + USING ERRCODE = '08006'; |
| 40 | + END IF; |
| 41 | + RETURN NEW; |
| 42 | + END $$; |
| 43 | + CREATE TRIGGER reconnect_fail_once_trg |
| 44 | + BEFORE INSERT ON reconnect_retransmit |
| 45 | + FOR EACH ROW EXECUTE FUNCTION reconnect_fail_once(); |
| 46 | + ALTER TABLE reconnect_retransmit |
| 47 | + ENABLE REPLICA TRIGGER reconnect_fail_once_trg; |
| 48 | +}); |
| 49 | + |
| 50 | +psql_or_bail(2, |
| 51 | + "SELECT spock.sub_create('sub_n1_n2', '$conn', " . |
| 52 | + "ARRAY['default', 'default_insert_only', 'ddl_sql'], false, false)"); |
| 53 | +ok(wait_for_sub_status(2, 'sub_n1_n2', 'replicating', 30), |
| 54 | + 'subscription starts in replicating state'); |
| 55 | + |
| 56 | +my $log_offset = -s $subscriber_log // 0; |
| 57 | +psql_or_bail(1, "INSERT INTO reconnect_retransmit VALUES (1, 'must survive reconnect')"); |
| 58 | + |
| 59 | +my $applied = 0; |
| 60 | +for (1 .. 60) { |
| 61 | + $applied = scalar_query(2, |
| 62 | + "SELECT count(*) FROM reconnect_retransmit WHERE id = 1"); |
| 63 | + last if defined $applied && $applied eq '1'; |
| 64 | + sleep(1); |
| 65 | +} |
| 66 | +is($applied, '1', 'retransmitted transaction is applied after worker restart'); |
| 67 | + |
| 68 | +is(scalar_query(2, |
| 69 | + "SELECT sub_enabled FROM spock.subscription WHERE sub_name = 'sub_n1_n2'"), |
| 70 | + 't', 'SUB_DISABLE subscription remains enabled'); |
| 71 | +ok(wait_for_sub_status(2, 'sub_n1_n2', 'replicating', 30), |
| 72 | + 'subscription returns to replicating state'); |
| 73 | +is(scalar_query(2, "SELECT last_value FROM reconnect_fail_once"), |
| 74 | + '2', 'replica trigger proves the transaction was attempted twice'); |
| 75 | + |
| 76 | +my $new_log = ''; |
| 77 | +if (open(my $lf, '<', $subscriber_log)) { |
| 78 | + seek($lf, $log_offset, 0); |
| 79 | + local $/; |
| 80 | + $new_log = <$lf> // ''; |
| 81 | + close($lf); |
| 82 | +} |
| 83 | + |
| 84 | +like($new_log, |
| 85 | + qr/cleared transient exception state after provider connection loss/, |
| 86 | + 'connection-loss path clears transient exception state'); |
| 87 | +unlike($new_log, |
| 88 | + qr/Transaction failed, subscription will be disabled/, |
| 89 | + 'retransmission does not enter SUB_DISABLE exception replay'); |
| 90 | + |
| 91 | +destroy_cluster('Destroy reconnect retransmission test cluster'); |
| 92 | +done_testing(); |
0 commit comments