pgtk 0.22.0 → 0.23.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 902941bbf98d37acfad72c9615282264148e6ad6ce1183f2fcd085d3a0cf288e
4
- data.tar.gz: 00a5488c8dd99f98df1ebce8cef20207514964a1628df8489bd320d0c199ca77
3
+ metadata.gz: 57546e93aaf2c029a795f61302d943e4f64e7d1e03b77d339636bdfb73d271d5
4
+ data.tar.gz: 467c0493e69e8402b83ea6b537ac69760d5e712ac088b856307d2418449878f9
5
5
  SHA512:
6
- metadata.gz: 47d6993b709ebce71a8cd1525f94230b8c554a8c2edd871ca67627ce6e6724d46ea3530827fa3cb653ae54b56cf4299cb4291197d3f870e602942ffc0b7b6134
7
- data.tar.gz: cbe8bf7eebf1c038d71079845355d805bc647be88e62523db89180ce9f39e4299a779f104ee648a1f4399f18f90a0e4f3aab371f277df964dec843299b5b733d
6
+ metadata.gz: 83b61249a1888eed186c7faa7814c9a07f28fa9268d8d7fad1dfc0b9d7f4b304d4c835116fce6e32b6e651d7a2fc12731417c52922daae8ad9759419b32d2344
7
+ data.tar.gz: 5107f90f035a11e3ab17c8718f759c6869ea24d3c195dafb1845d546587244ad1e6f82dca7da5b505055cefdafa97f7b0e6390f17374e2458f773e54f7f9dd75
data/Gemfile CHANGED
@@ -16,6 +16,7 @@ gem 'rubocop-performance', '~>1.25', require: false
16
16
  gem 'rubocop-rake', '~>0.7', require: false
17
17
  gem 'simplecov', '~>0.22', require: false
18
18
  gem 'simplecov-cobertura', '~>3.0'
19
+ gem 'threads', '~>0.4'
19
20
  gem 'timeout', '~>0.4'
20
21
  gem 'xcop', '~>0.8', require: false
21
22
  gem 'yard', '~>0.9', require: false
data/Gemfile.lock CHANGED
@@ -102,6 +102,9 @@ GEM
102
102
  simplecov_json_formatter (0.1.4)
103
103
  slop (4.10.1)
104
104
  tago (0.3.0)
105
+ threads (0.4.1)
106
+ backtrace (~> 0)
107
+ concurrent-ruby (~> 1.0)
105
108
  timeout (0.4.4)
106
109
  unicode-display_width (3.2.0)
107
110
  unicode-emoji (~> 4.1)
@@ -132,6 +135,7 @@ DEPENDENCIES
132
135
  rubocop-rake (~> 0.7)
133
136
  simplecov (~> 0.22)
134
137
  simplecov-cobertura (~> 3.0)
138
+ threads (~> 0.4)
135
139
  timeout (~> 0.4)
136
140
  xcop (~> 0.8)
137
141
  yard (~> 0.9)
data/README.md CHANGED
@@ -115,8 +115,8 @@ From inside your app you may find this class useful:
115
115
 
116
116
  ```ruby
117
117
  require 'pgtk/pool'
118
- pgsql = Pgtk::Pool.new(Pgtk::Wire::Yaml.new('config.yml'))
119
- pgsql.start!(5) # Start it with five simultaneous connections
118
+ pgsql = Pgtk::Pool.new(Pgtk::Wire::Yaml.new('config.yml'), max: 5)
119
+ pgsql.start! # Start it with five simultaneous connections
120
120
  ```
121
121
 
122
122
  You can also let it pick the connection parameters from the environment
@@ -19,8 +19,8 @@ require_relative '../pgtk'
19
19
  # Basic usage:
20
20
  #
21
21
  # # Create and configure a regular pool
22
- # pool = Pgtk::Pool.new(wire)
23
- # pool.start!(4)
22
+ # pool = Pgtk::Pool.new(wire, max: 4)
23
+ # pool.start!
24
24
  #
25
25
  # # Wrap the pool in an impatient decorator with a 2-second timeout
26
26
  # impatient = Pgtk::Impatient.new(pool, 2)
@@ -69,8 +69,8 @@ class Pgtk::Impatient
69
69
  end
70
70
 
71
71
  # Start a new connection pool with the given arguments.
72
- def start!(*)
73
- @pool.start!(*)
72
+ def start!
73
+ @pool.start!
74
74
  end
75
75
 
76
76
  # Get the version of PostgreSQL server.
data/lib/pgtk/pool.rb CHANGED
@@ -29,8 +29,8 @@ require_relative 'wire'
29
29
  # )
30
30
  #
31
31
  # # Create and start a connection pool with 4 connections
32
- # pool = Pgtk::Pool.new(wire)
33
- # pool.start!(4)
32
+ # pool = Pgtk::Pool.new(wire, max: 4)
33
+ # pool.start!
34
34
  #
35
35
  # # Execute a simple query
36
36
  # pool.exec('SELECT * FROM users')
@@ -51,11 +51,14 @@ class Pgtk::Pool
51
51
  # Constructor.
52
52
  #
53
53
  # @param [Pgtk::Wire] wire The wire
54
+ # @param [Integer] max Total amount of PostgreSQL connections in the pool
54
55
  # @param [Object] log The log
55
- def initialize(wire, log: Loog::NULL)
56
+ def initialize(wire, max: 8, log: Loog::NULL)
56
57
  @wire = wire
58
+ @max = max
57
59
  @log = log
58
- @pool = IterableQueue.new
60
+ @pool = IterableQueue.new(max)
61
+ @started = false
59
62
  end
60
63
 
61
64
  # Get the version of PostgreSQL server.
@@ -74,7 +77,42 @@ class Pgtk::Pool
74
77
  " PgSQL version: #{version}",
75
78
  " #{@pool.size} connections:",
76
79
  @pool.map do |c|
77
- " ##{c.backend_pid} #{c.pipeline_status} #{c.status} #{c.transaction_status}"
80
+ [
81
+ ' ',
82
+ "##{c.backend_pid}",
83
+ case c.pipeline_status
84
+ when PG::Constants::PQ_PIPELINE_ON
85
+ 'ON'
86
+ when PG::Constants::PQ_PIPELINE_OFF
87
+ 'OFF'
88
+ when PG::Constants::PQ_PIPELINE_ABORTED
89
+ 'ABORTED'
90
+ else
91
+ "pipeline_status=#{c.pipeline_status}"
92
+ end,
93
+ case c.status
94
+ when PG::Constants::CONNECTION_OK
95
+ 'OK'
96
+ when PG::Constants::CONNECTION_BAD
97
+ 'BAD'
98
+ else
99
+ "status=#{c.status}"
100
+ end,
101
+ case c.transaction_status
102
+ when PG::Constants::PQTRANS_IDLE
103
+ 'IDLE'
104
+ when PG::Constants::PQTRANS_ACTIVE
105
+ 'ACTIVE'
106
+ when PG::Constants::PQTRANS_INTRANS
107
+ 'INTRANS'
108
+ when PG::Constants::PQTRANS_INERROR
109
+ 'INERROR'
110
+ when PG::Constants::PQTRANS_UNKNOWN
111
+ 'UNKNOWN'
112
+ else
113
+ "transaction_status=#{c.transaction_status}"
114
+ end
115
+ ].join(' ')
78
116
  end
79
117
  ].flatten.join("\n")
80
118
  end
@@ -85,13 +123,13 @@ class Pgtk::Pool
85
123
  # keep in mind that not all servers will allow you to have many connections
86
124
  # open at the same time. For example, Heroku free PostgreSQL database
87
125
  # allows only one connection open.
88
- #
89
- # @param [Integer] max Total amount of PostgreSQL connections in the pool
90
- def start!(max = 8)
91
- max.times do
126
+ def start!
127
+ return if @started
128
+ @max.times do
92
129
  @pool << @wire.connection
93
130
  end
94
- @log.debug("PostgreSQL pool started with #{max} connections")
131
+ @started = true
132
+ @log.debug("PostgreSQL pool started with #{@max} connections")
95
133
  end
96
134
 
97
135
  # Make a query and return the result as an array of hashes. For example,
@@ -184,24 +222,43 @@ class Pgtk::Pool
184
222
  #
185
223
  # This class is used internally by Pool to store database connections
186
224
  # and provide the ability to iterate over them for inspection purposes.
225
+ #
226
+ # The queue is bounded by size. When an item is taken out, it remains in
227
+ # the internal array but is marked as "taken". When returned, it's placed
228
+ # back in its original slot and marked as available.
187
229
  class IterableQueue
188
- def initialize
230
+ def initialize(size)
231
+ @size = size
189
232
  @items = []
233
+ @taken = []
190
234
  @mutex = Mutex.new
191
235
  @condition = ConditionVariable.new
192
236
  end
193
237
 
194
238
  def <<(item)
195
239
  @mutex.synchronize do
196
- @items << item
240
+ if @items.size < @size
241
+ @items << item
242
+ @taken << false
243
+ else
244
+ index = @items.index(item)
245
+ if index.nil?
246
+ index = @taken.index(true)
247
+ raise 'No taken slot found' if index.nil?
248
+ @items[index] = item
249
+ end
250
+ @taken[index] = false
251
+ end
197
252
  @condition.signal
198
253
  end
199
254
  end
200
255
 
201
256
  def pop
202
257
  @mutex.synchronize do
203
- @condition.wait(@mutex) while @items.empty?
204
- @items.shift
258
+ @condition.wait(@mutex) while @taken.all? || @items.empty?
259
+ index = @taken.index(false)
260
+ @taken[index] = true
261
+ @items[index]
205
262
  end
206
263
  end
207
264
 
data/lib/pgtk/retry.rb CHANGED
@@ -16,8 +16,8 @@ require_relative '../pgtk'
16
16
  # Basic usage:
17
17
  #
18
18
  # # Create and configure a regular pool
19
- # pool = Pgtk::Pool.new(wire)
20
- # pool.start!(4)
19
+ # pool = Pgtk::Pool.new(wire, max: 4)
20
+ # pool.start!
21
21
  #
22
22
  # # Wrap the pool in a retry decorator with 3 attempts
23
23
  # retry_pool = Pgtk::Retry.new(pool, attempts: 3)
@@ -58,8 +58,8 @@ class Pgtk::Retry
58
58
  end
59
59
 
60
60
  # Start a new connection pool with the given arguments.
61
- def start!(*)
62
- @pool.start!(*)
61
+ def start!
62
+ @pool.start!
63
63
  end
64
64
 
65
65
  # Get the version of PostgreSQL server.
data/lib/pgtk/spy.rb CHANGED
@@ -19,8 +19,8 @@ require_relative 'wire'
19
19
  # Basic usage:
20
20
  #
21
21
  # # Create and configure a regular pool
22
- # pool = Pgtk::Pool.new(wire)
23
- # pool.start!(4)
22
+ # pool = Pgtk::Pool.new(wire, max: 4)
23
+ # pool.start!
24
24
  #
25
25
  # # Wrap the pool in a spy that tracks all executed queries
26
26
  # queries = []
@@ -57,8 +57,8 @@ class Pgtk::Spy
57
57
  end
58
58
 
59
59
  # Start a new connection pool with the given arguments.
60
- def start!(*)
61
- @pool.start!(*)
60
+ def start!
61
+ @pool.start!
62
62
  end
63
63
 
64
64
  # Get the version of PostgreSQL server.
data/lib/pgtk/stash.rb CHANGED
@@ -60,12 +60,13 @@ class Pgtk::Stash
60
60
  @max_queue_length = max_queue_length
61
61
  @threads = threads
62
62
  @loog = loog
63
+ @tpool = Concurrent::FixedThreadPool.new(@threads)
63
64
  end
64
65
 
65
66
  # Start a new connection pool with the given arguments.
66
- def start!(*)
67
+ def start!
67
68
  launch!
68
- @pool.start!(*)
69
+ @pool.start!
69
70
  end
70
71
 
71
72
  # Get the PostgreSQL server version.
@@ -90,9 +91,12 @@ class Pgtk::Stash
90
91
  '',
91
92
  "Pgtk::Stash (refill_interval=#{@refill_interval}s, max_queue_length=#{@max_queue_length}, threads=#{@threads}):",
92
93
  " #{'not ' if @launched.false?}launched",
93
- " #{@stash[:tables].count} tables in cache",
94
- " #{@stash[:queries].count} queries in cache:",
95
- qq.sort_by { -_1[2] }.take(64).map { |a| " #{a[1]}/#{a[2]}p/#{a[3]}s: #{a[0]}" }
94
+ " #{@tpool.queue_length} task(s) in the thread pool",
95
+ " #{@stash[:tables].count} table(s) in cache",
96
+ " #{qq.sum { |a| a[3] }} stale quer(ies) in cache:",
97
+ qq.select { |a| a[3].positive? }.sort_by { -_1[2] }.take(16).map { |a| " #{a[1]}/#{a[2]}p/#{a[3]}s: #{a[0]}" },
98
+ " #{qq.count { |a| a[3].zero? }} other quer(ies) in cache:",
99
+ qq.select { |a| a[3].zero? }.sort_by { -_1[2] }.take(8).map { |a| " #{a[1]}/#{a[2]}p/#{a[3]}s: #{a[0]}" }
96
100
  ].join("\n")
97
101
  end
98
102
 
@@ -132,7 +136,7 @@ class Pgtk::Stash
132
136
  @stash[:tables][t].append(pure).uniq!
133
137
  end
134
138
  @stash[:queries][pure] ||= {}
135
- @stash[:queries][pure][key] = { ret:, params:, result: }
139
+ @stash[:queries][pure][key] = { ret:, params:, result:, used: Time.now }
136
140
  end
137
141
  end
138
142
  end
@@ -140,6 +144,7 @@ class Pgtk::Stash
140
144
  @entrance.with_write_lock do
141
145
  @stash[:queries][pure][key][:popularity] ||= 0
142
146
  @stash[:queries][pure][key][:popularity] += 1
147
+ @stash[:queries][pure][key][:used] = Time.now
143
148
  end
144
149
  end
145
150
  end
@@ -171,34 +176,31 @@ class Pgtk::Stash
171
176
 
172
177
  def launch!
173
178
  raise 'Cannot launch multiple times on same cache data' unless @launched.make_true
174
- Concurrent::FixedThreadPool.new(@threads).then do |tpool|
175
- Concurrent::TimerTask.execute(execution_interval: 60 * 60, executor: tpool) do
176
- @entrance.with_write_lock do
177
- @stash[:queries].each_key do |q|
178
- @stash[:queries][q].each_key do |k|
179
- @stash[:queries][q][k][:popularity] = 0
180
- end
181
- end
179
+ retire = 60 * 60
180
+ Concurrent::TimerTask.execute(execution_interval: retire, executor: @tpool) do
181
+ @entrance.with_write_lock do
182
+ @stash[:queries].each_key do |q|
183
+ @stash[:queries][q].delete_if { |_, h| h[:used] < Time.now - retire }
182
184
  end
183
185
  end
184
- Concurrent::TimerTask.execute(execution_interval: @refill_interval, executor: tpool) do
185
- @stash[:queries]
186
- .map { |k, v| [k, v.values.sum { |vv| vv[:popularity] }, v.values.any? { |vv| vv[:stale] }] }
187
- .select { _1[2] }
188
- .sort_by { -_1[1] }
189
- .each do |a|
190
- q = a[0]
191
- @stash[:queries][q].each_key do |k|
192
- next unless @stash[:queries][q][k][:stale]
193
- next if tpool.queue_length >= @max_queue_length
194
- tpool.post do
186
+ end
187
+ Concurrent::TimerTask.execute(execution_interval: @refill_interval, executor: @tpool) do
188
+ @stash[:queries]
189
+ .map { |k, v| [k, v.values.sum { |vv| vv[:popularity] }, v.values.any? { |vv| vv[:stale] }] }
190
+ .select { _1[2] }
191
+ .sort_by { -_1[1] }
192
+ .each do |a|
193
+ q = a[0]
194
+ @stash[:queries][q].each_key do |k|
195
+ next unless @stash[:queries][q][k][:stale]
196
+ next if @tpool.queue_length >= @max_queue_length
197
+ @tpool.post do
198
+ h = @stash[:queries][q][k]
199
+ ret = @pool.exec(q, h[:params], h[:result])
200
+ @entrance.with_write_lock do
195
201
  h = @stash[:queries][q][k]
196
- ret = @pool.exec(q, h[:params], h[:result])
197
- @entrance.with_write_lock do
198
- h = @stash[:queries][q][k]
199
- h[:stale] = false
200
- h[:ret] = ret
201
- end
202
+ h[:stale] = false
203
+ h[:ret] = ret
202
204
  end
203
205
  end
204
206
  end
data/lib/pgtk/version.rb CHANGED
@@ -11,5 +11,5 @@ require_relative '../pgtk'
11
11
  # License:: MIT
12
12
  module Pgtk
13
13
  # Current version of the library.
14
- VERSION = '0.22.0'
14
+ VERSION = '0.23.0'
15
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgtk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko