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).start(4)

# 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



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

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

Instance Method Details

#exec(sql, *args) ⇒ Array

Execute a SQL query with a timeout.

Parameters:

  • sql (String)

    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



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/pgtk/impatient.rb', line 83

def exec(sql, *args)
  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

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

Run a transaction with a timeout for each query.

Yields:

Returns:

  • (Object)

    Result of the block



107
108
109
110
111
112
# File 'lib/pgtk/impatient.rb', line 107

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



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

def version
  @pool.version
end