Skip to content

Commit c054ff0

Browse files
authored
Avoid sending Z packet in the middle of extended protocol packet sequence if we fail to get connection from pool (#137)
* Failing test * maybe * try fail * try * add message * pool size * correct user * more * debug * try fix * see stdout * stick? * fix configs * modify * types * m * maybe * make tests idempotent * hopefully fails * Add client fix * revert pgcat.toml change * Fix tests
1 parent 5a0cea6 commit c054ff0

File tree

3 files changed

+106
-30
lines changed

3 files changed

+106
-30
lines changed

.circleci/run_tests.sh

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ PGPASSWORD=sharding_user pgbench -h 127.0.0.1 -U sharding_user shard1 -i
1919
PGPASSWORD=sharding_user pgbench -h 127.0.0.1 -U sharding_user shard2 -i
2020

2121
# Install Toxiproxy to simulate a downed/slow database
22-
wget -O toxiproxy-2.1.4.deb https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy_2.1.4_amd64.deb
23-
sudo dpkg -i toxiproxy-2.1.4.deb
22+
wget -O toxiproxy-2.4.0.deb https://github.com/Shopify/toxiproxy/releases/download/v2.4.0/toxiproxy_2.4.0_linux_$(dpkg --print-architecture).deb
23+
sudo dpkg -i toxiproxy-2.4.0.deb
2424

2525
# Start Toxiproxy
2626
toxiproxy-server &
@@ -129,11 +129,14 @@ toxiproxy-cli toxic remove --toxicName latency_downstream postgres_replica
129129
start_pgcat "info"
130130

131131
# Test session mode (and config reload)
132-
sed -i 's/pool_mode = "transaction"/pool_mode = "session"/' .circleci/pgcat.toml
132+
sed -i '0,/simple_db/s/pool_mode = "transaction"/pool_mode = "session"/' .circleci/pgcat.toml
133133

134134
# Reload config test
135135
kill -SIGHUP $(pgrep pgcat)
136136

137+
# Revert settings after reload. Makes test runs idempotent
138+
sed -i '0,/simple_db/s/pool_mode = "session"/pool_mode = "transaction"/' .circleci/pgcat.toml
139+
137140
sleep 1
138141

139142
# Prepared statements that will only work in session mode

src/client.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -643,9 +643,20 @@ where
643643
conn
644644
}
645645
Err(err) => {
646-
error!("Could not get connection from pool: {:?}", err);
647-
error_response(&mut self.write, "could not get connection from the pool")
648-
.await?;
646+
// Clients do not expect to get SystemError followed by ReadyForQuery in the middle
647+
// of extended protocol submission. So we will hold off on sending the actual error
648+
// message to the client until we get 'S' message
649+
match message[0] as char {
650+
'P' | 'B' | 'E' | 'D' => (),
651+
_ => {
652+
error!("Could not get connection from pool: {:?}", err);
653+
error_response(
654+
&mut self.write,
655+
"could not get connection from the pool",
656+
)
657+
.await?;
658+
}
659+
}
649660
continue;
650661
}
651662
};

tests/ruby/tests.rb

Lines changed: 86 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,89 @@
55
require 'toml'
66

77
$stdout.sync = true
8+
$stderr.sync = true
9+
10+
class ConfigEditor
11+
def initialize
12+
@original_config_text = File.read('../../.circleci/pgcat.toml')
13+
text_to_load = @original_config_text.gsub("5432", "\"5432\"")
14+
15+
@original_configs = TOML.load(text_to_load)
16+
end
17+
18+
def original_configs
19+
TOML.load(TOML::Generator.new(@original_configs).body)
20+
end
21+
22+
def with_modified_configs(new_configs)
23+
text_to_write = TOML::Generator.new(new_configs).body
24+
text_to_write = text_to_write.gsub("\"5432\"", "5432")
25+
File.write('../../.circleci/pgcat.toml', text_to_write)
26+
yield
27+
ensure
28+
File.write('../../.circleci/pgcat.toml', @original_config_text)
29+
end
30+
end
31+
32+
def with_captured_stdout_stderr
33+
sout = STDOUT.clone
34+
serr = STDERR.clone
35+
STDOUT.reopen("/tmp/out.txt", "w+")
36+
STDERR.reopen("/tmp/err.txt", "w+")
37+
STDOUT.sync = true
38+
STDERR.sync = true
39+
yield
40+
return File.read('/tmp/out.txt'), File.read('/tmp/err.txt')
41+
ensure
42+
STDOUT.reopen(sout)
43+
STDERR.reopen(serr)
44+
end
45+
46+
47+
def test_extended_protocol_pooler_errors
48+
admin_conn = PG::connect("postgres://admin_user:[email protected]:6432/pgcat")
49+
50+
conf_editor = ConfigEditor.new
51+
new_configs = conf_editor.original_configs
52+
53+
# shorter timeouts
54+
new_configs["general"]["connect_timeout"] = 500
55+
new_configs["general"]["ban_time"] = 1
56+
new_configs["general"]["shutdown_timeout"] = 1
57+
new_configs["pools"]["sharded_db"]["users"]["0"]["pool_size"] = 1
58+
new_configs["pools"]["sharded_db"]["users"]["1"]["pool_size"] = 1
59+
60+
conf_editor.with_modified_configs(new_configs) { admin_conn.async_exec("RELOAD") }
61+
62+
conn_str = "postgres://sharding_user:[email protected]:6432/sharded_db"
63+
10.times do
64+
Thread.new do
65+
conn = PG::connect(conn_str)
66+
conn.async_exec("SELECT pg_sleep(5)") rescue PG::SystemError
67+
ensure
68+
conn&.close
69+
end
70+
end
71+
72+
sleep(0.5)
73+
conn_under_test = PG::connect(conn_str)
74+
stdout, stderr = with_captured_stdout_stderr do
75+
5.times do |i|
76+
conn_under_test.async_exec("SELECT 1") rescue PG::SystemError
77+
conn_under_test.exec_params("SELECT #{i} + $1", [i]) rescue PG::SystemError
78+
sleep 1
79+
end
80+
end
81+
82+
raise StandardError, "Libpq got unexpected messages while idle" if stderr.include?("arrived from server while idle")
83+
puts "Pool checkout errors not breaking clients passed"
84+
ensure
85+
sleep 1
86+
admin_conn.async_exec("RELOAD") # Reset state
87+
conn_under_test&.close
88+
end
89+
90+
test_extended_protocol_pooler_errors
891

992
# Uncomment these two to see all queries.
1093
# ActiveRecord.verbose_query_logs = true
@@ -144,30 +227,6 @@ def test_server_parameters
144227
end
145228

146229

147-
class ConfigEditor
148-
def initialize
149-
@original_config_text = File.read('../../.circleci/pgcat.toml')
150-
text_to_load = @original_config_text.gsub("5432", "\"5432\"")
151-
152-
@original_configs = TOML.load(text_to_load)
153-
end
154-
155-
def original_configs
156-
TOML.load(TOML::Generator.new(@original_configs).body)
157-
end
158-
159-
def with_modified_configs(new_configs)
160-
text_to_write = TOML::Generator.new(new_configs).body
161-
text_to_write = text_to_write.gsub("\"5432\"", "5432")
162-
File.write('../../.circleci/pgcat.toml', text_to_write)
163-
yield
164-
ensure
165-
File.write('../../.circleci/pgcat.toml', @original_config_text)
166-
end
167-
168-
end
169-
170-
171230
def test_reload_pool_recycling
172231
admin_conn = PG::connect("postgres://admin_user:[email protected]:6432/pgcat")
173232
server_conn = PG::connect("postgres://sharding_user:[email protected]:6432/sharded_db?application_name=testing_pgcat")
@@ -201,3 +260,6 @@ def test_reload_pool_recycling
201260
end
202261

203262
test_reload_pool_recycling
263+
264+
265+

0 commit comments

Comments
 (0)