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 ClassicRails::FrameworkSpawner need this kind of functionality. SpawnManager maintains a collection of ClassicRails::FrameworkSpawner and ClassicRails::ApplicationSpawner objects, while ClassicRails::FrameworkSpawner maintains a collection of ClassicRails::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 208
    208:         def check_idle_servers!
    209:                 must_not_be_in_synchronize_block
    210:                 @lock.synchronize do
    211:                         @next_cleaning_time = Time.now - 60 * 60
    212:                         @cond.signal
    213:                 end
    214:         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 260
    260:         def cleanup
    261:                 must_not_be_in_synchronize_block
    262:                 @cleanup_lock.synchronize do
    263:                         return if @done
    264:                         @lock.synchronize do
    265:                                 @done = true
    266:                                 @cond.signal
    267:                         end
    268:                         @cleaner_thread.join
    269:                         synchronize do
    270:                                 clear
    271:                         end
    272:                 end
    273:         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 241
    241:         def clear
    242:                 must_be_in_synchronize_block
    243:                 @collection.each_value do |server|
    244:                         if server.started?
    245:                                 server.stop
    246:                         end
    247:                 end
    248:                 @collection.clear
    249:                 @next_cleaning_time = nil
    250:         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 170
    170:         def delete(key)
    171:                 raise ArgumentError, "cleanup() has already been called." if @done
    172:                 must_be_in_synchronize_block
    173:                 server = @collection[key]
    174:                 if server
    175:                         if server.started?
    176:                                 server.stop
    177:                         end
    178:                         @collection.delete(key)
    179:                         if server.next_cleaning_time == @next_cleaning_time
    180:                                 @next_cleaning_time = nil
    181:                         end
    182:                 end
    183:         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 219
    219:         def each
    220:                 must_be_in_synchronize_block
    221:                 each_pair do |key, server|
    222:                         yield server
    223:                 end
    224:         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 229
    229:         def each_pair
    230:                 raise ArgumentError, "cleanup() has already been called." if @done
    231:                 must_be_in_synchronize_block
    232:                 @collection.each_pair do |key, server|
    233:                         yield(key, server)
    234:                 end
    235:         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 159
    159:         def empty?
    160:                 must_be_in_synchronize_block
    161:                 return @collection.empty?
    162:         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 151
    151:         def has_key?(key)
    152:                 must_be_in_synchronize_block
    153:                 return @collection.has_key?(key)
    154:         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 124
    124:         def lookup_or_add(key)
    125:                 raise ArgumentError, "cleanup() has already been called." if @done
    126:                 must_be_in_synchronize_block
    127:                 server = @collection[key]
    128:                 if server
    129:                         register_activity(server)
    130:                         return server
    131:                 else
    132:                         server = yield
    133:                         if !server.respond_to?(:start)
    134:                                 raise TypeError, "The block didn't return a valid AbstractServer object."
    135:                         end
    136:                         if eligable_for_cleanup?(server)
    137:                                 server.next_cleaning_time = Time.now + server.max_idle_time
    138:                                 if @next_cleaning_time && server.next_cleaning_time < @next_cleaning_time
    139:                                         @next_cleaning_time = server.next_cleaning_time
    140:                                         @next_cleaning_time_changed = true
    141:                                 end
    142:                         end
    143:                         @collection[key] = server
    144:                         return server
    145:                 end
    146:         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 194
    194:         def register_activity(server)
    195:                 must_be_in_synchronize_block
    196:                 if eligable_for_cleanup?(server)
    197:                         if server.next_cleaning_time == @next_cleaning_time
    198:                                 @next_cleaning_time = nil
    199:                         end
    200:                         server.next_cleaning_time = Time.now + server.max_idle_time
    201:                 end
    202:         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:                         @in_synchronize_block = true
     81:                         begin
     82:                                 yield
     83:                         ensure
     84:                                 if @next_cleaning_time.nil?
     85:                                         @collection.each_value do |server|
     86:                                                 if @next_cleaning_time.nil? ||
     87:                                                    (eligable_for_cleanup?(server) &&
     88:                                                     server.next_cleaning_time < @next_cleaning_time
     89:                                                    )
     90:                                                         @next_cleaning_time = server.next_cleaning_time
     91:                                                 end
     92:                                         end
     93:                                         if @next_cleaning_time.nil?
     94:                                                 # There are no servers in the collection with an idle timeout.
     95:                                                 @next_cleaning_time = Time.now + 60 * 60
     96:                                         end
     97:                                         @next_cleaning_time_changed = true
     98:                                 end
     99:                                 if @next_cleaning_time_changed
    100:                                         @next_cleaning_time_changed = false
    101:                                         @cond.signal
    102:                                 end
    103:                                 @in_synchronize_block = false
    104:                         end
    105:                 end
    106:         end