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
Included Modules
Attributes
[R] next_cleaning_time
Public Class methods
new()
    # 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.

     # 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.

     # 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.

     # 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.

     # 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.

     # 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.

     # 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.

     # 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.

     # 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.

     # 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.

     # 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.

     # 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