class PhusionPassenger::AbstractServerCollection
This class is thread-safe as long as the specified thread-safety rules are followed.
collection of Railz::ApplicationSpawner objects.
and Railz::ApplicationSpawner objects, while Railz::FrameworkSpawner maintains a
of functionality. SpawnManager maintains a collection of Railz::FrameworkSpawner
This class exists because both SpawnManager and Railz::FrameworkSpawner need this kind
AbstractServers that have been idle for too long.
AbstractServerCollection also automatically takes care of cleaning up
AbstractServer objects, or look up existing ones via a key.
This class maintains a collection of AbstractServer objects. One can add new
def check_idle_servers!
of sleeping until the next scheduled cleaning time.
Tell the cleaner thread to check the collection as soon as possible, instead
def check_idle_servers! @lock.synchronize do @next_cleaning_time = Time.now - 60 * 60 @cond.signal end end
def cleaner_thread_main
def cleaner_thread_main while !@done current_time = Time.now # We add a 0.2 seconds delay to the sleep time because system # timers are not entirely accurate. sleep_time = (@next_cleaning_time - current_time).to_f + 0.2 if sleep_time > 0 && @cond.timed_wait(@lock, sleep_time) next else keys_to_delete = nil @next_cleaning_time = nil @collection.each_pair do |key, server| if eligable_for_cleanup?(server) # Cleanup this server if its idle timeout has expired. if server.next_cleaning_time <= current_time keys_to_delete ||= [] keys_to_delete << key if server.started? server.stop end # If not, then calculate the next cleaning time because # we're iterating the collection anyway. elsif @next_cleaning_time.nil? || server.next_cleaning_time < @next_cleaning_time @next_cleaning_time = server.next_cleaning_time end end end if keys_to_delete keys_to_delete.each do |key| @collection.delete(key) end end if @next_cleaning_time.nil? # There are no servers in the collection with an idle timeout. @next_cleaning_time = Time.now + 60 * 60 end end end end
def cleanup
unusable.
After calling this method, this AbstractServerCollection object will become
necessary. The background thread which removes idle AbstractServers will be stopped.
from the collection will be deleted. Each AbstractServer will be stopped, if
Cleanup all resources used by this AbstractServerCollection. All AbstractServers
def cleanup @cleanup_lock.synchronize do return if @done @lock.synchronize do @done = true @cond.signal end @cleaner_thread.join clear end end
def clear
stopped, if necessary.
Delete all AbstractServers from the collection. Each AbstractServer will be
def clear @collection.each_value do |server| if server.started? server.stop end end @collection.clear @next_cleaning_time = nil end
def delete(key)
If the AbstractServer is started, then it will be stopped before deletion.
given key. If no such AbstractServer exists, nothing will happen.
Deletes from the collection the AbstractServer that's associated with the
def delete(key) raise ArgumentError, "cleanup() has already been called." if @done server = @collection[key] if server if server.started? server.stop end @collection.delete(key) if server.next_cleaning_time == @next_cleaning_time @next_cleaning_time = nil end end end
def each
Iterate over all AbstractServer objects.
def each each_pair do |key, server| yield server end end
def each_pair
Iterate over all keys and associated AbstractServer objects.
def each_pair raise ArgumentError, "cleanup() has already been called." if @done @collection.each_pair do |key, server| yield(key, server) end end
def eligable_for_cleanup?(server)
def eligable_for_cleanup?(server) return server.max_idle_time && server.max_idle_time != 0 end
def empty?
Checks whether the collection is empty.
def empty? return @collection.empty? end
def has_key?(key)
Checks whether there's an AbstractServer object associated with the given key.
def has_key?(key) return @collection.has_key?(key) end
def initialize
def initialize @collection = {} @lock = Mutex.new @cleanup_lock = Mutex.new @cond = ConditionVariable.new @done = false # The next time the cleaner thread should check for idle servers. # The value may be nil, in which case the value will be calculated # at the end of the #synchronized block. # # Invariant: # if value is not nil: # There exists an s in @collection with s.next_cleaning_time == value. # for all s in @collection: # if eligable_for_cleanup?(s): # s.next_cleaning_time <= value @next_cleaning_time = Time.now + 60 * 60 @next_cleaning_time_changed = false @cleaner_thread = Thread.new do begin @lock.synchronize do cleaner_thread_main end rescue Exception => e print_exception(self.class.to_s, e) end end end
def lookup_or_add(key)
and the exception will be propagated.
If the block raises an exception, then the collection will not be modified,
A max_idle_time value of nil or 0 means the AbstractServer will never be idle cleaned.
effect on the idle cleaning interval.
with this. Changing the value outside this block is not guaranteed to have any
AbstractServerCollection's idle cleaning interval will be adapted to accomodate
The block must set the 'max_idle_time' attribute on the AbstractServer.
that object will be stored in the collection, and returned.
block will be called. That block must return an AbstractServer object. Then,
If there is no AbstractSerer associated with the given key, then the given
Lookup and returns an AbstractServer with the given key.
def lookup_or_add(key) raise ArgumentError, "cleanup() has already been called." if @done server = @collection[key] if server register_activity(server) return server else server = yield if !server.respond_to?(:start) raise TypeError, "The block didn't return a valid AbstractServer object." end if eligable_for_cleanup?(server) server.next_cleaning_time = Time.now + server.max_idle_time if @next_cleaning_time && server.next_cleaning_time < @next_cleaning_time @next_cleaning_time = server.next_cleaning_time @next_cleaning_time_changed = true end end @collection[key] = server return server end end
def register_activity(server)
not close to the time at which lookup_or_add had been called.
call this method if the time at which the server has performed an activity is
lookup_or_add already automatically updates idle information, so you only need to
accordingly.
This AbstractServerCollection will update the idle information associated with +server+
Notify this AbstractServerCollection that +server+ has performed an activity.
def register_activity(server) if eligable_for_cleanup?(server) if server.next_cleaning_time == @next_cleaning_time @next_cleaning_time = nil end server.next_cleaning_time = Time.now + server.max_idle_time end end
def synchronize
the code within the block. The entire block will be a single atomic
Acquire the lock for this AbstractServerCollection object, and run
def synchronize @lock.synchronize do yield if @next_cleaning_time.nil? @collection.each_value do |server| if @next_cleaning_time.nil? || (eligable_for_cleanup?(server) && server.next_cleaning_time < @next_cleaning_time ) @next_cleaning_time = server.next_cleaning_time end end if @next_cleaning_time.nil? # There are no servers in the collection with an idle timeout. @next_cleaning_time = Time.now + 60 * 60 end @next_cleaning_time_changed = true end if @next_cleaning_time_changed @next_cleaning_time_changed = false @cond.signal end end end