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

Included Modules

Utils

Attributes

next_cleaning_time  [R] 

Public Class methods

[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

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.

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

[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

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

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

[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

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.

[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

Iterate over all AbstractServer objects.

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

[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

Iterate over all keys and associated AbstractServer objects.

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

[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

Checks whether the collection is empty.

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

[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

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

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

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

[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

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.

[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

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

[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

[Validate]