pgtk 0.31.4 → 0.31.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3a43722aa806c7c496234f00c186abac8cf07dfeb48e9baa97353fca08a1e0a
4
- data.tar.gz: 63e903b575092c0bb62bf25b5aa96004723f4c4312bf3e16c4b5fc2af0b2b0c7
3
+ metadata.gz: 117996e543551102eebf83be228996818dbfdbfc324f7f45b5d346a57d39fc1a
4
+ data.tar.gz: 22bd0738c88e60ed29ad3310a87755709dc4512d6b92932c88442253c29847f3
5
5
  SHA512:
6
- metadata.gz: d03c3c8458643fc95801a51b6d2fd4a61808ff934f2ac4341ea02b779d8ef688f44283f079b69a8d8bd02dc1d2733c07af4549dfcf8b41e3acceb5402083223e
7
- data.tar.gz: 842c436ab8180751b612b55a5a0f113b3db10a101c1db9dd3adf1150ac2b5961c3baa5654a7d8c4cc44ccb788c77e495c0523a235ef0e2275f6c4b12729a8fa6
6
+ metadata.gz: 0ae1185f452400e2f2f984e5266bdb9c6b8dc7d92b798eb714d18903949f230d8707911b562431984f4fa52178dc52198bfeac5b24d2aaeff918ee0b36804541
7
+ data.tar.gz: 4b8fc6853d556967173556f0067a6c611b4a93be0440c31659b0892f7d944606fbc19f15e27b1361b06cacea99e268bb81008893dfcc910e56103c10d0b9c42c
data/lib/pgtk/pool.rb CHANGED
@@ -4,6 +4,7 @@ require 'loog'
4
4
  # SPDX-FileCopyrightText: Copyright (c) 2019-2026 Yegor Bugayenko
5
5
  # SPDX-License-Identifier: MIT
6
6
 
7
+ require 'ellipsized'
7
8
  require 'pg'
8
9
  require 'tago'
9
10
  require_relative '../pgtk'
@@ -84,46 +85,7 @@ class Pgtk::Pool
84
85
  " Pgtk version: #{Pgtk::VERSION}",
85
86
  " PgSQL version: #{version}",
86
87
  " #{@pool.size} connections:",
87
- @pool.map do |c|
88
- [
89
- ' ',
90
- "##{c.backend_pid}",
91
- case c.pipeline_status
92
- when PG::Constants::PQ_PIPELINE_ON
93
- 'ON'
94
- when PG::Constants::PQ_PIPELINE_OFF
95
- 'OFF'
96
- when PG::Constants::PQ_PIPELINE_ABORTED
97
- 'ABORTED'
98
- else
99
- "pipeline_status=#{c.pipeline_status}"
100
- end,
101
- case c.status
102
- when PG::Constants::CONNECTION_OK
103
- 'OK'
104
- when PG::Constants::CONNECTION_BAD
105
- 'BAD'
106
- else
107
- "status=#{c.status}"
108
- end,
109
- case c.transaction_status
110
- when PG::Constants::PQTRANS_IDLE
111
- 'IDLE'
112
- when PG::Constants::PQTRANS_ACTIVE
113
- 'ACTIVE'
114
- when PG::Constants::PQTRANS_INTRANS
115
- 'INTRANS'
116
- when PG::Constants::PQTRANS_INERROR
117
- 'INERROR'
118
- when PG::Constants::PQTRANS_UNKNOWN
119
- 'UNKNOWN'
120
- else
121
- "transaction_status=#{c.transaction_status}"
122
- end
123
- ].join(' ')
124
- rescue PG::ConnectionBad => e
125
- e.message
126
- end
88
+ @pool.map { |conn| info(conn) }
127
89
  ].flatten.join("\n")
128
90
  end
129
91
 
@@ -247,6 +209,7 @@ class Pgtk::Pool
247
209
  @timeout = timeout
248
210
  @items = []
249
211
  @taken = []
212
+ @free = []
250
213
  @mutex = Mutex.new
251
214
  @condition = ConditionVariable.new
252
215
  end
@@ -256,6 +219,7 @@ class Pgtk::Pool
256
219
  if @items.size < @size
257
220
  @items << item
258
221
  @taken << false
222
+ @free << (@items.size - 1)
259
223
  else
260
224
  index = @items.index(item)
261
225
  if index.nil?
@@ -264,6 +228,7 @@ class Pgtk::Pool
264
228
  @items[index] = item
265
229
  end
266
230
  @taken[index] = false
231
+ @free << index
267
232
  end
268
233
  @condition.signal
269
234
  end
@@ -272,12 +237,12 @@ class Pgtk::Pool
272
237
  def pop
273
238
  @mutex.synchronize do
274
239
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
275
- while @taken.all? || @items.empty?
240
+ while @free.empty?
276
241
  remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
277
242
  raise(Busy, "No free connection appeared in the pool after #{@timeout}s of waiting") if remaining <= 0
278
243
  @condition.wait(@mutex, remaining)
279
244
  end
280
- index = @taken.index(false)
245
+ index = @free.shift
281
246
  @taken[index] = true
282
247
  @items[index]
283
248
  end
@@ -311,6 +276,8 @@ class Pgtk::Pool
311
276
  def exec(query, args = [], result = 0)
312
277
  start = Time.now
313
278
  sql = query.is_a?(Array) ? query.join(' ') : query
279
+ @conn.instance_variable_set(:@pgtk_last_query, sql)
280
+ @conn.instance_variable_set(:@pgtk_started_at, start)
314
281
  begin
315
282
  out =
316
283
  if args.empty?
@@ -348,32 +315,85 @@ class Pgtk::Pool
348
315
 
349
316
  def connect
350
317
  conn = @pool.pop
351
- conn = renew(conn) if dead?(conn)
352
318
  begin
353
- yield(conn)
354
- rescue StandardError => e
319
+ reason = cause(conn)
320
+ if reason
321
+ begin
322
+ conn = renew(conn, reason)
323
+ rescue StandardError => e
324
+ @log.warn("Failed to renew dead connection (#{reason}): #{e.message}")
325
+ end
326
+ end
355
327
  begin
356
- conn = renew(conn)
357
- rescue StandardError => re
358
- @log.warn("Failed to renew connection after #{e.message}: #{re.message}")
328
+ yield(conn)
329
+ rescue StandardError => e
330
+ begin
331
+ conn = renew(conn, "query failed: #{e.message.strip}")
332
+ rescue StandardError => re
333
+ @log.warn("Failed to renew connection after #{e.message}: #{re.message}")
334
+ end
335
+ raise(e)
359
336
  end
360
- raise(e)
361
337
  ensure
362
338
  @pool.push(conn)
363
339
  end
364
340
  end
365
341
 
366
- def dead?(conn)
367
- conn.finished? ||
368
- conn.status == PG::Constants::CONNECTION_BAD ||
369
- conn.transaction_status != PG::Constants::PQTRANS_IDLE
370
- rescue StandardError
371
- true
342
+ def cause(conn)
343
+ return 'finished' if conn.finished?
344
+ return 'status BAD' if conn.status == PG::Constants::CONNECTION_BAD
345
+ return "transaction status #{conn.transaction_status}" if conn.transaction_status != PG::Constants::PQTRANS_IDLE
346
+ nil
347
+ rescue StandardError => e
348
+ "inspection failed: #{e.message.strip}"
349
+ end
350
+
351
+ def info(conn)
352
+ pipelines = { PG::Constants::PQ_PIPELINE_ON => 'ON', PG::Constants::PQ_PIPELINE_OFF => 'OFF',
353
+ PG::Constants::PQ_PIPELINE_ABORTED => 'ABORTED' }
354
+ statuses = { PG::Constants::CONNECTION_OK => 'OK', PG::Constants::CONNECTION_BAD => 'BAD' }
355
+ transactions = { PG::Constants::PQTRANS_IDLE => 'IDLE', PG::Constants::PQTRANS_ACTIVE => 'ACTIVE',
356
+ PG::Constants::PQTRANS_INTRANS => 'INTRANS', PG::Constants::PQTRANS_INERROR => 'INERROR',
357
+ PG::Constants::PQTRANS_UNKNOWN => 'UNKNOWN' }
358
+ conn.instance_variable_set(:@pgtk_pid, conn.backend_pid)
359
+ parts = [
360
+ ' ',
361
+ "##{conn.backend_pid}",
362
+ pipelines.fetch(conn.pipeline_status, "pipeline_status=#{conn.pipeline_status}"),
363
+ statuses.fetch(conn.status, "status=#{conn.status}"),
364
+ transactions.fetch(conn.transaction_status, "transaction_status=#{conn.transaction_status}")
365
+ ]
366
+ if conn.transaction_status != PG::Constants::PQTRANS_IDLE
367
+ started = conn.instance_variable_get(:@pgtk_started_at)
368
+ parts << started.ago if started
369
+ end
370
+ if conn.transaction_status == PG::Constants::PQTRANS_ACTIVE
371
+ running = conn.instance_variable_get(:@pgtk_last_query)
372
+ parts << "running: #{running.gsub(/\s+/, ' ').strip.ellipsized(60)}" if running
373
+ end
374
+ parts.join(' ')
375
+ rescue PG::ConnectionBad => e
376
+ pid = conn.instance_variable_get(:@pgtk_pid)
377
+ parts = [' ']
378
+ parts << (pid ? "##{pid}" : '#?')
379
+ parts << e.message.gsub(/\s+/, ' ').strip
380
+ closed = conn.instance_variable_get(:@pgtk_closed_at)
381
+ parts << "#{closed.ago} ago" if closed
382
+ reason = conn.instance_variable_get(:@pgtk_closed_reason)
383
+ parts << "because: #{reason.gsub(/\s+/, ' ').strip}" if reason
384
+ last = conn.instance_variable_get(:@pgtk_last_query)
385
+ parts << "last query: #{last.gsub(/\s+/, ' ').strip.ellipsized(60)}" if last
386
+ "#{parts.shift} #{parts.shift} #{parts.join(', ')}"
372
387
  end
373
388
 
374
- def renew(conn)
389
+ def renew(conn, reason)
375
390
  begin
376
- conn.close unless conn.finished?
391
+ unless conn.finished?
392
+ conn.instance_variable_set(:@pgtk_pid, conn.backend_pid)
393
+ conn.instance_variable_set(:@pgtk_closed_at, Time.now)
394
+ conn.instance_variable_set(:@pgtk_closed_reason, reason.gsub(/\s+/, ' ').strip)
395
+ conn.close
396
+ end
377
397
  rescue StandardError => e
378
398
  @log.warn("Failed to close connection: #{e.message}")
379
399
  end
data/lib/pgtk/retry.rb CHANGED
@@ -85,11 +85,14 @@ class Pgtk::Retry
85
85
  ].join("\n")
86
86
  end
87
87
 
88
- # Execute a SQL query with automatic retry for SELECT queries only.
89
- # Non-SELECT queries fail on the first error, since a failure may occur
90
- # after the server received the query but before the acknowledgement
91
- # reached the client, and retrying a non-idempotent write could duplicate
92
- # it.
88
+ # Execute a SQL query with automatic retry on transient failures.
89
+ # SELECT queries are retried on any error, since reads are idempotent.
90
+ # Non-SELECT queries are retried only on PG::ConnectionBad, since by
91
+ # definition the query never reached the server, so retrying cannot
92
+ # duplicate a write. Other errors on writes propagate immediately,
93
+ # because a failure may occur after the server received the query but
94
+ # before the acknowledgement reached the client, and retrying a
95
+ # non-idempotent write could duplicate it.
93
96
  #
94
97
  # @param [String] sql The SQL query with params inside (possibly)
95
98
  # @return [Array] Result rows
@@ -98,6 +101,10 @@ class Pgtk::Retry
98
101
  attempt = 0
99
102
  begin
100
103
  @pool.exec(sql, *)
104
+ rescue PG::ConnectionBad => e
105
+ attempt += 1
106
+ raise(Exhausted, "Retry gave up after #{@attempts} attempts: #{e.message}") if attempt >= @attempts
107
+ retry
101
108
  rescue StandardError, Pgtk::Impatient::TooSlow => e
102
109
  raise(e) unless query.strip.upcase.start_with?('SELECT')
103
110
  attempt += 1
data/lib/pgtk/version.rb CHANGED
@@ -10,5 +10,5 @@ require_relative '../pgtk'
10
10
  # Copyright:: Copyright (c) 2019-2026 Yegor Bugayenko
11
11
  # License:: MIT
12
12
  module Pgtk
13
- VERSION = '0.31.4' unless defined?(VERSION)
13
+ VERSION = '0.31.6' unless defined?(VERSION)
14
14
  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.31.4
4
+ version: 0.31.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko