-
Notifications
You must be signed in to change notification settings - Fork 265
Expand file tree
/
Copy pathpause_new_connections_spec.rb
More file actions
179 lines (141 loc) · 6.13 KB
/
Copy pathpause_new_connections_spec.rb
File metadata and controls
179 lines (141 loc) · 6.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# frozen_string_literal: true
require_relative 'spec_helper'
describe "PAUSE with new client connections" do
let(:processes) { Helpers::Pgcat.single_instance_setup("sharded_db", 10) }
let(:pgcat_conn_str) { processes.pgcat.connection_string("sharded_db", "sharding_user") }
after do
processes.all_databases.map(&:reset)
processes.pgcat.shutdown
end
describe "New connections during PAUSE" do
it "should complete authentication and wait, not hang" do
admin_conn = PG::connect(processes.pgcat.admin_connection_string)
# Verify pool is not paused initially
results = admin_conn.async_exec("SHOW DATABASES").to_a
paused_pools = results.select { |r| r["database"] == "sharded_db" && r["paused"] == "1" }
expect(paused_pools).to be_empty, "Pool should not be paused initially"
# PAUSE the pool
admin_conn.async_exec("PAUSE")
# Verify pool is now paused
results = admin_conn.async_exec("SHOW DATABASES").to_a
paused_pools = results.select { |r| r["database"] == "sharded_db" && r["paused"] == "1" }
expect(paused_pools).not_to be_empty, "Pool should be paused"
# THIS IS THE KEY TEST:
# Try to connect a NEW client DURING pause
# Without the fix: This would hang during authentication or block indefinitely
# With the fix: This should complete authentication and return a connection object
connection_completed = false
connection_error = nil
query_result = nil
query_start_time = nil
query_end_time = nil
# Attempt connection in a separate thread with timeout
connect_thread = Thread.new do
begin
# This should complete quickly even though pool is paused
# The fix ensures authentication completes before checking pause state
new_conn = PG::connect(pgcat_conn_str)
connection_completed = true
# Now try to execute a query
# This SHOULD block until RESUME is issued
query_start_time = Time.now
query_result = new_conn.async_exec("SELECT 1 as test_value").to_a
query_end_time = Time.now
new_conn.close
rescue => e
connection_error = e
end
end
# Give the connection attempt time to complete authentication
# Without the fix: This would timeout or hang
# With the fix: Connection should complete within 2 seconds
sleep(2)
# ASSERTION 1: Connection should have completed (authentication done)
expect(connection_completed).to be(true),
"Connection should complete authentication even during PAUSE. " \
"Error: #{connection_error}"
# ASSERTION 2: Query should NOT have completed yet (waiting for RESUME)
expect(query_result).to be_nil,
"Query should not execute while pool is paused"
# Wait a bit more to ensure query is truly blocked
sleep(2)
expect(query_result).to be_nil,
"Query should still be blocked 4 seconds after PAUSE"
# Now RESUME the pool
admin_conn.async_exec("RESUME")
# Wait for the query to complete (should happen immediately after RESUME)
connect_thread.join(5) # 5 second timeout
# ASSERTION 3: Thread should have completed
expect(connect_thread.alive?).to be(false),
"Connection thread should complete after RESUME"
# ASSERTION 4: Query should have completed successfully
expect(query_result).not_to be_nil,
"Query should complete after RESUME"
expect(query_result.first["test_value"]).to eq("1"),
"Query should return correct result"
# ASSERTION 5: Query should have been blocked for at least 2 seconds
# (the time between when we checked it was nil and when we issued RESUME)
query_duration = query_end_time - query_start_time
expect(query_duration).to be >= 2.0,
"Query should have been blocked for at least 2 seconds during PAUSE, " \
"but completed in #{query_duration} seconds"
admin_conn.close
end
it "should handle multiple new connections during PAUSE" do
admin_conn = PG::connect(processes.pgcat.admin_connection_string)
# PAUSE the pool
admin_conn.async_exec("PAUSE")
# Start multiple connections during PAUSE
threads = []
connection_results = []
5.times do |i|
threads << Thread.new do
begin
conn = PG::connect(pgcat_conn_str)
result = conn.async_exec("SELECT #{i} as conn_id").to_a
conn.close
connection_results[i] = result.first["conn_id"].to_i
rescue => e
connection_results[i] = "ERROR: #{e.message}"
end
end
end
# Wait a moment for connections to authenticate
sleep(2)
# All connections should be waiting (not completed queries)
expect(connection_results.compact.size).to eq(0),
"No queries should complete during PAUSE"
# RESUME
admin_conn.async_exec("RESUME")
# Wait for all threads to complete
threads.each { |t| t.join(10) }
# All connections should have completed successfully
expect(connection_results.compact.size).to eq(5),
"All 5 connections should complete after RESUME"
# Verify each connection got the right result
5.times do |i|
expect(connection_results[i]).to eq(i),
"Connection #{i} should return correct result"
end
admin_conn.close
end
it "should not affect admin connections during PAUSE" do
admin_conn = PG::connect(processes.pgcat.admin_connection_string)
# PAUSE the pool
admin_conn.async_exec("PAUSE")
# Admin connection should still work immediately
# (not blocked by PAUSE)
result = admin_conn.async_exec("SHOW DATABASES").to_a
expect(result).not_to be_empty
# Create a NEW admin connection during PAUSE
# This should work immediately
new_admin_conn = PG::connect(processes.pgcat.admin_connection_string)
result = new_admin_conn.async_exec("SHOW POOLS").to_a
expect(result).not_to be_empty
new_admin_conn.close
# RESUME
admin_conn.async_exec("RESUME")
admin_conn.close
end
end
end