2018-11-20 02:01:13 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-01-15 15:21:04 +00:00
|
|
|
module Gitlab
|
|
|
|
module QueryLimiting
|
|
|
|
class Transaction
|
|
|
|
THREAD_KEY = :__gitlab_query_counts_transaction
|
|
|
|
|
2021-03-16 18:11:53 +00:00
|
|
|
attr_accessor :count
|
2018-01-15 15:21:04 +00:00
|
|
|
|
|
|
|
# The name of the action (e.g. `UsersController#show`) that is being
|
|
|
|
# executed.
|
|
|
|
attr_accessor :action
|
|
|
|
|
|
|
|
# The maximum number of SQL queries that can be executed in a request. For
|
|
|
|
# the sake of keeping things simple we hardcode this value here, it's not
|
|
|
|
# supposed to be changed very often anyway.
|
|
|
|
THRESHOLD = 100
|
2021-03-08 00:09:10 +00:00
|
|
|
LOG_THRESHOLD = THRESHOLD * 1.5
|
2018-01-15 15:21:04 +00:00
|
|
|
|
|
|
|
# Error that is raised whenever exceeding the maximum number of queries.
|
|
|
|
ThresholdExceededError = Class.new(StandardError)
|
|
|
|
|
|
|
|
def self.current
|
|
|
|
Thread.current[THREAD_KEY]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Starts a new transaction and returns it and the blocks' return value.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# transaction, retval = Transaction.run do
|
|
|
|
# 10
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# retval # => 10
|
|
|
|
def self.run
|
|
|
|
transaction = new
|
|
|
|
Thread.current[THREAD_KEY] = transaction
|
|
|
|
|
|
|
|
[transaction, yield]
|
|
|
|
ensure
|
|
|
|
Thread.current[THREAD_KEY] = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
@action = nil
|
|
|
|
@count = 0
|
2021-03-08 00:09:10 +00:00
|
|
|
@sql_executed = []
|
2018-01-15 15:21:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Sends a notification based on the number of executed SQL queries.
|
|
|
|
def act_upon_results
|
|
|
|
return unless threshold_exceeded?
|
|
|
|
|
|
|
|
error = ThresholdExceededError.new(error_message)
|
|
|
|
|
2018-02-13 15:40:23 +00:00
|
|
|
raise(error) if raise_error?
|
2018-01-15 15:21:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def increment
|
2021-03-16 18:11:53 +00:00
|
|
|
@count += 1 if enabled?
|
2018-01-15 15:21:04 +00:00
|
|
|
end
|
|
|
|
|
2021-03-08 00:09:10 +00:00
|
|
|
def executed_sql(sql)
|
|
|
|
@sql_executed << sql if @count <= LOG_THRESHOLD
|
|
|
|
end
|
|
|
|
|
2018-01-15 15:21:04 +00:00
|
|
|
def raise_error?
|
|
|
|
Rails.env.test?
|
|
|
|
end
|
|
|
|
|
|
|
|
def threshold_exceeded?
|
|
|
|
count > THRESHOLD
|
|
|
|
end
|
|
|
|
|
|
|
|
def error_message
|
|
|
|
header = 'Too many SQL queries were executed'
|
2018-11-20 02:01:13 +00:00
|
|
|
header = "#{header} in #{action}" if action
|
2021-03-08 00:09:10 +00:00
|
|
|
msg = "a maximum of #{THRESHOLD} is allowed but #{count} SQL queries were executed"
|
|
|
|
log = @sql_executed.each_with_index.map { |sql, i| "#{i}: #{sql}" }.join("\n").presence
|
|
|
|
ellipsis = '...' if @count > LOG_THRESHOLD
|
2018-01-15 15:21:04 +00:00
|
|
|
|
2021-03-08 00:09:10 +00:00
|
|
|
["#{header}: #{msg}", log, ellipsis].compact.join("\n")
|
2018-01-15 15:21:04 +00:00
|
|
|
end
|
2021-03-16 18:11:53 +00:00
|
|
|
|
|
|
|
def enabled?
|
|
|
|
::Gitlab::QueryLimiting.enabled?
|
|
|
|
end
|
2018-01-15 15:21:04 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|