Class: Pgtk::Impatient

Inherits:
Object
  • Object
show all
Defined in:
lib/pgtk/impatient.rb

Overview

Impatient is a decorator for Pool that enforces timeouts on all database operations. It ensures that SQL queries don’t run indefinitely, which helps prevent application hangs and resource exhaustion when database operations are slow or stalled.

This class implements the same interface as Pool but wraps each database operation in a timeout block. If a query exceeds the specified timeout, it raises a Timeout::Error exception, allowing the application to handle slow queries gracefully.

Basic usage:

# Create and configure a regular pool
pool = Pgtk::Pool.new(wire, max: 4)
pool.start!

# Wrap the pool in an impatient decorator with a 2-second timeout
impatient = Pgtk::Impatient.new(pool, 2)

# Execute queries with automatic timeout enforcement
begin
  impatient.exec('SELECT * FROM large_table WHERE complex_condition')
rescue Timeout::Error
  puts "Query timed out after 2 seconds"
end

# Transactions also enforce timeouts on each query
begin
  impatient.transaction do |t|
    t.exec('UPDATE large_table SET processed = true')
    t.exec('DELETE FROM queue WHERE processed = true')
  end
rescue Timeout::Error
  puts "Transaction timed out"
end

# Combining with Spy for timeout monitoring
spy = Pgtk::Spy.new(impatient) do |sql, duration|
  puts "Query completed in #{duration} seconds: #{sql}"
end

# Now queries are both timed and monitored
spy.exec('SELECT * FROM users')
Author

Yegor Bugayenko ([email protected])

Copyright

Copyright © 2019-2026 Yegor Bugayenko

License

MIT

Defined Under Namespace

Classes: TooSlow

Instance Method Summary collapse

Constructor Details

#initialize(pool, timeout, *off) ⇒ Impatient

Constructor.



66
67
68
69
70
# File 'lib/pgtk/impatient.rb', line 66

def initialize(pool, timeout, *off)
  @pool = pool
  @timeout = timeout
  @off = off
end

Instance Method Details

#dumpObject

Convert internal state into text.



85
86
87
88
89
90
91
92
# File 'lib/pgtk/impatient.rb', line 85

def dump
  [
    @pool.dump,
    '',
    "Pgtk::Impatient (timeout=#{@timeout}s):",
    @off.map { |re| "  #{re}" }
  ].join("\n")
end

#exec(query, *args) ⇒ Array

Execute a SQL query with a timeout.

Raises:

  • (Timeout::Error)

    If the query takes too long



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/pgtk/impatient.rb', line 100

def exec(query, *args)
  sql = query.is_a?(Array) ? query.join(' ') : query
  return @pool.exec(sql, *args) if @off.any? { |re| re.match?(sql) }
  start = Time.now
  token = SecureRandom.uuid
  begin
    Timeout.timeout(@timeout, Timeout::Error, token) do
      @pool.exec(sql, *args)
    end
  rescue Timeout::Error => e
    raise e unless e.message == token
    raise TooSlow, [
      'SQL query',
      ("with #{args.count} argument#{'s' if args.count > 1}" unless args.empty?),
      'was terminated after',
      start.ago,
      'of waiting:',
      sql.ellipsized(50).inspect
    ].compact.join(' ')
  end
end

#start!Object

Start a new connection pool with the given arguments.



73
74
75
# File 'lib/pgtk/impatient.rb', line 73

def start!
  @pool.start!
end

#transaction {|Pgtk::Impatient| ... } ⇒ Object

Run a transaction with a timeout for each query.

Yields:



126
127
128
129
130
131
# File 'lib/pgtk/impatient.rb', line 126

def transaction
  @pool.transaction do |t|
    t.exec("SET LOCAL statement_timeout = #{(@timeout * 1000).to_i}")
    yield Pgtk::Impatient.new(t, @timeout)
  end
end

#versionString

Get the version of PostgreSQL server.



80
81
82
# File 'lib/pgtk/impatient.rb', line 80

def version
  @pool.version
end