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-2025 Yegor Bugayenko

License

MIT

Defined Under Namespace

Classes: TooSlow

Instance Method Summary collapse

Constructor Details

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

Constructor.

Parameters:

  • pool (Pgtk::Pool)

    The pool to decorate

  • timeout (Integer)

    Timeout in seconds for each SQL query

  • off (Array<Regex>)

    List of regex to exclude queries from checking



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

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

Instance Method Details

#dumpObject

Convert internal state into text.



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

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.

Parameters:

  • query (String, Array)

    The SQL query with params inside (possibly)

  • args (Array)

    List of arguments

Returns:

  • (Array)

    Result rows

Raises:

  • (Timeout::Error)

    If the query takes too long



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

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'
    ].compact.join(' ')
  end
end

#start!Object

Start a new connection pool with the given arguments.



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

def start!
  @pool.start!
end

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

Run a transaction with a timeout for each query.

Yields:

Returns:

  • (Object)

    Result of the block



124
125
126
127
128
129
# File 'lib/pgtk/impatient.rb', line 124

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.

Returns:

  • (String)

    Version of PostgreSQL server



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

def version
  @pool.version
end