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 39
39:         def initialize
40:                 @collection = {}
41:                 @lock = Mutex.new
42:                 @cleanup_lock = Mutex.new
43:                 @cond = ConditionVariable.new
44:                 @done = false
45:                 
46:                 # The next time the cleaner thread should check for idle servers.
47:                 # The value may be nil, in which case the value will be calculated
48:                 # at the end of the #synchronized block.
49:                 #
50:                 # Invariant:
51:                 #    if value is not nil:
52:                 #       There exists an s in @collection with s.next_cleaning_time == value.
53:                 #       for all s in @collection:
54:                 #          if eligable_for_cleanup?(s):
55:                 #             s.next_cleaning_time <= value
56:                 @next_cleaning_time = Time.now + 60 * 60
57:                 @next_cleaning_time_changed = false
58:                 
59:                 @cleaner_thread = Thread.new do
60:                         begin
61:                                 @lock.synchronize do
62:                                         cleaner_thread_main
63:                                 end
64:                         rescue Exception => e
65:                                 print_exception(self.class.to_s, e)
66:                         end
67:                 end
68:         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 193
193:         def check_idle_servers!
194:                 @lock.synchronize do
195:                         @next_cleaning_time = Time.now - 60 * 60
196:                         @cond.signal
197:                 end
198:         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 241
241:         def cleanup
242:                 @cleanup_lock.synchronize do
243:                         return if @done
244:                         @lock.synchronize do
245:                                 @done = true
246:                                 @cond.signal
247:                         end
248:                         @cleaner_thread.join
249:                         clear
250:                 end
251:         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 223
223:         def clear
224:                 @collection.each_value do |server|
225:                         if server.started?
226:                                 server.stop
227:                         end
228:                 end
229:                 @collection.clear
230:                 @next_cleaning_time = nil
231:         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 157
157:         def delete(key)
158:                 raise ArgumentError, "cleanup() has already been called." if @done
159:                 server = @collection[key]
160:                 if server
161:                         if server.started?
162:                                 server.stop
163:                         end
164:                         @collection.delete(key)
165:                         if server.next_cleaning_time == @next_cleaning_time
166:                                 @next_cleaning_time = nil
167:                         end
168:                 end
169:         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 203
203:         def each
204:                 each_pair do |key, server|
205:                         yield server
206:                 end
207:         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 212
212:         def each_pair
213:                 raise ArgumentError, "cleanup() has already been called." if @done
214:                 @collection.each_pair do |key, server|
215:                         yield(key, server)
216:                 end
217:         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 147
147:         def empty?
148:                 return @collection.empty?
149:         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 140
140:         def has_key?(key)
141:                 return @collection.has_key?(key)
142:         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 114
114:         def lookup_or_add(key)
115:                 raise ArgumentError, "cleanup() has already been called." if @done
116:                 server = @collection[key]
117:                 if server
118:                         register_activity(server)
119:                         return server
120:                 else
121:                         server = yield
122:                         if !server.respond_to?(:start)
123:                                 raise TypeError, "The block didn't return a valid AbstractServer object."
124:                         end
125:                         if eligable_for_cleanup?(server)
126:                                 server.next_cleaning_time = Time.now + server.max_idle_time
127:                                 if @next_cleaning_time && server.next_cleaning_time < @next_cleaning_time
128:                                         @next_cleaning_time = server.next_cleaning_time
129:                                         @next_cleaning_time_changed = true
130:                                 end
131:                         end
132:                         @collection[key] = server
133:                         return server
134:                 end
135:         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 180
180:         def register_activity(server)
181:                 if eligable_for_cleanup?(server)
182:                         if server.next_cleaning_time == @next_cleaning_time
183:                                 @next_cleaning_time = nil
184:                         end
185:                         server.next_cleaning_time = Time.now + server.max_idle_time
186:                 end
187:         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 73
73:         def synchronize
74:                 @lock.synchronize do
75:                         yield
76:                         if @next_cleaning_time.nil?
77:                                 @collection.each_value do |server|
78:                                         if @next_cleaning_time.nil? ||
79:                                            (eligable_for_cleanup?(server) &&
80:                                             server.next_cleaning_time < @next_cleaning_time
81:                                            )
82:                                                 @next_cleaning_time = server.next_cleaning_time
83:                                         end
84:                                 end
85:                                 if @next_cleaning_time.nil?
86:                                         # There are no servers in the collection with an idle timeout.
87:                                         @next_cleaning_time = Time.now + 60 * 60
88:                                 end
89:                                 @next_cleaning_time_changed = true
90:                         end
91:                         if @next_cleaning_time_changed
92:                                 @next_cleaning_time_changed = false
93:                                 @cond.signal
94:                         end
95:                 end
96:         end