Files
metasploit-gs/lib/msf/core/thread_manager.rb
T
James Lee 876c59b192 Make use of the new ActiveRecord 3.x concurrency contract
All Database usage must go through framework.db (which should have been
the case before, anyways) or explicitly checkout and checkin a
connection.  Failure to do so causes thread starvation and bizarre
random failures when attempting to use the database.

This commit also explicitly releases database connections at the end of
all threads created via framework.threads.spawn, which should alleviate
Deprecation Warning messages from ActiveRecord.

[Fixes #6613]
2012-04-19 14:21:21 -06:00

182 lines
4.5 KiB
Ruby

require 'msf/core/plugin'
=begin
require 'active_record'
#
# This monkeypatch can help to diagnose errors involving connection pool
# exhaustion and other strange ActiveRecord including errors like:
#
# DEPRECATION WARNING: Database connections will not be closed automatically, please close your
# database connection at the end of the thread by calling `close` on your
# connection. For example: ActiveRecord::Base.connection.close
#
# and
#
# ActiveRecord::StatementInvalid NoMethodError: undefined method `fields' for nil:NilClass: SELECT "workspaces".* FROM "workspaces" WHERE "workspaces"."id" = 24 LIMIT 1
#
#
# Based on this code: https://gist.github.com/1364551 linked here:
# http://bibwild.wordpress.com/2011/11/14/multi-threading-in-rails-activerecord-3-0-3-1/
module ActiveRecord
class Base
class << self
def connection
unless connection_pool.active_connection?
$stdout.puts("AR::B.connection implicit checkout")
$stdout.puts(caller.join("\n"))
raise ImplicitConnectionForbiddenError.new("Implicit ActiveRecord checkout attempted!")
end
retrieve_connection
end
end
end
class ImplicitConnectionForbiddenError < ActiveRecord::ConnectionTimeoutError ; end
end
=end
module Msf
###
#
# This class manages the threads spawned by the framework object, this provides some additional
# features over standard ruby threads.
#
###
class ThreadManager < Array
include Framework::Offspring
attr_accessor :monitor
#
# Initializes the thread manager.
#
def initialize(framework)
self.framework = framework
self.monitor = spawn_monitor
end
#
# Spawns a monitor thread for removing dead threads
#
def spawn_monitor
::Thread.new do
begin
::Thread.current[:tm_name] = "Thread Monitor"
::Thread.current[:tm_crit] = true
while true
::IO.select(nil, nil, nil, 1.0)
self.each_index do |i|
state = self[i].alive? rescue false
self[i] = nil if not state
end
self.delete(nil)
end
rescue ::Exception => e
elog("thread monitor: #{e} #{e.backtrace} source:#{self[:tm_call].inspect}")
end
end
end
#
# Spawns a new thread
#
def spawn(name, crit, *args, &block)
t = nil
if block
t = ::Thread.new(name, crit, caller, block, *args) do |*argv|
::Thread.current[:tm_name] = argv.shift.to_s
::Thread.current[:tm_crit] = argv.shift
::Thread.current[:tm_call] = argv.shift
::Thread.current[:tm_time] = Time.now
begin
argv.shift.call(*argv)
rescue ::Exception => e
elog("thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} error:#{e.class} #{e} source:#{::Thread.current[:tm_call].inspect}")
elog("Call Stack\n#{e.backtrace.join("\n")}")
raise e
ensure
if framework.db and framework.db.active
# NOTE: despite the Deprecation Warning's advice, this should *NOT*
# be ActiveRecord::Base.connection.close which causes unrelated
# threads to raise ActiveRecord::StatementInvalid exceptions at
# some point in the future, presumably due to the pool manager
# believing that the connection is still usable and handing it out
# to another thread.
::ActiveRecord::Base.connection_pool.release_connection
end
end
end
else
t = ::Thread.new(name, crit, caller, *args) do |*argv|
::Thread.current[:tm_name] = argv.shift
::Thread.current[:tm_crit] = argv.shift
::Thread.current[:tm_call] = argv.shift
::Thread.current[:tm_time] = Time.now
# Calling spawn without a block means we cannot force a database
# connection release when the thread completes, so doing so can
# potentially use up all database resources and starve all subsequent
# threads that make use of the database. Log a warning so we can track
# down this kind of usage.
dlog("Thread spawned without a block!")
dlog("Call stack: \n#{::Thread.current[:tm_call].join("\n")}")
end
end
self << t
t
end
#
# Registers an existing thread
#
def register(t, name, crit)
t[:tm_name] = name
t[:tm_crit] = crit
t[:tm_call] = caller
t[:tm_time] = Time.now
self << t
t
end
#
# Updates an existing thread
#
def update(ut, name, crit)
ti = nil
self.each_index do |i|
tt = self[i]
next if not tt
if ut.__id__ == tt.__id__
ti = i
break
end
end
t = self[ti]
if not t
raise RuntimeError, "Thread not found"
end
t[:tm_name] = name
t[:tm_crit] = crit
t
end
#
# Kills a thread by index
#
def kill(idx)
self[idx].kill rescue false
end
end
end