Skip to content

Commit 6b8a86c

Browse files
committed
Move the specs from the previous commit back back
... so that it's executed with sync and async methods. This is possible by moving execution of TcpGateSwitcher from concurrent threads to a separate process.
1 parent f6e9fe8 commit 6b8a86c

6 files changed

Lines changed: 135 additions & 89 deletions

File tree

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ group :test do
2020
# With bigdecimal commented out here, corresponding tests are omitted on ruby-3.4+ but are executed on ruby < 3.4.
2121
# That way we can check both situations in CI.
2222
# gem "bigdecimal", "~> 3.0"
23+
gem "drb"
2324
end

spec/helpers.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
require 'openssl'
88
require 'fileutils'
99
require 'objspace'
10-
require_relative 'helpers/scheduler.rb'
11-
require_relative 'helpers/tcp_gate_scheduler.rb'
12-
require_relative 'helpers/tcp_gate_switcher.rb'
10+
require_relative 'helpers/scheduler'
11+
require_relative 'helpers/tcp_gate_scheduler'
12+
require_relative 'helpers/tcp_gate_switcher'
13+
require_relative 'helpers/tcp_gate_switcher_process'
1314

1415
TEST_DIRECTORY = Pathname.new(ENV['RUBY_PG_TEST_DIR'] || Dir.pwd)
1516
DATA_OBJ_MEMSIZE = ObjectSpace.memsize_of(Object.new)
@@ -616,7 +617,7 @@ def run_with_scheduler(timeout=10)
616617

617618
def gate_setup
618619
# Run examples with gate
619-
gate = Helpers::TcpGateSwitcher.new(external_host: 'localhost', external_port: ENV['PGPORT'].to_i, debug: ENV['PG_DEBUG']=='1')
620+
gate = Helpers::TcpGateSwitcherProcess.new(external_host: 'localhost', external_port: ENV['PGPORT'].to_i, debug: ENV['PG_DEBUG']=='1')
620621
@conninfo_gate = @conninfo.gsub(/(^| )port=\d+/, " port=#{gate.internal_port}")
621622

622623
# Run examples without gate

spec/helpers/tcp_gate_switcher.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "socket"
4+
35
# This is a transparent TCP proxy for testing blocking behaviour in a time insensitive way.
46
#
57
# It works as a gate between the client and the server, which is enabled or disabled by the spec.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
require 'drb/drb'
4+
5+
# This is a wrapper of TcpGateSwitcher running in a separate process to avoid the need of threads.
6+
# It can therefore be used in conjunction with blocking GVL locking functions.
7+
8+
module Helpers
9+
class TcpGateSwitcherProcess
10+
def initialize(*args, **kwargs)
11+
file = File.expand_path("tcp_gate_switcher", __dir__)
12+
rbtext = <<~RBTEXT
13+
require #{file.inspect}
14+
require "drb/drb"
15+
16+
switcher = Helpers::TcpGateSwitcher.allocate
17+
def switcher.finish
18+
super
19+
DRb.stop_service
20+
end
21+
def switcher.init(*args, **kwargs)
22+
initialize(*args, **kwargs)
23+
end
24+
DRb.start_service('druby://localhost:0', switcher)
25+
puts DRb.uri
26+
# Redirect STDOUT to STDERR, so that p prints to STDERR
27+
STDOUT.reopen(STDERR)
28+
29+
# Wait for the drb server thread to finish before exiting.
30+
DRb.thread.join
31+
RBTEXT
32+
33+
io = IO.popen("ruby", "w+")
34+
io.write rbtext
35+
io.close_write
36+
server_uri = io.gets.strip
37+
@server = DRbObject.new_with_uri(server_uri)
38+
# Call initialize through DRb, so that Exceptions are passed to caller
39+
@server.init *args, **kwargs
40+
end
41+
42+
%i[finish internal_port start stop].each do |meth|
43+
define_method(meth) do
44+
@server.send(meth)
45+
end
46+
end
47+
end
48+
end

spec/pg/connection_async_spec.rb

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -135,31 +135,6 @@ def interrupt_thread(exc=nil)
135135
end
136136
end
137137

138-
it "connects without port and then retrieves the default port" do
139-
gate = Helpers::TcpGateSwitcher.new(
140-
external_host: 'localhost',
141-
external_port: ENV['PGPORT'].to_i,
142-
internal_host: "127.0.0.1",
143-
internal_port: PG::DEF_PGPORT,
144-
debug: ENV['PG_DEBUG']=='1')
145-
146-
PG.connect(host: "localhost",
147-
port: "",
148-
dbname: "test") do |conn|
149-
expect( conn.port ).to eq( PG::DEF_PGPORT )
150-
end
151-
152-
PG.connect(hostaddr: "127.0.0.1",
153-
port: nil,
154-
dbname: "test") do |conn|
155-
expect( conn.port ).to eq( PG::DEF_PGPORT )
156-
end
157-
158-
gate.finish
159-
rescue Errno::EADDRINUSE, Errno::EACCES => err
160-
skip err.to_s
161-
end
162-
163138
it "doesn't duplicate hosts in conn.reset", :without_transaction, :ipv6, :postgresql_12 do
164139
set_etc_hosts "::1", "rubypg_test2 rubypg_test_ipv6"
165140
set_etc_hosts "127.0.0.1", "rubypg_test2 rubypg_test_ipv4"
@@ -181,64 +156,4 @@ def interrupt_thread(exc=nil)
181156
expect( conn.hostaddr ).to eq( "::1" )
182157
expect( conn.port ).to eq( @port )
183158
end
184-
185-
context "in nonblocking mode" do
186-
after :each do
187-
@conn.setnonblocking(false)
188-
end
189-
190-
it "rejects to send lots of COPY data" do
191-
unless RUBY_PLATFORM =~ /i386-mingw|x86_64-darwin|x86_64-linux$/
192-
skip "this spec depends on out-of-memory condition in put_copy_data, which is not reliable on all platforms"
193-
end
194-
195-
run_with_gate(200) do |conn, gate|
196-
conn.setnonblocking(true)
197-
198-
res = nil
199-
conn.exec <<-EOSQL
200-
CREATE TEMP TABLE copytable (col1 TEXT);
201-
EOSQL
202-
203-
conn.exec( "COPY copytable FROM STDOUT CSV" )
204-
205-
gate.stop
206-
207-
data = "x" * 1000 * 1000
208-
data << "\n"
209-
20000.times do |idx|
210-
res = conn.put_copy_data(data)
211-
break if res == false
212-
end
213-
expect( res ).to be_falsey
214-
215-
gate.start
216-
conn.cancel
217-
conn.discard_results
218-
end
219-
end
220-
221-
it "needs to flush data after send_query" do
222-
run_with_gate(200) do |conn, gate|
223-
conn.setnonblocking(true)
224-
225-
gate.stop
226-
data = "x" * 1000 * 1000 * 30
227-
res = conn.send_query_params("SELECT LENGTH($1)", [data])
228-
expect( res ).to be_nil
229-
230-
res = conn.flush
231-
expect( res ).to be_falsey
232-
233-
gate.start
234-
until conn.flush
235-
IO.select(nil, [conn.socket_io], [conn.socket_io], 10)
236-
end
237-
expect( conn.flush ).to be_truthy
238-
239-
res = conn.get_last_result
240-
expect( res.values ).to eq( [[data.length.to_s]] )
241-
end
242-
end
243-
end
244159
end

spec/pg/connection_spec.rb

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,60 @@
796796
expect( res.values ).to eq([[data.length.to_s]])
797797
end
798798

799+
it "rejects to send lots of COPY data" do
800+
unless RUBY_PLATFORM =~ /i386-mingw|x86_64-darwin|x86_64-linux$/
801+
skip "this spec depends on out-of-memory condition in put_copy_data, which is not reliable on all platforms"
802+
end
803+
804+
run_with_gate(200) do |conn, gate|
805+
conn.setnonblocking(true)
806+
807+
res = nil
808+
conn.exec <<-EOSQL
809+
CREATE TEMP TABLE copytable (col1 TEXT);
810+
EOSQL
811+
812+
conn.exec( "COPY copytable FROM STDOUT CSV" )
813+
814+
gate.stop
815+
816+
data = "x" * 1000 * 1000
817+
data << "\n"
818+
20000.times do |idx|
819+
res = conn.put_copy_data(data)
820+
break if res == false
821+
end
822+
expect( res ).to be_falsey
823+
824+
gate.start
825+
conn.cancel
826+
conn.discard_results
827+
end
828+
end
829+
830+
it "needs to flush data after send_query" do
831+
run_with_gate(200) do |conn, gate|
832+
conn.setnonblocking(true)
833+
834+
gate.stop
835+
data = "x" * 1000 * 1000 * 30
836+
res = conn.send_query_params("SELECT LENGTH($1)", [data])
837+
expect( res ).to be_nil
838+
839+
res = conn.flush
840+
expect( res ).to be_falsey
841+
842+
gate.start
843+
until conn.flush
844+
IO.select(nil, [conn.socket_io], [conn.socket_io], 10)
845+
end
846+
expect( conn.flush ).to be_truthy
847+
848+
res = conn.get_last_result
849+
expect( res.values ).to eq( [[data.length.to_s]] )
850+
end
851+
end
852+
799853
it "returns immediately from get_copy_data(nonblock=true)" do
800854
expect do
801855
@conn.copy_data( "COPY (SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(10)) TO STDOUT" ) do |res|
@@ -844,6 +898,31 @@
844898
expect( @conn.options ).to eq( "" )
845899
end
846900

901+
it "connects without port and then retrieves the default port" do
902+
gate = Helpers::TcpGateSwitcherProcess.new(
903+
external_host: 'localhost',
904+
external_port: ENV['PGPORT'].to_i,
905+
internal_host: "127.0.0.1",
906+
internal_port: PG::DEF_PGPORT,
907+
debug: ENV['PG_DEBUG']=='1')
908+
909+
PG.connect(host: "localhost",
910+
port: "",
911+
dbname: "test") do |conn|
912+
expect( conn.port ).to eq( PG::DEF_PGPORT )
913+
end
914+
915+
PG.connect(hostaddr: "127.0.0.1",
916+
port: nil,
917+
dbname: "test") do |conn|
918+
expect( conn.port ).to eq( PG::DEF_PGPORT )
919+
end
920+
921+
gate.finish
922+
rescue Errno::EADDRINUSE, Errno::EACCES => err
923+
skip err.to_s
924+
end
925+
847926
it "can retrieve hostaddr for the established connection", :postgresql_12 do
848927
expect( @conn.hostaddr ).to match( /^127\.0\.0\.1$|^::1$/ )
849928
end

0 commit comments

Comments
 (0)