Class
PhusionPassenger::AbstractServerCollection

In: | lib/phusion_passenger/abstract_server_collection.rb
Parent: | Object

This class maintains a collection of AbstractServer objects. One can add new AbstractServer objects, or look up existing ones via a key. AbstractServerCollection also automatically takes care of cleaning up AbstractServers that have been idle for too long.

This class exists because both SpawnManager and Railz::FrameworkSpawner need this kind of functionality. SpawnManager maintains a collection of Railz::FrameworkSpawner and Railz::ApplicationSpawner objects, while Railz::FrameworkSpawner maintains a collection of Railz::ApplicationSpawner objects.

This class is thread-safe as long as the specified thread-safety rules are followed.

Methods

- check_idle_servers!
- cleanup
- clear
- delete
- each
- each_pair
- empty?
- has_key?
- lookup_or_add
- new
- register_activity
- synchronize

Included Modules

- Utils

Attributes

[R] | next_cleaning_time |

Public Class methods
new()

[ show source ]

        # File lib/phusion_passenger/abstract_server_collection.rb, line 44
    44:         def initialize
    45:                 @collection = {}
    46:                 @lock = Mutex.new
    47:                 @cleanup_lock = Mutex.new
    48:                 @cond = ConditionVariable.new
    49:                 @done = false
    50:
    51:                 # The next time the cleaner thread should check for idle servers.
    52:                 # The value may be nil, in which case the value will be calculated
    53:                 # at the end of the #synchronized block.
    54:                 #
    55:                 # Invariant:
    56:                 #    if value is not nil:
    57:                 #       There exists an s in @collection with s.next_cleaning_time == value.
    58:                 #       for all s in @collection:
    59:                 #          if eligable_for_cleanup?(s):
    60:                 #             s.next_cleaning_time <= value
    61:                 @next_cleaning_time = Time.now + 60 * 60
    62:                 @next_cleaning_time_changed = false
    63:
    64:                 @cleaner_thread = Thread.new do
    65:                         begin
    66:                                 @lock.synchronize do
    67:                                         cleaner_thread_main
    68:                                 end
    69:                         rescue Exception => e
    70:                                 print_exception(self.class.to_s, e)
    71:                         end
    72:                 end
    73:         end

Public Instance methods
check_idle_servers!()

Tell the cleaner thread to check the collection as soon as possible, instead of sleeping until the next scheduled cleaning time.

Precondition: this method must NOT be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 198
    198:         def check_idle_servers!
    199:                 @lock.synchronize do
    200:                         @next_cleaning_time = Time.now - 60 * 60
    201:                         @cond.signal
    202:                 end
    203:         end

cleanup()

Cleanup all resources used by this AbstractServerCollection. All AbstractServers from the collection will be deleted. Each AbstractServer will be stopped, if necessary. The background thread which removes idle AbstractServers will be stopped.

After calling this method, this AbstractServerCollection object will become unusable.

Precondition: this method must NOT be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 246
    246:         def cleanup
    247:                 @cleanup_lock.synchronize do
    248:                         return if @done
    249:                         @lock.synchronize do
    250:                                 @done = true
    251:                                 @cond.signal
    252:                         end
    253:                         @cleaner_thread.join
    254:                         clear
    255:                 end
    256:         end

clear()

Delete all AbstractServers from the collection. Each AbstractServer will be stopped, if necessary.

Precondition: this method must be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 228
    228:         def clear
    229:                 @collection.each_value do |server|
    230:                         if server.started?
    231:                                 server.stop
    232:                         end
    233:                 end
    234:                 @collection.clear
    235:                 @next_cleaning_time = nil
    236:         end

delete(key)

Deletes from the collection the AbstractServer that‘s associated with the given key. If no such AbstractServer exists, nothing will happen.

If the AbstractServer is started, then it will be stopped before deletion.

Precondition: this method must be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 162
    162:         def delete(key)
    163:                 raise ArgumentError, "cleanup() has already been called." if @done
    164:                 server = @collection[key]
    165:                 if server
    166:                         if server.started?
    167:                                 server.stop
    168:                         end
    169:                         @collection.delete(key)
    170:                         if server.next_cleaning_time == @next_cleaning_time
    171:                                 @next_cleaning_time = nil
    172:                         end
    173:                 end
    174:         end

each() {|server| ...}

Iterate over all AbstractServer objects.

Precondition: this method must be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 208
    208:         def each
    209:                 each_pair do |key, server|
    210:                         yield server
    211:                 end
    212:         end

each_pair() {|key, server| ...}

Iterate over all keys and associated AbstractServer objects.

Precondition: this method must be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 217
    217:         def each_pair
    218:                 raise ArgumentError, "cleanup() has already been called." if @done
    219:                 @collection.each_pair do |key, server|
    220:                         yield(key, server)
    221:                 end
    222:         end

empty?()

Checks whether the collection is empty.

Precondition: this method must be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 152
    152:         def empty?
    153:                 return @collection.empty?
    154:         end

has_key?(key)

Checks whether there‘s an AbstractServer object associated with the given key.

Precondition: this method must be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 145
    145:         def has_key?(key)
    146:                 return @collection.has_key?(key)
    147:         end

lookup_or_add(key) {|| ...}

Lookup and returns an AbstractServer with the given key.

If there is no AbstractSerer associated with the given key, then the given block will be called. That block must return an AbstractServer object. Then, that object will be stored in the collection, and returned.

The block must set the ‘max_idle_time’ attribute on the AbstractServer. AbstractServerCollection‘s idle cleaning interval will be adapted to accomodate with this. Changing the value outside this block is not guaranteed to have any effect on the idle cleaning interval. A max_idle_time value of nil or 0 means the AbstractServer will never be idle cleaned.

If the block raises an exception, then the collection will not be modified, and the exception will be propagated.

Precondition: this method must be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 119
    119:         def lookup_or_add(key)
    120:                 raise ArgumentError, "cleanup() has already been called." if @done
    121:                 server = @collection[key]
    122:                 if server
    123:                         register_activity(server)
    124:                         return server
    125:                 else
    126:                         server = yield
    127:                         if !server.respond_to?(:start)
    128:                                 raise TypeError, "The block didn't return a valid AbstractServer object."
    129:                         end
    130:                         if eligable_for_cleanup?(server)
    131:                                 server.next_cleaning_time = Time.now + server.max_idle_time
    132:                                 if @next_cleaning_time && server.next_cleaning_time < @next_cleaning_time
    133:                                         @next_cleaning_time = server.next_cleaning_time
    134:                                         @next_cleaning_time_changed = true
    135:                                 end
    136:                         end
    137:                         @collection[key] = server
    138:                         return server
    139:                 end
    140:         end

register_activity(server)

Notify this AbstractServerCollection that `server` has performed an activity. This AbstractServerCollection will update the idle information associated with `server` accordingly.

lookup_or_add already automatically updates idle information, so you only need to call this method if the time at which the server has performed an activity is not close to the time at which lookup_or_add had been called.

Precondition: this method must be called within a #synchronize block.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 185
    185:         def register_activity(server)
    186:                 if eligable_for_cleanup?(server)
    187:                         if server.next_cleaning_time == @next_cleaning_time
    188:                                 @next_cleaning_time = nil
    189:                         end
    190:                         server.next_cleaning_time = Time.now + server.max_idle_time
    191:                 end
    192:         end

synchronize() {|| ...}

Acquire the lock for this AbstractServerCollection object, and run the code within the block. The entire block will be a single atomic operation.

[ show source ]

         # File lib/phusion_passenger/abstract_server_collection.rb, line 78
     78:         def synchronize
     79:                 @lock.synchronize do
     80:                         yield
     81:                         if @next_cleaning_time.nil?
     82:                                 @collection.each_value do |server|
     83:                                         if @next_cleaning_time.nil? ||
     84:                                            (eligable_for_cleanup?(server) &&
     85:                                             server.next_cleaning_time < @next_cleaning_time
     86:                                            )
     87:                                                 @next_cleaning_time = server.next_cleaning_time
     88:                                         end
     89:                                 end
     90:                                 if @next_cleaning_time.nil?
     91:                                         # There are no servers in the collection with an idle timeout.
     92:                                         @next_cleaning_time = Time.now + 60 * 60
     93:                                 end
     94:                                 @next_cleaning_time_changed = true
     95:                         end
     96:                         if @next_cleaning_time_changed
     97:                                 @next_cleaning_time_changed = false
     98:                                 @cond.signal
     99:                         end
    100:                 end
    101:         end