pgtk 0.32.4 → 0.32.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +17 -16
- data/lib/pgtk/impatient/too_slow.rb +18 -0
- data/lib/pgtk/impatient.rb +23 -14
- data/lib/pgtk/liquibase_task.rb +3 -2
- data/lib/pgtk/liquicheck_task/must_error.rb +17 -0
- data/lib/pgtk/liquicheck_task.rb +2 -4
- data/lib/pgtk/pgsql_task.rb +41 -28
- data/lib/pgtk/pool/busy.rb +17 -0
- data/lib/pgtk/pool/iterable_queue.rb +83 -0
- data/lib/pgtk/pool/txn.rb +62 -0
- data/lib/pgtk/pool.rb +43 -152
- data/lib/pgtk/retry/exhausted.rb +17 -0
- data/lib/pgtk/retry.rb +2 -5
- data/lib/pgtk/spy.rb +4 -4
- data/lib/pgtk/stash.rb +68 -53
- data/lib/pgtk/version.rb +1 -1
- data/lib/pgtk/wire/direct.rb +41 -0
- data/lib/pgtk/wire/env.rb +48 -0
- data/lib/pgtk/wire/yaml.rb +47 -0
- data/lib/pgtk/wire.rb +4 -103
- data/pgtk.gemspec +1 -1
- data/resources/pom.xml +1 -1
- metadata +13 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bff4002fc7f88a973b99757aa0b8be3b82b6631b87f94c8ade93b30983b21761
|
|
4
|
+
data.tar.gz: 707d5ec1bf05047d60a0f25103b7210751c4731f79caa8b08c3021e7e3810e6c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 210230feda441fdee55cb3c3b6ccf1e2335efcdef77e95996afe89d3a4caf4f21bd8aa4fb9fd19f9b636d1336cc821345aa7454ec474d1693dff21990f2a1b40
|
|
7
|
+
data.tar.gz: ab98c8f9cf29405c495bd4808855e1107889ccdc2ee9011f4abe431b561f05d033564dc587f5a3084f64f0842e935d4bee6b9c8265cc729fe762a0f99bab4eb9
|
data/Gemfile.lock
CHANGED
|
@@ -2,7 +2,7 @@ PATH
|
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
4
|
pgtk (0.0.0)
|
|
5
|
-
|
|
5
|
+
cgi (~> 0.0)
|
|
6
6
|
concurrent-ruby (~> 1.3)
|
|
7
7
|
donce (~> 0.0)
|
|
8
8
|
ellipsized (~> 0.3)
|
|
@@ -22,7 +22,8 @@ GEM
|
|
|
22
22
|
ast (2.4.3)
|
|
23
23
|
backtrace (0.4.1)
|
|
24
24
|
builder (3.3.0)
|
|
25
|
-
|
|
25
|
+
cgi (0.5.1)
|
|
26
|
+
concurrent-ruby (1.3.7)
|
|
26
27
|
differ (0.1.2)
|
|
27
28
|
docile (1.4.1)
|
|
28
29
|
donce (0.7.0)
|
|
@@ -35,7 +36,7 @@ GEM
|
|
|
35
36
|
tago (~> 0.1)
|
|
36
37
|
ellipsized (0.3.0)
|
|
37
38
|
joined (0.4.0)
|
|
38
|
-
json (2.19.
|
|
39
|
+
json (2.19.9)
|
|
39
40
|
language_server-protocol (3.17.0.5)
|
|
40
41
|
lint_roller (1.1.0)
|
|
41
42
|
logger (1.7.0)
|
|
@@ -52,24 +53,24 @@ GEM
|
|
|
52
53
|
minitest (>= 5.0, < 7)
|
|
53
54
|
ruby-progressbar
|
|
54
55
|
minitest-stub-const (0.6)
|
|
55
|
-
nokogiri (1.19.
|
|
56
|
+
nokogiri (1.19.4-aarch64-linux-gnu)
|
|
56
57
|
racc (~> 1.4)
|
|
57
|
-
nokogiri (1.19.
|
|
58
|
+
nokogiri (1.19.4-aarch64-linux-musl)
|
|
58
59
|
racc (~> 1.4)
|
|
59
|
-
nokogiri (1.19.
|
|
60
|
+
nokogiri (1.19.4-arm-linux-gnu)
|
|
60
61
|
racc (~> 1.4)
|
|
61
|
-
nokogiri (1.19.
|
|
62
|
+
nokogiri (1.19.4-arm-linux-musl)
|
|
62
63
|
racc (~> 1.4)
|
|
63
|
-
nokogiri (1.19.
|
|
64
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
64
65
|
racc (~> 1.4)
|
|
65
|
-
nokogiri (1.19.
|
|
66
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
66
67
|
racc (~> 1.4)
|
|
67
|
-
nokogiri (1.19.
|
|
68
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
68
69
|
racc (~> 1.4)
|
|
69
|
-
nokogiri (1.19.
|
|
70
|
+
nokogiri (1.19.4-x86_64-linux-musl)
|
|
70
71
|
racc (~> 1.4)
|
|
71
72
|
os (1.1.4)
|
|
72
|
-
parallel (2.0
|
|
73
|
+
parallel (2.1.0)
|
|
73
74
|
parser (3.3.11.1)
|
|
74
75
|
ast (~> 2.4.1)
|
|
75
76
|
racc
|
|
@@ -81,7 +82,7 @@ GEM
|
|
|
81
82
|
pg (1.6.3-x86_64-linux)
|
|
82
83
|
pg (1.6.3-x86_64-linux-musl)
|
|
83
84
|
prism (1.9.0)
|
|
84
|
-
qbash (0.8.
|
|
85
|
+
qbash (0.8.4)
|
|
85
86
|
backtrace (> 0)
|
|
86
87
|
elapsed (> 0)
|
|
87
88
|
loog (> 0)
|
|
@@ -94,7 +95,7 @@ GEM
|
|
|
94
95
|
tago (~> 0.0)
|
|
95
96
|
regexp_parser (2.12.0)
|
|
96
97
|
rexml (3.4.4)
|
|
97
|
-
rubocop (1.
|
|
98
|
+
rubocop (1.87.0)
|
|
98
99
|
json (~> 2.3)
|
|
99
100
|
language_server-protocol (~> 3.17.0.2)
|
|
100
101
|
lint_roller (~> 1.1.0)
|
|
@@ -108,7 +109,7 @@ GEM
|
|
|
108
109
|
rubocop-ast (1.49.1)
|
|
109
110
|
parser (>= 3.3.7.2)
|
|
110
111
|
prism (~> 1.7)
|
|
111
|
-
rubocop-elegant (0.0
|
|
112
|
+
rubocop-elegant (0.6.0)
|
|
112
113
|
lint_roller (~> 1.1)
|
|
113
114
|
rubocop (~> 1.75)
|
|
114
115
|
rubocop-minitest (0.39.1)
|
|
@@ -147,7 +148,7 @@ GEM
|
|
|
147
148
|
nokogiri (~> 1.8)
|
|
148
149
|
rainbow (~> 3.0)
|
|
149
150
|
slop (~> 4.4)
|
|
150
|
-
yard (0.9.
|
|
151
|
+
yard (0.9.44)
|
|
151
152
|
|
|
152
153
|
PLATFORMS
|
|
153
154
|
aarch64-linux
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
|
|
8
|
+
class Pgtk::Impatient; end
|
|
9
|
+
|
|
10
|
+
# Raised by Pgtk::Impatient#exec when the query takes longer than the
|
|
11
|
+
# configured timeout. The deadline is enforced server-side via
|
|
12
|
+
# +SET LOCAL statement_timeout+, so the underlying +PG::QueryCanceled+
|
|
13
|
+
# is translated into this error for the caller.
|
|
14
|
+
#
|
|
15
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
16
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
17
|
+
# License:: MIT
|
|
18
|
+
class Pgtk::Impatient::TooSlow < StandardError; end
|
data/lib/pgtk/impatient.rb
CHANGED
|
@@ -58,18 +58,17 @@ require_relative '../pgtk'
|
|
|
58
58
|
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
59
59
|
# License:: MIT
|
|
60
60
|
class Pgtk::Impatient
|
|
61
|
-
# If timed out
|
|
62
|
-
class TooSlow < StandardError; end
|
|
63
|
-
|
|
64
61
|
# Constructor.
|
|
65
62
|
#
|
|
66
63
|
# @param [Pgtk::Pool] pool The pool to decorate
|
|
67
64
|
# @param [Integer] timeout Timeout in seconds for each SQL query
|
|
68
65
|
# @param [Array<Regex>] off List of regex to exclude queries from checking
|
|
69
|
-
|
|
66
|
+
# @param [Integer] default Fallback timeout in seconds for excluded queries (0 = no timeout)
|
|
67
|
+
def initialize(pool, timeout, *off, default: 300)
|
|
70
68
|
@pool = pool
|
|
71
69
|
@timeout = timeout
|
|
72
70
|
@off = off
|
|
71
|
+
@default = default
|
|
73
72
|
end
|
|
74
73
|
|
|
75
74
|
# Start a new connection pool with the given arguments.
|
|
@@ -89,7 +88,7 @@ class Pgtk::Impatient
|
|
|
89
88
|
[
|
|
90
89
|
@pool.dump,
|
|
91
90
|
'',
|
|
92
|
-
"Pgtk::Impatient (timeout=#{@timeout}s):",
|
|
91
|
+
"Pgtk::Impatient (timeout=#{@timeout}s, default=#{@default}s):",
|
|
93
92
|
@off.map { |re| " #{re}" }
|
|
94
93
|
].join("\n")
|
|
95
94
|
end
|
|
@@ -109,7 +108,13 @@ class Pgtk::Impatient
|
|
|
109
108
|
# @raise [TooSlow] If the query takes too long
|
|
110
109
|
def exec(query, *args)
|
|
111
110
|
sql = query.is_a?(Array) ? query.join(' ') : query
|
|
112
|
-
|
|
111
|
+
if @off.any? { |re| re.match?(sql) }
|
|
112
|
+
ms = Integer(@default * 1000)
|
|
113
|
+
return @pool.transaction do |t|
|
|
114
|
+
t.exec("SET LOCAL statement_timeout = #{ms}") unless ms.zero?
|
|
115
|
+
t.exec(sql, *args)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
113
118
|
start = Time.now
|
|
114
119
|
ms = [Integer(@timeout * 1000), 1].max
|
|
115
120
|
begin
|
|
@@ -118,14 +123,16 @@ class Pgtk::Impatient
|
|
|
118
123
|
t.exec(sql, *args)
|
|
119
124
|
end
|
|
120
125
|
rescue PG::QueryCanceled
|
|
121
|
-
raise(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
raise(
|
|
127
|
+
TooSlow, [
|
|
128
|
+
'SQL query',
|
|
129
|
+
("with #{args.count} argument#{'s' if args.count > 1}" unless args.empty?),
|
|
130
|
+
'was terminated after',
|
|
131
|
+
start.ago,
|
|
132
|
+
'of waiting:',
|
|
133
|
+
sql.ellipsized(50).inspect
|
|
134
|
+
].compact.join(' ')
|
|
135
|
+
)
|
|
129
136
|
end
|
|
130
137
|
end
|
|
131
138
|
|
|
@@ -146,3 +153,5 @@ class Pgtk::Impatient
|
|
|
146
153
|
end
|
|
147
154
|
end
|
|
148
155
|
end
|
|
156
|
+
|
|
157
|
+
require_relative 'impatient/too_slow'
|
data/lib/pgtk/liquibase_task.rb
CHANGED
|
@@ -127,14 +127,15 @@ class Pgtk::LiquibaseTask < Rake::TaskLib
|
|
|
127
127
|
password = yml['pgsql']['password']
|
|
128
128
|
host = yml.dig('pgsql', 'host')
|
|
129
129
|
Dir.chdir(File.dirname(@schema)) do
|
|
130
|
-
|
|
130
|
+
File.write(
|
|
131
|
+
@schema,
|
|
131
132
|
if (local && @docker != :always) || @docker == :never
|
|
132
133
|
pgdump(yml, host, password)
|
|
133
134
|
else
|
|
134
135
|
host = donce_host if OS.mac? && ['localhost', '127.0.0.1'].include?(host)
|
|
135
136
|
dockerdump(yml, host, password)
|
|
136
137
|
end
|
|
137
|
-
|
|
138
|
+
)
|
|
138
139
|
end
|
|
139
140
|
end
|
|
140
141
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
|
|
8
|
+
class Pgtk::LiquicheckTask; end
|
|
9
|
+
|
|
10
|
+
# Internal error raised by Pgtk::LiquicheckTask validation helpers and
|
|
11
|
+
# captured per-file by the +on+ helper to accumulate readable diagnostics.
|
|
12
|
+
#
|
|
13
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
14
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
15
|
+
# License:: MIT
|
|
16
|
+
class Pgtk::LiquicheckTask::MustError < StandardError
|
|
17
|
+
end
|
data/lib/pgtk/liquicheck_task.rb
CHANGED
|
@@ -110,8 +110,6 @@ class Pgtk::LiquicheckTask < Rake::TaskLib
|
|
|
110
110
|
def confirm(prop, regex, msg)
|
|
111
111
|
raise(MustError, msg) unless prop.match?(regex)
|
|
112
112
|
end
|
|
113
|
-
|
|
114
|
-
class MustError < StandardError
|
|
115
|
-
end
|
|
116
|
-
private_constant :MustError
|
|
117
113
|
end
|
|
114
|
+
|
|
115
|
+
require_relative 'liquicheck_task/must_error'
|
data/lib/pgtk/pgsql_task.rb
CHANGED
|
@@ -51,18 +51,18 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
51
51
|
|
|
52
52
|
def run
|
|
53
53
|
local = detect(:local)
|
|
54
|
-
|
|
55
|
-
preflight(local, docker)
|
|
54
|
+
preflight(local, detect(:docker))
|
|
56
55
|
home = File.expand_path(@dir)
|
|
57
56
|
FileUtils.rm_rf(home) if @fresh
|
|
58
57
|
raise(ArgumentError, "Directory/file #{home} is present, use fresh=true") if File.exist?(home)
|
|
59
|
-
stdout = @quiet ? nil : $stdout
|
|
60
58
|
port = acquire
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
launch(local, home, @quiet ? nil : $stdout, port).then do |place|
|
|
60
|
+
save(port)
|
|
61
|
+
unless @quiet
|
|
62
|
+
puts("PostgreSQL has been started #{place}, port #{port}")
|
|
63
|
+
puts("YAML config saved to #{@yaml}")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def preflight(local, docker)
|
|
@@ -87,11 +87,9 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
87
87
|
|
|
88
88
|
def launch(local, home, stdout, port)
|
|
89
89
|
if (local && @docker != :always) || @docker == :never
|
|
90
|
-
|
|
91
|
-
"in process ##{pid}"
|
|
90
|
+
"in process ##{localize(home, stdout, port)}"
|
|
92
91
|
else
|
|
93
|
-
container
|
|
94
|
-
"in container #{container}"
|
|
92
|
+
"in container #{dockerize(home, stdout, port)}"
|
|
95
93
|
end
|
|
96
94
|
end
|
|
97
95
|
|
|
@@ -122,21 +120,23 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
122
120
|
|
|
123
121
|
def dockerize(home, stdout, port)
|
|
124
122
|
FileUtils.mkdir_p(home)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
123
|
+
container = qbash(
|
|
124
|
+
'docker',
|
|
125
|
+
'run',
|
|
126
|
+
"--publish #{Shellwords.escape("#{port}:5432")}",
|
|
127
|
+
"-e POSTGRES_USER=#{Shellwords.escape(@user)}",
|
|
128
|
+
"-e POSTGRES_PASSWORD=#{Shellwords.escape(@password)}",
|
|
129
|
+
"-e POSTGRES_DB=#{Shellwords.escape(@dbname)}",
|
|
130
|
+
'--detach',
|
|
131
|
+
'--rm',
|
|
132
|
+
'--health-cmd', 'pg_isready',
|
|
133
|
+
'--health-interval', '1s',
|
|
134
|
+
'--health-timeout', '2s',
|
|
135
|
+
'--health-retries', '10',
|
|
136
|
+
'postgres:18.1',
|
|
137
|
+
@config.map { |k, v| "-c #{Shellwords.escape("#{k}=#{v}")}" },
|
|
138
|
+
stdout:
|
|
139
|
+
).scan(/[a-f0-9]+\Z/).first
|
|
140
140
|
File.write(File.join(home, 'docker-container'), container)
|
|
141
141
|
at_exit do
|
|
142
142
|
if qbash(
|
|
@@ -152,6 +152,15 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
152
152
|
rescue WaitUtil::TimeoutError => e
|
|
153
153
|
raise(IOError, "Failed to start PostgreSQL Docker container #{container.inspect}: #{e.message}")
|
|
154
154
|
end
|
|
155
|
+
(30 * 2).times do
|
|
156
|
+
status = qbash(
|
|
157
|
+
"docker inspect --format='{{.State.Health.Status}}' #{Shellwords.escape(container)}",
|
|
158
|
+
accept: nil, both: true
|
|
159
|
+
)[0]&.strip
|
|
160
|
+
break if status == 'healthy'
|
|
161
|
+
raise(IOError, "PostgreSQL container #{container.inspect} is #{status}") if status == 'unhealthy'
|
|
162
|
+
sleep(0.5)
|
|
163
|
+
end
|
|
155
164
|
container
|
|
156
165
|
end
|
|
157
166
|
|
|
@@ -185,13 +194,17 @@ class Pgtk::PgsqlTask < Rake::TaskLib
|
|
|
185
194
|
puts("PostgreSQL killed in PID #{pid}") unless @quiet
|
|
186
195
|
end
|
|
187
196
|
begin
|
|
188
|
-
WaitUtil.wait_for_service('PG in local', 'localhost', port, timeout_sec: 10, delay_sec: 0.
|
|
197
|
+
WaitUtil.wait_for_service('PG in local', 'localhost', port, timeout_sec: 10, delay_sec: 0.2)
|
|
189
198
|
rescue WaitUtil::TimeoutError => e
|
|
190
199
|
puts("+ #{cmd}")
|
|
191
200
|
puts("stdout:\n#{File.read(File.join(home, 'stdout.txt'))}")
|
|
192
201
|
puts("stderr:\n#{File.read(File.join(home, 'stderr.txt'))}")
|
|
193
202
|
raise(IOError, "Failed to start PostgreSQL database server on port #{port}: #{e.message}")
|
|
194
203
|
end
|
|
204
|
+
(10 * 2).times do
|
|
205
|
+
break if qbash("pg_isready -h localhost -p #{port}", accept: nil, both: true)[1].zero?
|
|
206
|
+
sleep(0.5)
|
|
207
|
+
end
|
|
195
208
|
qbash(
|
|
196
209
|
'createdb',
|
|
197
210
|
'--host', 'localhost',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
|
|
8
|
+
class Pgtk::Pool; end
|
|
9
|
+
|
|
10
|
+
# Raised when no connection becomes available from the pool within
|
|
11
|
+
# the configured timeout. Indicates that all connections are currently
|
|
12
|
+
# taken by other threads and none was returned in time.
|
|
13
|
+
#
|
|
14
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
15
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
16
|
+
# License:: MIT
|
|
17
|
+
class Pgtk::Pool::Busy < StandardError; end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../../pgtk'
|
|
7
|
+
require_relative 'busy'
|
|
8
|
+
|
|
9
|
+
class Pgtk::Pool; end
|
|
10
|
+
|
|
11
|
+
# Thread-safe queue implementation that supports iteration.
|
|
12
|
+
# Unlike Ruby's Queue class, this implementation allows safe iteration
|
|
13
|
+
# over all elements while maintaining thread safety for concurrent access.
|
|
14
|
+
#
|
|
15
|
+
# This class is used internally by Pool to store database connections
|
|
16
|
+
# and provide the ability to iterate over them for inspection purposes.
|
|
17
|
+
#
|
|
18
|
+
# The queue is bounded by size. When an item is taken out, it remains in
|
|
19
|
+
# the internal array but is marked as "taken". When returned, it's placed
|
|
20
|
+
# back in its original slot and marked as available.
|
|
21
|
+
#
|
|
22
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
23
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
24
|
+
# License:: MIT
|
|
25
|
+
class Pgtk::Pool::IterableQueue
|
|
26
|
+
def initialize(size, timeout)
|
|
27
|
+
@size = size
|
|
28
|
+
@timeout = timeout
|
|
29
|
+
@items = []
|
|
30
|
+
@taken = []
|
|
31
|
+
@free = []
|
|
32
|
+
@mutex = Mutex.new
|
|
33
|
+
@condition = ConditionVariable.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def push(item)
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
if @items.size < @size
|
|
39
|
+
@items << item
|
|
40
|
+
@taken << false
|
|
41
|
+
@free << (@items.size - 1)
|
|
42
|
+
else
|
|
43
|
+
index = @items.index(item)
|
|
44
|
+
if index.nil?
|
|
45
|
+
index = @taken.index(true)
|
|
46
|
+
raise(StandardError, 'No taken slot found') if index.nil?
|
|
47
|
+
@items[index] = item
|
|
48
|
+
end
|
|
49
|
+
@taken[index] = false
|
|
50
|
+
@free << index
|
|
51
|
+
end
|
|
52
|
+
@condition.signal
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def pop
|
|
57
|
+
@mutex.synchronize do
|
|
58
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
|
|
59
|
+
while @free.empty?
|
|
60
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
61
|
+
if remaining <= 0
|
|
62
|
+
raise(Pgtk::Pool::Busy, "No free connection appeared in the pool after #{@timeout}s of waiting")
|
|
63
|
+
end
|
|
64
|
+
@condition.wait(@mutex, remaining)
|
|
65
|
+
end
|
|
66
|
+
index = @free.shift
|
|
67
|
+
@taken[index] = true
|
|
68
|
+
@items[index]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def size
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
@items.size
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def map(&)
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
@items.map(&)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require 'tago'
|
|
7
|
+
require_relative '../../pgtk'
|
|
8
|
+
|
|
9
|
+
class Pgtk::Pool; end
|
|
10
|
+
|
|
11
|
+
# A temporary class to execute a single SQL request.
|
|
12
|
+
#
|
|
13
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
14
|
+
# Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
|
|
15
|
+
# License:: MIT
|
|
16
|
+
class Pgtk::Pool::Txn
|
|
17
|
+
def initialize(conn, log)
|
|
18
|
+
@conn = conn
|
|
19
|
+
@log = log
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Exec a single parameterized command.
|
|
23
|
+
# @param [String] query The SQL query with params inside (possibly)
|
|
24
|
+
# @param [Array] args List of arguments
|
|
25
|
+
# @param [Integer] result Should be 0 for text results, 1 for binary
|
|
26
|
+
# @yield [Hash] Rows
|
|
27
|
+
def exec(query, args = [], result = 0)
|
|
28
|
+
start = Time.now
|
|
29
|
+
sql = query.is_a?(Array) ? query.join(' ') : query
|
|
30
|
+
@conn.instance_variable_set(:@pgtk_last_query, sql)
|
|
31
|
+
@conn.instance_variable_set(:@pgtk_started_at, start)
|
|
32
|
+
begin
|
|
33
|
+
out =
|
|
34
|
+
if args.empty?
|
|
35
|
+
@conn.exec(sql) do |res|
|
|
36
|
+
if block_given?
|
|
37
|
+
yield(res)
|
|
38
|
+
else
|
|
39
|
+
res.each.to_a
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
@conn.exec_params(sql, args, result) do |res|
|
|
44
|
+
if block_given?
|
|
45
|
+
yield(res)
|
|
46
|
+
else
|
|
47
|
+
res.each.to_a
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
@log.error("#{sql} -> #{e.message}")
|
|
53
|
+
raise(e)
|
|
54
|
+
end
|
|
55
|
+
if (Time.now - start) < 1
|
|
56
|
+
@log.debug("#{sql} >> #{start.ago} / #{@conn.object_id}")
|
|
57
|
+
else
|
|
58
|
+
@log.info("#{sql} >> #{start.ago}")
|
|
59
|
+
end
|
|
60
|
+
out
|
|
61
|
+
end
|
|
62
|
+
end
|