discourse/config/initializers/000-trace_pg_connections.rb

88 lines
2.6 KiB
Ruby

# frozen_string_literal: true
# Setting TRACE_PG_CONNECTIONS=1 will cause all pg connections
# to be streamed to files for debugging. The filenames are formatted
# like tmp/pgtrace/{{PID}}_{{CONNECTION_OBJECT_ID}}.txt
#
# Setting TRACE_PG_CONNECTIONS=SIDEKIQ will only trace connections
# on in sidekiq (safer, because there will be minimal user-facing perf impact)
#
# Files will be automatically deleted when the connection is closed gracefully
# (e.g. when activerecord closes it after a period of inactivity)
# Files will not be automatically deleted when closed abruptly
# (e.g. terminating/restarting the app process)
#
# Warning: this could create some very large files!
if ENV["TRACE_PG_CONNECTIONS"]
PG::Connection.prepend(Module.new do
TRACE_DIR = "tmp/pgtrace"
def initialize(*args)
super(*args).tap do
next if ENV["TRACE_PG_CONNECTIONS"] == "SIDEKIQ" && !Sidekiq.server?
FileUtils.mkdir_p(TRACE_DIR)
@trace_filename = "#{TRACE_DIR}/#{Process.pid}_#{self.object_id}.txt"
trace File.new(@trace_filename, "w")
end
@access_log_mutex = Mutex.new
@accessor_thread = nil
end
def close
super.tap do
next if ENV["TRACE_PG_CONNECTIONS"] == "SIDEKIQ" && !Sidekiq.server?
File.delete(@trace_filename)
end
end
def log_access(&blk)
@access_log_mutex.synchronize do
if !@accessor_thread.nil?
Rails.logger.error <<~STRING
PG Clash: A connection is being accessed from two locations
#{@accessor_thread} was using the connection. Backtrace:
#{@accessor_thread&.backtrace&.join("\n")}
#{Thread.current} is now attempting to use the connection. Backtrace:
#{Thread.current&.backtrace&.join("\n")}
STRING
if ENV["ON_PG_CLASH"] == "byebug"
require "byebug"
byebug # rubocop:disable Lint/Debugger
end
end
@accessor_thread = Thread.current
end
yield
ensure
@access_log_mutex.synchronize do
@accessor_thread = nil
end
end
end)
class PG::Connection
LOG_ACCESS_METHODS = [:exec, :sync_exec, :async_exec,
:sync_exec_params, :async_exec_params,
:sync_prepare, :async_prepare,
:sync_exec_prepared, :async_exec_prepared,
]
LOG_ACCESS_METHODS.each do |method|
new_method = "#{method}_without_logging".to_sym
alias_method new_method, method
define_method(method) do |*args, &blk|
log_access { send(new_method, *args, &blk) }
end
end
end
end